Skip to content

Commit 09ef3f5

Browse files
author
Ilona Shishov
committed
chore: add vex generation output schema support and display vex document
Signed-off-by: Ilona Shishov <ishishov@ishishov-thinkpadp1gen7.raanaii.csb>
1 parent d4b38be commit 09ef3f5

16 files changed

+803
-433
lines changed

src/main/java/com/redhat/ecosystemappeng/morpheus/rest/ReportEndpoint.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,10 @@ public Response receive(
228228
"image": {...
229229
}
230230
},
231-
"output": [...
232-
],
231+
"output": {
232+
"analysis": [...],
233+
"vex": null | {...}
234+
},
233235
"info": {...
234236
},
235237
"metadata": {...
@@ -304,8 +306,10 @@ public Response list(
304306
"image": {...
305307
}
306308
},
307-
"output": [...
308-
],
309+
"output": {
310+
"analysis": [...],
311+
"vex": null | {...}
312+
},
309313
"info": {...
310314
},
311315
"metadata": {...

src/main/java/com/redhat/ecosystemappeng/morpheus/service/ReportRepositoryService.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,14 @@ public Report toReport(Document doc) {
106106
var input = doc.get("input", Document.class);
107107
var scan = input.get("scan", Document.class);
108108
var image = input.get("image", Document.class);
109-
var output = doc.getList("output", Document.class);
109+
var output = doc.get("output", Document.class);
110+
var analysis = Objects.nonNull(output) ? output.getList("analysis", Document.class) : null;
110111
var metadata = extractMetadata(doc);
111112
var vulnIds = new HashSet<VulnResult>();
112-
if (Objects.nonNull(output)) {
113-
output.forEach(o -> {
114-
var vulnId = o.getString("vuln_id");
115-
var justification = o.get("justification", Document.class);
113+
if (Objects.nonNull(analysis)) {
114+
analysis.forEach(a -> {
115+
var vulnId = a.getString("vuln_id");
116+
var justification = a.get("justification", Document.class);
116117

117118
vulnIds.add(new VulnResult(vulnId,
118119
new Justification(justification.getString("status"), justification.getString("label"))));
@@ -171,15 +172,15 @@ public void updateWithOutput(List<String> ids, JsonNode report)
171172

172173
Set<String> productIds = getProductId(ids);
173174

174-
List<Document> outputDocs = objectMapper.readValue(report.get("output").toPrettyString(),
175-
new TypeReference<List<Document>>() {
175+
Document outputDoc = objectMapper.readValue(report.get("output").toPrettyString(),
176+
new TypeReference<Document>() {
176177

177178
});
178179
var scan = report.get("input").get("scan").toPrettyString();
179180
var info = report.get("info").toPrettyString();
180181
var updates = Updates.combine(Updates.set("input.scan", Document.parse(scan)),
181182
Updates.set("info", Document.parse(info)),
182-
Updates.set("output", outputDocs),
183+
Updates.set("output", outputDoc),
183184
Updates.unset("error"));
184185
var bulk = ids.stream()
185186
.map(id -> new UpdateOneModel<Document>(Filters.eq(RepositoryConstants.ID_KEY, new ObjectId(id)), updates))
@@ -254,7 +255,7 @@ public List<Report> findByName(String name) {
254255
"completedAt", "input.scan.completed_at",
255256
"submittedAt", "metadata.submitted_at",
256257
"name", "input.scan.id",
257-
"vuln_id", "output.vuln_id");
258+
"vuln_id", "output.analysis.vuln_id");
258259

259260
public PaginatedResult<Report> list(Map<String, String> queryFilter, List<SortField> sortFields,
260261
Pagination pagination) {
@@ -330,14 +331,15 @@ public ProductReportsSummary getProductSummaryData(String productId) {
330331
}
331332
}
332333

333-
Object outputObj = doc.get("output");
334-
if (outputObj instanceof List<?> outputList) {
335-
for (Object output : outputList) {
336-
if (output instanceof org.bson.Document outputDoc) {
337-
String cve = outputDoc.getString("vuln_id");
334+
Document outputDoc = doc.get("output", Document.class);
335+
Object analysisObj = Objects.nonNull(outputDoc) ? outputDoc.get("analysis") : null;
336+
if (analysisObj instanceof List<?> analysisList) {
337+
for (Object analysis : analysisList) {
338+
if (analysis instanceof org.bson.Document analysisDoc) {
339+
String cve = analysisDoc.getString("vuln_id");
338340
if (cve != null && !cve.isEmpty()) {
339341
Set<Justification> justifications = cveSet.computeIfAbsent(cve, k -> new HashSet<>());
340-
Object justificationObj = outputDoc.get("justification");
342+
Object justificationObj = analysisDoc.get("justification");
341343
if (justificationObj instanceof org.bson.Document justificationDoc) {
342344
String status = justificationDoc.getString("status");
343345
String label = justificationDoc.getString("label");

src/main/webui/src/Report.jsx

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ import {
55
Breadcrumb,
66
BreadcrumbItem,
77
Button,
8+
CodeBlock,
9+
CodeBlockCode,
810
Divider,
911
EmptyState,
1012
EmptyStateBody,
13+
ExpandableSection,
1114
Flex,
15+
FlexItem,
1216
Grid,
1317
GridItem,
1418
PageSection,
@@ -42,6 +46,7 @@ export default function Report() {
4246
const [errorReport, setErrorReport] = React.useState({});
4347
const [comments, setComments] = React.useState({});
4448
const [name, setName] = React.useState();
49+
const [vexExpanded, setVexExpanded] = React.useState(false);
4550
const navigate = useNavigate();
4651

4752
React.useEffect(() => {
@@ -56,14 +61,15 @@ export default function Report() {
5661
.catch(e => setErrorReport(e));
5762
}, []);
5863

59-
const onDownload = () => {
64+
const onDownload = (data, filename) => {
6065
const element = document.createElement("a");
61-
const file = new Blob([JSON.stringify(report)], {type: 'application/json'});
66+
const file = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
6267
element.href = URL.createObjectURL(file);
63-
element.download = `${name}.json`;
68+
element.download = filename;
6469
document.body.appendChild(element);
6570
element.click();
66-
}
71+
document.body.removeChild(element); // cleanup
72+
};
6773

6874
const time_meta_fields = [
6975
"submitted_at",
@@ -118,8 +124,8 @@ export default function Report() {
118124
lines.push(`Image: ${report.input.image.name}`);
119125
lines.push('');
120126

121-
if (report.output) {
122-
report.output.forEach(vuln => {
127+
if (report.output?.analysis) {
128+
report.output.analysis.forEach(vuln => {
123129
lines.push(`Vulnerability: ${vuln.vuln_id}`);
124130
lines.push("");
125131
if (vuln.justification?.label) {
@@ -188,7 +194,7 @@ export default function Report() {
188194
}
189195

190196
const image = report.input.image
191-
const output = report.output;
197+
const analysis = report.output?.analysis;
192198
let metadata = [];
193199
let timestamps = [];
194200
if (report.metadata !== undefined) {
@@ -238,7 +244,7 @@ export default function Report() {
238244
</DescriptionListGroup>
239245
</DescriptionList>
240246

241-
{output?.map((vuln, v_idx) => {
247+
{analysis?.map((vuln, v_idx) => {
242248
const uid = getUniqueId();
243249
let userComments = '';
244250
if(comments[vuln.vuln_id] !== undefined) {
@@ -279,7 +285,43 @@ export default function Report() {
279285
</>
280286
)
281287
}
288+
<DescriptionListGroup>
289+
<DescriptionListTerm>VEX</DescriptionListTerm>
290+
<DescriptionListDescription>
291+
{report.output?.vex &&
292+
<Flex columnGap={{ default: 'columnGapSm' }} alignItems={{ default: 'alignItemsFlexStart' }}>
293+
<FlexItem>
294+
<Button
295+
variant="secondary"
296+
onClick={() => onDownload(report.output.vex, `${name}_vex.json`)}
297+
size="sm"
298+
>
299+
Download VEX
300+
</Button>
301+
</FlexItem>
302+
<FlexItem>
303+
<ExpandableSection
304+
toggleText={vexExpanded ? "Hide VEX document" : "Show VEX document"}
305+
onToggle={(_event, isExpanded) => setVexExpanded(isExpanded)}
306+
isExpanded={vexExpanded}
307+
isIndented
308+
isDisabled={!report.output?.vex}
309+
>
310+
{report.output?.vex && (
311+
<CodeBlock>
312+
<CodeBlockCode>
313+
{JSON.stringify(report.output.vex, null, 2)}
314+
</CodeBlockCode>
315+
</CodeBlock>
316+
)}
317+
</ExpandableSection>
318+
</FlexItem>
319+
</Flex>
320+
}
321+
</DescriptionListDescription>
322+
</DescriptionListGroup>
282323
</DescriptionList>
324+
283325
{Array.isArray(vuln.checklist) && vuln.checklist.length > 0 && (
284326
<>
285327
<Content component="h2">Checklist:</Content>
@@ -301,7 +343,7 @@ export default function Report() {
301343
})}
302344
<GridItem>
303345
<Flex columnGap={{ default: 'columnGapSm' }}>
304-
<Button variant="secondary" onClick={onDownload}>Download</Button>
346+
<Button variant="secondary" onClick={() => onDownload(report, `${name}_report.json`)}>Download</Button>
305347
<ConfirmationButton btnVariant="danger"
306348
onConfirm={() => onDelete()}
307349
message={`The report with id: ${name} will be permanently deleted.`}>Delete</ConfirmationButton>

src/test/resources/devservices/reports/1cd6c5da-60c9-4f61-83d0-6260d755b0c4.json

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4547,33 +4547,47 @@
45474547
}
45484548
]
45494549
},
4550-
"output": [
4551-
{
4552-
"vuln_id": "CVE-2024-44337",
4553-
"checklist": [
4554-
{
4555-
"input": "Verify Usage of `github.com/gomarkdown/markdown` Package: Check if the `github.com/gomarkdown/markdown` package is being used within the containerized application, specifically the `parser/block.go` file's paragraph function. This is the vulnerable component that could lead to an infinite loop condition.",
4556-
"response": "Unfortunately, I couldn't find any specific information on how to verify the usage of the `github.com/gomarkdown/markdown` package in a containerized application. However, in general, to verify the usage of a specific package in a containerized application, you can try to analyze the dependencies of the application, check the import statements in the code, or search for specific function or method calls related to the package. Additionally, you can try to query the vector databases containing information about the container image under investigation to check if the package is listed as a dependency.",
4557-
"intermediate_steps": null
4558-
},
4559-
{
4560-
"input": "Assess Input Handling for Markdown Parsing: Evaluate how the application handles user input for Markdown parsing. Since the vulnerability involves a specially crafted input causing an infinite loop, assess whether the application properly sanitizes and validates user input to prevent such crafted inputs from being processed.",
4561-
"response": "Based on the available information, it appears that the application does not properly sanitize and validate user input for Markdown parsing, making it vulnerable to specially crafted inputs that can cause an infinite loop. The CVE-2021-41277 vulnerability is related to this issue, and the application's input handling mechanisms do not meet the necessary security standards to prevent such attacks. Therefore, the application is likely vulnerable to this type of attack, and further investigation is needed to determine the severity of the vulnerability and potential mitigation strategies.",
4562-
"intermediate_steps": null
4563-
},
4564-
{
4565-
"input": "Review Error Handling for Infinite Loop Conditions: Check how the application handles errors related to infinite loop conditions. Given that the exploit causes the program to hang and consume resources indefinitely, assess whether the application has mechanisms in place to detect and mitigate such conditions, preventing a denial of service (DoS) scenario.",
4566-
"response": "The application's error handling for infinite loop conditions is unclear due to the lack of specific information on the CVE and its impact on the container image. However, in general, a well-designed application should have mechanisms in place to detect and mitigate infinite loop conditions, such as timeouts, resource limits, and monitoring tools to prevent a denial of service (DoS) scenario. It is recommended to review the container image's code and configuration to identify any built-in safeguards and consider implementing additional measures to prevent infinite loop conditions.",
4567-
"intermediate_steps": null
4568-
}
4569-
],
4570-
"summary": "Based on the provided Checklist and Findings, the CVE is exploitable. Specifically, Checklist Item 2: Assess Input Handling for Markdown Parsing, reveals that the application does not properly sanitize and validate user input for Markdown parsing, making it vulnerable to specially crafted inputs that can cause an infinite loop. This definitive answer indicates that the CVE is exploitable, as the application's input handling mechanisms do not meet the necessary security standards to prevent such attacks.",
4571-
"justification": {
4572-
"label": "vulnerable",
4573-
"reason": "The analysis concludes that the CVE is exploitable due to the application's failure to properly sanitize and validate user input for Markdown parsing, making it vulnerable to specially crafted inputs that can cause an infinite loop.",
4574-
"status": "TRUE"
4575-
},
4576-
"intel_score": 25
4577-
}
4578-
]
4550+
"output": {
4551+
"analysis": [
4552+
{
4553+
"vuln_id": "CVE-2024-44337",
4554+
"checklist": [
4555+
{
4556+
"input": "Is the `paragraph` function from the `github.com/gomarkdown/markdown` package called in the codebase?",
4557+
"response": "No, the `paragraph` function from the `github.com/gomarkdown/markdown` package is not called in the codebase.\n\nThe search results show that the `github.com/gomarkdown/markdown` package is imported in the codebase, but they do not provide conclusive evidence that the `paragraph` function is called. The term \"paragraph\" is used in the codebase, but it is not related to the `github.com/gomarkdown/markdown` package.",
4558+
"intermediate_steps": null
4559+
},
4560+
{
4561+
"input": "Does the application process untrusted Markdown input that could be used to trigger an infinite loop in the `paragraph` function?",
4562+
"response": "The application does process untrusted Markdown input, and there is a potential vulnerability in the `paragraph` function that could be used to trigger an infinite loop. However, the exact nature of the vulnerability and its exploitability depend on the specific implementation and usage of the `paragraph` function in the application.",
4563+
"intermediate_steps": null
4564+
},
4565+
{
4566+
"input": "Are there any input validation or sanitization mechanisms in place to prevent malicious Markdown input from reaching the `paragraph` function?",
4567+
"response": "There are no input validation or sanitization mechanisms in place to prevent malicious Markdown input from reaching the `paragraph` function. The search results suggest that there are input validation and sanitization mechanisms in place, but they appear to be related to Kubernetes API conventions, logging, and text processing, and do not directly apply to the `paragraph` function. Specifically, the `paragraph` function is not mentioned in the search results, and there is no clear connection between the function and the input validation and sanitization mechanisms found in the search results. Therefore, it is likely that the `paragraph` function is vulnerable to malicious Markdown input.",
4568+
"intermediate_steps": null
4569+
},
4570+
{
4571+
"input": "Can an attacker remotely provide Markdown input to the application, potentially allowing them to exploit the vulnerability?",
4572+
"response": "No, an attacker cannot remotely provide Markdown input to the application, potentially allowing them to exploit the vulnerability. The application uses a Markdown parser to parse Markdown documents, but the input is not user-provided and is not coming from a remote source. The `mdtoc` tool is a command-line utility that generates a table of contents for a markdown file, and it can be used in various ways, including as a console application or incorporated into markdown files directly. However, the tool does not allow remote input, and the application does not provide a way for an attacker to provide remote input.",
4573+
"intermediate_steps": null
4574+
},
4575+
{
4576+
"input": "Are there any resource limits or timeout mechanisms in place to prevent the infinite loop from causing a denial of service condition?",
4577+
"response": "There are some resource limits and timeout mechanisms in place in the container source code, such as CPU, memory, and storage limits, as well as timeout and rate limit mechanisms. However, upon closer inspection, it appears that these mechanisms are not directly related to preventing infinite loops from causing denial of service conditions. The only mention of an infinite loop is in the vendor/golang.org/x/net/html/parse.go file, where it is mentioned as a possible issue when mixing foreign content and templates, but this is not directly related to denial of service prevention. Therefore, it is unclear if there are any specific mechanisms in place to prevent infinite loops from causing denial of service conditions.",
4578+
"intermediate_steps": null
4579+
}
4580+
],
4581+
"summary": "The CVE is not exploitable in this application. Investigation confirmed that although the `github.com/gomarkdown/markdown` package is imported in the codebase, the `paragraph` function is not called. Additionally, while the application does process untrusted Markdown input, which could potentially be used to trigger an infinite loop in the `paragraph` function, the input is not user-provided and does not come from a remote source, eliminating the attack vector.",
4582+
"justification": {
4583+
"label": "code_not_reachable",
4584+
"reason": "The `github.com/gomarkdown/markdown` package is imported but the vulnerable `paragraph` function is not called, and the application's processing of untrusted Markdown input does not come from a user-provided or remote source, eliminating the attack vector.",
4585+
"status": "FALSE"
4586+
},
4587+
"intel_score": 92,
4588+
"cvss": null
4589+
}
4590+
],
4591+
"vex": null
4592+
}
45794593
}

0 commit comments

Comments
 (0)