Compare commits

...

43 Commits

Author SHA1 Message Date
Kota Kanbe
e788e6a5ad Support Alpine Linux #194 (#545)
* Support Alpine Linux #194

* Fix testcase

* Fix README

* Fix dep files

* Fix changelog

* Bump up version
2017-12-01 23:17:28 +09:00
Flaviu
d00e912934 Replace strings.HasPrefix with strings.Index for SuSE scanner (#546) 2017-11-21 11:37:43 +09:00
Kota Kanbe
8ebb663368 Fix yum changelog option (#543) 2017-11-15 17:32:17 +09:00
nnao45
445ffc4123 Update README.md (#542) 2017-11-14 17:05:12 +09:00
Kota Kanbe
6af49f4d55 Fix false positive: ignore oval info when kernel major version is different. (#541) 2017-11-10 23:33:43 +09:00
Mai MISHIRO
1de9e8c086 Fix: Misdetection of OvalMatch for CentOS and Scientific in oval/util.go (#536)
* Fix: Misdecection of OvalMatch for CentOS in oval/util.go

* Remediation: Misdetection of OvalMatch for Scientific (currently treated as RHEL) oval/util.go

* The regular expression was changed because the release number of CentOS and Scientific's unchanged package is different from upstream.

* OvalMatch test of RedHat and CentOS has been added.
2017-11-09 11:20:23 +09:00
Mai MISHIRO
59b0812adf Fix: "Reboot Required" detection process in scan/redhat.go (#534) 2017-11-08 17:16:59 +09:00
kota kanbe
719785c1ed Remove README.fr.md because unable to maintenance.. 2017-11-08 16:11:03 +09:00
nakacya
8e5f627e59 README Typo Update (#538)
* Update README.ja.md

Typo Update

* Update README.md

Typo Update
2017-11-08 15:57:18 +09:00
Kota Kanbe
5ced3c72b8 Insert sudo only at the beginning of command in deep scan #495 (#539)
* Insert `sudo` only at the beginning of command in deep scan #495

* Fix testcase
2017-11-08 15:48:43 +09:00
Kota Kanbe
c002f0168c Fix config.toml validation (#537) 2017-11-06 09:56:18 +09:00
Kota Kanbe
00c690f516 Add pseudo server type for non-ssh scanning (only cpe scan) #512 (#531)
* Add pseudo server type for non-ssh scanning (only cpe scan) #512

* Don't check hostname for pseudo type

* Update README.md
2017-11-02 17:02:06 +09:00
nakacya
ab68ad5cc5 README Update (#530)
* README.ja.md Update

Add Update steps

* Update README.ja.md

* Update README.ja.md

* README.md update

Add Update steps
2017-10-30 13:24:46 +09:00
kota kanbe
5c84ebefab Update README 2017-10-26 14:54:15 +09:00
sadayuki-matsuno
eb2acaff22 send slack msg by api (#525) 2017-10-26 13:30:01 +09:00
shimojomasatsugummm
84d0655c52 fix typo Privious -> Previous (#523) 2017-10-25 18:51:29 +09:00
nashiox
e137ebb9c2 Fix package query fails on debian based container (#519) (#522)
* Fix package query fails on debian based container (#519)

* Fix executil test (#519)
2017-10-25 18:49:47 +09:00
atsu
10d690d929 fix typo from "enviroment" to "environment" (#518) 2017-10-21 18:28:53 +09:00
yuu26
14611d2fd9 Fix typo in config/jsonloader.go (#517) 2017-10-20 14:34:48 +09:00
x-blood
0665bfe15f Modified Spell Miss of "README.md". (#516)
* Modified spell miss of README.md. 1305:Calculator

* Revert "Modified spell miss of README.md. 1305:Calculator"

This reverts commit 0e0db1be8d.

* Modified spell miss of README.md. line:1305"Calculator"
2017-10-20 14:02:16 +09:00
kota kanbe
473096d35d Fix .goreleaser.yml 2017-10-19 14:31:35 +09:00
kota kanbe
0eae26e261 Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  Fix a bug of making channels when fill oval information via HTTP (#514)
2017-10-17 13:37:06 +09:00
Kota Kanbe
a32845f652 Fix a bug of making channels when fill oval information via HTTP (#514)
* Fix a bug of making channels when fill oval information via HTTP
2017-10-17 13:36:49 +09:00
kota kanbe
15a0f7eadb Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  Fix OVAL detection on Debian and Ubuntu (#509)
2017-10-16 14:13:40 +09:00
Kota Kanbe
5a0a6abf11 Fix OVAL detection on Debian and Ubuntu (#509)
* Add filter options to tui subcommand (#508)

* Capture version of source packages on Debian based linux

* Change makefile, gofmt -s

* Refactoring

* Implement OVAL detection of source packages for Debian, Ubuntu
2017-10-13 17:22:11 +09:00
kota kanbe
032b8d9572 Merge branch 'master' of https://github.com/future-architect/vuls
* 'master' of https://github.com/future-architect/vuls:
  Add filter options to tui subcommand (#508)
2017-09-29 08:41:31 +09:00
Kota Kanbe
5798e3af83 Add filter options to tui subcommand (#508) 2017-09-29 08:37:32 +09:00
Kota Kanbe
8e15b9ce1c Add filter options to tui subcommand (#508) 2017-09-28 18:31:09 +09:00
Kota Kanbe
7a1f132c1f Add -ignore-unfixed option to report subcommand #485 (#507) 2017-09-28 17:29:47 +09:00
Emilien Kenler
a8483b2195 Add goreleaser to distribute binaries (#460)
See https://github.com/future-architect/vuls/issues/459
2017-09-28 15:29:27 +09:00
kota kanbe
83bbbd0cb0 Add goreportcard to README 2017-09-28 15:23:51 +09:00
Kota Kanbe
132432dce6 Support SUSE Enterprise Linux (#487)
* Support SUSE Enterprise Linux

* Implement Reboot Required detection on SLES

* Fix query OVAL because SUSE provides OVAL data each major.minor version

* Update README

* Support SUSE Enterprise 11
2017-09-28 12:23:19 +09:00
Xiuming Chen
e5eb8e42f5 Debian: Use --showformat flag to get status of packages and ignore n(not-inst… (#484)
* Use --showformat flag to get status of packages and ignore n(not-installed) and c(removed, only has config files remaining) packages.

* Ignoring all packages that are not in 'Installed' status.

* Simplify char escaping in the command.

* Fix typo.
2017-09-27 09:43:59 +09:00
Takayuki Ushida
1095ebea24 fix vulsrepo dockerfile (#496) 2017-09-26 18:17:46 +09:00
328
1541a602b2 Update README.ja.md (#498) 2017-09-26 18:17:19 +09:00
~Stack~
03a141c252 Fix typos (#499)
* Update bolt.go

Fix typos

* Update util.go

Fix Typos
2017-09-26 18:16:54 +09:00
Kota Kanbe
5f2183fc8e Check repoquery with sudo nopasswd in deep scan mode on RedHat (#492) 2017-09-14 09:14:20 -07:00
Kota Kanbe
820831fa5d Fix sort order of servers on TUI (#481) 2017-09-05 15:54:13 +09:00
Kota Kanbe
6d2d767c52 Fix a arg of report subcommand (#479) 2017-09-04 14:47:25 +08:00
Kota Kanbe
e0c3a728ae Fix ping option of discover subcommand #471 (#472) 2017-08-30 14:13:53 +08:00
sadayuki-matsuno
ec92f7797f add windows type (#470) 2017-08-28 18:49:34 +08:00
Kota Kanbe
0ba490c6df Merge pull request #469 from usiusi360/use_vulsrepo-server
use_vulsrepo-server
2017-08-25 21:59:52 +09:00
usiusi360
cfd668e11d use_vulsrepo-server 2017-08-25 21:42:33 +09:00
60 changed files with 2950 additions and 865 deletions

24
.goreleaser.yml Normal file
View File

@@ -0,0 +1,24 @@
project_name: vuls
release:
github:
owner: future-architect
name: vuls
builds:
- goos:
- linux
goarch:
- amd64
main: .
ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}}
binary: vuls
archive:
format: tar.gz
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
.Arm }}{{ end }}'
files:
- LICENSE
- NOTICE
- README*
- CHANGELOG.md
snapshot:
name_template: SNAPSHOT-{{ .Commit }}

View File

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

View File

@@ -1,5 +1,7 @@
# Change Log
## v0.4.1 and later, see [GitHub release](https://github.com/future-architect/vuls/releases)
## [v0.4.0](https://github.com/future-architect/vuls/tree/v0.4.0) (2017-08-25)
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.3.0...v0.4.0)
@@ -509,4 +511,4 @@
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@@ -31,10 +31,10 @@ depup:
go get -u github.com/golang/dep/...
dep ensure -update
build: main.go dep
build: main.go dep pretest
go build -ldflags "$(LDFLAGS)" -o vuls $<
install: main.go dep
install: main.go dep pretest
go install -ldflags "$(LDFLAGS)"
@@ -47,10 +47,10 @@ vet:
echo $(PKGS) | xargs go vet || exit;
fmt:
gofmt -w $(SRCS)
gofmt -s -w $(SRCS)
fmtcheck:
$(foreach file,$(SRCS),gofmt -d $(file);)
$(foreach file,$(SRCS),gofmt -s -d $(file);)
pretest: lint vet fmtcheck

91
Gopkg.lock generated
View File

@@ -4,14 +4,14 @@
[[projects]]
name = "github.com/Azure/azure-sdk-for-go"
packages = ["storage"]
revision = "57db66900881e9fd21fd041a9d013514700ecab3"
version = "v10.3.0-beta"
revision = "7692b0cef22674113fcf71cc17ac3ccc1a7fef48"
version = "v11.2.2-beta"
[[projects]]
name = "github.com/Azure/go-autorest"
packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
revision = "77a52603f06947221c672f10275abc9bf2c7d557"
version = "v8.3.0"
revision = "c67b24a8e30d876542a85022ebbdecf0e5a935e8"
version = "v9.4.1"
[[projects]]
name = "github.com/BurntSushi/toml"
@@ -28,8 +28,8 @@
[[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 = "264af29009637e0a9e5d4a276d0969c3ed918ffd"
version = "v1.10.29"
revision = "e4f7e38b704e3ed0acc4a7f8196b777696f6f1f3"
version = "v1.12.30"
[[projects]]
name = "github.com/boltdb/bolt"
@@ -46,26 +46,26 @@
[[projects]]
name = "github.com/cheggaaa/pb"
packages = ["."]
revision = "0d6285554e726cc0620cbecc7e6969c945dcc63b"
version = "v1.0.17"
revision = "657164d0228d6bebe316fdf725c69f131a50fb10"
version = "v1.0.18"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
version = "v3.0.0"
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "20b96f641a5ea98f2f8619ff4f3e061cff4833bd"
version = "v1.28.2"
revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a"
version = "v1.32.0"
[[projects]]
name = "github.com/go-redis/redis"
packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
revision = "19c1c2272e00c1aaa903cf574c746cd449f9cd3c"
version = "v6.5.7"
revision = "e5e021257bfc6d5fbf0beac33e30194311fb189f"
version = "v6.7.4"
[[projects]]
name = "github.com/go-sql-driver/mysql"
@@ -106,8 +106,7 @@
[[projects]]
name = "github.com/jmespath/go-jmespath"
packages = ["."]
revision = "3433f3ea46d9f8019119e7dd41274e112a2359a9"
version = "0.2.2"
revision = "0b12d6b5"
[[projects]]
name = "github.com/jroimartin/gocui"
@@ -119,7 +118,7 @@
branch = "master"
name = "github.com/k0kubun/pp"
packages = ["."]
revision = "d1532fc5d94ecdf2da29e24d7b99721f3287de4a"
revision = "e057ee7a28277be4d2af303443b6da377768181f"
[[projects]]
branch = "master"
@@ -137,7 +136,7 @@
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
packages = ["config","db","jvn","log","models","nvd","util"]
revision = "c20fa7e1d07f7c700baf12c855f7fcf61525f1b6"
revision = "a64c5fc25cd9669b213986e1a02831c71a5c601d"
[[projects]]
name = "github.com/kotakanbe/go-pingscanner"
@@ -149,7 +148,7 @@
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"
packages = ["config","db","db/rdb","log","models"]
revision = "3523cc174e68f285d0572d07c68ffa3a9290799c"
revision = "dca4f21940cbf2245a50817177fed7eb6793e72c"
[[projects]]
branch = "master"
@@ -157,17 +156,11 @@
packages = ["."]
revision = "75edb2e85a38873f0318be05a458446681d1022f"
[[projects]]
name = "github.com/labstack/gommon"
packages = ["color","log"]
revision = "779b8a8b9850a97acba6a3fe20feb628c39e17c1"
version = "0.2.2"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [".","hstore","oid"]
revision = "e42267488fe361b9dc034be7a6bffef5b195bceb"
revision = "8c6ee72f3e6bcb1542298dd5f76cb74af9742cec"
[[projects]]
name = "github.com/mattn/go-colorable"
@@ -178,8 +171,8 @@
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
version = "v0.0.2"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/mattn/go-runewidth"
@@ -190,8 +183,8 @@
[[projects]]
name = "github.com/mattn/go-sqlite3"
packages = ["."]
revision = "ca5e3819723d8eeaf170ad510e7da1d6d2e94a08"
version = "v1.2.0"
revision = "ed69081a91fd053f17672236b0dd52ba7485e1a3"
version = "v1.4.0"
[[projects]]
branch = "master"
@@ -203,13 +196,19 @@
branch = "master"
name = "github.com/moul/http2curl"
packages = ["."]
revision = "4e24498b31dba4683efb9d35c1c8a91e2eda28c8"
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 = "4ed959e0540971545eddb8c75514973d670cf739"
revision = "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43"
[[projects]]
name = "github.com/parnurzeal/gorequest"
@@ -239,19 +238,7 @@
branch = "master"
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "84573d5f03ab3740f524c7842c3a9bf617961d32"
[[projects]]
branch = "master"
name = "github.com/valyala/bytebufferpool"
packages = ["."]
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
[[projects]]
branch = "master"
name = "github.com/valyala/fasttemplate"
packages = ["."]
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
revision = "95cd2b9c79aa5e72ab0bc69b7ccc2be15bf850f6"
[[projects]]
branch = "master"
@@ -263,29 +250,29 @@
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
revision = "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","idna","publicsuffix"]
revision = "1c05540f6879653db88113bc4a2b70aec4bd491f"
packages = ["context","idna","publicsuffix","websocket"]
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "07c182904dbd53199946ba614a412c61d3c548f5"
revision = "0ac51a24ef1c37380f98ba8b98f56e3bffd59850"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["internal/gen","internal/triegen","internal/ucd","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "e56139fd9c5bc7244c76116c68e500765bb6db6b"
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 = "36d700add80d36c56484ed310b9a7e622b3e308ab22eb42bdfb02fd8f5c90407"
inputs-digest = "58ae46498625e705c582d70591148e07dbac30bc4f75cadef3d2fda514cd5099"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -88,3 +88,7 @@
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
[[constraint]]
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"

View File

@@ -1,224 +0,0 @@
# Vuls: VULnerability Scanner
[![Slack](https://img.shields.io/badge/slack-join-blue.svg)](http://goo.gl/forms/xm5KFo35tu)
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)
[README en English](https://github.com/future-architect/vuls/blob/master/README.md)
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)
[![asciicast](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck.png)](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
![Vuls-slack](img/vuls-slack-en.png)
----
# Résumé
Effectuer des recherches de vulnérabilités et des mises à jour quotidiennes peut etre un fardeau pour un administrateur système.
Afin d'éviter des interruptions systèmes dans un environnement de production, il est fréquent pour un administrateur système de choisir de ne pas utiliser la fonction de mise à jour automatique proposée par le gestionnaire de paquets et d'effecter ces mises à jour manuellement.
Ce qui implique les problèmes suivants :
- L'administrateur système devra surveiller constamment toutes les nouvelles vulnérabilités dans NVD (National Vulnerability Database) etc.
- Il pourrait être impossible pour un administrateur système de surveiller tous les logiciels installés sur un serveur.
- Il est coûteux d'effectuer une analyse pour déterminer quels sont les serveurs affectés par de nouvelles vulnérabilités. La possibilité de négliger un serveur ou deux est bien présente.
Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses caractéristiques.
- Informer les utilisateurs des vulnérabilités système.
- Informer les utilisateurs des systèmes concernés.
- La détection de vulnérabilités est effectuée automatiquement pour éviter toute négligence.
- Les rapports sont générés régulièrement via CRON pour mieux gérer ces vulnérabilités.
![Vuls-Motivation](img/vuls-motivation.png)
----
# Caractéristiques principales
- Recherche de vulnérabilités sur des serveurs Linux
- Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbian
- Cloud, auto-hébergement, Docker
- Scan d'intergiciels non inclus dans le gestionnaire de paquets de l'OS
- Scan d'intergiciels, de libraries de language de programmation et framework pour des vulnérabilités
- Supporte les logiciels inscrits au CPE
- Architecture sans agent
- L'utilisateur doit seulement mettre en place VULS sur une seule machine qui se connectera aux autres via SSH
- Génération automatique des fichiers de configuration
- Auto detection de serveurs via CIDR et génération de configuration
- Email et notification Slack possibles (supporte le Japonais)
- Les résultats d'un scan sont accessibles dans un shell via TUI Viewer terminal.
----
# Ce que Vuls ne fait pas
- Vuls ne met pas à jour les programmes affectés par les vulnérabilités découvertes.
----
# Hello Vuls
Ce tutoriel décrit la recherche de vulnérabilités sur une machine locale avec Vuls.
Voici les étapes à suivre.
1. Démrarrage d'Amazon Linux
1. Autoriser les connexions SSH depuis localhost
1. Installation des prérequis
1. Déploiement de go-cve-dictionary
1. Deploiement de Vuls
1. Configuration
1. Préparation
1. Scan
1. TUI(Terminal-Based User Interface)
## Step1. Démrarrage d'Amazon Linux
- Nous utilisons dans cette exemple une vieille AMI (amzn-ami-hvm-2015.09.1.x86_64-gp2 - ami-383c1956)
- Taille de l'instance : t2.medium
- La première fois, t2.medium et plus sont requis pour la récupération des CVE depuis NVD (2.3GB de mémoire utilisé)
- Une fois la récupération initiale des données NVD terminée vous pouvez passer sur une instance t2.nano.
- Ajoutez la configuration suivante au cloud-init, afin d'éviter une mise à jour automatique lors du premier démarrage.
- [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
```
#cloud-config
repo_upgrade: none
```
## Step2. Paramètres SSH
Il est obligatoire que le serveur puisse se connecter à son propre serveur SSH
Générez une paire de clés SSH et ajoutez la clé publique dans le fichier authorized_keys
```bash
$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
```
## Step3. Installation des prérequis
Vuls requiert l'installation des paquets suivants :
- sqlite
- git
- gcc
- go v1.7.1 or later
- https://golang.org/doc/install
```bash
$ ssh ec2-user@52.100.100.100 -i ~/.ssh/private.pem
$ sudo yum -y install sqlite git gcc
$ wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
$ mkdir $HOME/go
```
Ajoutez les lignes suivantes dans /etc/profile.d/goenv.sh
```bash
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
```
Ajoutons ces nouvelles variables denvironnement au shell
```bash
$ source /etc/profile.d/goenv.sh
```
## Step4. Déploiement de [go-cve-dictionary](https://github.com/kotakanbe/go-cve-dictionary)
go get
```bash
$ sudo mkdir /var/log/vuls
$ sudo chown ec2-user /var/log/vuls
$ sudo chmod 700 /var/log/vuls
$ go get github.com/kotakanbe/go-cve-dictionary
```
Démarrez go-cve-dictionary en mode serveur.
Lors de son premier démarrage go-cve-dictionary récupère la liste des vulnérabilités depuis NVD
Cette opération prend environ 10 minutes (sur AWS).
## Step5. Déploiement de Vuls
Ouvrez un second terminal, connectez vous à l'instance ec2 via SSH
go get
```
$ go get github.com/future-architect/vuls
```
## Step6. Configuration
Créez un fichier de configuration (TOML format).
```
$ cat config.toml
[servers]
[servers.172-31-4-82]
host = "172.31.4.82"
port = "22"
user = "ec2-user"
keyPath = "/home/ec2-user/.ssh/id_rsa"
```
## Step7. Configuration des serveurs cibles vuls
```
$ vuls prepare
```
## Step8. Scan
```
$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
... snip ...
172-31-4-82 (amazon 2015.09)
============================
CVE-2016-0494 10.0 Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
affect confidentiality, integrity, and availability via unknown vectors related to
2D.
... snip ...
CVE-2016-0494
-------------
Score 10.0 (High)
Vector (AV:N/AC:L/Au:N/C:C/I:C/A:C)
Summary Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
integrity, and availability via unknown vectors related to 2D.
NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
CVE Details http://www.cvedetails.com/cve/CVE-2016-0494
CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
RHEL-CVE https://access.redhat.com/security/cve/CVE-2016-0494
ALAS-2016-643 https://alas.aws.amazon.com/ALAS-2016-643.html
Package/CPE java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
```
## Step9. TUI
Les résultats de Vuls peuvent etre affichés dans un Shell via TUI (Terminal-Based User Interface).
```
$ vuls tui
```
![Vuls-TUI](img/hello-vuls-tui.png)
----
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)

View File

@@ -91,7 +91,7 @@ Table of Contents
* [Example: Use MySQL as a DB storage back-end](#example-use-mysql-as-a-db-storage-back-end)
* [Example: Use PostgreSQL as a DB storage back-end](#example-use-postgresql-as-a-db-storage-back-end)
* [Example: Use Redis as a DB storage back-end](#example-use-redis-as-a-db-storage-back-end)
* [Usage: Scan vulnerability of non-OS package](#usage-scan-vulnerability-of-non-os-package)
* [Usage: Scan vulnerability of non-OS package](#usage-scan-vulnerabilites-of-non-os-packages)
* [Usage: Integrate with OWASP Dependency Check to Automatic update when the libraries are updated (Experimental)](#usage-integrate-with-owasp-dependency-check-to-automatic-update-when-the-libraries-are-updated-experimental)
* [Usage: TUI](#usage-tui)
* [Display the latest scan results](#display-the-latest-scan-results)
@@ -139,7 +139,7 @@ Vulsは上に挙げた手動運用での課題を解決するツールであり
# Main Features
- サーバに存在する脆弱性をスキャン
- FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbianに対応
- Alpine, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise, Raspbian, FreeBSD に対応
- クラウド、オンプレミス、Docker
- 高精度なスキャン
- Vulsは複数の脆弱性データベース、複数の検知方法を組み合わせることで高精度なスキャンを実現している
@@ -324,10 +324,12 @@ $ goval-dictionary fetch-redhat 7
今回はスキャン対象がCentOS 7なので、RedHat 7のOVALを取得している。
他の種類のOSをスキャンする場合は以下を参照し、スキャン対象用のOVALを取得しておくこと
- [Alpine](https://github.com/kotakanbe/goval-dictionary#usage-fetch-alpine-secdb-as-oval-data-type)
- [RedHat, CentOS](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-redhat)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Oracle Linux](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
## Step5. Deploy Vuls
@@ -339,9 +341,20 @@ $ git clone https://github.com/future-architect/vuls.git
$ cd vuls
$ make install
```
The binary was built under `$GOPATH/bin`
もしもインストールプロセスが途中で止まる場合は、Out of memory errorが発生している可能性があるので、インスタンスタイプを大きくして再実行してみてください。
もし、あなたが以前にvulsをインストールしていて update をする場合は以下を実施してください。
```
$ rm -rf $GOPATH/pkg/linux_amd64/github.com/future-architect/vuls/
$ rm -rf $GOPATH/src/github.com/future-architect/vuls/
$ cd $GOPATH/src/github.com/future-architect
$ git clone https://github.com/future-architect/vuls.git
$ cd vuls
$ make install
```
## Step6. Config
Vulsの設定ファイルを作成するTOMLフォーマット
@@ -581,14 +594,16 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ
| Distribution| Scan Speed | Need Root Privilege | OVAL | Need Internet Access <br>on scan tareget|
|:------------|:--------------------------------------:|:-------------------:|:----------:|:---------------------------------------:|
| CentOS | Fast |  No | Supported | No |
| Alpine | Fast |  No | Supported | No |
| CentOS | Fast |  No | Supported | No |
| RHEL | Fast |  No | Supported | No |
| Oracle | Fast |  No | Supported | No |
| Ubuntu | Fast |  No | Supported | No |
| Debian | Fast |  No | Supported | No |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast | Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No |
----
@@ -599,27 +614,32 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ
| Distribution| Scan Speed | Need Root Privilege | OVAL | Need Internet Access <br>on scan tareget|
|:------------|:-------------------------------------:|:-------------------------:|:---------:|:---------------------------------------:|
| CentOS | Slow |  No | Supported | Need |
| Alpine | Fast |  No | Supported | No |
| CentOS | Slow |  No | Supported | Need |
| RHEL | Slow |  Need | Supported | Need |
| Oracle | Slow |  Need | Supported | Need |
| Ubuntu |1st time: Slow <br> From 2nd time: Fast| Need | Supported | Need |
| Debian |1st time: Slow <br> From 2nd time: Fast| Need | Supported | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Slow |  No | No | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No |
- Ubuntu, Debian, Raspbian
- On Ubuntu, Debian and Raspbian
`apt-get changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。
アップデート対象のパッケージが沢山ある場合、チェンジログの取得に時間がかかるので、初回のスキャンは遅い。
ただ、回目以降はキャッシュしたchangelogを使うので速くなる。
- CentOS
- On CentOS
`yum changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。
- Amazon, RHEL and FreeBSD
- On RHEL, Oracle, Amazon and FreeBSD
`yum changelog`でアップデート対象のパッケージのチェンジログを取得する(パースはしない)。
- On SUSE Enterprise Linux and Alpine Linux
Same as fast scan mode for now.
----
# Use Cases
@@ -640,12 +660,14 @@ web/app server in the same configuration under the load balancer
| Distribution| Release |
|:------------|-------------------:|
| Alpine | 3.2 and later |
| Ubuntu | 12, 14, 16|
| Debian | 7, 8, 9|
| RHEL | 5, 6, 7|
| CentOS | 6, 7|
| Amazon Linux| All|
| FreeBSD | 10, 11|
| SUSE Enterprise | 11, 12|
| Raspbian | Jessie, Stretch |
----
@@ -669,6 +691,7 @@ $ vuls discover 172.31.4.0/24
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
@@ -703,6 +726,7 @@ host = "172.31.4.82"
#port = "22"
#user = "root"
#keyPath = "/home/username/.ssh/id_rsa"
#type = "pseudo"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
@@ -725,6 +749,7 @@ host = "172.31.4.82"
```
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
@@ -732,10 +757,21 @@ host = "172.31.4.82"
notifyUsers = ["@username"]
```
- hookURL : Incoming webhook's URL
- hookURL or legacyToken
どちらか一方を指定する。
もし脆弱性が沢山有る場合はlegacyTokenの利用をおすすめする。legacyTokenはSlackのスレッド形式でポストされる。
スキャンサーバ単位で集約されるのでSlack通知が氾濫しない。
- hookURL : Incoming webhook's URL (legacyTokenが設定されている場合、hookURLは無視される。)
![Vuls-slack](img/vuls-slack-en.png)
- legacyToken : slack legacy token (https://api.slack.com/custom-integrations/legacy-tokens)
![Vuls-slack-thread](https://user-images.githubusercontent.com/8997330/31842418-02b703f2-b629-11e7-8ec3-beda5d3a397e.png)
- channel : channel name.
channelに`${servername}`を指定すると、結果レポートをサーバごとに別チャネルにすることが出来る。
以下のサンプルでは、`#server1`チャネルと`#server2`チャネルに送信される。スキャン前にチャネルを作成する必要がある。
**legacyTokenが設定されている場合、channelは実在するchannelでなければならない。**
```
[slack]
channel = "${servername}"
@@ -801,6 +837,7 @@ host = "172.31.4.82"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#type = "pseudo"
#ignoreCves = ["CVE-2016-6314"]
#optional = [
# ["key", "value"],
@@ -817,6 +854,7 @@ host = "172.31.4.82"
- port: SSH Port number
- user: SSH username
- keyPath: SSH private key path
- type: "pseudo" for non-ssh scanning. see [#531](https://github.com/future-architect/vuls/pull/531)
- cpeNames: see [Usage: Scan vulnerability of non-OS package](#usage-scan-vulnerability-of-non-os-package)
- ignoreCves: CVE IDs that will not be reported. But output to JSON file.
- optional: JSONレポートに含めたい追加情報
@@ -876,12 +914,14 @@ configtestサブコマンドは、config.tomlで定義されたサーバ/コン
| Distribution | Release | Requirements |
|:-------------|-------------------:|:-------------|
| Alpine | 3.2 and later | - |
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8, 9| reboot-notifier|
| CentOS | 6, 7| - |
| Amazon | All | - |
| RHEL | 5, 6, 7 | - |
| Oracle Linux | 5, 6, 7 | - |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10, 11 | - |
| Raspbian | Jessie, Stretch | - |
@@ -889,7 +929,7 @@ configtestサブコマンドは、config.tomlで定義されたサーバ/コン
Deep Scan Modeではスキャン対象サーバ上にいくつかの依存パッケージが必要。
configtestに--deepをつけて実行するとSSH接続に加えて以下もチェックする。
- スキャン対象のサーバ上に依存パッケーがインストールされているか
- スキャン対象のサーバ上に依存パッケーがインストールされているか
- /etc/sudoers
### Dependencies and /etc/sudoers on Target Servers
@@ -898,14 +938,16 @@ Deep Scan Modeでスキャンするためには、下記のパッケージが必
| Distribution | Release | Requirements |
|:-------------|-------------------:|:-------------|
| Alpine | 3.2 and later | - |
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8, 9| aptitude, reboot-notifier |
| Debian | 7, 8, 9| aptitude, reboot-notifier |
| CentOS | 6, 7| yum-plugin-changelog, yum-utils |
| Amazon | All | yum-plugin-changelog, yum-utils |
| RHEL | 5 | yum-utils, yum-security, yum-changelog |
| RHEL | 6, 7 | yum-utils, yum-plugin-changelog |
| Oracle Linux | 5 | yum-utils, yum-security, yum-changelog |
| Oracle Linux | 6, 7 | yum-utils, yum-plugin-changelog |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10 | - |
| Raspbian | Wheezy, Jessie | - |
@@ -919,13 +961,13 @@ For details, see [-ssh-native-insecure option](#-ssh-native-insecure-option)
- RHEL 5 / Oracle Linux 5
```
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never info-security
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never info-security, /usr/bin/repoquery, /usr/bin/yum --color=never changelog all *
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- RHEL 6, 7 / Oracle Linux 6, 7
```
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never --security updateinfo updates
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never --security updateinfo updates, /usr/bin/repoquery, /usr/bin/yum --color=never changelog all *
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
@@ -935,7 +977,7 @@ vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- CentOS, Amazon Linux, FreeBSDは今のところRoot権限なしでスキャン可能
- CentOS, Amazon Linux, SUSE Enterprise, FreeBSDは今のところRoot権限なしでスキャン可能
----
@@ -1152,6 +1194,7 @@ report:
[-cvss-over=7]
[-diff]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-to-email]
[-to-slack]
[-to-localfile]
@@ -1176,7 +1219,8 @@ report:
[-debug-sql]
[-pipe]
[SERVER]...
[RFC3339 datetime format under results dir]
-aws-profile string
AWS profile to use (default "default")
-aws-region string
@@ -1225,6 +1269,8 @@ report:
http://proxy-url:port (default: empty)
-ignore-unscored-cves
Don't report the unscored CVEs
-ignore-unfixed
Don't report the unfixed CVEs
-lang string
[en|ja] (default "en")
-log-dir string
@@ -1276,7 +1322,7 @@ CWE https://cwe.mitre.org/data/definitions/190.html
NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-5636
MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5636
CVE Details http://www.cvedetails.com/cve/CVE-2016-5636
CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-5636&vector=(AV:N/AC:L/...
CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-5636&vector=(AV:N/AC:L/...
RHEL-CVE https://access.redhat.com/security/cve/CVE-2016-5636
ALAS-2016-724 https://alas.aws.amazon.com/ALAS-2016-724.html
Package python27-2.7.10-4.119.amzn1 -> python27-2.7.12-2.120.amzn1
@@ -1337,7 +1383,7 @@ Confidence 100 / OvalMatch
| Detection Method | Confidence | OS |Description|
|:-----------------------|-------------------:|:---------------------------------|:--|
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian |Detection using OVAL |
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian, SUSE |Detection using OVAL |
| YumUpdateSecurityMatch | 100 | RHEL, Amazon, Oracle |Detection using yum-plugin-security|
| ChangelogExactMatch | 95 | CentOS, Ubuntu, Debian, Raspbian |Exact version match between changelog and package version|
| ChangelogLenientMatch | 50 | Ubuntu, Debian, Raspbian |Lenient version match between changelog and package version|
@@ -1565,6 +1611,18 @@ Vulsは、[CPE](https://nvd.nist.gov/cpe.cfm)に登録されているソフト
]
```
- Configuration
ネットワーク機器など、スキャン対象にSSH接続しない場合は`type="pseudo"`を指定する。
```
[servers]
[servers.172-31-4-82]
type = "pseudo"
cpeNames = [
"cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
]
```
# Usage: Integrate with OWASP Dependency Check to Automatic update when the libraries are updated (Experimental)
[OWASP Dependency check](https://www.owasp.org/index.php/OWASP_Dependency_Check) は、プログラミング言語のライブラリを特定しCPEを推測、公開済みの脆弱性を検知するツール。
@@ -1712,6 +1770,7 @@ $ vuls report -ovaldb-url=http://192.168.0.1:1323
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Oracle](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
----
@@ -1821,7 +1880,8 @@ Run with --debug, --sql-debug option.
[Riak docs](http://docs.basho.com/riak/latest/ops/tuning/open-files-limit/) is awesome.
- Does Vuls accept ssh connections with fish-shell or old zsh as the login shell?
No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)
~~No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)~~
Yes, fixed in [#545](https://github.com/future-architect/vuls/pull/545)
- Windows
Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)

110
README.md
View File

@@ -4,6 +4,7 @@
[![Slack](https://img.shields.io/badge/slack-join-blue.svg)](http://goo.gl/forms/xm5KFo35tu)
[![License](https://img.shields.io/github/license/future-architect/vuls.svg?style=flat-square)](https://github.com/future-architect/vuls/blob/master/LICENSE.txt)
[![Build Status](https://travis-ci.org/future-architect/vuls.svg?branch=master)](https://travis-ci.org/future-architect/vuls)
[![Go Report Card](https://goreportcard.com/badge/github.com/future-architect/vuls)](https://goreportcard.com/report/github.com/future-architect/vuls)
![Vuls-logo](img/vuls_logo.png)
@@ -13,7 +14,6 @@ We have a slack team. [Join slack team](http://goo.gl/forms/xm5KFo35tu)
Twitter: [@vuls_en](https://twitter.com/vuls_en)
[README 日本語](https://github.com/future-architect/vuls/blob/master/README.ja.md)
[README in French](https://github.com/future-architect/vuls/blob/master/README.fr.md)
![Vuls-Abstract](img/vuls-abstract.png)
@@ -144,7 +144,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
# Main Features
- Scan for any vulnerabilities in Linux/FreeBSD Server
- Supports FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux and Raspbian
- Supports Alpine, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise Linux and Raspbian, FreeBSD
- Cloud, on-premise, Docker
- High quality scan
- Vuls uses Multiple vulnerability databases
@@ -331,10 +331,12 @@ $ goval-dictionary fetch-redhat 7
```
If you want to scan other than CentOS 7, fetch OVAL data according to the OS type and version of scan target server in advance.
- [Alpine](https://github.com/kotakanbe/goval-dictionary#usage-fetch-alpine-secdb-as-oval-data-type)
- [RedHat, CentOS](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-redhat)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Oracle Linux](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
## Step5. Deploy Vuls
@@ -347,6 +349,16 @@ $ git clone https://github.com/future-architect/vuls.git
$ cd vuls
$ make install
```
If you have previously installed vuls and want to update, please do the following
```
$ rm -rf $GOPATH/pkg/linux_amd64/github.com/future-architect/vuls/
$ rm -rf $GOPATH/src/github.com/future-architect/vuls/
$ cd $GOPATH/src/github.com/future-architect
$ git clone https://github.com/future-architect/vuls.git
$ cd vuls
$ make install
```
The binary was built under `$GOPATH/bin`
If the installation process stops halfway, try increasing the instance type of EC2. An out of memory error may have occurred.
@@ -589,15 +601,16 @@ On the aggregation server, you can refer to the scanning result of each scan tar
| Distribution| Scan Speed | Need Root Privilege | OVAL | Need Internet Access <br>on scan tareget|
|:------------|:--------------------------------------:|:-------------------:|:----------:|:---------------------------------------:|
| CentOS | Fast |  No | Supported | No |
| Alpine | Fast |  No | Supported | No |
| CentOS | Fast |  No | Supported | No |
| RHEL | Fast |  No | Supported | No |
| Oracle | Fast |  No | Supported | No |
| Ubuntu | Fast |  No | Supported | No |
| Debian | Fast |  No | Supported | No |
| Raspbian |1st time: Slow <br> From 2nd time: Fast | Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No |
---------
@@ -606,7 +619,8 @@ On the aggregation server, you can refer to the scanning result of each scan tar
| Distribution| Scan Speed | Need Root Privilege | OVAL | Need Internet Access <br>on scan tareget|
|:------------|:-------------------------------------:|:-------------------------:|:---------:|:---------------------------------------:|
| CentOS | Slow |  No | Supported | Need |
| Alpine | Fast |  No | Supported | No |
| CentOS | Slow |  No | Supported | Need |
| RHEL | Slow |  Need | Supported | Need |
| Oracle | Slow |  Need | Supported | Need |
| Ubuntu |1st time: Slow <br> From 2nd time: Fast| Need | Supported | Need |
@@ -614,6 +628,7 @@ On the aggregation server, you can refer to the scanning result of each scan tar
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Slow |  No | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No |
- On Ubuntu, Debian and Raspbian
@@ -624,9 +639,13 @@ From the second time on, the scan speed is fast by using the local cache.
- On CentOS
Vuls issues `yum changelog` to get changelogs of upgradable packages at once and parse the changelog.
- On RHEL, Oracle, Amazon and FreeBSD
Detect CVE IDs by using package manager.
- On SUSE Enterprise Linux and Alpine Linux
Same as fast scan mode for now.
----
# Use Cases
@@ -658,7 +677,9 @@ If there is a staging environment with the same configuration as the production
| CentOS | 6, 7|
| Amazon Linux | All|
| FreeBSD | 10, 11|
| SUSE Enterprise | 11, 12|
| Raspbian | Jessie, Stretch |
| Alpine | 3.2 and later |
----
@@ -681,6 +702,7 @@ $ vuls discover 172.31.4.0/24
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
@@ -716,6 +738,7 @@ host = "172.31.4.82"
#port = "22"
#user = "root"
#keyPath = "/home/username/.ssh/id_rsa"
#type = "pseudo"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
@@ -739,6 +762,7 @@ You can customize your configuration using this template.
```
[slack]
hookURL = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
#legacyToken = "xoxp-11111111111-222222222222-3333333333"
channel = "#channel-name"
#channel = "${servername}"
iconEmoji = ":ghost:"
@@ -746,8 +770,16 @@ You can customize your configuration using this template.
notifyUsers = ["@username"]
```
- hookURL : Incoming webhook's URL
- channel : channel name.
- hookURL or legacyToken.
If there are a lot of vulnerabilities, it is better to use legacyToken since the Slack notification will be flooded.
- hookURL : Incoming webhook's URL (hookURL is ignored when legacyToken is set.)
![Vuls-slack](img/vuls-slack-en.png)
- legacyToken : slack legacy token (https://api.slack.com/custom-integrations/legacy-tokens)
![Vuls-slack-thread](https://user-images.githubusercontent.com/8997330/31842418-02b703f2-b629-11e7-8ec3-beda5d3a397e.png)
- channel : channel name.
If you set `${servername}` to channel, the report will be sent to each channel.
In the following example, the report will be sent to the `#server1` and `#server2`.
Be sure to create these channels before scanning.
@@ -811,6 +843,7 @@ You can customize your configuration using this template.
#port = "22"
#user = "root"
#keyPath = "/home/username/.ssh/id_rsa"
#type = "pseudo"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
@@ -830,6 +863,7 @@ You can customize your configuration using this template.
- port: SSH Port number
- user: SSH username
- keyPath: SSH private key path
- type: "pseudo" for non-ssh scanning. see [#531](https://github.com/future-architect/vuls/pull/531)
- cpeNames: see [Usage: Scan vulnerability of non-OS package](#usage-scan-vulnerability-of-non-os-package)
- ignoreCves: CVE IDs that will not be reported. But output to JSON file.
- optional: Add additional information to JSON report.
@@ -887,12 +921,14 @@ The configtest subcommand checks whether vuls is able to connect via SSH to serv
| Distribution | Release | Requirements |
|:-------------|-------------------:|:-------------|
| Alpine | 3.2 and later | - |
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8, 9| reboot-notifier|
| CentOS | 6, 7| - |
| Amazon | All | - |
| RHEL | 5, 6, 7 | - |
| Oracle Linux | 5, 6, 7 | - |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10, 11 | - |
| Raspbian | Jessie, Stretch | - |
@@ -907,6 +943,7 @@ In order to scan with deep scan mode, the following dependencies are required, s
| Distribution | Release | Requirements |
|:-------------|-------------------:|:-------------|
| Alpine | 3.2 and later | - |
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8, 9| aptitude, reboot-notifier |
| CentOS | 6, 7| yum-plugin-changelog, yum-utils |
@@ -915,6 +952,7 @@ In order to scan with deep scan mode, the following dependencies are required, s
| RHEL | 6, 7 | yum-utils, yum-plugin-changelog |
| Oracle Linux | 5 | yum-utils, yum-security, yum-changelog |
| Oracle Linux | 6, 7 | yum-utils, yum-plugin-changelog |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10 | - |
| Raspbian | Wheezy, Jessie | - |
@@ -928,13 +966,13 @@ Example of /etc/sudoers on target servers
- RHEL 5 / Oracle Linux 5
```
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never info-security
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never list-security --security, /usr/bin/yum --color=never info-security, /usr/bin/repoquery, /usr/bin/yum --color=never changelog all *
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- RHEL 6, 7 / Oracle Linux 6, 7
```
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never --security updateinfo updates
vuls ALL=(ALL) NOPASSWD:/usr/bin/yum --color=never repolist, /usr/bin/yum --color=never --security updateinfo list updates, /usr/bin/yum --color=never --security updateinfo updates, /usr/bin/repoquery, /usr/bin/yum --color=never changelog all *
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
@@ -944,7 +982,7 @@ vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- On CentOS, Amazon Linux, FreeBSD, it is possible to scan without root privilege for now.
- On CentOS, Amazon Linux, SUSE Enterprise, FreeBSD, it is possible to scan without root privilege for now.
----
@@ -1163,6 +1201,7 @@ report:
[-cvss-over=7]
[-diff]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-to-email]
[-to-slack]
[-to-localfile]
@@ -1187,7 +1226,8 @@ report:
[-debug-sql]
[-pipe]
[SERVER]...
[RFC3339 datetime format under results dir]
-aws-profile string
AWS profile to use (default "default")
-aws-region string
@@ -1236,6 +1276,8 @@ report:
http://proxy-url:port (default: empty)
-ignore-unscored-cves
Don't report the unscored CVEs
-ignore-unfixed
Don't report the unfixed CVEs
-lang string
[en|ja] (default "en")
-log-dir string
@@ -1287,7 +1329,7 @@ CWE https://cwe.mitre.org/data/definitions/190.html
NVD https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-5636
MITRE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5636
CVE Details http://www.cvedetails.com/cve/CVE-2016-5636
CVSS Claculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-5636&vector=(AV:N/AC:L/...
CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-5636&vector=(AV:N/AC:L/...
RHEL-CVE https://access.redhat.com/security/cve/CVE-2016-5636
ALAS-2016-724 https://alas.aws.amazon.com/ALAS-2016-724.html
Package python27-2.7.10-4.119.amzn1 -> python27-2.7.12-2.120.amzn1
@@ -1348,7 +1390,7 @@ Confidence 100 / OvalMatch
| Detection Method | Confidence | OS |Description|
|:-----------------------|-------------------:|:---------------------------------|:--|
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian |Detection using OVAL |
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian, SUSE |Detection using OVAL |
| YumUpdateSecurityMatch | 100 | RHEL, Amazon, Oracle |Detection using yum-plugin-security|
| ChangelogExactMatch | 95 | CentOS, Ubuntu, Debian, Raspbian |Exact version match between changelog and package version|
| ChangelogLenientMatch | 50 | Ubuntu, Debian, Raspbian |Lenient version match between changelog and package version|
@@ -1368,6 +1410,17 @@ With this sample command, it will ..
- Only Report CVEs that CVSS score is over 7
```
$ vuls report \
-to-slack \
-cvss-over=7 \
-cvedb-path=$PWD/cve.sqlite3
```
With this sample command, it will ..
- Send scan results to slack
- Only Report CVEs that CVSS score is over 7
## Example: Put results in S3 bucket
To put results in S3 bucket, configure following settings in AWS before reporting.
- Create S3 bucket. see [Creating a Bucket](http://docs.aws.amazon.com/AmazonS3/latest/UG/CreatingaBucket.html)
@@ -1568,6 +1621,20 @@ To detect the vulnerability of Ruby on Rails v4.2.1, cpeNames needs to be set in
]
```
- type="pseudo"
Specify this when you want to detect vulnerability by specifying cpename without SSH connection.
The pseudo type does not do anything when scanning.
Search for NVD at report time and detect vulnerability of software specified as cpenamae.
```
[servers]
[servers.172-31-4-82]
type = "pseudo"
cpeNames = [
"cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
]
```
# Usage: Integrate with OWASP Dependency Check to Automatic update when the libraries are updated (Experimental)
[OWASP Dependency check](https://www.owasp.org/index.php/OWASP_Dependency_Check) is a utility that identifies project dependencies and checks if there are any known, publicly disclosed, vulnerabilities.
@@ -1599,13 +1666,16 @@ How to integrate Vuls with OWASP Dependency Check
```
tui:
tui
[-refresh-cve]
[-cvedb-type=sqlite3|mysql|postgres]
[-cvedb-path=/path/to/cve.sqlite3]
[-cvedb-url=http://127.0.0.1:1323 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]
[-refresh-cve]
[-cvss-over=7]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-debug]
@@ -1624,6 +1694,12 @@ tui:
DB type for fetching OVAL dictionary (sqlite3 or mysql) (default "sqlite3")
-ovaldb-url string
http://goval-dictionary.com:1324 or mysql connection string
-cvss-over float
-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
-ignore-unfixed
Don't report the unfixed CVEs
-ignore-unscored-cves
Don't report the unscored CVEs
-debug
debug mode
-debug-sql
@@ -1711,6 +1787,7 @@ $ vuls report -ovaldb-url=http://192.168.0.1:1323
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Oracle](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
----
@@ -1783,7 +1860,8 @@ Run with --debug, --sql-debug option.
[Riak docs](http://docs.basho.com/riak/latest/ops/tuning/open-files-limit/) is awesome.
- Does Vuls accept SSH connections with fish-shell or old zsh as the login shell?
No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)
~~No, Vuls needs a user on the server for bash login. see also [#8](/../../issues/8)~~
Yes, fixed in [#545](https://github.com/future-architect/vuls/pull/545)
- Windows
Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/en-us/security/cc184924.aspx)

4
cache/bolt.go vendored
View File

@@ -163,7 +163,7 @@ func (b Bolt) GetChangelog(servername, packName string) (changelog string, err e
err = b.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket([]byte(servername))
if bkt == nil {
return fmt.Errorf("Faild to get Bucket: %s", servername)
return fmt.Errorf("Failed to get Bucket: %s", servername)
}
v := bkt.Get([]byte(packName))
if v == nil {
@@ -181,7 +181,7 @@ 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("Faild to get Bucket: %s", servername)
return fmt.Errorf("Failed to get Bucket: %s", servername)
}
if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
return err

View File

@@ -57,6 +57,7 @@ func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
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
}
@@ -65,7 +66,6 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
CIDR: cidr,
PingOptions: []string{
"-c1",
"-t1",
},
NumOfConcurrency: 100,
}
@@ -92,6 +92,7 @@ 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:"
@@ -131,6 +132,7 @@ host = "{{$ip}}"
#port = "22"
#user = "root"
#keyPath = "/home/username/.ssh/id_rsa"
#type = "pseudo"
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]

View File

@@ -44,7 +44,9 @@ type ReportCmd struct {
cvssScoreOver float64
ignoreUnscoredCves bool
httpProxy string
ignoreUnfixed bool
httpProxy string
cveDBType string
cveDBPath string
@@ -107,6 +109,7 @@ func (*ReportCmd) Usage() string {
[-cvss-over=7]
[-diff]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-to-email]
[-to-slack]
[-to-localfile]
@@ -131,7 +134,7 @@ func (*ReportCmd) Usage() string {
[-debug-sql]
[-pipe]
[SERVER]...
[RFC3339 datetime format under results dir]
`
}
@@ -213,6 +216,12 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
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",
@@ -312,6 +321,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
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

View File

@@ -49,6 +49,10 @@ type TuiCmd struct {
ovalDBPath string
ovalDBURL string
cvssScoreOver float64
ignoreUnscoredCves bool
ignoreUnfixed bool
pipe bool
}
@@ -62,6 +66,7 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabiliti
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]
@@ -69,7 +74,9 @@ func (*TuiCmd) Usage() string {
[-ovaldb-type=sqlite3|mysql]
[-ovaldb-path=/path/to/oval.sqlite3]
[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
[-refresh-cve]
[-cvss-over=7]
[-ignore-unscored-cves]
[-ignore-unfixed]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-debug]
@@ -139,6 +146,24 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
"",
"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",
@@ -169,6 +194,10 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
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() {

View File

@@ -58,6 +58,32 @@ const (
// 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
@@ -73,6 +99,7 @@ type Config struct {
CvssScoreOver float64
IgnoreUnscoredCves bool
IgnoreUnfixed bool
SSHNative bool
ContainersOnly bool
@@ -360,10 +387,11 @@ func (c *SMTPConf) Validate() (errs []error) {
// SlackConf is slack config
type SlackConf struct {
HookURL string `valid:"url" json:"-"`
Channel string `json:"channel"`
IconEmoji string `json:"icon_emoji"`
AuthUser string `json:"username"`
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"`
@@ -426,6 +454,9 @@ type ServerInfo struct {
// For CentOS, RHEL, Amazon
Enablerepo []string
// "pseudo" or ""
Type string
// used internal
LogMsgAnsiColor string // DebugLog Color
Container Container
@@ -438,7 +469,7 @@ func (s ServerInfo) GetServerName() string {
if len(s.Container.ContainerID) == 0 {
return s.ServerName
}
return fmt.Sprintf("%s@%s", s.Container.ContainerID, s.ServerName)
return fmt.Sprintf("%s@%s", s.Container.Name, s.ServerName)
}
// Distro has distribution info

View File

@@ -23,7 +23,7 @@ import "fmt"
type JSONLoader struct {
}
// Load load the configuraiton JSON file specified by path arg.
// 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")
}

View File

@@ -61,46 +61,48 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
s := ServerInfo{ServerName: name}
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)
if v.Type != ServerTypePseudo {
s.Host = v.Host
if len(s.Host) == 0 {
return fmt.Errorf("%s is invalid. host 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)
switch {
case v.Port != "":
s.Port = v.Port
case d.Port != "":
s.Port = d.Port
default:
s.Port = "22"
}
}
// s.KeyPassword = keyPass
s.KeyPassword = v.KeyPassword
if len(s.KeyPassword) == 0 {
s.KeyPassword = d.KeyPassword
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
}
}
s.CpeNames = v.CpeNames
@@ -175,6 +177,8 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
}
}
s.Type = v.Type
s.LogMsgAnsiColor = Colors[i%len(Colors)]
i++

View File

@@ -53,9 +53,11 @@
<y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
<y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
Alpine: apk
Debian/Ubuntu: dpkg-query
Amazon/RHEL/CentOS: rpm
SUSE: zypper
FreeBSD: pkg<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
@@ -264,7 +266,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
<y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
FreeBSD<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
@@ -298,7 +300,6 @@ FreeBSD<y:LabelModel>
</data>
</edge>
<edge id="e4" source="n1" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="-123.36984126984123" ty="0.0">
@@ -306,11 +307,13 @@ FreeBSD<y:LabelModel>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="74.6640625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">CentOS
<y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="102.9296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">Alpine Linux
CentOS
RHEL
Ubuntu
Debian
Oracle Linux<y:LabelModel>
Oracle Linux
Suse<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
@@ -323,7 +326,6 @@ Oracle Linux<y:LabelModel>
</data>
</edge>
<edge id="e5" source="n4" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@@ -364,7 +366,6 @@ Oracle Linux<y:LabelModel>
</data>
</edge>
<edge id="e9" source="n3" target="n5">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@@ -375,7 +376,6 @@ Oracle Linux<y:LabelModel>
</data>
</edge>
<edge id="e10" source="n1" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
@@ -396,7 +396,6 @@ Oracle Linux<y:LabelModel>
</data>
</edge>
<edge id="e11" source="n10" target="n3">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="-125.78842258255952" ty="0.0">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -53,10 +53,12 @@
<y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
<y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
<y:BorderStyle color="#000000" type="line" width="1.0"/>
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
<y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
Alpine Linux: apk
Debian/Ubuntu: dpkg-query
Amazon/RHEL/CentOS: rpm
FreeBSD: pkg<y:LabelModel>
FreeBSD: pkg
SUSE: zypper<y:LabelModel>
<y:SmartNodeLabelModel distance="4.0"/>
</y:LabelModel>
<y:ModelParameter>
@@ -235,7 +237,6 @@ Amazon / RHEL: yum changelog<y:LabelModel>
</node>
<node id="n13" yfiles.foldertype="group">
<data key="d4"/>
<data key="d5"/>
<data key="d6">
<y:ProxyAutoBoundsNode>
<y:Realizers active="0">
@@ -315,13 +316,13 @@ Amazon / RHEL: yum changelog<y:LabelModel>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-66.95987036992159" y="-48.39843398912808">Debian
<y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-257.65322875976574" y="2.0000035108718635">Debian
Ubuntu
Raspbian<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.02215389573439544" segment="0"/>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.8652035780364729" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@@ -379,13 +380,13 @@ Raspbian<y:LabelModel>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="standard"/>
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
<y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="200.87829463898197" y="4.000003510871693">Amazon
RHEL
FreeBSD<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.022401276994204813" segment="0"/>
<y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="6.999999999999886" distanceToCenter="false" position="right" ratio="0.8192728556300707" segment="-1"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
@@ -442,7 +443,6 @@ FreeBSD<y:LabelModel>
</data>
</edge>
<edge id="e11" source="n11" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="-24.34091537610618">
@@ -455,7 +455,6 @@ FreeBSD<y:LabelModel>
</data>
</edge>
<edge id="e12" source="n8" target="n12">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@@ -466,7 +465,6 @@ FreeBSD<y:LabelModel>
</data>
</edge>
<edge id="e13" source="n12" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
@@ -477,7 +475,6 @@ FreeBSD<y:LabelModel>
</data>
</edge>
<edge id="e14" source="n9" target="n13">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
@@ -487,6 +484,30 @@ FreeBSD<y:LabelModel>
</y:PolyLineEdge>
</data>
</edge>
<edge id="e15" source="n1" target="n7">
<data key="d9"/>
<data key="d10">
<y:PolyLineEdge>
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
<y:Point x="999.0" y="226.44247787610618"/>
<y:Point x="999.0" y="570.8409153761062"/>
<y:Point x="743.3698412698412" y="570.8409153761062"/>
</y:Path>
<y:LineStyle color="#000000" type="line" width="1.0"/>
<y:Arrows source="none" target="none"/>
<y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.8203125" x="422.923942251054" y="13.867191010871807">Alpine Linux
SUSE<y:LabelModel>
<y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
</y:LabelModel>
<y:ModelParameter>
<y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.8856709076027529" segment="0"/>
</y:ModelParameter>
<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
</y:EdgeLabel>
<y:BendStyle smoothed="false"/>
</y:PolyLineEdge>
</data>
</edge>
</graph>
<data key="d7">
<y:Resources/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -29,7 +29,7 @@ import (
)
// Version of Vuls
var version = "0.4.0"
var version = "0.4.2"
// Revision of Git
var revision string

View File

@@ -229,6 +229,9 @@ const (
// Oracle is Oracle Linux
Oracle CveContentType = "oracle"
// SUSE is SUSE Linux
SUSE CveContentType = "suse"
// Unknown is Unknown
Unknown CveContentType = "unknown"
)

View File

@@ -18,4 +18,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package models
// JSONVersion is JSON Version
const JSONVersion = 2
const JSONVersion = 3

View File

@@ -81,7 +81,7 @@ func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
return "", Package{}, false
}
// Package has installed packages.
// Package has installed binary packages.
type Package struct {
Name string
Version string
@@ -116,6 +116,8 @@ func (p Package) FormatVersionFromTo(notFixedYet bool) string {
to := p.FormatNewVer()
if notFixedYet {
to = "Not Fixed Yet"
} else if p.NewVersion == "" {
to = "Unknown"
}
return fmt.Sprintf("%s-%s -> %s", p.Name, p.FormatVer(), to)
}
@@ -151,3 +153,31 @@ type Changelog struct {
Contents string
Method DetectionMethod
}
// SrcPackage has installed source package information.
// Debian based Linux has both of package and source information in dpkg.
// OVAL database often includes a source version (Not a binary version),
// so it is also needed to capture source version for OVAL version comparison.
// https://github.com/future-architect/vuls/issues/504
type SrcPackage struct {
Name string
Version string
BinaryNames []string
}
// AddBinaryName add the name if not exists
func (s *SrcPackage) AddBinaryName(name string) {
found := false
for _, n := range s.BinaryNames {
if n == name {
return
}
}
if !found {
s.BinaryNames = append(s.BinaryNames, name)
}
}
// SrcPackages is Map of SrcPackage
// { "package-name": SrcPackage }
type SrcPackages map[string]SrcPackage

View File

@@ -87,3 +87,49 @@ func TestMerge(t *testing.T) {
t.Errorf("expected %s, actual %s", e, a)
}
}
func TestAddBinaryName(t *testing.T) {
var tests = []struct {
in SrcPackage
name string
expected SrcPackage
}{
{
SrcPackage{Name: "hoge"},
"curl",
SrcPackage{
Name: "hoge",
BinaryNames: []string{"curl"},
},
},
{
SrcPackage{
Name: "hoge",
BinaryNames: []string{"curl"},
},
"curl",
SrcPackage{
Name: "hoge",
BinaryNames: []string{"curl"},
},
},
{
SrcPackage{
Name: "hoge",
BinaryNames: []string{"curl"},
},
"openssh",
SrcPackage{
Name: "hoge",
BinaryNames: []string{"curl", "openssh"},
},
},
}
for _, tt := range tests {
tt.in.AddBinaryName(tt.name)
if !reflect.DeepEqual(tt.in, tt.expected) {
t.Errorf("expected %#v, actual %#v", tt.in, tt.expected)
}
}
}

View File

@@ -46,8 +46,10 @@ type ScanResult struct {
RunningKernel Kernel
Packages Packages
Errors []string
Optional [][]interface{}
SrcPackages SrcPackages
Errors []string
Optional [][]interface{}
Config struct {
Scan config.Config
@@ -76,10 +78,8 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
}
return false
})
copiedScanResult := r
copiedScanResult.ScannedCves = filtered
return copiedScanResult
r.ScannedCves = filtered
return r
}
// FilterIgnoreCves is filter function.
@@ -92,9 +92,24 @@ func (r ScanResult) FilterIgnoreCves(cveIDs []string) ScanResult {
}
return true
})
copiedScanResult := r
copiedScanResult.ScannedCves = filtered
return copiedScanResult
r.ScannedCves = filtered
return r
}
// FilterUnfixed is filter function.
func (r ScanResult) FilterUnfixed() ScanResult {
if !config.Conf.IgnoreUnfixed {
return r
}
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
NotFixedAll := true
for _, p := range v.AffectedPackages {
NotFixedAll = NotFixedAll && p.NotFixedYet
}
return !NotFixedAll
})
r.ScannedCves = filtered
return r
}
// ReportFileName returns the filename on localhost without extention

View File

@@ -21,6 +21,7 @@ import (
"testing"
"time"
"github.com/future-architect/vuls/config"
"github.com/k0kubun/pp"
)
@@ -255,3 +256,83 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
}
}
}
func TestFilterUnfixed(t *testing.T) {
var tests = []struct {
in ScanResult
out ScanResult
}{
{
in: ScanResult{
ScannedCves: VulnInfos{
"CVE-2017-0001": {
CveID: "CVE-2017-0001",
AffectedPackages: PackageStatuses{
{
Name: "a",
NotFixedYet: true,
},
},
},
"CVE-2017-0002": {
CveID: "CVE-2017-0002",
AffectedPackages: PackageStatuses{
{
Name: "b",
NotFixedYet: false,
},
},
},
"CVE-2017-0003": {
CveID: "CVE-2017-0003",
AffectedPackages: PackageStatuses{
{
Name: "c",
NotFixedYet: true,
},
{
Name: "d",
NotFixedYet: false,
},
},
},
},
},
out: ScanResult{
ScannedCves: VulnInfos{
"CVE-2017-0002": {
CveID: "CVE-2017-0002",
AffectedPackages: PackageStatuses{
{
Name: "b",
NotFixedYet: false,
},
},
},
"CVE-2017-0003": {
CveID: "CVE-2017-0003",
AffectedPackages: PackageStatuses{
{
Name: "c",
NotFixedYet: true,
},
{
Name: "d",
NotFixedYet: false,
},
},
},
},
},
},
}
for i, tt := range tests {
config.Conf.IgnoreUnfixed = true
actual := tt.in.FilterUnfixed()
if !reflect.DeepEqual(tt.out.ScannedCves, actual.ScannedCves) {
o := pp.Sprintf("%v", tt.out.ScannedCves)
a := pp.Sprintf("%v", actual.ScannedCves)
t.Errorf("[%d] expected: %v\n actual: %v\n", i, o, a)
}
}
}

View File

@@ -546,6 +546,8 @@ func (v VulnInfo) VendorLinks(family string) map[string]string {
return links
case config.Debian:
links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID
case config.SUSEEnterpriseServer:
links["SUSE-CVE"] = "https://www.suse.com/security/cve/" + v.CveID
case config.FreeBSD:
for _, advisory := range v.DistroAdvisories {
links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID)

82
oval/alpine.go Normal file
View File

@@ -0,0 +1,82 @@
/* 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 oval
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
)
// Alpine is the struct of Alpine Linux
type Alpine struct {
Base
}
// NewAlpine creates OVAL client for SUSE
func NewAlpine() Alpine {
return Alpine{
Base{
family: config.Alpine,
},
}
}
// FillWithOval returns scan result after updating CVE info by OVAL
func (o Alpine) FillWithOval(r *models.ScanResult) (err error) {
var relatedDefs ovalResult
if o.isFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
return err
}
}
for _, defPacks := range relatedDefs.entries {
o.update(r, defPacks)
}
return nil
}
func (o Alpine) update(r *models.ScanResult, defPacks defPacks) {
ovalContent := *o.convertToModel(&defPacks.def)
cveID := defPacks.def.Advisory.Cves[0].CveID
vinfo, ok := r.ScannedCves[cveID]
if !ok {
util.Log.Debugf("%s is newly detected by OVAL", cveID)
vinfo = models.VulnInfo{
CveID: cveID,
Confidence: models.OvalMatch,
CveContents: models.NewCveContents(ovalContent),
}
}
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
vinfo.AffectedPackages.Sort()
r.ScannedCves[cveID] = vinfo
}
func (o Alpine) convertToModel(def *ovalmodels.Definition) *models.CveContent {
return &models.CveContent{
CveID: def.Advisory.Cves[0].CveID,
}
}

View File

@@ -60,9 +60,11 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
for _, pack := range vinfo.AffectedPackages {
defPacks.actuallyAffectedPackNames[pack.Name] = true
notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
}
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
vinfo.AffectedPackages.Sort()
r.ScannedCves[defPacks.def.Debian.CveID] = vinfo
}
@@ -107,11 +109,17 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
linuxImage := "linux-image-" + r.RunningKernel.Release
// Add linux and set the version of running kernel to search OVAL.
newVer := ""
if p, ok := r.Packages[linuxImage]; ok {
newVer = p.NewVersion
}
if r.Container.ContainerID == "" {
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
}
}
@@ -121,7 +129,7 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
return err
}
}
@@ -129,10 +137,10 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
delete(r.Packages, "linux")
for _, defPacks := range relatedDefs.entries {
// Remove linux added above to search for oval
// Remove "linux" added above for oval search
// linux is not a real package name (key of affected packages in OVAL)
if _, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
defPacks.actuallyAffectedPackNames[linuxImage] = true
if notFixedYet, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
defPacks.actuallyAffectedPackNames[linuxImage] = notFixedYet
delete(defPacks.actuallyAffectedPackNames, "linux")
for i, p := range defPacks.def.AffectedPacks {
if p.Name == "linux" {
@@ -141,6 +149,7 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
}
}
}
o.update(r, defPacks)
}
@@ -230,7 +239,7 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
return err
}
}
@@ -240,7 +249,6 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
}
for _, defPacks := range relatedDefs.entries {
// Remove "linux" added above to search for oval
// "linux" is not a real package name (key of affected packages in OVAL)
if _, ok := defPacks.actuallyAffectedPackNames["linux"]; !found && ok {
@@ -253,6 +261,7 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
}
}
}
o.update(r, defPacks)
}

View File

@@ -36,7 +36,10 @@ func TestPackNamesOfUpdateDebian(t *testing.T) {
in: models.ScanResult{
ScannedCves: models.VulnInfos{
"CVE-2000-1000": models.VulnInfo{
AffectedPackages: models.PackageStatuses{{Name: "packA"}},
AffectedPackages: models.PackageStatuses{
{Name: "packA"},
{Name: "packC"},
},
},
},
},
@@ -55,7 +58,8 @@ func TestPackNamesOfUpdateDebian(t *testing.T) {
"CVE-2000-1000": models.VulnInfo{
AffectedPackages: models.PackageStatuses{
{Name: "packA"},
{Name: "packB"},
{Name: "packB", NotFixedYet: true},
{Name: "packC"},
},
},
},

View File

@@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/future-architect/vuls/config"
@@ -132,15 +131,14 @@ func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
}
}
major := strings.Split(release, ".")[0]
since := time.Now()
since = since.AddDate(0, 0, -3)
if lastModified.Before(since) {
util.Log.Warnf("OVAL for %s %s is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. How to update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage",
osFamily, major, lastModified)
osFamily, release, lastModified)
return false, nil
}
util.Log.Infof("OVAL is fresh: %s %s ", osFamily, major)
util.Log.Infof("OVAL is fresh: %s %s ", osFamily, release)
return true, nil
}

View File

@@ -41,8 +41,7 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(
o.family, r.Release, r.Packages); err != nil {
if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
return err
}
}
@@ -68,6 +67,35 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
return nil
}
var kernelRelatedPackNames = map[string]bool{
"kernel": true,
"kernel-aarch64": true,
"kernel-abi-whitelists": true,
"kernel-bootwrapper": true,
"kernel-debug": true,
"kernel-debug-devel": true,
"kernel-devel": true,
"kernel-doc": true,
"kernel-headers": true,
"kernel-kdump": true,
"kernel-kdump-devel": true,
"kernel-rt": true,
"kernel-rt-debug": true,
"kernel-rt-debug-devel": true,
"kernel-rt-debug-kvm": true,
"kernel-rt-devel": true,
"kernel-rt-doc": true,
"kernel-rt-kvm": true,
"kernel-rt-trace": true,
"kernel-rt-trace-devel": true,
"kernel-rt-trace-kvm": true,
"kernel-rt-virt": true,
"kernel-rt-virt-devel": true,
"kernel-tools": true,
"kernel-tools-libs": true,
"kernel-tools-libs-devel": true,
}
func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
ctype := models.NewCveContentType(o.family)
for _, cve := range defPacks.def.Advisory.Cves {
@@ -98,9 +126,10 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
for _, pack := range vinfo.AffectedPackages {
defPacks.actuallyAffectedPackNames[pack.Name] = true
notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
}
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
vinfo.AffectedPackages.Sort()
r.ScannedCves[cve.CveID] = vinfo
}
@@ -156,7 +185,7 @@ func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.Join(ss[1:len(ss)], "/")
return score, strings.Join(ss[1:], "/")
}
return 0, ""
}
@@ -170,7 +199,7 @@ func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.Join(ss[1:len(ss)], "/")
return score, strings.Join(ss[1:], "/")
}
return 0, ""
}

View File

@@ -102,7 +102,10 @@ func TestPackNamesOfUpdate(t *testing.T) {
in: models.ScanResult{
ScannedCves: models.VulnInfos{
"CVE-2000-1000": models.VulnInfo{
AffectedPackages: models.PackageStatuses{{Name: "packA"}},
AffectedPackages: models.PackageStatuses{
{Name: "packA"},
{Name: "packB", NotFixedYet: false},
},
},
},
},
@@ -125,7 +128,7 @@ func TestPackNamesOfUpdate(t *testing.T) {
"CVE-2000-1000": models.VulnInfo{
AffectedPackages: models.PackageStatuses{
{Name: "packA"},
{Name: "packB"},
{Name: "packB", NotFixedYet: true},
},
},
},

120
oval/suse.go Normal file
View File

@@ -0,0 +1,120 @@
/* 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 oval
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
)
// SUSE is the struct of SUSE Linux
type SUSE struct {
Base
}
// NewSUSE creates OVAL client for SUSE
func NewSUSE() SUSE {
// TODO implement other family
return SUSE{
Base{
family: config.SUSEEnterpriseServer,
},
}
}
// FillWithOval returns scan result after updating CVE info by OVAL
func (o SUSE) FillWithOval(r *models.ScanResult) (err error) {
var relatedDefs ovalResult
if o.isFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(r); err != nil {
return err
}
}
for _, defPacks := range relatedDefs.entries {
o.update(r, defPacks)
}
for _, vuln := range r.ScannedCves {
if cont, ok := vuln.CveContents[models.SUSE]; ok {
cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
vuln.CveContents[models.SUSE] = cont
}
}
return nil
}
func (o SUSE) update(r *models.ScanResult, defPacks defPacks) {
ovalContent := *o.convertToModel(&defPacks.def)
ovalContent.Type = models.NewCveContentType(o.family)
vinfo, ok := r.ScannedCves[defPacks.def.Title]
if !ok {
util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Title)
vinfo = models.VulnInfo{
CveID: defPacks.def.Title,
Confidence: models.OvalMatch,
CveContents: models.NewCveContents(ovalContent),
}
} else {
cveContents := vinfo.CveContents
ctype := models.NewCveContentType(o.family)
if _, ok := vinfo.CveContents[ctype]; ok {
util.Log.Debugf("%s OVAL will be overwritten", defPacks.def.Title)
} else {
util.Log.Debugf("%s is also detected by OVAL", defPacks.def.Title)
cveContents = models.CveContents{}
}
if vinfo.Confidence.Score < models.OvalMatch.Score {
vinfo.Confidence = models.OvalMatch
}
cveContents[ctype] = ovalContent
vinfo.CveContents = cveContents
}
// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
for _, pack := range vinfo.AffectedPackages {
notFixedYet, _ := defPacks.actuallyAffectedPackNames[pack.Name]
defPacks.actuallyAffectedPackNames[pack.Name] = notFixedYet
}
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family)
vinfo.AffectedPackages.Sort()
r.ScannedCves[defPacks.def.Title] = vinfo
}
func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
var refs []models.Reference
for _, r := range def.References {
refs = append(refs, models.Reference{
Link: r.RefURL,
Source: r.Source,
RefID: r.RefID,
})
}
return &models.CveContent{
CveID: def.Title,
Title: def.Title,
Summary: def.Description,
References: refs,
}
}

View File

@@ -21,6 +21,8 @@ import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/cenkalti/backoff"
@@ -40,114 +42,61 @@ type ovalResult struct {
}
type defPacks struct {
def ovalmodels.Definition
def ovalmodels.Definition
// BinaryPackageName : NotFixedYet
actuallyAffectedPackNames map[string]bool
}
func (e defPacks) toPackStatuses(family string, packs models.Packages) (ps models.PackageStatuses) {
switch family {
case config.Ubuntu:
packNotFixedYet := map[string]bool{}
for _, p := range e.def.AffectedPacks {
packNotFixedYet[p.Name] = p.NotFixedYet
}
for k := range e.actuallyAffectedPackNames {
ps = append(ps, models.PackageStatus{
Name: k,
NotFixedYet: packNotFixedYet[k],
})
}
case config.CentOS, config.Debian:
// There are many packages that has been fixed in RedHat, but not been fixed in CentOS
for name := range e.actuallyAffectedPackNames {
pack, ok := packs[name]
if !ok {
util.Log.Warnf("Faild to find in Package list: %s", name)
return
}
ovalPackVer := ""
for _, p := range e.def.AffectedPacks {
if p.Name == name {
ovalPackVer = p.Version
break
}
}
if ovalPackVer == "" {
util.Log.Warnf("Faild to find in Oval Package list: %s", name)
return
}
if pack.NewVersion == "" {
// compare version: installed vs oval
vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
verb := rpmver.NewVersion(ovalPackVer)
notFixedYet := false
if vera.LessThan(verb) {
notFixedYet = true
}
ps = append(ps, models.PackageStatus{
Name: name,
NotFixedYet: notFixedYet,
})
} else {
// compare version: newVer vs oval
packNewVer := fmt.Sprintf("%s-%s", pack.NewVersion, pack.NewRelease)
vera := rpmver.NewVersion(packNewVer)
verb := rpmver.NewVersion(ovalPackVer)
notFixedYet := false
if vera.LessThan(verb) {
notFixedYet = true
}
ps = append(ps, models.PackageStatus{
Name: name,
NotFixedYet: notFixedYet,
})
}
}
default:
for k := range e.actuallyAffectedPackNames {
ps = append(ps, models.PackageStatus{
Name: k,
})
}
func (e defPacks) toPackStatuses(family string) (ps models.PackageStatuses) {
for name, notFixedYet := range e.actuallyAffectedPackNames {
ps = append(ps, models.PackageStatus{
Name: name,
NotFixedYet: notFixedYet,
})
}
return
}
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string) (upserted bool) {
for i, entry := range e.entries {
if entry.def.DefinitionID == def.DefinitionID {
e.entries[i].actuallyAffectedPackNames[packName] = true
return true
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string, notFixedYet bool) (upserted bool) {
// alpine's entry is empty since Alpine secdb is not OVAL format
if def.DefinitionID != "" {
for i, entry := range e.entries {
if entry.def.DefinitionID == def.DefinitionID {
e.entries[i].actuallyAffectedPackNames[packName] = notFixedYet
return true
}
}
}
e.entries = append(e.entries, defPacks{
def: def,
actuallyAffectedPackNames: map[string]bool{packName: true},
actuallyAffectedPackNames: map[string]bool{packName: notFixedYet},
})
return false
}
type request struct {
pack models.Package
packName string
versionRelease string
NewVersionRelease string
binaryPackNames []string
isSrcPack bool
}
type response struct {
pack *models.Package
defs []ovalmodels.Definition
request request
defs []ovalmodels.Definition
}
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
func getDefsByPackNameViaHTTP(r *models.ScanResult) (
relatedDefs ovalResult, err error) {
reqChan := make(chan request, len(r.Packages))
resChan := make(chan response, len(r.Packages))
errChan := make(chan error, len(r.Packages))
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)
@@ -155,14 +104,26 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
go func() {
for _, pack := range r.Packages {
reqChan <- request{
pack: pack,
packName: pack.Name,
versionRelease: pack.FormatVer(),
NewVersionRelease: pack.FormatVer(),
isSrcPack: false,
}
for _, pack := range r.SrcPackages {
reqChan <- request{
packName: pack.Name,
binaryPackNames: pack.BinaryNames,
versionRelease: pack.Version,
isSrcPack: true,
}
}
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range r.Packages {
for i := 0; i < nReq; i++ {
tasks <- func() {
select {
case req := <-reqChan:
@@ -171,13 +132,13 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
"packs",
r.Family,
r.Release,
req.pack.Name,
req.packName,
)
if err != nil {
errChan <- err
} else {
util.Log.Debugf("HTTP Request to %s", url)
httpGet(url, &req.pack, resChan, errChan)
httpGet(url, req, resChan, errChan)
}
}
}
@@ -185,26 +146,21 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
timeout := time.After(2 * 60 * time.Second)
var errs []error
for range r.Packages {
for i := 0; i < nReq; i++ {
select {
case res := <-resChan:
for _, def := range res.defs {
for _, p := range def.AffectedPacks {
if res.pack.Name != p.Name {
continue
}
affected, notFixedYet := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel)
if !affected {
continue
}
if p.NotFixedYet {
relatedDefs.upsert(def, p.Name)
continue
}
if less, err := lessThan(r.Family, *res.pack, p); err != nil {
util.Log.Debugf("Failed to parse versions: %s", err)
util.Log.Debugf("%#v\n%#v", *res.pack, p)
} else if less {
relatedDefs.upsert(def, p.Name)
if res.request.isSrcPack {
for _, n := range res.request.binaryPackNames {
relatedDefs.upsert(def, n, false)
}
} else {
relatedDefs.upsert(def, res.request.packName, notFixedYet)
}
}
case err := <-errChan:
@@ -219,7 +175,7 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
return
}
func httpGet(url string, pack *models.Package, resChan chan<- response, errChan chan<- error) {
func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
@@ -257,14 +213,12 @@ func httpGet(url string, pack *models.Package, resChan chan<- response, errChan
return
}
resChan <- response{
pack: pack,
defs: defs,
request: req,
defs: defs,
}
}
func getDefsByPackNameFromOvalDB(family, osRelease string,
installedPacks models.Packages) (relatedDefs ovalResult, err error) {
func getDefsByPackNameFromOvalDB(r *models.ScanResult) (relatedDefs ovalResult, err error) {
ovallog.Initialize(config.Conf.LogDir)
path := config.Conf.OvalDBURL
if config.Conf.OvalDBType == "sqlite3" {
@@ -273,48 +227,119 @@ func getDefsByPackNameFromOvalDB(family, osRelease string,
util.Log.Debugf("Open oval-dictionary db (%s): %s", config.Conf.OvalDBType, path)
var ovaldb db.DB
if ovaldb, err = db.NewDB(
family,
config.Conf.OvalDBType,
path,
config.Conf.DebugSQL,
); err != nil {
if ovaldb, err = db.NewDB(r.Family, config.Conf.OvalDBType,
path, config.Conf.DebugSQL); err != nil {
return
}
defer ovaldb.CloseDB()
for _, installedPack := range installedPacks {
definitions, err := ovaldb.GetByPackName(osRelease, installedPack.Name)
requests := []request{}
for _, pack := range r.Packages {
requests = append(requests, request{
packName: pack.Name,
versionRelease: pack.FormatVer(),
NewVersionRelease: pack.FormatNewVer(),
isSrcPack: false,
})
}
for _, pack := range r.SrcPackages {
requests = append(requests, request{
packName: pack.Name,
binaryPackNames: pack.BinaryNames,
versionRelease: pack.Version,
isSrcPack: true,
})
}
for _, req := range requests {
definitions, err := ovaldb.GetByPackName(r.Release, req.packName)
if err != nil {
return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", family, err)
return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", r.Family, err)
}
for _, def := range definitions {
for _, ovalPack := range def.AffectedPacks {
if installedPack.Name != ovalPack.Name {
continue
}
affected, notFixedYet := isOvalDefAffected(def, req, r.Family, r.RunningKernel)
if !affected {
continue
}
if ovalPack.NotFixedYet {
relatedDefs.upsert(def, installedPack.Name)
continue
}
less, err := lessThan(family, installedPack, ovalPack)
if err != nil {
util.Log.Debugf("Failed to parse versions: %s", err)
util.Log.Debugf("%#v\n%#v", installedPack, ovalPack)
} else if less {
relatedDefs.upsert(def, installedPack.Name)
if req.isSrcPack {
for _, n := range req.binaryPackNames {
relatedDefs.upsert(def, n, false)
}
} else {
relatedDefs.upsert(def, req.packName, notFixedYet)
}
}
}
return
}
func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bool, error) {
func major(version string) string {
ss := strings.SplitN(version, ":", 2)
ver := ""
if len(ss) == 1 {
ver = ss[0]
} else {
ver = ss[1]
}
return ver[0:strings.Index(ver, ".")]
}
func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel) (affected, notFixedYet bool) {
for _, ovalPack := range def.AffectedPacks {
if req.packName != ovalPack.Name {
continue
}
if running.Release != "" {
switch family {
case config.RedHat, config.CentOS:
// For kernel related packages, ignore OVAL information with different major versions
if _, ok := kernelRelatedPackNames[ovalPack.Name]; ok {
if major(ovalPack.Version) != major(running.Release) {
continue
}
}
}
}
if ovalPack.NotFixedYet {
return true, true
}
less, err := lessThan(family, req.versionRelease, ovalPack)
if err != nil {
util.Log.Debugf("Failed to parse versions: %s, Ver: %#v, OVAL: %#v, DefID: %s",
err, req.versionRelease, ovalPack, def.DefinitionID)
return false, false
}
if less {
if req.isSrcPack {
// Unable to judge whether fixed or not fixed of src package(Ubuntu, Debian)
return true, false
}
if req.NewVersionRelease == "" {
return true, true
}
// compare version: newVer vs oval
less, err := lessThan(family, req.NewVersionRelease, ovalPack)
if err != nil {
util.Log.Debugf("Failed to parse versions: %s, NewVer: %#v, OVAL: %#v, DefID: %s",
err, req.NewVersionRelease, ovalPack, def.DefinitionID)
return false, false
}
return true, less
}
}
return false, false
}
func lessThan(family, versionRelease string, packB ovalmodels.Package) (bool, error) {
switch family {
case config.Debian, config.Ubuntu:
vera, err := debver.NewVersion(packA.Version)
vera, err := debver.NewVersion(versionRelease)
if err != nil {
return false, err
}
@@ -323,10 +348,18 @@ func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bo
return false, err
}
return vera.LessThan(verb), nil
case config.RedHat, config.CentOS, config.Oracle:
vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", packA.Version, packA.Release))
case config.Oracle, config.SUSEEnterpriseServer, config.Alpine:
vera := rpmver.NewVersion(versionRelease)
verb := rpmver.NewVersion(packB.Version)
return vera.LessThan(verb), nil
case config.RedHat, config.CentOS: // TODO: Suport config.Scientific
rea := regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.centos)?`)
reb := regexp.MustCompile(`\.el(\d+)(?:_\d+)?`)
vera := rpmver.NewVersion(rea.ReplaceAllString(versionRelease, ".el$1"))
verb := rpmver.NewVersion(reb.ReplaceAllString(packB.Version, ".el$1"))
return vera.LessThan(verb), nil
default:
util.Log.Errorf("Not implemented yet: %s", family)
}
return false, fmt.Errorf("Package version comparison not supported: %s", family)
}

File diff suppressed because it is too large Load Diff

View File

@@ -142,21 +142,11 @@ func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails [
cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
var driver cvedb.DB
if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
if driver, err = cvedb.NewDB(cveconfig.Conf.DBType, cveconfig.Conf.DBPath, cveconfig.Conf.DebugSQL); err != nil {
log.Error(err)
return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
}
util.Log.Debugf("Opening DB (%s).", driver.Name())
if err := driver.OpenDB(
cveconfig.Conf.DBType,
cveconfig.Conf.DBPath,
cveconfig.Conf.DebugSQL,
); err != nil {
return []*cve.CveDetail{},
fmt.Errorf("Failed to open DB. err: %s", err)
}
for _, cveID := range cveIDs {
cveDetail := driver.Get(cveID)
if len(cveDetail.CveID) == 0 {
@@ -276,19 +266,11 @@ func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) (cveDeta
cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
var driver cvedb.DB
if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
if driver, err = cvedb.NewDB(cveconfig.Conf.DBType, cveconfig.Conf.DBPath, cveconfig.Conf.DebugSQL); err != nil {
log.Error(err)
return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
}
util.Log.Debugf("Opening DB (%s).", driver.Name())
if err = driver.OpenDB(
cveconfig.Conf.DBType,
cveconfig.Conf.DBPath,
cveconfig.Conf.DebugSQL,
); err != nil {
return []*cve.CveDetail{},
fmt.Errorf("Failed to open DB. err: %s", err)
}
return driver.GetByCpeName(cpeName), nil
}

View File

@@ -19,7 +19,6 @@ package report
import (
"fmt"
"strings"
"time"
c "github.com/future-architect/vuls/config"
@@ -81,6 +80,10 @@ func FillCveInfos(rs []models.ScanResult, dir string) ([]models.ScanResult, erro
for _, r := range filled {
r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
r = r.FilterIgnoreCves(c.Conf.Servers[r.ServerName].IgnoreCves)
r = r.FilterUnfixed()
if c.Conf.IgnoreUnscoredCves {
r.ScannedCves = r.ScannedCves.FindScoredVulns()
}
filtered = append(filtered, r)
}
return filtered, nil
@@ -173,19 +176,29 @@ func FillWithOval(r *models.ScanResult) (err error) {
case c.Oracle:
ovalClient = oval.NewOracle()
ovalFamily = c.Oracle
case c.Amazon, c.Raspbian, c.FreeBSD:
case c.SUSEEnterpriseServer:
// TODO other suse family
ovalClient = oval.NewSUSE()
ovalFamily = c.SUSEEnterpriseServer
case c.Alpine:
ovalClient = oval.NewAlpine()
ovalFamily = c.Alpine
case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows:
return nil
case c.ServerTypePseudo:
return nil
default:
return fmt.Errorf("OVAL for %s is not implemented yet", r.Family)
}
util.Log.Debugf("Check whether oval is already fetched: %s %s",
ovalFamily, r.Release)
ok, err := ovalClient.CheckIfOvalFetched(ovalFamily, r.Release)
if err != nil {
return err
}
if !ok {
major := strings.Split(r.Release, ".")[0]
util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, major)
util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, r.Release)
return nil
}

View File

@@ -27,6 +27,7 @@ import (
"github.com/cenkalti/backoff"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/nlopes/slack"
"github.com/parnurzeal/gorequest"
log "github.com/sirupsen/logrus"
)
@@ -36,31 +37,23 @@ type field struct {
Value string `json:"value"`
Short bool `json:"short"`
}
type attachment struct {
Title string `json:"title"`
TitleLink string `json:"title_link"`
Fallback string `json:"fallback"`
Text string `json:"text"`
Pretext string `json:"pretext"`
Color string `json:"color"`
Fields []*field `json:"fields"`
MrkdwnIn []string `json:"mrkdwn_in"`
Footer string `json:"footer"`
}
type message struct {
Text string `json:"text"`
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Channel string `json:"channel"`
Attachments []*attachment `json:"attachments"`
Text string `json:"text"`
Username string `json:"username"`
IconEmoji string `json:"icon_emoji"`
Channel string `json:"channel"`
ThreadTimeStamp string `json:"thread_ts"`
Attachments []slack.Attachment `json:"attachments"`
}
// SlackWriter send report to slack
type SlackWriter struct{}
func (w SlackWriter) Write(rs ...models.ScanResult) error {
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
conf := config.Conf.Slack
channel := conf.Channel
token := conf.LegacyToken
for _, r := range rs {
if channel == "${servername}" {
@@ -78,7 +71,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
IconEmoji: conf.IconEmoji,
Channel: channel,
}
if err := send(msg); err != nil {
if err = send(msg); err != nil {
return err
}
continue
@@ -88,7 +81,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
// Split into chunks with 100 elements
// https://api.slack.com/methods/chat.postMessage
maxAttachments := 100
m := map[int][]*attachment{}
m := map[int][]slack.Attachment{}
for i, a := range toSlackAttachments(r) {
m[i/maxAttachments] = append(m[i/maxAttachments], a)
}
@@ -98,21 +91,49 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
}
sort.Ints(chunkKeys)
for i, k := range chunkKeys {
txt := ""
if i == 0 {
txt = msgText(r)
// Send slack by API
if 0 < len(token) {
api := slack.New(token)
ParentMsg := slack.PostMessageParameters{
// Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
}
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: m[k],
}
if err := send(msg); err != nil {
var ts string
if _, ts, err = api.PostMessage(channel, msgText(r), ParentMsg); err != nil {
return err
}
for _, k := range chunkKeys {
params := slack.PostMessageParameters{
// Text: msgText(r),
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Attachments: m[k],
ThreadTimestamp: ts,
}
if _, _, err = api.PostMessage(channel, msgText(r), params); err != nil {
return err
}
}
} else {
for i, k := range chunkKeys {
txt := ""
if i == 0 {
txt = msgText(r)
}
msg := message{
Text: txt,
Username: conf.AuthUser,
IconEmoji: conf.IconEmoji,
Channel: channel,
Attachments: m[k],
}
if err = send(msg); err != nil {
return err
}
}
}
}
return nil
@@ -164,14 +185,8 @@ func msgText(r models.ScanResult) string {
r.ScannedCves.FormatCveSummary())
}
func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
var vinfos []models.VulnInfo
if config.Conf.IgnoreUnscoredCves {
vinfos = r.ScannedCves.FindScoredVulns().ToSortedSlice()
} else {
vinfos = r.ScannedCves.ToSortedSlice()
}
func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
vinfos := r.ScannedCves.ToSortedSlice()
for _, vinfo := range vinfos {
curent := []string{}
for _, affected := range vinfo.AffectedPackages {
@@ -202,12 +217,12 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
new = append(new, "?")
}
a := attachment{
Title: vinfo.CveID,
TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
Text: attachmentText(vinfo, r.Family),
MrkdwnIn: []string{"text", "pretext"},
Fields: []*field{
a := slack.Attachment{
Title: vinfo.CveID,
TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
Text: attachmentText(vinfo, r.Family),
MarkdownIn: []string{"text", "pretext"},
Fields: []slack.AttachmentField{
{
// Title: "Current Package/CPE",
Title: "Installed",
@@ -222,7 +237,7 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
},
Color: color(vinfo.MaxCvssScore().Value.Score),
}
attaches = append(attaches, &a)
attaches = append(attaches, a)
}
return
}

View File

@@ -45,6 +45,12 @@ var currentChangelogLimitY int
// RunTui execute main logic
func RunTui(results models.ScanResults) subcommands.ExitStatus {
scanResults = results
sort.Slice(scanResults, func(i, j int) bool {
if scanResults[i].ServerName == scanResults[j].ServerName {
return scanResults[i].Container.Name < scanResults[j].Container.Name
}
return scanResults[i].ServerName < scanResults[j].ServerName
})
// g, err := gocui.NewGui(gocui.OutputNormal)
g := gocui.NewGui()

View File

@@ -93,12 +93,7 @@ func formatShortPlainText(r models.ScanResult) string {
header, r.Errors)
}
vulns := r.ScannedCves
if config.Conf.IgnoreUnscoredCves {
vulns = vulns.FindScoredVulns()
}
if len(vulns) == 0 {
if len(r.ScannedCves) == 0 {
return fmt.Sprintf(`
%s
No CVE-IDs are found in updatable packages.
@@ -109,7 +104,7 @@ No CVE-IDs are found in updatable packages.
stable := uitable.New()
stable.MaxColWidth = maxColWidth
stable.Wrap = true
for _, vuln := range vulns.ToSortedSlice() {
for _, vuln := range r.ScannedCves.ToSortedSlice() {
summaries := vuln.Summaries(config.Conf.Lang, r.Family)
links := vuln.CveContents.SourceLinks(
config.Conf.Lang, r.Family, vuln.CveID)
@@ -167,12 +162,7 @@ func formatFullPlainText(r models.ScanResult) string {
header, r.Errors)
}
vulns := r.ScannedCves
if config.Conf.IgnoreUnscoredCves {
vulns = vulns.FindScoredVulns()
}
if len(vulns) == 0 {
if len(r.ScannedCves) == 0 {
return fmt.Sprintf(`
%s
No CVE-IDs are found in updatable packages.
@@ -183,7 +173,7 @@ No CVE-IDs are found in updatable packages.
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
for _, vuln := range vulns.ToSortedSlice() {
for _, vuln := range r.ScannedCves.ToSortedSlice() {
table.AddRow(vuln.CveID)
table.AddRow("----------------")
table.AddRow("Max Score", vuln.FormatMaxCvssScore())
@@ -298,7 +288,7 @@ func loadPrevious(current models.ScanResults) (previous models.ScanResults, err
}
if r.Family == result.Family && r.Release == result.Release {
previous = append(previous, *r)
util.Log.Infof("Privious json found: %s", path)
util.Log.Infof("Previous json found: %s", path)
break
}
}

171
scan/alpine.go Normal file
View File

@@ -0,0 +1,171 @@
/* 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 scan
import (
"bufio"
"fmt"
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
// inherit OsTypeInterface
type alpine struct {
base
}
// NewAlpine is constructor
func newAlpine(c config.ServerInfo) *alpine {
d := &alpine{
base: base{
osPackages: osPackages{
Packages: models.Packages{},
VulnInfos: models.VulnInfos{},
},
},
}
d.log = util.NewCustomLogger(c)
d.setServerInfo(c)
return d
}
// Alpine
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/alpine.rb
func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) {
os = newAlpine(c)
if r := exec(c, "ls /etc/alpine-release", noSudo); !r.isSuccess() {
return false, os
}
if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
os.setDistro(config.Alpine, strings.TrimSpace(r.Stdout))
return true, os
}
return false, os
}
func (o *alpine) checkDependencies() error {
o.log.Infof("Dependencies... No need")
return nil
}
func (o *alpine) checkIfSudoNoPasswd() error {
o.log.Infof("sudo ... No need")
return nil
}
func (o *alpine) apkUpdate() error {
r := o.exec("apk update", noSudo)
if !r.isSuccess() {
return fmt.Errorf("Failed to SSH: %s", r)
}
return nil
}
func (o *alpine) scanPackages() error {
if err := o.apkUpdate(); err != nil {
return err
}
// collect the running kernel information
release, version, err := o.runningKernel()
if err != nil {
o.log.Errorf("Failed to scan the running kernel version: %s", err)
return err
}
o.Kernel = models.Kernel{
Release: release,
Version: version,
}
installed, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}
updatable, err := o.scanUpdatablePackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}
installed.MergeNewVersion(updatable)
o.Packages = installed
return nil
}
func (o *alpine) scanInstalledPackages() (models.Packages, error) {
cmd := util.PrependProxyEnv("apk info -v")
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
return o.parseApkInfo(r.Stdout)
}
func (o *alpine) parseApkInfo(stdout string) (models.Packages, error) {
packs := models.Packages{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
ss := strings.Split(line, "-")
if len(ss) < 3 {
return nil, fmt.Errorf("Failed to parse apk info -v: %s", line)
}
name := strings.Join(ss[:len(ss)-2], "-")
packs[name] = models.Package{
Name: name,
Version: strings.Join(ss[len(ss)-2:], "-"),
}
}
return packs, nil
}
func (o *alpine) scanUpdatablePackages() (models.Packages, error) {
cmd := util.PrependProxyEnv("apk version")
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to SSH: %s", r)
}
return o.parseApkVersion(r.Stdout)
}
func (o *alpine) parseApkVersion(stdout string) (models.Packages, error) {
packs := models.Packages{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, "<") {
continue
}
ss := strings.Split(line, "<")
namever := strings.TrimSpace(ss[0])
tt := strings.Split(namever, "-")
name := strings.Join(tt[:len(tt)-2], "-")
packs[name] = models.Package{
Name: name,
NewVersion: strings.TrimSpace(ss[1]),
}
}
return packs, nil
}

75
scan/alpine_test.go Normal file
View File

@@ -0,0 +1,75 @@
package scan
import (
"reflect"
"testing"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
func TestParseApkInfo(t *testing.T) {
var tests = []struct {
in string
packs models.Packages
}{
{
in: `musl-1.1.16-r14
busybox-1.26.2-r7
`,
packs: models.Packages{
"musl": {
Name: "musl",
Version: "1.1.16-r14",
},
"busybox": {
Name: "busybox",
Version: "1.26.2-r7",
},
},
},
}
d := newAlpine(config.ServerInfo{})
for i, tt := range tests {
pkgs, _ := d.parseApkInfo(tt.in)
if !reflect.DeepEqual(tt.packs, pkgs) {
t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
}
}
}
func TestParseApkVersion(t *testing.T) {
var tests = []struct {
in string
packs models.Packages
}{
{
in: `Installed: Available:
libcrypto1.0-1.0.1q-r0 < 1.0.2m-r0
libssl1.0-1.0.1q-r0 < 1.0.2m-r0
nrpe-2.14-r2 < 2.15-r5
`,
packs: models.Packages{
"libcrypto1.0": {
Name: "libcrypto1.0",
NewVersion: "1.0.2m-r0",
},
"libssl1.0": {
Name: "libssl1.0",
NewVersion: "1.0.2m-r0",
},
"nrpe": {
Name: "nrpe",
NewVersion: "2.15-r5",
},
},
},
}
d := newAlpine(config.ServerInfo{})
for i, tt := range tests {
pkgs, _ := d.parseApkVersion(tt.in)
if !reflect.DeepEqual(tt.packs, pkgs) {
t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
}
}
}

View File

@@ -313,6 +313,7 @@ func (l *base) convertToModel() models.ScanResult {
ScannedCves: l.VulnInfos,
RunningKernel: l.Kernel,
Packages: l.Packages,
SrcPackages: l.SrcPackages,
Optional: l.ServerInfo.Optional,
Errors: errs,
}

View File

@@ -29,8 +29,7 @@ import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/knqyf263/go-deb-version"
version "github.com/knqyf263/go-deb-version"
)
// inherit OsTypeInterface
@@ -176,7 +175,6 @@ func (o *debian) checkDependencies() error {
}
for _, name := range packNames {
//TODO --show-format
cmd := "dpkg-query -W " + name
if r := o.exec(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", name)
@@ -206,12 +204,13 @@ func (o *debian) scanPackages() error {
RebootRequired: rebootRequired,
}
installed, updatable, err := o.scanInstalledPackages()
installed, updatable, srcPacks, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}
o.Packages = installed
o.SrcPackages = srcPacks
if config.Conf.Deep || o.Distro.Family == config.Raspbian {
unsecures, err := o.scanUnsecurePackages(updatable)
@@ -238,34 +237,72 @@ func (o *debian) rebootRequired() (bool, error) {
}
}
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, error) {
installed, updatable := models.Packages{}, models.Packages{}
r := o.exec("dpkg-query -W", noSudo)
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, models.SrcPackages, error) {
installed, updatable, srcPacks := models.Packages{}, models.Packages{}, models.SrcPackages{}
r := o.exec(`dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${Source},\${source:Version}\n"`, noSudo)
if !r.isSuccess() {
return nil, nil, fmt.Errorf("Failed to SSH: %s", r)
return nil, nil, nil, fmt.Errorf("Failed to SSH: %s", r)
}
// e.g.
// curl 7.19.7-40.el6_6.4
// openldap 2.4.39-8.el6
// e.g.
// curl,ii ,7.38.0-4+deb8u2,,7.38.0-4+deb8u2
// openssh-server,ii ,1:6.7p1-5+deb8u3,openssh,1:6.7p1-5+deb8u3
// tar,ii ,1.27.1-2+b1,tar (1.27.1-2),1.27.1-2
lines := strings.Split(r.Stdout, "\n")
for _, line := range lines {
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
name, version, err := o.parseScannedPackagesLine(trimmed)
name, status, version, srcName, srcVersion, err := o.parseScannedPackagesLine(trimmed)
if err != nil {
return nil, nil, fmt.Errorf(
return nil, nil, nil, fmt.Errorf(
"Debian: Failed to parse package line: %s", line)
}
packageStatus := status[1]
// Package status:
// n = Not-installed
// c = Config-files
// H = Half-installed
// U = Unpacked
// F = Half-configured
// W = Triggers-awaiting
// t = Triggers-pending
// i = Installed
if packageStatus != 'i' {
o.log.Debugf("%s package status is '%c', ignoring", name, packageStatus)
continue
}
installed[name] = models.Package{
Name: name,
Version: version,
}
if srcName != "" && srcName != name {
if pack, ok := srcPacks[srcName]; ok {
pack.AddBinaryName(name)
srcPacks[srcName] = pack
} else {
srcPacks[srcName] = models.SrcPackage{
Name: srcName,
Version: srcVersion,
BinaryNames: []string{name},
}
}
}
}
}
// Remove "linux"
// kernel-related packages are showed "linux" as source package name
// If "linux" is left, oval detection will cause trouble, so delete.
delete(srcPacks, "linux")
// Remove duplicate
for name := range installed {
delete(srcPacks, name)
}
updatableNames, err := o.getUpdatablePackNames()
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
for _, name := range updatableNames {
for _, pack := range installed {
@@ -279,28 +316,30 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, erro
// Fill the candidate versions of upgradable packages
err = o.fillCandidateVersion(updatable)
if err != nil {
return nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
return nil, nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
}
installed.MergeNewVersion(updatable)
return installed, updatable, nil
return installed, updatable, srcPacks, nil
}
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
func (o *debian) parseScannedPackagesLine(line string) (name, version string, err error) {
result := packageLinePattern.FindStringSubmatch(line)
if len(result) == 3 {
func (o *debian) parseScannedPackagesLine(line string) (name, status, version, srcName, srcVersion string, err error) {
ss := strings.Split(line, ",")
if len(ss) == 5 {
// remove :amd64, i386...
name = result[1]
name = ss[0]
if i := strings.IndexRune(name, ':'); i >= 0 {
name = name[:i]
}
version = result[2]
status = ss[1]
version = ss[2]
// remove version. ex: tar (1.27.1-2)
srcName = strings.Split(ss[3], " ")[0]
srcVersion = ss[4]
return
}
return "", "", fmt.Errorf("Unknown format: %s", line)
return "", "", "", "", "", fmt.Errorf("Unknown format: %s", line)
}
func (o *debian) aptGetUpdate() error {

View File

@@ -29,35 +29,6 @@ import (
"github.com/sirupsen/logrus"
)
func TestParseScannedPackagesLineDebian(t *testing.T) {
var packagetests = []struct {
in string
name string
version string
}{
{"base-passwd 3.5.33", "base-passwd", "3.5.33"},
{"bzip2 1.0.6-5", "bzip2", "1.0.6-5"},
{"adduser 3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
{"bash 4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
{"bsdutils 1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
{"ca-certificates 20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
{"apt 1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
}
d := newDebian(config.ServerInfo{})
for _, tt := range packagetests {
n, v, _ := d.parseScannedPackagesLine(tt.in)
if n != tt.name {
t.Errorf("name: expected %s, actual %s", tt.name, n)
}
if v != tt.version {
t.Errorf("version: expected %s, actual %s", tt.version, v)
}
}
}
func TestGetCveIDsFromChangelog(t *testing.T) {
var tests = []struct {

View File

@@ -41,6 +41,7 @@ import (
type execResult struct {
Servername string
Container conf.Container
Host string
Port string
Cmd string
@@ -51,9 +52,16 @@ type execResult struct {
}
func (s execResult) String() string {
sname := ""
if s.Container.ContainerID == "" {
sname = s.Servername
} else {
sname = s.Container.Name + "@" + s.Servername
}
return fmt.Sprintf(
"execResult: servername: %s\n cmd: %s\n exitstatus: %d\n stdout: %s\n stderr: %s\n err: %s",
s.Servername, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
sname, s.Cmd, s.ExitStatus, s.Stdout, s.Stderr, s.Error)
}
func (s execResult) isSuccess(expectedStatusCodes ...int) bool {
@@ -167,10 +175,11 @@ func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (resul
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
cmdstr = decorateCmd(c, cmdstr, sudo)
var cmd *ex.Cmd
if c.Distro.Family == conf.FreeBSD {
switch c.Distro.Family {
// case conf.FreeBSD, conf.Alpine, conf.Debian:
// cmd = ex.Command("/bin/sh", "-c", cmdstr)
default:
cmd = ex.Command("/bin/sh", "-c", cmdstr)
} else {
cmd = ex.Command("/bin/bash", "-c", cmdstr)
}
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
@@ -196,6 +205,7 @@ func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult)
func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) {
result.Servername = c.ServerName
result.Container = c.Container
result.Host = c.Host
result.Port = c.Port
@@ -311,6 +321,7 @@ func sshExecExternal(c conf.ServerInfo, cmd string, sudo bool) (result execResul
result.Stdout = stdoutBuf.String()
result.Stderr = stderrBuf.String()
result.Servername = c.ServerName
result.Container = c.Container
result.Host = c.Host
result.Port = c.Port
result.Cmd = fmt.Sprintf("%s %s", sshBinaryPath, strings.Join(args, " "))
@@ -324,10 +335,19 @@ func getSSHLogger(log ...*logrus.Entry) *logrus.Entry {
return log[0]
}
func dockerShell(family string) string {
switch family {
// case conf.Alpine, conf.Debian:
// return "/bin/sh"
default:
// return "/bin/bash"
return "/bin/sh"
}
}
func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
if sudo && c.User != "root" && !c.IsContainer() {
cmd = fmt.Sprintf("sudo -S %s", cmd)
cmd = strings.Replace(cmd, "|", "| sudo ", -1)
}
// If you are using pipe and you want to detect preprocessing errors, remove comment out
@@ -342,9 +362,11 @@ func decorateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
if c.IsContainer() {
switch c.Containers.Type {
case "", "docker":
cmd = fmt.Sprintf(`docker exec --user 0 %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd)
cmd = fmt.Sprintf(`docker exec --user 0 %s %s -c '%s'`,
c.Container.ContainerID, dockerShell(c.Distro.Family), cmd)
case "lxd":
cmd = fmt.Sprintf(`lxc exec %s -- /bin/bash -c "%s"`, c.Container.Name, cmd)
cmd = fmt.Sprintf(`lxc exec %s -- %s -c '%s'`,
c.Container.Name, dockerShell(c.Distro.Family), cmd)
}
}
// cmd = fmt.Sprintf("set -x; %s", cmd)

View File

@@ -63,7 +63,7 @@ func TestDecorateCmd(t *testing.T) {
conf: config.ServerInfo{User: "non-roor"},
cmd: "ls | grep hoge",
sudo: true,
expected: "sudo -S ls | sudo grep hoge",
expected: "sudo -S ls | grep hoge",
},
// -------------docker-------------
// root sudo false docker
@@ -75,7 +75,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: false,
expected: `docker exec --user 0 abc /bin/bash -c "ls"`,
expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
},
// root sudo true docker
{
@@ -86,7 +86,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: true,
expected: `docker exec --user 0 abc /bin/bash -c "ls"`,
expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
},
// non-root sudo false, docker
{
@@ -97,7 +97,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: false,
expected: `docker exec --user 0 abc /bin/bash -c "ls"`,
expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
},
// non-root sudo true, docker
{
@@ -108,7 +108,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: true,
expected: `docker exec --user 0 abc /bin/bash -c "ls"`,
expected: `docker exec --user 0 abc /bin/sh -c 'ls'`,
},
// non-root sudo true, docker
{
@@ -119,7 +119,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls | grep hoge",
sudo: true,
expected: `docker exec --user 0 abc /bin/bash -c "ls | grep hoge"`,
expected: `docker exec --user 0 abc /bin/sh -c 'ls | grep hoge'`,
},
// -------------lxd-------------
// root sudo false lxd
@@ -131,7 +131,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: false,
expected: `lxc exec def -- /bin/bash -c "ls"`,
expected: `lxc exec def -- /bin/sh -c 'ls'`,
},
// root sudo true lxd
{
@@ -142,7 +142,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: true,
expected: `lxc exec def -- /bin/bash -c "ls"`,
expected: `lxc exec def -- /bin/sh -c 'ls'`,
},
// non-root sudo false, lxd
{
@@ -153,7 +153,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: false,
expected: `lxc exec def -- /bin/bash -c "ls"`,
expected: `lxc exec def -- /bin/sh -c 'ls'`,
},
// non-root sudo true, lxd
{
@@ -164,7 +164,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls",
sudo: true,
expected: `lxc exec def -- /bin/bash -c "ls"`,
expected: `lxc exec def -- /bin/sh -c 'ls'`,
},
// non-root sudo true lxd
{
@@ -175,7 +175,7 @@ func TestDecorateCmd(t *testing.T) {
},
cmd: "ls | grep hoge",
sudo: true,
expected: `lxc exec def -- /bin/bash -c "ls | grep hoge"`,
expected: `lxc exec def -- /bin/sh -c 'ls | grep hoge'`,
},
}

66
scan/pseudo.go Normal file
View File

@@ -0,0 +1,66 @@
/* 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 scan
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
// inherit OsTypeInterface
type pseudo struct {
base
}
func detectPseudo(c config.ServerInfo) (itsMe bool, pseudo osTypeInterface, err error) {
p := newPseudo(c)
p.setDistro(config.ServerTypePseudo, "")
return c.Type == config.ServerTypePseudo, p, nil
}
func newPseudo(c config.ServerInfo) *pseudo {
d := &pseudo{
base: base{
osPackages: osPackages{
Packages: models.Packages{},
VulnInfos: models.VulnInfos{},
},
},
}
d.log = util.NewCustomLogger(c)
d.setServerInfo(c)
return d
}
func (o *pseudo) checkIfSudoNoPasswd() error {
return nil
}
func (o *pseudo) checkDependencies() error {
return nil
}
func (o *pseudo) scanPackages() error {
return nil
}
func (o *pseudo) detectPlatform() {
o.setPlatform(models.Platform{Name: "other"})
return
}

View File

@@ -153,6 +153,10 @@ func (o *redhat) checkIfSudoNoPasswd() error {
{"yum --color=never --security updateinfo updates", zero},
}
}
if o.Distro.Family == config.RedHat {
cmds = append(cmds, cmd{"repoquery -h", zero})
}
}
for _, c := range cmds {
@@ -256,11 +260,13 @@ func (o *redhat) scanPackages() error {
}
func (o *redhat) rebootRequired() (bool, error) {
r := o.exec("rpm -q --last kernel | head -n1", noSudo)
if !r.isSuccess() {
return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r)
r := o.exec("rpm -q --last kernel", noSudo)
scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
if !r.isSuccess() || !scanner.Scan() {
o.log.Warn("Failed to detect the last installed kernel : %v", r)
return false, nil
}
lastInstalledKernelVer := strings.Fields(r.Stdout)[0]
lastInstalledKernelVer := strings.Fields(scanner.Text())[0]
running := fmt.Sprintf("kernel-%s", o.Kernel.Release)
return running != lastInstalledKernelVer, nil
}
@@ -276,14 +282,7 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
}
installed := models.Packages{}
var cmd string
majorVersion, _ := o.Distro.MajorVersion()
if majorVersion < 6 {
cmd = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'"
} else {
cmd = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
}
r := o.exec(cmd, noSudo)
r := o.exec(rpmQa(o.Distro), noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Scan packages failed: %s", r)
}
@@ -300,14 +299,13 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
// Kernel package may be isntalled multiple versions.
// From the viewpoint of vulnerability detection,
// pay attention only to the running kernel
if pack.Name == "kernel" {
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
if o.Kernel.Release != ver {
o.log.Debugf("Not a running kernel: %s, uname: %s", ver, release)
isKernel, running := isRunningKernel(pack, o.Distro.Family, o.Kernel)
if isKernel {
if !running {
o.log.Debugf("Not a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
continue
} else {
o.log.Debugf("Running kernel: %s, uname: %s", ver, release)
}
o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
}
installed[pack.Name] = pack
}
@@ -389,7 +387,7 @@ func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
ver = fmt.Sprintf("%s:%s", epoch, fields[2])
}
repos := strings.Join(fields[4:len(fields)], " ")
repos := strings.Join(fields[4:], " ")
p := models.Package{
Name: fields[0],
@@ -401,7 +399,7 @@ func (o *redhat) parseUpdatablePacksLine(line string) (models.Package, error) {
}
func (o *redhat) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
if config.Conf.Deep {
if config.Conf.Deep && o.Distro.Family != config.Amazon {
//TODO Cache changelogs to bolt
if err := o.fillChangelogs(updatable); err != nil {
return nil, err
@@ -455,7 +453,7 @@ func (o *redhat) getAvailableChangelogs(packNames []string) (map[string]string,
if config.Conf.SkipBroken {
yumopts += " --skip-broken"
}
cmd := `yum --color=never %s changelog all %s | grep -A 10000 '==================== Available Packages ===================='`
cmd := `yum --color=never changelog all %s %s | grep -A 1000000 '==================== Available Packages ===================='`
cmd = fmt.Sprintf(cmd, yumopts, strings.Join(packNames, " "))
r := o.exec(util.PrependProxyEnv(cmd), o.sudo())
@@ -820,7 +818,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
inDesctiption, inCves = true, false
ss := strings.Split(line, " : ")
advisory.Description += fmt.Sprintf("%s\n",
strings.Join(ss[1:len(ss)], " : "))
strings.Join(ss[1:], " : "))
continue
}
@@ -834,7 +832,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
if inDesctiption {
if ss := strings.Split(line, ": "); 1 < len(ss) {
advisory.Description += fmt.Sprintf("%s\n",
strings.Join(ss[1:len(ss)], ": "))
strings.Join(ss[1:], ": "))
}
continue
}
@@ -842,7 +840,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
if found := o.isCvesHeaderLine(line); found {
inCves = true
ss := strings.Split(line, "CVEs : ")
line = strings.Join(ss[1:len(ss)], " ")
line = strings.Join(ss[1:], " ")
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
for _, cveID := range cveIDs {
cveIDsSetInThisSection[cveID] = true

View File

@@ -41,7 +41,6 @@ type osTypeInterface interface {
detectPlatform()
getPlatform() models.Platform
// checkDependencies checks if dependencies are installed on the target server.
checkDependencies() error
checkIfSudoNoPasswd() error
@@ -61,6 +60,9 @@ type osPackages struct {
// installed packages
Packages models.Packages
// installed source packages (Debian based only)
SrcPackages models.SrcPackages
// unsecure packages
VulnInfos models.VulnInfos
@@ -72,6 +74,11 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
var itsMe bool
var fatalErr error
if itsMe, osType, _ = detectPseudo(c); itsMe {
util.Log.Debugf("Pseudo")
return
}
itsMe, osType, fatalErr = detectDebian(c)
if fatalErr != nil {
osType.setErrs([]error{
@@ -89,11 +96,21 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
return
}
if itsMe, osType = detectSUSE(c); itsMe {
util.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port)
return
}
if itsMe, osType = detectFreebsd(c); itsMe {
util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
return
}
if itsMe, osType = detectAlpine(c); itsMe {
util.Log.Debugf("Alpine. Host: %s:%s", c.Host, c.Port)
return
}
//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
osType.setErrs([]error{fmt.Errorf("Unknown OS Type")})
return

185
scan/suse.go Normal file
View File

@@ -0,0 +1,185 @@
package scan
import (
"bufio"
"fmt"
"regexp"
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
// inherit OsTypeInterface
type suse struct {
redhat
}
// NewRedhat is constructor
func newSUSE(c config.ServerInfo) *suse {
r := &suse{
redhat: redhat{
base: base{
osPackages: osPackages{
Packages: models.Packages{},
VulnInfos: models.VulnInfos{},
},
},
},
}
r.log = util.NewCustomLogger(c)
r.setServerInfo(c)
return r
}
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/suse.rb
func detectSUSE(c config.ServerInfo) (itsMe bool, suse osTypeInterface) {
suse = newSUSE(c)
if r := exec(c, "ls /etc/os-release", noSudo); r.isSuccess() {
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
name := ""
if strings.Contains(r.Stdout, "ID=opensuse") {
//TODO check opensuse or opensuse.leap
name = config.OpenSUSE
} else if strings.Contains(r.Stdout, `NAME="SLES"`) {
name = config.SUSEEnterpriseServer
} else {
util.Log.Warn("Failed to parse SUSE edition: %s", r)
return true, suse
}
re := regexp.MustCompile(`VERSION_ID=\"(\d+\.\d+|\d+)\"`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 2 {
util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
return true, suse
}
suse.setDistro(name, result[1])
return true, suse
}
}
} else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() {
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() {
re := regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
//TODO check opensuse or opensuse.leap
suse.setDistro(config.OpenSUSE, result[1])
return true, suse
}
re = regexp.MustCompile(`VERSION = (\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
version := result[1]
re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
suse.setDistro(config.SUSEEnterpriseServer,
fmt.Sprintf("%s.%s", version, result[1]))
return true, suse
}
}
util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
return true, suse
}
}
}
util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
return false, suse
}
func (o *suse) checkDependencies() error {
o.log.Infof("Dependencies... No need")
return nil
}
func (o *suse) checkIfSudoNoPasswd() error {
// SUSE doesn't need root privilege
o.log.Infof("sudo ... No need")
return nil
}
func (o *suse) scanPackages() error {
installed, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}
rebootRequired, err := o.rebootRequired()
if err != nil {
o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
return err
}
o.Kernel.RebootRequired = rebootRequired
updatable, err := o.scanUpdatablePackages()
if err != nil {
o.log.Errorf("Failed to scan updatable packages: %s", err)
return err
}
installed.MergeNewVersion(updatable)
o.Packages = installed
return nil
}
func (o *suse) rebootRequired() (bool, error) {
r := o.exec("rpm -q --last kernel-default | head -n1", noSudo)
if !r.isSuccess() {
return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r)
}
stdout := strings.Fields(r.Stdout)[0]
return !strings.Contains(stdout, strings.TrimSuffix(o.Kernel.Release, "-default")), nil
}
func (o *suse) scanUpdatablePackages() (models.Packages, error) {
cmd := ""
if v, _ := o.Distro.MajorVersion(); v < 12 {
cmd = "zypper -q lu"
} else {
cmd = "zypper --no-color -q lu"
}
r := o.exec(cmd, noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to scan updatable packages: %v", r)
}
return o.parseZypperLULines(r.Stdout)
}
func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) {
updatables := models.Packages{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
if strings.Index(line, "S | Repository") != -1 ||
strings.Index(line, "--+----------------") != -1 {
continue
}
pack, err := o.parseZypperLUOneLine(line)
if err != nil {
return nil, err
}
updatables[pack.Name] = *pack
}
return updatables, nil
}
func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) {
fs := strings.Fields(line)
if len(fs) != 11 {
return nil, fmt.Errorf("zypper -q lu Unknown format: %s", line)
}
available := strings.Split(fs[8], "-")
return &models.Package{
Name: fs[4],
NewVersion: available[0],
NewRelease: available[1],
Arch: fs[10],
}, nil
}

106
scan/suse_test.go Normal file
View File

@@ -0,0 +1,106 @@
/* 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 scan
import (
"reflect"
"testing"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
)
func TestScanUpdatablePackages(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: "sles"}
stdout := `S | Repository | Name | Current Version | Available Version | Arch
--+---------------------------------------------+-------------------------------+-----------------------------+-----------------------------+-------
v | SLES12-SP2-Updates | SUSEConnect | 0.3.0-19.8.1 | 0.3.1-19.11.2 | x86_64
v | SLES12-SP2-Updates | SuSEfirewall2 | 3.6.312-2.3.1 | 3.6.312-2.10.1 | noarch`
var tests = []struct {
in string
out models.Packages
}{
{
stdout,
models.NewPackages(
models.Package{
Name: "SUSEConnect",
NewVersion: "0.3.1",
NewRelease: "19.11.2",
Arch: "x86_64",
},
models.Package{
Name: "SuSEfirewall2",
NewVersion: "3.6.312",
NewRelease: "2.10.1",
Arch: "noarch",
},
),
},
}
for _, tt := range tests {
packages, err := r.parseZypperLULines(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
for name, ePack := range tt.out {
if !reflect.DeepEqual(ePack, packages[name]) {
e := pp.Sprintf("%v", ePack)
a := pp.Sprintf("%v", packages[name])
t.Errorf("expected %s, actual %s", e, a)
}
}
}
}
func TestScanUpdatablePackage(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: "sles"}
stdout := `v | SLES12-SP2-Updates | SUSEConnect | 0.3.0-19.8.1 | 0.3.1-19.11.2 | x86_64`
var tests = []struct {
in string
out models.Package
}{
{
stdout,
models.Package{
Name: "SUSEConnect",
NewVersion: "0.3.1",
NewRelease: "19.11.2",
Arch: "x86_64",
},
},
}
for _, tt := range tests {
pack, err := r.parseZypperLUOneLine(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
if !reflect.DeepEqual(*pack, tt.out) {
e := pp.Sprintf("%v", tt.out)
a := pp.Sprintf("%v", pack)
t.Errorf("expected %s, actual %s", e, a)
}
}
}

69
scan/utils.go Normal file
View File

@@ -0,0 +1,69 @@
/* 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 scan
import (
"fmt"
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
switch family {
case config.SUSEEnterpriseServer:
if pack.Name == "kernel-default" {
// Remove the last period and later because uname don't show that.
ss := strings.Split(pack.Release, ".")
rel := strings.Join(ss[0:len(ss)-1], ".")
ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
return true, kernel.Release == ver
}
return false, false
case config.RedHat, config.Oracle, config.CentOS, config.Amazon:
if pack.Name == "kernel" {
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
return true, kernel.Release == ver
}
return false, false
default:
util.Log.Warnf("Reboot required is not implemented yet: %s, %s", family, kernel)
}
return false, false
}
func rpmQa(distro config.Distro) string {
const old = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'"
const new = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}

117
scan/utils_test.go Normal file
View File

@@ -0,0 +1,117 @@
/* 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 scan
import (
"testing"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
func TestIsRunningKernelSUSE(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: config.SUSEEnterpriseServer}
kernel := models.Kernel{
Release: "4.4.74-92.35-default",
Version: "",
}
var tests = []struct {
pack models.Package
family string
kernel models.Kernel
expected bool
}{
{
pack: models.Package{
Name: "kernel-default",
Version: "4.4.74",
Release: "92.35.1",
Arch: "x86_64",
},
family: config.SUSEEnterpriseServer,
kernel: kernel,
expected: true,
},
{
pack: models.Package{
Name: "kernel-default",
Version: "4.4.59",
Release: "92.20.2",
Arch: "x86_64",
},
family: config.SUSEEnterpriseServer,
kernel: kernel,
expected: false,
},
}
for i, tt := range tests {
_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
if tt.expected != actual {
t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
}
}
}
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Distro = config.Distro{Family: config.Amazon}
kernel := models.Kernel{
Release: "4.9.43-17.38.amzn1.x86_64",
Version: "",
}
var tests = []struct {
pack models.Package
family string
kernel models.Kernel
expected bool
}{
{
pack: models.Package{
Name: "kernel",
Version: "4.9.43",
Release: "17.38.amzn1",
Arch: "x86_64",
},
family: config.Amazon,
kernel: kernel,
expected: true,
},
{
pack: models.Package{
Name: "kernel",
Version: "4.9.38",
Release: "16.35.amzn1",
Arch: "x86_64",
},
family: config.Amazon,
kernel: kernel,
expected: false,
},
}
for i, tt := range tests {
_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
if tt.expected != actual {
t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
}
}
}

View File

@@ -201,7 +201,7 @@ $ docker run --rm -it \
```console
$docker run -dt \
-v $PWD:/vuls \
-p 80:80 \
-p 5111:5111 \
vuls/vulsrepo
```

View File

@@ -1,31 +1,18 @@
FROM httpd:2.4
FROM alpine:3.6
MAINTAINER hikachan sadayuki-matsuno
# install packages
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
vim \
git \
libcgi-pm-perl \
libjson-perl \
&& rm -r /var/lib/apt/lists/*
# env
ENV HTTPD_PREFIX /usr/local/apache2
MAINTAINER hikachan sadayuki-matsuno usiusi360
VOLUME /vuls
WORKDIR ${HTTPD_PREFIX}/htdocs
RUN git clone https://github.com/usiusi360/vulsrepo.git \
&& echo "LoadModule cgid_module modules/mod_cgid.so" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo "<Directory \"$HTTPD_PREFIX/htdocs/vulsrepo/dist/cgi\">" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo " Options +ExecCGI +FollowSymLinks" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo " AddHandler cgi-script cgi" >> $HTTPD_PREFIX/conf/httpd.conf \
&& echo "</Directory>" >> $HTTPD_PREFIX/conf/httpd.conf \
&& sed -i -e 's/User daemon/#User/g' $HTTPD_PREFIX/conf/httpd.conf \
&& sed -i -e 's/Group daemon/#Group/g' $HTTPD_PREFIX/conf/httpd.conf \
&& ln -snf /vuls/results /usr/local/apache2/htdocs/vulsrepo/results
RUN apk --no-cache add git \
&& git clone https://github.com/usiusi360/vulsrepo.git
EXPOSE 80
CMD ["httpd-foreground"]
RUN cd /vulsrepo/server \
&& cp vulsrepo-config.toml.sample vulsrepo-config.toml \
&& sed -i -e 's/\/home\/vuls-user//g' vulsrepo-config.toml \
&& sed -i -e 's/\/opt//g' vulsrepo-config.toml \
&& mkdir /lib64 \
&& ln -s /lib/ld-musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
EXPOSE 5111
CMD ["/vulsrepo/server/vulsrepo-server"]

View File

@@ -23,7 +23,7 @@ VulsRepo is visualized based on the json report output in [vuls](https://github.
```console
$docker run -dt \
-v $PWD:/vuls \
-p 80:80 \
-p 5111:5111 \
vuls/vulsrepo
```

View File

@@ -109,7 +109,7 @@ func ProxyEnv() string {
return httpProxyEnv
}
// PrependProxyEnv prepends proxy enviroment variable
// PrependProxyEnv prepends proxy environment variable
func PrependProxyEnv(cmd string) string {
if len(config.Conf.HTTPProxy) == 0 {
return cmd