Skip to content

Commit 52b7f6e

Browse files
authored
feat - Exploitability (#151)
Eploits_Data
1 parent eee0cc9 commit 52b7f6e

26 files changed

+794
-1415
lines changed

.github/workflows/release-candidate.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131

3232
- name: Checkout repo
3333
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
34+
#ref: release-candidate
3435

3536
- name: Setup Node
3637
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2

.vscode/launch.json

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5-
"name": "Debug Scan2html with all args",
5+
"name": "Image scan with all args",
66
"type": "go",
77
"request": "launch",
88
"mode": "debug",
99
"program": "${workspaceFolder}",
10-
"args": ["image", "--format", "spdx", "ghcr.io/zalando/spilo-15:3.0-p1", "--scan2html-flags", "--output", "interactive_report.html", "--report-title", "Trivy Report Test", "--with-epss"]
10+
"args": ["image", "--scanners", "vuln,secret,misconfig,license", "ruby:3.1", "--scan2html-flags", "--output", "interactive_report.html", "--report-title", "Trivy Report Test", "--with-epss", "--with-exploits"]
1111
},
1212
{
1313
"name": "Image scan with a custom exit code",
1414
"type": "go",
1515
"request": "launch",
1616
"mode": "debug",
1717
"program": "${workspaceFolder}",
18-
"args": ["image", "--scanners", "vuln", "ruby:3.1", "--exit-code", "5", "--severity", "CRITICAL", "--scan2html-flags", "--output", "interactive_report.html"]
18+
"args": ["image", "--scanners", "vuln,secret,misconfig,license", "ruby:3.1", "--exit-code", "5", "--severity", "CRITICAL", "--scan2html-flags", "--output", "interactive_report.html"]
1919
},
2020
{
2121
"name": "Regression-1",
@@ -31,7 +31,7 @@
3131
"request": "launch",
3232
"mode": "debug",
3333
"program": "${workspaceFolder}",
34-
"args": ["generate", "--scan2html-flags", "--output", "interactive_report.html", "--from", "test/data/default/results.json,test/data/k8s/results.json"]
34+
"args": ["generate", "--scan2html-flags", "--with-exploits", "--output", "interactive_report.html", "--from", "test/data/default/results.json,test/data/k8s/results.json"]
3535
},
3636
{
3737
"name": "Debug Scan2html with Depricated flags",
@@ -40,14 +40,6 @@
4040
"mode": "debug",
4141
"program": "${workspaceFolder}",
4242
"args": ["image", "--scanners", "vuln", "ruby:3.1", "interactive_report.html"]
43-
},
44-
{
45-
"name": "Debug scan2htmlBash",
46-
"type": "bashdb",
47-
"request": "launch",
48-
"program": "${workspaceFolder}/scan2html",
49-
"args": ["trivy", "scan2html", "image", "--format", "spdx", "ghcr.io/zalando/spilo-15:3.0-p1", "test-report.html"]
50-
//"args": ["test/assets/app-template-test.html", "test/data/default/results.json", "test-report.html"]
5143
}
5244
]
5345
}

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,13 @@ Examples:
135135
# Scan and generate SBOM(spdx) report
136136
trivy scan2html image --format spdx alpine:3.15 --scan2html-flags --output interactive_report.html
137137

138-
# Generate a report from multiple json scan results - experimental
138+
# Generate a report from multiple json scan results
139139
trivy scan2html generate --scan2html-flags --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
140140

141-
# Generate report with EPSS scores from multiple scan results - experimental
141+
# Generate report with EPSS scores from multiple scan results
142142
trivy scan2html generate --scan2html-flags --with-epss --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
143143

144+
# Generate report with Exploitability from multiple scan results - experimental
145+
trivy scan2html generate --scan2html-flags --with-exploits --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
146+
144147
```

interactive_report.html

Lines changed: 420 additions & 0 deletions
Large diffs are not rendered by default.

internal/common/flags.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ package common
22

33
import (
44
"fmt"
5-
"scan2html/internal/logger"
65
"os"
6+
"scan2html/internal/logger"
77
)
88

99
var AvailableFlags = map[string]bool{
1010
// name : is boolean
1111
"--scan2html-flags": true,
1212
"--output": false,
1313
"--with-epss": true,
14+
"--with-exploits": true,
1415
"--report-title": false,
1516
"generate": true,
1617
"--from": false,
@@ -103,6 +104,7 @@ Flags:
103104
--output Report name
104105
--report-title Report title
105106
--with-epss Include EPSS data
107+
--with-exploits Include Exploits
106108
--from Comma separated json scan result files
107109
108110
Examples:
@@ -127,10 +129,14 @@ Examples:
127129
# Scan and generate SBOM(spdx) report
128130
trivy scan2html image --format spdx alpine:3.15 --scan2html-flags --output interactive_report.html
129131
130-
# Generate a report from multiple json scan results - experimental
132+
# Generate a report from multiple json scan results
131133
trivy scan2html generate --scan2html-flags --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
132134
133-
# Generate report with EPSS scores from multiple scan results - experimental
135+
# Generate report with EPSS scores from multiple scan results
134136
trivy scan2html generate --scan2html-flags --with-epss --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
137+
138+
# Generate report with Exploitability from multiple scan results - experimental
139+
trivy scan2html generate --scan2html-flags --with-exploits --output interactive_report.html --from vulnerabilities.json,misconfigs.json,secrets.json
140+
135141
`, version)
136142
}

