diff --git a/go.mod b/go.mod index f1d1c2ca..18b72b22 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module opensca go 1.16 require ( + github.com/BurntSushi/toml v1.0.0 github.com/Masterminds/semver/v3 v3.1.1 github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 github.com/dsnet/compress v0.0.1 // indirect diff --git a/go.sum b/go.sum index 6840892a..d6cd4063 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= diff --git a/internal/analyzer/rust/analyzer.go b/internal/analyzer/rust/analyzer.go new file mode 100644 index 00000000..597bbe81 --- /dev/null +++ b/internal/analyzer/rust/analyzer.go @@ -0,0 +1,60 @@ +package rust + +import ( + "opensca/internal/enum/language" + "opensca/internal/filter" + "opensca/internal/srt" +) + +type Analyzer struct{} + +func New() Analyzer { + return Analyzer{} +} + +/** + * @description: Get language of Analyzer + * @return {language.Type} language type + */ +func (a Analyzer) GetLanguage() language.Type { + return language.Rust +} + +/** + * @description: Check if it is a parsable file + * @param {string} filename file name + * @return {bool} is a parseable file returns true + */ +func (a Analyzer) CheckFile(filename string) bool { + return filter.RustCargoLock(filename) +} + +/** + * @description: filters the files that the current parser needs to parse + * @param {*srt.DirTree} dirRoot directory tree node + * @param {*srt.DepTree} depRoot Dependency tree node + * @return {[]*srt.FileData} List of files to parse + */ +func (a Analyzer) FilterFile(dirRoot *srt.DirTree, depRoot *srt.DepTree) []*srt.FileData { + files := []*srt.FileData{} + for _, f := range dirRoot.Files { + if a.CheckFile(f.Name) { + files = append(files, f) + } + } + return files +} + +/** + * @description: Parse the file + * @param {*srt.DirTree} dirRoot directory tree node + * @param {*srt.DepTree} depRoot Dependency tree node + * @param {*srt.FileData} file data to parse + * @return {[]*srt.DepTree} parsed dependency list + */ +func (a Analyzer) ParseFile(dirRoot *srt.DirTree, depRoot *srt.DepTree, file *srt.FileData) []*srt.DepTree { + if filter.RustCargoLock(file.Name) { + return parseCargoLock(dirRoot, depRoot, file) + } + return []*srt.DepTree{} +} diff --git a/internal/analyzer/rust/cargo.go b/internal/analyzer/rust/cargo.go new file mode 100644 index 00000000..608f4735 --- /dev/null +++ b/internal/analyzer/rust/cargo.go @@ -0,0 +1,93 @@ +package rust + +import ( + "opensca/internal/logs" + "opensca/internal/srt" + "sort" + "strings" + + "github.com/BurntSushi/toml" +) + +type cargoPkg struct { + Name string `toml:"name"` + Version string `toml:"version"` + DepStr []string `toml:"dependencies"` + Dependencies []struct { + Name string + Version string + } `toml:"-"` +} + +func parseCargoLock(dirRoot *srt.DirTree, depRoot *srt.DepTree, file *srt.FileData) []*srt.DepTree { + cargo := struct { + Pkgs []*cargoPkg `toml:"package"` + }{} + cdepMap := map[string]*cargoPkg{} + depMap := map[string]*srt.DepTree{} + directMap := map[string]*srt.DepTree{} + if err := toml.Unmarshal(file.Data, &cargo); err != nil { + logs.Warn(err) + } + for _, pkg := range cargo.Pkgs { + dep := srt.NewDepTree(nil) + dep.Name = pkg.Name + dep.Version = srt.NewVersion(pkg.Version) + pkg.Dependencies = make([]struct { + Name string + Version string + }, len(pkg.DepStr)) + for i, str := range pkg.DepStr { + name, version := str, "" + index := strings.Index(str, " ") + if index > -1 { + name, version = str[:index], str[index+1:] + } + pkg.Dependencies[i] = struct { + Name string + Version string + }{Name: name, Version: version} + } + depMap[dep.Name] = dep + directMap[dep.Name] = dep + cdepMap[dep.Name] = pkg + } + // 找出未被依赖的作为直接依赖 + for _, pkg := range cargo.Pkgs { + for _, d := range pkg.Dependencies { + delete(directMap, d.Name) + } + } + directDeps := []*srt.DepTree{} + for _, v := range directMap { + directDeps = append(directDeps, v) + } + sort.Slice(directDeps, func(i, j int) bool { + return directDeps[i].Name < directDeps[j].Name + }) + for _, d := range directDeps { + d.Parent = depRoot + depRoot.Children = append(depRoot.Children, d) + } + // 从顶层开始构建 + q := make([]*srt.DepTree, len(directDeps)) + copy(q, directDeps) + exist := map[string]struct{}{} + for len(q) > 0 { + n := q[0] + exist[n.Name] = struct{}{} + if cdep, ok := cdepMap[n.Name]; ok { + for _, d := range cdep.Dependencies { + if _, ok := exist[d.Name]; !ok { + exist[d.Name] = struct{}{} + if sub, ok := depMap[d.Name]; ok { + sub.Parent = n + n.Children = append(n.Children, sub) + } + } + } + } + q = append(q[1:], n.Children...) + } + return directDeps +} diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 170c6fad..3b275f7e 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -14,6 +14,7 @@ import ( "opensca/internal/analyzer/javascript" "opensca/internal/analyzer/php" "opensca/internal/analyzer/ruby" + "opensca/internal/analyzer/rust" "opensca/internal/args" "opensca/internal/enum/language" "opensca/internal/filter" @@ -44,6 +45,7 @@ func NewEngine() Engine { php.New(), ruby.New(), golang.New(), + rust.New(), }, } } diff --git a/internal/enum/language/language.go b/internal/enum/language/language.go index 97c70fce..636716f5 100644 --- a/internal/enum/language/language.go +++ b/internal/enum/language/language.go @@ -19,6 +19,7 @@ const ( Php Ruby Golang + Rust ) /** @@ -39,6 +40,8 @@ func (l Type) String() string { return "Ruby" case Golang: return "Golang" + case Rust: + return "Rust" default: return "None" } @@ -62,6 +65,8 @@ func (l Type) Vuln() string { return "ruby" case Golang: return "golang" + case Rust: + return "rust" default: return "" } @@ -77,7 +82,8 @@ func init() { lm[JavaScript] = []string{"js", "node", "nodejs", "javascript", "npm", "vue", "react"} lm[Php] = []string{"php", "composer"} lm[Ruby] = []string{"ruby"} - lm[Golang] = []string{"golang", "go"} + lm[Golang] = []string{"golang", "go", "gomod"} + lm[Rust] = []string{"rust", "cargo"} for t, ls := range lm { for _, l := range ls { lanMap[l] = t diff --git a/internal/filter/file.go b/internal/filter/file.go index a345d581..cd631fe9 100644 --- a/internal/filter/file.go +++ b/internal/filter/file.go @@ -75,3 +75,8 @@ var ( GoMod = filterFunc(strings.HasSuffix, "go.mod") GoSum = filterFunc(strings.HasSuffix, "go.sum") ) + +// rust +var ( + RustCargoLock = filterFunc(strings.HasSuffix, "Cargo.lock") +) diff --git a/internal/vuln/local.go b/internal/vuln/local.go index 2a797304..dec92bc9 100644 --- a/internal/vuln/local.go +++ b/internal/vuln/local.go @@ -46,10 +46,11 @@ func loadVulnDB() { } // 将漏洞信息存到vulnDB中 name := strings.ToLower(info.Product) - if _, ok := vulnDB[info.Language]; !ok { - vulnDB[info.Language] = map[string][]vulnInfo{} + language := strings.ToLower(info.Language) + if _, ok := vulnDB[language]; !ok { + vulnDB[language] = map[string][]vulnInfo{} } - vulns := vulnDB[info.Language] + vulns := vulnDB[language] vulns[name] = append(vulns[name], info) } }