diff --git a/Gopkg.lock b/Gopkg.lock index 4829d8f7..f20f5bdb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -4,14 +4,14 @@ [[projects]] name = "github.com/Azure/azure-sdk-for-go" packages = ["storage"] - revision = "57db66900881e9fd21fd041a9d013514700ecab3" - version = "v10.3.0-beta" + revision = "df4dd90d076ebbf6e87d08d3f00bfac8ff4bde1a" + version = "v10.3.1-beta" [[projects]] name = "github.com/Azure/go-autorest" packages = ["autorest","autorest/adal","autorest/azure","autorest/date"] - revision = "77a52603f06947221c672f10275abc9bf2c7d557" - version = "v8.3.0" + revision = "f6be1abbb5abd0517522f850dd785990d373da7e" + version = "v8.4.0" [[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 = "b69f447375c7fa0047ebcdd8ae5d585d5aac2f71" + version = "v1.10.51" [[projects]] name = "github.com/boltdb/bolt" @@ -46,8 +46,8 @@ [[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" @@ -64,8 +64,8 @@ [[projects]] name = "github.com/go-redis/redis" packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"] - revision = "19c1c2272e00c1aaa903cf574c746cd449f9cd3c" - version = "v6.5.7" + revision = "975882d73d21759d45a4eb49652064083bc23e61" + version = "v6.7.0" [[projects]] name = "github.com/go-sql-driver/mysql" @@ -137,7 +137,7 @@ branch = "master" name = "github.com/kotakanbe/go-cve-dictionary" packages = ["config","db","jvn","log","models","nvd","util"] - revision = "c20fa7e1d07f7c700baf12c855f7fcf61525f1b6" + revision = "f5406ffe8226f01f64544723339c6a17b2bd74af" [[projects]] name = "github.com/kotakanbe/go-pingscanner" @@ -149,7 +149,7 @@ branch = "master" name = "github.com/kotakanbe/goval-dictionary" packages = ["config","db","db/rdb","log","models"] - revision = "3523cc174e68f285d0572d07c68ffa3a9290799c" + revision = "a9de1f6e9126c5e75b46b39e4049624cde8c8bb4" [[projects]] branch = "master" @@ -157,17 +157,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 = "b77235e3890a962fe8a6f8c4c7198679ca7814e7" [[projects]] name = "github.com/mattn/go-colorable" @@ -178,8 +172,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" @@ -203,7 +197,7 @@ branch = "master" name = "github.com/moul/http2curl" packages = ["."] - revision = "4e24498b31dba4683efb9d35c1c8a91e2eda28c8" + revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d" [[projects]] branch = "master" @@ -239,19 +233,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 = "89742aefa4b206dcf400792f3bd35b542998eb3b" [[projects]] branch = "master" @@ -263,25 +245,25 @@ branch = "master" name = "golang.org/x/crypto" packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"] - revision = "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035" + revision = "847319b7fc94cab682988f93da778204da164588" [[projects]] branch = "master" name = "golang.org/x/net" packages = ["context","idna","publicsuffix"] - revision = "1c05540f6879653db88113bc4a2b70aec4bd491f" + revision = "0744d001aa8470aaa53df28d32e5ceeb8af9bd70" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix","windows"] - revision = "07c182904dbd53199946ba614a412c61d3c548f5" + revision = "429f518978ab01db8bb6f44b66785088e7fba58b" [[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 = "1cbadb444a806fd9430d14ad08967ed91da4fa0a" [solve-meta] analyzer-name = "dep" diff --git a/README.ja.md b/README.ja.md index 5e7abe1e..caed4596 100644 --- a/README.ja.md +++ b/README.ja.md @@ -139,7 +139,7 @@ Vulsは上に挙げた手動運用での課題を解決するツールであり # Main Features - サーバに存在する脆弱性をスキャン - - FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbianに対応 + - FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise, Raspbianに対応 - クラウド、オンプレミス、Docker - 高精度なスキャン - Vulsは複数の脆弱性データベース、複数の検知方法を組み合わせることで高精度なスキャンを実現している @@ -328,6 +328,7 @@ $ goval-dictionary fetch-redhat 7 - [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 @@ -586,9 +587,10 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ | Oracle | Fast |  No | Supported | No | | Ubuntu | Fast |  No | Supported | No | | Debian | Fast |  No | Supported | No | +| Raspbian |1st time: Slow
From 2nd time: Fast | Need | No | Need | | FreeBSD | Fast |  No | No | Need | | Amazon | Fast |  No | No | Need | -| Raspbian |1st time: Slow
From 2nd time: Fast | Need | No | Need | +| SUSE Enterprise | Fast |  No | Supported | No| ---- @@ -604,22 +606,26 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ | Oracle | Slow |  Need | Supported | Need | | Ubuntu |1st time: Slow
From 2nd time: Fast| Need | Supported | Need | | Debian |1st time: Slow
From 2nd time: Fast| Need | Supported | Need | +| Raspbian |1st time: Slow
From 2nd time: Fast| Need | No | Need | | FreeBSD | Fast |  No | No | Need | | Amazon | Slow |  No | No | Need | -| Raspbian |1st time: Slow
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をパースする。 アップデート対象のパッケージが沢山ある場合、チェンジログの取得に時間がかかるので、初回のスキャンは遅い。 ただ、2回目以降はキャッシュしたchangelogを使うので速くなる。 -- CentOS +- On CentOS `yum changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。 -- Amazon, RHEL and FreeBSD +- On RHEL, Oracle, Amazon and FreeBSD `yum changelog`でアップデート対象のパッケージのチェンジログを取得する(パースはしない)。 +- On SUSE Enterprise Linux +Same as fast scan mode for now. + ---- # Use Cases @@ -646,6 +652,7 @@ web/app server in the same configuration under the load balancer | CentOS | 6, 7| | Amazon Linux| All| | FreeBSD | 10, 11| +| SUSE Enterprise | 11, 12| | Raspbian | Jessie, Stretch | ---- @@ -882,6 +889,7 @@ configtestサブコマンドは、config.tomlで定義されたサーバ/コン | Amazon | All | - | | RHEL | 5, 6, 7 | - | | Oracle Linux | 5, 6, 7 | - | +| SUSE Enterprise| 11, 12 | - | | FreeBSD | 10, 11 | - | | Raspbian | Jessie, Stretch | - | @@ -899,13 +907,14 @@ Deep Scan Modeでスキャンするためには、下記のパッケージが必 | Distribution | Release | Requirements | |:-------------|-------------------:|:-------------| | 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 | - | @@ -935,7 +944,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権限なしでスキャン可能 ---- @@ -1338,7 +1347,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| @@ -1713,6 +1722,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) ---- diff --git a/README.md b/README.md index f57ef0e5..84cd9af2 100644 --- a/README.md +++ b/README.md @@ -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 FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise Linux and Raspbian - Cloud, on-premise, Docker - High quality scan - Vuls uses Multiple vulnerability databases @@ -335,6 +335,7 @@ If you want to scan other than CentOS 7, fetch OVAL data according to the OS typ - [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 @@ -597,6 +598,7 @@ On the aggregation server, you can refer to the scanning result of each scan tar | Raspbian |1st time: Slow
From 2nd time: Fast | Need | No | Need | | FreeBSD | Fast |  No | No | Need | | Amazon | Fast |  No | No | Need | +| SUSE Enterprise | Fast |  No | Supported | No| --------- @@ -614,6 +616,7 @@ On the aggregation server, you can refer to the scanning result of each scan tar | Raspbian |1st time: Slow
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 +627,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 +Same as fast scan mode for now. + ---- # Use Cases @@ -658,6 +665,7 @@ 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 | ---- @@ -893,6 +901,7 @@ The configtest subcommand checks whether vuls is able to connect via SSH to serv | Amazon | All | - | | RHEL | 5, 6, 7 | - | | Oracle Linux | 5, 6, 7 | - | +| SUSE Enterprise| 11, 12 | - | | FreeBSD | 10, 11 | - | | Raspbian | Jessie, Stretch | - | @@ -915,6 +924,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 | - | @@ -944,7 +954,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. ---- @@ -1349,7 +1359,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| @@ -1712,6 +1722,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) ---- diff --git a/config/config.go b/config/config.go index 20c78afc..03e3df55 100644 --- a/config/config.go +++ b/config/config.go @@ -61,6 +61,21 @@ const ( // 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" ) //Config is struct of Configuration diff --git a/models/cvecontents.go b/models/cvecontents.go index 170b3553..f783b9df 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -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" ) diff --git a/models/vulninfos.go b/models/vulninfos.go index b7451231..1fabc90f 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -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) diff --git a/oval/suse.go b/oval/suse.go new file mode 100644 index 00000000..b0078de7 --- /dev/null +++ b/oval/suse.go @@ -0,0 +1,119 @@ +/* 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 . +*/ + +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(o.family, r.Release, r.Packages); 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 { + defPacks.actuallyAffectedPackNames[pack.Name] = true + } + vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages) + 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, + } +} diff --git a/oval/util.go b/oval/util.go index 1555994d..aa537744 100644 --- a/oval/util.go +++ b/oval/util.go @@ -323,10 +323,12 @@ 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: + case config.RedHat, config.CentOS, config.Oracle, config.SUSEEnterpriseServer: vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", packA.Version, packA.Release)) verb := rpmver.NewVersion(packB.Version) 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) } diff --git a/report/report.go b/report/report.go index 0db7d437..edcad543 100644 --- a/report/report.go +++ b/report/report.go @@ -173,6 +173,10 @@ func FillWithOval(r *models.ScanResult) (err error) { case c.Oracle: ovalClient = oval.NewOracle() ovalFamily = c.Oracle + case c.SUSEEnterpriseServer: + // TODO other suse family + ovalClient = oval.NewSUSE() + ovalFamily = c.SUSEEnterpriseServer case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows: return nil default: diff --git a/scan/redhat.go b/scan/redhat.go index 8b5cbb8b..6ebb02e8 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -280,14 +280,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) } @@ -304,14 +297,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 } diff --git a/scan/serverapi.go b/scan/serverapi.go index 86c06cb5..a6d0b52b 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -89,6 +89,11 @@ 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 diff --git a/scan/suse.go b/scan/suse.go new file mode 100644 index 00000000..31a08cb8 --- /dev/null +++ b/scan/suse.go @@ -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.HasPrefix(line, "S | Repository") || + strings.HasPrefix(line, "--+----------------") { + 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 +} diff --git a/scan/suse_test.go b/scan/suse_test.go new file mode 100644 index 00000000..cdfa58df --- /dev/null +++ b/scan/suse_test.go @@ -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 . +*/ + +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) + } + } +} diff --git a/scan/utils.go b/scan/utils.go new file mode 100644 index 00000000..5b44c0cc --- /dev/null +++ b/scan/utils.go @@ -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 . +*/ + +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 + } +} diff --git a/scan/utils_test.go b/scan/utils_test.go new file mode 100644 index 00000000..91090a30 --- /dev/null +++ b/scan/utils_test.go @@ -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 . +*/ + +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) + } + } +}