Completed
Push — master ( 91df59...99c65e )
by kota
06:04
created

wordpress/wordpress.go   C

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 57
eloc 162
dl 0
loc 253
rs 5.04
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
B wordpress.httpRequest 0 23 7
F wordpress.FillWordPress 0 108 32
A wordpress.convertToVinfos 0 15 5
A wordpress.match 0 10 3
C wordpress.extractToVulnInfos 0 41 10
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
	"golang.org/x/xerrors"
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, xerrors.New("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
	if body == "" {
78
		util.Log.Warnf("A result of REST access is empty: %s", url)
79
	}
80
	wpVinfos, err := convertToVinfos(models.WPCore, body)
81
	if err != nil {
82
		return 0, err
83
	}
84
85
	//TODO add a flag ignore inactive plugin or themes such as -wp-ignore-inactive flag to cmd line option or config.toml
86
87
	// Themes
88
	for _, p := range r.WordPressPackages.Themes() {
89
		url := fmt.Sprintf("https://wpvulndb.com/api/v3/themes/%s", p.Name)
90
		body, err := httpRequest(url, token)
91
		if err != nil {
92
			return 0, err
93
		}
94
		if body == "" {
95
			continue
96
		}
97
98
		templateVinfos, err := convertToVinfos(p.Name, body)
99
		if err != nil {
100
			return 0, err
101
		}
102
103
		for _, v := range templateVinfos {
104
			for _, fixstat := range v.WpPackageFixStats {
105
				pkg, ok := r.WordPressPackages.Find(fixstat.Name)
106
				if !ok {
107
					continue
108
				}
109
				ok, err := match(pkg.Version, fixstat.FixedIn)
110
				if err != nil {
111
					return 0, xerrors.Errorf("Not a semantic versioning: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
112
				}
113
				if ok {
114
					wpVinfos = append(wpVinfos, v)
115
					util.Log.Infof("[match] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
116
				} else {
117
					//TODO Debugf
118
					util.Log.Infof("[miss] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
119
				}
120
			}
121
		}
122
	}
123
124
	// Plugins
125
	for _, p := range r.WordPressPackages.Plugins() {
126
		url := fmt.Sprintf("https://wpvulndb.com/api/v3/plugins/%s", p.Name)
127
		body, err := httpRequest(url, token)
128
		if err != nil {
129
			return 0, err
130
		}
131
		if body == "" {
132
			continue
133
		}
134
135
		pluginVinfos, err := convertToVinfos(p.Name, body)
136
		if err != nil {
137
			return 0, err
138
		}
139
140
		for _, v := range pluginVinfos {
141
			for _, fixstat := range v.WpPackageFixStats {
142
				pkg, ok := r.WordPressPackages.Find(fixstat.Name)
143
				if !ok {
144
					continue
145
				}
146
				ok, err := match(pkg.Version, fixstat.FixedIn)
147
				if err != nil {
148
					return 0, xerrors.Errorf("Not a semantic versioning: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
149
				}
150
				if ok {
151
					wpVinfos = append(wpVinfos, v)
152
					//TODO Debugf
153
					util.Log.Infof("[match] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
154
				} else {
155
					//TODO Debugf
156
					util.Log.Infof("[miss] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
157
				}
158
			}
159
		}
160
	}
161
162
	for _, wpVinfo := range wpVinfos {
163
		if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok {
164
			vinfo.CveContents[models.WPVulnDB] = wpVinfo.CveContents[models.WPVulnDB]
165
			vinfo.VulnType = wpVinfo.VulnType
166
			vinfo.Confidences = append(vinfo.Confidences, wpVinfo.Confidences...)
167
			vinfo.WpPackageFixStats = append(vinfo.WpPackageFixStats, wpVinfo.WpPackageFixStats...)
168
			r.ScannedCves[wpVinfo.CveID] = vinfo
169
		} else {
170
			r.ScannedCves[wpVinfo.CveID] = wpVinfo
171
		}
172
	}
173
	return len(wpVinfos), nil
174
}
175
176
func match(installedVer, fixedIn string) (bool, error) {
177
	v1, err := version.NewVersion(installedVer)
178
	if err != nil {
179
		return false, err
180
	}
181
	v2, err := version.NewVersion(fixedIn)
182
	if err != nil {
183
		return false, err
184
	}
185
	return v1.LessThan(v2), nil
186
}
187
188
func convertToVinfos(pkgName, body string) (vinfos []models.VulnInfo, err error) {
189
	if body == "" {
190
		return
191
	}
192
	// "pkgName" : CVE Detailed data
193
	pkgnameCves := map[string]WpCveInfos{}
194
	if err = json.Unmarshal([]byte(body), &pkgnameCves); err != nil {
195
		return nil, xerrors.Errorf("Failed to unmarshal %s. err: %w", body, err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
196
	}
197
198
	for _, v := range pkgnameCves {
199
		vs := extractToVulnInfos(pkgName, v.Vulnerabilities)
200
		vinfos = append(vinfos, vs...)
201
	}
202
	return vinfos, nil
203
}
204
205
func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnInfo) {
206
	for _, vulnerability := range cves {
207
		var cveIDs []string
208
209
		if len(vulnerability.References.Cve) == 0 {
210
			cveIDs = append(cveIDs, fmt.Sprintf("WPVDBID-%d", vulnerability.ID))
211
		}
212
		for _, cveNumber := range vulnerability.References.Cve {
213
			cveIDs = append(cveIDs, "CVE-"+cveNumber)
214
		}
215
216
		var refs []models.Reference
217
		for _, url := range vulnerability.References.URL {
218
			refs = append(refs, models.Reference{
219
				Link: url,
220
			})
221
		}
222
223
		for _, cveID := range cveIDs {
224
			vinfos = append(vinfos, models.VulnInfo{
225
				CveID: cveID,
226
				CveContents: models.NewCveContents(
227
					models.CveContent{
228
						Type:       models.WPVulnDB,
229
						CveID:      cveID,
230
						Title:      vulnerability.Title,
231
						References: refs,
232
					},
233
				),
234
				VulnType: vulnerability.VulnType,
235
				Confidences: []models.Confidence{
236
					models.WPVulnDBMatch,
237
				},
238
				WpPackageFixStats: []models.WpPackageFixStatus{{
239
					Name:    pkgName,
240
					FixedIn: vulnerability.FixedIn,
241
				}},
242
			})
243
		}
244
	}
245
	return
246
}
247
248
func httpRequest(url, token string) (string, error) {
249
	util.Log.Debugf("%s", url)
250
	req, err := http.NewRequest("GET", url, nil)
251
	if err != nil {
252
		return "", err
253
	}
254
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
255
	resp, err := new(http.Client).Do(req)
256
	if err != nil {
257
		return "", err
258
	}
259
	body, err := ioutil.ReadAll(resp.Body)
260
	if err != nil {
261
		return "", err
262
	}
263
	defer resp.Body.Close()
264
	if resp.StatusCode != 200 && resp.StatusCode != 404 {
265
		return "", xerrors.Errorf("status: %s", resp.Status)
266
	} else if resp.StatusCode == 404 {
267
		// This package is not in WPVulnDB
268
		return "", nil
269
	}
270
	return string(body), nil
271
}
272