From b042a600c3ccfd7ef618cf2d27aa331de4be46b3 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Mon, 24 Oct 2016 22:56:21 +0900 Subject: [PATCH] Integrate OWASP Dependency Check --- README.ja.md | 25 ++++++++ README.md | 26 +++++++- commands/discover.go | 2 + commands/scan.go | 4 +- config/config.go | 4 +- config/tomlloader.go | 29 +++++++-- .../owasp-dependency-check/parser/parser.go | 64 +++++++++++++++++++ 7 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 contrib/owasp-dependency-check/parser/parser.go diff --git a/README.ja.md b/README.ja.md index 1857f75145..8aa35edb5e 100644 --- a/README.ja.md +++ b/README.ja.md @@ -891,6 +891,31 @@ Vulsは、[CPE](https://nvd.nist.gov/cpe.cfm)に登録されているソフト "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を推測)、公開済みの脆弱性を検知するツール。 + +VulsとDependency Checkを連携させる方法は以下 +- Dependency Checkを、--format=XMLをつけて実行する +- そのXMLをconfig.toml内で以下のように定義する + + ``` + [servers] + + [servers.172-31-4-82] + host = "172.31.4.82" + user = "ec2-user" + keyPath = "/home/username/.ssh/id_rsa" + dependencyCheckXMLPath = "/tmp/dependency-check-report.xml" + ``` + +VulsとDependency Checkの連携すると以下の利点がある +- ライブラリを更新した場合に、config.tomlのCPEの定義を変更しなくても良い +- Vulsの機能でSlack, Emailで通知可能 +- 日本語のレポートが可能 + - Dependency Checkは日本語レポートに対応していない + # Usage: Scan Docker containers diff --git a/README.md b/README.md index 3905fdcf35..1493043167 100644 --- a/README.md +++ b/README.md @@ -866,7 +866,7 @@ optional = [ ---- -# Usage: Scan vulnerability of non-OS package +# Usage: Scan vulnerabilites of non-OS packages It is possible to detect vulnerabilities in non-OS packages, such as something you compiled by yourself, language libraries and frameworks, that have been registered in the [CPE](https://nvd.nist.gov/cpe.cfm). @@ -890,6 +890,30 @@ To detect the vulnerability of Ruby on Rails v4.2.1, cpeNames needs to be set in "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. + +Benefit of integrating Vuls And OWASP Dependency Check is below. +- Automatic Update of Vuls config when the libraries are updated. +- Reporting by Email or Slack by using Vuls. +- Reporting in Japanese + - OWASP Dependency Check supports only English + +How to integrate Vuls with OWASP Dependency Check +- Execute OWASP Dependency Check with --format=XML option. +- Define the xml file path of dependency check in config.toml. + + ``` + [servers] + + [servers.172-31-4-82] + host = "172.31.4.82" + user = "ec2-user" + keyPath = "/home/username/.ssh/id_rsa" + dependencyCheckXMLPath = "/tmp/dependency-check-report.xml" + ``` + # Usage: Scan Docker containers diff --git a/commands/discover.go b/commands/discover.go index cf9111c03d..ddaf45fc5c 100644 --- a/commands/discover.go +++ b/commands/discover.go @@ -115,6 +115,7 @@ subjectPrefix = "[vuls]" #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] +#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml" #containers = ["${running}"] #ignoreCves = ["CVE-2014-6271"] #optional = [ @@ -132,6 +133,7 @@ host = "{{$ip}}" #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] +#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml" #containers = ["${running}"] #ignoreCves = ["CVE-2014-0160"] #optional = [ diff --git a/commands/scan.go b/commands/scan.go index 1e1df7b746..c69790462e 100644 --- a/commands/scan.go +++ b/commands/scan.go @@ -33,6 +33,7 @@ import ( "github.com/future-architect/vuls/scan" "github.com/future-architect/vuls/util" "github.com/google/subcommands" + "github.com/k0kubun/pp" "golang.org/x/net/context" ) @@ -245,6 +246,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) return subcommands.ExitFailure } + c.Conf.Debug = p.debug err = c.Load(p.configPath, keyPass) if err != nil { logrus.Errorf("Error loading %s, %s", p.configPath, err) @@ -295,9 +297,9 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) if 0 < len(servernames) { c.Conf.Servers = target } + logrus.Debugf("%s", pp.Sprintf("%v", target)) c.Conf.Lang = p.lang - c.Conf.Debug = p.debug c.Conf.DebugSQL = p.debugSQL // logger diff --git a/config/config.go b/config/config.go index 6cd6141ebc..f62584db7b 100644 --- a/config/config.go +++ b/config/config.go @@ -188,7 +188,6 @@ type SlackConf struct { // Validate validates configuration func (c *SlackConf) Validate() (errs []error) { - if !c.UseThisTime { return } @@ -228,7 +227,8 @@ type ServerInfo struct { KeyPath string KeyPassword string - CpeNames []string + CpeNames []string + DependencyCheckXMLPath string // Container Names or IDs Containers []string diff --git a/config/tomlloader.go b/config/tomlloader.go index 21e52640cb..6b3338f010 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -23,7 +23,7 @@ import ( "github.com/BurntSushi/toml" log "github.com/Sirupsen/logrus" - "github.com/k0kubun/pp" + "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" ) // TOMLLoader loads config @@ -31,7 +31,11 @@ type TOMLLoader struct { } // Load load the configuraiton TOML file specified by path arg. -func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) { +func (c TOMLLoader) Load(pathToToml, keyPass string) error { + if Conf.Debug { + log.SetLevel(log.DebugLevel) + } + var conf Config if _, err := toml.DecodeFile(pathToToml, &conf); err != nil { log.Error("Load config failed", err) @@ -102,6 +106,23 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) { s.CpeNames = d.CpeNames } + s.DependencyCheckXMLPath = v.DependencyCheckXMLPath + if len(s.DependencyCheckXMLPath) == 0 { + s.DependencyCheckXMLPath = d.DependencyCheckXMLPath + } + + // Load CPEs from OWASP Dependency Check XML + if len(s.DependencyCheckXMLPath) != 0 { + cpes, err := parser.Parse(s.DependencyCheckXMLPath) + if err != nil { + return fmt.Errorf( + "Failed to read OWASP Dependency Check XML: %s", err) + } + log.Infof("Loaded from OWASP Dependency Check XML: %s", + s.ServerName) + s.CpeNames = append(s.CpeNames, cpes...) + } + s.Containers = v.Containers if len(s.Containers) == 0 { s.Containers = d.Containers @@ -140,8 +161,6 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) (err error) { servers[name] = s } - log.Debug("Config loaded") - log.Debugf("%s", pp.Sprintf("%v", servers)) Conf.Servers = servers - return + return nil } diff --git a/contrib/owasp-dependency-check/parser/parser.go b/contrib/owasp-dependency-check/parser/parser.go new file mode 100644 index 0000000000..c0e8e81884 --- /dev/null +++ b/contrib/owasp-dependency-check/parser/parser.go @@ -0,0 +1,64 @@ +package parser + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" + "sort" + "strings" +) + +type analysis struct { + Dependencies []dependency `xml:"dependencies>dependency"` +} + +type dependency struct { + Identifiers []identifier `xml:"identifiers>identifier"` +} + +type identifier struct { + Name string `xml:"name"` + Type string `xml:"type,attr"` +} + +func appendIfMissing(slice []string, str string) []string { + for _, s := range slice { + if s == str { + return slice + } + } + return append(slice, str) +} + +// Parse parses XML and collect list of cpe +func Parse(path string) ([]string, error) { + file, err := os.Open(path) + if err != nil { + return []string{}, fmt.Errorf("Failed to open: %s", err) + } + defer file.Close() + + b, err := ioutil.ReadAll(file) + if err != nil { + return []string{}, fmt.Errorf("Failed to read: %s", err) + } + + var anal analysis + if err := xml.Unmarshal(b, &anal); err != nil { + fmt.Errorf("Failed to unmarshal: %s", err) + } + + cpes := []string{} + for _, d := range anal.Dependencies { + for _, ident := range d.Identifiers { + if ident.Type == "cpe" { + name := strings.TrimPrefix(ident.Name, "(") + name = strings.TrimSuffix(name, ")") + cpes = appendIfMissing(cpes, name) + } + } + } + sort.Strings(cpes) + return cpes, nil +}