Skip to content

Commit 20db05d

Browse files
add ruby support (#3)
* Add support for ruby scanning * use both jgem and gem for finding gem cache dirs * small bug fixes / improvements * fix windows bug, add error logs when operation fails * add bundler info to README.md, fix gempath and nil panic bugs * clean up code, fix bugs * clean up README.md * remove paren
1 parent 570d0b7 commit 20db05d

File tree

20 files changed

+2973
-38
lines changed

20 files changed

+2973
-38
lines changed

README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ The supported packages managers are:
1414

1515
* gradle
1616
* maven
17+
* bundler
1718

18-
In addition, the tool will search for vulnerable files with the `.jar` extension.
19+
In addition, the tool will search for vulnerable files with the `.jar`,`.gem` extensions.
1920

2021
### Prerequisites:
2122

@@ -40,6 +41,12 @@ In addition, the tool will search for vulnerable files with the `.jar` extension
4041
```shell
4142
mvn install
4243
```
44+
45+
* bundler projects __must__ be built prior to scanning, e.g. with the following command:
46+
```shell
47+
jbundler install
48+
```
49+
4350
* It is not necessary to run `gradle build` prior to scanning a `gradle` project, but that will greatly decrease the
4451
scan time
4552

@@ -53,7 +60,8 @@ In order to scan your project, simply run the following command:
5360
log4j-detect scan -d PROJECT_DIR
5461
```
5562

56-
The folder can include source code that uses maven/gradle in the project, as well as binaries (i.e jar files)
63+
The folder can include source code that uses supported package managers in the project, as well binaries with the
64+
supported extensions mentioned above
5765

5866
## Installation
5967

cmd/clioptions/settings/ruby.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package settings
2+
3+
import (
4+
"github.com/go-logr/logr"
5+
"github.com/whitesource/log4j-detect/fs"
6+
"github.com/whitesource/log4j-detect/operations"
7+
rubyS "github.com/whitesource/log4j-detect/operations/ruby"
8+
rc "github.com/whitesource/log4j-detect/records"
9+
rubyQ "github.com/whitesource/log4j-detect/screening/ruby"
10+
"github.com/whitesource/log4j-detect/utils/exec"
11+
)
12+
13+
type RubyResolver struct {
14+
Disabled bool
15+
}
16+
17+
func (r RubyResolver) Queries() map[rc.Organ]*fs.Query {
18+
if r.Disabled {
19+
return nil
20+
}
21+
22+
return map[rc.Organ]*fs.Query{rc.ORuby: rubyQ.Query()}
23+
}
24+
25+
func (r RubyResolver) Surgeons(logger logr.Logger, commander exec.Commander) map[rc.Organ]operations.Surgeon {
26+
if r.Disabled {
27+
return nil
28+
}
29+
30+
return map[rc.Organ]operations.Surgeon{
31+
rc.ORuby: rubyS.NewSurgeon(logger, commander),
32+
}
33+
}

cmd/clioptions/settings/settings.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package settings
33
import (
44
"fmt"
55
"github.com/go-logr/logr"
6-
"github.com/spf13/cobra"
76
"github.com/whitesource/log4j-detect/fs"
87
"github.com/whitesource/log4j-detect/fs/match"
98
"github.com/whitesource/log4j-detect/operations"
@@ -33,17 +32,15 @@ func (f *Flags) ToSettings(logger logr.Logger) (*Settings, error) {
3332
Fs: FilesystemResolver{
3433
Disabled: false,
3534
},
35+
Ruby: RubyResolver{
36+
Disabled: false,
37+
},
3638
},
3739
logger: logger,
3840
}
3941
return s, nil
4042
}
4143

42-
func AddFlags(cmd *cobra.Command, f *Flags) {
43-
cmd.Flags().BoolVarP(&f.mavenOnly, "maven-only", "m", false, "only scan for maven projects")
44-
cmd.Flags().BoolVarP(&f.gradleOnly, "gradle-only", "g", false, "only scan for gradle projects")
45-
}
46-
4744
// Settings represents all settings.
4845
// this includes logging parameters and other parameters that may modify the
4946
// behavior of resolvers for different package managers.
@@ -58,6 +55,7 @@ type Resolvers struct {
5855
Gradle GradleResolver
5956
Maven MavenResolver
6057
Fs FilesystemResolver
58+
Ruby RubyResolver
6159
}
6260

6361
type Resolver interface {
@@ -90,9 +88,9 @@ func (s *Settings) GlobalExcludes() match.Matcher {
9088
}
9189

9290
func (r *Resolvers) ManifestQueries() map[records.Organ]*fs.Query {
93-
return mergeQueries(r.Maven, r.Gradle, r.Fs)
91+
return mergeQueries(r.Maven, r.Gradle, r.Fs, r.Ruby)
9492
}
9593

9694
func (r *Resolvers) Surgeons(logger logr.Logger, commander exec.Commander) map[records.Organ]operations.Surgeon {
97-
return mergeSurgeons(logger, commander, r.Maven, r.Gradle, r.Fs)
95+
return mergeSurgeons(logger, commander, r.Maven, r.Gradle, r.Fs, r.Ruby)
9896
}

cmd/scan/cve/libs.json

+2,537-1
Large diffs are not rendered by default.

cmd/scan/log4j.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@ var fixes = map[string]map[string]string{
2323
}
2424

2525
//go:embed cve/libs.json
26-
var cveFiles []byte
26+
var libs []byte
2727

28-
var cve2Lib []records.VulnerableLib
28+
type Sha1ToLib map[string]records.VulnerableLib
29+
30+
type CveToSha1ToLib map[string]Sha1ToLib
31+
32+
var cve2Sha2Lib CveToSha1ToLib
2933

3034
func init() {
31-
err := json.Unmarshal(cveFiles, &cve2Lib)
35+
err := json.Unmarshal(libs, &cve2Sha2Lib)
3236
if err != nil {
3337
panic(fmt.Sprintf("failed to unmarshal libraries: %v", err))
3438
}

cmd/scan/scan.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,14 @@ func (o *Options) Run() error {
7474

7575
operationResults := operations.Perform(o.Logger, detected, o.Settings.Resolvers.Surgeons(o.Logger, o.commander))
7676
enhancedResults := supplements.Supplement(o.Logger, operationResults)
77-
cves := o.addVulnerabilities(enhancedResults, cve2Lib)
77+
cves := o.addVulnerabilities(enhancedResults, cve2Sha2Lib)
7878

7979
_, _ = fmt.Fprintln(o.Out)
8080

8181
if len(cves) > 0 {
8282
o.displayVulnerabilities(enhancedResults)
8383
_, _ = fmt.Fprintf(o.Out, `
8484
One or more of your projects contain the %s exploit.
85-
86-
Remediation steps:
8785
%s
8886
Learn more about the vulnerability and it's remediation:
8987
%s
@@ -100,18 +98,16 @@ Learn more about the vulnerability and it's remediation:
10098
return nil
10199
}
102100

