Completed
Pull Request — master (#769)
by kota
24:30 queued 11:28
created

wordpress.match   A

Complexity

Conditions 3

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
dl 0
loc 10
rs 10
c 0
b 0
f 0
nop 2
1
/* Vuls - Vulnerability Scanner
2
Copyright (C) 2016  Future Corporation , Japan.
3
4
This program is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
package wordpress
19
20
import (
21
	"encoding/json"
22
	"fmt"
23
	"io/ioutil"
24
	"net/http"
25
	"strings"
26
27
	"github.com/future-architect/vuls/models"
28
	"github.com/future-architect/vuls/util"
29
	version "github.com/hashicorp/go-version"
30
	"github.com/pkg/errors"
31
)
32
33
//WpCveInfos is for wpvulndb's json
34
type WpCveInfos struct {
35
	ReleaseDate  string `json:"release_date"`
36
	ChangelogURL string `json:"changelog_url"`
37
	// Status        string `json:"status"`
38
	LatestVersion string `json:"latest_version"`
39
	LastUpdated   string `json:"last_updated"`
40
	// Popular         bool        `json:"popular"`
41
	Vulnerabilities []WpCveInfo `json:"vulnerabilities"`
42
	Error           string      `json:"error"`
43
}
44
45
//WpCveInfo is for wpvulndb's json
46
type WpCveInfo struct {
47
	ID        int    `json:"id"`
48
	Title     string `json:"title"`
49
	CreatedAt string `json:"created_at"`
50
	UpdatedAt string `json:"updated_at"`
51
	// PublishedDate string     `json:"published_date"`
52
	VulnType   string     `json:"vuln_type"`
53
	References References `json:"references"`
54
	FixedIn    string     `json:"fixed_in"`
55
}
56
57
//References is for wpvulndb's json
58
type References struct {
59
	URL     []string `json:"url"`
60
	Cve     []string `json:"cve"`
61
	Secunia []string `json:"secunia"`
62
}
63
64
// FillWordPress access to wpvulndb and fetch scurity alerts and then set to the given ScanResult.
65
// https://wpvulndb.com/
66
func FillWordPress(r *models.ScanResult, token string) (int, error) {
67
	// Core
68
	ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1)
69
	if ver == "" {
70
		return 0, fmt.Errorf("Failed to get WordPress core version")
71
	}
72
	url := fmt.Sprintf("https://wpvulndb.com/api/v3/wordpresses/%s", ver)
73
	body, err := httpRequest(url, token)
74
	if err != nil {
75
		return 0, err
76
	}
77
	wpVinfos, err := convertToVinfos(models.WPCore, string(body))
78
	if err != nil {
79
		return 0, err
80
	}
81
82
	//TODO add a flag ignore inactive plugin or themes such as -wp-ignore-inactive flag to cmd line option or config.toml
83
84
	// Themes
85
	for _, p := range r.WordPressPackages.Themes() {
86
		url := fmt.Sprintf("https://wpvulndb.com/api/v3/themes/%s", p.Name)
87
		body, err := httpRequest(url, token)
88
		if err != nil {
89
			return 0, err
90
		}
91
		if body == "" {
92
			continue
93
		}
94
95
		templateVinfos, err := convertToVinfos(p.Name, string(body))
96
		if err != nil {
97
			return 0, err
98
		}
99
100
		for _, v := range templateVinfos {
101
			for _, fixstat := range v.WpPackageFixStats {
102
				pkg, ok := r.WordPressPackages.Find(fixstat.Name)
103
				if !ok {
104
					continue
105
				}
106
				ok, err := match(pkg.Version, fixstat.FixedIn)
107
				if err != nil {
108
					return 0, errors.Wrap(err, "Not a semantic versionng")
109
				}
110
				if ok {
111
					wpVinfos = append(wpVinfos, v)
112
				} else {
113
					//TODO Debugf
114
					util.Log.Infof("no match %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
115
				}
116
			}
117
		}
118
	}
119
120
	// Plugins
121
	for _, p := range r.WordPressPackages.Plugins() {
122
		url := fmt.Sprintf("https://wpvulndb.com/api/v3/plugins/%s", p.Name)
123
		body, err := httpRequest(url, token)
124
		if err != nil {
125
			return 0, err
126
		}
127
		if body == "" {
128
			continue
129
		}
130
131
		pluginVinfos, err := convertToVinfos(p.Name, string(body))
132
		if err != nil {
133
			return 0, err
134
		}
135
136
		for _, v := range pluginVinfos {
137
			for _, fixstat := range v.WpPackageFixStats {
138
				pkg, ok := r.WordPressPackages.Find(fixstat.Name)
139
				if !ok {
140
					continue
141
				}
142
				ok, err := match(pkg.Version, fixstat.FixedIn)
143
				if err != nil {
144
					return 0, errors.Wrap(err, "Not a semantic versionng")
145
				}
146
				if ok {
147
					wpVinfos = append(wpVinfos, v)
148
				} else {
149
					//TODO Debugf
150
					util.Log.Infof("no match %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
151
				}
152
			}
153
		}
154
	}
155
156
	for _, wpVinfo := range wpVinfos {
157
		if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok {
158
			vinfo.CveContents[models.WPVulnDB] = wpVinfo.CveContents[models.WPVulnDB]
159
			vinfo.WpPackageFixStats = wpVinfo.WpPackageFixStats
160
			r.ScannedCves[wpVinfo.CveID] = vinfo
161
		} else {
162
			r.ScannedCves[wpVinfo.CveID] = wpVinfo
163
		}
164
	}
165
	return len(wpVinfos), nil
166
}
167
168
func match(installedVer, fixedIn string) (bool, error) {
169
	v1, err := version.NewVersion(installedVer)
170
	if err != nil {
171
		return false, err
172
	}
173
	v2, err := version.NewVersion(fixedIn)
174
	if err != nil {
175
		return false, err
176
	}
177
	return v1.LessThan(v2), nil
178
}
179
180
func convertToVinfos(pkgName, body string) (vinfos []models.VulnInfo, err error) {
181
	// "pkgName" : CVE Detailed data
182
	pkgnameCves := map[string]WpCveInfos{}
183
	if err = json.Unmarshal([]byte(body), &pkgnameCves); err != nil {
184
		return nil, errors.Wrap(err, fmt.Sprintf("Failed to unmarshal: %s", body))
185
	}
186
187
	for _, v := range pkgnameCves {
188
		vs := extractToVulnInfos(pkgName, v.Vulnerabilities)
189
		vinfos = append(vinfos, vs...)
190
	}
191
	return vinfos, nil
192
}
193
194
func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnInfo) {
195
	for _, vulnerability := range cves {
196
		if len(vulnerability.References.Cve) == 0 {
197
			//TODO ignore no-cve-vulns for now
198
			continue
199
		}
200
		var cveIDs []string
201
		for _, cveNumber := range vulnerability.References.Cve {
202
			//TODO ignore no-cve-vulns for now
203
			cveIDs = append(cveIDs, "CVE-"+cveNumber)
204
		}
205
206
		var refs []models.Reference
207
		for _, url := range vulnerability.References.URL {
208
			refs = append(refs, models.Reference{
209
				Link: url,
210
			})
211
		}
212
213
		for _, cveID := range cveIDs {
214
			vinfos = append(vinfos, models.VulnInfo{
215
				CveID: cveID,
216
				CveContents: models.NewCveContents(
217
					models.CveContent{
218
						Type:       models.WPVulnDB,
219
						CveID:      cveID,
220
						Title:      vulnerability.Title,
221
						References: refs,
222
					},
223
				),
224
				VulnType: vulnerability.VulnType,
225
				Confidences: []models.Confidence{
226
					models.WPVulnDBMatch,
227
				},
228
				WpPackageFixStats: []models.WpPackageFixStatus{{
229
					Name:    pkgName,
230
					FixedIn: vulnerability.FixedIn,
231
				}},
232
			})
233
		}
234
	}
235
	return
236
}
237
238
func httpRequest(url, token string) (string, error) {
239
	req, err := http.NewRequest("GET", url, nil)
240
	if err != nil {
241
		return "", err
242
	}
243
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
244
	resp, err := new(http.Client).Do(req)
245
	if err != nil {
246
		return "", err
247
	}
248
	body, err := ioutil.ReadAll(resp.Body)
249
	if err != nil {
250
		return "", err
251
	}
252
	defer resp.Body.Close()
253
	if resp.StatusCode != 200 && resp.StatusCode != 404 {
254
		return "", fmt.Errorf("status: %s", resp.Status)
255
	} else if resp.StatusCode == 404 {
256
		// This package is not in WPVulnDB
257
		return "", nil
258
	}
259
	return string(body), nil
260
}
261