Compare commits

...

410 Commits

Author SHA1 Message Date
Kota Kanbe
a6e53e4c1f fix build-tags 2021-06-09 09:23:23 +09:00
Kota Kanbe
4b487503d4 chore: add go.sum test data for integration test (#1249)
* add go.sum test data for integration test

* chore: .gitignore
2021-06-09 09:18:32 +09:00
Kota Kanbe
0095c40e69 fix(vet): go vet err of make build-scanner (#1248) 2021-06-09 08:00:52 +09:00
Kota Kanbe
82c1abfd3a fix(report): detection logic bugs for Oracle Linux (#1247)
* fix(report): continue detecting if arch is emtpy for Oracle Linux

* fix test case

* fix(report): a bug of `Not Fixed Yet` of Oracle linux scanning
2021-06-09 05:46:42 +09:00
sadayuki-matsuno
40988401bd feat(scanner) separate func analize libraries (#1246)
* feat(scanner) separate func analize libraries

* fix(scanner) fix typo
2021-06-04 07:42:29 +09:00
Kota Kanbe
e8e3f4d138 feat(lib): support of Go (go.sum) scan (#1244)
* chore: update trivy deps

* fix(test): fix sort order in json

* parse go.sum in scanning

* feat(lib): support go.sum
2021-06-03 11:31:37 +09:00
Norihiro NAKAOKA
7eb77f5b51 feat(scan): support external port scanner(nmap) in host machine (#1207)
* feat(scan): load portscan settings from config.toml

* feat(scan): support external port scanner:nmap

* style: rename variable

* feat(scan): logging apply options

* feat(scan): remove spoof ip address option

* feat(scan): more validate port scan config

* style: change comment

* fix: parse port number as uint16

* feat(discover): add portscan section

* feat(discover): change default scanTechniques

* feat(docker): add nmap and version update

* feat(scan): nmap module upgrade

* fix: wrap err using %w

* feat(scan): print cmd using external port scanner

* feat(scan): more details external port scan command

* feat(scan): add capability check in validation

* fix(scanner): format error

* chore: change format
2021-05-26 09:35:28 +09:00
Kota Kanbe
e115235299 fix(test): dev mode to false in package-lock.json (#1242)
* fix(test): dev mode to false in package-lock.json

* fix: vet warning
2021-05-17 08:04:16 +09:00
otuki
151d4b2d30 fix(scan): Avoid panic when SSH connection refused (#1236)
* fix(fix-ssh-fata): Avoid panic when SSH connection refused

* chore(fix-ssh-fata): fix typo
2021-05-12 18:30:26 +09:00
Kota Kanbe
e553f8b4c5 feat(trivy): go mod update trivy v0.17.2 (#1235)
* feat(trivy): go mod update trivy v0.17.2

* wg.Wait

* fix reporting

* fix test case

* add gemfile.lock of redmine to integration test

* fix(test): add Pipfile.lock

* add poetry.lock to integration test

* add composer.lock to integration test

* add integration test case
2021-05-12 18:27:55 +09:00
Kota Kanbe
47652ef0fb fix(report): include the num of criticals in total #1233 (#1234) 2021-05-07 07:57:33 +09:00
Kota Kanbe
ab0e950800 fix(oracle): extracting only advisory ID from OVAL.title (#1232) 2021-04-29 12:54:36 +09:00
otuki
a7b0ce1c85 refactor(git-conf): config template in github section changed (#1229) 2021-04-28 14:53:11 +09:00
otuki
dc9c0edece refactor(git-conf): Specifing ignoreGitHubDismissed per repository (#1224)
* refactor(git-conf): Specifing ignoreGitHubDismissed per repository with config.toml

* refactor(git-conf): change json tag into camelCase

* refactor(git-conf): change first char of json tag into lowercase
2021-04-28 13:41:38 +09:00
Kota Kanbe
17ae386d1e chore: add a test case #1227 (#1228) 2021-04-28 12:18:18 +09:00
Kota Kanbe
2d369d0cfe Fix false positive for Oracle Linux (#1227)
* fix(oracle): false-positive(handle arch of pkgs)

* fix(oracle): false positive kernel-related CVEs

* add a test case for ksplice1

* fix(scan): handle uek kernel for Oracle linux

* fix(scan): hanlde uek kernel for reboot required

* fix(oracle): false-positive for redis-backend
2021-04-27 20:38:45 +09:00
Kota Kanbe
c36e645d9b fix(report): false positive for kernel-related CVE for RedHat, CentOS, Oracle and Amazon #1199 (#1223) 2021-04-23 08:59:46 +09:00
Kota Kanbe
40039c07e2 fix(report): panic when closing db connection of gost (#1222) 2021-04-23 06:14:12 +09:00
Kota Kanbe
a692cec0ef fix(gost): close gost DB connection in server mode #1217 (#1221) 2021-04-21 11:59:11 +09:00
otuki
e7ca491a94 fix(report): Avoid http reports error (#1216) 2021-04-21 10:00:58 +09:00
Shigechika AIKAWA
23f3e2fc11 fix(config): add Ubuntu 20.10 (#1218) 2021-04-21 09:05:33 +09:00
Kota Kanbe
27b3e17b79 feat(saas): delete json dir automatically after upload (#1212)
* feat(saas): delete json dir automatically after upload

* fix lint err
2021-04-15 05:58:41 +09:00
Kota Kanbe
740781af56 feat(logging): add -log-to-file and don't output to file by default (#1209)
* feat(logging): add -log-to-file and don't output to file by default

* update go-cve-dict

* fix lint err
2021-04-05 17:41:07 +09:00
Kota Kanbe
36c9c229b8 fix(report): avoid nil pointer when report FreeBSD (#1208) 2021-04-05 12:54:27 +09:00
Norihiro NAKAOKA
183fdcbdef fix: support for missing files in the results or results directory (#1206)
* fix: support for missing files in the results or results directory

* fix: support for missing files in the results or results directory
2021-04-05 07:28:20 +09:00
Kota Kanbe
a2a697900a refactor: move const to constant pkg (#1205) 2021-04-02 15:33:02 +09:00
Kota Kanbe
6fef4db8a0 fix .goreleaser.yml (#1204)
* fix .goreleaser.yml

* chore: fix lint warnings
2021-04-01 17:43:54 +09:00
sadayuki-matsuno
e879ff1e9e feat(scanner) export pkg list scan method (#1203)
* feat(scanner) export pkg list scan method

* fix args

* fix func

* fix init debian
2021-04-01 17:38:20 +09:00
Kota Kanbe
9bfe0627ae refactor: don't use global Config in private func (#1197)
* refactor: cve_client.go

* refactor: don't use global Config in private func

* remove import alias for config

* refactor: dbclient

* refactor: resultDir

* refactor: resultsDir

* refactor

* refactor: gost

* refactor: db client

* refactor: cveDB

* refactor: cvedb

* refactor: exploitDB

* refactor: remove detector/dbclient.go

* refactor: writer

* refactor: syslog writer

* refactor: ips

* refactor: ensureResultDir

* refactor: proxy

* fix(db): call CloseDB

* add integration test

* feat(report): sort array in json

* sort func for json diff

* add build-int to makefile

* add int-rds-redis to makefile

* fix: test case, makefile

* fix makefile

* show cve count after diff

* make diff

* diff -c

* sort exploits in json for diff

* sort metasploit, exploit
2021-04-01 13:36:24 +09:00
Tomoya Amachi
0179f4299a fix(trivy-to-vuls): converts even if null vulnerabilities (#1201) 2021-03-22 19:32:08 +09:00
Kota Kanbe
56017e57a0 feat(trivy): update trivy (#1196) 2021-03-12 09:31:48 +09:00
Kota Kanbe
cda91e0906 refactor: loading owasp dependency check xml (#1195) 2021-03-11 08:51:44 +09:00
Kota Kanbe
5d47adb5c9 fix(report): prioritize env vars over config.toml (#1194) 2021-03-10 07:39:58 +09:00
Kota Kanbe
54e73c2f54 fix(wordpress): enable to detect vulns of WordPress Core (#1193) 2021-03-09 10:40:52 +09:00
segatomo
2d075079f1 fix(log): remove log output of opening and migrating db (#1191)
* fix(log): remove log output of opening and migrating db

* fix(log): remove log output of opening and migrating db
2021-03-05 16:16:10 +09:00
Kota Kanbe
2a8ee4b22b refactor(report): azure and aws writer (#1190) 2021-03-04 07:42:38 +09:00
Kota Kanbe
1ec31d7be9 fix(configtest): all servers in the config if no args #1184 (#1189) 2021-03-03 12:51:07 +09:00
Kota Kanbe
02286b0c59 fix(scan): scan all servers in the config if no args #1184 (#1188) 2021-03-03 12:30:30 +09:00
Kota Kanbe
1d0c5dea9f fix(ubuntu): Fix deferred packages not showing as affected (#1187)
* fix(ubuntu): Fix deferred packages not showing as affected

https://github.com/kotakanbe/goval-dictionary/pull/122

* chore: Go version up
2021-03-02 07:50:35 +09:00
Kota Kanbe
1c4a12c4b7 refactor(report): initialize DB connection (#1186) 2021-03-02 06:34:46 +09:00
Kota Kanbe
3f2ac45d71 Refactor logger (#1185)
* refactor: logger

* refactor: logging

* refactor: rename func

* refactor: logging

* refactor: logging format
2021-02-26 10:36:58 +09:00
Kota Kanbe
518f4dc039 refactor: VulnDict (#1183) 2021-02-25 10:13:51 +09:00
Kota Kanbe
2cdeef4ffe refactor(config): validateOnReport (#1182) 2021-02-25 07:41:49 +09:00
Kota Kanbe
03579126fd refactor(config): localize config used like a global variable (#1179)
* refactor(report): LocalFileWriter

* refactor -format-json

* refacotr: -format-one-email

* refactor: -format-csv

* refactor: -gzip

* refactor: -format-full-text

* refactor: -format-one-line-text

* refactor: -format-list

* refacotr: remove -to-* from config

* refactor: IgnoreGitHubDismissed

* refactor: GitHub

* refactor: IgnoreUnsocred

* refactor: diff

* refacotr: lang

* refacotr: cacheDBPath

* refactor: Remove config references

* refactor: ScanResults

* refacotr: constant pkg

* chore: comment

* refactor: scanner

* refactor: scanner

* refactor: serverapi.go

* refactor: serverapi

* refactor: change pkg structure

* refactor: serverapi.go

* chore: remove emtpy file

* fix(scan): remove -ssh-native-insecure option

* fix(scan): remove the deprecated option `keypassword`
2021-02-25 05:54:17 +09:00
Kota Kanbe
e3c27e1817 fix(saas): Don't overwrite config.toml if UUID already set (#1180)
* fix(saas): Don't overwrite config.toml if UUID already set

* add a test case
2021-02-19 06:42:22 +09:00
Richard Alloway
aeaf308679 Add test-case to verify proper version comparison in lessThan() (#1178)
* Add test-case to verify proper version comparison when either/both/neither of newVer and ovalmodels.Package contain "_<minor version>"

* Rename vera to newVer in Test_lessThan()

* Fix oval/util_test.go formatting (make fmt)

Co-authored-by: Richard Alloway (OpenLogic) <ralloway@perforce.com>
2021-02-14 05:30:07 +09:00
Kota Kanbe
f5e47bea40 chore: add a test-case to #1176 (#1177) 2021-02-12 13:46:29 +09:00
Richard Alloway
50cf13a7f2 Pass packInOVAL.Version through centOSVersionToRHEL() to remove the "_<point release>" portion so that packInOVAL.Version strings like 1.8.23-10.el7_9.1 become 1.8.23-10.el7.1 (same behavior as newVer, which now allows packInOVAL.Version and newVer to be directly compared). (#1176)
Co-authored-by: Richard Alloway (OpenLogic) <ralloway@perforce.com>
2021-02-12 13:33:36 +09:00
Kota Kanbe
abd8041772 fix(scan): yum ps warning for Red Hat family (#1174)
* fix(yumps): no debug message for known patterns

* refactor(scan): yum-ps

* refacotr(scan): pkgPs
2021-02-12 13:03:06 +09:00
Kota Kanbe
847c6438e7 chore: fix debug message (#1169) 2021-02-11 06:31:51 +09:00
Kota Kanbe
ef8309df27 chore: remove the heck binary (#1173) 2021-02-11 06:31:32 +09:00
sadayuki-matsuno
0dff6cf983 fix(gost/microsoft) add workaround into mitigation (#1170)
* fix(gost/microsoft) add workaround into mitigation

* fix(gost/microsoft) fix typo and delete workaround field from vulninfo
2021-02-10 19:37:28 +09:00
kazuminn
4c04acbd9e feat(report) : Differences between vulnerability patched items (#1157)
* add plusDiff() and minusDiff()
* add plusDiff minusDiff test

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2021-02-10 06:55:48 +09:00
Kota Kanbe
1c4f231572 fix(scan): ignore rpm -qf exit status (#1168) 2021-02-09 17:26:12 +09:00
Kota Kanbe
51b8e169d2 fix(scan): warning if lsof command not found (#1167) 2021-02-07 07:28:45 +09:00
Kota Kanbe
b4611ae9b7 fix(scan): fix yum-ps warning Failed to exec which -bash (#1166) 2021-02-07 07:23:12 +09:00
Kota Kanbe
cd6722017b fix(scan): yum-ps err Failed to find the package (#1165) 2021-02-06 08:42:06 +09:00
Kota Kanbe
290edffccf fix(log): output version to log for debugging purpose (#1163) 2021-02-04 07:47:56 +09:00
Kota Kanbe
64a6222bf9 fix(report): set created_at and updated_at of trivy to json (#1162) 2021-02-03 17:52:44 +09:00
Kota Kanbe
adb686b7c9 fix(report): set created_at and updated_at of wpscan.com to json (#1161) 2021-02-03 16:41:44 +09:00
Kota Kanbe
d4af341b0f fix(report): remove duplicated refreshing logic when report with -diff (#1160) 2021-02-03 07:37:19 +09:00
Kota Kanbe
fea7e93c8d chore: fix comment (#1158) 2021-02-02 06:06:49 +09:00
sadayuki-matsuno
8b6b8d0f2e feat(wordpress): define API limit exceed error for wpscan.com (#1155)
* feat(wordpress) specify wp err

* fix typo, chagne const name

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2021-01-30 09:53:41 +09:00
Kota Kanbe
4dcbd865cc fix(report): set http timeout 10 sec (#1154)
* fix(report): set http timeout 10 sec

* fix: add an error handling
2021-01-30 09:40:33 +09:00
Kota Kanbe
39b19444fe Merge branch 'master' of github.com:future-architect/vuls 2021-01-28 16:24:14 +09:00
Kota Kanbe
644d5a5462 fix(report): remove retry logic for wpscan.com (#1151)
* fix(saas) change saas upload s3 key (#1116)

* fix(report): remove retry logic for wpscan.com

Co-authored-by: sadayuki-matsuno <sadayuki.matsuno@gmail.com>
2021-01-28 16:21:33 +09:00
Kota Kanbe
8e18451e3f Merge branch 'master' of github.com:future-architect/vuls 2021-01-28 08:24:23 +09:00
Kota Kanbe
3dbdd01f97 fix(report): wordrpess scanning skipped when package is emtpy (#1150) 2021-01-28 08:24:03 +09:00
sadayuki-matsuno
a89079c005 fix(saas) change saas upload s3 key (#1116) 2021-01-28 08:20:13 +09:00
sadayuki-matsuno
a8c0926b4f fix(saas) change saas upload s3 key (#1116) 2021-01-27 14:43:09 +09:00
Kota Kanbe
dd2959a31b fix(eol): add eol for alpine 3.13 (#1149) 2021-01-27 12:52:07 +09:00
Kota Kanbe
51099f42c3 fix(tui): runtime panic when tui with docker-base-setup (#1148)
* fix(tui): runtime panic when tui with docker-base-setup

* pass test case
2021-01-26 09:40:26 +09:00
Kota Kanbe
63f170cc7a fix(report): set severity in Red Hat OVAL to both CVSS v3 and v2 #1146 (#1147) 2021-01-26 07:58:59 +09:00
Kota Kanbe
3c1489e588 feat(report): range notion calc by severity when no-cvss-score (#1145) 2021-01-25 13:22:55 +09:00
Kota Kanbe
e4f1e03f62 feat(github): display GitHub Security Advisory details (#1143) 2021-01-24 09:15:04 +09:00
Kota Kanbe
83d48ec990 Create codeql-analysis.yml 2021-01-24 09:06:13 +09:00
Kota Kanbe
b20d2b2684 fix(scan): skip wordpress scan for preudo servers (#1142) 2021-01-21 07:11:55 +09:00
Kota Kanbe
2b918c70ae fix(scan): config dump nocolor in debug mode. (#1141) 2021-01-21 06:38:37 +09:00
Kota Kanbe
1100c133ba feat(config): Default values for WordPress scanning to be set in config.toml (#1140)
* chore: update go mod

* fix(wordpress): set default if defined in config.toml
2021-01-21 06:22:25 +09:00
Kota Kanbe
88899f0e89 refactor: around CheckHTTPHealth (#1139) 2021-01-20 07:41:29 +09:00
Kota Kanbe
59dc0059bc fix(model): omit changelog from json if empty (#1137) 2021-01-19 09:01:35 +09:00
Kota Kanbe
986fb304c0 fix(scan): add --nogpgcheck to dnf mod list to avoid Error: Cache-only enabled but no cache for *** (#1136) 2021-01-19 08:05:20 +09:00
Kota Kanbe
d6435d2885 fix(xml): remove -format-xml #1068 (#1134) 2021-01-18 04:38:00 +09:00
shopper
affb456499 fix(email.go):Fix runtime error(invalid memory address) (#1133) 2021-01-18 04:08:14 +09:00
Kota Kanbe
705ed0a0ac fix(discover): change config.toml template (#1132) 2021-01-16 07:58:46 +09:00
Kota Kanbe
dfffe5b508 fix(config): err occurs when host not set in local-scan-mode (#1129)
If host is not set in local scan mode, an error occurs.
2021-01-14 09:22:04 +09:00
Shigechika AIKAWA
fca102edba fix dnf prompt and ssh user (#1126) 2021-01-14 08:22:06 +09:00
Kota Kanbe
554b6345a2 chore: go mod update (#1127) 2021-01-14 08:12:47 +09:00
Kota Kanbe
aa954dc84c fix(scan): kindness msg when no-cache err on dnf mod list (#1128) 2021-01-14 08:12:35 +09:00
Kota Kanbe
b5506a1368 chore: go mod update (#1125) 2021-01-13 11:56:35 +09:00
Kota Kanbe
0b55f94828 Improve implementation around config (#1122)
* refactor config

* fix saas config

* feat(config): scanmodule for each server in config.toml

* feat(config): enable to specify containersOnly in config.toml

* add new keys of config.toml to discover.go

* fix summary output, logging
2021-01-13 08:46:27 +09:00
Kota Kanbe
a67052f48c fix(scan): err detecting EOL for alpine Linux (#1124) 2021-01-12 20:10:22 +09:00
Kota Kanbe
6eff6a9329 feat(report): display EOL information to scan summary (#1120)
* feat(report): display EOL information to scan summary

* detect Amazon linux EOL
2021-01-09 07:58:55 +09:00
Kota Kanbe
69d32d4511 feat(report): add a err code to wpscan.com API error (#1119) 2021-01-07 14:57:49 +09:00
Kota Kanbe
d7a613b710 chore: go mod update (#1118) 2021-01-07 08:02:29 +09:00
sadayuki-matsuno
669c019287 fix(cvecontent) Fixed not to split empty string (#1117) 2021-01-06 15:52:55 +09:00
Shigechika AIKAWA
fcc4901a10 fix(scan): Failed to parse CentOS Stream (#1098) 2021-01-06 14:57:19 +09:00
Kota Kanbe
4359503484 fix(redhat): possibility of false positives on RHEL (#1115) 2021-01-06 13:33:08 +09:00
Kota Kanbe
b13f93a2d3 feat(scan): support dnf modules (#1114)
* feat(scan): support dnf modules

* change dnf module list --installed to --enabled

* chore: refactor

* feat(report): detect logic for dnf modularity label

* fix func name

* chore: update go mods
2021-01-06 11:36:41 +09:00
Kota Kanbe
8405e0fad6 refactor(gost): Duplicate code into function (#1110)
* refactor(gost): Duplicate code into function

* fix
2020-12-30 08:33:30 +09:00
Kota Kanbe
aceb3f1826 fix(scan): add an error case for rpm -qa (#1109) 2020-12-30 08:05:14 +09:00
Kota Kanbe
a206675f3e fix(wordpress): remove cache because not permitted. (#1107) 2020-12-29 07:25:58 +09:00
Kota Kanbe
f4253d74ae fix(wordpress): wpscan.com unmarshal error (#1106)
* refactor(report): remove Integration.apply

* add an err check

* fix(wordpress): wpscan.com unmarshal error

* fix warnings
2020-12-29 07:11:04 +09:00
Kota Kanbe
aaea15e516 refactor(report): remove Integration.apply (#1105)
* refactor(report): remove Integration.apply

* add an err check
2020-12-29 06:59:48 +09:00
Kota Kanbe
83d1f80959 chore(report): remove stride and hipchat support (#1104) 2020-12-26 08:52:45 +09:00
Kota Kanbe
a33cff8f13 fix(reprot): use SQLite3 in current dir if not specified (#1103) 2020-12-26 08:24:17 +09:00
Kota Kanbe
8679759f60 chore: fix typo (#1102) 2020-12-26 08:23:02 +09:00
Kota Kanbe
53deaee3d7 refactor(config): remove DependencyCheckXMLPath in config.toml (#1100) 2020-12-25 06:38:00 +09:00
Kota Kanbe
5a14a58fe4 refactor(nvdxml): Remove codes related to NVD xml(deprecated) (#1099) 2020-12-25 06:16:14 +09:00
Kota Kanbe
fb1fbf8f95 feat(report): Add NVD as a source for mitigations, primarySrc URL and Patch URL (#1097)
* feat(report): Add NVD as a src for mitigations.

* feat(report): display "Vendor Advisory" URL in NVD

* feat(report): display patch urls in report, tui
2020-12-24 08:37:10 +09:00
Kota Kanbe
cfbf779f9b feat(exploit): add exploit link in NVD as a source (#1096)
Added Refs information with NVD's Expoit tag as an information source
for Exploit.
2020-12-16 07:10:18 +09:00
Kota Kanbe
d576b6c6c1 refactor(report): around FillCveInfo (#1095)
* refactor(report): around FillCveInfo

* refacotr(report): around FillCveInfo
2020-12-15 15:48:23 +09:00
Kota Kanbe
514eb71482 fix(server): make config loading same as scan (#1091)
* fix(server): make config loading same as scan

* also remove from report, tui
2020-12-15 04:33:14 +09:00
Kota Kanbe
43ed904db1 fix(deps): update dependencies (#1094)
* fix(dpes): update dependencies

* update go ver

* update go ver

* update go

* update go
2020-12-15 04:32:23 +09:00
Kota Kanbe
0a440ca629 fix(saas): add saas subcmd (#1093) 2020-12-11 16:19:36 +09:00
Kota Kanbe
eff1dbf95b feat(scanner): vuls-scanner binary on release archive (#1092) 2020-12-11 11:05:48 +09:00
Kota Kanbe
9a32a94806 refactor: fix build warnings (#1090) 2020-12-11 06:45:39 +09:00
Shigechika AIKAWA
2534098509 fix(report): wpvulndb poor versioning(#1088) (#1089) 2020-12-11 05:53:41 +09:00
sadayuki-matsuno
9497365758 update pkg (#1087) 2020-12-04 15:57:02 +09:00
Kota Kanbe
101c44c9c0 Change .goreleaser to build binaries for arm, 386, amd64 at release. (#1082)
* fix go-releaser

* add vuls-scanner
2020-11-28 06:39:52 +09:00
Kota Kanbe
ffd745c004 fix a compile error #1083 (#1084) 2020-11-27 15:14:04 +09:00
Kota Kanbe
5fea4eaef8 feat(nocgo): enable to build with CGO_ENABLED=0 (#1080) 2020-11-27 09:55:09 +09:00
Kota Kanbe
1f610043cf feat(scan): IgnoredJSONKyes to clear values in result json #1071 (#1078) 2020-11-20 10:36:36 +09:00
Kota Kanbe
3f8de02683 fix(portscan): to keep backward compatibility before v0.13.0 (#1076) 2020-11-19 16:54:36 +09:00
Kota Kanbe
d02535d053 fix(debian): false negative of kernel cves with rdb backend (#1075)
* fix(debian): false negative of kernel cves with rdb backend

* update golangci.yml

* add --timeout=10m to golangci.yml
2020-11-18 10:32:37 +09:00
Kota Kanbe
75fceff5f7 refactor(report): format-csv (#1072) 2020-11-05 21:10:35 +09:00
gy741
ebd3834a35 add(report) -format-csv option (#1034) 2020-11-05 20:56:19 +09:00
Kota Kanbe
93059b74c3 feat(report): IgnoredJSONKyes to clear values in result json (#1071)
* feat(report): IgnoredJSONKyes to clear values in result json

* fix(report): marshal indent in JSON everytime
2020-11-05 20:13:09 +09:00
Kota Kanbe
2fc3462d35 fix(libscan): update trivy deps (#1070) 2020-11-05 15:38:12 +09:00
Kota Kanbe
f78dab50cb fix(fast-root): affectedProcs, ports bug (#1067) 2020-10-31 14:21:11 +09:00
Norihiro NAKAOKA
edb324c3d9 fix(portscan): ignore loopback address on remote scan (#1062)
* change ignore loop back address on remote scan

* fix test case

* change append simple

* fix format

* set golangci-lint timeout

* Revert "set golangci-lint timeout"

This reverts commit 56b1c7089a.
2020-10-23 16:40:03 +09:00
Norihiro NAKAOKA
83bcca6e66 experimental: add smart(fast, minimum ports, silently) TCP port scanner (#1060)
* add struct ListenPorts

* change parse to models.ListenPorts from string

* change support models.ListenPorts in TUI

* add scanPort template , detectScanDest

* add Test_detectScanDest

* change impl scanPorts template

* fix build error

* change collect scan success address

* add Test_matchListenPorts

* add Test_updatePortStatus

* change display port scan result on tui

* change display scan emoji on report

* Revert "change display scan emoji on report"

This reverts commit e281882cc6.

* add continue

* change display format

* change no use loop label

* remove comment code

* change display

* fix padding

* change refactoring var , fn name

* fix var name

* fix var name

* change eye icon

* change icon

* delete unuse mod
2020-10-19 17:47:20 +09:00
Kota Kanbe
a124518d78 fix: hard-coded version #1057 (#1059) 2020-10-16 20:42:31 +09:00
Alexander Stein
94bf630e29 Expand negative grep match for any error for lib scans. (#1056)
Many thanks 👍 

Sure, that's better.

Note: FreeBSD
find: `find: /var/run/ppp: Permission denied`
2020-10-12 11:30:11 +09:00
shopper
31bb33fd90 ignore apk warning (#1052) 2020-10-12 10:40:01 +09:00
Kota Kanbe
4b680b9960 fix(scan-freebsd): also get installed with pkg info #1042 (#1051)
* fix(scan-freebsd): also get installed with `pkg info` #1042

* fix test
2020-09-12 05:08:41 +09:00
Kota Kanbe
8a8ab8cb18 feat(libscan): enable to scan vulns of libs with pseudo #1035 (#1050) 2020-09-11 13:09:59 +09:00
Kota Kanbe
8146f5fd1b update readme (#1049) 2020-09-11 10:26:57 +09:00
shopper
425c585e47 Support for smtp LOGIN authentication (#1048)
* finished to implement new mail client

* delete email_test.go
2020-09-04 15:45:29 +09:00
Kota Kanbe
4f1578b2d6 [WIP]fix(scan): collect a running version of kernel-devel (#1044)
* fix(scan): collect a running kernel-devel version

* refactor
2020-09-01 14:37:40 +09:00
Norihiro NAKAOKA
7969b343b0 Raspberry Pi OS(Raspbian) scanning using OVAL DB (#1019)
* change: never refer to ChangeLog

* change raspberry pi os use debian oval at report

* change do not use r.Family

* change gost do not use r.Family

* change use r.Family because family has a large impact

* change replace MaineK00n/goval-dictionary@raspberrypi-oval

* note Raspbian Scan Policy

* add Raspbian Changelog support policy

* change grep Package for Raspbian at fast-scan mode

* add changelog preprocessing for Raspbian

* add take note of TODO

* change Changelog fetch part to function

* change error handling

* change solve one TODO

* change make ChangelogDir once

* add comment

* fix oval support Amazon Linux :refs #824

* change to useScannedCves from ovalSupproted

* change confidence for Raspbian

* change skip package for raspbian in OVAL DB

* change separate raspbian implementation from util

* change error, log format

* change print format

* change log format(delete newline)

* change support changelog.(Debian.)gz

* Revert "change support changelog.(Debian.)gz"

This reverts commit 2265a72c67.

* change test chnage.(Debian.)gz

* change support raspbian package(*raspberry*)

* change error format

* fix regexp pattern

* fix typo

* fix changelog cache

* change rename function name

* add TestParseChangelog

* change changelog lenient match for raspbian

* fix test case

* change clog dir support symbolic link, clog save dir name append suffix

* change remove more package for raspberry pi

* fix error handling

* change module update

* change refactoring around identifying raspbian package

* update go module

* update scan image

* update scan image

* change clarify scan mode

* change raspiPackNamePattern and add test case
2020-08-25 14:11:34 +09:00
Kota Kanbe
58cf1f4c8e refactor(typo): fix typos (#1041) 2020-08-24 16:34:32 +09:00
Norihiro NAKAOKA
a5b87af862 delete unnecessary images (#1036)
* delete unnecessary images

* Revert "delete unnecessary images"

This reverts commit 0967e1c522.

* delete unnecessary images
2020-08-21 17:07:20 +09:00
Kota Kanbe
a0e592b934 fix(report): fix segfault while uploading to s3 (#1033) 2020-08-07 10:31:43 +09:00
Kota Kanbe
7eccc538bb fix(msfdb): udpate go-msfdb-deps (#1032) 2020-08-06 16:54:14 +09:00
Kota Kanbe
59daa8570a fix(gost): suppress err logging when unsupported debian (#1031) 2020-08-05 20:05:50 +09:00
Kota Kanbe
3f52d318bc fix(log): suppress err msg if no access priv to logfile (#1029) 2020-07-31 16:55:12 +09:00
takuzoo
11a7a0c934 Display metasploit module information for each detected CVE-IDs (#1011)
* add metasploit

* fix go deps

* fix msf report

* fix msfdb server port number

* delete non-unique msfdb url from fulltext report

* fix(report): validate msfdb config on report (#1)

* fix(msfdb): update deps (go-msfdb)

* version up go-msfdb v0.1.0

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2020-07-03 14:05:07 +09:00
sadayuki-matsuno
89f49b0e29 Fix trivy parser test (#1014)
* fix trivy parser test

* fixed parser data
2020-06-24 17:14:43 +09:00
Kota Kanbe
72457cbf8e bump up version 2020-06-24 10:57:39 +09:00
Kota Kanbe
c11ba27509 fix(libscan): include a lockfile path of libs (#1012) 2020-06-24 10:46:00 +09:00
segatomo
8a611f9ba6 add diff-mode info (#1008) 2020-06-19 16:07:14 +09:00
Kota Kanbe
4a73875e4d bump up version (#1007) 2020-06-17 12:21:26 +09:00
shopper
d9d5e612ff Support ProxyJump option when using ssh command (#1004)
* Add proxyjump func

* Run go mod tidy

* Run make fmt
2020-06-17 12:15:12 +09:00
Kota Kanbe
4d8599e4fc update deps (#1006)
see https://github.com/knqyf263/go-apk-version/pull/1
2020-06-16 07:48:07 +09:00
Norihiro NAKAOKA
59c7061d29 Fix SSH failure due to .ssh/config owner (#1005)
* use -F option, success configtest and scan

* add sshConfigPath in config.toml

* Use sshConfigPath in config.toml when using ssh -F

* change -ssh-config to deprecated

* fix typo

* add sshConfigPath in tomltemplate
2020-06-16 05:48:31 +09:00
segatomo
996557c667 support alpine3.11 (#1002) 2020-06-12 13:42:11 +09:00
ahulab
519fb19a77 Added ReportedAt time for server mode reports (#996)
- Fixes #928
2020-06-11 11:42:04 +09:00
kazuminn
36456cb151 feat(wordpress): Cache WpVulnDB (#989)
* add wpVulnCache

* fix bug

* add test

* fmt

* fix bug

* refactor

* fix bug
2020-06-05 16:08:28 +09:00
sadayuki-matsuno
4ae87cc36c Fix releaser (#988)
* fix releaser

* fix releaser

* fix releaser

* fix releaser

* add 32 bit releaser and add exit code  in cmd

* delete 32 bit releaser

* fix
2020-06-05 15:04:06 +09:00
shopper
b37df89fb1 Support SMTPS when using report -to-email (#991)
* Add smtps func

* Add SMTPS implementation

* fix error message
2020-06-05 14:42:01 +09:00
sadayuki-matsuno
d18e7a751d add trivy parser (#981)
* add trivy parser

* fix test

* format

* add title and summary

* add trivy parse command

* add uploader

* set args by env

* add README

* add err check

* fix

* fix

* fix

* fix test

* update trivy

* refactor

* delete require uuid

* delete uuid from trivy parser

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2020-05-29 18:06:45 +09:00
kazuminn
8d5ea98e50 add -wp-ignore-inactive flag which ignores inactive plugin or themes (#974)
* command

* config

* ignore inactive

* fix

* add test

* fmt

* add unset test

* rename

* add test

* refactor

* fix

* refactor

* refactor

* fix golangci-lint error
2020-05-29 15:27:47 +09:00
Kota Kanbe
835dc08049 fix .golangci.yml 2020-05-27 20:33:57 +09:00
Kota Kanbe
62c9409fe9 add a github actions config (#985)
* add a github actions config

* fix(log): Don't create a log dir when testing

* remove a meaningless test case

* Thanks for everything, Mr, Travys.

* add golangci

* add goreleaser.yml

* add tidy.yml

* add golang-ci

* fix many lint warnings
2020-05-27 20:11:24 +09:00
Kota Kanbe
2374f578ed Bump up version 2020-05-26 09:32:10 +09:00
shopper
34e2f033d8 add kernelnames ubuntu20.04 (#982) 2020-05-22 12:19:07 +09:00
kazuminn
420825cacc remove append (#978) 2020-05-20 13:55:07 +09:00
Kota Kanbe
466ec93d8e bump up version 2020-05-08 17:15:25 +09:00
Kota Kanbe
3f5bb6ab29 fix(scan): alpine detection #965 (#966)
* fix(scan): alpine detection #965

* use knqyf263/go-apk-version
2020-05-08 16:12:01 +09:00
Kota Kanbe
ebe5f858c8 update trivy, and unsupport image scanning feature (#971)
* update trivy, fanal. unsupport image scanning

* Update models/library.go

Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp>

* add -no-progress flag to report/tui cmd

* Display trivy vuln info to tui/report

* add detection method to vulninfo detected by trivy

* fix(uuid): change uuid lib to go-uuid #929 (#969)

* update trivy, fanal. unsupport image scanning

* Update models/library.go

Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp>

* add -no-progress flag to report/tui cmd

* Display trivy vuln info to tui/report

* add detection method to vulninfo detected by trivy

* unique ref links in TUI

* download trivy DB only when lock file is specified in config.toml

Co-authored-by: Teppei Fukuda <teppei@elab.ic.i.u-tokyo.ac.jp>
2020-05-08 15:24:39 +09:00
Kota Kanbe
9dd025437b fix(uuid): change uuid lib to go-uuid #929 (#969) 2020-05-06 14:14:07 +09:00
Wagde Zabit
c0ebac305a composer.lock insteaad of composer.json (#973)
Co-authored-by: Wagde Zabit <wagde@orcasecurity.io>
2020-05-01 15:20:33 +09:00
Kota Kanbe
1f23ab7ba4 Bump up version 2020-04-28 14:27:46 +09:00
Kota Kanbe
ea3b63998d fix(report): GitHub Security Alerts Integration (#970) 2020-04-28 14:26:37 +09:00
Kota Kanbe
3093426458 fix(logging): panic if no write permission #949 (#968) 2020-04-27 17:37:30 +09:00
Kota Kanbe
37716feac7 refactor(lint): fix lint warnings (#967) 2020-04-27 17:02:27 +09:00
Kota Kanbe
56b12c38d2 fix(config): not working with empty config #962 (#963) 2020-04-23 10:50:35 +09:00
Kota Kanbe
749ead5d4a update go mod (#960) 2020-04-20 21:33:11 +09:00
Kota Kanbe
3be50ab8da bump up version 2020-04-19 09:06:01 +09:00
Kota Kanbe
649f4a6991 fix(report): kernel vulns detection BUG in Ubuntu (#958)
* fix(report): kernel vulns detection in Ubuntu

* fix(ubuntu): remove linux-* to detect only running kernel vulns
2020-04-19 09:04:08 +09:00
Kota Kanbe
0ff7641471 feat(report): display "fixed" when updatable even in fast mode (#957) 2020-04-13 18:20:32 +09:00
Kota Kanbe
1679bfae20 Update FUNDING.yml 2020-04-10 21:25:10 +09:00
Kota Kanbe
45aa364436 Update FUNDING.yml 2020-04-10 21:24:24 +09:00
Kota Kanbe
778516c4d9 Create FUNDING.yml 2020-04-10 21:21:30 +09:00
Kota Kanbe
464d523c42 Display fixed-in version for each package in report (#801)
* refactor(model): PackageFixStatus.Name to BinName

* refacotr(oval): change var name

* feat(report): Add FixedIn in JSON

* refactor(tui): chage args

* display fixedin in report

* refactor(model): change fileld name

* remove unused field of PackageFixStatus
2020-04-08 21:26:34 +09:00
Kota Kanbe
0f6a1987d4 fix(configtest): yum-utils instead of dnf-utils on RHEL8, Cent8 (#948) 2020-04-06 19:40:05 +09:00
Shigechika AIKAWA
20c6247ce5 fix CentOS8 configtest always failed (#947) 2020-04-06 15:47:08 +09:00
gy741
a10dd67e0f Fix typo in models/scanresults.go (#942) 2020-04-06 15:00:43 +09:00
segatomo
5729ad6026 Add CWE Top25 and SANS Top25 (#925)
* add top25 rank

* add CweTop25 and SansTop25

* fix report

* add cwetop25 and sanstop25 url

* fix condition branch

* fix condition branch
2020-03-03 17:33:06 +09:00
Tomoya Amachi
9aa0d87a21 feat : scan with image digest (#939) 2020-03-03 16:51:06 +09:00
ishiDACo
fe3f1b9924 Update OWASP Dependency Check parser for dependency-check.2.2.xsd schema (#936) 2020-02-27 10:08:26 +09:00
Kota Kanbe
00e52a88fa Update README.md 2020-02-01 09:27:17 +09:00
Kota Kanbe
5811dffe7a fix(report): Support CVSS 3.1 for Red Hat OVAL #930 (#932) 2020-01-30 22:48:04 +09:00
sadayuki-matsuno
7278982af4 update fanal (#931) 2020-01-30 20:40:49 +09:00
nyao
c17b4154ec fix(config): fix double checking ResultsDir Path (#927) 2019-12-12 09:29:12 +09:00
Kota Kanbe
d6e74cce08 bump up version (#923) 2019-11-26 09:54:30 +09:00
Kota Kanbe
3f80749241 Merge branch 'master' of github.com:future-architect/vuls 2019-11-26 09:44:10 +09:00
Kota Kanbe
7f72b6ac69 Warn no ip (#922)
* fix(scan): ignore wp-cli stderr messages (#825) (#915)

* fix(scan): warn if unable to get ip address on the scan tareget server

* fix test case
2019-11-26 09:40:38 +09:00
Kota Kanbe
03e7b90b9f Merge branch 'master' of github.com:future-architect/vuls 2019-11-26 08:53:03 +09:00
Kota Kanbe
7936b3533b Fill Red Hat CVE data for all distros (#920)
* fix(scan): ignore wp-cli stderr messages (#825) (#915)

* refactor

* feat(report): fill Red Hat CVE data for all distros

* fix lint err

* fix cve judgment (#921)
2019-11-25 17:01:18 +09:00
Shigechika AIKAWA
bd7e61d7cc fix(scan): ignore wp-cli stderr messages (#825) (#915) 2019-11-22 20:58:24 +09:00
Shigechika AIKAWA
69214e0c22 fix(scan): ignore wp-cli stderr messages (#825) (#915) 2019-11-01 10:01:50 +09:00
Wagde Zabit
45bff26558 Consider grep return value 1 as success (#907)
* Allow Offline scanning on Alpine

* Consider grep return value 1 as success
2019-09-18 23:26:37 +09:00
Kota Kanbe
b2e429ccc6 fix(log): add .log extension to vuls logfile (#910) 2019-09-18 23:21:06 +09:00
Kota Kanbe
76363c227b fix(report): enable to report when the sshkey not exist (#909) 2019-09-18 22:40:36 +09:00
Kota Kanbe
d5a3e5c2c5 fix(report): fix cert key in result json ja to jp (#908) 2019-09-18 19:30:32 +09:00
Kota Kanbe
2b02807ef0 fix(report): ignore exploits of no-cve-id vulns (#906) 2019-09-13 12:49:57 +09:00
Kota Kanbe
be659ae094 fix(docker): add git to image (#905) 2019-09-13 01:10:27 +09:00
Kota Kanbe
b2c105adbc fix(tui): enable to exec tui mode without cve.sqlite3 (#904) 2019-09-12 18:35:21 +09:00
Kota Kanbe
c61f462948 fix(report): show POC, CERT in tui and format-list. use vendor summary over NVD (#902)
* fix(report): show POC, CERT in tui and format-list. show vendor summary

* fix test case
2019-09-10 10:00:17 +09:00
Kota Kanbe
3ffed18e02 Change GPL v3 to AGPL v3 because of aquasecurity/trivy dependency (#897) 2019-09-09 21:12:17 +09:00
Kota Kanbe
f54e7257d1 fix(report): fill cert alerts from NVD and JVN feeds (#899)
* fix(report): fill cert alerts from NVD and JVN feeds

* fix import alias cve to cvemodels

* fix import alias cve to cvemodels

* remove unnecessary func
2019-09-09 21:11:59 +09:00
Kota Kanbe
cc13b6a27c fix(report): enable to report without NVD, exit if no OVAL data (#900)
* feat(report): enable to report without NVD

* fix(report): enable to report without NVD and exit if no OVAL data

* update deps

* go mod tidy

* fix err msg
2019-09-09 21:00:34 +09:00
Kota Kanbe
8877db1979 udpate deps, go 1.13 (#901) 2019-09-09 20:26:26 +09:00
Tomoya Amachi
af58122c91 for Amazon Linux image (#896)
* fit amazon linux image's version to OVAL

* add Arch to SrcPackage

* lint go.mod

* make fmt
2019-09-06 10:34:14 +09:00
Kota Kanbe
b7ca5e5590 feat(scan): add -wordpress-only and -libs-only flag (#898) 2019-09-06 10:33:03 +09:00
Tomoya Amachi
69b6d875e6 scanVuln => GetScanResults and writeScanResults (#891) 2019-09-04 13:28:34 +09:00
Kota Kanbe
1fbd516b83 fix(report): fix too many variables while reporting (#888) 2019-08-25 17:56:47 +09:00
DjinnS
dec5d3b165 No warning(s) in the output file with -quiet option. Report command (#885) 2019-08-25 10:56:42 +09:00
DjinnS
d5e2040cef awk is useless because ps already formats the output. Also, this syntaxe isn't correct when the command is excuted on a container because of the ' . (#883) 2019-08-25 10:13:58 +09:00
wagdez
4326befdec Allow Offline scanning on Alpine (#877) 2019-07-30 17:47:01 +09:00
Kota Kanbe
3d4a5d9917 fix(report): Unsupport family: centos (#876)
* fix(report): Unsupport family: centos

* go mod tidy
2019-07-25 12:47:41 +09:00
Shigechika AIKAWA
d770034788 fix centos yum makecache --assumeyes (#872) 2019-07-17 11:10:20 +09:00
Masahiro Fujimura
a977533c78 Fix performance and bug (#867)
* Fix performance

* Update goval-dictionary

* Go mod tidy
2019-07-15 21:20:01 +09:00
Kota Kanbe
c5e13dd5e4 fix(configtest): remove yum-plugin-ps check on Amazon Linux (#870) 2019-07-12 07:25:47 +09:00
Kota Kanbe
a8040fe4d2 fix(wordpress): add --allow-root to wp cmd for docker based wp (#865) 2019-07-07 19:15:17 +09:00
Tomoya Amachi
9e066008c3 fix go module problems & update trivy version (#864)
* update trivy version

* use goval-dictionary@v0.1.4
2019-07-07 17:04:52 +09:00
Kota Kanbe
22c6601526 make fmt 2019-07-06 23:25:46 +09:00
Kota Kanbe
425464fd76 fix(scan): allow exit 1 for no match lsof | grep (#863) 2019-07-06 23:15:34 +09:00
Kota Kanbe
ccb0751ffd fix(scan): show listening ip:port of procs (#862) 2019-07-06 14:10:08 +09:00
Kota Kanbe
f832de81b7 feat(saas): log.info done after uploading 2019-07-05 17:30:31 +09:00
Tomoya Amachi
8a37de0686 Add ips flag to scan (#861)
* add scan -ips flag

* fix usage
2019-07-04 18:42:12 +09:00
Kota Kanbe
836e4704f8 feat(scan): Display listen port of affected procs for each vulnerable pkgs (#859)
* refactor(redhat): move rpmQa and rpmQf to redhatbase.go

* feat(scan): Display listen port of affected procs
2019-07-03 23:01:59 +09:00
Kota Kanbe
3e5390309c feat(redhat): ignore will not fix vulns (#858) 2019-07-03 20:59:23 +09:00
Kota Kanbe
f8c0b38716 feat(fast-root): get running procs for each pkgs (all RHEL, CentOS, AmazonLinux, Ubuntu, Debian) (#855)
* fix(scan): exec yum-plugin-ps on RHEL6 and 7

* feat(yumps): get affected procs on RHEL6 and RHEL8

* feat(scan): get affected processes for each packages

* tuning

* feat(scan): get running procs for each pkgs on Debian, Ubuntu
2019-07-02 14:55:46 +09:00
Masahiro Fujimura
65e6070e5f Fix race condition in server mode (#857) 2019-07-02 10:11:36 +09:00
Tomoya Amachi
7b78ebbc42 retrieve ips(deep security) identifiers (#852)
* retrieve ips identifiers

* fix golangci

* use IPS type

* fix log message

* fix lockfiles config

* change label

* IPS : only work with fast-root mode
2019-07-02 10:06:30 +09:00
Masahiro Fujimura
03c3189c02 Changes don't required config.toml in server mode (#853) 2019-06-26 21:21:17 +09:00
Masahiro Fujimura
4a34dfe0e9 Support amazonlinux via http text/plain (#850) 2019-06-25 10:00:54 +09:00
Kota Kanbe
4cf9a723fe set GO111MODULE=on in .goreleaser.yml 2019-06-18 10:15:42 +09:00
Kota Kanbe
bd1b135db3 Add vulsrepo issue template 2019-06-17 14:15:23 +09:00
alfe
8c3b305149 fix(readme): typo in news (#841) 2019-06-15 18:39:00 +09:00
Kota Kanbe
a3719038b8 fix(scan): scan Amazon Linux with offline mode (#840) 2019-06-14 19:10:07 +09:00
Kota Kanbe
c68a261c0b Update README.md 2019-06-14 19:02:21 +09:00
Kota Kanbe
75fea79ac1 feat(scan): Support RHEL8 (#813)
* feat(scan): Support RHEL8

* fix(scan): check if `dnf-uils` is installed
2019-06-14 12:28:16 +09:00
Kota Kanbe
eb9f9680ec refactor(scan): remove yum-security related code (#836)
* refactor(scan): remove yum-security related code

* fix(reporting): error if no OVAL entry
2019-06-14 11:42:38 +09:00
Tomoya Amachi
3634afdb81 enhance issue_template (#837) 2019-06-14 11:34:36 +09:00
Sajan Alexander
77b5df896a update goval-dictionary dependency to valid version (#839) 2019-06-14 09:28:39 +09:00
Kota Kanbe
b81f64058c fix(report): remove extra check logic #802 (#835) 2019-06-13 21:45:22 +09:00
Kota Kanbe
a8a90d7c63 refactor(report): speed up oval reporting #833 (#834) 2019-06-13 17:47:36 +09:00
Kota Kanbe
17bb575002 fix(scan): enable to report if some warnings occured on scanning (#805)
* fix(scan): enable to report if some warnings occured on scanning

* alpine, debian, freebsd, suse

* -format-full-text, -format-list, -format-one-line-text

* implement slack.go

* implement tui.go

* go fmt
2019-06-12 21:35:21 +09:00
Tomoya Amachi
abcea1a14d add Library Scan (with image scan) (#829)
* add static container image scan

* server has many staticContainers

* use go module

* for staticContainer

* fix typo

* fix setErrs error

* change name : StaticContainer -> Image

* add scan -images-only flag

* fix makefile

* fix makefile for go module

* use rpmcmd instead of rpm

* add scrutinizer.yml

* change scrutinizer.yml

* fix scrutinizer.yml

* fix scrutinizer.yml

* fix scrutinizer.yml

* fix scrutinizer.yml

* delete scrutinizer

* add report test

* add sourcePackages and Arch

* fix for sider

* fix staticContainer -> image

* init scan library

* add library scan for servers

* fix tui bug

* fix lint error

* divide WpPackageFixStats and LibraryPackageFixedIns

* fix error

* Delete libManager_test.go

* stop use alpine os if err occurred in container

* merge upstream/master

* Delete libManager.go

* update goval-dictionary

* fix go.mod

* update Readme

* add feature : auto detect lockfiles
2019-06-12 18:50:07 +09:00
Kota Kanbe
10942f7c08 fix(scan): fetch only updatable package changelogs (#815) 2019-06-12 15:08:03 +09:00
Kota Kanbe
87ee829e80 fix(scan): exec yum makecache to update metadata on RedHat based linux (#810)
* fix(scan): exec `yum makecache` to update metadata on RedHat based linux

* sudo
2019-06-12 14:44:42 +09:00
Chandrapal Badshah
fcc2c1e4c7 Changing the scannedAt time in the original result (#823) 2019-06-12 07:55:29 +09:00
Kota Kanbe
269095d034 feat(report): support Amazon OVAL scanning (#824)
* feat(report): support Amazon OVAL scanning

* add distroAdvisories

* see goval/master
2019-06-10 23:20:39 +09:00
Neal McBurnett
40492ee00a fix typos, extraneous text (#831) 2019-06-10 09:55:17 +09:00
Shigechika AIKAWA
64cdd5aedc fix(report): WordPress(WPVULNDB API) 429 Too Many Requests (#826)
* fix(report): WordPress(WPVULNDB API) 429 Too Many Requests

* fix(report): WordPress(WPVULNDB API) 429 Too Many Requests
2019-06-04 12:11:40 +09:00
Kota Kanbe
3bb650cb77 fix(report-redhat): fix false negative of affected vulns #827 (#828) 2019-06-04 09:55:32 +09:00
Kota Kanbe
774544c975 fix(report): warning only if the kernel version is unknown (#822) 2019-05-24 10:09:11 +09:00
Kota Kanbe
299805a726 [WIP]fix(scan): false negative of kernel related vulns on Ubuntu 16 (#819)
* fix(scan): a bug of detect kernel vulns on Ubuntu 16

* fix(scan): support Ubuntu 14
2019-05-23 23:52:00 +09:00
Kota Kanbe
276363e793 fix(scan): a bug of kernel Vulns detection on Ubuntu18 (#818)
* fix(scan): a bug of kernel Vulns detection on Ubuntu18

* fix the test case
2019-05-23 17:00:33 +09:00
Kota Kanbe
e750bd53fc fix(report): fix the number of fixed/total in reporting (#817) 2019-05-20 14:30:29 +09:00
sadayuki-matsuno
98fee7b5d2 Implement Vuls's own error code (#812)
* add error pkg

* fix fmt format

* fix NewError -> New

* fix err msg format
2019-05-15 17:42:09 +09:00
sadayuki-matsuno
53aaea9fe2 add scannedVia field to know the way of access such as SSH, local or pseudo (#811)
* add sacnned via

* change scannedVia type to const
2019-05-15 13:33:09 +09:00
Chandrapal
824fbb6368 Updated config.toml reference url (#809)
* Update URL in scan.go

* Update URL in configtest.go
2019-05-10 07:11:30 +09:00
Kota Kanbe
80566b91ab fix(report): exit 1 when scan result has errors (#804) 2019-04-25 15:09:29 +09:00
Kota Kanbe
533d05a1b5 fix(report): Error when GitHub integration failed (#800) 2019-04-15 21:51:04 +09:00
Kota Kanbe
6a1fc4fade Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  fix goreleaser.yml
  Add news to readme
2019-04-08 21:19:12 +09:00
Kota Kanbe
9008d0ddf0 Add news to readme 2019-04-08 21:17:05 +09:00
Kota Kanbe
583f4577bc fix goreleaser.yml 2019-04-08 19:51:58 +09:00
Kota Kanbe
e5716d5092 Add news to readme 2019-04-08 18:22:03 +09:00
Kota Kanbe
7192ae1287 Bump up version 2019-04-08 17:33:57 +09:00
kazuminn
99c65eff48 feat(scan): WordPress Vulnerability Scan (core, plugin, theme) (#769)
https://github.com/future-architect/vuls/pull/769
2019-04-08 17:27:44 +09:00
Josh Soref
91df593566 Editorial fixes (#798)
mostly suggested by app.grammarly.com

* articles
* brand name fixes
* hyphenation
* Oxford comma
* sorting lists
* spelling
2019-04-04 22:51:06 +09:00
sadayuki-matsuno
07aeaeb989 update go-exploitdb (#797) 2019-03-28 00:49:31 +09:00
sadayuki-matsuno
cfeecdacd0 update pkgs (#796) 2019-03-26 10:56:14 +09:00
sadayuki-matsuno
564dfa8b62 update cve dictionary (#795) 2019-03-26 10:10:40 +09:00
seph
75dd6f2010 Specify VOLUME using json syntax (#791)
When using a json array for VOLUME, values must be quoted. Else it's interpreted as a string, eg /[vuls

Fixes https://github.com/kotakanbe/goval-dictionary/issues/58
2019-03-22 16:30:23 +09:00
Kota Kanbe
e26fd0b759 fix(report): Critical Bug Fix for CPE based scanning #793 (#794) 2019-03-22 16:28:40 +09:00
Kota Kanbe
d630680a51 feat(slack): enable -format-one-line-text with -to-slack (#792) 2019-03-18 13:56:49 +09:00
Kota Kanbe
1723c3f6a0 fix(report): cpe match bug: go-cve-dictionary#120 (#790) 2019-03-15 21:31:21 +09:00
Kota Kanbe
53dd90302e fix(scan): parse error on SUSE #515 (#786) 2019-03-12 17:36:27 +09:00
b3tyar
5c6e06b05e Handle no-auth SMTP Servers and one liner email fix (#772)
* Handle no-auth SMTP Servers

* Remove unneeded else block

* Fix for Issue #633
2019-03-12 16:45:25 +09:00
Iskander (Alex) Sharipov
cf6fb0c8a5 models: fix no-op append calls (#785)
Fixed simplest cases of append calls that have no
effect aside from driving Go static analysis tools crazy.

One issue remains (#784) since I'm not sure
what would be the right behavior there.
2019-03-07 11:28:44 +09:00
sadayuki-matsuno
e0e71b2eae add scanner info in -to-saas (#783) 2019-03-04 16:36:32 +09:00
sadayuki-matsuno
53f4a29fb1 change implemention of integration (#780) 2019-02-21 14:06:21 +09:00
Kota Kanbe
89d58d1abc bump up version 2019-02-20 14:58:49 +09:00
Kota Kanbe
d6b6969cb3 update README 2019-02-20 14:56:24 +09:00
kazuminn
e7bf6fa69d feat(README): contiruters shield (#778)
* add contiruters shield

* Update README.md

* Update README.md
2019-02-20 12:20:56 +09:00
Kota Kanbe
6e51970b91 fix(discovery): show the template of GitHub Security Alerts integration to discovery subcommand 2019-02-20 12:08:22 +09:00
Kota Kanbe
56d7d43768 feat(report): GitHub security alerts integration (#775)
feat(report): integrate to GitHub security alerts
2019-02-20 12:04:10 +09:00
Shota Ito
256c99ffa2 Delete tab from output in case of No CVE-IDs (#768) 2019-01-25 00:21:41 +09:00
Takayuki Ushida
9c0bc3b13b modify build time (#766) 2019-01-24 15:26:12 +09:00
Kota Kanbe
9b8a323d85 fix(report): detect 0 vulns for Amazon, FreeBSD, Raspbian (#765) 2019-01-24 11:49:33 +09:00
kota kanbe
3178c1e326 Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  Add Telegram support (#762)
2019-01-23 00:25:54 +09:00
kota kanbe
321d68e03a Bump up version 2019-01-23 00:25:02 +09:00
Yao Ding
3d8753c621 Add Telegram support (#762)
* add telegram support

* format message

* remove debug print

* fix linting error

* add telegram to discover; group message by 10

* use chatID instead of channel

* apply refactor

* remove reduntant space
2019-01-23 00:19:16 +09:00
Tomoya Amachi
967c56909d add ScannedIPv4Addrs and ScannedIPv6Addrs (#764) 2019-01-19 22:19:06 +09:00
Takayuki Ushida
7c4831d2d1 add build time (#763) 2019-01-18 13:13:50 +09:00
Shigechika AIKAWA
4b49e11a33 add(report) -format-list option to -to-email (#761) 2019-01-17 16:31:04 +09:00
Kota Kanbe
d84a6a8627 fix(oracle): vuls report returns different result each time in the case of Oracle Linux (#759)
https://github.com/kotakanbe/goval-dictionary/pull/56
2019-01-12 23:11:06 +09:00
sadayuki-matsuno
63b7f4a8db delete paperr (#758) 2019-01-12 22:40:56 +09:00
yahharo
ca2160264a Remove ThreadTimeStamp from message struct (#756)
- If `thread_ts` valus sent as empty string ("") to Slack, it returns error `invalid_thread_ts`
- When API try to send, it use `slack.PostMessageParameters`, not use `message`
2018-12-25 12:27:53 +09:00
Kota Kanbe
7842594f53 fix(scan): OS detection ssh timeout in first run #699 (#753) 2018-12-20 13:59:54 +09:00
Kota Kanbe
7db056102c fix(report): overdetection for Red Hat/CentOS with redis backend (#748)
fix(report): miss detection for Red Hat/CentOS with redis backend
2018-12-06 15:29:28 +09:00
Tomoya Amachi
a5a800fa0a add alert data to result json (#747)
* add alert data to result json

* delete omitempty from AlertDict
2018-12-05 15:38:23 +09:00
Tomoya Amachi
9147ec148d Beautify alert (#746)
* update dep

* to make easy edit alert data manually

* fix alert data bug
2018-12-05 12:30:04 +09:00
sadayuki-matsuno
b3260588c6 fix(gost) update pkg to incorporate the latest gost (#745) 2018-12-04 17:33:31 +09:00
sadayuki-matsuno
7d31328271 export exploit func (#744) 2018-11-30 16:53:51 +09:00
Kota Kanbe
6e82981ee3 feat(report): Display CERT information to reports (#741)
* fix(tui): show JPCERT Alert URL in TUI

* feat(tui): show `!` when the CVE-ID corresponds to USCERT or JPCERT alert

* feat(report): display cert alert info to stdout report

* fix(report): Display CVEs detected by CPEs with -ignore-unfixed flag
2018-11-30 15:41:59 +09:00
Tomoya Amachi
9d7b115bb5 add JPCERT and USCERT alert dictionary (#740)
* add alert dictionary

* fix for sider review

* fix for sider review
2018-11-30 14:17:17 +09:00
Kota Kanbe
8eae5002a3 fix(report): return both scores of gost and oval (#739) 2018-11-29 12:17:19 +09:00
Kota Kanbe
31bd6c0371 feat(scan): get repository name of updatable pkgs for debian/ubuntu (#738) 2018-11-26 12:02:52 +09:00
Kota Kanbe
7585f9d537 fix(report): fix cvedb-url, add -cvedb-type=http (#734)
* fix(report): fix cvedb-url, add -cvedb-type=http

* feat(report): support go-exploitdb server mode

* update deps

* implement tui

* fix server mode

* fix(tui): default value of cvedb-type to ""

* update deps
2018-11-16 21:22:18 +09:00
sadayuki-matsuno
76037cdf72 fix new cve contents (#735) 2018-11-15 13:43:06 +09:00
sadayuki-matsuno
98c5421edc fix exploit db (#733) 2018-11-12 17:36:53 +09:00
Kota Kanbe
e63fc7e3f5 fix(report): nil pointer in deep scan mode #728 (#732) 2018-11-10 12:36:12 +09:00
sadayuki-matsuno
6ed9cf3fb4 add scan mode (#731) 2018-11-05 15:35:50 +09:00
sadayuki-matsuno
9865eab2c0 Display exploit codes information for each detected CVE-IDs (#729)
* add exploit

* bug fix while loading config in TUI, display in format-full-text

* fix readme
2018-11-03 16:36:59 +09:00
Kota Kanbe
678e72a8b6 fix(gost): a bug of parseCwe (#726) 2018-10-29 21:21:20 +09:00
sadayuki-matsuno
ec41899089 check cve_contents init (#725)
check cve_contents init to avoid nil pointer
2018-10-29 16:27:54 +09:00
Harald Nordgren
b2d913cc21 Bump Go versions and use '.x' to always get latest patch versions (#724) 2018-10-29 16:26:20 +09:00
sadayuki-matsuno
bc86c24e6a update pkg (#723)
* update pkg

* change lint url
2018-10-18 13:37:17 +09:00
sadayuki-matsuno
87a77dd95c update pkgs (#720) 2018-10-10 17:43:26 +09:00
sadayuki-matsuno
e8188f3432 add ms gost (#718)
* add ms gost

* change gost branch
2018-10-05 12:45:26 +09:00
Kota Kanbe
50506be546 [WIP] feat(report): show repository of affected pkgs (#713)
feat(report): show repository of affected pkgs
2018-10-04 16:01:55 +09:00
Iskander (Alex) Sharipov
4ded028258 config: remove commented-out code from tomlloader (#714)
Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2018-10-04 12:37:58 +09:00
Iskander (Alex) Sharipov
6da8b3c4a1 commands: simplify s[:] to s (#715)
If s is a slice, then `s[:]` is identical to just `s`.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2018-10-04 12:37:31 +09:00
Iskander (Alex) Sharipov
d5c92cbcb3 report: simplify x = x <op> y to x <op>= y (#716)
Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2018-10-04 12:35:02 +09:00
sadayuki-matsuno
ed5f98d6f0 change syslog pkg (#717) 2018-10-04 12:34:23 +09:00
Kota Kanbe
f854b8f908 fix(report): fix an error while loading cveDict.type in config.toml (#711) 2018-10-02 09:27:34 +09:00
Shigechika AIKAWA
de7a6159d4 remove table.SetHeaderColor codes (#709)
table.SetHeaderColor does not need in case of formatFullPlainText().
2018-09-25 10:31:22 +09:00
Kota Kanbe
6090a34037 fix(cpe): update deps to avoid parsing err of cpeNames (#708) 2018-09-13 13:42:04 +09:00
Kota Kanbe
f566745479 fix(config): a DB URL error 'does not validate as url' #705 (#706) 2018-09-11 09:19:24 +09:00
kota kanbe
153234b623 update readme 2018-08-29 22:39:05 +09:00
Kota Kanbe
ac510d21ff fix(scan): fix err msg when unable to connect via SSH (#702) 2018-08-29 10:48:32 +09:00
Kota Kanbe
44fa2c5800 v0.5.0 (no backwards compatibility) (#478)
* Change config.toml, Auto-generate UUIDs, change structure of optional field

* Detect processes affected by update using yum-ps (#482)

Detect processes affected by update using yum-ps

* Detect processes needs restart using checkrestart on Debian and Ubuntu.

* pass cpename by args when calling FillCveInfo (#513)

* fix new db (#502)

* Include Version,Revision in JSON

* Include hostname in JSON

* Update goval-dictionary's commit hash in Gopkg.lock

* Remove README.ja.md

* update packages (#596)

* fix: change ControlPath to .vuls of SSH option (#618)

* feat: checkrestart for Ubuntu and Debian (#622)

* feat: checkrestart for Ubuntu and Debian

* fix: dependencies check logic of configtest

* feat: need-restarting on RedHat

* refactor: Process.ProcName to Process.Name

* feat: detect a systemd service name of need-restarting-process

* feat: detect a systemd service name of need-restarting-process on Ubuntu

* feat: fill a service name of need-restarting-process, init-system

* Support NVD JSON and CVSS3 of JVN (#605)

* fix: compile errors

* fix: Show CVSS3 on TUI

* fix: test cases

* fix: Avoid null in JSON

* Fix maxCvssScore (#621)

* Fix maxCvssScore

* Update vulninfos.go

* fix(init): remove unnecessary log initialization

* refactor(nvd): use only json feed if exists json data. if not, use xml feed

* fix(scan): make Confidence slice

* feat(CWE): Display CWE name to TUI

* feat(cwe): import CWE defs in Japanese

* feat(cwe): add OWASP Top 10 ranking to CWE if applicable

* feat(scan): add -fast-root mode, implement scan/amazon.go

* refactor(const): change const name JVN to Jvn

* feat(scan): add -fast-root mode, implement scan/centos.go

* refactor(dep): update deps

* fix(amazon): deps check

* feat(scan): add -fast-root mode, implement scan/rhel.go

* feat(scan): add -fast-root mode, implement scan/oracle.go

* fix complile err

* feat(scan): add -fast-root mode, implement scan/debian.go

* fix testcase

* fix(amazon): scan using yum

* fix(configtest): change error message, status when no scannnable servers

* Fix(scan): detect init process logic

* fix(tui): display cvss as table format

* fix(scan): parse a output of reboot-notifier on CentOS6.9

* fix(tui): don't display score, vector when score is zero

* fix(scan): add -offline mode to suse scanner

* fix(scan): fix help message

* feat(scan): enable to define scan mode for each servers in config.toml #510

* refactor(config): chagne cpeNames to cpeURIs

* refactor(config): change dependencyCheckXMLPath to owaspDCXMLPath

* fix(config): containers -> containersIncluded, Excluded, containerType

* feature(report): enable to define cpeURIs for each contaner

* feature(report): enable to specify owasp dc xml path for each container

* fix(discover): fix a template displayed at the end of discover

* feature(report): add ignorePkgsRegexp #665

* feature(report): enable to define ignoreCves for each container #666

* fix(report): Displayed nothing in TUI detail area when CweID is nil

* Gopkg.toml diet

* feat(server): support server mode (#678)

* feat(server): support server mode

* Lock go version

* Use the latest kernel release among the installed release when the running kernel release is unknown

* Add TestViaHTTP

* Set logger to go-cve-dictionary client

* Add -to-localfile

* Add -to-http option to report

* Load -to-http conf from config.toml

* Support gost (#676)

* feat(gost): Support RedHat API

* feat(gost): Support Debian Security Tracker

* feat(db): display error msg when SQLite3 is locked at the beginning of reporting.

* feat(gost): TUI

* Only use RedHat information of installed packages

* feat(tui): show mitigation on TUI

* feat(gost): support redis backend

* fix test case

* fix nil pointer when db is nil

* fix(gost): detect vulns of src packages for Debian

* feat(gost): implement redis backend for gost redhat api

* feat(report): display fixState of unfixed pkgs

* fix(report): display distincted cweIDs

* feat(slack): display gost info

* feat(slack): display mitigation

* feat(report): display available patch state as fixed/total

* fix(tui): display - if source of reference is empty

* update deps

* fix(report): key in ScanResult JSON be lowerCamelcase.

* some keys to lower camel

* fix(configtest): dep check logic of yum-plugin-ps

* fix(tui): format

* feat(report): add -format-list option

* fix(report): -format-full-text

* fix(report): report -format-full-text

* fix(report): display v3 score detected by gost

* fix(scan): scan in fast mode if not defined in config.toml

* fix(gost): fetch RedHat data for fixed CVEs

* feat(report): show number of cves detected in each database

* fix(report): show new version as `Unknown` in offline and fast scan mode

* fix(report): fix num of upadtable and fixed

* fix(report): set `Not fixed yet` if packageStatus is empty

* refact(gost): make convertToModel public

* fix(test): fix test case

* update deps

* fix(report): include gost score in MaxCvssScore

* [WIP] feat(config): enable to set options in config.toml instead of cmd opt (#690)

* feat(config): enable to set options in config.toml instead of cmd opt

* fix(config): change Conf.Report.Slack to Conf.Slack

* fix(discover): change tempalte

* fix(report): fix config.toml auto-generate with -uuid

* Add endpoint for health check and change endpoint

* refact(cmd): refactor flag set

* fix(report): enable to specify opts with cmd arg and env value

* fix(scan): enable to parse the release version of amazon linux 2

* add(report) add -to-saas option (#695)

* add(report) add -to-saas option

* ignore other writer if -to-saas

* fix(saas) fix bug

* fix(scan): need-restarting needs internet connection

* fix(scan,configtest): check scan mode

* refactor(scan): change func name

* fix(suse): support offline mode, bug fix on AWS, zypper --no-color

* fix(tui): fix nil pointer when no vulns in tui

* feat(report): enable to define CPE FS format in config.toml

* fix(vet): fix warnings of go vet

* fix(travis): go version to 1.11

* update deps
2018-08-27 13:51:09 +09:00
Masayuki Matsuki
d785fc2a54 Lint (#700)
* adjust GNUmakefile by using ... wildcard

go command excludes vendored packages from ... wildcard Go1.9 or later

* fix vet warnings

* fmt
2018-08-26 21:22:37 +09:00
Kota Kanbe
ea800e04bc fix(report): generate report even if some scan-err-jsons are included #685 (#686) 2018-07-24 22:26:46 +09:00
kota kanbe
fe582ac635 Change GitHub templates 2018-07-19 10:04:31 +09:00
Takayuki Ushida
330edb3bce change copyright (#677) 2018-07-17 15:10:36 +09:00
Teppei Fukuda
212fec7115 Remove old Dockerfile (#684) 2018-07-12 21:02:59 +09:00
Teppei Fukuda
24d7021c47 Refactor Dockerfile (#683) 2018-07-12 20:28:18 +09:00
Kota Kanbe
e3a01ff6a8 fix(report): database is locked with SQLite3 backend #681 (#682) 2018-07-11 11:11:57 +09:00
Kota Kanbe
81f2ba8a46 fix(report): record not found on reporting with OVAL #679 (#680)
* fix(report): record not found on reporting with OVAL #679

* lock go version in .travis.yml
2018-07-10 15:14:35 +09:00
Kota Kanbe
9e9370b178 refactor(suse): add testcase for detectSUSE (#675)
* refactor(suse): add testcase for detectSUSE
2018-06-25 14:46:41 +09:00
jenningsloy318
ced6114a95 pull request to add SLES variant OS SLES_SAP support (#672)
* add SLES_SAP fix

* add SLES_SAP version regexp
2018-06-25 14:34:40 +09:00
Teppei Fukuda
3144faae5d feat(syslog): add all CVSS scores/vectors (#664) 2018-06-06 20:56:56 +09:00
Teppei Fukuda
8960c67a82 fix(report): use CVSS score not calculated from severity preferentially (#663) 2018-06-06 18:58:24 +09:00
Teppei Fukuda
f8ca924434 Add title to syslog (#662) 2018-06-06 10:36:59 +09:00
Kota Kanbe
399a08775e feat(scan): add -ssh-config option #417 (#660) 2018-05-31 12:39:46 +09:00
Zsolt
92f36ca558 Add missing ca-certificates, needed for slack webhook (#657) 2018-05-24 10:16:13 +09:00
Zsolt
3dcc58205a Move to alpine based docker images (#643) 2018-05-23 15:32:05 +09:00
Kota Kanbe
09779962cf Fix(reporting): NotFixedYet of SourcePackage in OVAL match on Debian and Ubuntu (#656)
* fix(refactoring): oval

* Fix(reporting): NotFixedYet of SourcePackage in OVAL match on Debian and Ubuntu #655
2018-05-22 18:53:08 +09:00
Kota Kanbe
9cc78770a3 fix(configtest): Only warning when reboot-notifier is not installed on Debian (#654) 2018-05-21 14:57:05 +09:00
Zsolt
f653ca9131 Don't check reboot-notifier package for debian containers (#642) 2018-05-21 14:11:59 +09:00
Teppei Fukuda
6f9fd91849 Send logs via syslog when no CVE-IDs found (#646) 2018-05-17 12:04:23 +09:00
Teppei Fukuda
cb1aec4fc0 Add scanned_at into syslog report (#641) 2018-05-11 11:17:45 +09:00
Kota Kanbe
7cebaf8a76 Use servername for SSH ControlPath filename (#640) 2018-05-09 16:45:03 +09:00
Kota Kanbe
241c943424 fix(tui): show CVSS severity on TUI for Ubuntu (#638)
* fix(tui): show CVSS severity on TUI for Ubuntu

* refactoring
2018-05-02 17:07:20 +09:00
kazuminn
d5d88d8cf0 Refactor stride (#637)
* refactor

* go fmt
2018-05-02 16:58:29 +09:00
nohararc
cf9d26068c Update README.md (#631)
fix typo.
2018-04-27 15:52:40 +09:00
Cyrille Hemidy
308a93dc72 misspell (#632)
* Update tomlloader.go

fix misspelling

* Update packages.go

fix misspelling

* Update scanresults.go

fix misspelling
2018-04-27 15:52:16 +09:00
kota kanbe
d6a7e65e4c [refactor]make fmt 2018-04-27 15:07:12 +09:00
kazuminn
e0a5c5d3b8 refactoring : hipchat (#635)
* refactoring
2018-04-27 15:04:35 +09:00
adachin
314f775243 Chatwork support (#634) 2018-04-27 14:59:58 +09:00
kazuminn
7a1644135a Stride support (#624) 2018-04-10 13:30:22 +09:00
Kota Kanbe
5076326589 Fix Amazon Linux 2 scanning (#630)
* fix(amazon2): fix OS version parse error
2018-04-10 11:53:11 +09:00
Kota Kanbe
ce56261b52 fix(redhat): fix detection method of changelog scan (#628)
fix(redhat, deepscan): fix detection method of changelog scan
2018-03-29 21:17:44 +09:00
Kota Kanbe
baa0e897b2 fix: a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at (#627)
* fix: a bug of diff logic when multiple oval-defs hav certain CVE-ID and same updated_at

Commented out beause a bug of diff logic when multiple oval defs has certain CVE-ID and same updated_at.
If these OVAL defs have different affected packages, this logic detects not-updated-CVE-ID as updated.
This logic will be uncommented after integration with ghost https://github.com/knqyf263/gost
2018-03-26 22:29:14 +09:00
Teppei Fukuda
1d49c0e1ce fix(scan): fix RHEL 5 (#626) 2018-03-26 17:40:39 +09:00
Teppei Fukuda
08755e446e fix(fmt): fix gofmt warn (#625) 2018-03-23 12:28:12 +09:00
Kota Kanbe
bb12d9dadb Add diff to TUI (#620)
* fix: change ControlPath to .vuls of SSH option (#618)

* feat: Add diff option to TUI
2018-03-16 15:18:10 +09:00
Kota Kanbe
fd1429fef0 Fix diff logic (#619)
* fix: change ControlPath to .vuls of SSH option (#618)

* fix: Bug of diff logic
2018-03-16 15:07:26 +09:00
kazuminn
d3c421a4a8 inform new release on diff option (#614)
inform new release on diff option (#614)
2018-03-15 13:30:33 +09:00
Kota Kanbe
0c919da4b1 fix: change ControlPath to .vuls of SSH option (#618) 2018-03-14 16:39:17 +09:00
Kota Kanbe
9afbf1255f feat: Add -vvv option to scan cmd (#617) 2018-03-14 12:18:03 +09:00
Kota Kanbe
50b105c4af fix: SSH session multiplexing (#616) 2018-03-13 22:35:25 +09:00
kazuminn
028508c1f7 fix link nvd on hipchat (#613) 2018-03-13 12:32:55 +09:00
Kota Kanbe
f0137a3695 feat: Display pkg information to slack notification #611 (#612) 2018-03-09 10:26:41 +09:00
Kota Kanbe
e6d3a1718c fix: validation for reporting (#610) 2018-03-07 14:01:52 +09:00
Kota Kanbe
86ba551e07 fix: remove a validation of hipchat (#609) 2018-03-07 05:21:57 +09:00
kazuminn
26418be937 hipchat support (#593)
* first commit

* hipchat conf

* hipchat conf
2018-03-06 17:40:21 +09:00
Kota Kanbe
092a19bdc1 fix: bug of report -diff option (#607) 2018-03-06 16:50:09 +09:00
Kota Kanbe
6d3398574c fix: support CentOS cloud image (#606)
https://bugzilla.redhat.com/show_bug.cgi?id=1332025
2018-03-06 14:10:21 +09:00
Teppei Fukuda
b08969ad89 Support a reporting via Syslog (#604)
* Support a reporting via syslog

* Update dependencies
2018-02-27 20:38:34 +09:00
Kota Kanbe
0653656526 fix: add some logging for goval-dictionary (#603) 2018-02-19 13:30:42 +09:00
Teppei Fukuda
7a5793c562 Add IP address to scan results (#602) 2018-02-19 12:50:00 +09:00
Emilien Kenler
562ff7807d Support AWS S3 Server-Side Encryption (#597)
* Support AWS S3 Server-Side Encryption

* Improve documentation for aws-s3-server-side-encryption
2018-02-12 11:26:23 +09:00
Kota Kanbe
7971bdf7f7 fix: Kindness error message in reporting (#601) 2018-02-12 10:57:09 +09:00
Kota Kanbe
d926b7fd6d Update deps (#592) 2018-01-24 01:02:02 +09:00
Kota Kanbe
c00404793a Add offline option to scan and configtest (#588)
Add offline option to scan and configtest
2018-01-19 01:07:44 +09:00
Kota Kanbe
a0e0ee6c1e Move README to Vulsdoc https://vuls.io (#586) 2018-01-17 18:03:37 +09:00
Kota Kanbe
4ccbee705b If the OWASP dc XML does not exist, continue reporting after warning #580 (#582) 2018-01-16 17:08:12 +09:00
Mai MISHIRO
db43d55b2c Fixed panic occurred when blank line continued in changelog (#569) 2018-01-05 10:23:44 +09:00
~Stack~
5a3a333eec Fixed Typo (#574) 2018-01-05 10:20:35 +09:00
nakamurakyo
039edf1616 fix typo(BackSpace) in README.ja.md (#576) 2018-01-05 10:20:06 +09:00
Kota Kanbe
47498bbf23 Fix a bug of sending to closed socket while oval access via HTTP #578 (#579) 2018-01-05 10:12:21 +09:00
Yoshikazu Aoyama
cc28bf4ae2 fix typo in reports/s3.go (#573) 2017-12-27 22:30:26 +09:00
Mai MISHIRO
0e8736045e LXC container support without LXD (#552)
* LXC container support without LXD

* Fix: LXC required root privilege

* Update README
2017-12-18 22:54:32 +09:00
Kota Kanbe
19b581edef Support Amazon Linux2 (#562) 2017-12-15 20:07:49 +09:00
Mai MISHIRO
295f6656d9 Fix #548 and #557 - RHEL's Fast Scan no longer required internet connection and root privilege (#559) 2017-12-15 19:34:10 +09:00
Mai MISHIRO
1214d8c14d Change error handling of "Reboot Required" detection (#556) 2017-12-12 17:03:42 +09:00
Mai MISHIRO
b4cd96fc9a Fix some RPM related commands failed in the container (#554) 2017-12-12 12:14:57 +09:00
Davor Kapsa
3238a9b898 travis: update go version (#555) 2017-12-11 14:35:13 +09:00
Mai MISHIRO
c0f66320f6 Add more kernel related packages (Fix #541) (#551) 2017-12-11 14:32:20 +09:00
Kota Kanbe
383220f384 Remove empty CveContent output to JSON with Alpine Linux scan (#550) 2017-12-04 12:52:32 +09:00
Takayuki Ushida
76a9c37e6b Update README (#547) 2017-12-02 00:47:49 +09:00
237 changed files with 60159 additions and 22562 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
.dockerignore
Dockerfile
vendor/
cve.sqlite3*
oval.sqlite3*
setup/
img/

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: kotakanbe

View File

@@ -1,36 +0,0 @@
# Environment
## Vuls
Hash : ____
To check the commit hash of HEAD
$ vuls -v
or
$ cd $GOPATH/src/github.com/future-architect/vuls
$ git rev-parse --short HEAD
## OS
- Target Server: Write here
- Vuls Server: Write here
## Go
- Go version: here
# Current Output
Please re-run the command using ```-debug``` and provide the output below.
# Addition Details
Can you also please fill in each of the remaining sections.
## Expected Behavior
## Actual Behavior
## Steps to reproduce the behaviour

43
.github/ISSUE_TEMPLATE/BUG_REPORT.md vendored Normal file
View File

@@ -0,0 +1,43 @@
---
name: Bug Report
labels: bug
about: If something isn't working as expected.
---
# What did you do? (required. The issue will be **closed** when not provided.)
# What did you expect to happen?
# What happened instead?
* Current Output
Please re-run the command using ```-debug``` and provide the output below.
# Steps to reproduce the behaviour
# Configuration (**MUST** fill this out):
* Go version (`go version`):
* Go environment (`go env`):
* Vuls environment:
Hash : ____
To check the commit hash of HEAD
$ vuls -v
or
$ cd $GOPATH/src/github.com/future-architect/vuls
$ git rev-parse --short HEAD
* config.toml:
* command:

View File

@@ -0,0 +1,9 @@
---
name: Feature Request
labels: enhancement
about: I have a suggestion (and might want to implement myself)!
---
<!--
If this is a FEATURE REQUEST, request format does not matter!
-->

View File

@@ -0,0 +1,10 @@
---
name: Support Question
labels: question
about: If you have a question about Vuls.
---
<!--
If you have a trouble, feel free to ask.
Make sure you're not asking duplicate question by searching on the issues lists.
-->

7
.github/ISSUE_TEMPLATE/VULSREPO.md vendored Normal file
View File

@@ -0,0 +1,7 @@
---
name: Vuls Repo
labels: vulsrepo
about: If something isn't working as expected.
---

View File

@@ -1,14 +1,26 @@
## What did you implement:
Closes #XXXXX
If this Pull Request is work in progress, Add a prefix of “[WIP]” in the title.
## How did you implement it:
# What did you implement:
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
## How can we verify it:
Fixes # (issue)
## Type of change
## Todos:
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
# Checklist:
You don't have to satisfy all of the following.
- [ ] Write tests
@@ -21,4 +33,8 @@ You don't have to satisfy all of the following.
- [ ] Update the messages below
***Is this ready for review?:*** NO
***Is it a breaking change?:*** NO
# Reference
* https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '32 20 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

29
.github/workflows/golangci.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- master
pull_request:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.32
args: --timeout=10m
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

31
.github/workflows/goreleaser.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: goreleaser
on:
push:
tags:
- '*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Unshallow
run: git fetch --prune --unshallow
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

21
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Test
on: [pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: 1.16.x
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Test
run: make test

22
.github/workflows/tidy.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: go-mod-tidy-pr
on:
schedule:
- cron: "0 0 * * 1" # Weekly build
jobs:
go-mod-tidy-pr:
name: go-mod-tidy-pr
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run go-mod-tidy-pr
uses: sue445/go-mod-tidy-pr@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_user_name: kotakanbe
git_user_email: kotakanbe@gmail.com
go_version: 1.16.x

7
.gitignore vendored
View File

@@ -1,4 +1,3 @@
vuls
.vscode
*.txt
*.sqlite3*
@@ -10,5 +9,9 @@ issues/
vendor/
log/
results/
*config.toml
config.toml
!setup/docker/*
.DS_Store
dist/
.idea
vuls.*

17
.golangci.yml Normal file
View File

@@ -0,0 +1,17 @@
name: golang-ci
linters-settings:
errcheck:
#exclude: /path/to/file.txt
linters:
disable-all: true
enable:
- goimports
- golint
- govet
- misspell
- errcheck
- staticcheck
- prealloc
- ineffassign

View File

@@ -1,23 +1,113 @@
project_name: vuls
env:
- GO111MODULE=on
release:
github:
owner: future-architect
name: vuls
builds:
- goos:
- id: vuls
goos:
- linux
goarch:
- amd64
main: .
ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}}
main: ./cmd/vuls/main.go
flags:
- -a
ldflags:
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
binary: vuls
archive:
- id: vuls-scanner
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
main: ./cmd/scanner/main.go
flags:
- -a
tags:
- scanner
ldflags:
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
binary: vuls-scanner
- id: trivy-to-vuls
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
tags:
- scanner
main: ./contrib/trivy/cmd/main.go
binary: trivy-to-vuls
- id: future-vuls
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- 386
- amd64
- arm
- arm64
flags:
- -a
tags:
- scanner
main: ./contrib/future-vuls/cmd/main.go
binary: future-vuls
archives:
- id: vuls
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- vuls
format: tar.gz
files:
- LICENSE
- README*
- CHANGELOG.md
- id: vuls-scanner
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- vuls-scanner
format: tar.gz
files:
- LICENSE
- README*
- CHANGELOG.md
- id: trivy-to-vuls
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- trivy-to-vuls
format: tar.gz
files:
- LICENSE
- README*
- CHANGELOG.md
- id: future-vuls
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- future-vuls
format: tar.gz
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
files:
- LICENSE
- NOTICE
- README*
- CHANGELOG.md
snapshot:

View File

@@ -1,7 +0,0 @@
language: go
go:
- 1.8
after_success:
- test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash

35
Dockerfile Normal file
View File

@@ -0,0 +1,35 @@
FROM golang:alpine as builder
RUN apk add --no-cache \
git \
make \
gcc \
musl-dev
ENV REPOSITORY github.com/future-architect/vuls
COPY . $GOPATH/src/$REPOSITORY
RUN cd $GOPATH/src/$REPOSITORY && make install
FROM alpine:3.13
LABEL maintainer hikachan sadayuki-matsuno
ENV LOGDIR /var/log/vuls
ENV WORKDIR /vuls
RUN apk add --no-cache \
openssh-client \
ca-certificates \
git \
nmap \
&& mkdir -p $WORKDIR $LOGDIR
COPY --from=builder /go/bin/vuls /usr/local/bin/
VOLUME ["$WORKDIR", "$LOGDIR"]
WORKDIR $WORKDIR
ENV PWD $WORKDIR
ENTRYPOINT ["vuls"]
CMD ["--help"]

View File

@@ -1,11 +1,9 @@
.PHONY: \
dep \
depup \
build \
install \
all \
vendor \
lint \
lint \
vet \
fmt \
fmtcheck \
@@ -15,57 +13,231 @@
clean
SRCS = $(shell git ls-files '*.go')
PKGS = ./. ./cache ./commands ./config ./models ./oval ./report ./scan ./util
PKGS = $(shell go list ./...)
VERSION := $(shell git describe --tags --abbrev=0)
REVISION := $(shell git rev-parse --short HEAD)
LDFLAGS := -X 'main.version=$(VERSION)' \
-X 'main.revision=$(REVISION)'
BUILDTIME := $(shell date "+%Y%m%d_%H%M%S")
LDFLAGS := -X 'github.com/future-architect/vuls/config.Version=$(VERSION)' \
-X 'github.com/future-architect/vuls/config.Revision=build-$(BUILDTIME)_$(REVISION)'
GO := GO111MODULE=on go
CGO_UNABLED := CGO_ENABLED=0 go
GO_OFF := GO111MODULE=off go
all: dep build test
dep:
go get -u github.com/golang/dep/...
dep ensure
all: build
depup:
go get -u github.com/golang/dep/...
dep ensure -update
build: ./cmd/vuls/main.go pretest fmt
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
build: main.go dep pretest
go build -ldflags "$(LDFLAGS)" -o vuls $<
b: ./cmd/vuls/main.go
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
install: main.go dep pretest
go install -ldflags "$(LDFLAGS)"
install: ./cmd/vuls/main.go
$(GO) install -ldflags "$(LDFLAGS)" ./cmd/vuls
build-scanner: ./cmd/scanner/main.go
$(CGO_UNABLED) build -tags=scanner -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/scanner
install-scanner: ./cmd/scanner/main.go
$(CGO_UNABLED) install -tags=scanner -ldflags "$(LDFLAGS)" ./cmd/scanner
lint:
@ go get -v github.com/golang/lint/golint
$(foreach file,$(SRCS),golint $(file) || exit;)
$(GO_OFF) get -u golang.org/x/lint/golint
golint $(PKGS)
vet:
# @-go get -v golang.org/x/tools/cmd/vet
echo $(PKGS) | xargs go vet || exit;
echo $(PKGS) | xargs env $(GO) vet || exit;
fmt:
gofmt -s -w $(SRCS)
mlint:
$(foreach file,$(SRCS),gometalinter $(file) || exit;)
fmtcheck:
$(foreach file,$(SRCS),gofmt -s -d $(file);)
pretest: lint vet fmtcheck
test: pretest
go install
echo $(PKGS) | xargs go test -cover -v || exit;
test:
$(GO) test -cover -v ./... || exit;
unused :
unused:
$(foreach pkg,$(PKGS),unused $(pkg);)
cov:
@ go get -v github.com/axw/gocov/gocov
@ go get golang.org/x/tools/cmd/cover
gocov test | gocov report
gocov test -v ./... | gocov report
clean:
echo $(PKGS) | xargs go clean || exit;
# trivy-to-vuls
build-trivy-to-vuls: pretest fmt
$(GO) build -o trivy-to-vuls contrib/trivy/cmd/*.go
# future-vuls
build-future-vuls: pretest fmt
$(GO) build -o future-vuls contrib/future-vuls/cmd/*.go
# integration-test
BASE_DIR := '${PWD}/integration/results'
# $(shell mkdir -p ${BASE_DIR})
NOW=$(shell date --iso-8601=seconds)
NOW_JSON_DIR := '${BASE_DIR}/$(NOW)'
ONE_SEC_AFTER=$(shell date -d '+1 second' --iso-8601=seconds)
ONE_SEC_AFTER_JSON_DIR := '${BASE_DIR}/$(ONE_SEC_AFTER)'
LIBS := 'gemfile' 'pipfile' 'poetry' 'composer' 'packagelock' 'yarn' 'cargo' 'gomod'
diff:
# git clone git@github.com:vulsio/vulsctl.git
# cd vulsctl/docker
# ./update-all.sh
# cd /path/to/vuls
# vim integration/int-config.toml
# ln -s vuls vuls.new
# ln -s oldvuls vuls.old
# make int
# (ex. test 10 times: for i in `seq 10`; do make int ARGS=-quiet ; done)
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
mv ${BASE_DIR} /tmp/${NOW}
endif
mkdir -p ${NOW_JSON_DIR}
sleep 1
./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
cp integration/data/results/*.json ${NOW_JSON_DIR}
./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
sleep 1
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${ONE_SEC_AFTER}
$(call sed-d)
- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
$(call count-cve)
diff-redis:
# docker network create redis-nw
# docker run --name redis -d --network redis-nw -p 127.0.0.1:6379:6379 redis
# git clone git@github.com:vulsio/vulsctl.git
# cd vulsctl/docker
# ./update-all-redis.sh
# (or export DOCKER_NETWORK=redis-nw; cd /home/ubuntu/vulsctl/docker; ./update-all.sh --dbtype redis --dbpath "redis://redis/0")
# vim integration/int-redis-config.toml
# ln -s vuls vuls.new
# ln -s oldvuls vuls.old
# make int-redis
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
mv ${BASE_DIR} /tmp/${NOW}
endif
mkdir -p ${NOW_JSON_DIR}
sleep 1
./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
cp integration/data/results/*.json ${NOW_JSON_DIR}
./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${NOW}
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
sleep 1
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
$(call sed-d)
- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
$(call count-cve)
diff-rdb-redis:
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
mv ${BASE_DIR} /tmp/${NOW}
endif
mkdir -p ${NOW_JSON_DIR}
sleep 1
# new vs new
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
cp integration/data/results/*.json ${NOW_JSON_DIR}
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
sleep 1
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
$(call sed-d)
- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
$(call count-cve)
head= $(shell git rev-parse HEAD)
prev= $(shell git rev-parse HEAD^)
branch=$(shell git rev-parse --abbrev-ref HEAD)
build-integration:
git stash
# buld HEAD
git checkout ${head}
make build
mv -f ./vuls ./vuls.${head}
# HEAD^
git checkout ${prev}
make build
mv -f ./vuls ./vuls.${prev}
# master
git checkout master
make build
mv -f ./vuls ./vuls.master
# working tree
git checkout ${branch}
git stash apply stash@\{0\}
make build
# for integration testing, vuls.new and vuls.old needed.
# ex)
# $ ln -s ./vuls ./vuls.new
# $ ln -s ./vuls.${head} ./vuls.old
# or
# $ ln -s ./vuls.${prev} ./vuls.old
# then
# $ make diff
# $ make diff-redis
# $ make diff-rdb-redis
define sed-d
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/scannedAt/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/scannedAt/d' {} \;
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/reportedAt/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/reportedAt/d' {} \;
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/"Type":/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/"Type":/d' {} \;
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/"SQLite3Path":/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/"SQLite3Path":/d' {} \;
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/reportedRevision/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/reportedRevision/d' {} \;
find ${NOW_JSON_DIR} -type f -exec sed -i -e '/scannedRevision/d' {} \;
find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/scannedRevision/d' {} \;
endef
define count-cve
for jsonfile in ${NOW_JSON_DIR}/*.json ; do \
echo $$jsonfile; cat $$jsonfile | jq ".scannedCves | length" ; \
done
for jsonfile in ${ONE_SEC_AFTER_JSON_DIR}/*.json ; do \
echo $$jsonfile; cat $$jsonfile | jq ".scannedCves | length" ; \
done
endef

278
Gopkg.lock generated
View File

@@ -1,278 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/Azure/azure-sdk-for-go"
packages = ["storage"]
revision = "7692b0cef22674113fcf71cc17ac3ccc1a7fef48"
version = "v11.2.2-beta"
[[projects]]
name = "github.com/Azure/go-autorest"
packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
revision = "c67b24a8e30d876542a85022ebbdecf0e5a935e8"
version = "v9.4.1"
[[projects]]
name = "github.com/BurntSushi/toml"
packages = ["."]
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
version = "v0.3.0"
[[projects]]
name = "github.com/asaskevich/govalidator"
packages = ["."]
revision = "4918b99a7cb949bb295f3c7bbaf24b577d806e35"
version = "v6"
[[projects]]
name = "github.com/aws/aws-sdk-go"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/s3","service/sts"]
revision = "e4f7e38b704e3ed0acc4a7f8196b777696f6f1f3"
version = "v1.12.30"
[[projects]]
name = "github.com/boltdb/bolt"
packages = ["."]
revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8"
version = "v1.3.1"
[[projects]]
name = "github.com/cenkalti/backoff"
packages = ["."]
revision = "61153c768f31ee5f130071d08fc82b85208528de"
version = "v1.1.0"
[[projects]]
name = "github.com/cheggaaa/pb"
packages = ["."]
revision = "657164d0228d6bebe316fdf725c69f131a50fb10"
version = "v1.0.18"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
version = "v1.32.0"
[[projects]]
name = "github.com/go-redis/redis"
packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
revision = "e5e021257bfc6d5fbf0beac33e30194311fb189f"
version = "v6.7.4"
[[projects]]
name = "github.com/go-sql-driver/mysql"
packages = ["."]
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
version = "v1.3"
[[projects]]
branch = "master"
name = "github.com/google/subcommands"
packages = ["."]
revision = "ce3d4cfc062faac7115d44e5befec8b5a08c3faa"
[[projects]]
branch = "master"
name = "github.com/gosuri/uitable"
packages = [".","util/strutil","util/wordwrap"]
revision = "36ee7e946282a3fb1cfecd476ddc9b35d8847e42"
[[projects]]
branch = "master"
name = "github.com/howeyc/gopass"
packages = ["."]
revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
[[projects]]
name = "github.com/jinzhu/gorm"
packages = [".","dialects/mysql","dialects/postgres","dialects/sqlite"]
revision = "5174cc5c242a728b435ea2be8a2f7f998e15429b"
version = "v1.0"
[[projects]]
branch = "master"
name = "github.com/jinzhu/inflection"
packages = ["."]
revision = "1c35d901db3da928c72a72d8458480cc9ade058f"
[[projects]]
name = "github.com/jmespath/go-jmespath"
packages = ["."]
revision = "0b12d6b5"
[[projects]]
name = "github.com/jroimartin/gocui"
packages = ["."]
revision = "4e9ce9a8e26f2ef33dfe297dbdfca148733b6b9b"
version = "v0.3.0"
[[projects]]
branch = "master"
name = "github.com/k0kubun/pp"
packages = ["."]
revision = "e057ee7a28277be4d2af303443b6da377768181f"
[[projects]]
branch = "master"
name = "github.com/knqyf263/go-deb-version"
packages = ["."]
revision = "9865fe14d09b1c729188ac810466dde90f897ee3"
[[projects]]
branch = "master"
name = "github.com/knqyf263/go-rpm-version"
packages = ["."]
revision = "74609b86c936dff800c69ec89fcf4bc52d5f13a4"
[[projects]]
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
packages = ["config","db","jvn","log","models","nvd","util"]
revision = "a64c5fc25cd9669b213986e1a02831c71a5c601d"
[[projects]]
name = "github.com/kotakanbe/go-pingscanner"
packages = ["."]
revision = "641dc2cc2d3cbf295dad356667b74c69bcbd6f70"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"
packages = ["config","db","db/rdb","log","models"]
revision = "dca4f21940cbf2245a50817177fed7eb6793e72c"
[[projects]]
branch = "master"
name = "github.com/kotakanbe/logrus-prefixed-formatter"
packages = ["."]
revision = "75edb2e85a38873f0318be05a458446681d1022f"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [".","hstore","oid"]
revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
revision = "ed69081a91fd053f17672236b0dd52ba7485e1a3"
version = "v1.4.0"
[[projects]]
branch = "master"
name = "github.com/mgutz/ansi"
packages = ["."]
revision = "9520e82c474b0a04dd04f8a40959027271bab992"
[[projects]]
branch = "master"
name = "github.com/moul/http2curl"
packages = ["."]
revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d"
[[projects]]
name = "github.com/nlopes/slack"
packages = ["."]
revision = "c86337c0ef2486a15edd804355d9c73d2f2caed1"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
revision = "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43"
[[projects]]
name = "github.com/parnurzeal/gorequest"
packages = ["."]
revision = "a578a48e8d6ca8b01a3b18314c43c6716bb5f5a3"
version = "v0.2.15"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/rifflock/lfshook"
packages = ["."]
revision = "6844c808343cb8fa357d7f141b1b990e05d24e41"
version = "1.7"
[[projects]]
name = "github.com/satori/uuid"
packages = ["."]
revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "95cd2b9c79aa5e72ab0bc69b7ccc2be15bf850f6"
[[projects]]
branch = "master"
name = "github.com/ymomoi/goval-parser"
packages = ["oval"]
revision = "0a0be1dd9d0855b50be0be5a10ad3085382b6d59"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","idna","publicsuffix","websocket"]
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "0ac51a24ef1c37380f98ba8b98f56e3bffd59850"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "58ae46498625e705c582d70591148e07dbac30bc4f75cadef3d2fda514cd5099"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,94 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/BurntSushi/toml"
version = "0.3.0"
[[constraint]]
name = "github.com/asaskevich/govalidator"
version = "6.0.0"
[[constraint]]
name = "github.com/boltdb/bolt"
version = "1.3.1"
[[constraint]]
name = "github.com/cenkalti/backoff"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/google/subcommands"
[[constraint]]
branch = "master"
name = "github.com/gosuri/uitable"
[[constraint]]
branch = "master"
name = "github.com/howeyc/gopass"
[[constraint]]
name = "github.com/jroimartin/gocui"
version = "0.3.0"
[[constraint]]
branch = "master"
name = "github.com/k0kubun/pp"
[[constraint]]
branch = "master"
name = "github.com/knqyf263/go-deb-version"
[[constraint]]
branch = "master"
name = "github.com/knqyf263/go-rpm-version"
[[constraint]]
name = "github.com/kotakanbe/go-pingscanner"
version = "0.1.0"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/logrus-prefixed-formatter"
[[constraint]]
name = "github.com/parnurzeal/gorequest"
version = "0.2.15"
[[constraint]]
name = "github.com/rifflock/lfshook"
version = "1.7.0"
[[constraint]]
branch = "master"
name = "github.com/sirupsen/logrus"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"

153
LICENSE
View File

@@ -1,23 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Vuls Copyright (C) 2016 Future Architect, Inc. Japan.
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

2
NOTICE
View File

@@ -1,2 +0,0 @@
Vuls Copyright (C) 2016 Future Architect, Inc. Japan.

File diff suppressed because it is too large Load Diff

1996
README.md

File diff suppressed because it is too large Load Diff

48
cache/bolt.go vendored
View File

@@ -1,42 +1,25 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cache
import (
"encoding/json"
"fmt"
"time"
"github.com/boltdb/bolt"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/util"
"github.com/sirupsen/logrus"
"golang.org/x/xerrors"
)
// Bolt holds a pointer of bolt.DB
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
type Bolt struct {
Path string
Log *logrus.Entry
Log logging.Logger
db *bolt.DB
}
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
func SetupBolt(path string, l *logrus.Entry) error {
func SetupBolt(path string, l logging.Logger) error {
l.Infof("Open boltDB: %s", path)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
@@ -64,12 +47,12 @@ func (b Bolt) Close() error {
return b.db.Close()
}
// CreateBucketIfNotExists creates a buket that is specified by arg.
// CreateBucketIfNotExists creates a bucket that is specified by arg.
func (b *Bolt) createBucketIfNotExists(name string) error {
return b.db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(name))
if err != nil {
return fmt.Errorf("Failed to create bucket: %s", err)
return xerrors.Errorf("Failed to create bucket: %w", err)
}
return nil
})
@@ -98,7 +81,7 @@ func (b Bolt) RefreshMeta(meta Meta) error {
meta.CreatedAt = time.Now()
jsonBytes, err := json.Marshal(meta)
if err != nil {
return fmt.Errorf("Failed to marshal to JSON: %s", err)
return xerrors.Errorf("Failed to marshal to JSON: %w", err)
}
return b.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
@@ -110,11 +93,11 @@ func (b Bolt) RefreshMeta(meta Meta) error {
})
}
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
// EnsureBuckets puts a Meta information and create a bucket that holds changelogs.
func (b Bolt) EnsureBuckets(meta Meta) error {
jsonBytes, err := json.Marshal(meta)
if err != nil {
return fmt.Errorf("Failed to marshal to JSON: %s", err)
return xerrors.Errorf("Failed to marshal to JSON: %w", err)
}
return b.db.Update(func(tx *bolt.Tx) error {
b.Log.Debugf("Put to meta: %s", meta.Name)
@@ -158,12 +141,12 @@ func (b Bolt) PrettyPrint(meta Meta) error {
})
}
// GetChangelog get the changelgo of specified packName from the Bucket
// GetChangelog get the changelog of specified packName from the Bucket
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
err = b.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
return fmt.Errorf("Failed to get Bucket: %s", servername)
return xerrors.Errorf("Failed to get Bucket: %s", servername)
}
v := bkt.Get([]byte(packName))
if v == nil {
@@ -176,16 +159,13 @@ func (b Bolt) GetChangelog(servername, packName string) (changelog string, err e
return
}
// PutChangelog put the changelgo of specified packName into the Bucket
// PutChangelog put the changelog of specified packName into the Bucket
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
return b.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
return fmt.Errorf("Failed to get Bucket: %s", servername)
return xerrors.Errorf("Failed to get Bucket: %s", servername)
}
if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
return err
}
return nil
return bkt.Put([]byte(packName), []byte(changelog))
})
}

29
cache/bolt_test.go vendored
View File

@@ -1,20 +1,3 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cache
import (
@@ -24,8 +7,8 @@ import (
"github.com/boltdb/bolt"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/sirupsen/logrus"
)
const path = "/tmp/vuls-test-cache-11111111.db"
@@ -46,7 +29,7 @@ var meta = Meta{
}
func TestSetupBolt(t *testing.T) {
log := logrus.NewEntry(&logrus.Logger{})
log := logging.NewNormalLogger()
err := SetupBolt(path, log)
if err != nil {
t.Errorf("Failed to setup bolt: %s", err)
@@ -63,7 +46,7 @@ func TestSetupBolt(t *testing.T) {
t.Errorf("Failed to open bolt: %s", err)
}
db.View(func(tx *bolt.Tx) error {
_ = db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(metabucket))
if bkt == nil {
t.Errorf("Meta bucket nof found")
@@ -74,7 +57,7 @@ func TestSetupBolt(t *testing.T) {
}
func TestEnsureBuckets(t *testing.T) {
log := logrus.NewEntry(&logrus.Logger{})
log := logging.NewNormalLogger()
if err := SetupBolt(path, log); err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}
@@ -104,7 +87,7 @@ func TestEnsureBuckets(t *testing.T) {
if err != nil {
t.Errorf("Failed to open bolt: %s", err)
}
db.View(func(tx *bolt.Tx) error {
_ = db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
t.Errorf("Meta bucket nof found")
@@ -115,7 +98,7 @@ func TestEnsureBuckets(t *testing.T) {
func TestPutGetChangelog(t *testing.T) {
clog := "changelog-text"
log := logrus.NewEntry(&logrus.Logger{})
log := logging.NewNormalLogger()
if err := SetupBolt(path, log); err != nil {
t.Errorf("Failed to setup bolt: %s", err)
}

17
cache/db.go vendored
View File

@@ -1,20 +1,3 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cache
import (

36
cmd/scanner/main.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"flag"
"fmt"
"os"
"context"
"github.com/future-architect/vuls/config"
commands "github.com/future-architect/vuls/subcmds"
"github.com/google/subcommands"
)
func main() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&commands.DiscoverCmd{}, "discover")
subcommands.Register(&commands.ScanCmd{}, "scan")
subcommands.Register(&commands.HistoryCmd{}, "history")
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
subcommands.Register(&commands.SaaSCmd{}, "saas")
var v = flag.Bool("v", false, "Show version")
flag.Parse()
if *v {
fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
os.Exit(int(subcommands.ExitSuccess))
}
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}

38
cmd/vuls/main.go Normal file
View File

@@ -0,0 +1,38 @@
package main
import (
"flag"
"fmt"
"os"
"context"
"github.com/future-architect/vuls/config"
commands "github.com/future-architect/vuls/subcmds"
"github.com/google/subcommands"
)
func main() {
subcommands.Register(subcommands.HelpCommand(), "")
subcommands.Register(subcommands.FlagsCommand(), "")
subcommands.Register(subcommands.CommandsCommand(), "")
subcommands.Register(&commands.DiscoverCmd{}, "discover")
subcommands.Register(&commands.TuiCmd{}, "tui")
subcommands.Register(&commands.ScanCmd{}, "scan")
subcommands.Register(&commands.HistoryCmd{}, "history")
subcommands.Register(&commands.ReportCmd{}, "report")
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
subcommands.Register(&commands.ServerCmd{}, "server")
var v = flag.Bool("v", false, "Show version")
flag.Parse()
if *v {
fmt.Printf("vuls-%s-%s\n", config.Version, config.Revision)
os.Exit(int(subcommands.ExitSuccess))
}
ctx := context.Background()
os.Exit(int(subcommands.Execute(ctx)))
}

View File

@@ -1,185 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"context"
"flag"
"os"
"path/filepath"
"github.com/google/subcommands"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/scan"
"github.com/future-architect/vuls/util"
)
// ConfigtestCmd is Subcommand
type ConfigtestCmd struct {
configPath string
logDir string
askKeyPassword bool
containersOnly bool
deep bool
sshNative bool
httpProxy string
timeoutSec int
debug bool
}
// Name return subcommand name
func (*ConfigtestCmd) Name() string { return "configtest" }
// Synopsis return synopsis
func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
// Usage return usage
func (*ConfigtestCmd) Usage() string {
return `configtest:
configtest
[-deep]
[-config=/path/to/config.toml]
[-log-dir=/path/to/log]
[-ask-key-password]
[-timeout=300]
[-ssh-external]
[-containers-only]
[-http-proxy=http://192.168.0.1:8080]
[-debug]
[SERVER]...
`
}
// SetFlags set flag
func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
f.BoolVar(&p.debug, "debug", false, "debug mode")
f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
f.BoolVar(
&p.askKeyPassword,
"ask-key-password",
false,
"Ask ssh privatekey password before scanning",
)
f.BoolVar(&p.deep, "deep", false, "Config test for deep scan mode")
f.StringVar(
&p.httpProxy,
"http-proxy",
"",
"http://proxy-url:port (default: empty)",
)
f.BoolVar(
&p.sshNative,
"ssh-native-insecure",
false,
"Use Native Go implementation of SSH. Default: Use the external command")
f.BoolVar(
&p.containersOnly,
"containers-only",
false,
"Test containers only. Default: Test both of hosts and containers")
}
// Execute execute
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
}
err = c.Load(p.configPath, keyPass)
if err != nil {
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
return subcommands.ExitUsageError
}
c.Conf.SSHNative = p.sshNative
c.Conf.HTTPProxy = p.httpProxy
c.Conf.ContainersOnly = p.containersOnly
c.Conf.Deep = p.deep
var servernames []string
if 0 < len(f.Args()) {
servernames = f.Args()
}
target := make(map[string]c.ServerInfo)
for _, arg := range servernames {
found := false
for servername, info := range c.Conf.Servers {
if servername == arg {
target[servername] = info
found = true
break
}
}
if !found {
util.Log.Errorf("%s is not in config", arg)
return subcommands.ExitUsageError
}
}
if 0 < len(servernames) {
c.Conf.Servers = target
}
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnConfigtest() {
return subcommands.ExitUsageError
}
util.Log.Info("Detecting Server/Container OS... ")
if err := scan.InitServers(p.timeoutSec); err != nil {
util.Log.Errorf("Failed to init servers: %s", err)
return subcommands.ExitFailure
}
util.Log.Info("Checking dependencies...")
scan.CheckDependencies(p.timeoutSec)
util.Log.Info("Checking sudo settings...")
scan.CheckIfSudoNoPasswd(p.timeoutSec)
scan.PrintSSHableServerNames()
return subcommands.ExitSuccess
}

View File

@@ -1,177 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"context"
"flag"
"fmt"
"os"
"strings"
"text/template"
"github.com/google/subcommands"
ps "github.com/kotakanbe/go-pingscanner"
"github.com/sirupsen/logrus"
)
// DiscoverCmd is Subcommand of host discovery mode
type DiscoverCmd struct {
}
// Name return subcommand name
func (*DiscoverCmd) Name() string { return "discover" }
// Synopsis return synopsis
func (*DiscoverCmd) Synopsis() string { return "Host discovery in the CIDR" }
// Usage return usage
func (*DiscoverCmd) Usage() string {
return `discover:
discover 192.168.0.0/24
`
}
// SetFlags set flag
func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
}
// Execute execute
func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// validate
if len(f.Args()) == 0 {
logrus.Errorf("Usage: " + p.Usage())
return subcommands.ExitUsageError
}
for _, cidr := range f.Args() {
scanner := ps.PingScanner{
CIDR: cidr,
PingOptions: []string{
"-c1",
},
NumOfConcurrency: 100,
}
hosts, err := scanner.Scan()
if err != nil {
logrus.Errorf("Host Discovery failed. err: %s", err)
return subcommands.ExitFailure
}
if len(hosts) < 1 {
logrus.Errorf("Active hosts not found in %s", cidr)
return subcommands.ExitSuccess
} else if err := printConfigToml(hosts); err != nil {
logrus.Errorf("Failed to parse template. err: %s", err)
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}
// Output the template of config.toml
func printConfigToml(ips []string) (err error) {
const tomlTemplate = `
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
authUser = "username"
notifyUsers = ["@username"]
[email]
smtpAddr = "smtp.example.com"
smtpPort = "587"
user = "username"
password = "password"
from = "from@example.com"
to = ["to@example.com"]
cc = ["cc@example.com"]
subjectPrefix = "[vuls]"
[default]
#port = "22"
#user = "username"
#keyPath = "/home/username/.ssh/id_rsa"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-6271"]
#optional = [
# ["key", "value"],
#]
#containers = ["${running}"]
[servers]
{{- $names:= .Names}}
{{range $i, $ip := .IPs}}
[servers.{{index $names $i}}]
host = "{{$ip}}"
#port = "22"
#user = "root"
#keyPath = "/home/username/.ssh/id_rsa"
#type = "pseudo"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-0160"]
#optional = [
# ["key", "value"],
#]
#[servers.{{index $names $i}}.containers]
#type = "docker" #or "lxd" default: docker
#includes = ["${running}"]
#excludes = ["container_name_a", "4aa37a8b63b9"]
{{end}}
`
var tpl *template.Template
if tpl, err = template.New("template").Parse(tomlTemplate); err != nil {
return
}
type activeHosts struct {
IPs []string
Names []string
}
a := activeHosts{IPs: ips}
names := []string{}
for _, ip := range ips {
// TOML section header must not contain "."
name := strings.Replace(ip, ".", "-", -1)
names = append(names, name)
}
a.Names = names
fmt.Println("# Create config.toml using below and then ./vuls -config=/path/to/config.toml")
if err = tpl.Execute(os.Stdout, a); err != nil {
return
}
return
}

View File

@@ -1,454 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"context"
"flag"
"fmt"
"os"
"path/filepath"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/oval"
"github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
)
// ReportCmd is subcommand for reporting
type ReportCmd struct {
lang string
debug bool
debugSQL bool
configPath string
resultsDir string
logDir string
refreshCve bool
cvssScoreOver float64
ignoreUnscoredCves bool
ignoreUnfixed bool
httpProxy string
cveDBType string
cveDBPath string
cveDBURL string
ovalDBType string
ovalDBPath string
ovalDBURL string
toSlack bool
toEMail bool
toLocalFile bool
toS3 bool
toAzureBlob bool
formatJSON bool
formatXML bool
formatOneEMail bool
formatOneLineText bool
formatShortText bool
formatFullText bool
gzip bool
awsProfile string
awsS3Bucket string
awsS3ResultsDir string
awsRegion string
azureAccount string
azureKey string
azureContainer string
pipe bool
diff bool
}
// Name return subcommand name
func (*ReportCmd) Name() string { return "report" }
// Synopsis return synopsis
func (*ReportCmd) Synopsis() string { return "Reporting" }
// Usage return usage
func (*ReportCmd) Usage() string {
return `report:
report
[-lang=en|ja]
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-refresh-cve]
[-cvedb-type=sqlite3|mysql|postgres]
[-cvedb-path=/path/to/cve.sqlite3]
[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
[-ovaldb-type=sqlite3|mysql]
[-ovaldb-path=/path/to/oval.sqlite3]
[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
[-cvss-over=7]
[-diff]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-to-email]
[-to-slack]
[-to-localfile]
[-to-s3]
[-to-azure-blob]
[-format-json]
[-format-xml]
[-format-one-email]
[-format-one-line-text]
[-format-short-text]
[-format-full-text]
[-gzip]
[-aws-profile=default]
[-aws-region=us-west-2]
[-aws-s3-bucket=bucket_name]
[-aws-s3-results-dir=/bucket/path/to/results]
[-azure-account=account]
[-azure-key=key]
[-azure-container=container]
[-http-proxy=http://192.168.0.1:8080]
[-debug]
[-debug-sql]
[-pipe]
[RFC3339 datetime format under results dir]
`
}
// SetFlags set flag
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&p.lang, "lang", "en", "[en|ja]")
f.BoolVar(&p.debug, "debug", false, "debug mode")
f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
f.BoolVar(
&p.refreshCve,
"refresh-cve",
false,
"Refresh CVE information in JSON file under results dir")
f.StringVar(
&p.cveDBType,
"cvedb-type",
"sqlite3",
"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
f.StringVar(
&p.cveDBPath,
"cvedb-path",
defaultCveDBPath,
"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
f.StringVar(
&p.cveDBURL,
"cvedb-url",
"",
"http://cve-dictionary.com:1323 or mysql connection string")
f.StringVar(
&p.ovalDBType,
"ovaldb-type",
"sqlite3",
"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
f.StringVar(
&p.ovalDBPath,
"ovaldb-path",
defaultOvalDBPath,
"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
f.StringVar(
&p.ovalDBURL,
"ovaldb-url",
"",
"http://goval-dictionary.com:1324 or mysql connection string")
f.Float64Var(
&p.cvssScoreOver,
"cvss-over",
0,
"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
f.BoolVar(&p.diff,
"diff",
false,
fmt.Sprintf("Difference between previous result and current result "))
f.BoolVar(
&p.ignoreUnscoredCves,
"ignore-unscored-cves",
false,
"Don't report the unscored CVEs")
f.BoolVar(
&p.ignoreUnfixed,
"ignore-unfixed",
false,
"Don't report the unfixed CVEs")
f.StringVar(
&p.httpProxy,
"http-proxy",
"",
"http://proxy-url:port (default: empty)")
f.BoolVar(&p.formatJSON,
"format-json",
false,
fmt.Sprintf("JSON format"))
f.BoolVar(&p.formatXML,
"format-xml",
false,
fmt.Sprintf("XML format"))
f.BoolVar(&p.formatOneEMail,
"format-one-email",
false,
"Send all the host report via only one EMail (Specify with -to-email)")
f.BoolVar(&p.formatOneLineText,
"format-one-line-text",
false,
fmt.Sprintf("One line summary in plain text"))
f.BoolVar(&p.formatShortText,
"format-short-text",
false,
fmt.Sprintf("Summary in plain text"))
f.BoolVar(&p.formatFullText,
"format-full-text",
false,
fmt.Sprintf("Detail report in plain text"))
f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
f.BoolVar(&p.toLocalFile,
"to-localfile",
false,
fmt.Sprintf("Write report to localfile"))
f.BoolVar(&p.toS3,
"to-s3",
false,
"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
f.StringVar(&p.awsS3ResultsDir, "aws-s3-results-dir", "", "/bucket/path/to/results")
f.BoolVar(&p.toAzureBlob,
"to-azure-blob",
false,
"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
f.StringVar(&p.azureAccount,
"azure-account",
"",
"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
f.StringVar(&p.azureKey,
"azure-key",
"",
"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
f.BoolVar(
&p.pipe,
"pipe",
false,
"Use args passed via PIPE")
}
// Execute execute
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.Debug = p.debug
c.Conf.DebugSQL = p.debugSQL
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
if err := c.Load(p.configPath, ""); err != nil {
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
return subcommands.ExitUsageError
}
c.Conf.Lang = p.lang
c.Conf.ResultsDir = p.resultsDir
c.Conf.RefreshCve = p.refreshCve
c.Conf.Diff = p.diff
c.Conf.CveDBType = p.cveDBType
c.Conf.CveDBPath = p.cveDBPath
c.Conf.CveDBURL = p.cveDBURL
c.Conf.OvalDBType = p.ovalDBType
c.Conf.OvalDBPath = p.ovalDBPath
c.Conf.OvalDBURL = p.ovalDBURL
c.Conf.CvssScoreOver = p.cvssScoreOver
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.IgnoreUnfixed = p.ignoreUnfixed
c.Conf.HTTPProxy = p.httpProxy
c.Conf.FormatXML = p.formatXML
c.Conf.FormatJSON = p.formatJSON
c.Conf.FormatOneEMail = p.formatOneEMail
c.Conf.FormatOneLineText = p.formatOneLineText
c.Conf.FormatShortText = p.formatShortText
c.Conf.FormatFullText = p.formatFullText
c.Conf.GZIP = p.gzip
c.Conf.Diff = p.diff
c.Conf.Pipe = p.pipe
var dir string
var err error
if p.diff {
dir, err = report.JSONDir([]string{})
} else {
dir, err = report.JSONDir(f.Args())
}
if err != nil {
util.Log.Errorf("Failed to read from JSON: %s", err)
return subcommands.ExitFailure
}
// report
reports := []report.ResultWriter{
report.StdoutWriter{},
}
if p.toSlack {
reports = append(reports, report.SlackWriter{})
}
if p.toEMail {
reports = append(reports, report.EMailWriter{})
}
if p.toLocalFile {
reports = append(reports, report.LocalFileWriter{
CurrentDir: dir,
})
}
if p.toS3 {
c.Conf.AwsRegion = p.awsRegion
c.Conf.AwsProfile = p.awsProfile
c.Conf.S3Bucket = p.awsS3Bucket
c.Conf.S3ResultsDir = p.awsS3ResultsDir
if err := report.CheckIfBucketExists(); err != nil {
util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
return subcommands.ExitUsageError
}
reports = append(reports, report.S3Writer{})
}
if p.toAzureBlob {
c.Conf.AzureAccount = p.azureAccount
if len(c.Conf.AzureAccount) == 0 {
c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
}
c.Conf.AzureKey = p.azureKey
if len(c.Conf.AzureKey) == 0 {
c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
}
c.Conf.AzureContainer = p.azureContainer
if len(c.Conf.AzureContainer) == 0 {
util.Log.Error("Azure storage container name is required with -azure-container option")
return subcommands.ExitUsageError
}
if err := report.CheckIfAzureContainerExists(); err != nil {
util.Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
return subcommands.ExitUsageError
}
reports = append(reports, report.AzureBlobWriter{})
}
if !(p.formatJSON || p.formatOneLineText ||
p.formatShortText || p.formatFullText || p.formatXML) {
c.Conf.FormatShortText = true
}
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnReport() {
return subcommands.ExitUsageError
}
if err := report.CveClient.CheckHealth(); err != nil {
util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with -cvedb-path option")
return subcommands.ExitFailure
}
if c.Conf.CveDBURL != "" {
util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBURL)
} else {
if c.Conf.CveDBType == "sqlite3" {
util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
}
}
if c.Conf.OvalDBURL != "" {
err := oval.Base{}.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with -ovaldb-path option")
return subcommands.ExitFailure
}
}
var res models.ScanResults
if res, err = report.LoadScanResults(dir); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
util.Log.Infof("Loaded: %s", dir)
if res, err = report.FillCveInfos(res, dir); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
for _, w := range reports {
if err := w.Write(res...); err != nil {
util.Log.Errorf("Failed to report: %s", err)
return subcommands.ExitFailure
}
}
return subcommands.ExitSuccess
}

View File

@@ -1,261 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/scan"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
"github.com/k0kubun/pp"
)
// ScanCmd is Subcommand of host discovery mode
type ScanCmd struct {
debug bool
configPath string
resultsDir string
logDir string
cacheDBPath string
httpProxy string
askKeyPassword bool
containersOnly bool
deep bool
skipBroken bool
sshNative bool
pipe bool
timeoutSec int
scanTimeoutSec int
}
// Name return subcommand name
func (*ScanCmd) Name() string { return "scan" }
// Synopsis return synopsis
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
// Usage return usage
func (*ScanCmd) Usage() string {
return `scan:
scan
[-deep]
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-cachedb-path=/path/to/cache.db]
[-ssh-native-insecure]
[-containers-only]
[-skip-broken]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-timeout=300]
[-timeout-scan=7200]
[-debug]
[-pipe]
[SERVER]...
`
}
// SetFlags set flag
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.debug, "debug", false, "debug mode")
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
defaultCacheDBPath := filepath.Join(wd, "cache.db")
f.StringVar(
&p.cacheDBPath,
"cachedb-path",
defaultCacheDBPath,
"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
f.BoolVar(
&p.sshNative,
"ssh-native-insecure",
false,
"Use Native Go implementation of SSH. Default: Use the external command")
f.BoolVar(
&p.containersOnly,
"containers-only",
false,
"Scan containers only. Default: Scan both of hosts and containers")
f.BoolVar(
&p.skipBroken,
"skip-broken",
false,
"[For CentOS] yum update changelog with --skip-broken option")
f.StringVar(
&p.httpProxy,
"http-proxy",
"",
"http://proxy-url:port (default: empty)",
)
f.BoolVar(
&p.askKeyPassword,
"ask-key-password",
false,
"Ask ssh privatekey password before scanning",
)
f.BoolVar(
&p.deep,
"deep",
false,
"Deep scan mode. Scan accuracy improves and scanned information becomes richer. Since analysis of changelog, issue commands requiring sudo, but it may be slower and high load on the target server")
f.BoolVar(
&p.pipe,
"pipe",
false,
"Use stdin via PIPE")
f.IntVar(
&p.timeoutSec,
"timeout",
5*60,
"Number of seconds for processing other than scan",
)
f.IntVar(
&p.scanTimeoutSec,
"timeout-scan",
120*60,
"Number of seconds for scanning vulnerabilities for all servers",
)
}
// Execute execute
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
}
err = c.Load(p.configPath, keyPass)
if err != nil {
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
util.Log.Errorf("If you update Vuls and get this error, there may be incompatible changes in config.toml")
util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
return subcommands.ExitUsageError
}
util.Log.Info("Start scanning")
util.Log.Infof("config: %s", p.configPath)
c.Conf.Pipe = p.pipe
var servernames []string
if 0 < len(f.Args()) {
servernames = f.Args()
} else if c.Conf.Pipe {
bytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
util.Log.Errorf("Failed to read stdin: %s", err)
return subcommands.ExitFailure
}
fields := strings.Fields(string(bytes))
if 0 < len(fields) {
servernames = fields
}
}
target := make(map[string]c.ServerInfo)
for _, arg := range servernames {
found := false
for servername, info := range c.Conf.Servers {
if servername == arg {
target[servername] = info
found = true
break
}
}
if !found {
util.Log.Errorf("%s is not in config", arg)
return subcommands.ExitUsageError
}
}
if 0 < len(servernames) {
c.Conf.Servers = target
}
util.Log.Debugf("%s", pp.Sprintf("%v", target))
c.Conf.ResultsDir = p.resultsDir
c.Conf.CacheDBPath = p.cacheDBPath
c.Conf.SSHNative = p.sshNative
c.Conf.HTTPProxy = p.httpProxy
c.Conf.ContainersOnly = p.containersOnly
c.Conf.Deep = p.deep
c.Conf.SkipBroken = p.skipBroken
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnScan() {
return subcommands.ExitUsageError
}
util.Log.Info("Detecting Server/Container OS... ")
if err := scan.InitServers(p.timeoutSec); err != nil {
util.Log.Errorf("Failed to init servers: %s", err)
return subcommands.ExitFailure
}
util.Log.Info("Detecting Platforms... ")
scan.DetectPlatforms(p.timeoutSec)
util.Log.Info("Scanning vulnerabilities... ")
if err := scan.Scan(p.scanTimeoutSec); err != nil {
util.Log.Errorf("Failed to scan. err: %s", err)
return subcommands.ExitFailure
}
fmt.Printf("\n\n\n")
fmt.Println("To view the detail, vuls tui is useful.")
fmt.Println("To send a report, run vuls report -h.")
return subcommands.ExitSuccess
}

View File

@@ -1,226 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"context"
"flag"
"os"
"path/filepath"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
)
// TuiCmd is Subcommand of host discovery mode
type TuiCmd struct {
lang string
debugSQL bool
debug bool
configPath string
logDir string
resultsDir string
refreshCve bool
cvedbtype string
cvedbpath string
cveDictionaryURL string
ovalDBType string
ovalDBPath string
ovalDBURL string
cvssScoreOver float64
ignoreUnscoredCves bool
ignoreUnfixed bool
pipe bool
}
// Name return subcommand name
func (*TuiCmd) Name() string { return "tui" }
// Synopsis return synopsis
func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabilities" }
// Usage return usage
func (*TuiCmd) Usage() string {
return `tui:
tui
[-refresh-cve]
[-config=/path/to/config.toml]
[-cvedb-type=sqlite3|mysql|postgres]
[-cvedb-path=/path/to/cve.sqlite3]
[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
[-ovaldb-type=sqlite3|mysql]
[-ovaldb-path=/path/to/oval.sqlite3]
[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
[-cvss-over=7]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-debug]
[-debug-sql]
[-pipe]
`
}
// SetFlags set flag
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
// f.StringVar(&p.lang, "lang", "en", "[en|ja]")
f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
f.BoolVar(&p.debug, "debug", false, "debug mode")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
wd, _ := os.Getwd()
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
f.BoolVar(
&p.refreshCve,
"refresh-cve",
false,
"Refresh CVE information in JSON file under results dir")
f.StringVar(
&p.cvedbtype,
"cvedb-type",
"sqlite3",
"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
f.StringVar(
&p.cvedbpath,
"cvedb-path",
defaultCveDBPath,
"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
f.StringVar(
&p.cveDictionaryURL,
"cvedb-url",
"",
"http://cve-dictionary.example.com:1323 or mysql connection string")
f.StringVar(
&p.ovalDBType,
"ovaldb-type",
"sqlite3",
"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
f.StringVar(
&p.ovalDBPath,
"ovaldb-path",
defaultOvalDBPath,
"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
f.StringVar(
&p.ovalDBURL,
"ovaldb-url",
"",
"http://goval-dictionary.example.com:1324 or mysql connection string")
f.Float64Var(
&p.cvssScoreOver,
"cvss-over",
0,
"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
f.BoolVar(
&p.ignoreUnscoredCves,
"ignore-unscored-cves",
false,
"Don't report the unscored CVEs")
f.BoolVar(
&p.ignoreUnfixed,
"ignore-unfixed",
false,
"Don't report the unfixed CVEs")
f.BoolVar(
&p.pipe,
"pipe",
false,
"Use stdin via PIPE")
}
// Execute execute
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
c.Conf.Lang = "en"
// Setup Logger
c.Conf.Debug = p.debug
c.Conf.DebugSQL = p.debugSQL
c.Conf.LogDir = p.logDir
util.Log = util.NewCustomLogger(c.ServerInfo{})
log := util.Log
if err := c.Load(p.configPath, ""); err != nil {
util.Log.Errorf("Error loading %s, %s", p.configPath, err)
return subcommands.ExitUsageError
}
c.Conf.ResultsDir = p.resultsDir
c.Conf.CveDBType = p.cvedbtype
c.Conf.CveDBPath = p.cvedbpath
c.Conf.CveDBURL = p.cveDictionaryURL
c.Conf.OvalDBType = p.ovalDBType
c.Conf.OvalDBPath = p.ovalDBPath
c.Conf.OvalDBURL = p.ovalDBURL
c.Conf.CvssScoreOver = p.cvssScoreOver
c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
c.Conf.IgnoreUnfixed = p.ignoreUnfixed
c.Conf.RefreshCve = p.refreshCve
log.Info("Validating config...")
if !c.Conf.ValidateOnTui() {
return subcommands.ExitUsageError
}
c.Conf.Pipe = p.pipe
dir, err := report.JSONDir(f.Args())
if err != nil {
util.Log.Errorf("Failed to read from JSON: %s", err)
return subcommands.ExitFailure
}
var res models.ScanResults
if res, err = report.LoadScanResults(dir); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
util.Log.Infof("Loaded: %s", dir)
if res, err = report.FillCveInfos(res, dir); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
return report.RunTui(res)
}

View File

@@ -1,38 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands
import (
"fmt"
"github.com/howeyc/gopass"
)
func getPasswd(prompt string) (string, error) {
for {
fmt.Print(prompt)
pass, err := gopass.GetPasswdMasked()
if err != nil {
return "", fmt.Errorf("Failed to read password")
}
if 0 < len(pass) {
return string(pass[:]), nil
}
}
}

View File

@@ -1,18 +0,0 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package commands

30
config/awsconf.go Normal file
View File

@@ -0,0 +1,30 @@
package config
// AWSConf is aws config
type AWSConf struct {
// AWS profile to use
Profile string `json:"profile"`
// AWS region to use
Region string `json:"region"`
// S3 bucket name
S3Bucket string `json:"s3Bucket"`
// /bucket/path/to/results
S3ResultsDir string `json:"s3ResultsDir"`
// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
Enabled bool `toml:"-" json:"-"`
}
// Validate configuration
func (c *AWSConf) Validate() (errs []error) {
// TODO
if !c.Enabled {
return
}
return
}

46
config/azureconf.go Normal file
View File

@@ -0,0 +1,46 @@
package config
import (
"os"
"golang.org/x/xerrors"
)
// AzureConf is azure config
type AzureConf struct {
// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
AccountName string `json:"accountName"`
// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
AccountKey string `json:"-"`
// Azure storage container name
ContainerName string `json:"containerName"`
Enabled bool `toml:"-" json:"-"`
}
const (
azureAccount = "AZURE_STORAGE_ACCOUNT"
azureKey = "AZURE_STORAGE_ACCESS_KEY"
)
// Validate configuration
func (c *AzureConf) Validate() (errs []error) {
if !c.Enabled {
return
}
// overwrite if env var is not empty
if os.Getenv(azureAccount) != "" {
c.AccountName = os.Getenv(azureAccount)
}
if os.Getenv(azureKey) != "" {
c.AccountKey = os.Getenv(azureKey)
}
if c.ContainerName == "" {
errs = append(errs, xerrors.Errorf("Azure storage container name is required"))
}
return
}

33
config/chatworkconf.go Normal file
View File

@@ -0,0 +1,33 @@
package config
import (
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// ChatWorkConf is ChatWork config
type ChatWorkConf struct {
APIToken string `json:"-"`
Room string `json:"-"`
Enabled bool `toml:"-" json:"-"`
}
// Validate validates configuration
func (c *ChatWorkConf) Validate() (errs []error) {
if !c.Enabled {
return
}
if len(c.Room) == 0 {
errs = append(errs, xerrors.New("chatWorkConf.room must not be empty"))
}
if len(c.APIToken) == 0 {
errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty"))
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}

View File

@@ -1,20 +1,3 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
var (

View File

@@ -1,470 +1,281 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"fmt"
"os"
"runtime"
"strconv"
"strings"
valid "github.com/asaskevich/govalidator"
log "github.com/sirupsen/logrus"
"github.com/asaskevich/govalidator"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"golang.org/x/xerrors"
)
// Version of Vuls
var Version = "`make build` or `make install` will show the version"
// Revision of Git
var Revision string
// Conf has Configuration
var Conf Config
const (
// RedHat is
RedHat = "redhat"
// Debian is
Debian = "debian"
// Ubuntu is
Ubuntu = "ubuntu"
// CentOS is
CentOS = "centos"
// Fedora is
Fedora = "fedora"
// Amazon is
Amazon = "amazon"
// Oracle is
Oracle = "oracle"
// FreeBSD is
FreeBSD = "freebsd"
// Raspbian is
Raspbian = "raspbian"
// Windows is
Windows = "windows"
// OpenSUSE is
OpenSUSE = "opensuse"
// OpenSUSELeap is
OpenSUSELeap = "opensuse.leap"
// SUSEEnterpriseServer is
SUSEEnterpriseServer = "suse.linux.enterprise.server"
// SUSEEnterpriseDesktop is
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
// SUSEOpenstackCloud is
SUSEOpenstackCloud = "suse.openstack.cloud"
// Alpine is
Alpine = "alpine"
)
const (
// ServerTypePseudo is used for ServerInfo.Type
ServerTypePseudo = "pseudo"
)
//Config is struct of Configuration
type Config struct {
Debug bool
DebugSQL bool
Lang string
logging.LogOpts
EMail SMTPConf
Slack SlackConf
Default ServerInfo
Servers map[string]ServerInfo
// scan, report
HTTPProxy string `valid:"url" json:"httpProxy,omitempty"`
ResultsDir string `json:"resultsDir,omitempty"`
Pipe bool `json:"pipe,omitempty"`
CvssScoreOver float64
IgnoreUnscoredCves bool
IgnoreUnfixed bool
Default ServerInfo `json:"default,omitempty"`
Servers map[string]ServerInfo `json:"servers,omitempty"`
SSHNative bool
ContainersOnly bool
Deep bool
SkipBroken bool
ScanOpts
HTTPProxy string `valid:"url"`
LogDir string
ResultsDir string
// report
CveDict GoCveDictConf `json:"cveDict,omitempty"`
OvalDict GovalDictConf `json:"ovalDict,omitempty"`
Gost GostConf `json:"gost,omitempty"`
Exploit ExploitConf `json:"exploit,omitempty"`
Metasploit MetasploitConf `json:"metasploit,omitempty"`
CveDBType string
CveDBPath string
CveDBURL string
Slack SlackConf `json:"-"`
EMail SMTPConf `json:"-"`
HTTP HTTPConf `json:"-"`
Syslog SyslogConf `json:"-"`
AWS AWSConf `json:"-"`
Azure AzureConf `json:"-"`
ChatWork ChatWorkConf `json:"-"`
Telegram TelegramConf `json:"-"`
WpScan WpScanConf `json:"-"`
Saas SaasConf `json:"-"`
OvalDBType string
OvalDBPath string
OvalDBURL string
ReportOpts
}
CacheDBPath string
// ReportConf is an interface to Validate Report Config
type ReportConf interface {
Validate() []error
}
RefreshCve bool
// ScanOpts is options for scan
type ScanOpts struct {
Vvv bool `json:"vvv,omitempty"`
}
FormatXML bool
FormatJSON bool
FormatOneEMail bool
FormatOneLineText bool
FormatShortText bool
FormatFullText bool
GZIP bool
AwsProfile string
AwsRegion string
S3Bucket string
S3ResultsDir string
AzureAccount string
AzureKey string `json:"-"`
AzureContainer string
Pipe bool
Diff bool
// ReportOpts is options for report
type ReportOpts struct {
// refactored
CvssScoreOver float64 `json:"cvssScoreOver,omitempty"`
TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
NoProgress bool `json:"noProgress,omitempty"`
RefreshCve bool `json:"refreshCve,omitempty"`
IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"`
IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"`
DiffPlus bool `json:"diffPlus,omitempty"`
DiffMinus bool `json:"diffMinus,omitempty"`
Diff bool `json:"diff,omitempty"`
Lang string `json:"lang,omitempty"`
}
// ValidateOnConfigtest validates
func (c Config) ValidateOnConfigtest() bool {
errs := []error{}
if runtime.GOOS == "windows" && !c.SSHNative {
errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows"))
}
_, err := valid.ValidateStruct(c)
if err != nil {
errs := c.checkSSHKeyExist()
if _, err := govalidator.ValidateStruct(c); err != nil {
errs = append(errs, err)
}
for _, err := range errs {
log.Error(err)
logging.Log.Error(err)
}
return len(errs) == 0
}
// ValidateOnPrepare validates configuration
func (c Config) ValidateOnPrepare() bool {
return c.ValidateOnConfigtest()
}
// ValidateOnScan validates configuration
func (c Config) ValidateOnScan() bool {
errs := []error{}
errs := c.checkSSHKeyExist()
if len(c.ResultsDir) != 0 {
if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, fmt.Errorf(
if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, xerrors.Errorf(
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
}
if runtime.GOOS == "windows" && !c.SSHNative {
errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows"))
}
if len(c.ResultsDir) != 0 {
if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, fmt.Errorf(
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
}
if len(c.CacheDBPath) != 0 {
if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
errs = append(errs, fmt.Errorf(
"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
}
}
_, err := valid.ValidateStruct(c)
if err != nil {
if _, err := govalidator.ValidateStruct(c); err != nil {
errs = append(errs, err)
}
for _, err := range errs {
log.Error(err)
for _, server := range c.Servers {
if !server.Module.IsScanPort() {
continue
}
if es := server.PortScan.Validate(); 0 < len(es) {
errs = append(errs, es...)
}
}
for _, err := range errs {
logging.Log.Error(err)
}
return len(errs) == 0
}
func (c Config) checkSSHKeyExist() (errs []error) {
for serverName, v := range c.Servers {
if v.Type == constant.ServerTypePseudo {
continue
}
if v.KeyPath != "" {
if _, err := os.Stat(v.KeyPath); err != nil {
errs = append(errs, xerrors.Errorf(
"%s is invalid. keypath: %s not exists", serverName, v.KeyPath))
}
}
}
return errs
}
// ValidateOnReport validates configuration
func (c Config) ValidateOnReport() bool {
func (c *Config) ValidateOnReport() bool {
errs := []error{}
if len(c.ResultsDir) != 0 {
if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, fmt.Errorf(
if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, xerrors.Errorf(
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
}
if err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
errs = append(errs, err)
}
if c.CveDBType == "sqlite3" {
if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
}
}
if err := validateDB("ovaldb", c.OvalDBType, c.OvalDBPath, c.OvalDBURL); err != nil {
errs = append(errs, err)
}
_, err := valid.ValidateStruct(c)
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
errs = append(errs, mailerrs...)
for _, rc := range []ReportConf{
&c.EMail,
&c.Slack,
&c.ChatWork,
&c.Telegram,
&c.Syslog,
&c.HTTP,
&c.AWS,
&c.Azure,
} {
if es := rc.Validate(); 0 < len(es) {
errs = append(errs, es...)
}
}
if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
errs = append(errs, slackerrs...)
for _, cnf := range []VulnDictInterface{
&Conf.CveDict,
&Conf.OvalDict,
&Conf.Gost,
&Conf.Exploit,
&Conf.Metasploit,
} {
if err := cnf.Validate(); err != nil {
errs = append(errs, xerrors.Errorf("Failed to validate %s: %+v", cnf.GetName(), err))
}
if err := cnf.CheckHTTPHealth(); err != nil {
errs = append(errs, xerrors.Errorf("Run %s as server mode before reporting: %+v", cnf.GetName(), err))
}
}
for _, err := range errs {
log.Error(err)
logging.Log.Error(err)
}
return len(errs) == 0
}
// ValidateOnTui validates configuration
func (c Config) ValidateOnTui() bool {
errs := []error{}
if len(c.ResultsDir) != 0 {
if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
errs = append(errs, fmt.Errorf(
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
}
// ValidateOnSaaS validates configuration
func (c Config) ValidateOnSaaS() bool {
saaserrs := c.Saas.Validate()
for _, err := range saaserrs {
logging.Log.Error("Failed to validate SaaS conf: %+w", err)
}
if err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
errs = append(errs, err)
}
if c.CveDBType == "sqlite3" {
if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
}
}
for _, err := range errs {
log.Error(err)
}
return len(errs) == 0
return len(saaserrs) == 0
}
// validateDB validates configuration
// dictionaryDB name is 'cvedb' or 'ovaldb'
func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
switch dbType {
case "sqlite3":
if ok, _ := valid.IsFilePath(dbPath); !ok {
return fmt.Errorf(
"SQLite3 DB path (%s) must be a *Absolute* file path. -%s-path: %s",
dictionaryDBName,
dictionaryDBName,
dbPath)
}
case "mysql":
if dbURL == "" {
return fmt.Errorf(
`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`,
dictionaryDBName)
}
case "postgres":
if dbURL == "" {
return fmt.Errorf(
`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`,
dictionaryDBName)
}
case "redis":
if dbURL == "" {
return fmt.Errorf(
`Redis connection string is needed. -%s-url="redis://localhost/0"`,
dictionaryDBName)
}
default:
return fmt.Errorf(
"%s type must be either 'sqlite3', 'mysql', 'postgres' or 'redis'. -%s-type: %s",
dictionaryDBName,
dictionaryDBName,
dbType)
}
return nil
}
// SMTPConf is smtp config
type SMTPConf struct {
SMTPAddr string
SMTPPort string `valid:"port"`
User string
Password string `json:"-"`
From string
To []string
Cc []string
SubjectPrefix string
UseThisTime bool
}
func checkEmails(emails []string) (errs []error) {
for _, addr := range emails {
if len(addr) == 0 {
return
}
if ok := valid.IsEmail(addr); !ok {
errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr))
}
}
return
}
// Validate SMTP configuration
func (c *SMTPConf) Validate() (errs []error) {
if !c.UseThisTime {
return
}
// Check Emails fromat
emails := []string{}
emails = append(emails, c.From)
emails = append(emails, c.To...)
emails = append(emails, c.Cc...)
if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
errs = append(errs, emailErrs...)
}
if len(c.SMTPAddr) == 0 {
errs = append(errs, fmt.Errorf("smtpAddr must not be empty"))
}
if len(c.SMTPPort) == 0 {
errs = append(errs, fmt.Errorf("smtpPort must not be empty"))
}
if len(c.To) == 0 {
errs = append(errs, fmt.Errorf("To required at least one address"))
}
if len(c.From) == 0 {
errs = append(errs, fmt.Errorf("From required at least one address"))
}
_, err := valid.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}
// SlackConf is slack config
type SlackConf struct {
HookURL string `valid:"url" json:"-"`
LegacyToken string `json:"token" toml:"legacyToken,omitempty"`
Channel string `json:"channel"`
IconEmoji string `json:"icon_emoji"`
AuthUser string `json:"username"`
NotifyUsers []string
Text string `json:"text"`
UseThisTime bool
}
// Validate validates configuration
func (c *SlackConf) Validate() (errs []error) {
if !c.UseThisTime {
return
}
if len(c.HookURL) == 0 {
errs = append(errs, fmt.Errorf("hookURL must not be empty"))
}
if len(c.Channel) == 0 {
errs = append(errs, fmt.Errorf("channel must not be empty"))
} else {
if !(strings.HasPrefix(c.Channel, "#") ||
c.Channel == "${servername}") {
errs = append(errs, fmt.Errorf(
"channel's prefix must be '#', channel: %s", c.Channel))
}
}
if len(c.AuthUser) == 0 {
errs = append(errs, fmt.Errorf("authUser must not be empty"))
}
_, err := valid.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
// WpScanConf is wpscan.com config
type WpScanConf struct {
Token string `toml:"token,omitempty" json:"-"`
DetectInactive bool `toml:"detectInactive,omitempty" json:"detectInactive,omitempty"`
}
// ServerInfo has SSH Info, additional CPE packages to scan.
type ServerInfo struct {
ServerName string
User string
Host string
Port string
KeyPath string
KeyPassword string `json:"-"`
ServerName string `toml:"-" json:"serverName,omitempty"`
User string `toml:"user,omitempty" json:"user,omitempty"`
Host string `toml:"host,omitempty" json:"host,omitempty"`
JumpServer []string `toml:"jumpServer,omitempty" json:"jumpServer,omitempty"`
Port string `toml:"port,omitempty" json:"port,omitempty"`
SSHConfigPath string `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
KeyPath string `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
CpeNames []string `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
ScanMode []string `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
ScanModules []string `toml:"scanModules,omitempty" json:"scanModules,omitempty"`
OwaspDCXMLPath string `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath,omitempty"`
ContainersOnly bool `toml:"containersOnly,omitempty" json:"containersOnly,omitempty"`
ContainersIncluded []string `toml:"containersIncluded,omitempty" json:"containersIncluded,omitempty"`
ContainersExcluded []string `toml:"containersExcluded,omitempty" json:"containersExcluded,omitempty"`
ContainerType string `toml:"containerType,omitempty" json:"containerType,omitempty"`
Containers map[string]ContainerSetting `toml:"containers,omitempty" json:"containers,omitempty"`
IgnoreCves []string `toml:"ignoreCves,omitempty" json:"ignoreCves,omitempty"`
IgnorePkgsRegexp []string `toml:"ignorePkgsRegexp,omitempty" json:"ignorePkgsRegexp,omitempty"`
GitHubRepos map[string]GitHubConf `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo
UUIDs map[string]string `toml:"uuids,omitempty" json:"uuids,omitempty"`
Memo string `toml:"memo,omitempty" json:"memo,omitempty"`
Enablerepo []string `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, RHEL, Amazon
Optional map[string]interface{} `toml:"optional,omitempty" json:"optional,omitempty"` // Optional key-value set that will be outputted to JSON
Lockfiles []string `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"` // ie) path/to/package-lock.json
FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"`
Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or ""
IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"`
IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"`
IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"`
WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
PortScan *PortScanConf `toml:"portscan,omitempty" json:"portscan,omitempty"`
CpeNames []string
DependencyCheckXMLPath string
// internal use
LogMsgAnsiColor string `toml:"-" json:"-"` // DebugLog Color
Container Container `toml:"-" json:"-"`
Distro Distro `toml:"-" json:"-"`
Mode ScanMode `toml:"-" json:"-"`
Module ScanModule `toml:"-" json:"-"`
}
// Container Names or IDs
Containers Containers
// ContainerSetting is used for loading container setting in config.toml
type ContainerSetting struct {
Cpes []string `json:"cpes,omitempty"`
OwaspDCXMLPath string `json:"owaspDCXMLPath,omitempty"`
IgnorePkgsRegexp []string `json:"ignorePkgsRegexp,omitempty"`
IgnoreCves []string `json:"ignoreCves,omitempty"`
}
IgnoreCves []string
// WordPressConf used for WordPress Scanning
type WordPressConf struct {
OSUser string `toml:"osUser,omitempty" json:"osUser,omitempty"`
DocRoot string `toml:"docRoot,omitempty" json:"docRoot,omitempty"`
CmdPath string `toml:"cmdPath,omitempty" json:"cmdPath,omitempty"`
}
// Optional key-value set that will be outputted to JSON
Optional [][]interface{}
// IsZero return whether this struct is not specified in config.toml
func (cnf WordPressConf) IsZero() bool {
return cnf.OSUser == "" && cnf.DocRoot == "" && cnf.CmdPath == ""
}
// For CentOS, RHEL, Amazon
Enablerepo []string
// "pseudo" or ""
Type string
// used internal
LogMsgAnsiColor string // DebugLog Color
Container Container
Distro Distro
// GitHubConf is used for GitHub Security Alerts
type GitHubConf struct {
Token string `json:"-"`
IgnoreGitHubDismissed bool `json:"ignoreGitHubDismissed,omitempty"`
}
// GetServerName returns ServerName if this serverInfo is about host.
// If this serverInfo is abount a container, returns containerID@ServerName
// If this serverInfo is about a container, returns containerID@ServerName
func (s ServerInfo) GetServerName() string {
if len(s.Container.ContainerID) == 0 {
return s.ServerName
@@ -483,13 +294,17 @@ func (l Distro) String() string {
}
// MajorVersion returns Major version
func (l Distro) MajorVersion() (ver int, err error) {
if 0 < len(l.Release) {
ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0])
} else {
err = fmt.Errorf("Release is empty")
func (l Distro) MajorVersion() (int, error) {
if l.Family == constant.Amazon {
if isAmazonLinux1(l.Release) {
return 1, nil
}
return 2, nil
}
return
if 0 < len(l.Release) {
return strconv.Atoi(strings.Split(l.Release, ".")[0])
}
return 0, xerrors.New("Release is empty")
}
// IsContainer returns whether this ServerInfo is about container
@@ -502,13 +317,6 @@ func (s *ServerInfo) SetContainer(d Container) {
s.Container = d
}
// Containers has Containers information.
type Containers struct {
Type string
Includes []string
Excludes []string
}
// Container has Container information.
type Container struct {
ContainerID string

105
config/config_test.go Normal file
View File

@@ -0,0 +1,105 @@
package config
import (
"testing"
. "github.com/future-architect/vuls/constant"
)
func TestSyslogConfValidate(t *testing.T) {
var tests = []struct {
conf SyslogConf
expectedErrLength int
}{
{
conf: SyslogConf{},
expectedErrLength: 0,
},
{
conf: SyslogConf{
Protocol: "tcp",
Port: "5140",
},
expectedErrLength: 0,
},
{
conf: SyslogConf{
Protocol: "udp",
Port: "12345",
Severity: "emerg",
Facility: "user",
},
expectedErrLength: 0,
},
{
conf: SyslogConf{
Protocol: "foo",
Port: "514",
},
expectedErrLength: 1,
},
{
conf: SyslogConf{
Protocol: "invalid",
Port: "-1",
},
expectedErrLength: 2,
},
{
conf: SyslogConf{
Protocol: "invalid",
Port: "invalid",
Severity: "invalid",
Facility: "invalid",
},
expectedErrLength: 4,
},
}
for i, tt := range tests {
tt.conf.Enabled = true
errs := tt.conf.Validate()
if len(errs) != tt.expectedErrLength {
t.Errorf("test: %d, expected %d, actual %d", i, tt.expectedErrLength, len(errs))
}
}
}
func TestDistro_MajorVersion(t *testing.T) {
var tests = []struct {
in Distro
out int
}{
{
in: Distro{
Family: Amazon,
Release: "2 (2017.12)",
},
out: 2,
},
{
in: Distro{
Family: Amazon,
Release: "2017.12",
},
out: 1,
},
{
in: Distro{
Family: CentOS,
Release: "7.10",
},
out: 7,
},
}
for i, tt := range tests {
ver, err := tt.in.MajorVersion()
if err != nil {
t.Errorf("[%d] err occurred: %s", i, err)
}
if tt.out != ver {
t.Errorf("[%d] expected %d, actual %d", i, tt.out, ver)
}
}
}

32
config/httpconf.go Normal file
View File

@@ -0,0 +1,32 @@
package config
import (
"os"
"github.com/asaskevich/govalidator"
)
// HTTPConf is HTTP config
type HTTPConf struct {
URL string `valid:"url" json:"-"`
Enabled bool `toml:"-" json:"-"`
}
const httpKey = "VULS_HTTP_URL"
// Validate validates configuration
func (c *HTTPConf) Validate() (errs []error) {
if !c.Enabled {
return nil
}
// overwrite if env var is not empty
if os.Getenv(httpKey) != "" {
c.URL = os.Getenv(httpKey)
}
if _, err := govalidator.ValidateStruct(c); err != nil {
errs = append(errs, err)
}
return errs
}

View File

@@ -1,23 +1,6 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import "fmt"
import "golang.org/x/xerrors"
// JSONLoader loads configuration
type JSONLoader struct {
@@ -25,5 +8,5 @@ type JSONLoader struct {
// Load load the configuration JSON file specified by path arg.
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
return fmt.Errorf("Not implement yet")
return xerrors.New("Not implement yet")
}

View File

@@ -1,20 +1,3 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
// Load loads configuration

201
config/os.go Normal file
View File

@@ -0,0 +1,201 @@
package config
import (
"fmt"
"strings"
"time"
"github.com/future-architect/vuls/constant"
)
// EOL has End-of-Life information
type EOL struct {
StandardSupportUntil time.Time
ExtendedSupportUntil time.Time
Ended bool
}
// IsStandardSupportEnded checks now is under standard support
func (e EOL) IsStandardSupportEnded(now time.Time) bool {
return e.Ended ||
!e.ExtendedSupportUntil.IsZero() && e.StandardSupportUntil.IsZero() ||
!e.StandardSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
}
// IsExtendedSuppportEnded checks now is under extended support
func (e EOL) IsExtendedSuppportEnded(now time.Time) bool {
if e.Ended {
return true
}
if e.StandardSupportUntil.IsZero() && e.ExtendedSupportUntil.IsZero() {
return false
}
return !e.ExtendedSupportUntil.IsZero() && now.After(e.ExtendedSupportUntil) ||
e.ExtendedSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
}
// GetEOL return EOL information for the OS-release passed by args
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/redhat/redhat.go#L20
func GetEOL(family, release string) (eol EOL, found bool) {
switch family {
case constant.Amazon:
rel := "2"
if isAmazonLinux1(release) {
rel = "1"
}
eol, found = map[string]EOL{
"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
"2": {},
}[rel]
case constant.RedHat:
// https://access.redhat.com/support/policy/updates/errata
eol, found = map[string]EOL{
"3": {Ended: true},
"4": {Ended: true},
"5": {Ended: true},
"6": {
StandardSupportUntil: time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
},
"7": {
StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
},
"8": {
StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
},
}[major(release)]
case constant.CentOS:
// https://en.wikipedia.org/wiki/CentOS#End-of-support_schedule
// TODO Stream
eol, found = map[string]EOL{
"3": {Ended: true},
"4": {Ended: true},
"5": {Ended: true},
"6": {Ended: true},
"7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
"8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)},
}[major(release)]
case constant.Oracle:
eol, found = map[string]EOL{
// Source:
// https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf
// https://community.oracle.com/docs/DOC-917964
"3": {Ended: true},
"4": {Ended: true},
"5": {Ended: true},
"6": {
StandardSupportUntil: time.Date(2021, 3, 1, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2024, 3, 1, 23, 59, 59, 0, time.UTC),
},
"7": {
StandardSupportUntil: time.Date(2024, 7, 1, 23, 59, 59, 0, time.UTC),
},
"8": {
StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
},
}[major(release)]
case constant.Debian:
eol, found = map[string]EOL{
// https://wiki.debian.org/LTS
"6": {Ended: true},
"7": {Ended: true},
"8": {Ended: true},
"9": {StandardSupportUntil: time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC)},
"10": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
}[major(release)]
case constant.Raspbian:
// Not found
eol, found = map[string]EOL{}[major(release)]
case constant.Ubuntu:
// https://wiki.ubuntu.com/Releases
eol, found = map[string]EOL{
"14.10": {Ended: true},
"14.04": {
ExtendedSupportUntil: time.Date(2022, 4, 1, 23, 59, 59, 0, time.UTC),
},
"15.04": {Ended: true},
"16.10": {Ended: true},
"17.04": {Ended: true},
"17.10": {Ended: true},
"16.04": {
StandardSupportUntil: time.Date(2021, 4, 1, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2024, 4, 1, 23, 59, 59, 0, time.UTC),
},
"18.04": {
StandardSupportUntil: time.Date(2023, 4, 1, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2028, 4, 1, 23, 59, 59, 0, time.UTC),
},
"18.10": {Ended: true},
"19.04": {Ended: true},
"19.10": {Ended: true},
"20.04": {
StandardSupportUntil: time.Date(2025, 4, 1, 23, 59, 59, 0, time.UTC),
},
"20.10": {
StandardSupportUntil: time.Date(2021, 7, 1, 23, 59, 59, 0, time.UTC),
},
"21.04": {
StandardSupportUntil: time.Date(2022, 1, 1, 23, 59, 59, 0, time.UTC),
},
"21.10": {
StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
},
}[release]
case constant.SUSEEnterpriseServer:
//TODO
case constant.Alpine:
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19
// https://wiki.alpinelinux.org/wiki/Alpine_Linux:Releases
eol, found = map[string]EOL{
"2.0": {Ended: true},
"2.1": {Ended: true},
"2.2": {Ended: true},
"2.3": {Ended: true},
"2.4": {Ended: true},
"2.5": {Ended: true},
"2.6": {Ended: true},
"2.7": {Ended: true},
"3.0": {Ended: true},
"3.1": {Ended: true},
"3.2": {Ended: true},
"3.3": {Ended: true},
"3.4": {Ended: true},
"3.5": {Ended: true},
"3.6": {Ended: true},
"3.7": {Ended: true},
"3.8": {Ended: true},
"3.9": {Ended: true},
"3.10": {StandardSupportUntil: time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC)},
"3.11": {StandardSupportUntil: time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC)},
"3.12": {StandardSupportUntil: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC)},
"3.13": {StandardSupportUntil: time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC)},
}[majorDotMinor(release)]
case constant.FreeBSD:
// https://www.freebsd.org/security/
eol, found = map[string]EOL{
"7": {Ended: true},
"8": {Ended: true},
"9": {Ended: true},
"10": {Ended: true},
"11": {StandardSupportUntil: time.Date(2021, 9, 30, 23, 59, 59, 0, time.UTC)},
"12": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
}[major(release)]
}
return
}
func major(osVer string) (majorVersion string) {
return strings.Split(osVer, ".")[0]
}
func majorDotMinor(osVer string) (majorDotMinor string) {
ss := strings.SplitN(osVer, ".", 3)
if len(ss) == 1 {
return osVer
}
return fmt.Sprintf("%s.%s", ss[0], ss[1])
}
func isAmazonLinux1(osRelease string) bool {
return len(strings.Fields(osRelease)) == 1
}

383
config/os_test.go Normal file
View File

@@ -0,0 +1,383 @@
package config
import (
"testing"
"time"
. "github.com/future-architect/vuls/constant"
)
func TestEOL_IsStandardSupportEnded(t *testing.T) {
type fields struct {
family string
release string
}
tests := []struct {
name string
fields fields
now time.Time
found bool
stdEnded bool
extEnded bool
}{
// Amazon Linux
{
name: "amazon linux 1 supported",
fields: fields{family: Amazon, release: "2018.03"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "amazon linux 1 eol on 2023-6-30",
fields: fields{family: Amazon, release: "2018.03"},
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "amazon linux 2 supported",
fields: fields{family: Amazon, release: "2 (Karoo)"},
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
//RHEL
{
name: "RHEL7 supported",
fields: fields{family: RedHat, release: "7"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "RHEL8 supported",
fields: fields{family: RedHat, release: "8"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "RHEL6 eol",
fields: fields{family: RedHat, release: "6"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: false,
found: true,
},
{
name: "RHEL9 not found",
fields: fields{family: RedHat, release: "9"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: false,
},
//CentOS
{
name: "CentOS 7 supported",
fields: fields{family: CentOS, release: "7"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "CentOS 8 supported",
fields: fields{family: CentOS, release: "8"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "CentOS 6 eol",
fields: fields{family: CentOS, release: "6"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "CentOS 9 not found",
fields: fields{family: CentOS, release: "9"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: false,
},
//Oracle
{
name: "Oracle Linux 7 supported",
fields: fields{family: Oracle, release: "7"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Oracle Linux 8 supported",
fields: fields{family: Oracle, release: "8"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Oracle Linux 6 eol",
fields: fields{family: Oracle, release: "6"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Oracle Linux 9 not found",
fields: fields{family: Oracle, release: "9"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: false,
},
//Ubuntu
{
name: "Ubuntu 18.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 18.04 ext supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2025, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: false,
found: true,
},
{
name: "Ubuntu 16.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 14.04 eol",
fields: fields{family: Ubuntu, release: "14.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: false,
found: true,
},
{
name: "Ubuntu 14.10 eol",
fields: fields{family: Ubuntu, release: "14.10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "Ubuntu 12.10 not found",
fields: fields{family: Ubuntu, release: "12.10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
found: false,
stdEnded: false,
extEnded: false,
},
{
name: "Ubuntu 20.10 supported",
fields: fields{family: Ubuntu, release: "20.10"},
now: time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: false,
extEnded: false,
},
{
name: "Ubuntu 21.04 supported",
fields: fields{family: Ubuntu, release: "21.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: false,
extEnded: false,
},
//Debian
{
name: "Debian 9 supported",
fields: fields{family: Debian, release: "9"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Debian 10 supported",
fields: fields{family: Debian, release: "10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Debian 8 supported",
fields: fields{family: Debian, release: "8"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "Debian 11 supported",
fields: fields{family: Debian, release: "11"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: false,
},
//alpine
{
name: "alpine 3.10 supported",
fields: fields{family: Alpine, release: "3.10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Alpine 3.11 supported",
fields: fields{family: Alpine, release: "3.11"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Alpine 3.12 supported",
fields: fields{family: Alpine, release: "3.12"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Alpine 3.9 eol",
fields: fields{family: Alpine, release: "3.9"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "Alpine 3.14 not found",
fields: fields{family: Alpine, release: "3.14"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: false,
},
// freebsd
{
name: "freebsd 11 supported",
fields: fields{family: FreeBSD, release: "11"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "freebsd 11 eol on 2021-9-30",
fields: fields{family: FreeBSD, release: "11"},
now: time.Date(2021, 10, 1, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
{
name: "freebsd 12 supported",
fields: fields{family: FreeBSD, release: "12"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "freebsd 10 eol",
fields: fields{family: FreeBSD, release: "10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: true,
found: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
eol, found := GetEOL(tt.fields.family, tt.fields.release)
if found != tt.found {
t.Errorf("GetEOL.found = %v, want %v", found, tt.found)
}
if found {
if got := eol.IsStandardSupportEnded(tt.now); got != tt.stdEnded {
t.Errorf("EOL.IsStandardSupportEnded() = %v, want %v", got, tt.stdEnded)
}
if got := eol.IsExtendedSuppportEnded(tt.now); got != tt.extEnded {
t.Errorf("EOL.IsExtendedSupportEnded() = %v, want %v", got, tt.extEnded)
}
}
})
}
}
func Test_majorDotMinor(t *testing.T) {
type args struct {
osVer string
}
tests := []struct {
name string
args args
wantMajorDotMinor string
}{
{
name: "empty",
args: args{
osVer: "",
},
wantMajorDotMinor: "",
},
{
name: "major",
args: args{
osVer: "3",
},
wantMajorDotMinor: "3",
},
{
name: "major dot minor",
args: args{
osVer: "3.1",
},
wantMajorDotMinor: "3.1",
},
{
name: "major dot minor dot release",
args: args{
osVer: "3.1.4",
},
wantMajorDotMinor: "3.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMajorDotMinor := majorDotMinor(tt.args.osVer); gotMajorDotMinor != tt.wantMajorDotMinor {
t.Errorf("majorDotMinor() = %v, want %v", gotMajorDotMinor, tt.wantMajorDotMinor)
}
})
}
}

222
config/portscan.go Normal file
View File

@@ -0,0 +1,222 @@
package config
import (
"os"
"os/exec"
"strconv"
"strings"
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// PortScanConf is the setting for using an external port scanner
type PortScanConf struct {
IsUseExternalScanner bool `toml:"-" json:"-"`
// Path to external scanner
ScannerBinPath string `toml:"scannerBinPath,omitempty" json:"scannerBinPath,omitempty"`
// set user has privileged
HasPrivileged bool `toml:"hasPrivileged,omitempty" json:"hasPrivileged,omitempty"`
// set the ScanTechniques for ScannerBinPath
ScanTechniques []string `toml:"scanTechniques,omitempty" json:"scanTechniques,omitempty"`
// set the FIREWALL/IDS EVASION AND SPOOFING(Use given port number)
SourcePort string `toml:"sourcePort,omitempty" json:"sourcePort,omitempty"`
}
// ScanTechnique is implemented to represent the supported ScanTechniques in an Enum.
type ScanTechnique int
const (
// NotSupportTechnique is a ScanTechnique that is currently not supported.
NotSupportTechnique ScanTechnique = iota
// TCPSYN is SYN scan
TCPSYN
// TCPConnect is TCP connect scan
TCPConnect
// TCPACK is ACK scan
TCPACK
// TCPWindow is Window scan
TCPWindow
// TCPMaimon is Maimon scan
TCPMaimon
// TCPNull is Null scan
TCPNull
// TCPFIN is FIN scan
TCPFIN
// TCPXmas is Xmas scan
TCPXmas
)
var scanTechniqueMap = map[ScanTechnique]string{
TCPSYN: "sS",
TCPConnect: "sT",
TCPACK: "sA",
TCPWindow: "sW",
TCPMaimon: "sM",
TCPNull: "sN",
TCPFIN: "sF",
TCPXmas: "sX",
}
func (s ScanTechnique) String() string {
switch s {
case TCPSYN:
return "TCPSYN"
case TCPConnect:
return "TCPConnect"
case TCPACK:
return "TCPACK"
case TCPWindow:
return "TCPWindow"
case TCPMaimon:
return "TCPMaimon"
case TCPNull:
return "TCPNull"
case TCPFIN:
return "TCPFIN"
case TCPXmas:
return "TCPXmas"
default:
return "NotSupportTechnique"
}
}
// GetScanTechniques converts ScanTechniques loaded from config.toml to []scanTechniques.
func (c *PortScanConf) GetScanTechniques() []ScanTechnique {
if len(c.ScanTechniques) == 0 {
return []ScanTechnique{}
}
scanTechniques := []ScanTechnique{}
for _, technique := range c.ScanTechniques {
findScanTechniqueFlag := false
for key, value := range scanTechniqueMap {
if strings.EqualFold(value, technique) {
scanTechniques = append(scanTechniques, key)
findScanTechniqueFlag = true
break
}
}
if !findScanTechniqueFlag {
scanTechniques = append(scanTechniques, NotSupportTechnique)
}
}
if len(scanTechniques) == 0 {
return []ScanTechnique{NotSupportTechnique}
}
return scanTechniques
}
// Validate validates configuration
func (c *PortScanConf) Validate() (errs []error) {
if !c.IsUseExternalScanner {
if c.IsZero() {
return
}
errs = append(errs, xerrors.New("To enable the PortScan option, ScannerBinPath must be set."))
}
if _, err := os.Stat(c.ScannerBinPath); err != nil {
errs = append(errs, xerrors.Errorf(
"scanner is not found. ScannerBinPath: %s not exists", c.ScannerBinPath))
}
scanTechniques := c.GetScanTechniques()
for _, scanTechnique := range scanTechniques {
if scanTechnique == NotSupportTechnique {
errs = append(errs, xerrors.New("There is an unsupported option in ScanTechniques."))
}
}
// It does not currently support multiple ScanTechniques.
// But if it supports UDP scanning, it will need to accept multiple ScanTechniques.
if len(scanTechniques) > 1 {
errs = append(errs, xerrors.New("Currently multiple ScanTechniques are not supported."))
}
if c.HasPrivileged {
if os.Geteuid() != 0 {
output, err := exec.Command("getcap", c.ScannerBinPath).Output()
if err != nil {
errs = append(errs, xerrors.Errorf("Failed to check capability of %s. error message: %w", c.ScannerBinPath, err))
} else {
parseOutput := strings.SplitN(string(output), "=", 2)
if len(parseOutput) != 2 {
errs = append(errs, xerrors.Errorf("Failed to parse getcap outputs. please execute this command: `$ getcap %s`. If the following string (`/usr/bin/nmap = ... `) is not displayed, you need to set the capability with the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", c.ScannerBinPath, c.ScannerBinPath))
} else {
parseCapability := strings.Split(strings.TrimSpace(parseOutput[1]), "+")
capabilities := strings.Split(parseCapability[0], ",")
for _, needCap := range []string{"cap_net_bind_service", "cap_net_admin", "cap_net_raw"} {
existCapFlag := false
for _, cap := range capabilities {
if needCap == cap {
existCapFlag = true
break
}
}
if existCapFlag {
continue
}
errs = append(errs, xerrors.Errorf("Not enough capability to execute. needs: ['cap_net_bind_service', 'cap_net_admin', 'cap_net_raw'], actual: %s. To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", capabilities, c.ScannerBinPath))
break
}
if parseCapability[1] != "eip" {
errs = append(errs, xerrors.Errorf("Capability(`cap_net_bind_service,cap_net_admin,cap_net_raw`) must belong to the following capability set(need: eip, actual: %s). To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", parseCapability[1], c.ScannerBinPath))
}
}
}
}
}
if !c.HasPrivileged {
for _, scanTechnique := range scanTechniques {
if scanTechnique != TCPConnect && scanTechnique != NotSupportTechnique {
errs = append(errs, xerrors.New("If not privileged, only TCPConnect Scan(-sT) can be used."))
break
}
}
}
if c.SourcePort != "" {
for _, scanTechnique := range scanTechniques {
if scanTechnique == TCPConnect {
errs = append(errs, xerrors.New("SourcePort Option(-g/--source-port) is incompatible with the default TCPConnect Scan(-sT)."))
break
}
}
portNumber, err := strconv.Atoi(c.SourcePort)
if err != nil {
errs = append(errs, xerrors.Errorf("SourcePort conversion failed. %w", err))
} else {
if portNumber < 0 || 65535 < portNumber {
errs = append(errs, xerrors.Errorf("SourcePort(%s) must be between 0 and 65535.", c.SourcePort))
}
if portNumber == 0 {
errs = append(errs, xerrors.New("SourcePort(0) may not work on all systems."))
}
}
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}
// IsZero return whether this struct is not specified in config.toml
func (c PortScanConf) IsZero() bool {
return c.ScannerBinPath == "" && !c.HasPrivileged && len(c.ScanTechniques) == 0 && c.SourcePort == ""
}

69
config/portscan_test.go Normal file
View File

@@ -0,0 +1,69 @@
package config
import (
"reflect"
"testing"
)
func TestPortScanConf_getScanTechniques(t *testing.T) {
tests := []struct {
name string
techniques []string
want []ScanTechnique
}{
{
name: "nil",
techniques: []string{},
want: []ScanTechnique{},
},
{
name: "single",
techniques: []string{"sS"},
want: []ScanTechnique{TCPSYN},
},
{
name: "multiple",
techniques: []string{"sS", "sT"},
want: []ScanTechnique{TCPSYN, TCPConnect},
},
{
name: "unknown",
techniques: []string{"sU"},
want: []ScanTechnique{NotSupportTechnique},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := PortScanConf{ScanTechniques: tt.techniques}
if got := c.GetScanTechniques(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("PortScanConf.getScanTechniques() = %v, want %v", got, tt.want)
}
})
}
}
func TestPortScanConf_IsZero(t *testing.T) {
tests := []struct {
name string
conf PortScanConf
want bool
}{
{
name: "not zero",
conf: PortScanConf{ScannerBinPath: "/usr/bin/nmap"},
want: false,
},
{
name: "zero",
conf: PortScanConf{},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.conf.IsZero(); got != tt.want {
t.Errorf("PortScanConf.IsZero() = %v, want %v", got, tt.want)
}
})
}
}

34
config/saasconf.go Normal file
View File

@@ -0,0 +1,34 @@
package config
import (
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// SaasConf is FutureVuls config
type SaasConf struct {
GroupID int64 `json:"-"`
Token string `json:"-"`
URL string `json:"-"`
}
// Validate validates configuration
func (c *SaasConf) Validate() (errs []error) {
if c.GroupID == 0 {
errs = append(errs, xerrors.New("GroupID must not be empty"))
}
if len(c.Token) == 0 {
errs = append(errs, xerrors.New("Token must not be empty"))
}
if len(c.URL) == 0 {
errs = append(errs, xerrors.New("URL must not be empty"))
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}

110
config/scanmode.go Normal file
View File

@@ -0,0 +1,110 @@
package config
import (
"strings"
"golang.org/x/xerrors"
)
// ScanMode has a type of scan mode. fast, fast-root, deep and offline
type ScanMode struct {
flag byte
}
const (
// Fast is fast scan mode
Fast = byte(1 << iota)
// FastRoot is scanmode
FastRoot
// Deep is scanmode
Deep
// Offline is scanmode
Offline
fastStr = "fast"
fastRootStr = "fast-root"
deepStr = "deep"
offlineStr = "offline"
)
// Set mode
func (s *ScanMode) Set(f byte) {
s.flag |= f
}
// IsFast return whether scan mode is fast
func (s ScanMode) IsFast() bool {
return s.flag&Fast == Fast
}
// IsFastRoot return whether scan mode is fastroot
func (s ScanMode) IsFastRoot() bool {
return s.flag&FastRoot == FastRoot
}
// IsDeep return whether scan mode is deep
func (s ScanMode) IsDeep() bool {
return s.flag&Deep == Deep
}
// IsOffline return whether scan mode is offline
func (s ScanMode) IsOffline() bool {
return s.flag&Offline == Offline
}
func (s *ScanMode) ensure() error {
numTrue := 0
for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} {
if b {
numTrue++
}
}
if numTrue == 0 {
s.Set(Fast)
} else if s.IsDeep() && s.IsOffline() {
return xerrors.New("Don't specify both of deep and offline")
} else if numTrue != 1 {
return xerrors.New("Specify only one of offline, fast, fast-root or deep")
}
return nil
}
func (s ScanMode) String() string {
ss := ""
if s.IsFast() {
ss = fastStr
} else if s.IsFastRoot() {
ss = fastRootStr
} else if s.IsDeep() {
ss = deepStr
}
if s.IsOffline() {
ss += " " + offlineStr
}
return ss + " mode"
}
func setScanMode(server *ServerInfo, d ServerInfo) error {
if len(server.ScanMode) == 0 {
server.ScanMode = Conf.Default.ScanMode
}
for _, m := range server.ScanMode {
switch strings.ToLower(m) {
case fastStr:
server.Mode.Set(Fast)
case fastRootStr:
server.Mode.Set(FastRoot)
case deepStr:
server.Mode.Set(Deep)
case offlineStr:
server.Mode.Set(Offline)
default:
return xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline",
m, server.ServerName)
}
}
if err := server.Mode.ensure(); err != nil {
return xerrors.Errorf("%s in %s", err, server.ServerName)
}
return nil
}

97
config/scanmodule.go Normal file
View File

@@ -0,0 +1,97 @@
package config
import (
"strings"
"golang.org/x/xerrors"
)
// ScanModule has a type of scan module
type ScanModule struct {
flag byte
}
const (
// OSPkg is scanmodule
OSPkg = byte(1 << iota)
// WordPress is scanmodule
WordPress
// Lockfile is scanmodule
Lockfile
// Port is scanmodule
Port
osPkgStr = "ospkg"
wordPressStr = "wordpress"
lockfileStr = "lockfile"
portStr = "port"
)
var allModules = []string{osPkgStr, wordPressStr, lockfileStr, portStr}
// Set module
func (s *ScanModule) Set(f byte) {
s.flag |= f
}
// IsScanOSPkg return whether scanning os pkg
func (s ScanModule) IsScanOSPkg() bool {
return s.flag&OSPkg == OSPkg
}
// IsScanWordPress return whether scanning wordpress
func (s ScanModule) IsScanWordPress() bool {
return s.flag&WordPress == WordPress
}
// IsScanLockFile whether scanning lock file
func (s ScanModule) IsScanLockFile() bool {
return s.flag&Lockfile == Lockfile
}
// IsScanPort whether scanning listening ports
func (s ScanModule) IsScanPort() bool {
return s.flag&Port == Port
}
// IsZero return the struct value are all false
func (s ScanModule) IsZero() bool {
return !(s.IsScanOSPkg() || s.IsScanWordPress() || s.IsScanLockFile() || s.IsScanPort())
}
func (s *ScanModule) ensure() error {
if s.IsZero() {
s.Set(OSPkg)
s.Set(WordPress)
s.Set(Lockfile)
s.Set(Port)
} else if !s.IsScanOSPkg() && s.IsScanPort() {
return xerrors.New("When specifying the Port, Specify OSPkg as well")
}
return nil
}
func setScanModules(server *ServerInfo, d ServerInfo) error {
if len(server.ScanModules) == 0 {
server.ScanModules = d.ScanModules
}
for _, m := range server.ScanModules {
switch strings.ToLower(m) {
case osPkgStr:
server.Module.Set(OSPkg)
case wordPressStr:
server.Module.Set(WordPress)
case lockfileStr:
server.Module.Set(Lockfile)
case portStr:
server.Module.Set(Port)
default:
return xerrors.Errorf("scanMode: %s of %s is invalid. Specify %s",
m, server.ServerName, allModules)
}
}
if err := server.Module.ensure(); err != nil {
return xerrors.Errorf("%s in %s", err, server.ServerName)
}
return nil
}

65
config/scanmodule_test.go Normal file
View File

@@ -0,0 +1,65 @@
package config
import (
"testing"
)
func TestScanModule_IsZero(t *testing.T) {
tests := []struct {
name string
modes []byte
want bool
}{
{
name: "not zero",
modes: []byte{OSPkg},
want: false,
},
{
name: "zero",
modes: []byte{},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := ScanModule{}
for _, b := range tt.modes {
s.Set(b)
}
if got := s.IsZero(); got != tt.want {
t.Errorf("ScanModule.IsZero() = %v, want %v", got, tt.want)
}
})
}
}
func TestScanModule_validate(t *testing.T) {
tests := []struct {
name string
modes []byte
wantErr bool
}{
{
name: "valid",
modes: []byte{},
wantErr: false,
},
{
name: "err",
modes: []byte{WordPress, Lockfile, Port},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := ScanModule{}
for _, b := range tt.modes {
s.Set(b)
}
if err := s.ensure(); (err != nil) != tt.wantErr {
t.Errorf("ScanModule.validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

52
config/slackconf.go Normal file
View File

@@ -0,0 +1,52 @@
package config
import (
"strings"
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// SlackConf is slack config
type SlackConf struct {
HookURL string `valid:"url" json:"-" toml:"hookURL,omitempty"`
LegacyToken string `json:"-" toml:"legacyToken,omitempty"`
Channel string `json:"-" toml:"channel,omitempty"`
IconEmoji string `json:"-" toml:"iconEmoji,omitempty"`
AuthUser string `json:"-" toml:"authUser,omitempty"`
NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"`
Text string `json:"-"`
Enabled bool `toml:"-" json:"-"`
}
// Validate validates configuration
func (c *SlackConf) Validate() (errs []error) {
if !c.Enabled {
return
}
if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 {
errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty"))
}
if len(c.Channel) == 0 {
errs = append(errs, xerrors.New("slack.channel must not be empty"))
} else {
if !(strings.HasPrefix(c.Channel, "#") ||
c.Channel == "${servername}") {
errs = append(errs, xerrors.Errorf(
"channel's prefix must be '#', channel: %s", c.Channel))
}
}
if len(c.AuthUser) == 0 {
errs = append(errs, xerrors.New("slack.authUser must not be empty"))
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}

65
config/smtpconf.go Normal file
View File

@@ -0,0 +1,65 @@
package config
import (
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// SMTPConf is smtp config
type SMTPConf struct {
SMTPAddr string `toml:"smtpAddr,omitempty" json:"-"`
SMTPPort string `toml:"smtpPort,omitempty" valid:"port" json:"-"`
User string `toml:"user,omitempty" json:"-"`
Password string `toml:"password,omitempty" json:"-"`
From string `toml:"from,omitempty" json:"-"`
To []string `toml:"to,omitempty" json:"-"`
Cc []string `toml:"cc,omitempty" json:"-"`
SubjectPrefix string `toml:"subjectPrefix,omitempty" json:"-"`
Enabled bool `toml:"-" json:"-"`
}
func checkEmails(emails []string) (errs []error) {
for _, addr := range emails {
if len(addr) == 0 {
return
}
if ok := govalidator.IsEmail(addr); !ok {
errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr))
}
}
return
}
// Validate SMTP configuration
func (c *SMTPConf) Validate() (errs []error) {
if !c.Enabled {
return
}
emails := []string{}
emails = append(emails, c.From)
emails = append(emails, c.To...)
emails = append(emails, c.Cc...)
if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
errs = append(errs, emailErrs...)
}
if c.SMTPAddr == "" {
errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
}
if c.SMTPPort == "" {
errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
}
if len(c.To) == 0 {
errs = append(errs, xerrors.New("email.To required at least one address"))
}
if len(c.From) == 0 {
errs = append(errs, xerrors.New("email.From required at least one address"))
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}

130
config/syslogconf.go Normal file
View File

@@ -0,0 +1,130 @@
package config
import (
"errors"
"log/syslog"
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// SyslogConf is syslog config
type SyslogConf struct {
Protocol string `json:"-"`
Host string `valid:"host" json:"-"`
Port string `valid:"port" json:"-"`
Severity string `json:"-"`
Facility string `json:"-"`
Tag string `json:"-"`
Verbose bool `json:"-"`
Enabled bool `toml:"-" json:"-"`
}
// Validate validates configuration
func (c *SyslogConf) Validate() (errs []error) {
if !c.Enabled {
return nil
}
// If protocol is empty, it will connect to the local syslog server.
if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" {
errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`))
}
// Default port: 514
if c.Port == "" {
c.Port = "514"
}
if _, err := c.GetSeverity(); err != nil {
errs = append(errs, err)
}
if _, err := c.GetFacility(); err != nil {
errs = append(errs, err)
}
if _, err := govalidator.ValidateStruct(c); err != nil {
errs = append(errs, err)
}
return errs
}
// GetSeverity gets severity
func (c *SyslogConf) GetSeverity() (syslog.Priority, error) {
if c.Severity == "" {
return syslog.LOG_INFO, nil
}
switch c.Severity {
case "emerg":
return syslog.LOG_EMERG, nil
case "alert":
return syslog.LOG_ALERT, nil
case "crit":
return syslog.LOG_CRIT, nil
case "err":
return syslog.LOG_ERR, nil
case "warning":
return syslog.LOG_WARNING, nil
case "notice":
return syslog.LOG_NOTICE, nil
case "info":
return syslog.LOG_INFO, nil
case "debug":
return syslog.LOG_DEBUG, nil
default:
return -1, xerrors.Errorf("Invalid severity: %s", c.Severity)
}
}
// GetFacility gets facility
func (c *SyslogConf) GetFacility() (syslog.Priority, error) {
if c.Facility == "" {
return syslog.LOG_AUTH, nil
}
switch c.Facility {
case "kern":
return syslog.LOG_KERN, nil
case "user":
return syslog.LOG_USER, nil
case "mail":
return syslog.LOG_MAIL, nil
case "daemon":
return syslog.LOG_DAEMON, nil
case "auth":
return syslog.LOG_AUTH, nil
case "syslog":
return syslog.LOG_SYSLOG, nil
case "lpr":
return syslog.LOG_LPR, nil
case "news":
return syslog.LOG_NEWS, nil
case "uucp":
return syslog.LOG_UUCP, nil
case "cron":
return syslog.LOG_CRON, nil
case "authpriv":
return syslog.LOG_AUTHPRIV, nil
case "ftp":
return syslog.LOG_FTP, nil
case "local0":
return syslog.LOG_LOCAL0, nil
case "local1":
return syslog.LOG_LOCAL1, nil
case "local2":
return syslog.LOG_LOCAL2, nil
case "local3":
return syslog.LOG_LOCAL3, nil
case "local4":
return syslog.LOG_LOCAL4, nil
case "local5":
return syslog.LOG_LOCAL5, nil
case "local6":
return syslog.LOG_LOCAL6, nil
case "local7":
return syslog.LOG_LOCAL7, nil
default:
return -1, xerrors.Errorf("Invalid facility: %s", c.Facility)
}
}

33
config/telegramconf.go Normal file
View File

@@ -0,0 +1,33 @@
package config
import (
"github.com/asaskevich/govalidator"
"golang.org/x/xerrors"
)
// TelegramConf is Telegram config
type TelegramConf struct {
Token string `json:"-"`
ChatID string `json:"-"`
Enabled bool `toml:"-" json:"-"`
}
// Validate validates configuration
func (c *TelegramConf) Validate() (errs []error) {
if !c.Enabled {
return
}
if len(c.ChatID) == 0 {
errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty"))
}
if len(c.Token) == 0 {
errs = append(errs, xerrors.New("TelegramConf.Token must not be empty"))
}
_, err := govalidator.ValidateStruct(c)
if err != nil {
errs = append(errs, err)
}
return
}

View File

@@ -1,189 +1,248 @@
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Architect, Inc. Japan.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/BurntSushi/toml"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
log "github.com/sirupsen/logrus"
"github.com/future-architect/vuls/constant"
"github.com/knqyf263/go-cpe/naming"
"golang.org/x/xerrors"
)
// TOMLLoader loads config
type TOMLLoader struct {
}
// Load load the configuraiton TOML file specified by path arg.
// Load load the configuration TOML file specified by path arg.
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
if Conf.Debug {
log.SetLevel(log.DebugLevel)
}
var conf Config
if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
log.Error("Load config failed", err)
// util.Log.Infof("Loading config: %s", pathToToml)
if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
return err
}
Conf.EMail = conf.EMail
Conf.Slack = conf.Slack
d := conf.Default
Conf.Default = d
servers := make(map[string]ServerInfo)
if keyPass != "" {
d.KeyPassword = keyPass
for _, cnf := range []VulnDictInterface{
&Conf.CveDict,
&Conf.OvalDict,
&Conf.Gost,
&Conf.Exploit,
&Conf.Metasploit,
} {
cnf.Init()
}
i := 0
for name, v := range conf.Servers {
if 0 < len(v.KeyPassword) {
log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
index := 0
for name, server := range Conf.Servers {
server.ServerName = name
if err := setDefaultIfEmpty(&server, Conf.Default); err != nil {
return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err)
}
s := ServerInfo{ServerName: name}
if v.Type != ServerTypePseudo {
s.Host = v.Host
if len(s.Host) == 0 {
return fmt.Errorf("%s is invalid. host is empty", name)
}
switch {
case v.Port != "":
s.Port = v.Port
case d.Port != "":
s.Port = d.Port
default:
s.Port = "22"
}
switch {
case v.User != "":
s.User = v.User
case d.User != "":
s.User = d.User
default:
if s.Port != "local" {
return fmt.Errorf("%s is invalid. User is empty", name)
}
}
s.KeyPath = v.KeyPath
if len(s.KeyPath) == 0 {
s.KeyPath = d.KeyPath
}
if s.KeyPath != "" {
if _, err := os.Stat(s.KeyPath); err != nil {
return fmt.Errorf(
"%s is invalid. keypath: %s not exists", name, s.KeyPath)
}
}
// s.KeyPassword = keyPass
s.KeyPassword = v.KeyPassword
if len(s.KeyPassword) == 0 {
s.KeyPassword = d.KeyPassword
}
if err := setScanMode(&server, Conf.Default); err != nil {
return xerrors.Errorf("Failed to set ScanMode: %w", err)
}
s.CpeNames = v.CpeNames
if len(s.CpeNames) == 0 {
s.CpeNames = d.CpeNames
if err := setScanModules(&server, Conf.Default); err != nil {
return xerrors.Errorf("Failed to set ScanModule: %w", err)
}
s.DependencyCheckXMLPath = v.DependencyCheckXMLPath
if len(s.DependencyCheckXMLPath) == 0 {
s.DependencyCheckXMLPath = d.DependencyCheckXMLPath
if len(server.CpeNames) == 0 {
server.CpeNames = Conf.Default.CpeNames
}
// Load CPEs from OWASP Dependency Check XML
if len(s.DependencyCheckXMLPath) != 0 {
cpes, err := parser.Parse(s.DependencyCheckXMLPath)
for i, n := range server.CpeNames {
uri, err := toCpeURI(n)
if err != nil {
return fmt.Errorf(
"Failed to read OWASP Dependency Check XML: %s", err)
return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, name, err)
}
log.Debugf("Loaded from OWASP Dependency Check XML: %s",
s.ServerName)
s.CpeNames = append(s.CpeNames, cpes...)
server.CpeNames[i] = uri
}
s.Containers = v.Containers
if len(s.Containers.Includes) == 0 {
s.Containers = d.Containers
}
s.IgnoreCves = v.IgnoreCves
for _, cve := range d.IgnoreCves {
for _, cve := range Conf.Default.IgnoreCves {
found := false
for _, c := range s.IgnoreCves {
for _, c := range server.IgnoreCves {
if cve == c {
found = true
break
}
}
if !found {
s.IgnoreCves = append(s.IgnoreCves, cve)
server.IgnoreCves = append(server.IgnoreCves, cve)
}
}
s.Optional = v.Optional
for _, dkv := range d.Optional {
for _, pkg := range Conf.Default.IgnorePkgsRegexp {
found := false
for _, kv := range s.Optional {
if dkv[0] == kv[0] {
for _, p := range server.IgnorePkgsRegexp {
if pkg == p {
found = true
break
}
}
if !found {
s.Optional = append(s.Optional, dkv)
server.IgnorePkgsRegexp = append(server.IgnorePkgsRegexp, pkg)
}
}
s.Enablerepo = v.Enablerepo
if len(s.Enablerepo) == 0 {
s.Enablerepo = d.Enablerepo
for _, reg := range server.IgnorePkgsRegexp {
_, err := regexp.Compile(reg)
if err != nil {
return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, name, err)
}
}
if len(s.Enablerepo) != 0 {
for _, repo := range s.Enablerepo {
switch repo {
case "base", "updates":
// nop
default:
return fmt.Errorf(
"For now, enablerepo have to be base or updates: %s, servername: %s",
s.Enablerepo, name)
for contName, cont := range server.Containers {
for _, reg := range cont.IgnorePkgsRegexp {
_, err := regexp.Compile(reg)
if err != nil {
return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w",
reg, contName, name, err)
}
}
}
s.Type = v.Type
for ownerRepo, githubSetting := range server.GitHubRepos {
if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
ownerRepo, name)
}
if githubSetting.Token == "" {
return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
ownerRepo, name)
}
}
s.LogMsgAnsiColor = Colors[i%len(Colors)]
i++
if len(server.Enablerepo) == 0 {
server.Enablerepo = Conf.Default.Enablerepo
}
if len(server.Enablerepo) != 0 {
for _, repo := range server.Enablerepo {
switch repo {
case "base", "updates":
// nop
default:
return xerrors.Errorf(
"For now, enablerepo have to be base or updates: %s",
server.Enablerepo)
}
}
}
servers[name] = s
if server.PortScan.ScannerBinPath != "" {
server.PortScan.IsUseExternalScanner = true
}
server.LogMsgAnsiColor = Colors[index%len(Colors)]
index++
Conf.Servers[name] = server
}
Conf.Servers = servers
return nil
}
func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
if server.Type != constant.ServerTypePseudo {
if len(server.Host) == 0 {
return xerrors.Errorf("server.host is empty")
}
if len(server.JumpServer) == 0 {
server.JumpServer = Conf.Default.JumpServer
}
if server.Port == "" {
if Conf.Default.Port != "" {
server.Port = Conf.Default.Port
} else {
server.Port = "22"
}
}
if server.User == "" {
server.User = Conf.Default.User
if server.User == "" && server.Port != "local" {
return xerrors.Errorf("server.user is empty")
}
}
if server.SSHConfigPath == "" {
server.SSHConfigPath = Conf.Default.SSHConfigPath
}
if server.KeyPath == "" {
server.KeyPath = Conf.Default.KeyPath
}
}
if len(server.Lockfiles) == 0 {
server.Lockfiles = Conf.Default.Lockfiles
}
if len(server.ContainersIncluded) == 0 {
server.ContainersIncluded = Conf.Default.ContainersIncluded
}
if len(server.ContainersExcluded) == 0 {
server.ContainersExcluded = Conf.Default.ContainersExcluded
}
if server.ContainerType == "" {
server.ContainerType = Conf.Default.ContainerType
}
for contName, cont := range server.Containers {
cont.IgnoreCves = append(cont.IgnoreCves, Conf.Default.IgnoreCves...)
server.Containers[contName] = cont
}
if server.OwaspDCXMLPath == "" {
server.OwaspDCXMLPath = Conf.Default.OwaspDCXMLPath
}
if server.Memo == "" {
server.Memo = Conf.Default.Memo
}
if server.WordPress == nil {
server.WordPress = Conf.Default.WordPress
if server.WordPress == nil {
server.WordPress = &WordPressConf{}
}
}
if server.PortScan == nil {
server.PortScan = Conf.Default.PortScan
if server.PortScan == nil {
server.PortScan = &PortScanConf{}
}
}
if len(server.IgnoredJSONKeys) == 0 {
server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
}
opt := map[string]interface{}{}
for k, v := range Conf.Default.Optional {
opt[k] = v
}
for k, v := range server.Optional {
opt[k] = v
}
server.Optional = opt
return nil
}
func toCpeURI(cpename string) (string, error) {
if strings.HasPrefix(cpename, "cpe:2.3:") {
wfn, err := naming.UnbindFS(cpename)
if err != nil {
return "", err
}
return naming.BindToURI(wfn), nil
} else if strings.HasPrefix(cpename, "cpe:/") {
wfn, err := naming.UnbindURI(cpename)
if err != nil {
return "", err
}
return naming.BindToURI(wfn), nil
}
return "", xerrors.Errorf("Unknown CPE format: %s", cpename)
}

44
config/tomlloader_test.go Normal file
View File

@@ -0,0 +1,44 @@
package config
import (
"testing"
)
func TestToCpeURI(t *testing.T) {
var tests = []struct {
in string
expected string
err bool
}{
{
in: "",
expected: "",
err: true,
},
{
in: "cpe:/a:microsoft:internet_explorer:10",
expected: "cpe:/a:microsoft:internet_explorer:10",
err: false,
},
{
in: "cpe:2.3:a:microsoft:internet_explorer:10:*:*:*:*:*:*:*",
expected: "cpe:/a:microsoft:internet_explorer:10",
err: false,
},
}
for i, tt := range tests {
actual, err := toCpeURI(tt.in)
if err != nil && !tt.err {
t.Errorf("[%d] unexpected error occurred, in: %s act: %s, exp: %s",
i, tt.in, actual, tt.expected)
} else if err == nil && tt.err {
t.Errorf("[%d] expected error is not occurred, in: %s act: %s, exp: %s",
i, tt.in, actual, tt.expected)
}
if actual != tt.expected {
t.Errorf("[%d] in: %s, actual: %s, expected: %s",
i, tt.in, actual, tt.expected)
}
}
}

276
config/vulnDictConf.go Normal file
View File

@@ -0,0 +1,276 @@
package config
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/asaskevich/govalidator"
"github.com/future-architect/vuls/logging"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
)
// VulnDictInterface is an interface of vulnsrc
type VulnDictInterface interface {
Init()
Validate() error
IsFetchViaHTTP() bool
CheckHTTPHealth() error
GetName() string
GetType() string
GetURL() string
GetSQLite3Path() string
GetDebugSQL() bool
}
// VulnDict is a base struct of vuln dicts
type VulnDict struct {
Name string
// DB type of CVE dictionary (sqlite3, mysql, postgres or redis)
Type string
// http://cve-dictionary.com:1323 or DB connection string
URL string `json:"-"`
// /path/to/cve.sqlite3
SQLite3Path string
DebugSQL bool
}
// GetType returns type
func (cnf VulnDict) GetType() string {
return cnf.Type
}
// GetName returns name
func (cnf VulnDict) GetName() string {
return cnf.Name
}
// GetURL returns url
func (cnf VulnDict) GetURL() string {
return cnf.URL
}
// GetSQLite3Path return the path of SQLite3
func (cnf VulnDict) GetSQLite3Path() string {
return cnf.SQLite3Path
}
// GetDebugSQL return debugSQL flag
func (cnf VulnDict) GetDebugSQL() bool {
return cnf.DebugSQL
}
// Validate settings
func (cnf VulnDict) Validate() error {
logging.Log.Infof("%s.type=%s, %s.url=%s, %s.SQLite3Path=%s",
cnf.Name, cnf.Type, cnf.Name, cnf.URL, cnf.Name, cnf.SQLite3Path)
switch cnf.Type {
case "sqlite3":
if cnf.URL != "" {
return xerrors.Errorf("To use SQLite3, specify %s.type=sqlite3 and %s.SQLite3Path. To use as HTTP server mode, specify %s.type=http and %s.url",
cnf.Name, cnf.Name, cnf.Name, cnf.Name)
}
if ok, _ := govalidator.IsFilePath(cnf.SQLite3Path); !ok {
return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. %s.SQLite3Path: %s",
cnf.Name, cnf.SQLite3Path)
}
if _, err := os.Stat(cnf.SQLite3Path); os.IsNotExist(err) {
logging.Log.Warnf("%s.SQLite3Path=%s file not found", cnf.Name, cnf.SQLite3Path)
}
case "mysql":
if cnf.URL == "" {
return xerrors.Errorf(`MySQL connection string is needed. %s.url="user:pass@tcp(localhost:3306)/dbname"`, cnf.Name)
}
case "postgres":
if cnf.URL == "" {
return xerrors.Errorf(`PostgreSQL connection string is needed. %s.url="host=myhost user=user dbname=dbname sslmode=disable password=password"`, cnf.Name)
}
case "redis":
if cnf.URL == "" {
return xerrors.Errorf(`Redis connection string is needed. %s.url="redis://localhost/0"`, cnf.Name)
}
case "http":
if cnf.URL == "" {
return xerrors.Errorf(`URL is needed. -%s-url="http://localhost:1323"`, cnf.Name)
}
default:
return xerrors.Errorf("%s.type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'. %s.type: %s", cnf.Name, cnf.Name, cnf.Type)
}
return nil
}
// Init the struct
func (cnf VulnDict) Init() {}
func (cnf *VulnDict) setDefault(sqlite3Name string) {
if cnf.Type == "" {
cnf.Type = "sqlite3"
}
if cnf.URL == "" && cnf.SQLite3Path == "" {
wd, _ := os.Getwd()
cnf.SQLite3Path = filepath.Join(wd, sqlite3Name)
}
}
// IsFetchViaHTTP returns if fetch via HTTP
func (cnf VulnDict) IsFetchViaHTTP() bool {
return cnf.Type == "http"
}
// CheckHTTPHealth checks http server status
func (cnf VulnDict) CheckHTTPHealth() error {
if !cnf.IsFetchViaHTTP() {
return nil
}
url := fmt.Sprintf("%s/health", cnf.URL)
resp, _, errs := gorequest.New().Timeout(10 * time.Second).SetDebug(Conf.Debug).Get(url).End()
// resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %s",
url, errs)
}
return nil
}
// GovalDictConf is goval-dictionary config
type GovalDictConf struct {
VulnDict
}
const govalType = "OVALDB_TYPE"
const govalURL = "OVALDB_URL"
const govalPATH = "OVALDB_SQLITE3_PATH"
// Init set options with the following priority.
// 1. Environment variable
// 2. config.toml
func (cnf *GovalDictConf) Init() {
cnf.Name = "ovalDict"
if os.Getenv(govalType) != "" {
cnf.Type = os.Getenv(govalType)
}
if os.Getenv(govalURL) != "" {
cnf.URL = os.Getenv(govalURL)
}
if os.Getenv(govalPATH) != "" {
cnf.SQLite3Path = os.Getenv(govalPATH)
}
cnf.setDefault("oval.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}
// ExploitConf is exploit config
type ExploitConf struct {
VulnDict
}
const exploitDBType = "EXPLOITDB_TYPE"
const exploitDBURL = "EXPLOITDB_URL"
const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH"
// Init set options with the following priority.
// 1. Environment variable
// 2. config.toml
func (cnf *ExploitConf) Init() {
cnf.Name = "exploit"
if os.Getenv(exploitDBType) != "" {
cnf.Type = os.Getenv(exploitDBType)
}
if os.Getenv(exploitDBURL) != "" {
cnf.URL = os.Getenv(exploitDBURL)
}
if os.Getenv(exploitDBPATH) != "" {
cnf.SQLite3Path = os.Getenv(exploitDBPATH)
}
cnf.setDefault("go-exploitdb.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}
// GoCveDictConf is GoCveDict config
type GoCveDictConf struct {
VulnDict
}
const cveDBType = "CVEDB_TYPE"
const cveDBURL = "CVEDB_URL"
const cveDBPATH = "CVEDB_SQLITE3_PATH"
// Init set options with the following priority.
// 1. Environment variable
// 2. config.toml
func (cnf *GoCveDictConf) Init() {
cnf.Name = "cveDict"
if os.Getenv(cveDBType) != "" {
cnf.Type = os.Getenv(cveDBType)
}
if os.Getenv(cveDBURL) != "" {
cnf.URL = os.Getenv(cveDBURL)
}
if os.Getenv(cveDBPATH) != "" {
cnf.SQLite3Path = os.Getenv(cveDBPATH)
}
cnf.setDefault("cve.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}
// GostConf is gost config
type GostConf struct {
VulnDict
}
const gostDBType = "GOSTDB_TYPE"
const gostDBURL = "GOSTDB_URL"
const gostDBPATH = "GOSTDB_SQLITE3_PATH"
// Init set options with the following priority.
// 1. Environment variable
// 2. config.toml
func (cnf *GostConf) Init() {
cnf.Name = "gost"
if os.Getenv(gostDBType) != "" {
cnf.Type = os.Getenv(gostDBType)
}
if os.Getenv(gostDBURL) != "" {
cnf.URL = os.Getenv(gostDBURL)
}
if os.Getenv(gostDBPATH) != "" {
cnf.SQLite3Path = os.Getenv(gostDBPATH)
}
cnf.setDefault("gost.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}
// MetasploitConf is gost go-metasploitdb
type MetasploitConf struct {
VulnDict
}
const metasploitDBType = "METASPLOITDB_TYPE"
const metasploitDBURL = "METASPLOITDB_URL"
const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH"
// Init set options with the following priority.
// 1. Environment variable
// 2. config.toml
func (cnf *MetasploitConf) Init() {
cnf.Name = "metasploit"
if os.Getenv(metasploitDBType) != "" {
cnf.Type = os.Getenv(metasploitDBType)
}
if os.Getenv(metasploitDBURL) != "" {
cnf.URL = os.Getenv(metasploitDBURL)
}
if os.Getenv(metasploitDBPATH) != "" {
cnf.SQLite3Path = os.Getenv(metasploitDBPATH)
}
cnf.setDefault("go-msfdb.sqlite3")
cnf.DebugSQL = Conf.DebugSQL
}

61
constant/constant.go Normal file
View File

@@ -0,0 +1,61 @@
package constant
// Global constant
// Pkg local constants should not be defined here.
// Define them in the each package.
const (
// RedHat is
RedHat = "redhat"
// Debian is
Debian = "debian"
// Ubuntu is
Ubuntu = "ubuntu"
// CentOS is
CentOS = "centos"
// Fedora is
// Fedora = "fedora"
// Amazon is
Amazon = "amazon"
// Oracle is
Oracle = "oracle"
// FreeBSD is
FreeBSD = "freebsd"
// Raspbian is
Raspbian = "raspbian"
// Windows is
Windows = "windows"
// OpenSUSE is
OpenSUSE = "opensuse"
// OpenSUSELeap is
OpenSUSELeap = "opensuse.leap"
// SUSEEnterpriseServer is
SUSEEnterpriseServer = "suse.linux.enterprise.server"
// SUSEEnterpriseDesktop is
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
// SUSEOpenstackCloud is
SUSEOpenstackCloud = "suse.openstack.cloud"
// Alpine is
Alpine = "alpine"
// ServerTypePseudo is used for ServerInfo.Type, r.Family
ServerTypePseudo = "pseudo"
// DeepSecurity is
DeepSecurity = "deepsecurity"
)

View File

@@ -0,0 +1,38 @@
# future-vuls
## Main Features
- upload vuls results json to future-vuls
## Installation
```
git clone https://github.com/future-architect/vuls.git
make build-future-vuls
```
## Command Reference
```
Upload to FutureVuls
Usage:
future-vuls upload [flags]
Flags:
--config string config file (default is $HOME/.cobra.yaml)
-g, --group-id int future vuls group id, ENV: VULS_GROUP_ID
-h, --help help for upload
-s, --stdin input from stdin. ENV: VULS_STDIN
-t, --token string future vuls token
--url string future vuls upload url
--uuid string server uuid. ENV: VULS_SERVER_UUID
```
## Usage
- update results json
```
cat results.json | future-vuls upload --stdin --token xxxx --url https://xxxx --group-id 1 --uuid xxxx
```

View File

@@ -0,0 +1,98 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"os"
"strconv"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/saas"
"github.com/spf13/cobra"
)
var (
configFile string
stdIn bool
jsonDir string
serverUUID string
groupID int64
token string
url string
)
func main() {
var err error
var cmdFvulsUploader = &cobra.Command{
Use: "upload",
Short: "Upload to FutureVuls",
Long: `Upload to FutureVuls`,
Run: func(cmd *cobra.Command, args []string) {
if len(serverUUID) == 0 {
serverUUID = os.Getenv("VULS_SERVER_UUID")
}
if groupID == 0 {
envGroupID := os.Getenv("VULS_GROUP_ID")
if groupID, err = strconv.ParseInt(envGroupID, 10, 64); err != nil {
fmt.Printf("Invalid GroupID: %s\n", envGroupID)
return
}
}
if len(url) == 0 {
url = os.Getenv("VULS_URL")
}
if len(token) == 0 {
token = os.Getenv("VULS_TOKEN")
}
var scanResultJSON []byte
if stdIn {
reader := bufio.NewReader(os.Stdin)
buf := new(bytes.Buffer)
if _, err = buf.ReadFrom(reader); err != nil {
return
}
scanResultJSON = buf.Bytes()
} else {
fmt.Println("use --stdin option")
os.Exit(1)
return
}
var scanResult models.ScanResult
if err = json.Unmarshal(scanResultJSON, &scanResult); err != nil {
fmt.Println("Failed to parse json", err)
os.Exit(1)
return
}
scanResult.ServerUUID = serverUUID
config.Conf.Saas.GroupID = groupID
config.Conf.Saas.Token = token
config.Conf.Saas.URL = url
if err = (saas.Writer{}).Write(scanResult); err != nil {
fmt.Println(err)
os.Exit(1)
return
}
return
},
}
cmdFvulsUploader.PersistentFlags().StringVar(&serverUUID, "uuid", "", "server uuid. ENV: VULS_SERVER_UUID")
cmdFvulsUploader.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
cmdFvulsUploader.PersistentFlags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin. ENV: VULS_STDIN")
// TODO Read JSON file from directory
// cmdFvulsUploader.Flags().StringVarP(&jsonDir, "results-dir", "d", "./", "vuls scan results json dir")
cmdFvulsUploader.PersistentFlags().Int64VarP(&groupID, "group-id", "g", 0, "future vuls group id, ENV: VULS_GROUP_ID")
cmdFvulsUploader.PersistentFlags().StringVarP(&token, "token", "t", "", "future vuls token")
cmdFvulsUploader.PersistentFlags().StringVar(&url, "url", "", "future vuls upload url")
var rootCmd = &cobra.Command{Use: "future-vuls"}
rootCmd.AddCommand(cmdFvulsUploader)
if err = rootCmd.Execute(); err != nil {
fmt.Println("Failed to execute command", err)
}
}

View File

@@ -2,10 +2,13 @@ package parser
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/knqyf263/go-cpe/naming"
log "github.com/sirupsen/logrus"
"golang.org/x/xerrors"
)
type analysis struct {
@@ -13,12 +16,11 @@ type analysis struct {
}
type dependency struct {
Identifiers []identifier `xml:"identifiers>identifier"`
Identifiers []vulnerabilityID `xml:"identifiers>vulnerabilityIds"`
}
type identifier struct {
Name string `xml:"name"`
Type string `xml:"type,attr"`
type vulnerabilityID struct {
ID string `xml:"id"`
}
func appendIfMissing(slice []string, str string) []string {
@@ -30,32 +32,39 @@ func appendIfMissing(slice []string, str string) []string {
return append(slice, str)
}
// Parse parses XML and collect list of cpe
// Parse parses OWASP dependency check XML and collect list of cpe
func Parse(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Failed to open: %s", err)
log.Warnf("OWASP Dependency Check XML is not found: %s", path)
return []string{}, nil
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("Failed to read: %s", err)
log.Warnf("Failed to read OWASP Dependency Check XML: %s", path)
return []string{}, nil
}
var anal analysis
if err := xml.Unmarshal(b, &anal); err != nil {
return nil, fmt.Errorf("Failed to unmarshal: %s", err)
return nil, xerrors.Errorf("Failed to unmarshal: %s", err)
}
cpes := []string{}
for _, d := range anal.Dependencies {
for _, ident := range d.Identifiers {
if ident.Type == "cpe" {
name := strings.TrimPrefix(ident.Name, "(")
name = strings.TrimSuffix(name, ")")
cpes = appendIfMissing(cpes, name)
id := ident.ID // Start with cpe:2.3:
// Convert from CPE 2.3 to CPE 2.2
if strings.HasPrefix(id, "cpe:2.3:") {
wfn, err := naming.UnbindFS(id)
if err != nil {
return []string{}, err
}
id = naming.BindToURI(wfn)
}
cpes = appendIfMissing(cpes, id)
}
}
return cpes, nil

35
contrib/trivy/README.md Normal file
View File

@@ -0,0 +1,35 @@
# trivy-to-vuls
## Main Features
- convert trivy's results json to vuls's report json
## Installation
```
git clone https://github.com/future-architect/vuls.git
make build-trivy-to-vuls
```
## Command Reference
```
Parse trivy json to vuls results
Usage:
trivy-to-vuls parse [flags]
Flags:
-h, --help help for parse
-s, --stdin input from stdin
-d, --trivy-json-dir string trivy json dir (default "./")
-f, --trivy-json-file-name string trivy json file name (default "results.json")
```
## Usage
- use trivy output
```
trivy -q image -f=json python:3.4-alpine | trivy-to-vuls parse --stdin
```

78
contrib/trivy/cmd/main.go Normal file
View File

@@ -0,0 +1,78 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/future-architect/vuls/contrib/trivy/parser"
"github.com/future-architect/vuls/models"
"github.com/spf13/cobra"
)
var (
serverUUID string
stdIn bool
jsonDir string
jsonFileName string
)
func main() {
var err error
var cmdTrivyToVuls = &cobra.Command{
Use: "parse",
Short: "Parse trivy json to vuls results",
Long: `Parse trivy json to vuls results`,
Run: func(cmd *cobra.Command, args []string) {
jsonFilePath := filepath.Join(jsonDir, jsonFileName)
var trivyJSON []byte
if stdIn {
reader := bufio.NewReader(os.Stdin)
buf := new(bytes.Buffer)
if _, err = buf.ReadFrom(reader); err != nil {
os.Exit(1)
return
}
trivyJSON = buf.Bytes()
} else {
if trivyJSON, err = ioutil.ReadFile(jsonFilePath); err != nil {
fmt.Println("Failed to read file", err)
os.Exit(1)
return
}
}
scanResult := &models.ScanResult{
JSONVersion: models.JSONVersion,
ScannedCves: models.VulnInfos{},
}
if scanResult, err = parser.Parse(trivyJSON, scanResult); err != nil {
fmt.Println("Failed to execute command", err)
os.Exit(1)
return
}
var resultJSON []byte
if resultJSON, err = json.MarshalIndent(scanResult, "", " "); err != nil {
fmt.Println("Failed to create json", err)
os.Exit(1)
return
}
fmt.Println(string(resultJSON))
return
},
}
cmdTrivyToVuls.Flags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin")
cmdTrivyToVuls.Flags().StringVarP(&jsonDir, "trivy-json-dir", "d", "./", "trivy json dir")
cmdTrivyToVuls.Flags().StringVarP(&jsonFileName, "trivy-json-file-name", "f", "results.json", "trivy json file name")
var rootCmd = &cobra.Command{Use: "trivy-to-vuls"}
rootCmd.AddCommand(cmdTrivyToVuls)
if err = rootCmd.Execute(); err != nil {
fmt.Println("Failed to execute command", err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,180 @@
package parser
import (
"encoding/json"
"sort"
"time"
"github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/future-architect/vuls/models"
)
// Parse :
func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanResult, err error) {
var trivyResults report.Results
if err = json.Unmarshal(vulnJSON, &trivyResults); err != nil {
return nil, err
}
pkgs := models.Packages{}
vulnInfos := models.VulnInfos{}
uniqueLibraryScannerPaths := map[string]models.LibraryScanner{}
for _, trivyResult := range trivyResults {
if IsTrivySupportedOS(trivyResult.Type) {
overrideServerData(scanResult, &trivyResult)
}
for _, vuln := range trivyResult.Vulnerabilities {
if _, ok := vulnInfos[vuln.VulnerabilityID]; !ok {
vulnInfos[vuln.VulnerabilityID] = models.VulnInfo{
CveID: vuln.VulnerabilityID,
Confidences: models.Confidences{
{
Score: 100,
DetectionMethod: models.TrivyMatchStr,
},
},
AffectedPackages: models.PackageFixStatuses{},
CveContents: models.CveContents{},
LibraryFixedIns: models.LibraryFixedIns{},
// VulnType : "",
}
}
vulnInfo := vulnInfos[vuln.VulnerabilityID]
var notFixedYet bool
fixState := ""
if len(vuln.FixedVersion) == 0 {
notFixedYet = true
fixState = "Affected"
}
var references models.References
for _, reference := range vuln.References {
references = append(references, models.Reference{
Source: "trivy",
Link: reference,
})
}
sort.Slice(references, func(i, j int) bool {
return references[i].Link < references[j].Link
})
var published time.Time
if vuln.PublishedDate != nil {
published = *vuln.PublishedDate
}
var lastModified time.Time
if vuln.LastModifiedDate != nil {
lastModified = *vuln.LastModifiedDate
}
vulnInfo.CveContents = models.CveContents{
models.Trivy: models.CveContent{
Cvss3Severity: vuln.Severity,
References: references,
Title: vuln.Title,
Summary: vuln.Description,
Published: published,
LastModified: lastModified,
},
}
// do only if image type is Vuln
if IsTrivySupportedOS(trivyResult.Type) {
pkgs[vuln.PkgName] = models.Package{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
}
vulnInfo.AffectedPackages = append(vulnInfo.AffectedPackages, models.PackageFixStatus{
Name: vuln.PkgName,
NotFixedYet: notFixedYet,
FixState: fixState,
FixedIn: vuln.FixedVersion,
})
} else {
// LibraryScanの結果
vulnInfo.LibraryFixedIns = append(vulnInfo.LibraryFixedIns, models.LibraryFixedIn{
Key: trivyResult.Type,
Name: vuln.PkgName,
Path: trivyResult.Target,
FixedIn: vuln.FixedVersion,
})
libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
libScanner.Libs = append(libScanner.Libs, types.Library{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
})
uniqueLibraryScannerPaths[trivyResult.Target] = libScanner
}
vulnInfos[vuln.VulnerabilityID] = vulnInfo
}
}
// flatten and unique libraries
libraryScanners := make([]models.LibraryScanner, 0, len(uniqueLibraryScannerPaths))
for path, v := range uniqueLibraryScannerPaths {
uniqueLibrary := map[string]types.Library{}
for _, lib := range v.Libs {
uniqueLibrary[lib.Name+lib.Version] = lib
}
var libraries []types.Library
for _, library := range uniqueLibrary {
libraries = append(libraries, library)
}
sort.Slice(libraries, func(i, j int) bool {
return libraries[i].Name < libraries[j].Name
})
libscanner := models.LibraryScanner{
Path: path,
Libs: libraries,
}
libraryScanners = append(libraryScanners, libscanner)
}
sort.Slice(libraryScanners, func(i, j int) bool {
return libraryScanners[i].Path < libraryScanners[j].Path
})
scanResult.ScannedCves = vulnInfos
scanResult.Packages = pkgs
scanResult.LibraryScanners = libraryScanners
return scanResult, nil
}
// IsTrivySupportedOS :
func IsTrivySupportedOS(family string) bool {
supportedFamilies := []string{
os.RedHat,
os.Debian,
os.Ubuntu,
os.CentOS,
os.Fedora,
os.Amazon,
os.Oracle,
os.Windows,
os.OpenSUSE,
os.OpenSUSELeap,
os.OpenSUSETumbleweed,
os.SLES,
os.Photon,
os.Alpine,
}
for _, supportedFamily := range supportedFamilies {
if family == supportedFamily {
return true
}
}
return false
}
func overrideServerData(scanResult *models.ScanResult, trivyResult *report.Result) {
scanResult.Family = trivyResult.Type
scanResult.ServerName = trivyResult.Target
scanResult.Optional = map[string]interface{}{
"trivy-target": trivyResult.Target,
}
scanResult.ScannedAt = time.Now()
scanResult.ScannedBy = "trivy"
scanResult.ScannedVia = "trivy"
}

File diff suppressed because it is too large Load Diff

33
cwe/cwe.go Normal file
View File

@@ -0,0 +1,33 @@
package cwe
// CweTopTwentyfive2019 has CWE-ID in CWE Top 25
var CweTopTwentyfive2019 = map[string]string{
"119": "1",
"79": "2",
"20": "3",
"200": "4",
"125": "5",
"89": "6",
"416": "7",
"190": "8",
"352": "9",
"22": "10",
"78": "11",
"787": "12",
"287": "13",
"476": "14",
"732": "16",
"434": "16",
"611": "17",
"94": "18",
"798": "19",
"400": "20",
"772": "21",
"426": "22",
"502": "23",
"269": "24",
"295": "25",
}
// CweTopTwentyfive2019URL has CWE Top25 links
var CweTopTwentyfive2019URL = "https://cwe.mitre.org/top25/archive/2019/2019_cwe_top25.html"

5145
cwe/en.go Normal file

File diff suppressed because it is too large Load Diff

1027
cwe/ja.go Normal file

File diff suppressed because it is too large Load Diff

65
cwe/owasp.go Normal file
View File

@@ -0,0 +1,65 @@
package cwe
// OwaspTopTen2017 has CWE-ID in OWSP Top 10
var OwaspTopTen2017 = map[string]string{
"77": "1",
"89": "1",
"564": "1",
"917": "1",
"287": "2",
"384": "2",
"220": "3",
"310": "3",
"312": "3",
"319": "3",
"326": "3",
"359": "3",
"611": "4",
"22": "5",
"284": "5",
"285": "5",
"639": "5",
"2": "6",
"16": "6",
"388": "6",
"79": "7",
"502": "8",
"223": "10",
"778": "10",
}
// OwaspTopTen2017GitHubURLEn has GitHub links
var OwaspTopTen2017GitHubURLEn = map[string]string{
"1": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa1-injection.md",
"2": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa2-broken-authentication.md",
"3": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa3-sensitive-data-disclosure.md",
"4": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa4-xxe.md",
"5": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa5-broken-access-control.md",
"6": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa6-security-misconfiguration.md",
"7": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa7-xss.md",
"8": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa8-insecure-deserialization.md",
"9": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa9-known-vulns.md<Paste>",
"10": "https://github.com/OWASP/Top10/blob/master/2017/en/0xaa-logging-detection-response.md",
}
// OwaspTopTen2017GitHubURLJa has GitHub links
var OwaspTopTen2017GitHubURLJa = map[string]string{
"1": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa1-injection.md",
"2": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa2-broken-authentication.md",
"3": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa3-sensitive-data-disclosure.md",
"4": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa4-xxe.md",
"5": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa5-broken-access-control.md",
"6": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa6-security-misconfiguration.md",
"7": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa7-xss.md",
"8": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa8-insecure-deserialization.md",
"9": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa9-known-vulns.md<Paste>",
"10": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xaa-logging-detection-response.md",
}

33
cwe/sans.go Normal file
View File

@@ -0,0 +1,33 @@
package cwe
// SansTopTwentyfive has CWE-ID in CWE/SANS Top 25
var SansTopTwentyfive = map[string]string{
"89": "1",
"78": "2",
"120": "3",
"79": "4",
"306": "5",
"862": "6",
"798": "7",
"311": "8",
"434": "9",
"807": "10",
"250": "11",
"352": "12",
"22": "13",
"494": "14",
"863": "15",
"829": "16",
"732": "17",
"676": "18",
"327": "19",
"131": "20",
"307": "21",
"601": "22",
"134": "23",
"190": "24",
"759": "25",
}
// SansTopTwentyfiveURL is a URL of sans 25
var SansTopTwentyfiveURL = "https://www.sans.org/top25-software-errors/"

216
detector/cve_client.go Normal file
View File

@@ -0,0 +1,216 @@
// +build !scanner
package detector
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/cenkalti/backoff"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/util"
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
cvelog "github.com/kotakanbe/go-cve-dictionary/log"
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
)
type goCveDictClient struct {
cnf config.VulnDictInterface
driver cvedb.DB
}
func newGoCveDictClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goCveDictClient, error) {
cvelog.SetLogger(o.Debug, o.Quiet, false, o.LogToFile, o.LogDir)
driver, locked, err := newCveDB(cnf)
if locked {
return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return nil, err
}
return &goCveDictClient{cnf: cnf, driver: driver}, nil
}
func (api goCveDictClient) closeDB() error {
if api.driver == nil {
return nil
}
if err := api.driver.CloseDB(); err != nil {
return xerrors.Errorf("Failed to close DB: %+v", err)
}
return nil
}
func (api goCveDictClient) fetchCveDetails(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
for _, cveID := range cveIDs {
cveDetail, err := api.driver.Get(cveID)
if err != nil {
return nil, xerrors.Errorf("Failed to fetch CVE. err: %w", err)
}
if len(cveDetail.CveID) == 0 {
cveDetails = append(cveDetails, cvemodels.CveDetail{CveID: cveID})
} else {
cveDetails = append(cveDetails, *cveDetail)
}
}
return
}
type response struct {
Key string
CveDetail cvemodels.CveDetail
}
func (api goCveDictClient) fetchCveDetailsViaHTTP(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
reqChan := make(chan string, len(cveIDs))
resChan := make(chan response, len(cveIDs))
errChan := make(chan error, len(cveIDs))
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, cveID := range cveIDs {
reqChan <- cveID
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range cveIDs {
tasks <- func() {
select {
case cveID := <-reqChan:
url, err := util.URLPathJoin(api.cnf.GetURL(), "cves", cveID)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
api.httpGet(cveID, url, resChan, errChan)
}
}
}
}
timeout := time.After(2 * 60 * time.Second)
var errs []error
for range cveIDs {
select {
case res := <-resChan:
if len(res.CveDetail.CveID) == 0 {
cveDetails = append(cveDetails, cvemodels.CveDetail{
CveID: res.Key,
})
} else {
cveDetails = append(cveDetails, res.CveDetail)
}
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching CVE")
}
}
if len(errs) != 0 {
return nil,
xerrors.Errorf("Failed to fetch CVE. err: %w", errs)
}
return
}
func (api goCveDictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
f := func() (err error) {
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %+v",
url, resp, errs)
}
return nil
}
notify := func(err error, t time.Duration) {
logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
errChan <- xerrors.Errorf("HTTP Error: %w", err)
return
}
cveDetail := cvemodels.CveDetail{}
if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
errChan <- xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
return
}
resChan <- response{
key,
cveDetail,
}
}
func (api goCveDictClient) fetchCveDetailsByCpeName(cpeName string) ([]cvemodels.CveDetail, error) {
if api.cnf.IsFetchViaHTTP() {
url, err := util.URLPathJoin(api.cnf.GetURL(), "cpes")
if err != nil {
return nil, err
}
query := map[string]string{"name": cpeName}
logging.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
return api.httpPost(cpeName, url, query)
}
return api.driver.GetByCpeURI(cpeName)
}
func (api goCveDictClient) httpPost(key, url string, query map[string]string) ([]cvemodels.CveDetail, error) {
var body string
var errs []error
var resp *http.Response
f := func() (err error) {
req := gorequest.New().Timeout(10 * time.Second).Post(url)
for key := range query {
req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
}
resp, body, errs = req.End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", url, resp, errs)
}
return nil
}
notify := func(err error, t time.Duration) {
logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
return nil, xerrors.Errorf("HTTP Error: %w", err)
}
cveDetails := []cvemodels.CveDetail{}
if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
return nil,
xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
return cveDetails, nil
}
func newCveDB(cnf config.VulnDictInterface) (driver cvedb.DB, locked bool, err error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
driver, locked, err = cvedb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL())
if err != nil {
err = xerrors.Errorf("Failed to init CVE DB. err: %w, path: %s", err, path)
return nil, locked, err
}
return driver, false, nil
}

479
detector/detector.go Normal file
View File

@@ -0,0 +1,479 @@
// +build !scanner
package detector
import (
"os"
"strings"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
"github.com/future-architect/vuls/cwe"
"github.com/future-architect/vuls/gost"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/oval"
"github.com/future-architect/vuls/reporter"
"github.com/future-architect/vuls/util"
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
"golang.org/x/xerrors"
)
// Detect vulns and fill CVE detailed information
func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
// Use the same reportedAt for all rs
reportedAt := time.Now()
for i, r := range rs {
if !config.Conf.RefreshCve && !needToRefreshCve(r) {
logging.Log.Info("No need to refresh")
continue
}
if !reuseScannedCves(&r) {
r.ScannedCves = models.VulnInfos{}
}
cpeURIs, owaspDCXMLPath := []string{}, ""
if len(r.Container.ContainerID) == 0 {
cpeURIs = config.Conf.Servers[r.ServerName].CpeNames
owaspDCXMLPath = config.Conf.Servers[r.ServerName].OwaspDCXMLPath
} else {
if s, ok := config.Conf.Servers[r.ServerName]; ok {
if con, ok := s.Containers[r.Container.Name]; ok {
cpeURIs = con.Cpes
owaspDCXMLPath = con.OwaspDCXMLPath
}
}
}
if owaspDCXMLPath != "" {
cpes, err := parser.Parse(owaspDCXMLPath)
if err != nil {
return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w",
r.ServerInfo(), owaspDCXMLPath, err)
}
cpeURIs = append(cpeURIs, cpes...)
}
if err := DetectLibsCves(&r, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}
if err := DetectCpeURIsCves(&r, cpeURIs, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to detect CVE of `%s`: %w", cpeURIs, err)
}
repos := config.Conf.Servers[r.ServerName].GitHubRepos
if err := DetectGitHubCves(&r, repos); err != nil {
return nil, xerrors.Errorf("Failed to detect GitHub Cves: %w", err)
}
if err := DetectWordPressCves(&r, config.Conf.WpScan); err != nil {
return nil, xerrors.Errorf("Failed to detect WordPress Cves: %w", err)
}
if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost); err != nil {
return nil, xerrors.Errorf("Failed to fill with gost: %w", err)
}
if err := FillCvesWithNvdJvn(&r, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to fill with CVE: %w", err)
}
nExploitCve, err := FillWithExploit(&r, config.Conf.Exploit)
if err != nil {
return nil, xerrors.Errorf("Failed to fill with exploit: %w", err)
}
logging.Log.Infof("%s: %d PoC are detected", r.FormatServerName(), nExploitCve)
nMetasploitCve, err := FillWithMetasploit(&r, config.Conf.Metasploit)
if err != nil {
return nil, xerrors.Errorf("Failed to fill with metasploit: %w", err)
}
logging.Log.Infof("%s: %d exploits are detected", r.FormatServerName(), nMetasploitCve)
FillCweDict(&r)
r.ReportedBy, _ = os.Hostname()
r.Lang = config.Conf.Lang
r.ReportedAt = reportedAt
r.ReportedVersion = config.Version
r.ReportedRevision = config.Revision
r.Config.Report = config.Conf
r.Config.Report.Servers = map[string]config.ServerInfo{
r.ServerName: config.Conf.Servers[r.ServerName],
}
rs[i] = r
}
// Overwrite the json file every time to clear the fields specified in config.IgnoredJSONKeys
for _, r := range rs {
if s, ok := config.Conf.Servers[r.ServerName]; ok {
r = r.ClearFields(s.IgnoredJSONKeys)
}
//TODO don't call here
if err := reporter.OverwriteJSONFile(dir, r); err != nil {
return nil, xerrors.Errorf("Failed to write JSON: %w", err)
}
}
if config.Conf.DiffPlus || config.Conf.DiffMinus {
prevs, err := loadPrevious(rs, config.Conf.ResultsDir)
if err != nil {
return nil, err
}
rs = diff(rs, prevs, config.Conf.DiffPlus, config.Conf.DiffMinus)
}
for i, r := range rs {
r.ScannedCves = r.ScannedCves.FilterByCvssOver(config.Conf.CvssScoreOver)
r.ScannedCves = r.ScannedCves.FilterUnfixed(config.Conf.IgnoreUnfixed)
// IgnoreCves
ignoreCves := []string{}
if r.Container.Name == "" {
ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
} else if con, ok := config.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
ignoreCves = con.IgnoreCves
}
r.ScannedCves = r.ScannedCves.FilterIgnoreCves(ignoreCves)
// ignorePkgs
ignorePkgsRegexps := []string{}
if r.Container.Name == "" {
ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
} else if s, ok := config.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
ignorePkgsRegexps = s.IgnorePkgsRegexp
}
r.ScannedCves = r.ScannedCves.FilterIgnorePkgs(ignorePkgsRegexps)
// IgnoreUnscored
if config.Conf.IgnoreUnscoredCves {
r.ScannedCves = r.ScannedCves.FindScoredVulns()
}
r.FilterInactiveWordPressLibs(config.Conf.WpScan.DetectInactive)
rs[i] = r
}
return rs, nil
}
// DetectPkgCves detects OS pkg cves
// pass 2 configs
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf) error {
// Pkg Scan
if r.Release != "" {
// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
}
// gost
if err := detectPkgsCvesWithGost(gostCnf, r); err != nil {
return xerrors.Errorf("Failed to detect CVE with gost: %w", err)
}
} else if reuseScannedCves(r) {
logging.Log.Infof("r.Release is empty. Use CVEs as it as.")
} else if r.Family == constant.ServerTypePseudo {
logging.Log.Infof("pseudo type. Skip OVAL and gost detection")
} else {
return xerrors.Errorf("Failed to fill CVEs. r.Release is empty")
}
for i, v := range r.ScannedCves {
for j, p := range v.AffectedPackages {
if p.NotFixedYet && p.FixState == "" {
p.FixState = "Not fixed yet"
r.ScannedCves[i].AffectedPackages[j] = p
}
}
}
// To keep backward compatibility
// Newer versions use ListenPortStats,
// but older versions of Vuls are set to ListenPorts.
// Set ListenPorts to ListenPortStats to allow newer Vuls to report old results.
for i, pkg := range r.Packages {
for j, proc := range pkg.AffectedProcs {
for _, ipPort := range proc.ListenPorts {
ps, err := models.NewPortStat(ipPort)
if err != nil {
logging.Log.Warnf("Failed to parse ip:port: %s, err:%+v", ipPort, err)
continue
}
r.Packages[i].AffectedProcs[j].ListenPortStats = append(
r.Packages[i].AffectedProcs[j].ListenPortStats, *ps)
}
}
}
return nil
}
// DetectGitHubCves fetches CVEs from GitHub Security Alerts
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHubConf) error {
if len(githubConfs) == 0 {
return nil
}
for ownerRepo, setting := range githubConfs {
ss := strings.Split(ownerRepo, "/")
if len(ss) != 2 {
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s", ownerRepo)
}
owner, repo := ss[0], ss[1]
n, err := DetectGitHubSecurityAlerts(r, owner, repo, setting.Token, setting.IgnoreGitHubDismissed)
if err != nil {
return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err)
}
logging.Log.Infof("%s: %d CVEs detected with GHSA %s/%s",
r.FormatServerName(), n, owner, repo)
}
return nil
}
// DetectWordPressCves detects CVEs of WordPress
func DetectWordPressCves(r *models.ScanResult, wpCnf config.WpScanConf) error {
if len(r.WordPressPackages) == 0 {
return nil
}
logging.Log.Infof("%s: Detect WordPress CVE. Number of pkgs: %d ", r.ServerInfo(), len(r.WordPressPackages))
n, err := detectWordPressCves(r, wpCnf)
if err != nil {
return xerrors.Errorf("Failed to detect WordPress CVE: %w", err)
}
logging.Log.Infof("%s: found %d WordPress CVEs", r.FormatServerName(), n)
return nil
}
// FillCvesWithNvdJvn fills CVE detail with NVD, JVN
func FillCvesWithNvdJvn(r *models.ScanResult, cnf config.GoCveDictConf, logOpts logging.LogOpts) (err error) {
cveIDs := []string{}
for _, v := range r.ScannedCves {
cveIDs = append(cveIDs, v.CveID)
}
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return err
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
var ds []cvemodels.CveDetail
if cnf.IsFetchViaHTTP() {
ds, err = client.fetchCveDetailsViaHTTP(cveIDs)
} else {
ds, err = client.fetchCveDetails(cveIDs)
}
if err != nil {
return err
}
for _, d := range ds {
nvd, exploits, mitigations := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON)
jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
alerts := fillCertAlerts(&d)
for cveID, vinfo := range r.ScannedCves {
if vinfo.CveID == d.CveID {
if vinfo.CveContents == nil {
vinfo.CveContents = models.CveContents{}
}
for _, con := range []*models.CveContent{nvd, jvn} {
if con != nil && !con.Empty() {
vinfo.CveContents[con.Type] = *con
}
}
vinfo.AlertDict = alerts
vinfo.Exploits = append(vinfo.Exploits, exploits...)
vinfo.Mitigations = append(vinfo.Mitigations, mitigations...)
r.ScannedCves[cveID] = vinfo
break
}
}
}
return nil
}
func fillCertAlerts(cvedetail *cvemodels.CveDetail) (dict models.AlertDict) {
if cvedetail.NvdJSON != nil {
for _, cert := range cvedetail.NvdJSON.Certs {
dict.En = append(dict.En, models.Alert{
URL: cert.Link,
Title: cert.Title,
Team: "us",
})
}
}
if cvedetail.Jvn != nil {
for _, cert := range cvedetail.Jvn.Certs {
dict.Ja = append(dict.Ja, models.Alert{
URL: cert.Link,
Title: cert.Title,
Team: "jp",
})
}
}
return dict
}
// detectPkgsCvesWithOval fetches OVAL database
func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult) error {
ovalClient, err := oval.NewOVALClient(r.Family, cnf)
if err != nil {
return err
}
if ovalClient == nil {
return nil
}
logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
ok, err := ovalClient.CheckIfOvalFetched(r.Family, r.Release)
if err != nil {
return err
}
if !ok {
return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", r.Family, r.Release)
}
logging.Log.Debugf("Check if oval fresh: %s %s", r.Family, r.Release)
_, err = ovalClient.CheckIfOvalFresh(r.Family, r.Release)
if err != nil {
return err
}
logging.Log.Debugf("Fill with oval: %s %s", r.Family, r.Release)
nCVEs, err := ovalClient.FillWithOval(r)
if err != nil {
return err
}
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), nCVEs)
return nil
}
func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
client, err := gost.NewClient(cnf, r.Family)
if err != nil {
return xerrors.Errorf("Failed to new a gost client: %w", err)
}
defer func() {
if err := client.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close the gost DB. err: %+v", err)
}
}()
nCVEs, err := client.DetectUnfixed(r, true)
if err != nil {
return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
}
logging.Log.Infof("%s: %d unfixed CVEs are detected with gost", r.FormatServerName(), nCVEs)
return nil
}
// DetectCpeURIsCves detects CVEs of given CPE-URIs
func DetectCpeURIsCves(r *models.ScanResult, cpeURIs []string, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return err
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
nCVEs := 0
for _, name := range cpeURIs {
details, err := client.fetchCveDetailsByCpeName(name)
if err != nil {
return err
}
for _, detail := range details {
if val, ok := r.ScannedCves[detail.CveID]; ok {
names := val.CpeURIs
names = util.AppendIfMissing(names, name)
val.CpeURIs = names
val.Confidences.AppendIfMissing(models.CpeNameMatch)
r.ScannedCves[detail.CveID] = val
} else {
v := models.VulnInfo{
CveID: detail.CveID,
CpeURIs: []string{name},
Confidences: models.Confidences{models.CpeNameMatch},
}
r.ScannedCves[detail.CveID] = v
nCVEs++
}
}
}
logging.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs)
return nil
}
// FillCweDict fills CWE
func FillCweDict(r *models.ScanResult) {
uniqCweIDMap := map[string]bool{}
for _, vinfo := range r.ScannedCves {
for _, cont := range vinfo.CveContents {
for _, id := range cont.CweIDs {
if strings.HasPrefix(id, "CWE-") {
id = strings.TrimPrefix(id, "CWE-")
uniqCweIDMap[id] = true
}
}
}
}
dict := map[string]models.CweDictEntry{}
for id := range uniqCweIDMap {
entry := models.CweDictEntry{}
if e, ok := cwe.CweDictEn[id]; ok {
if rank, ok := cwe.OwaspTopTen2017[id]; ok {
entry.OwaspTopTen2017 = rank
}
if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
entry.CweTopTwentyfive2019 = rank
}
if rank, ok := cwe.SansTopTwentyfive[id]; ok {
entry.SansTopTwentyfive = rank
}
entry.En = &e
} else {
logging.Log.Debugf("CWE-ID %s is not found in English CWE Dict", id)
entry.En = &cwe.Cwe{CweID: id}
}
if r.Lang == "ja" {
if e, ok := cwe.CweDictJa[id]; ok {
if rank, ok := cwe.OwaspTopTen2017[id]; ok {
entry.OwaspTopTen2017 = rank
}
if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
entry.CweTopTwentyfive2019 = rank
}
if rank, ok := cwe.SansTopTwentyfive[id]; ok {
entry.SansTopTwentyfive = rank
}
entry.Ja = &e
} else {
logging.Log.Debugf("CWE-ID %s is not found in Japanese CWE Dict", id)
entry.Ja = &cwe.Cwe{CweID: id}
}
}
dict[id] = entry
}
r.CweDict = dict
return
}

223
detector/exploitdb.go Normal file
View File

@@ -0,0 +1,223 @@
// +build !scanner
package detector
import (
"encoding/json"
"net/http"
"time"
"github.com/cenkalti/backoff"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
exploitdb "github.com/vulsio/go-exploitdb/db"
exploitmodels "github.com/vulsio/go-exploitdb/models"
"golang.org/x/xerrors"
)
// FillWithExploit fills exploit information that has in Exploit
func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf) (nExploitCve int, err error) {
if cnf.IsFetchViaHTTP() {
var cveIDs []string
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
prefix, _ := util.URLPathJoin(cnf.GetURL(), "cves")
responses, err := getCvesViaHTTP(cveIDs, prefix)
if err != nil {
return 0, err
}
for _, res := range responses {
exps := []*exploitmodels.Exploit{}
if err := json.Unmarshal([]byte(res.json), &exps); err != nil {
return 0, err
}
exploits := ConvertToModels(exps)
v, ok := r.ScannedCves[res.request.cveID]
if ok {
v.Exploits = exploits
}
r.ScannedCves[res.request.cveID] = v
nExploitCve++
}
} else {
driver, locked, err := newExploitDB(&cnf)
if locked {
return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
for cveID, vuln := range r.ScannedCves {
if cveID == "" {
continue
}
es := driver.GetExploitByCveID(cveID)
if len(es) == 0 {
continue
}
exploits := ConvertToModels(es)
vuln.Exploits = exploits
r.ScannedCves[cveID] = vuln
nExploitCve++
}
}
return nExploitCve, nil
}
// ConvertToModels converts gost model to vuls model
func ConvertToModels(es []*exploitmodels.Exploit) (exploits []models.Exploit) {
for _, e := range es {
var documentURL, shellURL *string
if e.OffensiveSecurity != nil {
os := e.OffensiveSecurity
if os.Document != nil {
documentURL = &os.Document.DocumentURL
}
if os.ShellCode != nil {
shellURL = &os.ShellCode.ShellCodeURL
}
}
exploit := models.Exploit{
ExploitType: e.ExploitType,
ID: e.ExploitUniqueID,
URL: e.URL,
Description: e.Description,
DocumentURL: documentURL,
ShellCodeURL: shellURL,
}
exploits = append(exploits, exploit)
}
return exploits
}
type exploitResponse struct {
request request
json string
}
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
responses []exploitResponse, err error) {
nReq := len(cveIDs)
reqChan := make(chan request, nReq)
resChan := make(chan exploitResponse, nReq)
errChan := make(chan error, nReq)
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, cveID := range cveIDs {
reqChan <- request{
cveID: cveID,
}
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for i := 0; i < nReq; i++ {
tasks <- func() {
select {
case req := <-reqChan:
url, err := util.URLPathJoin(
urlPrefix,
req.cveID,
)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
httpGet(url, req, resChan, errChan)
}
}
}
}
timeout := time.After(2 * 60 * time.Second)
var errs []error
for i := 0; i < nReq; i++ {
select {
case res := <-resChan:
responses = append(responses, res)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching OVAL")
}
}
if len(errs) != 0 {
return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
}
return
}
type request struct {
osMajorVersion string
packName string
isSrcPack bool
cveID string
}
func httpGet(url string, req request, resChan chan<- exploitResponse, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
count, retryMax := 0, 3
f := func() (err error) {
// resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
count++
if count == retryMax {
return nil
}
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
}
return nil
}
notify := func(err error, t time.Duration) {
logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
errChan <- xerrors.Errorf("HTTP Error %w", err)
return
}
if count == retryMax {
errChan <- xerrors.New("Retry count exceeded")
return
}
resChan <- exploitResponse{
request: req,
json: body,
}
}
func newExploitDB(cnf config.VulnDictInterface) (driver exploitdb.DB, locked bool, err error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = exploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL()); err != nil {
if locked {
return nil, true, xerrors.Errorf("exploitDB is locked. err: %w", err)
}
return nil, false, err
}
return driver, false, nil
}

200
detector/github.go Normal file
View File

@@ -0,0 +1,200 @@
// +build !scanner
package detector
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"github.com/future-architect/vuls/errof"
"github.com/future-architect/vuls/models"
"golang.org/x/oauth2"
)
// DetectGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, ignoreDismissed bool) (nCVEs int, err error) {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
//TODO Proxy
httpClient := oauth2.NewClient(context.Background(), src)
// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
// Memo : https://developer.github.com/v4/explorer/
const jsonfmt = `{"query":
"query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}`
after := ""
for {
jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
"https://api.github.com/graphql",
bytes.NewBuffer([]byte(jsonStr)),
)
defer cancel()
if err != nil {
return 0, err
}
// https://developer.github.com/v4/previews/#repository-vulnerability-alerts
// To toggle this preview and access data, need to provide a custom media type in the Accept header:
// MEMO: I tried to get the affected version via GitHub API. Bit it seems difficult to determin the affected version if there are multiple dependency files such as package.json.
// TODO remove this header if it is no longer preview status in the future.
req.Header.Set("Accept", "application/vnd.github.package-deletes-preview+json")
req.Header.Set("Content-Type", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, err
}
alerts := SecurityAlerts{}
if err := json.Unmarshal(body, &alerts); err != nil {
return 0, err
}
// util.Log.Debugf("%s", pp.Sprint(alerts))
// util.Log.Debugf("%s", string(body))
if alerts.Data.Repository.URL == "" {
return 0, errof.New(errof.ErrFailedToAccessGithubAPI,
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)))
}
for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
if ignoreDismissed && v.Node.DismissReason != "" {
continue
}
pkgName := fmt.Sprintf("%s %s",
alerts.Data.Repository.URL, v.Node.SecurityVulnerability.Package.Name)
m := models.GitHubSecurityAlert{
PackageName: pkgName,
FixedIn: v.Node.SecurityVulnerability.FirstPatchedVersion.Identifier,
AffectedRange: v.Node.SecurityVulnerability.VulnerableVersionRange,
Dismissed: len(v.Node.DismissReason) != 0,
DismissedAt: v.Node.DismissedAt,
DismissReason: v.Node.DismissReason,
}
cveIDs, other := []string{}, []string{}
for _, identifier := range v.Node.SecurityAdvisory.Identifiers {
if identifier.Type == "CVE" {
cveIDs = append(cveIDs, identifier.Value)
} else {
other = append(other, identifier.Value)
}
}
// If CVE-ID has not been assigned, use the GHSA ID etc as a ID.
if len(cveIDs) == 0 {
cveIDs = other
}
refs := []models.Reference{}
for _, r := range v.Node.SecurityAdvisory.References {
refs = append(refs, models.Reference{Link: r.URL})
}
for _, cveID := range cveIDs {
cveContent := models.CveContent{
Type: models.GitHub,
CveID: cveID,
Title: v.Node.SecurityAdvisory.Summary,
Summary: v.Node.SecurityAdvisory.Description,
Cvss2Severity: v.Node.SecurityVulnerability.Severity,
Cvss3Severity: v.Node.SecurityVulnerability.Severity,
SourceLink: v.Node.SecurityAdvisory.Permalink,
References: refs,
Published: v.Node.SecurityAdvisory.PublishedAt,
LastModified: v.Node.SecurityAdvisory.UpdatedAt,
}
if val, ok := r.ScannedCves[cveID]; ok {
val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m)
val.CveContents[models.GitHub] = cveContent
r.ScannedCves[cveID] = val
} else {
v := models.VulnInfo{
CveID: cveID,
Confidences: models.Confidences{models.GitHubMatch},
GitHubSecurityAlerts: models.GitHubSecurityAlerts{m},
CveContents: models.NewCveContents(cveContent),
}
r.ScannedCves[cveID] = v
}
nCVEs++
}
}
if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
break
}
after = fmt.Sprintf(`after: \"%s\"`, alerts.Data.Repository.VulnerabilityAlerts.PageInfo.EndCursor)
}
return nCVEs, err
}
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
type SecurityAlerts struct {
Data struct {
Repository struct {
URL string `json:"url"`
VulnerabilityAlerts struct {
PageInfo struct {
EndCursor string `json:"endCursor"`
HasNextPage bool `json:"hasNextPage"`
StartCursor string `json:"startCursor"`
} `json:"pageInfo"`
Edges []struct {
Node struct {
ID string `json:"id"`
DismissReason string `json:"dismissReason"`
DismissedAt time.Time `json:"dismissedAt"`
SecurityVulnerability struct {
Package struct {
Name string `json:"name"`
Ecosystem string `json:"ecosystem"`
} `json:"package"`
Severity string `json:"severity"`
VulnerableVersionRange string `json:"vulnerableVersionRange"`
FirstPatchedVersion struct {
Identifier string `json:"identifier"`
} `json:"firstPatchedVersion"`
} `json:"securityVulnerability"`
SecurityAdvisory struct {
Description string `json:"description"`
GhsaID string `json:"ghsaId"`
Permalink string `json:"permalink"`
PublishedAt time.Time `json:"publishedAt"`
Summary string `json:"summary"`
UpdatedAt time.Time `json:"updatedAt"`
WithdrawnAt time.Time `json:"withdrawnAt"`
Origin string `json:"origin"`
Severity string `json:"severity"`
References []struct {
URL string `json:"url"`
} `json:"references"`
Identifiers []struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"identifiers"`
} `json:"securityAdvisory"`
} `json:"node"`
} `json:"edges"`
} `json:"vulnerabilityAlerts"`
} `json:"repository"`
} `json:"data"`
}

113
detector/library.go Normal file
View File

@@ -0,0 +1,113 @@
// +build !scanner
package detector
import (
"context"
db2 "github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/db"
"github.com/aquasecurity/trivy/pkg/github"
"github.com/aquasecurity/trivy/pkg/indicator"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/spf13/afero"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
)
// DetectLibsCves fills LibraryScanner information
func DetectLibsCves(r *models.ScanResult, cacheDir string, noProgress bool) (err error) {
totalCnt := 0
if len(r.LibraryScanners) == 0 {
return
}
// initialize trivy's logger and db
err = log.InitLogger(false, false)
if err != nil {
return err
}
logging.Log.Info("Updating library db...")
if err := downloadDB("", cacheDir, noProgress, false, false); err != nil {
return err
}
if err := db2.Init(cacheDir); err != nil {
return err
}
defer db2.Close()
for _, lib := range r.LibraryScanners {
vinfos, err := lib.Scan()
if err != nil {
return err
}
for _, vinfo := range vinfos {
vinfo.Confidences.AppendIfMissing(models.TrivyMatch)
if v, ok := r.ScannedCves[vinfo.CveID]; !ok {
r.ScannedCves[vinfo.CveID] = vinfo
} else {
v.LibraryFixedIns = append(v.LibraryFixedIns, vinfo.LibraryFixedIns...)
r.ScannedCves[vinfo.CveID] = v
}
}
totalCnt += len(vinfos)
}
logging.Log.Infof("%s: %d CVEs are detected with Library",
r.FormatServerName(), totalCnt)
return nil
}
func downloadDB(appVersion, cacheDir string, quiet, light, skipUpdate bool) error {
client := initializeDBClient(cacheDir, quiet)
ctx := context.Background()
needsUpdate, err := client.NeedsUpdate(appVersion, light, skipUpdate)
if err != nil {
return xerrors.Errorf("database error: %w", err)
}
if needsUpdate {
logging.Log.Info("Need to update DB")
logging.Log.Info("Downloading DB...")
if err := client.Download(ctx, cacheDir, light); err != nil {
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
}
if err = client.UpdateMetadata(cacheDir); err != nil {
return xerrors.Errorf("unable to update database metadata: %w", err)
}
}
// for debug
if err := showDBInfo(cacheDir); err != nil {
return xerrors.Errorf("failed to show database info: %w", err)
}
return nil
}
func initializeDBClient(cacheDir string, quiet bool) db.Client {
config := db2.Config{}
client := github.NewClient()
progressBar := indicator.NewProgressBar(quiet)
realClock := clock.RealClock{}
fs := afero.NewOsFs()
metadata := db.NewMetadata(fs, cacheDir)
dbClient := db.NewClient(config, client, progressBar, realClock, metadata)
return dbClient
}
func showDBInfo(cacheDir string) error {
m := db.NewMetadata(afero.NewOsFs(), cacheDir)
metadata, err := m.Get()
if err != nil {
return xerrors.Errorf("something wrong with DB: %w", err)
}
logging.Log.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
return nil
}

81
detector/msf.go Normal file
View File

@@ -0,0 +1,81 @@
// +build !scanner
package detector
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
metasploitdb "github.com/takuzoo3868/go-msfdb/db"
metasploitmodels "github.com/takuzoo3868/go-msfdb/models"
"golang.org/x/xerrors"
)
// FillWithMetasploit fills metasploit module information that has in module
func FillWithMetasploit(r *models.ScanResult, cnf config.MetasploitConf) (nMetasploitCve int, err error) {
driver, locked, err := newMetasploitDB(&cnf)
if locked {
return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
for cveID, vuln := range r.ScannedCves {
if cveID == "" {
continue
}
ms := driver.GetModuleByCveID(cveID)
if len(ms) == 0 {
continue
}
modules := ConvertToModelsMsf(ms)
vuln.Metasploits = modules
r.ScannedCves[cveID] = vuln
nMetasploitCve++
}
return nMetasploitCve, nil
}
// ConvertToModelsMsf converts gost model to vuls model
func ConvertToModelsMsf(ms []*metasploitmodels.Metasploit) (modules []models.Metasploit) {
for _, m := range ms {
var links []string
if 0 < len(m.References) {
for _, u := range m.References {
links = append(links, u.Link)
}
}
module := models.Metasploit{
Name: m.Name,
Title: m.Title,
Description: m.Description,
URLs: links,
}
modules = append(modules, module)
}
return modules
}
func newMetasploitDB(cnf config.VulnDictInterface) (driver metasploitdb.DB, locked bool, err error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = metasploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), false); err != nil {
if locked {
return nil, true, xerrors.Errorf("metasploitDB is locked. err: %w", err)
}
return nil, false, err
}
return driver, false, nil
}

272
detector/util.go Normal file
View File

@@ -0,0 +1,272 @@
// +build !scanner
package detector
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"golang.org/x/xerrors"
)
func reuseScannedCves(r *models.ScanResult) bool {
switch r.Family {
case constant.FreeBSD, constant.Raspbian:
return true
}
if isTrivyResult(r) {
return true
}
return false
}
func isTrivyResult(r *models.ScanResult) bool {
_, ok := r.Optional["trivy-target"]
return ok
}
func needToRefreshCve(r models.ScanResult) bool {
for _, cve := range r.ScannedCves {
if 0 < len(cve.CveContents) {
return false
}
}
return true
}
func loadPrevious(currs models.ScanResults, resultsDir string) (prevs models.ScanResults, err error) {
dirs, err := ListValidJSONDirs(resultsDir)
if err != nil {
return
}
for _, result := range currs {
filename := result.ServerName + ".json"
if result.Container.Name != "" {
filename = fmt.Sprintf("%s@%s.json", result.Container.Name, result.ServerName)
}
for _, dir := range dirs[1:] {
path := filepath.Join(dir, filename)
r, err := loadOneServerScanResult(path)
if err != nil {
logging.Log.Debugf("%+v", err)
continue
}
if r.Family == result.Family && r.Release == result.Release {
prevs = append(prevs, *r)
logging.Log.Infof("Previous json found: %s", path)
break
} else {
logging.Log.Infof("Previous json is different family.Release: %s, pre: %s.%s cur: %s.%s",
path, r.Family, r.Release, result.Family, result.Release)
}
}
}
return prevs, nil
}
func diff(curResults, preResults models.ScanResults, isPlus, isMinus bool) (diffed models.ScanResults) {
for _, current := range curResults {
found := false
var previous models.ScanResult
for _, r := range preResults {
if current.ServerName == r.ServerName && current.Container.Name == r.Container.Name {
found = true
previous = r
break
}
}
if !found {
diffed = append(diffed, current)
continue
}
cves := models.VulnInfos{}
if isPlus {
cves = getPlusDiffCves(previous, current)
}
if isMinus {
minus := getMinusDiffCves(previous, current)
if len(cves) == 0 {
cves = minus
} else {
for k, v := range minus {
cves[k] = v
}
}
}
packages := models.Packages{}
for _, s := range cves {
for _, affected := range s.AffectedPackages {
var p models.Package
if s.DiffStatus == models.DiffPlus {
p = current.Packages[affected.Name]
} else {
p = previous.Packages[affected.Name]
}
packages[affected.Name] = p
}
}
current.ScannedCves = cves
current.Packages = packages
diffed = append(diffed, current)
}
return
}
func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
previousCveIDsSet := map[string]bool{}
for _, previousVulnInfo := range previous.ScannedCves {
previousCveIDsSet[previousVulnInfo.CveID] = true
}
new := models.VulnInfos{}
updated := models.VulnInfos{}
for _, v := range current.ScannedCves {
if previousCveIDsSet[v.CveID] {
if isCveInfoUpdated(v.CveID, previous, current) {
v.DiffStatus = models.DiffPlus
updated[v.CveID] = v
logging.Log.Debugf("updated: %s", v.CveID)
// TODO commented out because a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at
// if these OVAL defs have different affected packages, this logic detects as updated.
// This logic will be uncomented after integration with gost https://github.com/knqyf263/gost
// } else if isCveFixed(v, previous) {
// updated[v.CveID] = v
// logging.Log.Debugf("fixed: %s", v.CveID)
} else {
logging.Log.Debugf("same: %s", v.CveID)
}
} else {
logging.Log.Debugf("new: %s", v.CveID)
v.DiffStatus = models.DiffPlus
new[v.CveID] = v
}
}
if len(updated) == 0 && len(new) == 0 {
logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
}
for cveID, vuln := range new {
updated[cveID] = vuln
}
return updated
}
func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
currentCveIDsSet := map[string]bool{}
for _, currentVulnInfo := range current.ScannedCves {
currentCveIDsSet[currentVulnInfo.CveID] = true
}
clear := models.VulnInfos{}
for _, v := range previous.ScannedCves {
if !currentCveIDsSet[v.CveID] {
v.DiffStatus = models.DiffMinus
clear[v.CveID] = v
logging.Log.Debugf("clear: %s", v.CveID)
}
}
if len(clear) == 0 {
logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
}
return clear
}
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
cTypes := []models.CveContentType{
models.Nvd,
models.Jvn,
models.NewCveContentType(current.Family),
}
prevLastModified := map[models.CveContentType]time.Time{}
preVinfo, ok := previous.ScannedCves[cveID]
if !ok {
return true
}
for _, cType := range cTypes {
if content, ok := preVinfo.CveContents[cType]; ok {
prevLastModified[cType] = content.LastModified
}
}
curLastModified := map[models.CveContentType]time.Time{}
curVinfo, ok := current.ScannedCves[cveID]
if !ok {
return true
}
for _, cType := range cTypes {
if content, ok := curVinfo.CveContents[cType]; ok {
curLastModified[cType] = content.LastModified
}
}
for _, t := range cTypes {
if !curLastModified[t].Equal(prevLastModified[t]) {
logging.Log.Debugf("%s LastModified not equal: \n%s\n%s",
cveID, curLastModified[t], prevLastModified[t])
return true
}
}
return false
}
// jsonDirPattern is file name pattern of JSON directory
// 2016-11-16T10:43:28+09:00
// 2016-11-16T10:43:28Z
var jsonDirPattern = regexp.MustCompile(
`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
// ListValidJSONDirs returns valid json directory as array
// Returned array is sorted so that recent directories are at the head
func ListValidJSONDirs(resultsDir string) (dirs []string, err error) {
var dirInfo []os.FileInfo
if dirInfo, err = ioutil.ReadDir(resultsDir); err != nil {
err = xerrors.Errorf("Failed to read %s: %w",
config.Conf.ResultsDir, err)
return
}
for _, d := range dirInfo {
if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
jsonDir := filepath.Join(resultsDir, d.Name())
dirs = append(dirs, jsonDir)
}
}
sort.Slice(dirs, func(i, j int) bool {
return dirs[j] < dirs[i]
})
return
}
// loadOneServerScanResult read JSON data of one server
func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
var (
data []byte
err error
)
if data, err = ioutil.ReadFile(jsonFile); err != nil {
return nil, xerrors.Errorf("Failed to read %s: %w", jsonFile, err)
}
result := &models.ScanResult{}
if err := json.Unmarshal(data, result); err != nil {
return nil, xerrors.Errorf("Failed to parse %s: %w", jsonFile, err)
}
return result, nil
}

272
detector/wordpress.go Normal file
View File

@@ -0,0 +1,272 @@
// +build !scanner
package detector
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/errof"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
version "github.com/hashicorp/go-version"
"golang.org/x/xerrors"
)
//WpCveInfos is for wpscan json
type WpCveInfos struct {
ReleaseDate string `json:"release_date"`
ChangelogURL string `json:"changelog_url"`
// Status string `json:"status"`
LatestVersion string `json:"latest_version"`
LastUpdated string `json:"last_updated"`
// Popular bool `json:"popular"`
Vulnerabilities []WpCveInfo `json:"vulnerabilities"`
Error string `json:"error"`
}
//WpCveInfo is for wpscan json
type WpCveInfo struct {
ID string `json:"id"`
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
VulnType string `json:"vuln_type"`
References References `json:"references"`
FixedIn string `json:"fixed_in"`
}
//References is for wpscan json
type References struct {
URL []string `json:"url"`
Cve []string `json:"cve"`
Secunia []string `json:"secunia"`
}
// DetectWordPressCves access to wpscan and fetch scurity alerts and then set to the given ScanResult.
// https://wpscan.com/
func detectWordPressCves(r *models.ScanResult, cnf config.WpScanConf) (int, error) {
if len(r.WordPressPackages) == 0 {
return 0, nil
}
// Core
ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1)
if ver == "" {
return 0, errof.New(errof.ErrFailedToAccessWpScan,
fmt.Sprintf("Failed to get WordPress core version."))
}
url := fmt.Sprintf("https://wpscan.com/api/v3/wordpresses/%s", ver)
wpVinfos, err := wpscan(url, ver, cnf.Token, true)
if err != nil {
return 0, err
}
// Themes
themes := r.WordPressPackages.Themes()
if !cnf.DetectInactive {
themes = removeInactives(themes)
}
for _, p := range themes {
url := fmt.Sprintf("https://wpscan.com/api/v3/themes/%s", p.Name)
candidates, err := wpscan(url, p.Name, cnf.Token, false)
if err != nil {
return 0, err
}
vulns := detect(p, candidates)
wpVinfos = append(wpVinfos, vulns...)
}
// Plugins
plugins := r.WordPressPackages.Plugins()
if !cnf.DetectInactive {
plugins = removeInactives(plugins)
}
for _, p := range plugins {
url := fmt.Sprintf("https://wpscan.com/api/v3/plugins/%s", p.Name)
candidates, err := wpscan(url, p.Name, cnf.Token, false)
if err != nil {
return 0, err
}
vulns := detect(p, candidates)
wpVinfos = append(wpVinfos, vulns...)
}
for _, wpVinfo := range wpVinfos {
if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok {
vinfo.CveContents[models.WpScan] = wpVinfo.CveContents[models.WpScan]
vinfo.VulnType = wpVinfo.VulnType
vinfo.Confidences = append(vinfo.Confidences, wpVinfo.Confidences...)
vinfo.WpPackageFixStats = append(vinfo.WpPackageFixStats, wpVinfo.WpPackageFixStats...)
r.ScannedCves[wpVinfo.CveID] = vinfo
} else {
r.ScannedCves[wpVinfo.CveID] = wpVinfo
}
}
return len(wpVinfos), nil
}
func wpscan(url, name, token string, isCore bool) (vinfos []models.VulnInfo, err error) {
body, err := httpRequest(url, token)
if err != nil {
return nil, err
}
if body == "" {
logging.Log.Debugf("wpscan.com response body is empty. URL: %s", url)
}
if isCore {
name = "core"
}
return convertToVinfos(name, body)
}
func detect(installed models.WpPackage, candidates []models.VulnInfo) (vulns []models.VulnInfo) {
for _, v := range candidates {
for _, fixstat := range v.WpPackageFixStats {
ok, err := match(installed.Version, fixstat.FixedIn)
if err != nil {
logging.Log.Warnf("Failed to compare versions %s installed: %s, fixedIn: %s, v: %+v",
installed.Name, installed.Version, fixstat.FixedIn, v)
// continue scanning
continue
}
if ok {
vulns = append(vulns, v)
logging.Log.Debugf("Affected: %s installed: %s, fixedIn: %s",
installed.Name, installed.Version, fixstat.FixedIn)
} else {
logging.Log.Debugf("Not affected: %s : %s, fixedIn: %s",
installed.Name, installed.Version, fixstat.FixedIn)
}
}
}
return
}
func match(installedVer, fixedIn string) (bool, error) {
v1, err := version.NewVersion(installedVer)
if err != nil {
return false, err
}
v2, err := version.NewVersion(fixedIn)
if err != nil {
return false, err
}
return v1.LessThan(v2), nil
}
func convertToVinfos(pkgName, body string) (vinfos []models.VulnInfo, err error) {
if body == "" {
return
}
// "pkgName" : CVE Detailed data
pkgnameCves := map[string]WpCveInfos{}
if err = json.Unmarshal([]byte(body), &pkgnameCves); err != nil {
return nil, xerrors.Errorf("Failed to unmarshal %s. err: %w", body, err)
}
for _, v := range pkgnameCves {
vs := extractToVulnInfos(pkgName, v.Vulnerabilities)
vinfos = append(vinfos, vs...)
}
return vinfos, nil
}
func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnInfo) {
for _, vulnerability := range cves {
var cveIDs []string
if len(vulnerability.References.Cve) == 0 {
cveIDs = append(cveIDs, fmt.Sprintf("WPVDBID-%s", vulnerability.ID))
}
for _, cveNumber := range vulnerability.References.Cve {
cveIDs = append(cveIDs, "CVE-"+cveNumber)
}
var refs []models.Reference
for _, url := range vulnerability.References.URL {
refs = append(refs, models.Reference{
Link: url,
})
}
for _, cveID := range cveIDs {
vinfos = append(vinfos, models.VulnInfo{
CveID: cveID,
CveContents: models.NewCveContents(
models.CveContent{
Type: models.WpScan,
CveID: cveID,
Title: vulnerability.Title,
References: refs,
Published: vulnerability.CreatedAt,
LastModified: vulnerability.UpdatedAt,
},
),
VulnType: vulnerability.VulnType,
Confidences: []models.Confidence{
models.WpScanMatch,
},
WpPackageFixStats: []models.WpPackageFixStatus{{
Name: pkgName,
FixedIn: vulnerability.FixedIn,
}},
})
}
}
return
}
func httpRequest(url, token string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
defer cancel()
if err != nil {
return "", errof.New(errof.ErrFailedToAccessWpScan,
fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
}
req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", errof.New(errof.ErrFailedToAccessWpScan,
fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", errof.New(errof.ErrFailedToAccessWpScan,
fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
return string(body), nil
} else if resp.StatusCode == 404 {
// This package is not in wpscan
return "", nil
} else if resp.StatusCode == 429 {
return "", errof.New(errof.ErrWpScanAPILimitExceeded,
fmt.Sprintf("wpscan.com API limit exceeded: %+v", resp.Status))
} else {
logging.Log.Warnf("wpscan.com unknown status code: %+v", resp.Status)
return "", nil
}
}
func removeInactives(pkgs models.WordPressPackages) (removed models.WordPressPackages) {
for _, p := range pkgs {
if p.Status == "inactive" {
continue
}
removed = append(removed, p)
}
return removed
}

View File

@@ -0,0 +1,83 @@
// +build !scanner
package detector
import (
"reflect"
"testing"
"github.com/future-architect/vuls/models"
)
func TestRemoveInactive(t *testing.T) {
var tests = []struct {
in models.WordPressPackages
expected models.WordPressPackages
}{
{
in: models.WordPressPackages{
{
Name: "akismet",
Status: "inactive",
Update: "",
Version: "",
Type: "",
},
},
expected: nil,
},
{
in: models.WordPressPackages{
{
Name: "akismet",
Status: "inactive",
Update: "",
Version: "",
Type: "",
},
{
Name: "BackWPup",
Status: "inactive",
Update: "",
Version: "",
Type: "",
},
},
expected: nil,
},
{
in: models.WordPressPackages{
{
Name: "akismet",
Status: "active",
Update: "",
Version: "",
Type: "",
},
{
Name: "BackWPup",
Status: "inactive",
Update: "",
Version: "",
Type: "",
},
},
expected: models.WordPressPackages{
{
Name: "akismet",
Status: "active",
Update: "",
Version: "",
Type: "",
},
},
},
}
for i, tt := range tests {
actual := removeInactives(tt.in)
if !reflect.DeepEqual(actual, tt.expected) {
t.Errorf("[%d] WordPressPackages error ", i)
}
}
}

33
errof/errof.go Normal file
View File

@@ -0,0 +1,33 @@
package errof
// ErrorCode is vuls error code
type ErrorCode string
// Error is vuls error
type Error struct {
Code ErrorCode
Message string
}
func (e Error) Error() string {
return e.Message
}
var (
// ErrFailedToAccessGithubAPI is error of github alert's api access
ErrFailedToAccessGithubAPI ErrorCode = "ErrFailedToAccessGithubAPI"
// ErrFailedToAccessWpScan is error of wpscan.com api access
ErrFailedToAccessWpScan ErrorCode = "ErrFailedToAccessWpScan"
// ErrWpScanAPILimitExceeded is error of wpscan.com api limit exceeded
ErrWpScanAPILimitExceeded ErrorCode = "ErrWpScanAPILimitExceeded"
)
// New :
func New(code ErrorCode, msg string) Error {
return Error{
Code: code,
Message: msg,
}
}

62
go.mod Normal file
View File

@@ -0,0 +1,62 @@
module github.com/future-architect/vuls
go 1.16
require (
github.com/Azure/azure-sdk-for-go v50.2.0+incompatible
github.com/BurntSushi/toml v0.3.1
github.com/Ullaakut/nmap/v2 v2.1.2-0.20210406060955-59a52fe80a4f
github.com/aquasecurity/fanal v0.0.0-20210520034323-54c5a82e861f
github.com/aquasecurity/trivy v0.18.3
github.com/aquasecurity/trivy-db v0.0.0-20210429114658-ae22941a55d0
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/aws/aws-sdk-go v1.36.31
github.com/boltdb/bolt v1.3.1
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.14.0
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/google/subcommands v1.2.0
github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
github.com/jesseduffield/gocui v0.3.0
github.com/k0kubun/pp v3.0.1+incompatible
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
github.com/knqyf263/gost v0.1.10
github.com/kotakanbe/go-cve-dictionary v0.5.12
github.com/kotakanbe/go-pingscanner v0.1.0
github.com/kotakanbe/goval-dictionary v0.3.6-0.20210429000733-6db1754b1d87
github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96
github.com/lib/pq v1.10.1 // indirect
github.com/magiconair/properties v1.8.4 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nlopes/slack v0.6.0
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/parnurzeal/gorequest v0.2.16
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.8.0
github.com/spf13/afero v1.6.0
github.com/spf13/cobra v1.1.3
github.com/takuzoo3868/go-msfdb v0.1.5
github.com/vulsio/go-exploitdb v0.1.7
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 // indirect
golang.org/x/tools v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
gopkg.in/ini.v1 v1.62.0 // indirect
k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
)

1973
go.sum Normal file

File diff suppressed because it is too large Load Diff

190
gost/debian.go Normal file
View File

@@ -0,0 +1,190 @@
// +build !scanner
package gost
import (
"encoding/json"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
gostmodels "github.com/knqyf263/gost/models"
)
// Debian is Gost client for Debian GNU/Linux
type Debian struct {
Base
}
type packCves struct {
packName string
isSrcPack bool
cves []models.CveContent
}
func (deb Debian) supported(major string) bool {
_, ok := map[string]string{
"8": "jessie",
"9": "stretch",
"10": "buster",
}[major]
return ok
}
// DetectUnfixed fills cve information that has in Gost
func (deb Debian) DetectUnfixed(r *models.ScanResult, _ bool) (nCVEs int, err error) {
if !deb.supported(major(r.Release)) {
// only logging
logging.Log.Warnf("Debian %s is not supported yet", r.Release)
return 0, nil
}
linuxImage := "linux-image-" + r.RunningKernel.Release
// Add linux and set the version of running kernel to search OVAL.
if r.Container.ContainerID == "" {
newVer := ""
if p, ok := r.Packages[linuxImage]; ok {
newVer = p.NewVersion
}
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
}
}
// Debian Security Tracker does not support Package for Raspbian, so skip it.
var scanResult models.ScanResult
if r.Family != constant.Raspbian {
scanResult = *r
} else {
scanResult = r.RemoveRaspbianPackFromResult()
}
packCvesList := []packCves{}
if deb.DBDriver.Cnf.IsFetchViaHTTP() {
url, _ := util.URLPathJoin(deb.DBDriver.Cnf.GetURL(), "debian", major(scanResult.Release), "pkgs")
responses, err := getAllUnfixedCvesViaHTTP(r, url)
if err != nil {
return 0, err
}
for _, res := range responses {
debCves := map[string]gostmodels.DebianCVE{}
if err := json.Unmarshal([]byte(res.json), &debCves); err != nil {
return 0, err
}
cves := []models.CveContent{}
for _, debcve := range debCves {
cves = append(cves, *deb.ConvertToModel(&debcve))
}
packCvesList = append(packCvesList, packCves{
packName: res.request.packName,
isSrcPack: res.request.isSrcPack,
cves: cves,
})
}
} else {
if deb.DBDriver.DB == nil {
return 0, nil
}
for _, pack := range scanResult.Packages {
cveDebs := deb.DBDriver.DB.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
cves := []models.CveContent{}
for _, cveDeb := range cveDebs {
cves = append(cves, *deb.ConvertToModel(&cveDeb))
}
packCvesList = append(packCvesList, packCves{
packName: pack.Name,
isSrcPack: false,
cves: cves,
})
}
// SrcPack
for _, pack := range scanResult.SrcPackages {
cveDebs := deb.DBDriver.DB.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
cves := []models.CveContent{}
for _, cveDeb := range cveDebs {
cves = append(cves, *deb.ConvertToModel(&cveDeb))
}
packCvesList = append(packCvesList, packCves{
packName: pack.Name,
isSrcPack: true,
cves: cves,
})
}
}
delete(r.Packages, "linux")
for _, p := range packCvesList {
for _, cve := range p.cves {
v, ok := r.ScannedCves[cve.CveID]
if ok {
if v.CveContents == nil {
v.CveContents = models.NewCveContents(cve)
} else {
v.CveContents[models.DebianSecurityTracker] = cve
}
} else {
v = models.VulnInfo{
CveID: cve.CveID,
CveContents: models.NewCveContents(cve),
Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
}
nCVEs++
}
names := []string{}
if p.isSrcPack {
if srcPack, ok := r.SrcPackages[p.packName]; ok {
for _, binName := range srcPack.BinaryNames {
if _, ok := r.Packages[binName]; ok {
names = append(names, binName)
}
}
}
} else {
if p.packName == "linux" {
names = append(names, linuxImage)
} else {
names = append(names, p.packName)
}
}
for _, name := range names {
v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
Name: name,
FixState: "open",
NotFixedYet: true,
})
}
r.ScannedCves[cve.CveID] = v
}
}
return nCVEs, nil
}
// ConvertToModel converts gost model to vuls model
func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
severity := ""
for _, p := range cve.Package {
for _, r := range p.Release {
severity = r.Urgency
break
}
}
return &models.CveContent{
Type: models.DebianSecurityTracker,
CveID: cve.CveID,
Summary: cve.Description,
Cvss2Severity: severity,
Cvss3Severity: severity,
SourceLink: "https://security-tracker.debian.org/tracker/" + cve.CveID,
Optional: map[string]string{
"attack range": cve.Scope,
},
}
}

63
gost/debian_test.go Normal file
View File

@@ -0,0 +1,63 @@
// +build !scanner
package gost
import "testing"
func TestDebian_Supported(t *testing.T) {
type fields struct {
Base Base
}
type args struct {
major string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "8 is supported",
args: args{
major: "8",
},
want: true,
},
{
name: "9 is supported",
args: args{
major: "9",
},
want: true,
},
{
name: "10 is supported",
args: args{
major: "10",
},
want: true,
},
{
name: "11 is not supported yet",
args: args{
major: "11",
},
want: false,
},
{
name: "empty string is not supported yet",
args: args{
major: "",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
deb := Debian{}
if got := deb.supported(tt.args.major); got != tt.want {
t.Errorf("Debian.Supported() = %v, want %v", got, tt.want)
}
})
}
}

95
gost/gost.go Normal file
View File

@@ -0,0 +1,95 @@
// +build !scanner
package gost
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/knqyf263/gost/db"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
)
// DBDriver is a DB Driver
type DBDriver struct {
DB db.DB
Cnf config.VulnDictInterface
}
// Client is the interface of OVAL client.
type Client interface {
DetectUnfixed(*models.ScanResult, bool) (int, error)
CloseDB() error
}
// Base is a base struct
type Base struct {
DBDriver DBDriver
}
// CloseDB close a DB connection
func (b Base) CloseDB() error {
if b.DBDriver.DB == nil {
return nil
}
return b.DBDriver.DB.CloseDB()
}
// FillCVEsWithRedHat fills CVE detailed with Red Hat Security
func FillCVEsWithRedHat(r *models.ScanResult, cnf config.GostConf) error {
db, locked, err := newGostDB(cnf)
if locked {
return xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return err
}
defer func() {
if err := db.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
return RedHat{Base{DBDriver{DB: db, Cnf: &cnf}}}.fillCvesWithRedHatAPI(r)
}
// NewClient make Client by family
func NewClient(cnf config.GostConf, family string) (Client, error) {
db, locked, err := newGostDB(cnf)
if locked {
return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return nil, err
}
driver := DBDriver{DB: db, Cnf: &cnf}
switch family {
case constant.RedHat, constant.CentOS:
return RedHat{Base{DBDriver: driver}}, nil
case constant.Debian, constant.Raspbian:
return Debian{Base{DBDriver: driver}}, nil
case constant.Windows:
return Microsoft{Base{DBDriver: driver}}, nil
default:
return Pseudo{}, nil
}
}
// NewGostDB returns db client for Gost
func newGostDB(cnf config.GostConf) (driver db.DB, locked bool, err error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = db.NewDB(cnf.GetType(), path, cnf.GetDebugSQL()); err != nil {
if locked {
return nil, true, xerrors.Errorf("gostDB is locked. err: %w", err)
}
return nil, false, err
}
return driver, false, nil
}

131
gost/gost_test.go Normal file
View File

@@ -0,0 +1,131 @@
// +build !scanner
package gost
import (
"reflect"
"testing"
"github.com/future-architect/vuls/models"
gostmodels "github.com/knqyf263/gost/models"
)
func TestSetPackageStates(t *testing.T) {
var tests = []struct {
pkgstats []gostmodels.RedhatPackageState
installed models.Packages
release string
in models.VulnInfo
out models.PackageFixStatuses
}{
//0 one
{
pkgstats: []gostmodels.RedhatPackageState{
{
FixState: "Will not fix",
PackageName: "bouncycastle",
Cpe: "cpe:/o:redhat:enterprise_linux:7",
},
},
installed: models.Packages{
"bouncycastle": models.Package{},
},
release: "7",
in: models.VulnInfo{},
out: []models.PackageFixStatus{
{
Name: "bouncycastle",
FixState: "Will not fix",
NotFixedYet: true,
},
},
},
//1 two
{
pkgstats: []gostmodels.RedhatPackageState{
{
FixState: "Will not fix",
PackageName: "bouncycastle",
Cpe: "cpe:/o:redhat:enterprise_linux:7",
},
{
FixState: "Fix deferred",
PackageName: "pack_a",
Cpe: "cpe:/o:redhat:enterprise_linux:7",
},
// ignore not-installed-package
{
FixState: "Fix deferred",
PackageName: "pack_b",
Cpe: "cpe:/o:redhat:enterprise_linux:7",
},
},
installed: models.Packages{
"bouncycastle": models.Package{},
"pack_a": models.Package{},
},
release: "7",
in: models.VulnInfo{},
out: []models.PackageFixStatus{
{
Name: "bouncycastle",
FixState: "Will not fix",
NotFixedYet: true,
},
{
Name: "pack_a",
FixState: "Fix deferred",
NotFixedYet: true,
},
},
},
//2 ignore affected
{
pkgstats: []gostmodels.RedhatPackageState{
{
FixState: "affected",
PackageName: "bouncycastle",
Cpe: "cpe:/o:redhat:enterprise_linux:7",
},
},
installed: models.Packages{
"bouncycastle": models.Package{},
},
release: "7",
in: models.VulnInfo{
AffectedPackages: models.PackageFixStatuses{},
},
out: models.PackageFixStatuses{},
},
//3 look only the same os release.
{
pkgstats: []gostmodels.RedhatPackageState{
{
FixState: "Will not fix",
PackageName: "bouncycastle",
Cpe: "cpe:/o:redhat:enterprise_linux:6",
},
},
installed: models.Packages{
"bouncycastle": models.Package{},
},
release: "7",
in: models.VulnInfo{
AffectedPackages: models.PackageFixStatuses{},
},
out: models.PackageFixStatuses{},
},
}
r := RedHat{}
for i, tt := range tests {
out := r.mergePackageStates(tt.in, tt.pkgstats, tt.installed, tt.release)
if ok := reflect.DeepEqual(tt.out, out); !ok {
t.Errorf("[%d]\nexpected: %v:%T\n actual: %v:%T\n", i, tt.out, tt.out, out, out)
}
}
}

116
gost/microsoft.go Normal file
View File

@@ -0,0 +1,116 @@
// +build !scanner
package gost
import (
"strings"
"github.com/future-architect/vuls/models"
gostmodels "github.com/knqyf263/gost/models"
)
// Microsoft is Gost client for windows
type Microsoft struct {
Base
}
// DetectUnfixed fills cve information that has in Gost
func (ms Microsoft) DetectUnfixed(r *models.ScanResult, _ bool) (nCVEs int, err error) {
if ms.DBDriver.DB == nil {
return 0, nil
}
cveIDs := []string{}
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
for cveID, msCve := range ms.DBDriver.DB.GetMicrosoftMulti(cveIDs) {
if _, ok := r.ScannedCves[cveID]; !ok {
continue
}
cveCont, mitigations := ms.ConvertToModel(&msCve)
v, _ := r.ScannedCves[cveID]
if v.CveContents == nil {
v.CveContents = models.CveContents{}
}
v.CveContents[models.Microsoft] = *cveCont
v.Mitigations = append(v.Mitigations, mitigations...)
r.ScannedCves[cveID] = v
}
return len(cveIDs), nil
}
// ConvertToModel converts gost model to vuls model
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) {
v3score := 0.0
var v3Vector string
for _, scoreSet := range cve.ScoreSets {
if v3score < scoreSet.BaseScore {
v3score = scoreSet.BaseScore
v3Vector = scoreSet.Vector
}
}
var v3Severity string
for _, s := range cve.Severity {
v3Severity = s.Description
}
var refs []models.Reference
for _, r := range cve.References {
if r.AttrType == "External" {
refs = append(refs, models.Reference{Link: r.URL})
}
}
var cwe []string
if 0 < len(cve.CWE) {
cwe = []string{cve.CWE}
}
option := map[string]string{}
if 0 < len(cve.ExploitStatus) {
// TODO: CVE-2020-0739
// "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A",
option["exploit"] = cve.ExploitStatus
}
kbids := []string{}
for _, kbid := range cve.KBIDs {
kbids = append(kbids, kbid.KBID)
}
if 0 < len(kbids) {
option["kbids"] = strings.Join(kbids, ",")
}
vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
mitigations := []models.Mitigation{}
if cve.Mitigation != "" {
mitigations = append(mitigations, models.Mitigation{
CveContentType: models.Microsoft,
Mitigation: cve.Mitigation,
URL: vendorURL,
})
}
if cve.Workaround != "" {
mitigations = append(mitigations, models.Mitigation{
CveContentType: models.Microsoft,
Mitigation: cve.Workaround,
URL: vendorURL,
})
}
return &models.CveContent{
Type: models.Microsoft,
CveID: cve.CveID,
Title: cve.Title,
Summary: cve.Description,
Cvss3Score: v3score,
Cvss3Vector: v3Vector,
Cvss3Severity: v3Severity,
References: refs,
CweIDs: cwe,
Published: cve.PublishDate,
LastModified: cve.LastUpdateDate,
SourceLink: vendorURL,
Optional: option,
}, mitigations
}

17
gost/pseudo.go Normal file
View File

@@ -0,0 +1,17 @@
// +build !scanner
package gost
import (
"github.com/future-architect/vuls/models"
)
// Pseudo is Gost client except for RedHat family and Debian
type Pseudo struct {
Base
}
// DetectUnfixed fills cve information that has in Gost
func (pse Pseudo) DetectUnfixed(r *models.ScanResult, _ bool) (int, error) {
return 0, nil
}

252
gost/redhat.go Normal file
View File

@@ -0,0 +1,252 @@
// +build !scanner
package gost
import (
"encoding/json"
"strconv"
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
gostmodels "github.com/knqyf263/gost/models"
)
// RedHat is Gost client for RedHat family linux
type RedHat struct {
Base
}
// DetectUnfixed fills cve information that has in Gost
func (red RedHat) DetectUnfixed(r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
if red.DBDriver.Cnf.IsFetchViaHTTP() {
prefix, _ := util.URLPathJoin(red.DBDriver.Cnf.GetURL(), "redhat", major(r.Release), "pkgs")
responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
if err != nil {
return 0, err
}
for _, res := range responses {
// CVE-ID: RedhatCVE
cves := map[string]gostmodels.RedhatCVE{}
if err := json.Unmarshal([]byte(res.json), &cves); err != nil {
return 0, err
}
for _, cve := range cves {
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
nCVEs++
}
}
}
} else {
if red.DBDriver.DB == nil {
return 0, nil
}
for _, pack := range r.Packages {
// CVE-ID: RedhatCVE
cves := red.DBDriver.DB.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
for _, cve := range cves {
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
nCVEs++
}
}
}
}
return nCVEs, nil
}
func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error {
cveIDs := []string{}
for cveID, vuln := range r.ScannedCves {
if _, ok := vuln.CveContents[models.RedHatAPI]; ok {
continue
}
cveIDs = append(cveIDs, cveID)
}
if red.DBDriver.Cnf.IsFetchViaHTTP() {
prefix, _ := util.URLPathJoin(config.Conf.Gost.URL, "redhat", "cves")
responses, err := getCvesViaHTTP(cveIDs, prefix)
if err != nil {
return err
}
for _, res := range responses {
redCve := gostmodels.RedhatCVE{}
if err := json.Unmarshal([]byte(res.json), &redCve); err != nil {
return err
}
if redCve.ID == 0 {
continue
}
red.setFixedCveToScanResult(&redCve, r)
}
} else {
if red.DBDriver.DB == nil {
return nil
}
for _, redCve := range red.DBDriver.DB.GetRedhatMulti(cveIDs) {
if len(redCve.Name) == 0 {
continue
}
red.setFixedCveToScanResult(&redCve, r)
}
}
return nil
}
func (red RedHat) setFixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) {
cveCont, mitigations := red.ConvertToModel(cve)
v, ok := r.ScannedCves[cveCont.CveID]
if ok {
if v.CveContents == nil {
v.CveContents = models.NewCveContents(*cveCont)
} else {
v.CveContents[models.RedHatAPI] = *cveCont
}
} else {
v = models.VulnInfo{
CveID: cveCont.CveID,
CveContents: models.NewCveContents(*cveCont),
Confidences: models.Confidences{models.RedHatAPIMatch},
}
}
v.Mitigations = append(v.Mitigations, mitigations...)
r.ScannedCves[cveCont.CveID] = v
}
func (red RedHat) setUnfixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) (newly bool) {
cveCont, mitigations := red.ConvertToModel(cve)
v, ok := r.ScannedCves[cve.Name]
if ok {
if v.CveContents == nil {
v.CveContents = models.NewCveContents(*cveCont)
} else {
v.CveContents[models.RedHatAPI] = *cveCont
}
} else {
v = models.VulnInfo{
CveID: cveCont.CveID,
CveContents: models.NewCveContents(*cveCont),
Confidences: models.Confidences{models.RedHatAPIMatch},
}
newly = true
}
v.Mitigations = append(v.Mitigations, mitigations...)
pkgStats := red.mergePackageStates(v,
cve.PackageState, r.Packages, r.Release)
if 0 < len(pkgStats) {
v.AffectedPackages = pkgStats
r.ScannedCves[cve.Name] = v
}
return
}
func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageFixStatuses) {
pkgStats = v.AffectedPackages
for _, pstate := range ps {
if pstate.Cpe !=
"cpe:/o:redhat:enterprise_linux:"+major(release) {
return
}
if !(pstate.FixState == "Will not fix" ||
pstate.FixState == "Fix deferred" ||
pstate.FixState == "Affected") {
return
}
if _, ok := installed[pstate.PackageName]; !ok {
return
}
notFixedYet := false
switch pstate.FixState {
case "Will not fix", "Fix deferred", "Affected":
notFixedYet = true
}
pkgStats = pkgStats.Store(models.PackageFixStatus{
Name: pstate.PackageName,
FixState: pstate.FixState,
NotFixedYet: notFixedYet,
})
}
return
}
func (red RedHat) parseCwe(str string) (cwes []string) {
if str != "" {
s := strings.Replace(str, "(", "|", -1)
s = strings.Replace(s, ")", "|", -1)
s = strings.Replace(s, "->", "|", -1)
for _, s := range strings.Split(s, "|") {
if s != "" {
cwes = append(cwes, s)
}
}
}
return
}
// ConvertToModel converts gost model to vuls model
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) (*models.CveContent, []models.Mitigation) {
cwes := red.parseCwe(cve.Cwe)
details := []string{}
for _, detail := range cve.Details {
details = append(details, detail.Detail)
}
v2score := 0.0
if cve.Cvss.CvssBaseScore != "" {
v2score, _ = strconv.ParseFloat(cve.Cvss.CvssBaseScore, 64)
}
v2severity := ""
if v2score != 0 {
v2severity = cve.ThreatSeverity
}
v3score := 0.0
if cve.Cvss3.Cvss3BaseScore != "" {
v3score, _ = strconv.ParseFloat(cve.Cvss3.Cvss3BaseScore, 64)
}
v3severity := ""
if v3score != 0 {
v3severity = cve.ThreatSeverity
}
refs := []models.Reference{}
for _, r := range cve.References {
refs = append(refs, models.Reference{Link: r.Reference})
}
vendorURL := "https://access.redhat.com/security/cve/" + cve.Name
mitigations := []models.Mitigation{}
if cve.Mitigation != "" {
mitigations = []models.Mitigation{
{
CveContentType: models.RedHatAPI,
Mitigation: cve.Mitigation,
URL: vendorURL,
},
}
}
return &models.CveContent{
Type: models.RedHatAPI,
CveID: cve.Name,
Title: cve.Bugzilla.Description,
Summary: strings.Join(details, "\n"),
Cvss2Score: v2score,
Cvss2Vector: cve.Cvss.CvssScoringVector,
Cvss2Severity: v2severity,
Cvss3Score: v3score,
Cvss3Vector: cve.Cvss3.Cvss3ScoringVector,
Cvss3Severity: v3severity,
References: refs,
CweIDs: cwes,
Published: cve.PublicDate,
SourceLink: vendorURL,
}, mitigations
}

39
gost/redhat_test.go Normal file
View File

@@ -0,0 +1,39 @@
// +build !scanner
package gost
import (
"reflect"
"sort"
"testing"
)
func TestParseCwe(t *testing.T) {
var tests = []struct {
in string
out []string
}{
{
in: "CWE-665->(CWE-200|CWE-89)",
out: []string{"CWE-665", "CWE-200", "CWE-89"},
},
{
in: "CWE-841->CWE-770->CWE-454",
out: []string{"CWE-841", "CWE-770", "CWE-454"},
},
{
in: "(CWE-122|CWE-125)",
out: []string{"CWE-122", "CWE-125"},
},
}
r := RedHat{}
for i, tt := range tests {
out := r.parseCwe(tt.in)
sort.Strings(out)
sort.Strings(tt.out)
if !reflect.DeepEqual(tt.out, out) {
t.Errorf("[%d]expected: %s, actual: %s", i, tt.out, out)
}
}
}

191
gost/util.go Normal file
View File

@@ -0,0 +1,191 @@
// +build !scanner
package gost
import (
"net/http"
"strings"
"time"
"github.com/cenkalti/backoff"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
)
type response struct {
request request
json string
}
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
responses []response, err error) {
nReq := len(cveIDs)
reqChan := make(chan request, nReq)
resChan := make(chan response, nReq)
errChan := make(chan error, nReq)
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, cveID := range cveIDs {
reqChan <- request{
cveID: cveID,
}
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for i := 0; i < nReq; i++ {
tasks <- func() {
select {
case req := <-reqChan:
url, err := util.URLPathJoin(
urlPrefix,
req.cveID,
)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
httpGet(url, req, resChan, errChan)
}
}
}
}
timeout := time.After(2 * 60 * time.Second)
var errs []error
for i := 0; i < nReq; i++ {
select {
case res := <-resChan:
responses = append(responses, res)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching OVAL")
}
}
if len(errs) != 0 {
return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
}
return
}
type request struct {
osMajorVersion string
packName string
isSrcPack bool
cveID string
}
func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) (
responses []response, err error) {
nReq := len(r.Packages) + len(r.SrcPackages)
reqChan := make(chan request, nReq)
resChan := make(chan response, nReq)
errChan := make(chan error, nReq)
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, pack := range r.Packages {
reqChan <- request{
osMajorVersion: major(r.Release),
packName: pack.Name,
isSrcPack: false,
}
}
for _, pack := range r.SrcPackages {
reqChan <- request{
osMajorVersion: major(r.Release),
packName: pack.Name,
isSrcPack: true,
}
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for i := 0; i < nReq; i++ {
tasks <- func() {
select {
case req := <-reqChan:
url, err := util.URLPathJoin(
urlPrefix,
req.packName,
"unfixed-cves",
)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
httpGet(url, req, resChan, errChan)
}
}
}
}
timeout := time.After(2 * 60 * time.Second)
var errs []error
for i := 0; i < nReq; i++ {
select {
case res := <-resChan:
responses = append(responses, res)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching OVAL")
}
}
if len(errs) != 0 {
return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
}
return
}
func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
count, retryMax := 0, 3
f := func() (err error) {
// resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
count++
if count == retryMax {
return nil
}
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
}
return nil
}
notify := func(err error, t time.Duration) {
logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
}
err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
if err != nil {
errChan <- xerrors.Errorf("HTTP Error %w", err)
return
}
if count == retryMax {
errChan <- xerrors.New("Retry count exceeded")
return
}
resChan <- response{
request: req,
json: body,
}
}
func major(osVer string) (majorVersion string) {
return strings.Split(osVer, ".")[0]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 297 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More