103-
func (o *Options) addVulnerabilities(results []records.EnhancedResult, vulnerableLibs []records.VulnerableLib) []string {
104-
count := 0
101+
func (o *Options) addVulnerabilities(results []records.EnhancedResult, cve2Sha12Lib CveToSha1ToLib) []string {
105102
cveMap := map[string]bool{}
106103
for i := range results {
107104
r := &results[i]
108105
r.DepId2VulnerableLib = map[records.Id]records.VulnerableLib{}
109106
for id, dep := range *r.Deps {
110-
for _, lib := range vulnerableLibs {
111-
if dep.Sha1 == lib.Sha1 {
107+
for cve, sha12Lib := range cve2Sha12Lib {
108+
if lib, ok := sha12Lib[dep.Sha1]; ok {
112109
r.DepId2VulnerableLib[id] = lib
113-
cveMap[lib.CVE] = true
114-
count++
110+
cveMap[cve] = true
115111
}
116112
}
117113
}
@@ -141,7 +137,7 @@ func (o *Options) displayVulnerabilities(results []records.EnhancedResult) {
141137
break
142138
}
143139

144-
_, _ = fmt.Fprintln(o.Out, utils.MakeBlueText("Vulnerable Jars: "))
140+
_, _ = fmt.Fprintln(o.Out, utils.MakeBlueText("Vulnerable Files: "))
145141
for id := range r.DepId2VulnerableLib {
146142
path := (*r.Libraries)[id].SystemPath
147143
if abs, err := filepath.Abs(path); err == nil {
@@ -159,14 +155,19 @@ func (o *Options) generateRemediationSteps(results []records.EnhancedResult) str
159155
for _, r := range results {
160156
for _, v := range r.DepId2VulnerableLib {
161157
if artifact2Fix, found := fixes[v.GroupId]; found {
162-
if fix, found := artifact2Fix[v.ArtifactId]; found {
158+
if fix, fixFound := artifact2Fix[v.ArtifactId]; fixFound {
163159
set[fix] = true
164160
}
165161
}
166162
}
167163
}
168164

165+
if len(set) == 0 {
166+
return ""
167+
}
168+
169169
var steps strings.Builder
170+
steps.WriteString("\nRemediation Steps:\n")
170171
for fix := range set {
171172
steps.WriteString(fmt.Sprintf("\t* %s\n", fix))
172173
}

fs/match/name.go

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package match
33
import (
44
"io/fs"
55
"regexp"
6+
"strings"
67
)
78

89
// NameMatcher represents an arbitrary test for a file name (without leading directories)
@@ -34,3 +35,14 @@ func NewNameRegexMatcher(rs ...*regexp.Regexp) func(name string, _ fs.FileMode)
3435
return false
3536
}
3637
}
38+
39+
func NewExtensionMatcher(extensions ...string) func(name string, _ fs.FileMode) bool {
40+
return func(name string, _ fs.FileMode) bool {
41+
for _, ext := range extensions {
42+
if strings.HasSuffix(name, "."+ext) {
43+
return true
44+
}
45+
}
46+
return false
47+
}
48+
}

operations/gradle/gradle.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
_ "embed"
55
"encoding/json"
66
"errors"
7-
"fmt"
87
"github.com/go-logr/logr"
98
"github.com/whitesource/log4j-detect/records"
109
"github.com/whitesource/log4j-detect/utils"
@@ -229,12 +228,7 @@ func toStringList(strMap map[string]bool) (values []string) {
229228

230229
// persistInitScript saves the gradle init script to a temp file and returns the path
231230
func persistInitScript() (string, error) {
232-
file, err := ioutil.TempFile("", "gradle")
233-
if err != nil {
234-
return "", fmt.Errorf("gradle: failed to create temp file: %w", err)
235-
}
236-
_, err = file.WriteString(initScriptSource)
237-
return file.Name(), err
231+
return utils.CreateTempFile(initScriptSource, "gradle")
238232
}
239233

240234
func buildOpResult(project Project) records.OperationResult {
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require 'bundler'
2+
require 'json'
3+
4+
def parse_lock(file_path)
5+
parser = Bundler::LockfileParser.new(Bundler.read_file(file_path))
6+
7+
direct = parser.dependencies.keys
8+
depsToChildren = {}
9+
deps = {}
10+
11+
parser.specs.each do |spec|
12+
children = []
13+
spec.dependencies.each do |dep|
14+
children << dep.name
15+
end
16+
depsToChildren[spec.name] = children
17+
deps[spec.name] = {"name": spec.name, "version": spec.version.to_s}
18+
end
19+
20+
res = { directDependencies: direct, depsToChildren: depsToChildren, dependencies: deps }
21+
puts JSON.pretty_generate(res)
22+
end
23+
24+
# ARGV[0] -> path to Gemfile.lock or gems.locked
25+
parse_lock(ARGV[0])

0 commit comments

Comments
 (0)