internal/exploit/downloader.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package exploit
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"scan2html/internal/epss"
8+
"scan2html/internal/logger"
9+
)
10+
11+
// PrepareExploitData downloads the CISA dataset, saves it as a temporary file,
12+
func PrepareExploitData() (string, error) {
13+
const (
14+
cisaURL = "https://www.cisa.gov/sites/default/files/feeds"
15+
cisaFileName = "known_exploited_vulnerabilities.json"
16+
)
17+
18+
// Define paths
19+
tmpCisaFilepath := filepath.Join(os.TempDir(), cisaFileName)
20+
cisaDownloadUrl := fmt.Sprintf("%s/%s", cisaURL, cisaFileName)
21+
logger.Logger.Infof("Downloading Exploit data from: %s\n", cisaDownloadUrl)
22+
23+
if err := epss.DownloadFile(cisaDownloadUrl, tmpCisaFilepath); err != nil {
24+
return "", err
25+
}
26+
27+
stats, _ := os.Stat(tmpCisaFilepath)
28+
logger.Logger.Infof("Exploit data downloaded successfully to %s with size of: %d bytes\n", tmpCisaFilepath, stats.Size())
29+
30+
return tmpCisaFilepath, nil
31+
}

internal/report/generator.go

Lines changed: 93 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"log"
88
"os"
99
"path/filepath"
10+
"runtime"
1011
"scan2html/internal/common"
1112
"scan2html/internal/epss"
13+
"scan2html/internal/exploit"
1214
"scan2html/internal/logger"
1315
"strings"
1416
"time"
@@ -25,10 +27,12 @@ func GenerateHtmlReport(pluginFlags common.Flags, version string) error {
2527

2628
reportName := pluginFlags["--output"]
2729
_, withEpss := pluginFlags["--with-epss"]
30+
_, withExploits := pluginFlags["--with-exploits"]
2831
reportTitle := pluginFlags["--report-title"]
2932
// Log input parameters for clarity
3033
logger.Logger.Infof("Base Directory: %s\n", baseDir)
3134
logger.Logger.Infof("With EPSS: %t\n", withEpss)
35+
logger.Logger.Infof("With Exploits: %t\n", withExploits)
3236
logger.Logger.Infof("Report Title: %s\n", reportTitle)
3337
logger.Logger.Infof("Report Name: %s\n", reportName)
3438

@@ -43,7 +47,7 @@ func GenerateHtmlReport(pluginFlags common.Flags, version string) error {
4347

4448
err = replaceTextByText(reportName, "TEMP_APP_VERSION", version)
4549
if err != nil {
46-
return fmt.Errorf("failed to replace report title in %s: %v", reportName, err)
50+
return fmt.Errorf("failed to replace app version in %s: %v", reportName, err)
4751
}
4852

4953
// Replace placeholders with actual content in the report file
@@ -57,26 +61,67 @@ func GenerateHtmlReport(pluginFlags common.Flags, version string) error {
5761
}
5862

5963
// Handle EPSS data if enabled
64+
// replaceTextByFile "$report_name" "\"TEMP_EPSS_DATA\"" "$epss_data"
65+
// Schedule deletion of the EPSS data file upon function exit
66+
shouldReturn, returnValue := handleEPSS(withEpss, reportName)
67+
if shouldReturn {
68+
return returnValue
69+
}
70+
71+
shouldReturn, returnValue = handleExploit(withExploits, reportName)
72+
if shouldReturn {
73+
return returnValue
74+
}
75+
76+
logger.Logger.Infof("%s has been created successfully!\n", reportName)
77+
return nil
78+
}
79+
80+
func handleEPSS(withEpss bool, reportName string) (bool, error) {
6081
if withEpss {
6182
logger.Logger.Infoln("EPSS enabled!")
6283
var epssDataFile, err = epss.PrepareEpssData()
6384
if err != nil {
64-
return fmt.Errorf("failed to prepare EPSS data: %v", err)
85+
return true, fmt.Errorf("failed to prepare EPSS data: %v", err)
6586
}
6687

67-
// replaceTextByFile "$report_name" "\"TEMP_EPSS_DATA\"" "$epss_data"
6888
if err := replaceTextByFile(reportName, "\"TEMP_EPSS_DATA\"", epssDataFile); err != nil {
69-
return fmt.Errorf("failed to replace EPSS data in %s: %v", reportName, err)
89+
return true, fmt.Errorf("failed to replace EPSS data in %s: %v", reportName, err)
7090
}
7191

7292
logger.Logger.Infoln("EPSS data imported!")
7393

74-
// Schedule deletion of the EPSS data file upon function exit
7594
defer os.Remove(epssDataFile)
7695
}
96+
return false, nil
97+
}
7798

78-
logger.Logger.Infof("%s has been created successfully!\n", reportName)
79-
return nil
99+
func handleExploit(withExploits bool, reportName string) (bool, error) {
100+
if withExploits {
101+
logger.Logger.Infoln("Exploits enabled!")
102+
var exploitDataFile, err = exploit.PrepareExploitData()
103+
if err != nil {
104+
return true, fmt.Errorf("failed to prepare Exploits data: %v", err)
105+
}
106+
107+
if err := replaceTextByFile(reportName, "{TEMP_EXPLOITS:0}", exploitDataFile); err != nil {
108+
return true, fmt.Errorf("failed to replace Exploits data in %s: %v", reportName, err)
109+
}
110+
111+
logger.Logger.Infoln("Exploits data imported!")
112+
113+
defer os.Remove(exploitDataFile)
114+
}
115+
return false, nil
116+
}
117+
118+
// replaceTextByFile replaces occurrences of search_text in the input file with content from replace_file.
119+
func replaceTextByFile(inputFile, searchText, replaceFile string) error {
120+
replaceContent, err := os.ReadFile(replaceFile)
121+
if err != nil {
122+
return fmt.Errorf("could not read file %s: %v", replaceFile, err)
123+
}
124+
return replaceTextByText(inputFile, searchText, string(replaceContent))
80125
}
81126

82127
// replaceTextByText replaces occurrences of search_text in the input file with replace_content.
@@ -87,11 +132,11 @@ func replaceTextByText(inputFile, searchText, replaceContent string) error {
87132
}
88133
defer file.Close()
89134

90-
tempFile, err := os.CreateTemp("", "modified_")
135+
timestamp := time.Now().Format("2006_01_02_15_04_05_06")
136+
tempFile, err := os.CreateTemp("", fmt.Sprintf("modified_%s", timestamp))
91137
if err != nil {
92138
return fmt.Errorf("could not create temp file: %v", err)
93139
}
94-
defer tempFile.Close()
95140

96141
reader := bufio.NewReader(file)
97142
writer := bufio.NewWriter(tempFile)
@@ -119,45 +164,52 @@ func replaceTextByText(inputFile, searchText, replaceContent string) error {
119164
return fmt.Errorf("error writing to temp file: %v", err)
120165
}
121166

122-
123167
return copyAndRemove(tempFile.Name(), inputFile)
124168
}
125169

