Skip to content

Commit 381ef89

Browse files
refactor AppScanReader to use provided CWE (#93)
1 parent 64f72b2 commit 381ef89

File tree

7 files changed

+90
-251
lines changed

7 files changed

+90
-251
lines changed

plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/HCLAppScanStandardReader.java

Lines changed: 74 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,48 @@
1717
*/
1818
package org.owasp.benchmarkutils.score.parsers;
1919

20-
import java.io.StringReader;
20+
import static java.lang.Integer.parseInt;
21+
2122
import java.util.ArrayList;
2223
import java.util.List;
23-
import javax.xml.parsers.DocumentBuilder;
24-
import javax.xml.parsers.DocumentBuilderFactory;
2524
import org.owasp.benchmarkutils.score.BenchmarkScore;
2625
import org.owasp.benchmarkutils.score.CweNumber;
2726
import org.owasp.benchmarkutils.score.ResultFile;
2827
import org.owasp.benchmarkutils.score.TestCaseResult;
2928
import org.owasp.benchmarkutils.score.TestSuiteResults;
30-
import org.w3c.dom.Document;
31-
import org.w3c.dom.NamedNodeMap;
3229
import org.w3c.dom.Node;
33-
import org.xml.sax.InputSource;
3430

35-
// This is the new HCL AppScan DAST reader, where they generate ".xml" files. HCL calls this AppScan
36-
// Standard.
37-
// The 'old' reader is AppScanDynamicReader, which supports the previous .xml format from IBM.
31+
/**
32+
* This is the new HCL AppScan DAST reader, where they generate ".xml" files. HCL calls this AppScan
33+
* Standard. The 'old' reader is AppScanDynamicReader, which supports the previous .xml format from
34+
* IBM.
35+
*/
3836
public class HCLAppScanStandardReader extends Reader {
3937

38+
private final List<String> ignoreList = new ArrayList<>();
39+
40+
public HCLAppScanStandardReader() {
41+
ignoreList.add("attContentSecurityPolicyObjectSrc");
42+
ignoreList.add("attContentSecurityPolicyScriptSrc");
43+
ignoreList.add("attCachedSSL");
44+
ignoreList.add("attJSCookie");
45+
ignoreList.add("attLinkInjection");
46+
ignoreList.add("attUndefinedState");
47+
ignoreList.add("bodyParamsInQuery");
48+
ignoreList.add("ContentSecurityPolicy");
49+
ignoreList.add("ContentTypeOptions");
50+
ignoreList.add("GD_EmailAddress");
51+
ignoreList.add("GETParamOverSSL");
52+
ignoreList.add("GV_SQLErr");
53+
ignoreList.add("HSTS");
54+
ignoreList.add("MHTMLXSS");
55+
ignoreList.add("OpenSource");
56+
ignoreList.add("phishingInFrames");
57+
ignoreList.add("OldTLS");
58+
ignoreList.add("ShellShockCheck");
59+
ignoreList.add("SriSupport");
60+
}
61+
4062
@Override
4163
public boolean canRead(ResultFile resultFile) {
4264
return resultFile.filename().endsWith(".xml")
@@ -47,245 +69,75 @@ public boolean canRead(ResultFile resultFile) {
4769

4870
@Override
4971
public TestSuiteResults parse(ResultFile resultFile) throws Exception {
50-
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
51-
// Prevent XXE
52-
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
53-
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
54-
InputSource is = new InputSource(new StringReader(resultFile.content()));
55-
Document doc = docBuilder.parse(is);
56-
57-
Node root = doc.getDocumentElement();
58-
Node scanInfo = getNamedChild("scan-information", root);
59-
60-
Node scanConfiguration = getNamedChild("scan-configuration", root);
61-
String startingUrl = getNamedChild("starting-url", scanConfiguration).getTextContent();
72+
Node root = resultFile.xml().getDocumentElement();
6273

6374
TestSuiteResults tr =
6475
new TestSuiteResults("HCL AppScan Standard", true, TestSuiteResults.ToolType.DAST);
6576

66-
// version is usually like 9.3.0 but sometimes like 9.3.0 iFix005. We trim off the part
67-
// after the space char.
68-
Node version = getNamedChild("product-version", scanInfo);
69-
// System.out.println("Product version is: " + version.getTextContent());
70-
if (version != null) {
71-
tr.setToolVersion(version.getTextContent().split(" ")[0]);
72-
}
73-
74-
Node allIssueVariants = getNamedChild("issue-group", root);
75-
List<Node> variants = getNamedChildren("item", allIssueVariants);
76-
77-
List<String> testCaseElementsFromVariants = new ArrayList<>();
78-
79-
80-
if (variants.isEmpty()) {
81-
// Handle non-variant issue types , Older xml format as in 9.x release versions and
82-
// before
83-
// First get the type of vuln, and if we don't care about that type, move on
84-
Node allIssues = getNamedChild("url-group", root);
85-
List<Node> vulnerabilities = getNamedChildren("item", allIssues);
86-
for (Node vulnerability : vulnerabilities) {
87-
String issueType = getNamedChild("issue-type", vulnerability).getTextContent();
88-
89-
String url = getNamedChild("name", vulnerability).getTextContent();
90-
91-
TestCaseResult tcr = TestCaseLookup(issueType, url);
92-
if (tcr != null) tr.put(tcr);
93-
}
94-
}
95-
96-
else {
97-
// Handle issues which are Variants, new xml format after 10.x release
98-
for (Node variant : variants) {
99-
String variantIssueType = getNamedChild("issue-type", variant).getTextContent().trim();
100-
// System.out.println("Variant Url Ref ID: " + variantUrlRefId);
101-
102-
// Add the record only if the issue type matches for the relevant variants
103-
Node variantNodes = getNamedChild("variant-group", variant);
104-
List<Node> variantNodeChildren = getNamedChildren("item", variantNodes);
105-
for (Node variantNodeChild : variantNodeChildren) {
106-
String httpTraffic =
107-
getNamedChild("test-http-traffic", variantNodeChild).getTextContent();
108-
String[] variantUrl = httpTraffic.split(" ");
109-
110-
String benchMarkTestCase = variantUrl[1].trim();
111-
112-
if (benchMarkTestCase.contains("BenchmarkTest")) {
113-
String[] urlElements = benchMarkTestCase.split("/");
114-
115-
String testAreaUrl =
116-
startingUrl
117-
+ urlElements[urlElements.length - 2]
118-
+ "/"
119-
+ urlElements[urlElements.length - 1];
120-
String testArea = testAreaUrl.split("\\?")[0]; // .split strips off the -##
121-
122-
if (testArea.contains("BenchmarkTest"))
123-
testCaseElementsFromVariants.add(testArea);
124-
TestCaseResult tcr = TestCaseLookup(variantIssueType, testArea);
125-
if (tcr != null)
126-
tr.put(tcr);
127-
}
128-
}
129-
}
130-
}
131-
return tr;
132-
}
133-
134-
/// Issues which are not variants
135-
private TestCaseResult TestCaseLookup(String issueType, String url) {
136-
String[] urlElements = url.split("/");
137-
String testArea =
138-
urlElements[urlElements.length - 2].split("-")[0]; // .split strips off the -##
139-
140-
int vtype = cweLookup(issueType, testArea);
141-
142-
// Then get the filename containing the vuln. And if not in a test case, skip it.
143-
// Parse out test number from:
144-
// https://localhost:port/benchmark/testarea-##/BenchmarkTest02603
145-
int startOfTestCase = url.lastIndexOf("/") + 1;
146-
String testcase = url.substring(startOfTestCase, url.length());
147-
// if test case has extension (e.g., BenchmarkTestCase#####.html), strip it off.
148-
testcase = testcase.split("\\.")[0];
149-
// System.out.println("Candidate test case is: " + testcase);
150-
if (testcase.startsWith(BenchmarkScore.TESTCASENAME)) {
151-
// if (tn == -1) System.out.println("Found vuln outside of test case of type: " +
152-
// issueType);
153-
154-
// Add the vuln found in a test case to the results for this tool
155-
TestCaseResult tcr = new TestCaseResult();
156-
tcr.setNumber(testNumber(testcase));
157-
tcr.setCategory(issueType); // TODO: Is this right?
158-
tcr.setCWE(vtype);
159-
tcr.setEvidence(issueType);
160-
return tcr;
161-
}
162-
return null;
163-
}
164-
165-
// Fetch Issues listed as variants, to cater to post 10.x release xml format
166-
private List<String> variantLookup(
167-
String issueType, String itemID, String startingUrl, List<Node> variants) {
168-
List<String> testCaseElementsFromVariants = new ArrayList<>();
77+
setTime(root, tr);
78+
setVersion(root, tr);
16979

170-
// System.out.println("Variant Lookup Item ID: " + itemID);
80+
List<Node> variants = getNamedChildren("item", getNamedChild("issue-group", root));
17181

17282
for (Node variant : variants) {
173-
String variantUrlRefId = getNamedChild("url", variant).getTextContent().trim();
83+
int xmlCwe = parseInt(getNamedChild("cwe", variant).getTextContent());
17484
String variantIssueType = getNamedChild("issue-type", variant).getTextContent().trim();
175-
// System.out.println("Variant Url Ref ID: " + variantUrlRefId);
176-
177-
// Add the record only if the issue type matches for the relevant variants
178-
if (issueType.equals(variantIssueType) && itemID.equals(variantUrlRefId)) {
179-
Node variantNodes = getNamedChild("variant-group", variant);
180-
List<Node> variantNodeChildren = getNamedChildren("item", variantNodes);
181-
for (Node variantNodeChild : variantNodeChildren) {
182-
String httpTraffic =
183-
getNamedChild("test-http-traffic", variantNodeChild).getTextContent();
184-
String[] variantUrl = httpTraffic.split(" ");
185-
186-
String benchMarkTestCase = variantUrl[1].trim();
18785

188-
if (benchMarkTestCase.contains("BenchmarkTest")) {
189-
String[] urlElements = benchMarkTestCase.split("/");
86+
getNamedChildren("item", getNamedChild("variant-group", variant)).stream()
87+
.map(node -> extractFilenameWithoutEnding(extractUrlFrom(node)))
88+
.filter(filename -> filename.startsWith(BenchmarkScore.TESTCASENAME))
89+
.forEach(
90+
filename -> {
91+
TestCaseResult tcr = new TestCaseResult();
19092

191-
String testAreaUrl =
192-
startingUrl
193-
+ urlElements[urlElements.length - 2]
194-
+ "/"
195-
+ urlElements[urlElements.length - 1];
196-
String testArea = testAreaUrl.split("\\?")[0]; // .split strips off the -##
93+
tcr.setNumber(testNumber(filename));
94+
tcr.setCategory(variantIssueType); // TODO: Is this right?
95+
tcr.setCWE(cweLookup(variantIssueType, xmlCwe));
96+
tcr.setEvidence(variantIssueType);
19797

198-
if (testArea.contains("BenchmarkTest"))
199-
testCaseElementsFromVariants.add(testArea);
200-
}
201-
}
202-
}
98+
tr.put(tcr);
99+
});
203100
}
204101

205-
return testCaseElementsFromVariants;
102+
return tr;
206103
}
207104

208-
private int cweLookup(String vtype, String testArea) {
209-
int cwe = cweLookup(vtype); // Do the standard CWE lookup
210-
211-
// Then map some to other CWEs based on the test area being processed.
212-
if ("xpathi".equals(testArea) && cwe == 89) cwe = 643; // CWE for XPath injection
213-
if ("ldapi".equals(testArea) && cwe == 89) cwe = 90; // CWE for LDAP injection
214-
215-
return cwe;
105+
private void setTime(Node root, TestSuiteResults tr) {
106+
tr.setTime(
107+
getNamedChild("scan-Duration", getNamedChild("scan-summary", root))
108+
.getTextContent());
216109
}
217110

218-
private int cweLookup(String vtype) {
219-
switch (vtype) {
220-
case "attDirectoryFound":
221-
case "attDirOptions":
222-
case "attFileUnix":
223-
return CweNumber.PATH_TRAVERSAL;
224-
225-
case "attApplicationRemoteCodeExecutionAdns":
226-
case "attCodeInjectionInSystemCall":
227-
case "attCommandInjectionAdns":
228-
case "attCommandInjectionUnixTws":
229-
case "attFileParamPipe":
230-
return CweNumber.COMMAND_INJECTION;
231-
232-
case "attCrossSiteScripting":
233-
return CweNumber.XSS;
234-
235-
case "attBlindSqlInjectionStrings":
236-
case "attSqlInjectionChecks":
237-
return CweNumber.SQL_INJECTION;
238-
239-
case "attLDAPInjection":
240-
case "attLDAPInjection2":
241-
case "attBlindLDAPInjection":
242-
return CweNumber.LDAP_INJECTION;
111+
private static String extractUrlFrom(Node variantNodeChild) {
112+
String[] variantUrl =
113+
getNamedChild("test-http-traffic", variantNodeChild).getTextContent().split(" ");
243114

244-
case "SHA1CipherSuites":
245-
return CweNumber.WEAK_HASH_ALGO; // Better if set to 327?
115+
return variantUrl[1].trim();
116+
}
246117

247-
case "passParamGET":
248-
return CweNumber.UNPROTECTED_CREDENTIALS_TRANSPORT;
118+
/*
119+
* Version is usually like 9.3.0 but sometimes like 9.3.0 iFix005. We trim off the part after the space char.
120+
*/
121+
private static void setVersion(Node root, TestSuiteResults tr) {
122+
Node version = getNamedChild("product-version", getNamedChild("scan-information", root));
249123

250-
case "attRespCookieNotSecureSSL":
251-
return CweNumber.INSECURE_COOKIE;
124+
if (version != null) {
125+
tr.setToolVersion(version.getTextContent().split(" ")[0]);
126+
}
127+
}
252128

129+
private int cweLookup(String vtype, int xmlCwe) {
130+
switch (vtype) {
253131
case "attXPathInjection":
254132
case "attBlindXpathInjectionSingleQuote":
255133
case "attBlindXPathInjection":
256134
return CweNumber.XPATH_INJECTION;
135+
}
257136

258-
// These don't map to anything we care about
259-
case "attContentSecurityPolicyObjectSrc":
260-
case "attContentSecurityPolicyScriptSrc":
261-
case "attCachedSSL":
262-
case "attJSCookie":
263-
// case "attLinkInjection" : return 00;
264-
case "attUndefinedState":
265-
case "bodyParamsInQuery":
266-
case "ContentSecurityPolicy":
267-
case "ContentTypeOptions":
268-
case "GD_EmailAddress":
269-
case "GETParamOverSSL":
270-
case "GV_SQLErr":
271-
// case "HSTS" : return 00;
272-
273-
// Microsoft MHTML XSS - Giving AppScan XSS 'credit' for this introduces ~2.4% False
274-
// Positives and no real ones so I (shivababuh) disabled it instead
275-
case "MHTMLXSS":
276-
// case "OpenSource" : return 00; // Known vuln in open source lib.
277-
// case "phishingInFrames" : return 00;
278-
case "OldTLS":
279-
case "ShellShockCheck":
280-
case "SriSupport":
281-
// case "SSL_CertWithBadCN" : return 00;
282-
// case "XSSProtectionHeader" : return 00;
283-
return CweNumber.DONTCARE;
284-
285-
default:
286-
System.out.println(
287-
"WARNING: HCL AppScan Standard-Unrecognized finding type: " + vtype);
137+
if (ignoreList.contains(vtype)) {
138+
return CweNumber.DONTCARE;
288139
}
289-
return 0;
140+
141+
return xmlCwe;
290142
}
291143
}

plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/MendReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public TestSuiteResults parse(ResultFile resultFile) throws Exception {
4747
for (Report.EngineResults.Result.Vulnerability vulnerability :
4848
result.vulnerabilities) {
4949
try {
50-
String testfile = extractFilename(vulnerability.filename);
50+
String testfile = extractFilenameWithoutEnding(vulnerability.filename);
5151

5252
if (testfile.startsWith(BenchmarkScore.TESTCASENAME)) {
5353
TestCaseResult tcr = new TestCaseResult();

plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/Rapid7Reader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public TestSuiteResults parse(ResultFile resultFile) throws Exception {
4747

4848
for (Report.Vulnerability vulnerability : report.vulnerabilities) {
4949
try {
50-
String testfile = extractFilename(vulnerability.url);
50+
String testfile = extractFilenameWithoutEnding(vulnerability.url);
5151

5252
if (testfile.startsWith(BenchmarkScore.TESTCASENAME)) {
5353
TestCaseResult tcr = new TestCaseResult();

plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/Reader.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,15 @@ public static int testNumber(String path, String testCaseName) {
274274
}
275275
}
276276

277-
public static String extractFilename(String path) {
277+
public static String extractFilenameWithoutEnding(String path) {
278278
try {
279-
path = removeUrlPart(path);
279+
String name = new File(fixWindowsPath(removeUrlPart(path))).getName();
280280

281-
return new File(fixWindowsPath(path)).getName();
281+
if (name.contains(".")) {
282+
return name.substring(0, name.lastIndexOf("."));
283+
} else {
284+
return name;
285+
}
282286
} catch (Throwable t) {
283287
return "";
284288
}

plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/sarif/SarifReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public String toolName(ResultFile resultFile) {
236236
private TestCaseResult testCaseResultFor(JSONObject result, Map<String, Integer> mappings) {
237237
TestCaseResult tcr = new TestCaseResult();
238238

239-
String className = extractFilename(resultUri(result));
239+
String className = extractFilenameWithoutEnding(resultUri(result));
240240

241241
if (!className.startsWith(BenchmarkScore.TESTCASENAME)) {
242242
return null;

0 commit comments

Comments
 (0)