126170
func copyAndRemove(src, dst string) error {
127-
// Open the source file
128-
sourceFile, err := os.Open(src)
129-
if err != nil {
130-
return err
131-
}
132-
defer sourceFile.Close()
133-
134-
// Create the destination file
135-
destFile, err := os.Create(dst)
136-
if err != nil {
137-
return err
138-
}
139-
defer destFile.Close()
140-
141-
// Copy the contents
142-
if _, err := io.Copy(destFile, sourceFile); err != nil {
143-
return err
144-
}
145-
146-
// Close files before removal
147-
sourceFile.Close()
148-
destFile.Close()
149-
150-
// Remove the source file
151-
return os.Remove(src)
152-
}
171+
// Open the source file
172+
sourceFile, err := os.Open(src)
173+
if err != nil {
174+
return fmt.Errorf("failed to open source file %s: %v", src, err)
175+
}
176+
defer sourceFile.Close()
153177

154-
// replaceTextByFile replaces occurrences of search_text in the input file with content from replace_file.
155-
func replaceTextByFile(inputFile, searchText, replaceFile string) error {
156-
replaceContent, err := os.ReadFile(replaceFile)
178+
// Create the destination file
179+
destFile, err := os.Create(dst)
157180
if err != nil {
158-
return fmt.Errorf("could not read file %s: %v", replaceFile, err)
181+
return fmt.Errorf("failed to create destination file %s: %v", dst, err)
159182
}
160-
return replaceTextByText(inputFile, searchText, string(replaceContent))
183+
defer destFile.Close()
184+
185+
// Copy the contents
186+
if _, err := io.Copy(destFile, sourceFile); err != nil {
187+
return fmt.Errorf("failed to copy contents from %s to %s: %v", src, dst, err)
188+
}
189+
190+
// Ensure all data is written to the destination file
191+
if err := destFile.Sync(); err != nil {
192+
return fmt.Errorf("failed to sync destination file %s: %v", dst, err)
193+
}
194+
195+
// Close the destination file
196+
if err := destFile.Close(); err != nil {
197+
return fmt.Errorf("failed to close destination file %s: %v", dst, err)
198+
}
199+
200+
// Close the source file
201+
if err := sourceFile.Close(); err != nil {
202+
return fmt.Errorf("failed to close source file %s: %v", src, err)
203+
}
204+
205+
// Check if the operating system is Windows
206+
if runtime.GOOS == "windows" {
207+
// Remove the source file is failing on windows
208+
return nil
209+
}
210+
211+
defer os.Remove(src)
212+
return nil
161213
}
162214

163215
// generateReportName creates a unique report name based on timestamp if the file already exists.

0 commit comments

Comments
 (0)