From dd2f1f00a8c0dfd62e96ec43bd5100f0ee7dbc0c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 16 Jan 2024 13:48:13 -0500 Subject: [PATCH 01/28] Merged latest main branch with my old code changes. --- .../CliArgExecutableTestCaseInput.java | 23 + .../CliFileExecutableTestCaseInput.java | 37 ++ .../helpers/ContentFormatEnum.java | 10 + .../helpers/ExecutableTestCaseInput.java | 88 ++++ .../helpers/FileCopyConfig.java | 42 ++ .../helpers/HttpClientConfig.java | 52 ++ .../helpers/HttpGetTestCaseInput.java | 26 + .../helpers/HttpPostTestCaseInput.java | 50 ++ .../helpers/HttpTestCaseInput.java | 231 +++++++++ .../helpers/JerseyTestCase.java | 2 +- .../helpers/RequestVariable.java | 21 +- .../helpers/ServletTestCase.java | 2 +- .../helpers/SpringTestCase.java | 2 +- .../benchmarkutils/helpers/Sqlite3Config.java | 72 +++ .../helpers/StdinExecutableTestCaseInput.java | 42 ++ .../benchmarkutils/helpers/TestCase.java | 120 ++--- .../benchmarkutils/helpers/TestCaseInput.java | 27 + .../benchmarkutils/helpers/TestCaseSetup.java | 9 + .../helpers/TestCaseSetupException.java | 8 + .../benchmarkutils/helpers/TestSuite.java | 32 +- .../owasp/benchmarkutils/helpers/Utils.java | 40 +- .../tools/AbstractTestCaseRequest.java | 144 ++++-- .../tools/BenchmarkCrawler.java | 225 ++++---- .../tools/BenchmarkCrawlerVerification.java | 88 ++-- .../tools/JerseyTestCaseRequest.java | 24 +- .../tools/RegressionTesting.java | 32 +- .../benchmarkutils/tools/ResponseInfo.java | 19 +- .../tools/ServletTestCaseRequest.java | 31 +- .../tools/SimpleFileLogger.java | 17 - .../tools/SpringTestCaseRequest.java | 28 +- .../benchmarkutils/tools/TestCaseRequest.java | 484 ++++++++++++++++++ .../tools/TestCaseVerificationResults.java | 19 +- 32 files changed, 1607 insertions(+), 440 deletions(-) create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java new file mode 100644 index 00000000..f51140b7 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java @@ -0,0 +1,23 @@ +package org.owasp.benchmarkutils.helpers; + +import java.util.Arrays; +import java.util.List; + +public class CliArgExecutableTestCaseInput extends ExecutableTestCaseInput { + public void execute() { + List executeArgs = Arrays.asList(getCommand()); + + // crawlArgs.extend([arg1]) + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + + executeArgs.add(getPayload()); + ProcessBuilder builder = new ProcessBuilder(executeArgs); + final Process process = builder.start(); + int exitValue = process.waitFor(); + System.out.printf("Program terminated with return code: %s%n", exitValue); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java new file mode 100644 index 00000000..039c2378 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java @@ -0,0 +1,37 @@ +package org.owasp.benchmarkutils.helpers; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; + +public class CliFileExecutableTestCaseInput extends ExecutableTestCaseInput { + public void execute() { + List executeArgs = Arrays.asList(getCommand()); + + File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); + + // args_file = 'args_file.txt' + // with open(TEST_SUITE_DIR + args_file, 'w') as f: + // f.write(arg1) + // crawlArgs.extend([args_file]) + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + + executeArgs.add(getPayload()); + executeArgs.add("-f"); + executeArgs.add(argsFile.getPath()); + try (PrintWriter writer = new PrintWriter(new FileWriter(argsFile))) { + writer.print(getPayload()); + } + + ProcessBuilder builder = new ProcessBuilder(executeArgs); + final Process process = builder.start(); + int exitValue = process.waitFor(); + System.out.printf("Program terminated with return code: %s%n", exitValue); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java new file mode 100644 index 00000000..21082ac9 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java @@ -0,0 +1,10 @@ +package org.owasp.benchmarkutils.helpers; + +import javax.xml.bind.annotation.*; + +@XmlType(name = "ContentFormat") +@XmlEnum +public enum ContentFormatEnum { + JSON, + XML; +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java new file mode 100644 index 00000000..dcb72185 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java @@ -0,0 +1,88 @@ +package org.owasp.benchmarkutils.helpers; + +import java.util.Arrays; +import java.util.List; + +public class ExecutableTestCaseInput implements TestCaseInput { + + private String command; + + private String payload; + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + public void execute() { + // Execute the appropriate command string + List executeArgs = Arrays.asList(command); + // if (isSingleApplication) { + // executeArgs = Arrays.asList("benchmark-python.py", "-t", this.getName()); + // } else { + // executeArgs = Arrays.asList("testcode/" + "JulietPyTest" + this.getName() + ".py"); + // } + + if (payloadType == PayloadType.CLI_ARG) { + // crawlArgs.extend([arg1]) + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + + executeArgs.add(payload); + ProcessBuilder builder = new ProcessBuilder(executeArgs); + final Process process = builder.start(); + int exitValue = process.waitFor(); + System.out.printf("Program terminated with return code: %s%n", exitValue); + + } else if (payloadType == PayloadType.CLI_FILE) { + // args_file = 'args_file.txt' + // with open(TEST_SUITE_DIR + args_file, 'w') as f: + // f.write(arg1) + // crawlArgs.extend([args_file]) + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + } else if (payloadType == PayloadType.CLI_STDIN) { + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // #child.interact() + // child.sendline(arg1) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + } else { + // TODO: Throw an exception? + System.out.printf("ERROR: Unrecognized payload type: %s%n", payloadType); + } + } + + public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; + for (RequestVariable arg : getCliArgs()) { + // setSafe() considers whether attack and safe values exist for this parameter before + // setting isSafe true or false. So you don't have to check that here. + arg.setSafe(isSafe); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " [payload=" + payload + "]"; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java new file mode 100644 index 00000000..b8b9cc74 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java @@ -0,0 +1,42 @@ +package org.owasp.benchmarkutils.helpers; + +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; + +public class FileCopyConfig extends TestCaseSetup { + + private String sourceFile; + + private String destinationFile; + + public void setup() throws TestCaseSetupException { + try { + Files.copy(new File(sourceFile), new File(destinationFile)); + } catch (IOException e) { + throw new TestCaseSetupException("Could not setup HttpClientConfig for test case", e); + } + } + + @XmlAttribute(name = "source", required = true) + @NotNull + public String getSourceFile() { + return sourceFile; + } + + public void setSourceFile(String sourceFile) { + this.sourceFile = sourceFile; + } + + @XmlAttribute(name = "destination", required = true) + @NotNull + public String getDestinationFile() { + return destinationFile; + } + + public void setDestinationFile(String destinationFile) { + this.destinationFile = destinationFile; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java new file mode 100644 index 00000000..fab45866 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java @@ -0,0 +1,52 @@ +package org.owasp.benchmarkutils.helpers; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; + +public class HttpClientConfig extends TestCaseSetup { + + private static CloseableHttpClient httpclient; + + // This method taken directly from: + // https://memorynotfound.com/ignore-certificate-errors-apache-httpclient/ + private CloseableHttpClient createAcceptSelfSignedCertificateClient() + throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + + // use the TrustSelfSignedStrategy to allow Self Signed Certificates + SSLContext sslContext = + SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + + // we can optionally disable hostname verification. + // if you don't want to further weaken the security, you don't have to include this. + HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); + + // create an SSL Socket Factory to use the SSLContext with the trust self signed certificate + // strategy and allow all hosts verifier. + SSLConnectionSocketFactory connectionFactory = + new SSLConnectionSocketFactory(sslContext, allowAllHosts); + + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket + // Factory + return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); + } + + public void setup() throws TestCaseSetupException { + if (httpclient == null) { + try { + httpclient = createAcceptSelfSignedCertificateClient(); + } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { + throw new TestCaseSetupException( + "Could not setup HttpClientConfig for test case", e); + } + } + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java new file mode 100644 index 00000000..0f717887 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java @@ -0,0 +1,26 @@ +package org.owasp.benchmarkutils.helpers; + +import org.apache.http.client.methods.HttpRequestBase; + +public class HttpGetTestCaseInput extends HttpTestCaseInput { + void buildQueryString() { + setQueryString(""); + boolean first = true; + for (RequestVariable field : getGetParameters()) { + if (first) { + setQueryString("?"); + first = false; + } else { + setQueryString(getQueryString() + "&"); + } + String name = field.getName(); + String value = field.getValue(); + // System.out.println(query); + setQueryString(getQueryString() + (name + "=" + urlEncode(value))); + } + } + + void buildBodyParameters(HttpRequestBase request) { + // No request body + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java new file mode 100644 index 00000000..08713381 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java @@ -0,0 +1,50 @@ +package org.owasp.benchmarkutils.helpers; + +import java.io.UnsupportedEncodingException; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; + +public class HttpPostTestCaseInput extends HttpTestCaseInput { + @Override + void buildQueryString() { + setQueryString(""); + boolean first = true; + for (RequestVariable field : getGetParameters()) { + if (first) { + setQueryString("?"); + first = false; + } else { + setQueryString(getQueryString() + "&"); + } + String name = field.getName(); + String value = field.getValue(); + // System.out.println(query); + setQueryString(getQueryString() + (name + "=" + urlEncode(value))); + } + } + + @Override + void buildBodyParameters(HttpRequestBase request) { + boolean first = true; + String params = "{"; + for (RequestVariable field : getFormParameters()) { + String name = field.getName(); + String value = field.getValue(); + // System.out.println(name+"="+value); + if (first) { + first = false; + } else { + params = params + ","; + } + params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); + } + params += "}"; + try { + StringEntity paramsEnt = new StringEntity(params); + ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); + } catch (UnsupportedEncodingException e) { + System.out.println("Error encoding URL: " + e.getMessage()); + } + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java new file mode 100644 index 00000000..7e9ff251 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java @@ -0,0 +1,231 @@ +package org.owasp.benchmarkutils.helpers; + +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; + +public abstract class HttpTestCaseInput extends TestCaseInput { + + private String url; + + @XmlElement(required = true) + protected ContentFormatEnum contentFormat; + + private String queryString; + + private List formParameters; + + private List getParameters; + + private List cookies; + + private List headers; + + private static CloseableHttpClient httpclient; + + @XmlAttribute(name = "URL", required = true) + @NotNull + public String getUrl() { + return url; + } + + public String getQueryString() { + return queryString; + } + + @XmlElement(name = "formparam") + @NotNull + public List getFormParameters() { + return formParameters; + } + + @XmlElement(name = "getparam") + @NotNull + public List getGetParameters() { + return getParameters; + } + + @XmlElement(name = "cookie") + @NotNull + public List getCookies() { + return cookies; + } + + @XmlElement(name = "header") + @NotNull + public List getHeaders() { + return headers; + } + + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + public void setFormParameters(List formParameters) { + this.formParameters = formParameters; + } + + public void setGetParameters(List getParameters) { + this.getParameters = getParameters; + } + + public void setCookies(List cookies) { + this.cookies = cookies; + } + + public void setHeaders(List headers) { + this.headers = headers; + } + + public ContentFormatEnum getContentFormat() { + return contentFormat; + } + + public void setContentFormat(ContentFormatEnum contentFormat) { + this.contentFormat = contentFormat; + } + + /** Defines what parameters in the body will be sent. */ + abstract void buildBodyParameters(HttpRequestBase request); + + /** Defines what cookies will be sent. */ + void buildCookies(HttpRequestBase request) { + for (RequestVariable cookie : getCookies()) { + String name = cookie.getName(); + String value = cookie.getValue(); + // Note: URL encoding of a space becomes a +, which is OK for Java, but + // not other languages. So after URLEncoding, replace all + with %20, which is the + // standard URL encoding for a space char. + request.addHeader("Cookie", name + "=" + urlEncode(value).replace("+", "%20")); + } + } + + /** Defines what headers will be sent. */ + void buildHeaders(HttpRequestBase request) { + for (RequestVariable header : getHeaders()) { + String name = header.getName(); + String value = header.getValue(); + // System.out.println("Header:" + name + "=" + value); + request.addHeader(name, value); + } + } + + @SuppressWarnings("deprecation") + String urlEncode(String input) { + return URLEncoder.encode(input); + } + + /** Defines how to construct URL query string. */ + abstract void buildQueryString(); + + public void execute() { + // TODO: Not thread-safe + // TODO: We never close this resource, which is poor form + // TODO: What about other setup tasks, like starting a DB server or app server? + if (httpclient == null) { + httpclient = createAcceptSelfSignedCertificateClient(); + } + + HttpUriRequest request = buildAttackRequest(); + + // Send the next test case request + sendRequest(httpclient, request); + } + + /** + * TODO: Make this class a POJO TestCase and pass it as an arg to another class TestCaseRequest + * that can build an actual HttpUriRequest. + * + * @return + */ + public HttpUriRequest buildRequest() { + buildQueryString(); + HttpRequestBase request = createRequestInstance(fullURL + query); + buildHeaders(request); + buildCookies(request); + buildBodyParameters(request); + return request; + } + + public HttpUriRequest buildAttackRequest() { + setSafe(false); + return buildRequest(); + } + + public HttpUriRequest buildSafeRequest() { + setSafe(true); + return buildRequest(); + } + + public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; + for (RequestVariable header : getHeaders()) { + // setSafe() considers whether attack and safe values exist for this parameter before + // setting isSafe true or false. So you don't have to check that here. + header.setSafe(isSafe); + } + for (RequestVariable cookie : getCookies()) { + cookie.setSafe(isSafe); + } + for (RequestVariable getParam : getGetParams()) { + getParam.setSafe(isSafe); + } + for (RequestVariable formParam : getFormParams()) { + formParam.setSafe(isSafe); + } + } + + // This method taken directly from: + // https://memorynotfound.com/ignore-certificate-errors-apache-httpclient/ + private CloseableHttpClient createAcceptSelfSignedCertificateClient() + throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + + // use the TrustSelfSignedStrategy to allow Self Signed Certificates + SSLContext sslContext = + SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + + // we can optionally disable hostname verification. + // if you don't want to further weaken the security, you don't have to include this. + HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); + + // create an SSL Socket Factory to use the SSLContext with the trust self signed certificate + // strategy and allow all hosts verifier. + SSLConnectionSocketFactory connectionFactory = + new SSLConnectionSocketFactory(sslContext, allowAllHosts); + + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket + // Factory + return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + " [url=" + + url + + ", formParameters=" + + formParameters + + ", getParameters=" + + getParameters + + ", cookies=" + + cookies + + ", headers=" + + headers + + "]"; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java index bd50f3bc..b98baafc 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java @@ -20,6 +20,6 @@ import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("JERSEYWS") -public class JerseyTestCase extends TestCase { +public class JerseyTestCase extends TestCaseInput { public JerseyTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java index 17bd2d78..6660152e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java @@ -1,27 +1,10 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2021 - */ package org.owasp.benchmarkutils.helpers; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; -import org.apache.hc.core5.http.NameValuePair; -import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.http.NameValuePair; +import org.apache.http.message.BasicNameValuePair; @XmlRootElement public class RequestVariable { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java index defa0567..52bdeed4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java @@ -20,6 +20,6 @@ import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("SERVLET") -public class ServletTestCase extends TestCase { +public class ServletTestCase extends TestCaseInput { public ServletTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java index 147cd025..9102e1e9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java @@ -20,6 +20,6 @@ import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("SPRINGWS") -public class SpringTestCase extends TestCase { +public class SpringTestCase extends TestCaseInput { public SpringTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java new file mode 100644 index 00000000..90dca553 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java @@ -0,0 +1,72 @@ +package org.owasp.benchmarkutils.helpers; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; + +public class Sqlite3Config extends TestCaseSetup { + + private String initializationScriptFile; + + private String scriptFile; + + public void executeScripts(String scriptFile) throws FileNotFoundException, IOException { + // db parameters + String url = "jdbc:sqlite:./benchmark.db"; + + // create a connection to the database + try (Connection conn = DriverManager.getConnection(url)) { + + System.out.println("Connection to SQLite has been established."); + + Statement statement = conn.createStatement(); + + List lines = new ArrayList(); + try (BufferedReader reader = new BufferedReader(new FileReader(scriptFile))) { + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + + for (String line : lines) { + statement.execute(line); + } + + } catch (SQLException e) { + System.out.println(e.getMessage()); + } + } + + public void initialize() throws FileNotFoundException, IOException { + executeScripts(this.initializationScriptFile); + } + + public void setup() throws TestCaseSetupException { + try { + initialize(); + executeScripts(getScriptFile()); + } catch (IOException e) { + throw new TestCaseSetupException("Could not setup Sqlite3Config for test case", e); + } + } + + @XmlAttribute(name = "script") + @NotNull + public String getScriptFile() { + return scriptFile; + } + + public void setScriptFile(String scriptFile) { + this.scriptFile = scriptFile; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java new file mode 100644 index 00000000..010bd2ba --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java @@ -0,0 +1,42 @@ +package org.owasp.benchmarkutils.helpers; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Arrays; +import java.util.List; + +public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { + public void execute() { + List executeArgs = Arrays.asList(getCommand()); + + File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); + + // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // #child.interact() + // child.sendline(arg1) + // child.logfile = sys.stdout + // child.expect(pexpect.EOF) + // child.close() + // print("Return code: %d" % child.exitstatus) + + ProcessBuilder builder = new ProcessBuilder(executeArgs); + final Process process = builder.start(); + OutputStream stdin = process.getOutputStream(); + InputStream stdout = process.getInputStream(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin)); + + writer.write(getPayload()); + writer.flush(); + writer.close(); + + int exitValue = process.waitFor(); + System.out.printf("Program terminated with return code: %s%n", exitValue); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java index c4e98a8c..eee2b027 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java @@ -1,35 +1,17 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https:/owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2021 - */ package org.owasp.benchmarkutils.helpers; -import java.util.List; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlElements; +import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; +import org.eclipse.persistence.oxm.annotations.XmlPath; +import org.eclipse.persistence.oxm.annotations.XmlPaths; +import org.eclipse.persistence.oxm.annotations.XmlReadOnly; -@XmlSeeAlso({ServletTestCase.class, JerseyTestCase.class, SpringTestCase.class}) -@XmlDiscriminatorNode("@tcType") -public abstract class TestCase { - - private String url; +@XmlRootElement +public class TestCase { private Category category; @@ -47,23 +29,29 @@ public abstract class TestCase { private String templateFile; + private String type; + private String UITemplateFile; private boolean isVulnerable; - private List formParameters; + private TestCaseInput testCaseInput; - private List getParameters; + @XmlElements({ + @XmlElement(type = HttpClientConfig.class), + @XmlElement(type = FileCopyConfig.class), + @XmlElement(type = Sqlite3Config.class) + }) + @XmlPaths({ + @XmlPath("fee[@type='HttpClientConfig']"), + @XmlPath("fee[@type='FileCopyConfig']"), + @XmlPath("fee[@type='Sqlite3Config']") + }) + private TestCaseSetup testCaseSetup; - private List cookies; + private TestCaseRequest attackTestCaseRequest; - private List headers; - - @XmlAttribute(name = "URL", required = true) - @NotNull - public String getUrl() { - return url; - } + private TestCaseRequest safeTestCaseRequest; @XmlAttribute(name = "tcCategory", required = true) @XmlJavaTypeAdapter(CategoryAdapter.class) @@ -119,56 +107,52 @@ public String getUITemplateFile() { return UITemplateFile; } + @XmlAttribute(name = "tcType", required = true) + @XmlReadOnly + @NotNull + public String getType() { + return type; + } + @XmlAttribute(name = "tcVulnerable", required = true) public boolean isVulnerable() { return isVulnerable; } - @XmlElement(name = "formparam") - @NotNull - public List getFormParameters() { - return formParameters; + @XmlElement(name = "input", required = true) + public TestCaseInput getTestCaseInput() { + return testCaseInput; } - @XmlElement(name = "getparam") - @NotNull - public List getGetParameters() { - return getParameters; + public TestCaseRequest getAttackTestCaseRequest() { + return attackTestCaseRequest; } - @XmlElement(name = "cookie") - @NotNull - public List getCookies() { - return cookies; + public void setAttackTestCaseRequest(TestCaseRequest attackTestCaseRequest) { + this.attackTestCaseRequest = attackTestCaseRequest; } - @XmlElement(name = "header") - @NotNull - public List getHeaders() { - return headers; + public TestCaseRequest getSafeTestCaseRequest() { + return safeTestCaseRequest; } - public void setFormParameters(List formParameters) { - this.formParameters = formParameters; + public void setSafeTestCaseRequest(TestCaseRequest safeTestCaseRequest) { + this.safeTestCaseRequest = safeTestCaseRequest; } - public void setGetParameters(List getParameters) { - this.getParameters = getParameters; + public void setTestCaseInput(TestCaseInput testCaseInput) { + this.testCaseInput = testCaseInput; } - public void setCookies(List cookies) { - this.cookies = cookies; - } + public void execute() { - public void setHeaders(List headers) { - this.headers = headers; + this.getTestCaseInput().execute(this.getName()); } @Override public String toString() { - return "TestCase [url=" - + url - + ", category=" + return "TestCase [" + + "category=" + category + ", dataflowFile=" + dataflowFile @@ -188,14 +172,8 @@ public String toString() { + UITemplateFile + ", isVulnerable=" + isVulnerable - + ", formParameters=" - + formParameters - + ", getParameters=" - + getParameters - + ", cookies=" - + cookies - + ", headers=" - + headers + + ", " + + testCaseInput + "]"; } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java new file mode 100644 index 00000000..7645d53b --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java @@ -0,0 +1,27 @@ +package org.owasp.benchmarkutils.helpers; + +import javax.xml.bind.annotation.XmlSeeAlso; + +@XmlSeeAlso({ + CliArgExecutableTestCaseInput.class, + CliFileExecutableTestCaseInput.class, + HttpTestCaseInput.class, + StdinExecutableTestCaseInput.class, + TCPSocketTestCaseInput.class +}) +public abstract class TestCaseInput { + + private String testCaseName; + + abstract void execute(String testCaseName); + + abstract void setSafe(boolean isSafe); + + public String getTestCaseName() { + return testCaseName; + } + + public void setTestCaseName(String testCaseName) { + this.testCaseName = testCaseName; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java new file mode 100644 index 00000000..e1620477 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java @@ -0,0 +1,9 @@ +package org.owasp.benchmarkutils.helpers; + +import javax.xml.bind.annotation.XmlSeeAlso; + +@XmlSeeAlso({Sqlite3Config.class, FileCopyConfig.class, HttpClientConfig.class}) +public abstract class TestCaseSetup { + + abstract void setup() throws TestCaseSetupException; +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java new file mode 100644 index 00000000..9e8ef6e1 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java @@ -0,0 +1,8 @@ +package org.owasp.benchmarkutils.helpers; + +public class TestCaseSetupException extends Exception { + + public TestCaseSetupException(String message, Exception e) { + super(message, e); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java index 5599483e..9b7becc3 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java @@ -21,16 +21,19 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import org.apache.http.impl.client.CloseableHttpClient; import org.owasp.benchmarkutils.tools.AbstractTestCaseRequest; @XmlRootElement(name = "benchmarkSuite") public class TestSuite { private List testCases; - private String name; // Name of the test suite, e.g., benchmark (Which is BenchmarkJava) + private String name; private String version; + CloseableHttpClient httpclient = null; + @XmlElement(name = "benchmarkTest") public List getTestCases() { return testCases; @@ -58,13 +61,26 @@ public void setVersion(String version) { this.version = version; } - /** - * Dump out some basic details from the Crawler file to the command line to verify it was read - * in properly. Used for debugging. - */ - public void dumpBasicDetails() { - System.out.println("Test suite name and version: " + name + " v" + version); - System.out.println("Total test cases: " + this.getTestCases().size()); + public void execute() { + + // TODO: Maybe create a TestCaseContext class to hold the httpclient, and setup tasks like + // starting the DB server and app server. Pass this object as the argument to execute(). + // It is annoying that the httpclient field of the class will be null if there are no Http + // test cases in the test suite. It would be better if we had a context class for each + // TestCaseInput class. + // The XML doc that defines the testcases could specify the config class by class name. The + // testcase class would need to instantiate the named class accessible via a singleton map. + // Then, each testcase can call a shared setup method and access shared state via its own + // config field. I think this means that the DB server would be started in the middle of + // parsing the XML doc, though. + + TestCaseContext context = TestCase.getContext(testCase); + testCase.execute(context); + + // Execute all of the test cases + for (TestCase testCase : this.getTestCases()) { + testCase.execute(); + } } @Override diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 9c15a056..7b347245 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -48,8 +48,13 @@ import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXSource; import org.apache.commons.io.FileUtils; import org.eclipse.persistence.jaxb.JAXBContextFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; public class Utils { @@ -59,7 +64,9 @@ public class Utils { public static final String DATA_DIR = USERDIR + "data" + File.separator; - public static final DocumentBuilderFactory safeDocBuilderFactory = + public static final String CRAWLER_CONFIG_FILE = "benchmark-attack-http.xml"; + + private static final DocumentBuilderFactory safeDocBuilderFactory = DocumentBuilderFactory.newInstance(); static { @@ -74,6 +81,10 @@ public class Utils { } } + public static DocumentBuilderFactory getSafeDocBuilderFactory() { + return safeDocBuilderFactory; + } + /** * Find the specified file on the class path and return a File handle to it. Note: If the * specified file is inside of a JAR on the classpath, this method will throw an error or return @@ -95,7 +106,10 @@ public static File getFileFromClasspath(String filePath, ClassLoader classLoader System.out.printf( "getFileFromClasspath() url.toURI() is: %s and external form is: %s%n", resourceURI, externalFormURI); - + // String filePath = resourceURI.getPath(); + // System.out.println("getFileFromClasspath() url.toURI().getPath() + // is: " + filePath); + // if (resourceURI != null) return new File(resourceURI); if (externalFormURI != null) return new File(externalFormURI); else { System.out.printf( @@ -174,14 +188,32 @@ public static List getLinesFromStream(InputStream fileStream, String sou * @return A list of requests * @throws JAXBException * @throws FileNotFoundException + * @throws ParserConfigurationException + * @throws SAXException + * @throws TestCaseRequestFileParseException */ - public static TestSuite parseHttpFile(File file) throws JAXBException, FileNotFoundException { + public static TestSuite parseHttpFile(File file) + throws JAXBException, FileNotFoundException, SAXException, + ParserConfigurationException { TestSuite testSuite = null; + + // Disable XXE + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + // Do unmarshall operation + String crawlerFileName = new File(Utils.DATA_DIR, CRAWLER_CONFIG_FILE).getPath(); + Source xmlSource = + new SAXSource( + spf.newSAXParser().getXMLReader(), + new InputSource(new FileReader(crawlerFileName))); JAXBContext context = JAXBContextFactory.createContext(new Class[] {TestSuite.class}, null); Unmarshaller unmarshaller = context.createUnmarshaller(); unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); - testSuite = (TestSuite) unmarshaller.unmarshal(new FileReader(file)); + testSuite = (TestSuite) unmarshaller.unmarshal(xmlSource); return testSuite; } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java index 7d368d40..fcc1f54b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java @@ -21,17 +21,16 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpUriRequest; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; +import org.eclipse.persistence.oxm.annotations.XmlReadOnly; import org.owasp.benchmarkutils.helpers.Category; import org.owasp.benchmarkutils.helpers.CategoryAdapter; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -76,13 +75,7 @@ public int compare(AbstractTestCaseRequest o1, AbstractTestCaseRequest o2) { private boolean isUnverifiable; private boolean isVulnerability; private String attackSuccessString; - - // Occasionally, its useful to verify that a string is MISSING from the response to indicate an - // attack was successful - private boolean attackSuccessStringPresent = true; // The default - - private String name; // TestCase name - private int number = -1; // TestCase number, auto extracted from the name when its set + private String name; private String query; private String sinkFile; private String sourceFile; @@ -93,14 +86,96 @@ public int compare(AbstractTestCaseRequest o1, AbstractTestCaseRequest o2) { public AbstractTestCaseRequest() {} + // /** + // * This class contains enough information to generate an HttpUriRequest for a generated + // test + // * case. + // * + // * @param fullURL + // * @param tcType + // * @param category + // * @param name + // * @param uiTemplateFile + // * @param templateFile + // * @param sourceFile + // * @param sourceUIType + // * @param dataflowFile + // * @param sinkFile + // * @param isUnverifiable + // * @param isVulnerability + // * @param attackSuccessString + // * @param headers + // * @param cookies + // * @param getParams + // * @param formParams + // */ + // public AbstractTestCaseRequest( + // String fullURL, + // TestCaseType tcType, + // Category category, + // String name, + // String uiTemplateFile, + // String templateFile, + // String sourceFile, + // String sourceUIType, + // String dataflowFile, + // String sinkFile, + // boolean isUnverifiable, + // boolean isVulnerability, + // String attackSuccessString, + // List headers, + // List cookies, + // List getParams, + // List formParams) { + // super(); + // this.fullURL = fullURL; + // this.tcType = tcType; + // this.category = category; + // this.name = name; + // this.uiTemplateFile = uiTemplateFile; + // this.templateFile = templateFile; + // this.sourceFile = sourceFile; + // this.sourceUIType = sourceUIType; + // this.dataflowFile = dataflowFile; + // this.sinkFile = sinkFile; + // this.isUnverifiable = isUnverifiable; + // this.isVulnerability = isVulnerability; + // this.attackSuccessString = attackSuccessString; + // this.headers = headers; + // this.cookies = cookies; + // this.getParams = getParams; + // this.formParams = formParams; + // + // // // Figure out if ANY of the values in the request include an attack value. + // // this.isSafe = true; + // // // Bitwise AND is done on all parameters isSafe() values. If ANY of them are + // // unsafe, isSafe + // // // set to False. + // // for (RequestVariable header : getHeaders()) { + // // this.isSafe &= header.isSafe(); + // // } + // // + // // for (RequestVariable cookie : getCookies()) { + // // this.isSafe &= cookie.isSafe(); + // // } + // // + // // for (RequestVariable getParam : getGetParams()) { + // // this.isSafe &= getParam.isSafe(); + // // } + // // + // // for (RequestVariable formParam : getFormParams()) { + // // this.isSafe &= formParam.isSafe(); + // // } + // } + /** Defines what parameters in the body will be sent. */ - abstract void buildBodyParameters(HttpUriRequestBase request); + abstract void buildBodyParameters(HttpRequestBase request); /** Defines what cookies will be sent. */ - abstract void buildCookies(HttpUriRequestBase request); + abstract void buildCookies(HttpRequestBase request); /** Defines what headers will be sent. */ - abstract void buildHeaders(HttpUriRequestBase request); + abstract void buildHeaders(HttpRequestBase request); /** Defines how to construct URL query string. */ abstract void buildQueryString(); @@ -113,7 +188,7 @@ public AbstractTestCaseRequest() {} */ public HttpUriRequest buildRequest() { buildQueryString(); - HttpUriRequestBase request = createRequestInstance(fullURL + query); + HttpRequestBase request = createRequestInstance(fullURL + query); buildHeaders(request); buildCookies(request); buildBodyParameters(request); @@ -133,20 +208,15 @@ public HttpUriRequest buildSafeRequest() { /** * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. * - * @return an instance of a subclass of HttpUriRequestBase + * @return an instance of a subclass of HttpRequestBase */ - abstract HttpUriRequestBase createRequestInstance(String URL); + abstract HttpRequestBase createRequestInstance(String URL); @XmlAttribute(name = "tcAttackSuccess") public String getAttackSuccessString() { return this.attackSuccessString; } - @XmlAttribute(name = "tcAttackSuccessPresent") - public boolean getAttackSuccessStringPresent() { - return this.attackSuccessStringPresent; - } - @XmlAttribute(name = "tcCategory", required = true) @XmlJavaTypeAdapter(CategoryAdapter.class) @NotNull @@ -196,12 +266,6 @@ public String getName() { return this.name; } - // This value is extracted from the test case name when it is set via setName(). Not sure it is - // set when autoloaded from XML file. - public int getNumber() { - return this.number; - } - @XmlTransient public String getQuery() { return this.query; @@ -231,9 +295,9 @@ public String getTemplateFile() { return this.templateFile; } - // @XmlAttribute(name = "tcType", required = true) - // @XmlReadOnly - // @NotNull + @XmlAttribute(name = "tcType", required = true) + @XmlReadOnly + @NotNull public TestCaseType getType() { return this.tcType; } @@ -286,10 +350,6 @@ public String setAttackSuccessString(String attackSuccessString) { return this.attackSuccessString = attackSuccessString; } - public boolean setAttackSuccessStringPresent(boolean attackSuccessStringPresent) { - return this.attackSuccessStringPresent = attackSuccessStringPresent; - } - public void setCategory(Category category) { this.category = category; } @@ -318,21 +378,8 @@ public void setHeaders(List headers) { this.headers = headers; } - static final Pattern lastIntPattern = Pattern.compile("[^0-9]+([0-9]+)$"); - public void setName(String name) { this.name = name; - // Auto extract the test case number from the name. - Matcher matcher = lastIntPattern.matcher(name); - if (matcher.find()) { - String someNumberStr = matcher.group(1); - this.number = Integer.parseInt(someNumberStr); - } else { - System.out.println( - "Warning: TestCaseRequest.setName() invoked with test case name: " - + name - + " that doesn't end with a test case number."); - } } public void setQuery(String query) { @@ -372,6 +419,7 @@ public void setVulnerability(boolean isVulnerability) { } public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; for (RequestVariable header : getHeaders()) { // setSafe() considers whether attack and safe values exist for this parameter before // setting isSafe true or false. So you don't have to check that here. diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index bc768bd2..169b3775 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -20,7 +20,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -28,7 +27,6 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.commons.cli.CommandLine; @@ -39,21 +37,17 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang.time.StopWatch; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.util.EntityUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -61,6 +55,7 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.owasp.benchmarkutils.helpers.Categories; +import org.owasp.benchmarkutils.helpers.TestCase; import org.owasp.benchmarkutils.helpers.TestSuite; import org.owasp.benchmarkutils.helpers.Utils; import org.owasp.benchmarkutils.score.BenchmarkScore; @@ -68,23 +63,41 @@ @Mojo(name = "run-crawler", requiresProject = false, defaultPhase = LifecyclePhase.COMPILE) public class BenchmarkCrawler extends AbstractMojo { - // Intended to be a Singleton. So when instantiated, put it here: - static BenchmarkCrawler thisInstance = null; - - static final long MAX_NETWORK_TIMEOUT = 15; // seconds - public static String proxyHost, proxyPort; + @Parameter(property = "crawlerFile") + String pluginFilenameParam; /* - * Attaching the @Parameter property to the crawlerFile variable allows you to set the value directly when invoking via maven. - * For example: -DcrawlerFile=data/benchmark-crawler-http.xml + * Attaching the @Parameter property to the crawlerFile variable directly didn't work for some + * reason. So I attached it to a new String variable, and set it later. No clue why it doesn't + * work. But for now, leaving it this way because it works. + * + * If you run the mvn command with -X, when invoking this plugin, you'd see something like + * this at the end: + * + * [DEBUG] (s) crawlerFile = /Users/PATH/TO/BenchmarkJava/data/benchmark-crawler-http.xml + * [DEBUG] -- end configuration -- + * but the crawlerFile variable would be null. + * + * When it should be: + * [DEBUG] (f) crawlerFile = data/benchmark-crawler-http.xml + * [DEBUG] -- end configuration -- + * + * So after changing this, I now get: + * [DEBUG] (f) pluginFilenameParam = data/benchmark-crawler-http.xml + * [DEBUG] -- end configuration -- + * and the pluginFilenameParam variable value is set properly. */ - @Parameter(property = "crawlerFile") - String crawlerFile = null; + String crawlerFile; File theCrawlerFile; String selectedTestCaseName = null; TestSuite testSuite; + BenchmarkCrawler() { + // A default constructor required to support Maven plugin API. + // The theCrawlerFile has to be instantiated before a crawl can be done. + } + /** Crawl the target test suite. */ protected void run() { try { @@ -127,43 +140,45 @@ void load() { } } - /** - * Load the crawler file that defines all the test cases, including their endpoints, and how to - * crawl them. - * - * @param targetFileName The crawler file name - * @throws RuntimeException If the file doesn't exist or can't be opened for some reason. - */ - public void setCrawlerFile(String targetFileName) throws RuntimeException { - File targetFile = new File(targetFileName); - if (targetFile.exists()) { - this.crawlerFile = targetFileName; - this.theCrawlerFile = targetFile; - } else { - throw new RuntimeException( - "Could not find crawler configuration file: '" + targetFileName + "'"); - } + public void setCrawlerFile(File theCrawlerFile) { + this.theCrawlerFile = theCrawlerFile; } /** * This method could be static, but needs to be an instance method so Verification crawler can - * overload this method. + * override this method. * * @param testSuite The TestSuite to crawl. - * @throws Exception If crawler configuration is messed up somehow. + * @throws Exception */ protected void crawl(TestSuite testSuite) throws Exception { - CloseableHttpClient httpclient = - createAcceptSelfSignedCertificateClient( - MAX_NETWORK_TIMEOUT); // Max 15 seconds for timeouts + // FIXME: Use try-with-resources to close this resource before returning + CloseableHttpClient httpclient = createAcceptSelfSignedCertificateClient(); long start = System.currentTimeMillis(); - for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { - - HttpUriRequest request = requestTemplate.buildSafeRequest(); - - // Send the next test case request - sendRequest(httpclient, request); + // Iterate through TestCase objects instead. + // Execution of the test case depends on the type of TestCase.getTestCaseInput() + // Where should the code that executes the test case go? + // Maybe I need a TestCaseExecuter that takes a TestCaseInput to initialize. + // for (TestCase testCase : testSuite.getTestCases()) { + // if (testCase.getTestCaseInput() instanceof HttpTestCaseInput) { + // HttpUriRequest request = testCase.getAttackTestCaseRequest().buildAttackRequest(); + // sendRequest(httpclient, request); + // } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { + // // Execute the testCase using exec() + // } + // } + + // FIXME: If there are any HttpTestCaseInput, create a CloseableHttpClient + for (TestCase testCase : testSuite.getTestCases()) { + testCase.getTestCaseExecutor().execute(); + + // for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { + // + // HttpUriRequest request = requestTemplate.buildAttackRequest(); + // + // // Send the next test case request + // sendRequest(httpclient, request); } // Log the elapsed time for all test cases @@ -179,7 +194,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // This method taken directly from: // https://memorynotfound.com/ignore-certificate-errors-apache-httpclient/ - static CloseableHttpClient createAcceptSelfSignedCertificateClient(long timeout) + static CloseableHttpClient createAcceptSelfSignedCertificateClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { // use the TrustSelfSignedStrategy to allow Self Signed Certificates @@ -195,37 +210,9 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient(long timeout) SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); - HttpClientConnectionManager cm = - PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(connectionFactory) - .build(); - - // Set Proxy settings - HttpHost httpHost = null; - RequestConfig config = - RequestConfig.custom() - .setConnectTimeout(timeout, TimeUnit.SECONDS) - .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) - .setResponseTimeout(timeout, TimeUnit.SECONDS) - .build(); - if ((proxyHost = System.getProperty("proxyHost")) != null - && (proxyPort = System.getProperty("proxyPort")) != null) { - httpHost = new HttpHost(proxyHost, Integer.parseInt(proxyPort)); - // finally create the HttpClient using HttpClient factory methods and assign the SSL - // Socket Factory and assign the setProxy - return HttpClients.custom() - .setDefaultRequestConfig(config) - .setConnectionManager(cm) - .setProxy(httpHost) - .build(); - } else { - // finally create the HttpClient using HttpClient factory methods and assign the SSL - // Socket Factory - return HttpClients.custom() - .setDefaultRequestConfig(config) - .setConnectionManager(cm) - .build(); - } + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket + // Factory + return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); } /** @@ -242,48 +229,30 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r CloseableHttpResponse response = null; boolean isPost = request instanceof HttpPost; - try { - System.out.println((isPost ? "POST " : "GET ") + request.getUri()); - } catch (URISyntaxException e1) { - System.out.println( - (isPost ? "POST " : "GET ") + "COULDN'T LOG Uri because of URISyntaxException"); - e1.printStackTrace(); - } + System.out.println((isPost ? "POST " : "GET ") + request.getURI()); StopWatch watch = new StopWatch(); watch.start(); try { response = httpclient.execute(request); } catch (IOException e) { - // When this occurs, a null pointer exception happens later on, so we need to do - // something so we can continue crawling. e.printStackTrace(); } watch.stop(); try { + HttpEntity entity = response.getEntity(); + int statusCode = response.getStatusLine().getStatusCode(); + responseInfo.setStatusCode(statusCode); int seconds = (int) watch.getTime() / 1000; responseInfo.setTimeInSeconds(seconds); - if (response != null) { - HttpEntity entity = response.getEntity(); - int statusCode = response.getCode(); - responseInfo.setStatusCode(statusCode); - System.out.printf("--> (%d : %d sec)%n", statusCode, seconds); + System.out.printf("--> (%d : %d sec)%n", statusCode, seconds); - try { - if (entity != null) { - responseInfo.setResponseString(EntityUtils.toString(entity)); - EntityUtils.consume(entity); - } else - // Can occur when there is a 204 No Content response - responseInfo.setResponseString(""); - } catch (IOException | org.apache.hc.core5.http.ParseException e) { - e.printStackTrace(); - } - } else { // This can occur when the test case never responds, throwing an exception. - responseInfo.setStatusCode(-1); // since no response at all - System.out.printf("--> (%d : %d sec)%n", -1, seconds); - responseInfo.setResponseString("NONE!"); + try { + responseInfo.setResponseString(EntityUtils.toString(entity)); + EntityUtils.consume(entity); + } catch (IOException e) { + e.printStackTrace(); } } finally { if (response != null) @@ -302,7 +271,7 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r * @param args - args passed to main(). * @return specified crawler file if valid command line arguments provided. Null otherwise. */ - protected void processCommandLineArgs(String[] args) { + private void processCommandLineArgs(String[] args) { // Create the command line parser CommandLineParser parser = new DefaultParser(); @@ -329,7 +298,14 @@ protected void processCommandLineArgs(String[] args) { CommandLine line = parser.parse(options, args); if (line.hasOption("f")) { - setCrawlerFile(line.getOptionValue("f")); + this.crawlerFile = line.getOptionValue("f"); + File targetFile = new File(this.crawlerFile); + if (targetFile.exists()) { + setCrawlerFile(targetFile); + } else { + throw new RuntimeException( + "Could not find crawler configuration file '" + this.crawlerFile + "'"); + } } if (line.hasOption("h")) { formatter.printHelp("BenchmarkCrawlerVerification", options, true); @@ -345,24 +321,19 @@ protected void processCommandLineArgs(String[] args) { @Override public void execute() throws MojoExecutionException, MojoFailureException { - if (thisInstance == null) thisInstance = this; - - if (null == this.crawlerFile) { + if (null == this.pluginFilenameParam) { System.out.println("ERROR: A crawlerFile parameter must be specified."); } else { - String[] mainArgs = {"-f", this.crawlerFile}; + String[] mainArgs = {"-f", this.pluginFilenameParam}; main(mainArgs); } } public static void main(String[] args) { - // thisInstance can be set from execute() or here, depending on how this class is invoked - // (via maven or commmand line) - if (thisInstance == null) { - thisInstance = new BenchmarkCrawler(); - } - thisInstance.processCommandLineArgs(args); - thisInstance.load(); - thisInstance.run(); + + BenchmarkCrawler crawler = new BenchmarkCrawler(); + crawler.processCommandLineArgs(args); + crawler.load(); + crawler.run(); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 70698b47..0cc474c7 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -20,7 +20,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -31,8 +30,8 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -64,18 +63,14 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { SimpleFileLogger eLogger; SimpleFileLogger uLogger; - /** - * Overload the base crawl() method to send both attack and safe requests, and verify whether - * the test exploit worked or not based on the results that came back in both the attack - * response and safe response and whether this test case is a true positive or not. - * - * @param testSuite The TestSuite to crawl. - * @throws Exception If crawler configuration is messed up somehow. - */ + BenchmarkCrawlerVerification() { + // A default constructor required to support Maven plugin API. + // The theCrawlerFile has to be instantiated before a crawl can be done. + } + @Override protected void crawl(TestSuite testSuite) throws Exception { - CloseableHttpClient httpclient = - createAcceptSelfSignedCertificateClient(MAX_NETWORK_TIMEOUT); + CloseableHttpClient httpclient = createAcceptSelfSignedCertificateClient(); long start = System.currentTimeMillis(); List responseInfoList = new ArrayList(); List results = new ArrayList(); @@ -233,22 +228,14 @@ private void log(ResponseInfo responseInfo) throws IOException { String.format( "--> (%d : %d sec)%n", responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); - try { - if (isTimingEnabled) { - if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } // else do nothing - } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + if (isTimingEnabled) { + if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestBase.getMethod() + " " + requestBase.getURI()); tLogger.println(outputString); } - } catch (URISyntaxException e) { - String errMsg = requestBase.getMethod() + " COULDN'T LOG URI due to URISyntaxException"; - tLogger.println(errMsg); + } else { + tLogger.println(requestBase.getMethod() + " " + requestBase.getURI()); tLogger.println(outputString); - System.out.println(errMsg); - e.printStackTrace(); } } @@ -278,15 +265,11 @@ protected static void handleResponse(TestCaseVerificationResults result) * @param args - args passed to main(). * @return specified crawler file if valid command line arguments provided. Null otherwise. */ - @Override - protected void processCommandLineArgs(String[] args) { + private void processCommandLineArgs(String[] args) { - // Set default attack crawler file, if it exists - // This value can be changed by the -f parameter for other test suites with different names - File defaultAttackCrawlerFile = new File(Utils.DATA_DIR, "benchmark-attack-http.xml"); - if (defaultAttackCrawlerFile.exists()) { - setCrawlerFile(defaultAttackCrawlerFile.getPath()); - } + // Set default attack crawler file + String crawlerFileName = new File(Utils.DATA_DIR, "benchmark-attack-http.xml").getPath(); + this.theCrawlerFile = new File(crawlerFileName); RegressionTesting.isTestingEnabled = true; @@ -300,7 +283,7 @@ protected void processCommandLineArgs(String[] args) { options.addOption( Option.builder("f") .longOpt("file") - .desc("a TESTSUITE-attack-http.xml file") + .desc("a TESTSUITE-crawler-http.xml file") .hasArg() .required() .build()); @@ -324,10 +307,16 @@ protected void processCommandLineArgs(String[] args) { CommandLine line = parser.parse(options, args); if (line.hasOption("f")) { - // Following throws a RuntimeException if the target file doesn't exist - setCrawlerFile(line.getOptionValue("f")); - // Crawler output files go into the same directory as the crawler config file - CRAWLER_DATA_DIR = this.theCrawlerFile.getParent() + File.separator; + this.crawlerFile = line.getOptionValue("f"); + File targetFile = new File(this.crawlerFile); + if (targetFile.exists()) { + setCrawlerFile(targetFile); + // Crawler output files go into the same directory as the crawler config file + CRAWLER_DATA_DIR = targetFile.getParent() + File.separator; + } else { + throw new RuntimeException( + "Could not find crawler configuration file '" + this.crawlerFile + "'"); + } } if (line.hasOption("h")) { formatter.printHelp("BenchmarkCrawlerVerification", options, true); @@ -346,24 +335,19 @@ protected void processCommandLineArgs(String[] args) { @Override public void execute() throws MojoExecutionException, MojoFailureException { - if (thisInstance == null) thisInstance = this; - - if (null == this.crawlerFile) { - System.out.println("ERROR: An attack crawlerFile parameter must be specified."); + if (null == this.pluginFilenameParam) { + System.out.println("ERROR: A crawlerFile parameter must be specified."); } else { - String[] mainArgs = {"-f", this.crawlerFile}; + String[] mainArgs = {"-f", this.pluginFilenameParam}; main(mainArgs); } } public static void main(String[] args) { - // thisInstance can be set from execute() or here, depending on how this class is invoked - // (via maven or commmand line) - if (thisInstance == null) { - thisInstance = new BenchmarkCrawlerVerification(); - } - thisInstance.processCommandLineArgs(args); - thisInstance.load(); - thisInstance.run(); + + BenchmarkCrawlerVerification crawler = new BenchmarkCrawlerVerification(); + crawler.processCommandLineArgs(args); + crawler.load(); + crawler.run(); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java index 1ed2df56..ef519dd1 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java @@ -17,9 +17,11 @@ */ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.core5.http.io.entity.StringEntity; +import java.io.UnsupportedEncodingException; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -34,7 +36,7 @@ void buildQueryString() { } @Override - HttpUriRequestBase createRequestInstance(String URL) { + HttpRequestBase createRequestInstance(String URL) { // Apparently all Jersey Requests are POSTS. Never any query string params per buildQuery() // above. HttpPost httpPost = new HttpPost(URL); @@ -42,7 +44,7 @@ HttpUriRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpUriRequestBase request) { + void buildHeaders(HttpRequestBase request) { request.addHeader("Content-Type", "application/xml; charset=utf-8"); for (RequestVariable header : getHeaders()) { String name = header.getName(); @@ -53,7 +55,7 @@ void buildHeaders(HttpUriRequestBase request) { } @Override - void buildCookies(HttpUriRequestBase request) { + void buildCookies(HttpRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -63,7 +65,7 @@ void buildCookies(HttpUriRequestBase request) { } @Override - void buildBodyParameters(HttpUriRequestBase request) { + void buildBodyParameters(HttpRequestBase request) { String params = ""; for (RequestVariable field : getFormParams()) { String name = field.getName(); @@ -71,8 +73,12 @@ void buildBodyParameters(HttpUriRequestBase request) { params += "<" + name + ">" + escapeXML(value) + ""; } params += ""; - StringEntity paramsEnt = new StringEntity(params); - request.setEntity(paramsEnt); + try { + StringEntity paramsEnt = new StringEntity(params); + ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); + } catch (UnsupportedEncodingException e) { + System.out.println("Error encoding URL: " + e.getMessage()); + } } private static String escapeXML(String value) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 0383cf45..5b714cc1 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -30,10 +30,11 @@ import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.core5.http.Header; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.HttpMessage; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpMessage; +import org.apache.http.ParseException; +import org.apache.http.client.methods.HttpPost; /** * Test all supported test cases to verify that the results are as expected and write the report to @@ -180,7 +181,7 @@ public static void genFailedTCFile(List results, St private static void printHttpRequest(HttpMessage request, Logger out) { out.println(request.toString()); - for (Header header : request.getHeaders()) { + for (Header header : request.getAllHeaders()) { out.printf("%s:%s%n", header.getName(), header.getValue()); } if (request instanceof HttpPost) { @@ -191,7 +192,7 @@ private static void printHttpRequest(HttpMessage request, Logger out) { if (entity != null) { out.println(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); } - } catch (IOException e) { + } catch (ParseException | IOException e) { System.out.println("ERROR: Could not parse HttpPost entities"); e.printStackTrace(); } @@ -222,11 +223,8 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log out.printf("Safe response: [%d]:%n", attackResponseInfo.getStatusCode()); out.println(safeResponseInfo == null ? "null" : safeResponseInfo.getResponseString()); out.println(); - String negatedAttackSuccessString = - (requestTemplate.getAttackSuccessStringPresent() ? "" : "Failure "); out.printf( - "Attack success %sindicator: -->%s<--%n", - negatedAttackSuccessString, requestTemplate.getAttackSuccessString()); + "Attack success indicator: -->%s<--%n", requestTemplate.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); } @@ -342,17 +340,14 @@ public static void verifyTestCase(TestCaseVerificationResults result) } if (!result.isUnverifiable()) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); boolean isAttackValueVerified = verifyResponse( result.getResponseToAttackValue().getResponseString(), - requestTemplate.getAttackSuccessString(), - requestTemplate.getAttackSuccessStringPresent()); + result.getRequestTemplate().getAttackSuccessString()); boolean isSafeValueVerified = verifyResponse( result.getResponseToSafeValue().getResponseString(), - requestTemplate.getAttackSuccessString(), - requestTemplate.getAttackSuccessStringPresent()); + result.getRequestTemplate().getAttackSuccessString()); if (result.getRequestTemplate().isVulnerability()) { // True positive success? if (isAttackValueVerified) { @@ -442,17 +437,14 @@ private static List findErrors(ResponseInfo responseInfo, String prefix) * @param response - The response from this test case. * @param attackSuccessIndicator - The value to look for in the response to determine if the * attack was successful. - * @param attackSuccessStringPresent - boolean indicating if attack success indicator must be - * present (or absent) to pass. * @return true if the response passes the described checks. False otherwise. */ - public static boolean verifyResponse( - String response, String attackSuccessIndicator, boolean attackSuccessStringPresent) { + public static boolean verifyResponse(String response, String attackSuccessIndicator) { // Rip out any REFERER values attackSuccessIndicator = attackSuccessIndicator.replace("REFERER", ""); - return (response.contains(attackSuccessIndicator) == attackSuccessStringPresent); + return response.contains(attackSuccessIndicator); } private static boolean isIncludedInTest(AbstractTestCaseRequest testCaseRequestTemplate) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java index b4e0b4a3..54acc4ac 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java @@ -1,23 +1,6 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author Dave Wichers - * @created 2021 - */ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.http.client.methods.HttpUriRequest; class ResponseInfo { private String responseString; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java index b4e7889f..b24bf7e0 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java @@ -17,14 +17,16 @@ */ package org.owasp.benchmarkutils.tools; +import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; -import org.apache.hc.core5.http.NameValuePair; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -59,7 +61,7 @@ void buildQueryString() { } @Override - HttpUriRequestBase createRequestInstance(String URL) { + HttpRequestBase createRequestInstance(String URL) { // If there are query parameters, this must be a GET, otherwise a POST. if (getQuery().length() == 0) { return new HttpPost(URL); @@ -69,11 +71,7 @@ HttpUriRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpUriRequestBase request) { - // AJAX does: text/plain;charset=UTF-8, while HTML Form: application/x-www-form-urlencoded - // request.addHeader("Content-Type", ";charset=UTF-8"); --This BREAKS BenchmarkCrawling - request.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Works for both though - + void buildHeaders(HttpRequestBase request) { for (RequestVariable header : getHeaders()) { String name = header.getName(); String value = header.getValue(); @@ -84,7 +82,7 @@ void buildHeaders(HttpUriRequestBase request) { @SuppressWarnings("deprecation") @Override - void buildCookies(HttpUriRequestBase request) { + void buildCookies(HttpRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -96,7 +94,7 @@ void buildCookies(HttpUriRequestBase request) { } @Override - void buildBodyParameters(HttpUriRequestBase request) { + void buildBodyParameters(HttpRequestBase request) { List fields = new ArrayList<>(); for (RequestVariable formParam : getFormParams()) { fields.add(formParam.getNameValuePair()); @@ -104,7 +102,12 @@ void buildBodyParameters(HttpUriRequestBase request) { // Add the body parameters to the request if there were any if (fields.size() > 0) { - request.setEntity(new UrlEncodedFormEntity(fields)); + try { + ((HttpEntityEnclosingRequestBase) request) + .setEntity(new UrlEncodedFormEntity(fields)); + } catch (UnsupportedEncodingException e) { + System.out.println("Error encoding URL: " + e.getMessage()); + } } } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java index 8447fdf3..64d4ab86 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java @@ -1,20 +1,3 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2021 - */ package org.owasp.benchmarkutils.tools; import java.io.Closeable; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java index a583c5d4..a8ce7bd1 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java @@ -17,9 +17,11 @@ */ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.core5.http.io.entity.StringEntity; +import java.io.UnsupportedEncodingException; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -34,7 +36,7 @@ void buildQueryString() { } @Override - HttpUriRequestBase createRequestInstance(String URL) { + HttpRequestBase createRequestInstance(String URL) { // Apparently all Spring Requests are POSTS. Never any query string params per buildQuery() // above. HttpPost httpPost = new HttpPost(URL); @@ -42,10 +44,8 @@ HttpUriRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpUriRequestBase request) { - request.addHeader("Content-type", "application/json"); // Should this add ;charset=utf-8? - // No: "Designating the encoding is somewhat redundant for JSON, since the default encoding - // for JSON is UTF-8." + void buildHeaders(HttpRequestBase request) { + request.addHeader("Content-type", "application/json"); for (RequestVariable header : getHeaders()) { String name = header.getName(); String value = header.getValue(); @@ -55,7 +55,7 @@ void buildHeaders(HttpUriRequestBase request) { } @Override - void buildCookies(HttpUriRequestBase request) { + void buildCookies(HttpRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -65,7 +65,7 @@ void buildCookies(HttpUriRequestBase request) { } @Override - void buildBodyParameters(HttpUriRequestBase request) { + void buildBodyParameters(HttpRequestBase request) { boolean first = true; String params = "{"; for (RequestVariable field : getFormParams()) { @@ -80,7 +80,11 @@ void buildBodyParameters(HttpUriRequestBase request) { params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); } params += "}"; - StringEntity paramsEnt = new StringEntity(params); - request.setEntity(paramsEnt); + try { + StringEntity paramsEnt = new StringEntity(params); + ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); + } catch (UnsupportedEncodingException e) { + System.out.println("Error encoding URL: " + e.getMessage()); + } } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java new file mode 100644 index 00000000..385f4d1d --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java @@ -0,0 +1,484 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details + * + * @author Juan Gama + * @created 2017 + */ +package org.owasp.benchmarkutils.tools; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.eclipse.persistence.oxm.annotations.XmlReadOnly; +import org.owasp.benchmarkutils.helpers.Category; +import org.owasp.benchmarkutils.helpers.CategoryAdapter; +import org.owasp.benchmarkutils.helpers.RequestVariable; +import org.owasp.benchmarkutils.helpers.TestCaseInput; + +public class TestCaseRequest { + + /* + * The 1st three are Java. + */ + public enum TestCaseType { + JERSEYWS, + SERVLET, + SPRINGWS, + NODEEXPRESS + } + + public static Comparator getNameComparator() { + return new Comparator() { + + @Override + public int compare(TestCaseRequest o1, TestCaseRequest o2) { + if (!o1.name.equalsIgnoreCase(o2.name)) return o1.name.compareTo(o2.name); + return 0; + } + }; + } + + private TestCaseInput testCaseInput; + private Category category; + private List cookies = new ArrayList(); + private String dataflowFile; + private List formParams = new ArrayList(); + private String fullURL; + private List getParams = new ArrayList(); + private List headers = new ArrayList(); + private String notAutoverifiableReason; + private boolean isUnverifiable; + private boolean isVulnerability; + private String attackSuccessString; + private String name; + private String query; + private String sinkFile; + private String sourceFile; + private String sourceUIType; + private TestCaseType tcType; + private String templateFile; + private String uiTemplateFile; + + public TestCaseRequest() {} + + // /** + // * This class contains enough information to generate an HttpUriRequest for a generated + // test + // * case. + // * + // * @param fullURL + // * @param tcType + // * @param category + // * @param name + // * @param uiTemplateFile + // * @param templateFile + // * @param sourceFile + // * @param sourceUIType + // * @param dataflowFile + // * @param sinkFile + // * @param isUnverifiable + // * @param isVulnerability + // * @param attackSuccessString + // * @param headers + // * @param cookies + // * @param getParams + // * @param formParams + // */ + // public AbstractTestCaseRequest( + // String fullURL, + // TestCaseType tcType, + // Category category, + // String name, + // String uiTemplateFile, + // String templateFile, + // String sourceFile, + // String sourceUIType, + // String dataflowFile, + // String sinkFile, + // boolean isUnverifiable, + // boolean isVulnerability, + // String attackSuccessString, + // List headers, + // List cookies, + // List getParams, + // List formParams) { + // super(); + // this.fullURL = fullURL; + // this.tcType = tcType; + // this.category = category; + // this.name = name; + // this.uiTemplateFile = uiTemplateFile; + // this.templateFile = templateFile; + // this.sourceFile = sourceFile; + // this.sourceUIType = sourceUIType; + // this.dataflowFile = dataflowFile; + // this.sinkFile = sinkFile; + // this.isUnverifiable = isUnverifiable; + // this.isVulnerability = isVulnerability; + // this.attackSuccessString = attackSuccessString; + // this.headers = headers; + // this.cookies = cookies; + // this.getParams = getParams; + // this.formParams = formParams; + // + // // // Figure out if ANY of the values in the request include an attack value. + // // this.isSafe = true; + // // // Bitwise AND is done on all parameters isSafe() values. If ANY of them are + // // unsafe, isSafe + // // // set to False. + // // for (RequestVariable header : getHeaders()) { + // // this.isSafe &= header.isSafe(); + // // } + // // + // // for (RequestVariable cookie : getCookies()) { + // // this.isSafe &= cookie.isSafe(); + // // } + // // + // // for (RequestVariable getParam : getGetParams()) { + // // this.isSafe &= getParam.isSafe(); + // // } + // // + // // for (RequestVariable formParam : getFormParams()) { + // // this.isSafe &= formParam.isSafe(); + // // } + // } + + /** Defines what parameters in the body will be sent. */ + public void buildBodyParameters(HttpRequestBase request) { + testCaseInput.buildBodyParameters(request); + } + + /** Defines what cookies will be sent. */ + public void buildCookies(HttpRequestBase request) { + testCaseInput.buildCookies(request); + } + + /** Defines what headers will be sent. */ + public void buildHeaders(HttpRequestBase request) { + testCaseInput.buildHeaders(request); + } + + /** Defines how to construct URL query string. */ + public void buildQueryString() { + testCaseInput.buildQueryString(); + } + + /** + * TODO: Make this class a POJO TestCase and pass it as an arg to another class TestCaseRequest + * that can build an actual HttpUriRequest. + * + * @return + */ + public HttpUriRequest buildRequest() { + buildQueryString(); + HttpRequestBase request = createRequestInstance(fullURL + query); + buildHeaders(request); + buildCookies(request); + buildBodyParameters(request); + return request; + } + + public HttpUriRequest buildAttackRequest() { + setSafe(false); + return buildRequest(); + } + + public HttpUriRequest buildSafeRequest() { + setSafe(true); + return buildRequest(); + } + + /** + * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. + * + * @return an instance of a subclass of HttpRequestBase + */ + abstract HttpRequestBase createRequestInstance(String URL); + + @XmlAttribute(name = "tcAttackSuccess") + public String getAttackSuccessString() { + return this.attackSuccessString; + } + + @XmlAttribute(name = "tcCategory", required = true) + @XmlJavaTypeAdapter(CategoryAdapter.class) + @NotNull + public Category getCategory() { + return this.category; + } + + @XmlElement(name = "cookie") + @NotNull + public List getCookies() { + return this.cookies; + } + + @XmlAttribute(name = "tcDataflowFile", required = true) + @NotNull + public String getDataflowFile() { + return this.dataflowFile; + } + + @XmlElement(name = "formparam") + @NotNull + public List getFormParams() { + return this.formParams; + } + + @XmlAttribute(name = "URL", required = true) + @NotNull + public String getFullURL() { + return this.fullURL; + } + + @XmlElement(name = "getparam") + @NotNull + public List getGetParams() { + return this.getParams; + } + + @XmlElement(name = "header") + @NotNull + public List getHeaders() { + return this.headers; + } + + @XmlAttribute(name = "tcName", required = true) + @NotNull + public String getName() { + return this.name; + } + + @XmlTransient + public String getQuery() { + return this.query; + } + + @XmlAttribute(name = "tcSinkFile", required = true) + @NotNull + public String getSinkFile() { + return this.sinkFile; + } + + @XmlAttribute(name = "tcSourceFile", required = true) + @NotNull + public String getSourceFile() { + return this.sourceFile; + } + + @XmlAttribute(name = "tcSourceUIType", required = true) + @NotNull + public String getSourceUIType() { + return this.sourceUIType; + } + + @XmlAttribute(name = "tcTemplateFile", required = true) + @NotNull + public String getTemplateFile() { + return this.templateFile; + } + + @XmlAttribute(name = "tcType", required = true) + @XmlReadOnly + @NotNull + public TestCaseType getType() { + return this.tcType; + } + + @XmlAttribute(name = "tcUITemplateFile", required = true) + @NotNull + public String getUiTemplateFile() { + return this.uiTemplateFile; + } + + public boolean isUnverifiable() { + return getNotAutoverifiableReason() != null; + } + + @XmlAttribute(name = "tcNotAutoverifiable") + public String getNotAutoverifiableReason() { + return this.notAutoverifiableReason; + } + + @XmlAttribute(name = "tcVulnerable", required = true) + public boolean isVulnerability() { + return this.isVulnerability; + } + + public boolean isSafe() { + + boolean isSafe = true; + // Bitwise AND is done on all parameters isSafe() values. If ANY of them are unsafe, isSafe + // set to False. + for (RequestVariable header : getHeaders()) { + isSafe &= header.isSafe(); + } + + for (RequestVariable cookie : getCookies()) { + isSafe &= cookie.isSafe(); + } + + for (RequestVariable getParam : getGetParams()) { + isSafe &= getParam.isSafe(); + } + + for (RequestVariable formParam : getFormParams()) { + isSafe &= formParam.isSafe(); + } + + return isSafe; + } + + public String setAttackSuccessString(String attackSuccessString) { + return this.attackSuccessString = attackSuccessString; + } + + public void setCategory(Category category) { + this.category = category; + } + + public void setCookies(List cookies) { + this.cookies = cookies; + } + + public void setDataflowFile(String dataflowFile) { + this.dataflowFile = dataflowFile; + } + + public void setFormParams(List formParams) { + this.formParams = formParams; + } + + public void setFullURL(String fullURL) { + this.fullURL = fullURL; + } + + public void setGetParams(List getParams) { + this.getParams = getParams; + } + + public void setHeaders(List headers) { + this.headers = headers; + } + + public void setName(String name) { + this.name = name; + } + + public void setQuery(String query) { + this.query = query; + } + + public void setSinkFile(String sinkFile) { + this.sinkFile = sinkFile; + } + + public void setSourceFile(String sourceFile) { + this.sourceFile = sourceFile; + } + + public void setSourceUIType(String sourceUIType) { + this.sourceUIType = sourceUIType; + } + + public void setTemplateFile(String templateFile) { + this.templateFile = templateFile; + } + + public void setType(TestCaseType type) { + this.tcType = type; + } + + public void setUiTemplateFile(String uiTemplateFile) { + this.uiTemplateFile = uiTemplateFile; + } + + public void setNotAutoverifiableReason(String notAutoverifiableReason) { + this.notAutoverifiableReason = notAutoverifiableReason; + } + + public void setVulnerability(boolean isVulnerability) { + this.isVulnerability = isVulnerability; + } + + public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; + for (RequestVariable header : getHeaders()) { + // setSafe() considers whether attack and safe values exist for this parameter before + // setting isSafe true or false. So you don't have to check that here. + header.setSafe(isSafe); + } + for (RequestVariable cookie : getCookies()) { + cookie.setSafe(isSafe); + } + for (RequestVariable getParam : getGetParams()) { + getParam.setSafe(isSafe); + } + for (RequestVariable formParam : getFormParams()) { + formParam.setSafe(isSafe); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + " [category=" + + category + + ", name=" + + name + + ", uiTemplateFile=" + + new File(uiTemplateFile).getName() + + ", templateFile=" + + new File(templateFile).getName() + + ", sourceFile=" + + sourceFile + + ", sourceUIType=" + + sourceUIType + + ", dataflowFile=" + + dataflowFile + + ", sinkFile=" + + sinkFile + + ", fullURL=" + + fullURL + + ", getParams=" + + getParams + + ", headers=" + + headers + + ", cookies=" + + cookies + + ", formParams=" + + formParams + + ", isUnverifiable=" + + isUnverifiable + + ", isVulnerability=" + + isVulnerability + + ", attackSuccessString=" + + attackSuccessString + + ", isSafe=" + + isSafe() + + ", query=" + + query + + ", tcType=" + + tcType + + "]"; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java index 8e01abf7..6ba699da 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -1,23 +1,6 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2021 - */ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.http.client.methods.HttpUriRequest; /** Not a great class name. */ public class TestCaseVerificationResults { From a768390a32f7367ca3480d70e3213d1031beead1 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 17 Jan 2024 07:45:08 -0500 Subject: [PATCH 02/28] Adapting to API changes in HttpClient 5. --- examplescripts_configfiles/sample.xml | 31 +++++++++++++ .../helpers/HttpClientConfig.java | 33 +++++++++----- .../helpers/HttpTestCaseInput.java | 44 ++++++++++-------- .../helpers/RequestVariable.java | 4 +- .../tools/AbstractTestCaseRequest.java | 16 +++---- .../tools/BenchmarkCrawler.java | 45 ++++++++++++------- .../tools/BenchmarkCrawlerVerification.java | 28 +++++++----- .../tools/JerseyTestCaseRequest.java | 25 +++++------ .../tools/RegressionTesting.java | 14 +++--- .../benchmarkutils/tools/ResponseInfo.java | 2 +- .../tools/ServletTestCaseRequest.java | 29 +++++------- .../tools/SpringTestCaseRequest.java | 25 +++++------ 12 files changed, 175 insertions(+), 121 deletions(-) create mode 100644 examplescripts_configfiles/sample.xml diff --git a/examplescripts_configfiles/sample.xml b/examplescripts_configfiles/sample.xml new file mode 100644 index 00000000..996b969b --- /dev/null +++ b/examplescripts_configfiles/sample.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java index fab45866..139edbb5 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java @@ -5,12 +5,21 @@ import java.security.NoSuchAlgorithmException; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +//import org.apache.http.conn.ssl.NoopHostnameVerifier; +//import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +//import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +//import org.apache.http.impl.client.CloseableHttpClient; +//import org.apache.http.impl.client.HttpClients; +//import org.apache.http.ssl.SSLContextBuilder; +import org.apache.hc.core5.ssl.SSLContextBuilder; public class HttpClientConfig extends TestCaseSetup { @@ -23,8 +32,10 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // use the TrustSelfSignedStrategy to allow Self Signed Certificates SSLContext sslContext = - SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); - + SSLContextBuilder.create() + .loadTrustMaterial(null, TrustAllStrategy.INSTANCE) + .build(); + // we can optionally disable hostname verification. // if you don't want to further weaken the security, you don't have to include this. HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); @@ -33,10 +44,12 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // strategy and allow all hosts verifier. SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); - + HttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(connectionFactory).build(); + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket // Factory - return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); + return HttpClients.custom().setConnectionManager(connectionManager).build(); } public void setup() throws TestCaseSetupException { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java index 7e9ff251..6763c9cb 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java @@ -10,14 +10,16 @@ import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; + +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; public abstract class HttpTestCaseInput extends TestCaseInput { @@ -101,10 +103,10 @@ public void setContentFormat(ContentFormatEnum contentFormat) { } /** Defines what parameters in the body will be sent. */ - abstract void buildBodyParameters(HttpRequestBase request); + abstract void buildBodyParameters(HttpUriRequestBase request); /** Defines what cookies will be sent. */ - void buildCookies(HttpRequestBase request) { + void buildCookies(HttpUriRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -116,7 +118,7 @@ void buildCookies(HttpRequestBase request) { } /** Defines what headers will be sent. */ - void buildHeaders(HttpRequestBase request) { + void buildHeaders(HttpUriRequestBase request) { for (RequestVariable header : getHeaders()) { String name = header.getName(); String value = header.getValue(); @@ -141,7 +143,7 @@ public void execute() { httpclient = createAcceptSelfSignedCertificateClient(); } - HttpUriRequest request = buildAttackRequest(); + HttpUriRequestBase request = buildAttackRequest(); // Send the next test case request sendRequest(httpclient, request); @@ -153,21 +155,21 @@ public void execute() { * * @return */ - public HttpUriRequest buildRequest() { + public HttpUriRequestBase buildRequest() { buildQueryString(); - HttpRequestBase request = createRequestInstance(fullURL + query); + HttpUriRequestBase request = createRequestInstance(fullURL + query); buildHeaders(request); buildCookies(request); buildBodyParameters(request); return request; } - public HttpUriRequest buildAttackRequest() { + public HttpUriRequestBase buildAttackRequest() { setSafe(false); return buildRequest(); } - public HttpUriRequest buildSafeRequest() { + public HttpUriRequestBase buildSafeRequest() { setSafe(true); return buildRequest(); } @@ -197,8 +199,10 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // use the TrustSelfSignedStrategy to allow Self Signed Certificates SSLContext sslContext = - SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); - + SSLContextBuilder.create() + .loadTrustMaterial(null, TrustAllStrategy.INSTANCE) + .build(); + // we can optionally disable hostname verification. // if you don't want to further weaken the security, you don't have to include this. HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); @@ -207,10 +211,12 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // strategy and allow all hosts verifier. SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); + HttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(connectionFactory).build(); // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket // Factory - return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); + return HttpClients.custom().setConnectionManager(connectionManager).build(); } @Override diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java index 6660152e..300566f5 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java @@ -3,8 +3,8 @@ import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.message.BasicNameValuePair; @XmlRootElement public class RequestVariable { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java index fcc1f54b..eac83ed4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java @@ -27,8 +27,8 @@ import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; import org.eclipse.persistence.oxm.annotations.XmlReadOnly; import org.owasp.benchmarkutils.helpers.Category; @@ -169,13 +169,13 @@ public AbstractTestCaseRequest() {} // } /** Defines what parameters in the body will be sent. */ - abstract void buildBodyParameters(HttpRequestBase request); + abstract void buildBodyParameters(HttpUriRequestBase request); /** Defines what cookies will be sent. */ - abstract void buildCookies(HttpRequestBase request); + abstract void buildCookies(HttpUriRequestBase request); /** Defines what headers will be sent. */ - abstract void buildHeaders(HttpRequestBase request); + abstract void buildHeaders(HttpUriRequestBase request); /** Defines how to construct URL query string. */ abstract void buildQueryString(); @@ -188,7 +188,7 @@ public AbstractTestCaseRequest() {} */ public HttpUriRequest buildRequest() { buildQueryString(); - HttpRequestBase request = createRequestInstance(fullURL + query); + HttpUriRequestBase request = createRequestInstance(fullURL + query); buildHeaders(request); buildCookies(request); buildBodyParameters(request); @@ -208,9 +208,9 @@ public HttpUriRequest buildSafeRequest() { /** * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. * - * @return an instance of a subclass of HttpRequestBase + * @return an instance of a subclass of HttpUriRequestBase */ - abstract HttpRequestBase createRequestInstance(String URL); + abstract HttpUriRequestBase createRequestInstance(String URL); @XmlAttribute(name = "tcAttackSuccess") public String getAttackSuccessString() { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 169b3775..efff2e2a 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -37,17 +38,19 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.lang.time.StopWatch; -import org.apache.http.HttpEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.util.EntityUtils; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -199,7 +202,9 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient() // use the TrustSelfSignedStrategy to allow Self Signed Certificates SSLContext sslContext = - SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + SSLContextBuilder.create() + .loadTrustMaterial(null, TrustAllStrategy.INSTANCE) + .build(); // we can optionally disable hostname verification. // if you don't want to further weaken the security, you don't have to include this. @@ -209,10 +214,12 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient() // strategy and allow all hosts verifier. SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); - + HttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(connectionFactory).build(); + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket // Factory - return HttpClients.custom().setSSLSocketFactory(connectionFactory).build(); + return HttpClients.custom().setConnectionManager(connectionManager).build(); } /** @@ -229,7 +236,11 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r CloseableHttpResponse response = null; boolean isPost = request instanceof HttpPost; - System.out.println((isPost ? "POST " : "GET ") + request.getURI()); + try { + System.out.println((isPost ? "POST " : "GET ") + request.getUri()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } StopWatch watch = new StopWatch(); watch.start(); @@ -242,7 +253,7 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r try { HttpEntity entity = response.getEntity(); - int statusCode = response.getStatusLine().getStatusCode(); + int statusCode = response.getCode(); responseInfo.setStatusCode(statusCode); int seconds = (int) watch.getTime() / 1000; responseInfo.setTimeInSeconds(seconds); @@ -251,7 +262,7 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r try { responseInfo.setResponseString(EntityUtils.toString(entity)); EntityUtils.consume(entity); - } catch (IOException e) { + } catch (IOException | org.apache.hc.core5.http.ParseException e) { e.printStackTrace(); } } finally { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 0cc474c7..c90f9569 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -30,8 +31,8 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; @@ -228,15 +229,20 @@ private void log(ResponseInfo responseInfo) throws IOException { String.format( "--> (%d : %d sec)%n", responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); - if (isTimingEnabled) { - if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getURI()); - tLogger.println(outputString); - } - } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getURI()); - tLogger.println(outputString); - } + try { + if (isTimingEnabled) { + if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } else { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } /** diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java index ef519dd1..2fdf7a3e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java @@ -18,10 +18,11 @@ package org.owasp.benchmarkutils.tools; import java.io.UnsupportedEncodingException; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -36,7 +37,7 @@ void buildQueryString() { } @Override - HttpRequestBase createRequestInstance(String URL) { + HttpUriRequestBase createRequestInstance(String URL) { // Apparently all Jersey Requests are POSTS. Never any query string params per buildQuery() // above. HttpPost httpPost = new HttpPost(URL); @@ -44,7 +45,7 @@ HttpRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpRequestBase request) { + void buildHeaders(HttpUriRequestBase request) { request.addHeader("Content-Type", "application/xml; charset=utf-8"); for (RequestVariable header : getHeaders()) { String name = header.getName(); @@ -55,7 +56,7 @@ void buildHeaders(HttpRequestBase request) { } @Override - void buildCookies(HttpRequestBase request) { + void buildCookies(HttpUriRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -65,7 +66,7 @@ void buildCookies(HttpRequestBase request) { } @Override - void buildBodyParameters(HttpRequestBase request) { + void buildBodyParameters(HttpUriRequestBase request) { String params = ""; for (RequestVariable field : getFormParams()) { String name = field.getName(); @@ -73,12 +74,8 @@ void buildBodyParameters(HttpRequestBase request) { params += "<" + name + ">" + escapeXML(value) + ""; } params += ""; - try { - StringEntity paramsEnt = new StringEntity(params); - ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); - } catch (UnsupportedEncodingException e) { - System.out.println("Error encoding URL: " + e.getMessage()); - } + StringEntity paramsEnt = new StringEntity(params); + ((BasicClassicHttpRequest) request).setEntity(paramsEnt); } private static String escapeXML(String value) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 5b714cc1..00c9a0c8 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -30,11 +30,11 @@ import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpMessage; -import org.apache.http.ParseException; -import org.apache.http.client.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpMessage; +import org.apache.hc.core5.http.ParseException; /** * Test all supported test cases to verify that the results are as expected and write the report to @@ -181,7 +181,7 @@ public static void genFailedTCFile(List results, St private static void printHttpRequest(HttpMessage request, Logger out) { out.println(request.toString()); - for (Header header : request.getAllHeaders()) { + for (Header header : request.getHeaders()) { out.printf("%s:%s%n", header.getName(), header.getValue()); } if (request instanceof HttpPost) { @@ -192,7 +192,7 @@ private static void printHttpRequest(HttpMessage request, Logger out) { if (entity != null) { out.println(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); } - } catch (ParseException | IOException e) { + } catch (IOException e) { System.out.println("ERROR: Could not parse HttpPost entities"); e.printStackTrace(); } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java index 54acc4ac..428e3bcd 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java @@ -1,6 +1,6 @@ package org.owasp.benchmarkutils.tools; -import org.apache.http.client.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; class ResponseInfo { private String responseString; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java index b24bf7e0..c58aa684 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java @@ -17,16 +17,15 @@ */ package org.owasp.benchmarkutils.tools; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -61,7 +60,7 @@ void buildQueryString() { } @Override - HttpRequestBase createRequestInstance(String URL) { + HttpUriRequestBase createRequestInstance(String URL) { // If there are query parameters, this must be a GET, otherwise a POST. if (getQuery().length() == 0) { return new HttpPost(URL); @@ -71,7 +70,7 @@ HttpRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpRequestBase request) { + void buildHeaders(HttpUriRequestBase request) { for (RequestVariable header : getHeaders()) { String name = header.getName(); String value = header.getValue(); @@ -82,7 +81,7 @@ void buildHeaders(HttpRequestBase request) { @SuppressWarnings("deprecation") @Override - void buildCookies(HttpRequestBase request) { + void buildCookies(HttpUriRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -94,7 +93,7 @@ void buildCookies(HttpRequestBase request) { } @Override - void buildBodyParameters(HttpRequestBase request) { + void buildBodyParameters(HttpUriRequestBase request) { List fields = new ArrayList<>(); for (RequestVariable formParam : getFormParams()) { fields.add(formParam.getNameValuePair()); @@ -102,12 +101,8 @@ void buildBodyParameters(HttpRequestBase request) { // Add the body parameters to the request if there were any if (fields.size() > 0) { - try { - ((HttpEntityEnclosingRequestBase) request) - .setEntity(new UrlEncodedFormEntity(fields)); - } catch (UnsupportedEncodingException e) { - System.out.println("Error encoding URL: " + e.getMessage()); - } + ((BasicClassicHttpRequest) request) + .setEntity(new UrlEncodedFormEntity(fields)); } } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java index a8ce7bd1..d0a4989f 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java @@ -17,11 +17,10 @@ */ package org.owasp.benchmarkutils.tools; -import java.io.UnsupportedEncodingException; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; import org.owasp.benchmarkutils.helpers.RequestVariable; @@ -36,7 +35,7 @@ void buildQueryString() { } @Override - HttpRequestBase createRequestInstance(String URL) { + HttpUriRequestBase createRequestInstance(String URL) { // Apparently all Spring Requests are POSTS. Never any query string params per buildQuery() // above. HttpPost httpPost = new HttpPost(URL); @@ -44,7 +43,7 @@ HttpRequestBase createRequestInstance(String URL) { } @Override - void buildHeaders(HttpRequestBase request) { + void buildHeaders(HttpUriRequestBase request) { request.addHeader("Content-type", "application/json"); for (RequestVariable header : getHeaders()) { String name = header.getName(); @@ -55,7 +54,7 @@ void buildHeaders(HttpRequestBase request) { } @Override - void buildCookies(HttpRequestBase request) { + void buildCookies(HttpUriRequestBase request) { for (RequestVariable cookie : getCookies()) { String name = cookie.getName(); String value = cookie.getValue(); @@ -65,7 +64,7 @@ void buildCookies(HttpRequestBase request) { } @Override - void buildBodyParameters(HttpRequestBase request) { + void buildBodyParameters(HttpUriRequestBase request) { boolean first = true; String params = "{"; for (RequestVariable field : getFormParams()) { @@ -80,11 +79,7 @@ void buildBodyParameters(HttpRequestBase request) { params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); } params += "}"; - try { - StringEntity paramsEnt = new StringEntity(params); - ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); - } catch (UnsupportedEncodingException e) { - System.out.println("Error encoding URL: " + e.getMessage()); - } + StringEntity paramsEnt = new StringEntity(params); + ((BasicClassicHttpRequest) request).setEntity(paramsEnt); } } From a063d394c599636dcd7f07f02eb962aa6522fdc6 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 19 Jan 2024 07:58:14 -0500 Subject: [PATCH 03/28] Resolve remaining merge-related compilation errors. --- .../helpers/HttpGetTestCaseInput.java | 4 +- .../helpers/HttpPostTestCaseInput.java | 18 +++--- .../helpers/HttpTestCaseInput.java | 63 +++++++++++++++++++ .../benchmarkutils/helpers/TestSuite.java | 3 +- .../benchmarkutils/tools/TestCaseRequest.java | 23 +++---- .../tools/TestCaseVerificationResults.java | 22 +++---- 6 files changed, 98 insertions(+), 35 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java index 0f717887..af1bd58a 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java @@ -1,6 +1,6 @@ package org.owasp.benchmarkutils.helpers; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; public class HttpGetTestCaseInput extends HttpTestCaseInput { void buildQueryString() { @@ -20,7 +20,7 @@ void buildQueryString() { } } - void buildBodyParameters(HttpRequestBase request) { + void buildBodyParameters(HttpUriRequestBase request) { // No request body } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java index 08713381..0293e1d1 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java @@ -1,9 +1,10 @@ package org.owasp.benchmarkutils.helpers; import java.io.UnsupportedEncodingException; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; + +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpRequest; public class HttpPostTestCaseInput extends HttpTestCaseInput { @Override @@ -25,7 +26,7 @@ void buildQueryString() { } @Override - void buildBodyParameters(HttpRequestBase request) { + void buildBodyParameters(HttpUriRequestBase request) { boolean first = true; String params = "{"; for (RequestVariable field : getFormParameters()) { @@ -40,11 +41,8 @@ void buildBodyParameters(HttpRequestBase request) { params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); } params += "}"; - try { - StringEntity paramsEnt = new StringEntity(params); - ((HttpEntityEnclosingRequestBase) request).setEntity(paramsEnt); - } catch (UnsupportedEncodingException e) { - System.out.println("Error encoding URL: " + e.getMessage()); - } + StringEntity paramsEnt = new StringEntity(params); + ((BasicClassicHttpRequest) request).setEntity(paramsEnt); + } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java index 6763c9cb..fdc6dd42 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java @@ -1,5 +1,7 @@ package org.owasp.benchmarkutils.helpers; +import java.io.IOException; +import java.net.URISyntaxException; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -16,9 +18,16 @@ import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.TrustAllStrategy; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.owasp.benchmarkutils.tools.ResponseInfo; +import org.apache.commons.lang.time.StopWatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; public abstract class HttpTestCaseInput extends TestCaseInput { @@ -148,6 +157,60 @@ public void execute() { // Send the next test case request sendRequest(httpclient, request); } + + /** + * Issue the requested request, measure the time required to execute, then output both to stdout + * and the global variable timeString the URL tested, the time required to execute and the + * response code. + * + * @param httpclient - The HTTP client to use to make the request + * @param request - THe HTTP request to issue + */ + static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { + ResponseInfo responseInfo = new ResponseInfo(); + responseInfo.setRequestBase(request); + CloseableHttpResponse response = null; + + boolean isPost = request instanceof HttpPost; + try { + System.out.println((isPost ? "POST " : "GET ") + request.getUri()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + StopWatch watch = new StopWatch(); + + watch.start(); + try { + response = httpclient.execute(request); + } catch (IOException e) { + e.printStackTrace(); + } + watch.stop(); + + try { + HttpEntity entity = response.getEntity(); + int statusCode = response.getCode(); + responseInfo.setStatusCode(statusCode); + int seconds = (int) watch.getTime() / 1000; + responseInfo.setTimeInSeconds(seconds); + System.out.printf("--> (%d : %d sec)%n", statusCode, seconds); + + try { + responseInfo.setResponseString(EntityUtils.toString(entity)); + EntityUtils.consume(entity); + } catch (IOException | org.apache.hc.core5.http.ParseException e) { + e.printStackTrace(); + } + } finally { + if (response != null) + try { + response.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return responseInfo; + } /** * TODO: Make this class a POJO TestCase and pass it as an arg to another class TestCaseRequest diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java index 9b7becc3..2786392b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java @@ -21,7 +21,8 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; -import org.apache.http.impl.client.CloseableHttpClient; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.owasp.benchmarkutils.tools.AbstractTestCaseRequest; @XmlRootElement(name = "benchmarkSuite") diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java index 385f4d1d..6ce885dc 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java @@ -26,8 +26,9 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.client.methods.HttpUriRequest; + +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.eclipse.persistence.oxm.annotations.XmlReadOnly; import org.owasp.benchmarkutils.helpers.Category; import org.owasp.benchmarkutils.helpers.CategoryAdapter; @@ -163,17 +164,17 @@ public TestCaseRequest() {} // } /** Defines what parameters in the body will be sent. */ - public void buildBodyParameters(HttpRequestBase request) { + public void buildBodyParameters(HttpUriRequestBase request) { testCaseInput.buildBodyParameters(request); } /** Defines what cookies will be sent. */ - public void buildCookies(HttpRequestBase request) { + public void buildCookies(HttpUriRequestBase request) { testCaseInput.buildCookies(request); } /** Defines what headers will be sent. */ - public void buildHeaders(HttpRequestBase request) { + public void buildHeaders(HttpUriRequestBase request) { testCaseInput.buildHeaders(request); } @@ -188,21 +189,21 @@ public void buildQueryString() { * * @return */ - public HttpUriRequest buildRequest() { + public HttpUriRequestBase buildRequest() { buildQueryString(); - HttpRequestBase request = createRequestInstance(fullURL + query); + HttpUriRequestBase request = createRequestInstance(fullURL + query); buildHeaders(request); buildCookies(request); buildBodyParameters(request); return request; } - public HttpUriRequest buildAttackRequest() { + public HttpUriRequestBase buildAttackRequest() { setSafe(false); return buildRequest(); } - public HttpUriRequest buildSafeRequest() { + public HttpUriRequestBase buildSafeRequest() { setSafe(true); return buildRequest(); } @@ -210,9 +211,9 @@ public HttpUriRequest buildSafeRequest() { /** * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. * - * @return an instance of a subclass of HttpRequestBase + * @return an instance of a subclass of HttpUriRequestBase */ - abstract HttpRequestBase createRequestInstance(String URL); + abstract HttpUriRequestBase createRequestInstance(String URL); @XmlAttribute(name = "tcAttackSuccess") public String getAttackSuccessString() { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java index 6ba699da..f83c6ebe 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -1,6 +1,6 @@ package org.owasp.benchmarkutils.tools; -import org.apache.http.client.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; /** Not a great class name. */ public class TestCaseVerificationResults { @@ -15,15 +15,15 @@ public class TestCaseVerificationResults { private boolean isPassed; - private HttpUriRequest attackRequest; + private HttpUriRequestBase attackRequest; - private HttpUriRequest safeRequest; + private HttpUriRequestBase safeRequest; private AbstractTestCaseRequest requestTemplate; public TestCaseVerificationResults( - HttpUriRequest attackRequest, - HttpUriRequest safeRequest, + HttpUriRequestBase attackRequest, + HttpUriRequestBase safeRequest, AbstractTestCaseRequest requestTemplate, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue) { @@ -39,8 +39,8 @@ public TestCaseVerificationResults( } public TestCaseVerificationResults( - HttpUriRequest attackRequest, - HttpUriRequest safeRequest, + HttpUriRequestBase attackRequest, + HttpUriRequestBase safeRequest, AbstractTestCaseRequest requestTemplate, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue, @@ -98,19 +98,19 @@ public boolean isPassed() { return isPassed; } - public HttpUriRequest getAttackRequest() { + public HttpUriRequestBase getAttackRequest() { return attackRequest; } - public void setAttackRequest(HttpUriRequest attackRequest) { + public void setAttackRequest(HttpUriRequestBase attackRequest) { this.attackRequest = attackRequest; } - public HttpUriRequest getSafeRequest() { + public HttpUriRequestBase getSafeRequest() { return safeRequest; } - public void setSafeRequest(HttpUriRequest safeRequest) { + public void setSafeRequest(HttpUriRequestBase safeRequest) { this.safeRequest = safeRequest; } From 4b451c21714ea02194315fc430a66382936ffe46 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 14 Feb 2024 03:55:36 -0500 Subject: [PATCH 04/28] Modified the XML parser that reads the crawler XML files to support command-line inputs. --- library/pom.xml | 96 ++++ .../CliArgExecutableTestCaseInput.java | 102 ++++ .../CliFileExecutableTestCaseInput.java | 70 +++ .../benchmarkutils/entities/CliRequest.java | 45 ++ .../benchmarkutils/entities/CliTestCase.java | 53 ++ .../entities}/ContentFormatEnum.java | 2 +- .../entities/ExecutableTestCaseInput.java | 86 ++++ .../entities}/FileCopyConfig.java | 6 +- .../entities}/HttpClientConfig.java | 28 +- .../entities}/HttpGetTestCaseInput.java | 12 +- .../entities}/HttpPostTestCaseInput.java | 13 +- .../benchmarkutils/entities/HttpTestCase.java | 49 ++ .../entities}/HttpTestCaseInput.java | 139 +++-- .../entities}/JerseyTestCase.java | 4 +- .../entities}/RequestVariable.java | 4 +- .../entities}/ResponseInfo.java | 4 +- .../entities}/ServletTestCase.java | 4 +- .../entities}/SpringTestCase.java | 4 +- .../entities}/Sqlite3Config.java | 6 +- .../StdinExecutableTestCaseInput.java | 65 +++ .../entities/TcpSocketTestCaseInput.java | 43 ++ .../benchmarkutils/entities/TestCase.java | 338 ++++++++++++ .../entities/TestCaseInput.java | 59 +++ .../entities/TestCaseSetup.java | 11 + .../entities}/TestCaseSetupException.java | 2 +- .../benchmarkutils/entities/TestSuite.java | 97 ++++ .../benchmarkutils/entities/jaxb.properties | 1 + .../helpers/CategoryAdapter.java | 0 .../CliArgExecutableTestCaseInput.java | 23 - .../CliFileExecutableTestCaseInput.java | 37 -- .../helpers/ExecutableTestCaseInput.java | 88 ---- .../helpers/StdinExecutableTestCaseInput.java | 42 -- .../benchmarkutils/helpers/TestCase.java | 179 ------- .../benchmarkutils/helpers/TestCaseInput.java | 27 - .../benchmarkutils/helpers/TestCaseSetup.java | 9 - .../benchmarkutils/helpers/TestSuite.java | 91 ---- .../owasp/benchmarkutils/helpers/Utils.java | 10 +- .../benchmarkutils/score/TestCaseResult.java | 4 +- .../tools/AbstractTestCaseRequest.java | 482 ----------------- .../tools/BenchmarkCrawler.java | 193 +++++-- .../tools/BenchmarkCrawlerVerification.java | 421 +++++++++------ .../tools/CalculateToolCodeBlocksSupport.java | 190 +++---- .../tools/JerseyTestCaseRequest.java | 90 ---- .../tools/RegressionTesting.java | 99 ++-- .../tools/ServletTestCaseRequest.java | 108 ---- .../tools/SpringTestCaseRequest.java | 85 --- .../benchmarkutils/tools/TestCaseRequest.java | 485 ------------------ .../TestCaseRequestFileParseException.java | 2 +- .../tools/TestCaseVerificationResults.java | 57 +- 49 files changed, 1874 insertions(+), 2191 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/ContentFormatEnum.java (75%) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/FileCopyConfig.java (89%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/HttpClientConfig.java (81%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/HttpGetTestCaseInput.java (66%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/HttpPostTestCaseInput.java (79%) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/HttpTestCaseInput.java (70%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/JerseyTestCase.java (90%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/RequestVariable.java (96%) rename {plugin/src/main/java/org/owasp/benchmarkutils/tools => library/src/main/java/org/owasp/benchmarkutils/entities}/ResponseInfo.java (92%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/ServletTestCase.java (90%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/SpringTestCase.java (90%) rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/Sqlite3Config.java (94%) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java rename {plugin/src/main/java/org/owasp/benchmarkutils/helpers => library/src/main/java/org/owasp/benchmarkutils/entities}/TestCaseSetupException.java (78%) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TestSuite.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/jaxb.properties rename {plugin => library}/src/main/java/org/owasp/benchmarkutils/helpers/CategoryAdapter.java (100%) delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java rename plugin/src/main/java/org/owasp/benchmarkutils/{helpers => tools}/TestCaseRequestFileParseException.java (96%) diff --git a/library/pom.xml b/library/pom.xml index 81c5c79f..8d874944 100755 --- a/library/pom.xml +++ b/library/pom.xml @@ -12,6 +12,90 @@ 1.3 + + + + commons-lang + commons-lang + 2.6 + + + + com.fasterxml.jackson.core + jackson-annotations + ${version.fasterxml.jackson} + + + + com.fasterxml.jackson.core + jackson-core + ${version.fasterxml.jackson} + + + + com.fasterxml.jackson.core + jackson-databind + ${version.fasterxml.jackson} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${version.fasterxml.jackson} + + + + com.google.guava + guava + 33.0.0-jre + + + + javax.xml.bind + jaxb-api + 2.4.0-b180830.0359 + + + + org.eclipse.persistence + org.eclipse.persistence.core + ${version.eclipse.persistence} + + + + + org.eclipse.persistence + org.eclipse.persistence.moxy + ${version.eclipse.persistence} + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3 + + + + org.apache.httpcomponents.core5 + httpcore5 + 5.2.4 + + + + javax.validation + validation-api + 2.0.1.Final + + + + xml-apis + xml-apis + + 1.4.01 + + + + benchmarkutils @@ -19,9 +103,21 @@ ${basedir}/src/main/resources + + ${basedir}/src/main/java + + **/*.java + + + + 2.16.1 + + 2.7.14 + + diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java new file mode 100644 index 00000000..e059bdc3 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java @@ -0,0 +1,102 @@ +package org.owasp.benchmarkutils.entities; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("CliArg") +// @XmlType(name = "CliArgExecutableTestCaseInput") +public class CliArgExecutableTestCaseInput extends ExecutableTestCaseInput { + + List args; + + void beforeMarshal(Marshaller marshaller) { + // System.out.println("Before marshal"); + if (args != null && args.isEmpty()) args = null; + } + + void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { + // System.out.println("After unmarshal"); + if (args == null) args = new ArrayList(); + } + + @XmlElementWrapper(name = "args") + @XmlElement(name = "arg", required = true) + @NotNull + public List getArgs() { + return args; + } + + public void setArgs(List args) { + this.args = args; + } + + public void addArg(RequestVariable arg) { + if (this.args == null) { + this.args = new ArrayList<>(); + } + this.args.add(arg); + } + + public CliRequest buildAttackRequest() { + // ArrayList executeArgs = new ArrayList<>(); + // // FIXME: This will break if the command string has arguments that contain spaces. + // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); + // executeArgs.addAll(getArgs()); + + setSafe(false); + return new CliRequest(getCommand(), getArgs()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), getArgs()); + } + + public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; + for (RequestVariable arg : getArgs()) { + // setSafe() considers whether attack and safe values exist for this parameter before + // setting isSafe true or false. So you don't have to check that here. + arg.setSafe(isSafe); + } + } + + // @Override + // public String toString() { + // return this.getClass().getSimpleName() + " [args=" + getArgs() + "]"; + // } + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[" + + "command=" + + getCommand() + + ", args=" + + getArgs() + + "]"; + } + + // public void execute() { + // List executeArgs = Arrays.asList(getCommand()); + // + // // crawlArgs.extend([arg1]) + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // + // executeArgs.add(getPayload()); + // ProcessBuilder builder = new ProcessBuilder(executeArgs); + // final Process process = builder.start(); + // int exitValue = process.waitFor(); + // System.out.printf("Program terminated with return code: %s%n", exitValue); + // } + +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java new file mode 100644 index 00000000..13439b3e --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java @@ -0,0 +1,70 @@ +package org.owasp.benchmarkutils.entities; + +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlElement; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("CliFile") +public class CliFileExecutableTestCaseInput extends ExecutableTestCaseInput { + + List fileArgs; + + @XmlElement(name = "fileArg", required = true) + @NotNull + public List getFileArgs() { + return fileArgs; + } + + public CliRequest buildAttackRequest() { + // ArrayList executeArgs = new ArrayList<>(); + // // FIXME: This will break if the command string has arguments that contain spaces. + // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); + // executeArgs.addAll(getArgs()); + + setSafe(false); + return new CliRequest(getCommand(), getFileArgs()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), getFileArgs()); + } + + public void setSafe(boolean isSafe) { + // this.isSafe = isSafe; + for (RequestVariable arg : getFileArgs()) { + // setSafe() considers whether attack and safe values exist for this parameter before + // setting isSafe true or false. So you don't have to check that here. + arg.setSafe(isSafe); + } + } + + // public void execute() { + // List executeArgs = Arrays.asList(getCommand()); + // + // File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); + // + // // args_file = 'args_file.txt' + // // with open(TEST_SUITE_DIR + args_file, 'w') as f: + // // f.write(arg1) + // // crawlArgs.extend([args_file]) + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // + // executeArgs.add(getPayload()); + // executeArgs.add("-f"); + // executeArgs.add(argsFile.getPath()); + // try (PrintWriter writer = new PrintWriter(new FileWriter(argsFile))) { + // writer.print(getPayload()); + // } + // + // ProcessBuilder builder = new ProcessBuilder(executeArgs); + // final Process process = builder.start(); + // int exitValue = process.waitFor(); + // System.out.printf("Program terminated with return code: %s%n", exitValue); + // } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java new file mode 100644 index 00000000..26ecebfb --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -0,0 +1,45 @@ +package org.owasp.benchmarkutils.entities; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class CliRequest { + String command; + + List args; + + public CliRequest(String command, List args) { + super(); + this.command = command; + this.args = args; + } + + public CliRequest(String command, RequestVariable arg) { + super(); + this.command = command; + this.args = new ArrayList(Arrays.asList(arg)); + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public List getArgs() { + return args; + } + + public void setArgs(List args) { + this.args = args; + } + + // public List getExecuteArgs() { + // List executeArgs = Arrays.asList(getCommand().split(" ")); + // executeArgs.addAll(getArgs()); + // return executeArgs; + // } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java new file mode 100644 index 00000000..2dda4236 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java @@ -0,0 +1,53 @@ +package org.owasp.benchmarkutils.entities; + +import javax.xml.bind.annotation.XmlElement; + +public class CliTestCase extends TestCase { + + private ExecutableTestCaseInput testCaseInput; + + @Override + @XmlElement(name = "input", required = true) + public ExecutableTestCaseInput getTestCaseInput() { + return testCaseInput; + } + + // public void execute() { + // + // // FIXME: What would the executable testcase's attackRequest look like? + // HttpUriRequest attackRequest = getTestCaseInput().buildAttackRequest(); + // HttpUriRequest safeRequest = getTestCaseInput().buildSafeRequest(); + // + // // Send the next test case request with its attack payload + // ResponseInfo attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); + // responseInfoList.add(attackPayloadResponseInfo); + // + // // Log the response + // log(attackPayloadResponseInfo); + // + // ResponseInfo safePayloadResponseInfo = null; + // if (!isUnverifiable()) { + // // Send the next test case request with its safe payload + // safePayloadResponseInfo = sendRequest(httpclient, safeRequest); + // responseInfoList.add(safePayloadResponseInfo); + // + // // Log the response + // log(safePayloadResponseInfo); + // } + // + // TestCaseVerificationResults result = + // new TestCaseVerificationResults( + // attackRequest, + // safeRequest, + // this, + // attackPayloadResponseInfo, + // safePayloadResponseInfo); + // + // // Verify the response + // if (RegressionTesting.isTestingEnabled) { + // handleResponse(result); + // } + // + // return result; + // } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java similarity index 75% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java index 21082ac9..be00bb65 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ContentFormatEnum.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java @@ -1,4 +1,4 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.*; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java new file mode 100644 index 00000000..18477b46 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java @@ -0,0 +1,86 @@ +package org.owasp.benchmarkutils.entities; + +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlElement; + +public abstract class ExecutableTestCaseInput extends TestCaseInput { + + private String command; + + // private String payload; + + @XmlElement(name = "command", required = true) + @NotNull + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public abstract CliRequest buildAttackRequest(); + + public abstract CliRequest buildSafeRequest(); + + // public String getPayload() { + // return payload; + // } + // + // public void setPayload(String payload) { + // this.payload = payload; + // } + + // public void execute() { + // // Execute the appropriate command string + // List executeArgs = Arrays.asList(command); + // // if (isSingleApplication) { + // // executeArgs = Arrays.asList("benchmark-python.py", "-t", this.getName()); + // // } else { + // // executeArgs = Arrays.asList("testcode/" + "JulietPyTest" + this.getName() + + // ".py"); + // // } + // + // if (payloadType == PayloadType.CLI_ARG) { + // // crawlArgs.extend([arg1]) + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // + // executeArgs.add(payload); + // ProcessBuilder builder = new ProcessBuilder(executeArgs); + // final Process process = builder.start(); + // int exitValue = process.waitFor(); + // System.out.printf("Program terminated with return code: %s%n", exitValue); + // + // } else if (payloadType == PayloadType.CLI_FILE) { + // // args_file = 'args_file.txt' + // // with open(TEST_SUITE_DIR + args_file, 'w') as f: + // // f.write(arg1) + // // crawlArgs.extend([args_file]) + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // } else if (payloadType == PayloadType.CLI_STDIN) { + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // #child.interact() + // // child.sendline(arg1) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // } else { + // // TODO: Throw an exception? + // System.out.printf("ERROR: Unrecognized payload type: %s%n", payloadType); + // } + // } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "[" + "command=" + command + "]"; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java similarity index 89% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java index b8b9cc74..09182a32 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/FileCopyConfig.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java @@ -1,4 +1,4 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import com.google.common.io.Files; import java.io.File; @@ -20,6 +20,10 @@ public void setup() throws TestCaseSetupException { } } + public void close() throws TestCaseSetupException { + // Do nothing + } + @XmlAttribute(name = "source", required = true) @NotNull public String getSourceFile() { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java similarity index 81% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java index 139edbb5..d562c917 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpClientConfig.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java @@ -1,11 +1,11 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; +import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; - import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; @@ -13,12 +13,6 @@ import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.client5.http.ssl.TrustAllStrategy; -//import org.apache.http.conn.ssl.NoopHostnameVerifier; -//import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -//import org.apache.http.conn.ssl.TrustSelfSignedStrategy; -//import org.apache.http.impl.client.CloseableHttpClient; -//import org.apache.http.impl.client.HttpClients; -//import org.apache.http.ssl.SSLContextBuilder; import org.apache.hc.core5.ssl.SSLContextBuilder; public class HttpClientConfig extends TestCaseSetup { @@ -35,7 +29,7 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() SSLContextBuilder.create() .loadTrustMaterial(null, TrustAllStrategy.INSTANCE) .build(); - + // we can optionally disable hostname verification. // if you don't want to further weaken the security, you don't have to include this. HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); @@ -44,9 +38,11 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // strategy and allow all hosts verifier. SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); - HttpClientConnectionManager connectionManager = - PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(connectionFactory).build(); - + HttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(connectionFactory) + .build(); + // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket // Factory return HttpClients.custom().setConnectionManager(connectionManager).build(); @@ -62,4 +58,12 @@ public void setup() throws TestCaseSetupException { } } } + + public void close() throws TestCaseSetupException { + try { + httpclient.close(); + } catch (IOException e) { + throw new TestCaseSetupException("Could not close HttpClientConfig for test case", e); + } + } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java similarity index 66% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java index af1bd58a..c1727750 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpGetTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java @@ -1,7 +1,11 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; +import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; +@XmlDiscriminatorValue("HttpGet") +// @XmlType(name = "HttpGetTestCaseInput") public class HttpGetTestCaseInput extends HttpTestCaseInput { void buildQueryString() { setQueryString(""); @@ -23,4 +27,10 @@ void buildQueryString() { void buildBodyParameters(HttpUriRequestBase request) { // No request body } + + @Override + HttpUriRequestBase createRequestInstance(String url) { + HttpGet httpGet = new HttpGet(url); + return httpGet; + } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java similarity index 79% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java index 0293e1d1..99890885 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpPostTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java @@ -1,11 +1,13 @@ -package org.owasp.benchmarkutils.helpers; - -import java.io.UnsupportedEncodingException; +package org.owasp.benchmarkutils.entities; +import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; +@XmlDiscriminatorValue("HttpPost") +// @XmlType(name = "HttpPostTestCaseInput") public class HttpPostTestCaseInput extends HttpTestCaseInput { @Override void buildQueryString() { @@ -43,6 +45,11 @@ void buildBodyParameters(HttpUriRequestBase request) { params += "}"; StringEntity paramsEnt = new StringEntity(params); ((BasicClassicHttpRequest) request).setEntity(paramsEnt); + } + @Override + HttpUriRequestBase createRequestInstance(String url) { + HttpPost httpPost = new HttpPost(url); + return httpPost; } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java new file mode 100644 index 00000000..7d8a0f6a --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java @@ -0,0 +1,49 @@ +package org.owasp.benchmarkutils.entities; + +public class HttpTestCase extends TestCase { + + // @Override + // @XmlElement(name = "input", required = true) + // public HttpTestCaseInput getTestCaseInput() { + // return testCaseInput; + // } + + // public void execute() { + // + // // FIXME: What would the executable testcase's attackRequest look like? + // HttpUriRequest attackRequest = getTestCaseInput().buildAttackRequest(); + // HttpUriRequest safeRequest = getTestCaseInput().buildSafeRequest(); + // + // // Send the next test case request with its attack payload + // ResponseInfo attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); + // responseInfoList.add(attackPayloadResponseInfo); + // + // // Log the response + // log(attackPayloadResponseInfo); + // + // ResponseInfo safePayloadResponseInfo = null; + // if (!isUnverifiable()) { + // // Send the next test case request with its safe payload + // safePayloadResponseInfo = sendRequest(httpclient, safeRequest); + // responseInfoList.add(safePayloadResponseInfo); + // + // // Log the response + // log(safePayloadResponseInfo); + // } + // + // TestCaseVerificationResults result = + // new TestCaseVerificationResults( + // attackRequest, + // safeRequest, + // this, + // attackPayloadResponseInfo, + // safePayloadResponseInfo); + // + // // Verify the response + // if (RegressionTesting.isTestingEnabled) { + // handleResponse(result); + // } + // + // return result; + // } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java similarity index 70% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java index fdc6dd42..0e6190d9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/HttpTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java @@ -1,4 +1,4 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import java.io.IOException; import java.net.URISyntaxException; @@ -6,13 +6,22 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.List; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlElement; - +import javax.xml.bind.annotation.XmlElementWrapper; +import org.apache.commons.lang.time.StopWatch; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; @@ -21,20 +30,11 @@ import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.ssl.SSLContextBuilder; -import org.owasp.benchmarkutils.tools.ResponseInfo; -import org.apache.commons.lang.time.StopWatch; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; public abstract class HttpTestCaseInput extends TestCaseInput { private String url; - @XmlElement(required = true) protected ContentFormatEnum contentFormat; private String queryString; @@ -47,9 +47,25 @@ public abstract class HttpTestCaseInput extends TestCaseInput { private List headers; - private static CloseableHttpClient httpclient; + private static CloseableHttpClient httpClient; + + void beforeMarshal(Marshaller marshaller) { + // System.out.println("Before marshal"); + if (formParameters != null && formParameters.isEmpty()) formParameters = null; + if (getParameters != null && getParameters.isEmpty()) getParameters = null; + if (cookies != null && cookies.isEmpty()) cookies = null; + if (headers != null && headers.isEmpty()) headers = null; + } + + void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { + // System.out.println("After unmarshal"); + if (formParameters == null) formParameters = new ArrayList(); + if (getParameters == null) getParameters = new ArrayList(); + if (cookies == null) cookies = new ArrayList(); + if (headers == null) headers = new ArrayList(); + } - @XmlAttribute(name = "URL", required = true) + @XmlElement(name = "url", required = true) @NotNull public String getUrl() { return url; @@ -59,30 +75,38 @@ public String getQueryString() { return queryString; } - @XmlElement(name = "formparam") + @XmlElementWrapper(name = "formParams", required = false) + @XmlElement(name = "formParam", required = false) @NotNull public List getFormParameters() { return formParameters; } - @XmlElement(name = "getparam") + @XmlElementWrapper(name = "getParams", required = false) + @XmlElement(name = "getParam", required = false) @NotNull public List getGetParameters() { return getParameters; } - @XmlElement(name = "cookie") + @XmlElementWrapper(name = "cookies", required = false) + @XmlElement(name = "cookie", required = false) @NotNull public List getCookies() { return cookies; } - @XmlElement(name = "header") + @XmlElementWrapper(name = "headers", required = false) + @XmlElement(name = "header", required = false) @NotNull public List getHeaders() { return headers; } + public void setUrl(String url) { + this.url = url; + } + public void setQueryString(String queryString) { this.queryString = queryString; } @@ -103,6 +127,35 @@ public void setHeaders(List headers) { this.headers = headers; } + public void addFormParameter(RequestVariable formParameter) { + if (this.formParameters == null) { + this.formParameters = new ArrayList<>(); + } + this.formParameters.add(formParameter); + } + + public void addGetParameter(RequestVariable getParameter) { + if (this.getParameters == null) { + this.getParameters = new ArrayList<>(); + } + this.getParameters.add(getParameter); + } + + public void addCookie(RequestVariable cookie) { + if (this.cookies == null) { + this.cookies = new ArrayList<>(); + } + this.cookies.add(cookie); + } + + public void addHeader(RequestVariable header) { + if (this.headers == null) { + this.headers = new ArrayList<>(); + } + this.headers.add(header); + } + + @XmlElement public ContentFormatEnum getContentFormat() { return contentFormat; } @@ -144,20 +197,20 @@ String urlEncode(String input) { /** Defines how to construct URL query string. */ abstract void buildQueryString(); - public void execute() { - // TODO: Not thread-safe - // TODO: We never close this resource, which is poor form - // TODO: What about other setup tasks, like starting a DB server or app server? - if (httpclient == null) { - httpclient = createAcceptSelfSignedCertificateClient(); - } + // public void execute() { + // // TODO: Not thread-safe + // // TODO: We never close this resource, which is poor form + // // TODO: What about other setup tasks, like starting a DB server or app server? + // if (httpclient == null) { + // httpclient = createAcceptSelfSignedCertificateClient(); + // } + // + // HttpUriRequestBase request = buildAttackRequest(); + // + // // Send the next test case request + // sendRequest(httpclient, request); + // } - HttpUriRequestBase request = buildAttackRequest(); - - // Send the next test case request - sendRequest(httpclient, request); - } - /** * Issue the requested request, measure the time required to execute, then output both to stdout * and the global variable timeString the URL tested, the time required to execute and the @@ -173,10 +226,10 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r boolean isPost = request instanceof HttpPost; try { - System.out.println((isPost ? "POST " : "GET ") + request.getUri()); - } catch (URISyntaxException e) { - e.printStackTrace(); - } + System.out.println((isPost ? "POST " : "GET ") + request.getUri()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } StopWatch watch = new StopWatch(); watch.start(); @@ -220,13 +273,15 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r */ public HttpUriRequestBase buildRequest() { buildQueryString(); - HttpUriRequestBase request = createRequestInstance(fullURL + query); + HttpUriRequestBase request = createRequestInstance(getUrl() + getQueryString()); buildHeaders(request); buildCookies(request); buildBodyParameters(request); return request; } + abstract HttpUriRequestBase createRequestInstance(String url); + public HttpUriRequestBase buildAttackRequest() { setSafe(false); return buildRequest(); @@ -247,10 +302,10 @@ public void setSafe(boolean isSafe) { for (RequestVariable cookie : getCookies()) { cookie.setSafe(isSafe); } - for (RequestVariable getParam : getGetParams()) { + for (RequestVariable getParam : getGetParameters()) { getParam.setSafe(isSafe); } - for (RequestVariable formParam : getFormParams()) { + for (RequestVariable formParam : getFormParameters()) { formParam.setSafe(isSafe); } } @@ -265,7 +320,7 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() SSLContextBuilder.create() .loadTrustMaterial(null, TrustAllStrategy.INSTANCE) .build(); - + // we can optionally disable hostname verification. // if you don't want to further weaken the security, you don't have to include this. HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); @@ -274,8 +329,10 @@ private CloseableHttpClient createAcceptSelfSignedCertificateClient() // strategy and allow all hosts verifier. SSLConnectionSocketFactory connectionFactory = new SSLConnectionSocketFactory(sslContext, allowAllHosts); - HttpClientConnectionManager connectionManager = - PoolingHttpClientConnectionManagerBuilder.create().setSSLSocketFactory(connectionFactory).build(); + HttpClientConnectionManager connectionManager = + PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(connectionFactory) + .build(); // finally create the HttpClient using HttpClient factory methods and assign the SSL Socket // Factory diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCase.java similarity index 90% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCase.java index b98baafc..359b6d00 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/JerseyTestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCase.java @@ -15,11 +15,11 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("JERSEYWS") -public class JerseyTestCase extends TestCaseInput { +public class JerseyTestCase extends TestCase { public JerseyTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java similarity index 96% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java index 300566f5..70fd2d21 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java @@ -1,8 +1,9 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.message.BasicNameValuePair; @@ -104,6 +105,7 @@ public NameValuePair getNameValuePair() { return new BasicNameValuePair(name, value); } + @XmlTransient public boolean isSafe() { return isSafe; } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java similarity index 92% rename from plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index 428e3bcd..7dfe9375 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -1,8 +1,8 @@ -package org.owasp.benchmarkutils.tools; +package org.owasp.benchmarkutils.entities; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -class ResponseInfo { +public class ResponseInfo { private String responseString; private int seconds; private int statusCode; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCase.java similarity index 90% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCase.java index 52bdeed4..676e78f5 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ServletTestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCase.java @@ -15,11 +15,11 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("SERVLET") -public class ServletTestCase extends TestCaseInput { +public class ServletTestCase extends TestCase { public ServletTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCase.java similarity index 90% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCase.java index 9102e1e9..81e39865 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCase.java @@ -15,11 +15,11 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("SPRINGWS") -public class SpringTestCase extends TestCaseInput { +public class SpringTestCase extends TestCase { public SpringTestCase() {} } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java similarity index 94% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java index 90dca553..c40f9d17 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Sqlite3Config.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java @@ -1,4 +1,4 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import java.io.BufferedReader; import java.io.FileNotFoundException; @@ -60,6 +60,10 @@ public void setup() throws TestCaseSetupException { } } + public void close() throws TestCaseSetupException { + // Do nothing + } + @XmlAttribute(name = "script") @NotNull public String getScriptFile() { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java new file mode 100644 index 00000000..e6f3637b --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java @@ -0,0 +1,65 @@ +package org.owasp.benchmarkutils.entities; + +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("Stdin") +public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { + public RequestVariable getStdinData() { + // FIXME + return null; + } + + public CliRequest buildAttackRequest() { + // ArrayList executeArgs = new ArrayList<>(); + // // FIXME: This will break if the command string has arguments that contain spaces. + // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); + // executeArgs.addAll(getArgs()); + + setSafe(false); + return new CliRequest(getCommand(), getStdinData()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), getStdinData()); + } + + public void setSafe(boolean isSafe) { + // // this.isSafe = isSafe; + // for (RequestVariable arg : getArgs()) { + // // setSafe() considers whether attack and safe values exist for this parameter + // before + // // setting isSafe true or false. So you don't have to check that here. + // arg.setSafe(isSafe); + // } + } + + // public void execute() { + // List executeArgs = Arrays.asList(getCommand()); + // + // File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); + // + // // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) + // // #child.interact() + // // child.sendline(arg1) + // // child.logfile = sys.stdout + // // child.expect(pexpect.EOF) + // // child.close() + // // print("Return code: %d" % child.exitstatus) + // + // ProcessBuilder builder = new ProcessBuilder(executeArgs); + // final Process process = builder.start(); + // OutputStream stdin = process.getOutputStream(); + // InputStream stdout = process.getInputStream(); + // + // BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)); + // BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin)); + // + // writer.write(getPayload()); + // writer.flush(); + // writer.close(); + // + // int exitValue = process.waitFor(); + // System.out.printf("Program terminated with return code: %s%n", exitValue); + // } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java new file mode 100644 index 00000000..2c9608eb --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java @@ -0,0 +1,43 @@ +package org.owasp.benchmarkutils.entities; + +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("TcpSocket") +public class TcpSocketTestCaseInput extends ExecutableTestCaseInput { + + public RequestVariable getTcpSocketData() { + // FIXME + return null; + } + + public CliRequest buildAttackRequest() { + // ArrayList executeArgs = new ArrayList<>(); + // // FIXME: This will break if the command string has arguments that contain spaces. + // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); + // executeArgs.addAll(getArgs()); + + setSafe(false); + return new CliRequest(getCommand(), getTcpSocketData()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), getTcpSocketData()); + } + + public void setSafe(boolean isSafe) { + // // this.isSafe = isSafe; + // for (RequestVariable arg : getArgs()) { + // // setSafe() considers whether attack and safe values exist for this parameter + // before + // // setting isSafe true or false. So you don't have to check that here. + // arg.setSafe(isSafe); + // } + } + + // public void execute() { + // // FIXME: Not yet implemented + // + // // System.out.printf("Program terminated with return code: %s%n", exitValue); + // } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java new file mode 100644 index 00000000..3f6d465d --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java @@ -0,0 +1,338 @@ +package org.owasp.benchmarkutils.entities; + +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElements; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import org.eclipse.persistence.oxm.annotations.XmlPath; +import org.eclipse.persistence.oxm.annotations.XmlPaths; +import org.owasp.benchmarkutils.helpers.Category; +import org.owasp.benchmarkutils.helpers.CategoryAdapter; + +public class TestCase { + + private Category category; + + private String dataflowFile; + + private String name; + + private int number; + + private String notAutoverifiableReason; + + private String sinkFile; + + private String sourceFile; + + private String sourceUIType; + + private String templateFile; + + private String type; + + private String UITemplateFile; + + private boolean isVulnerability; + + private String attackSuccessString; + + private TestCaseInput testCaseInput; + + @XmlElements({ + @XmlElement(type = HttpClientConfig.class), + @XmlElement(type = FileCopyConfig.class), + @XmlElement(type = Sqlite3Config.class) + }) + @XmlPaths({ + @XmlPath("fee[@type='HttpClientConfig']"), + @XmlPath("fee[@type='FileCopyConfig']"), + @XmlPath("fee[@type='Sqlite3Config']") + }) + private TestCaseSetup testCaseSetup; + + // FIXME: These fields are not in the crawler config file, but they need to be captured when + // running the verification crawler because we retrieve request details from them to write to + // failedTestCases.txt. + // private TestCaseRequest attackTestCaseRequest; + // private TestCaseRequest safeTestCaseRequest; + + static final Pattern lastIntPattern = Pattern.compile("[^0-9]+([0-9]+)$"); + + public TestCase() { + super(); + } + + // @XmlAttribute(name = "tcCategory", required = true) + @XmlAttribute(name = "Category", required = true) + @XmlJavaTypeAdapter(CategoryAdapter.class) + @NotNull + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public void setDataflowFile(String dataflowFile) { + this.dataflowFile = dataflowFile; + } + + public void setNotAutoverifiableReason(String notAutoverifiableReason) { + this.notAutoverifiableReason = notAutoverifiableReason; + } + + public void setSinkFile(String sinkFile) { + this.sinkFile = sinkFile; + } + + public void setSourceFile(String sourceFile) { + this.sourceFile = sourceFile; + } + + public void setSourceUIType(String sourceUIType) { + this.sourceUIType = sourceUIType; + } + + public void setTemplateFile(String templateFile) { + this.templateFile = templateFile; + } + + public void setUITemplateFile(String uITemplateFile) { + UITemplateFile = uITemplateFile; + } + + public void setVulnerability(boolean isVulnerability) { + this.isVulnerability = isVulnerability; + } + + public void setAttackSuccessString(String attackSuccessString) { + this.attackSuccessString = attackSuccessString; + } + + // @XmlAttribute(name = "tcDataflowFile", required = true) + @XmlAttribute(name = "DataflowFile", required = true) + @NotNull + public String getDataflowFile() { + return dataflowFile; + } + + // @XmlAttribute(name = "tcName", required = true) + @XmlAttribute(name = "Name", required = true) + @NotNull + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + + // Auto extract the test case number from the name. + Matcher matcher = lastIntPattern.matcher(name); + if (matcher.find()) { + String someNumberStr = matcher.group(1); + this.number = Integer.parseInt(someNumberStr); + } else { + System.out.println( + "Warning: TestCaseRequest.setName() invoked with test case name: " + + name + + " that doesn't end with a test case number."); + } + } + + public int getNumber() { + return number; + } + + // @XmlAttribute(name = "tcNotAutoverifiable") + @XmlAttribute(name = "NotAutoverifiable") + public String getNotAutoverifiableReason() { + return notAutoverifiableReason; + } + + // @XmlAttribute(name = "tcSinkFile", required = true) + @XmlAttribute(name = "SinkFile", required = true) + @NotNull + public String getSinkFile() { + return sinkFile; + } + + // @XmlAttribute(name = "tcSourceFile", required = true) + @XmlAttribute(name = "SourceFile", required = true) + @NotNull + public String getSourceFile() { + return sourceFile; + } + + // @XmlAttribute(name = "tcSourceUIType", required = true) + @XmlAttribute(name = "SourceUIType", required = true) + @NotNull + public String getSourceUIType() { + return sourceUIType; + } + + // @XmlAttribute(name = "tcTemplateFile", required = true) + @XmlAttribute(name = "TemplateFile", required = true) + @NotNull + public String getTemplateFile() { + return templateFile; + } + + // @XmlAttribute(name = "tcUITemplateFile", required = true) + @XmlAttribute(name = "UITemplateFile", required = true) + @NotNull + public String getUITemplateFile() { + return UITemplateFile; + } + + // @XmlAttribute(name = "tcType", required = true) + // @XmlReadOnly + // @NotNull + // public String getType() { + // return type; + // } + + // @XmlAttribute(name = "tcAttackSuccess") + @XmlAttribute(name = "AttackSuccess") + public String getAttackSuccessString() { + return this.attackSuccessString; + } + + // @XmlAttribute(name = "tcVulnerable", required = true) + @XmlAttribute(name = "Vulnerability", required = true) + public boolean isVulnerability() { + return isVulnerability; + } + + @XmlElement(name = "Input", required = true) + public TestCaseInput getTestCaseInput() { + return testCaseInput; + } + + // public getTestCaseExecutor() { + // + // } + + public boolean isUnverifiable() { + return getNotAutoverifiableReason() != null; + } + + // public TestCaseRequest getAttackTestCaseRequest() { + // return attackTestCaseRequest; + // } + // + // public void setAttackTestCaseRequest(TestCaseRequest attackTestCaseRequest) { + // this.attackTestCaseRequest = attackTestCaseRequest; + // } + // + // public TestCaseRequest getSafeTestCaseRequest() { + // return safeTestCaseRequest; + // } + // + // public void setSafeTestCaseRequest(TestCaseRequest safeTestCaseRequest) { + // this.safeTestCaseRequest = safeTestCaseRequest; + // } + + public void setTestCaseInput(TestCaseInput testCaseInput) { + this.testCaseInput = testCaseInput; + } + + public TestCaseSetup getTestCaseSetup() { + return testCaseSetup; + } + + public void setTestCaseSetup(TestCaseSetup testCaseSetup) { + this.testCaseSetup = testCaseSetup; + } + + // public void execute() { + // + // this.getTestCaseInput().execute(this.getName()); + // } + // + // // FIXME: Maybe not a good idea to move this here + // public void executeAndVerify() { + // TestCaseInput testCaseInput = getTestCaseInput(); + // + // if (testCaseInput instanceof HttpTestCaseInput) { + // HttpTestCaseInput httpTestCaseInput = (HttpTestCaseInput) testCaseInput; + // HttpUriRequest attackRequest = httpTestCaseInput.buildAttackRequest(); + // HttpUriRequest safeRequest = httpTestCaseInput.buildSafeRequest(); + // + // // Send the next test case request with its attack payload + // ResponseInfo attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); + // responseInfoList.add(attackPayloadResponseInfo); + // + // // Log the response + // log(attackPayloadResponseInfo); + // + // ResponseInfo safePayloadResponseInfo = null; + // if (!isUnverifiable()) { + // // Send the next test case request with its safe payload + // safePayloadResponseInfo = sendRequest(httpclient, safeRequest); + // responseInfoList.add(safePayloadResponseInfo); + // + // // Log the response + // log(safePayloadResponseInfo); + // } + // + // TestCaseVerificationResults result = + // new TestCaseVerificationResults( + // attackRequest, + // safeRequest, + // this, + // attackPayloadResponseInfo, + // safePayloadResponseInfo); + // results.add(result); + // } + // + // // Verify the response + // if (RegressionTesting.isTestingEnabled) { + // handleResponse(result); + // } + // } + + @Override + public String toString() { + return "TestCase [" + + "category=" + + category + + ", dataflowFile=" + + dataflowFile + + ", name=" + + name + + ", notAutoverifiableReason=" + + notAutoverifiableReason + + ", sinkFile=" + + sinkFile + + ", sourceFile=" + + sourceFile + + ", sourceUIType=" + + sourceUIType + + ", templateFile=" + + templateFile + + ", UITemplateFile=" + + UITemplateFile + + ", isVulnerability=" + + isVulnerability + + ", " + + testCaseInput + + "]"; + } + + public static Comparator getNameComparator() { + return new Comparator() { + + @Override + public int compare(TestCase o1, TestCase o2) { + if (!o1.name.equalsIgnoreCase(o2.name)) return o1.name.compareTo(o2.name); + return 0; + } + }; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java new file mode 100644 index 00000000..e10f0d25 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java @@ -0,0 +1,59 @@ +package org.owasp.benchmarkutils.entities; + +import javax.xml.bind.annotation.XmlSeeAlso; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; + +@XmlSeeAlso({ + CliArgExecutableTestCaseInput.class, + CliFileExecutableTestCaseInput.class, + ExecutableTestCaseInput.class, + HttpTestCaseInput.class, + HttpGetTestCaseInput.class, + HttpPostTestCaseInput.class, + StdinExecutableTestCaseInput.class, + TcpSocketTestCaseInput.class +}) +@XmlDiscriminatorNode("@type") +public abstract class TestCaseInput { + + public enum TestCaseInputType { + HttpGet, + HttpPost, + CliArg + } + + private String testCaseName; + + // private TestCaseInputType type; + + abstract void setSafe(boolean isSafe); + + public String getTestCaseName() { + return testCaseName; + } + + public void setTestCaseName(String testCaseName) { + this.testCaseName = testCaseName; + } + + // @XmlAttribute(name = "inputType", required = true) + // @XmlReadOnly + // @NotNull + // public TestCaseInputType getType() { + // return type; + // } + // + // public void setType(TestCaseInputType type) { + // this.type = type; + // } + + // abstract HttpUriRequestBase buildAttackRequest(); + // + // abstract HttpUriRequestBase buildSafeRequest(); + + @Override + public String toString() { + return this.getClass().getSimpleName(); + // return this.getClass().getSimpleName() + " [" + "type=" + type + "]"; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java new file mode 100644 index 00000000..aaf9afaf --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java @@ -0,0 +1,11 @@ +package org.owasp.benchmarkutils.entities; + +import javax.xml.bind.annotation.XmlSeeAlso; + +@XmlSeeAlso({Sqlite3Config.class, FileCopyConfig.class, HttpClientConfig.class}) +public abstract class TestCaseSetup { + + public abstract void setup() throws TestCaseSetupException; + + public abstract void close() throws TestCaseSetupException; +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java similarity index 78% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java index 9e8ef6e1..e5671bc1 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetupException.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java @@ -1,4 +1,4 @@ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; public class TestCaseSetupException extends Exception { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestSuite.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestSuite.java new file mode 100644 index 00000000..233d5daf --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestSuite.java @@ -0,0 +1,97 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https:/owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2021 + */ +package org.owasp.benchmarkutils.entities; + +import java.util.List; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; + +@XmlRootElement(name = "TestSuite") +public class TestSuite { + private List testCases; + + private String name; + + private String version; + + CloseableHttpClient httpclient = null; + + @XmlElement(name = "TestCase") + public List getTestCases() { + return testCases; + } + + public void setTestCases(List testCases) { + this.testCases = testCases; + } + + @XmlAttribute(required = true) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @XmlAttribute(required = true) + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + // public void execute() { + // + // // TODO: Maybe create a TestCaseContext class to hold the httpclient, and setup tasks + // like + // // starting the DB server and app server. Pass this object as the argument to + // execute(). + // // It is annoying that the httpclient field of the class will be null if there are no + // Http + // // test cases in the test suite. It would be better if we had a context class for + // each + // // TestCaseInput class. + // // The XML doc that defines the testcases could specify the config class by class + // name. The + // // testcase class would need to instantiate the named class accessible via a singleton + // map. + // // Then, each testcase can call a shared setup method and access shared state via its + // own + // // config field. I think this means that the DB server would be started in the middle + // of + // // parsing the XML doc, though. + // + // TestCaseContext context = TestCase.getContext(testCase); + // testCase.execute(context); + // + // // Execute all of the test cases + // for (TestCase testCase : this.getTestCases()) { + // testCase.execute(); + // } + // } + + @Override + public String toString() { + return "TestSuite [testCases=" + testCases + "]"; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/jaxb.properties b/library/src/main/java/org/owasp/benchmarkutils/entities/jaxb.properties new file mode 100644 index 00000000..b2979f90 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/jaxb.properties @@ -0,0 +1 @@ +javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CategoryAdapter.java b/library/src/main/java/org/owasp/benchmarkutils/helpers/CategoryAdapter.java similarity index 100% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/CategoryAdapter.java rename to library/src/main/java/org/owasp/benchmarkutils/helpers/CategoryAdapter.java diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java deleted file mode 100644 index f51140b7..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliArgExecutableTestCaseInput.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import java.util.Arrays; -import java.util.List; - -public class CliArgExecutableTestCaseInput extends ExecutableTestCaseInput { - public void execute() { - List executeArgs = Arrays.asList(getCommand()); - - // crawlArgs.extend([arg1]) - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - - executeArgs.add(getPayload()); - ProcessBuilder builder = new ProcessBuilder(executeArgs); - final Process process = builder.start(); - int exitValue = process.waitFor(); - System.out.printf("Program terminated with return code: %s%n", exitValue); - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java deleted file mode 100644 index 039c2378..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/CliFileExecutableTestCaseInput.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import java.io.File; -import java.io.FileWriter; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; - -public class CliFileExecutableTestCaseInput extends ExecutableTestCaseInput { - public void execute() { - List executeArgs = Arrays.asList(getCommand()); - - File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); - - // args_file = 'args_file.txt' - // with open(TEST_SUITE_DIR + args_file, 'w') as f: - // f.write(arg1) - // crawlArgs.extend([args_file]) - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - - executeArgs.add(getPayload()); - executeArgs.add("-f"); - executeArgs.add(argsFile.getPath()); - try (PrintWriter writer = new PrintWriter(new FileWriter(argsFile))) { - writer.print(getPayload()); - } - - ProcessBuilder builder = new ProcessBuilder(executeArgs); - final Process process = builder.start(); - int exitValue = process.waitFor(); - System.out.printf("Program terminated with return code: %s%n", exitValue); - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java deleted file mode 100644 index dcb72185..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/ExecutableTestCaseInput.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import java.util.Arrays; -import java.util.List; - -public class ExecutableTestCaseInput implements TestCaseInput { - - private String command; - - private String payload; - - public String getCommand() { - return command; - } - - public void setCommand(String command) { - this.command = command; - } - - public String getPayload() { - return payload; - } - - public void setPayload(String payload) { - this.payload = payload; - } - - public void execute() { - // Execute the appropriate command string - List executeArgs = Arrays.asList(command); - // if (isSingleApplication) { - // executeArgs = Arrays.asList("benchmark-python.py", "-t", this.getName()); - // } else { - // executeArgs = Arrays.asList("testcode/" + "JulietPyTest" + this.getName() + ".py"); - // } - - if (payloadType == PayloadType.CLI_ARG) { - // crawlArgs.extend([arg1]) - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - - executeArgs.add(payload); - ProcessBuilder builder = new ProcessBuilder(executeArgs); - final Process process = builder.start(); - int exitValue = process.waitFor(); - System.out.printf("Program terminated with return code: %s%n", exitValue); - - } else if (payloadType == PayloadType.CLI_FILE) { - // args_file = 'args_file.txt' - // with open(TEST_SUITE_DIR + args_file, 'w') as f: - // f.write(arg1) - // crawlArgs.extend([args_file]) - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - } else if (payloadType == PayloadType.CLI_STDIN) { - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // #child.interact() - // child.sendline(arg1) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - } else { - // TODO: Throw an exception? - System.out.printf("ERROR: Unrecognized payload type: %s%n", payloadType); - } - } - - public void setSafe(boolean isSafe) { - // this.isSafe = isSafe; - for (RequestVariable arg : getCliArgs()) { - // setSafe() considers whether attack and safe values exist for this parameter before - // setting isSafe true or false. So you don't have to check that here. - arg.setSafe(isSafe); - } - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + " [payload=" + payload + "]"; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java deleted file mode 100644 index 010bd2ba..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/StdinExecutableTestCaseInput.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.util.Arrays; -import java.util.List; - -public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { - public void execute() { - List executeArgs = Arrays.asList(getCommand()); - - File argsFile = new File(TEST_SUITE_DIR, "args_file.txt"); - - // child = pexpect.spawn("python", cwd=TEST_SUITE_DIR, args=crawlArgs) - // #child.interact() - // child.sendline(arg1) - // child.logfile = sys.stdout - // child.expect(pexpect.EOF) - // child.close() - // print("Return code: %d" % child.exitstatus) - - ProcessBuilder builder = new ProcessBuilder(executeArgs); - final Process process = builder.start(); - OutputStream stdin = process.getOutputStream(); - InputStream stdout = process.getInputStream(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(stdout)); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin)); - - writer.write(getPayload()); - writer.flush(); - writer.close(); - - int exitValue = process.waitFor(); - System.out.printf("Program terminated with return code: %s%n", exitValue); - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java deleted file mode 100644 index eee2b027..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java +++ /dev/null @@ -1,179 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlElements; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.eclipse.persistence.oxm.annotations.XmlPath; -import org.eclipse.persistence.oxm.annotations.XmlPaths; -import org.eclipse.persistence.oxm.annotations.XmlReadOnly; - -@XmlRootElement -public class TestCase { - - private Category category; - - private String dataflowFile; - - private String name; - - private String notAutoverifiableReason; - - private String sinkFile; - - private String sourceFile; - - private String sourceUIType; - - private String templateFile; - - private String type; - - private String UITemplateFile; - - private boolean isVulnerable; - - private TestCaseInput testCaseInput; - - @XmlElements({ - @XmlElement(type = HttpClientConfig.class), - @XmlElement(type = FileCopyConfig.class), - @XmlElement(type = Sqlite3Config.class) - }) - @XmlPaths({ - @XmlPath("fee[@type='HttpClientConfig']"), - @XmlPath("fee[@type='FileCopyConfig']"), - @XmlPath("fee[@type='Sqlite3Config']") - }) - private TestCaseSetup testCaseSetup; - - private TestCaseRequest attackTestCaseRequest; - - private TestCaseRequest safeTestCaseRequest; - - @XmlAttribute(name = "tcCategory", required = true) - @XmlJavaTypeAdapter(CategoryAdapter.class) - @NotNull - public Category getCategory() { - return category; - } - - @XmlAttribute(name = "tcDataflowFile", required = true) - @NotNull - public String getDataflowFile() { - return dataflowFile; - } - - @XmlAttribute(name = "tcName", required = true) - @NotNull - public String getName() { - return name; - } - - @XmlAttribute(name = "tcNotAutoverifiable") - public String getNotAutoverifiableReason() { - return notAutoverifiableReason; - } - - @XmlAttribute(name = "tcSinkFile", required = true) - @NotNull - public String getSinkFile() { - return sinkFile; - } - - @XmlAttribute(name = "tcSourceFile", required = true) - @NotNull - public String getSourceFile() { - return sourceFile; - } - - @XmlAttribute(name = "tcSourceUIType", required = true) - @NotNull - public String getSourceUIType() { - return sourceUIType; - } - - @XmlAttribute(name = "tcTemplateFile", required = true) - @NotNull - public String getTemplateFile() { - return templateFile; - } - - @XmlAttribute(name = "tcUITemplateFile", required = true) - @NotNull - public String getUITemplateFile() { - return UITemplateFile; - } - - @XmlAttribute(name = "tcType", required = true) - @XmlReadOnly - @NotNull - public String getType() { - return type; - } - - @XmlAttribute(name = "tcVulnerable", required = true) - public boolean isVulnerable() { - return isVulnerable; - } - - @XmlElement(name = "input", required = true) - public TestCaseInput getTestCaseInput() { - return testCaseInput; - } - - public TestCaseRequest getAttackTestCaseRequest() { - return attackTestCaseRequest; - } - - public void setAttackTestCaseRequest(TestCaseRequest attackTestCaseRequest) { - this.attackTestCaseRequest = attackTestCaseRequest; - } - - public TestCaseRequest getSafeTestCaseRequest() { - return safeTestCaseRequest; - } - - public void setSafeTestCaseRequest(TestCaseRequest safeTestCaseRequest) { - this.safeTestCaseRequest = safeTestCaseRequest; - } - - public void setTestCaseInput(TestCaseInput testCaseInput) { - this.testCaseInput = testCaseInput; - } - - public void execute() { - - this.getTestCaseInput().execute(this.getName()); - } - - @Override - public String toString() { - return "TestCase [" - + "category=" - + category - + ", dataflowFile=" - + dataflowFile - + ", name=" - + name - + ", notAutoverifiableReason=" - + notAutoverifiableReason - + ", sinkFile=" - + sinkFile - + ", sourceFile=" - + sourceFile - + ", sourceUIType=" - + sourceUIType - + ", templateFile=" - + templateFile - + ", UITemplateFile=" - + UITemplateFile - + ", isVulnerable=" - + isVulnerable - + ", " - + testCaseInput - + "]"; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java deleted file mode 100644 index 7645d53b..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseInput.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import javax.xml.bind.annotation.XmlSeeAlso; - -@XmlSeeAlso({ - CliArgExecutableTestCaseInput.class, - CliFileExecutableTestCaseInput.class, - HttpTestCaseInput.class, - StdinExecutableTestCaseInput.class, - TCPSocketTestCaseInput.class -}) -public abstract class TestCaseInput { - - private String testCaseName; - - abstract void execute(String testCaseName); - - abstract void setSafe(boolean isSafe); - - public String getTestCaseName() { - return testCaseName; - } - - public void setTestCaseName(String testCaseName) { - this.testCaseName = testCaseName; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java deleted file mode 100644 index e1620477..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseSetup.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.owasp.benchmarkutils.helpers; - -import javax.xml.bind.annotation.XmlSeeAlso; - -@XmlSeeAlso({Sqlite3Config.class, FileCopyConfig.class, HttpClientConfig.class}) -public abstract class TestCaseSetup { - - abstract void setup() throws TestCaseSetupException; -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java deleted file mode 100644 index 2786392b..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https:/owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2021 - */ -package org.owasp.benchmarkutils.helpers; - -import java.util.List; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.owasp.benchmarkutils.tools.AbstractTestCaseRequest; - -@XmlRootElement(name = "benchmarkSuite") -public class TestSuite { - private List testCases; - - private String name; - - private String version; - - CloseableHttpClient httpclient = null; - - @XmlElement(name = "benchmarkTest") - public List getTestCases() { - return testCases; - } - - public void setTestCases(List testCases) { - this.testCases = testCases; - } - - @XmlAttribute(name = "testsuite", required = true) - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @XmlAttribute(name = "version", required = true) - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public void execute() { - - // TODO: Maybe create a TestCaseContext class to hold the httpclient, and setup tasks like - // starting the DB server and app server. Pass this object as the argument to execute(). - // It is annoying that the httpclient field of the class will be null if there are no Http - // test cases in the test suite. It would be better if we had a context class for each - // TestCaseInput class. - // The XML doc that defines the testcases could specify the config class by class name. The - // testcase class would need to instantiate the named class accessible via a singleton map. - // Then, each testcase can call a shared setup method and access shared state via its own - // config field. I think this means that the DB server would be started in the middle of - // parsing the XML doc, though. - - TestCaseContext context = TestCase.getContext(testCase); - testCase.execute(context); - - // Execute all of the test cases - for (TestCase testCase : this.getTestCases()) { - testCase.execute(); - } - } - - @Override - public String toString() { - return "TestSuite [testCases=" + testCases + "]"; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 7b347245..095b00b2 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -44,6 +44,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilderFactory; @@ -53,6 +54,8 @@ import javax.xml.transform.sax.SAXSource; import org.apache.commons.io.FileUtils; import org.eclipse.persistence.jaxb.JAXBContextFactory; +import org.owasp.benchmarkutils.entities.TestSuite; +import org.owasp.benchmarkutils.tools.TestCaseRequestFileParseException; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -196,8 +199,6 @@ public static TestSuite parseHttpFile(File file) throws JAXBException, FileNotFoundException, SAXException, ParserConfigurationException { - TestSuite testSuite = null; - // Disable XXE SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); @@ -213,9 +214,10 @@ public static TestSuite parseHttpFile(File file) JAXBContext context = JAXBContextFactory.createContext(new Class[] {TestSuite.class}, null); Unmarshaller unmarshaller = context.createUnmarshaller(); unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); - testSuite = (TestSuite) unmarshaller.unmarshal(xmlSource); + JAXBElement testSuite = + (JAXBElement) unmarshaller.unmarshal(xmlSource); - return testSuite; + return testSuite.getValue(); } /** diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/score/TestCaseResult.java b/plugin/src/main/java/org/owasp/benchmarkutils/score/TestCaseResult.java index 0244c0d9..a684a84e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/score/TestCaseResult.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/score/TestCaseResult.java @@ -17,7 +17,7 @@ */ package org.owasp.benchmarkutils.score; -import org.owasp.benchmarkutils.tools.AbstractTestCaseRequest; +import org.owasp.benchmarkutils.entities.TestCase; /* This class represents a single test case result. It documents the expected result (real), * and the actual result (result). @@ -49,7 +49,7 @@ public TestCaseResult() { * * @param request The request object used to access this test case. */ - public TestCaseResult(AbstractTestCaseRequest request) { + public TestCaseResult(TestCase request) { this.testCaseName = request.getName(); this.number = request.getNumber(); this.truePositive = request.isVulnerability(); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java deleted file mode 100644 index eac83ed4..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java +++ /dev/null @@ -1,482 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details - * - * @author Juan Gama - * @created 2017 - */ -package org.owasp.benchmarkutils.tools; - -import java.io.File; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; -import org.eclipse.persistence.oxm.annotations.XmlReadOnly; -import org.owasp.benchmarkutils.helpers.Category; -import org.owasp.benchmarkutils.helpers.CategoryAdapter; -import org.owasp.benchmarkutils.helpers.RequestVariable; - -@XmlSeeAlso({ - ServletTestCaseRequest.class, - JerseyTestCaseRequest.class, - SpringTestCaseRequest.class -}) -@XmlDiscriminatorNode("@tcType") -public abstract class AbstractTestCaseRequest { - - /* - * The 1st three are Java. - */ - public enum TestCaseType { - JERSEYWS, - SERVLET, - SPRINGWS, - NODEEXPRESS - } - - public static Comparator getNameComparator() { - return new Comparator() { - - @Override - public int compare(AbstractTestCaseRequest o1, AbstractTestCaseRequest o2) { - if (!o1.name.equalsIgnoreCase(o2.name)) return o1.name.compareTo(o2.name); - return 0; - } - }; - } - - private Category category; - private List cookies = new ArrayList(); - private String dataflowFile; - private List formParams = new ArrayList(); - private String fullURL; - private List getParams = new ArrayList(); - private List headers = new ArrayList(); - private String notAutoverifiableReason; - private boolean isUnverifiable; - private boolean isVulnerability; - private String attackSuccessString; - private String name; - private String query; - private String sinkFile; - private String sourceFile; - private String sourceUIType; - private TestCaseType tcType; - private String templateFile; - private String uiTemplateFile; - - public AbstractTestCaseRequest() {} - - // /** - // * This class contains enough information to generate an HttpUriRequest for a generated - // test - // * case. - // * - // * @param fullURL - // * @param tcType - // * @param category - // * @param name - // * @param uiTemplateFile - // * @param templateFile - // * @param sourceFile - // * @param sourceUIType - // * @param dataflowFile - // * @param sinkFile - // * @param isUnverifiable - // * @param isVulnerability - // * @param attackSuccessString - // * @param headers - // * @param cookies - // * @param getParams - // * @param formParams - // */ - // public AbstractTestCaseRequest( - // String fullURL, - // TestCaseType tcType, - // Category category, - // String name, - // String uiTemplateFile, - // String templateFile, - // String sourceFile, - // String sourceUIType, - // String dataflowFile, - // String sinkFile, - // boolean isUnverifiable, - // boolean isVulnerability, - // String attackSuccessString, - // List headers, - // List cookies, - // List getParams, - // List formParams) { - // super(); - // this.fullURL = fullURL; - // this.tcType = tcType; - // this.category = category; - // this.name = name; - // this.uiTemplateFile = uiTemplateFile; - // this.templateFile = templateFile; - // this.sourceFile = sourceFile; - // this.sourceUIType = sourceUIType; - // this.dataflowFile = dataflowFile; - // this.sinkFile = sinkFile; - // this.isUnverifiable = isUnverifiable; - // this.isVulnerability = isVulnerability; - // this.attackSuccessString = attackSuccessString; - // this.headers = headers; - // this.cookies = cookies; - // this.getParams = getParams; - // this.formParams = formParams; - // - // // // Figure out if ANY of the values in the request include an attack value. - // // this.isSafe = true; - // // // Bitwise AND is done on all parameters isSafe() values. If ANY of them are - // // unsafe, isSafe - // // // set to False. - // // for (RequestVariable header : getHeaders()) { - // // this.isSafe &= header.isSafe(); - // // } - // // - // // for (RequestVariable cookie : getCookies()) { - // // this.isSafe &= cookie.isSafe(); - // // } - // // - // // for (RequestVariable getParam : getGetParams()) { - // // this.isSafe &= getParam.isSafe(); - // // } - // // - // // for (RequestVariable formParam : getFormParams()) { - // // this.isSafe &= formParam.isSafe(); - // // } - // } - - /** Defines what parameters in the body will be sent. */ - abstract void buildBodyParameters(HttpUriRequestBase request); - - /** Defines what cookies will be sent. */ - abstract void buildCookies(HttpUriRequestBase request); - - /** Defines what headers will be sent. */ - abstract void buildHeaders(HttpUriRequestBase request); - - /** Defines how to construct URL query string. */ - abstract void buildQueryString(); - - /** - * TODO: Make this class a POJO TestCase and pass it as an arg to another class TestCaseRequest - * that can build an actual HttpUriRequest. - * - * @return - */ - public HttpUriRequest buildRequest() { - buildQueryString(); - HttpUriRequestBase request = createRequestInstance(fullURL + query); - buildHeaders(request); - buildCookies(request); - buildBodyParameters(request); - return request; - } - - public HttpUriRequest buildAttackRequest() { - setSafe(false); - return buildRequest(); - } - - public HttpUriRequest buildSafeRequest() { - setSafe(true); - return buildRequest(); - } - - /** - * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. - * - * @return an instance of a subclass of HttpUriRequestBase - */ - abstract HttpUriRequestBase createRequestInstance(String URL); - - @XmlAttribute(name = "tcAttackSuccess") - public String getAttackSuccessString() { - return this.attackSuccessString; - } - - @XmlAttribute(name = "tcCategory", required = true) - @XmlJavaTypeAdapter(CategoryAdapter.class) - @NotNull - public Category getCategory() { - return this.category; - } - - @XmlElement(name = "cookie") - @NotNull - public List getCookies() { - return this.cookies; - } - - @XmlAttribute(name = "tcDataflowFile", required = true) - @NotNull - public String getDataflowFile() { - return this.dataflowFile; - } - - @XmlElement(name = "formparam") - @NotNull - public List getFormParams() { - return this.formParams; - } - - @XmlAttribute(name = "URL", required = true) - @NotNull - public String getFullURL() { - return this.fullURL; - } - - @XmlElement(name = "getparam") - @NotNull - public List getGetParams() { - return this.getParams; - } - - @XmlElement(name = "header") - @NotNull - public List getHeaders() { - return this.headers; - } - - @XmlAttribute(name = "tcName", required = true) - @NotNull - public String getName() { - return this.name; - } - - @XmlTransient - public String getQuery() { - return this.query; - } - - @XmlAttribute(name = "tcSinkFile", required = true) - @NotNull - public String getSinkFile() { - return this.sinkFile; - } - - @XmlAttribute(name = "tcSourceFile", required = true) - @NotNull - public String getSourceFile() { - return this.sourceFile; - } - - @XmlAttribute(name = "tcSourceUIType", required = true) - @NotNull - public String getSourceUIType() { - return this.sourceUIType; - } - - @XmlAttribute(name = "tcTemplateFile", required = true) - @NotNull - public String getTemplateFile() { - return this.templateFile; - } - - @XmlAttribute(name = "tcType", required = true) - @XmlReadOnly - @NotNull - public TestCaseType getType() { - return this.tcType; - } - - @XmlAttribute(name = "tcUITemplateFile", required = true) - @NotNull - public String getUiTemplateFile() { - return this.uiTemplateFile; - } - - public boolean isUnverifiable() { - return getNotAutoverifiableReason() != null; - } - - @XmlAttribute(name = "tcNotAutoverifiable") - public String getNotAutoverifiableReason() { - return this.notAutoverifiableReason; - } - - @XmlAttribute(name = "tcVulnerable", required = true) - public boolean isVulnerability() { - return this.isVulnerability; - } - - public boolean isSafe() { - - boolean isSafe = true; - // Bitwise AND is done on all parameters isSafe() values. If ANY of them are unsafe, isSafe - // set to False. - for (RequestVariable header : getHeaders()) { - isSafe &= header.isSafe(); - } - - for (RequestVariable cookie : getCookies()) { - isSafe &= cookie.isSafe(); - } - - for (RequestVariable getParam : getGetParams()) { - isSafe &= getParam.isSafe(); - } - - for (RequestVariable formParam : getFormParams()) { - isSafe &= formParam.isSafe(); - } - - return isSafe; - } - - public String setAttackSuccessString(String attackSuccessString) { - return this.attackSuccessString = attackSuccessString; - } - - public void setCategory(Category category) { - this.category = category; - } - - public void setCookies(List cookies) { - this.cookies = cookies; - } - - public void setDataflowFile(String dataflowFile) { - this.dataflowFile = dataflowFile; - } - - public void setFormParams(List formParams) { - this.formParams = formParams; - } - - public void setFullURL(String fullURL) { - this.fullURL = fullURL; - } - - public void setGetParams(List getParams) { - this.getParams = getParams; - } - - public void setHeaders(List headers) { - this.headers = headers; - } - - public void setName(String name) { - this.name = name; - } - - public void setQuery(String query) { - this.query = query; - } - - public void setSinkFile(String sinkFile) { - this.sinkFile = sinkFile; - } - - public void setSourceFile(String sourceFile) { - this.sourceFile = sourceFile; - } - - public void setSourceUIType(String sourceUIType) { - this.sourceUIType = sourceUIType; - } - - public void setTemplateFile(String templateFile) { - this.templateFile = templateFile; - } - - public void setType(TestCaseType type) { - this.tcType = type; - } - - public void setUiTemplateFile(String uiTemplateFile) { - this.uiTemplateFile = uiTemplateFile; - } - - public void setNotAutoverifiableReason(String notAutoverifiableReason) { - this.notAutoverifiableReason = notAutoverifiableReason; - } - - public void setVulnerability(boolean isVulnerability) { - this.isVulnerability = isVulnerability; - } - - public void setSafe(boolean isSafe) { - // this.isSafe = isSafe; - for (RequestVariable header : getHeaders()) { - // setSafe() considers whether attack and safe values exist for this parameter before - // setting isSafe true or false. So you don't have to check that here. - header.setSafe(isSafe); - } - for (RequestVariable cookie : getCookies()) { - cookie.setSafe(isSafe); - } - for (RequestVariable getParam : getGetParams()) { - getParam.setSafe(isSafe); - } - for (RequestVariable formParam : getFormParams()) { - formParam.setSafe(isSafe); - } - } - - @Override - public String toString() { - return this.getClass().getSimpleName() - + " [category=" - + category - + ", name=" - + name - + ", uiTemplateFile=" - + new File(uiTemplateFile).getName() - + ", templateFile=" - + new File(templateFile).getName() - + ", sourceFile=" - + sourceFile - + ", sourceUIType=" - + sourceUIType - + ", dataflowFile=" - + dataflowFile - + ", sinkFile=" - + sinkFile - + ", fullURL=" - + fullURL - + ", getParams=" - + getParams - + ", headers=" - + headers - + ", cookies=" - + cookies - + ", formParams=" - + formParams - + ", isUnverifiable=" - + isUnverifiable - + ", isVulnerability=" - + isVulnerability - + ", attackSuccessString=" - + attackSuccessString - + ", isSafe=" - + isSafe() - + ", query=" - + query - + ", tcType=" - + tcType - + "]"; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index efff2e2a..e5c1b7fa 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -25,6 +25,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; @@ -57,15 +58,23 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.owasp.benchmarkutils.entities.CliRequest; +import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; +import org.owasp.benchmarkutils.entities.HttpTestCaseInput; +import org.owasp.benchmarkutils.entities.RequestVariable; +import org.owasp.benchmarkutils.entities.ResponseInfo; +import org.owasp.benchmarkutils.entities.TestCase; +import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.helpers.Categories; -import org.owasp.benchmarkutils.helpers.TestCase; -import org.owasp.benchmarkutils.helpers.TestSuite; import org.owasp.benchmarkutils.helpers.Utils; import org.owasp.benchmarkutils.score.BenchmarkScore; @Mojo(name = "run-crawler", requiresProject = false, defaultPhase = LifecyclePhase.COMPILE) public class BenchmarkCrawler extends AbstractMojo { + // Intended to be a Singleton. So when instantiated, put it here: + static BenchmarkCrawler thisInstance = null; + @Parameter(property = "crawlerFile") String pluginFilenameParam; @@ -119,15 +128,16 @@ void load() { new Categories(categoriesFileStream); this.testSuite = Utils.parseHttpFile(this.theCrawlerFile); + System.out.println("Test suite: " + this.testSuite); Collections.sort( this.testSuite.getTestCases(), - AbstractTestCaseRequest.getNameComparator()); // Probably not necessary + TestCase.getNameComparator()); // Probably not necessary // This allows a single test case to be tested, rather than all of them. if (selectedTestCaseName != null) { - for (AbstractTestCaseRequest request : this.testSuite.getTestCases()) { + for (TestCase request : this.testSuite.getTestCases()) { if (request.getName().equals(selectedTestCaseName)) { - List requests = new ArrayList<>(); + List requests = new ArrayList<>(); requests.add(request); this.testSuite = new TestSuite(); this.testSuite.setTestCases(requests); @@ -155,44 +165,55 @@ public void setCrawlerFile(File theCrawlerFile) { * @throws Exception */ protected void crawl(TestSuite testSuite) throws Exception { - // FIXME: Use try-with-resources to close this resource before returning - CloseableHttpClient httpclient = createAcceptSelfSignedCertificateClient(); - long start = System.currentTimeMillis(); - - // Iterate through TestCase objects instead. - // Execution of the test case depends on the type of TestCase.getTestCaseInput() - // Where should the code that executes the test case go? - // Maybe I need a TestCaseExecuter that takes a TestCaseInput to initialize. - // for (TestCase testCase : testSuite.getTestCases()) { - // if (testCase.getTestCaseInput() instanceof HttpTestCaseInput) { - // HttpUriRequest request = testCase.getAttackTestCaseRequest().buildAttackRequest(); - // sendRequest(httpclient, request); - // } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { - // // Execute the testCase using exec() - // } - // } - - // FIXME: If there are any HttpTestCaseInput, create a CloseableHttpClient - for (TestCase testCase : testSuite.getTestCases()) { - testCase.getTestCaseExecutor().execute(); - - // for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { - // - // HttpUriRequest request = requestTemplate.buildAttackRequest(); - // - // // Send the next test case request - // sendRequest(httpclient, request); - } + // Use try-with-resources to close this resource before returning + try (CloseableHttpClient httpClient = createAcceptSelfSignedCertificateClient()) { + long start = System.currentTimeMillis(); + + // Iterate through TestCase objects instead. + // Execution of the test case depends on the type of TestCase.getTestCaseInput() + // Where should the code that executes the test case go? + // Maybe I need a TestCaseExecuter that takes a TestCaseInput to initialize. + // for (TestCase testCase : testSuite.getTestCases()) { + // if (testCase.getTestCaseInput() instanceof HttpTestCaseInput) { + // HttpUriRequest request = + // testCase.getAttackTestCaseRequest().buildAttackRequest(); + // sendRequest(httpclient, request); + // } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { + // // Execute the testCase using exec() + // } + // } + + for (TestCase testCase : testSuite.getTestCases()) { + System.out.println("Executing test case: " + testCase.getName()); // DEBUG + if (testCase.getTestCaseInput() instanceof HttpTestCaseInput) { + HttpTestCaseInput httpTestCaseInput = + (HttpTestCaseInput) testCase.getTestCaseInput(); + + HttpUriRequest attackRequest = httpTestCaseInput.buildAttackRequest(); + + // Send the next test case request with its attack payload + sendRequest(httpClient, attackRequest); + } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { + ExecutableTestCaseInput executableTestCaseInput = + (ExecutableTestCaseInput) testCase.getTestCaseInput(); + + CliRequest attackRequest = executableTestCaseInput.buildAttackRequest(); + + // Send the next test case request with its attack payload + execute(attackRequest); + } + } - // Log the elapsed time for all test cases - long stop = System.currentTimeMillis(); - int seconds = (int) (stop - start) / 1000; + // Log the elapsed time for all test cases + long stop = System.currentTimeMillis(); + int seconds = (int) (stop - start) / 1000; - Date now = new Date(); + Date now = new Date(); - System.out.printf( - "Crawl ran on %tF % executeArgs = Arrays.asList(request.getCommand().split(" ")); + for (RequestVariable arg : request.getArgs()) { + executeArgs.add(arg.getName()); + executeArgs.add(arg.getValue()); + } + // executeArgs.addAll(request.getArgs()); + System.out.println(String.join(" ", executeArgs)); + + StopWatch watch = new StopWatch(); + + watch.start(); + try { + // response = httpclient.execute(request); + ProcessBuilder builder = new ProcessBuilder(executeArgs); + final Process process = builder.start(); + int exitValue = process.waitFor(); + // attackPayloadResponseInfo = new ResponseInfo(); + // System.out.printf("Program terminated with return code: %s%n", exitValue); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + watch.stop(); + + // try { + // HttpEntity entity = response.getEntity(); + // int statusCode = response.getCode(); + // responseInfo.setStatusCode(statusCode); + // int seconds = (int) watch.getTime() / 1000; + // responseInfo.setTimeInSeconds(seconds); + // System.out.printf("--> (%d : %d sec)%n", statusCode, seconds); + // + // try { + // responseInfo.setResponseString(EntityUtils.toString(entity)); + // EntityUtils.consume(entity); + // } catch (IOException | org.apache.hc.core5.http.ParseException e) { + // e.printStackTrace(); + // } + // } finally { + // if (response != null) + // try { + // response.close(); + // } catch (IOException e) { + // e.printStackTrace(); + // } + // } + return responseInfo; + } + /** * Process the command line arguments that make any configuration changes. * * @param args - args passed to main(). * @return specified crawler file if valid command line arguments provided. Null otherwise. */ - private void processCommandLineArgs(String[] args) { + protected void processCommandLineArgs(String[] args) { // Create the command line parser CommandLineParser parser = new DefaultParser(); @@ -341,10 +425,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { } public static void main(String[] args) { - - BenchmarkCrawler crawler = new BenchmarkCrawler(); - crawler.processCommandLineArgs(args); - crawler.load(); - crawler.run(); + // thisInstance can be set from execute() or here, depending on how this class is invoked + // (via maven or command line) + if (thisInstance == null) { + thisInstance = new BenchmarkCrawler(); + } + thisInstance.processCommandLineArgs(args); + thisInstance.load(); + thisInstance.run(); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index c90f9569..e71f4011 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -37,7 +37,14 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.owasp.benchmarkutils.helpers.TestSuite; +import org.owasp.benchmarkutils.entities.CliRequest; +import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; +import org.owasp.benchmarkutils.entities.HttpTestCaseInput; +import org.owasp.benchmarkutils.entities.ResponseInfo; +import org.owasp.benchmarkutils.entities.TestCase; +import org.owasp.benchmarkutils.entities.TestCaseSetup; +import org.owasp.benchmarkutils.entities.TestCaseSetupException; +import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.helpers.Utils; /** @@ -71,155 +78,274 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { @Override protected void crawl(TestSuite testSuite) throws Exception { - CloseableHttpClient httpclient = createAcceptSelfSignedCertificateClient(); - long start = System.currentTimeMillis(); - List responseInfoList = new ArrayList(); - List results = new ArrayList(); - - final File FILE_NON_DISCRIMINATORY_LOG = - new File(CRAWLER_DATA_DIR, FILENAME_NON_DISCRIMINATORY_LOG); - final File FILE_ERRORS_LOG = new File(CRAWLER_DATA_DIR, FILENAME_ERRORS_LOG); - final File FILE_TIMES_LOG; - if (isTimingEnabled) { - FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES); - } else { - FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES_ALL); - } - final File FILE_UNVERIFIABLE_LOG = new File(CRAWLER_DATA_DIR, FILENAME_UNVERIFIABLE_LOG); - SimpleFileLogger.setFile("TIMES", FILE_TIMES_LOG); - SimpleFileLogger.setFile("NONDISCRIMINATORY", FILE_NON_DISCRIMINATORY_LOG); - SimpleFileLogger.setFile("ERRORS", FILE_ERRORS_LOG); - SimpleFileLogger.setFile("UNVERIFIABLE", FILE_UNVERIFIABLE_LOG); - - String completionMessage = null; - - try (SimpleFileLogger nl = SimpleFileLogger.getLogger("NONDISCRIMINATORY"); - SimpleFileLogger el = SimpleFileLogger.getLogger("ERRORS"); - SimpleFileLogger ul = SimpleFileLogger.getLogger("UNVERIFIABLE"); - SimpleFileLogger tl = SimpleFileLogger.getLogger("TIMES")) { - - ndLogger = nl; - eLogger = el; - uLogger = ul; - tLogger = tl; - - for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { - - HttpUriRequest attackRequest = requestTemplate.buildAttackRequest(); - HttpUriRequest safeRequest = requestTemplate.buildSafeRequest(); - - // Send the next test case request with its attack payload - ResponseInfo attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); - responseInfoList.add(attackPayloadResponseInfo); - - // Log the response - log(attackPayloadResponseInfo); - - ResponseInfo safePayloadResponseInfo = null; - if (!requestTemplate.isUnverifiable()) { - // Send the next test case request with its safe payload - safePayloadResponseInfo = sendRequest(httpclient, safeRequest); - responseInfoList.add(safePayloadResponseInfo); - - // Log the response - log(safePayloadResponseInfo); - } - - TestCaseVerificationResults result = - new TestCaseVerificationResults( - attackRequest, - safeRequest, - requestTemplate, - attackPayloadResponseInfo, - safePayloadResponseInfo); - results.add(result); - // Verify the response - if (RegressionTesting.isTestingEnabled) { - handleResponse(result); - } + // FIXME: Initialize all setup resources that are required + // List setups = getTestCaseSetups(testSuite); + // initializeSetups(setups); + try (CloseableHttpClient httpClient = createAcceptSelfSignedCertificateClient()) { + + long start = System.currentTimeMillis(); + List responseInfoList = new ArrayList(); + List results = + new ArrayList(); + + final File FILE_NON_DISCRIMINATORY_LOG = + new File(CRAWLER_DATA_DIR, FILENAME_NON_DISCRIMINATORY_LOG); + final File FILE_ERRORS_LOG = new File(CRAWLER_DATA_DIR, FILENAME_ERRORS_LOG); + final File FILE_TIMES_LOG; + if (isTimingEnabled) { + FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES); + } else { + FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES_ALL); } + final File FILE_UNVERIFIABLE_LOG = + new File(CRAWLER_DATA_DIR, FILENAME_UNVERIFIABLE_LOG); + SimpleFileLogger.setFile("TIMES", FILE_TIMES_LOG); + SimpleFileLogger.setFile("NONDISCRIMINATORY", FILE_NON_DISCRIMINATORY_LOG); + SimpleFileLogger.setFile("ERRORS", FILE_ERRORS_LOG); + SimpleFileLogger.setFile("UNVERIFIABLE", FILE_UNVERIFIABLE_LOG); + + String completionMessage = null; + + try (SimpleFileLogger nl = SimpleFileLogger.getLogger("NONDISCRIMINATORY"); + SimpleFileLogger el = SimpleFileLogger.getLogger("ERRORS"); + SimpleFileLogger ul = SimpleFileLogger.getLogger("UNVERIFIABLE"); + SimpleFileLogger tl = SimpleFileLogger.getLogger("TIMES")) { + + ndLogger = nl; + eLogger = el; + uLogger = ul; + tLogger = tl; + + for (TestCase testCase : testSuite.getTestCases()) { + + // TestCaseVerificationResults result = testCase.execute(); + // results.add(result); + + TestExecutor attackExecutor = null; + TestExecutor safeExecutor = null; + ResponseInfo attackPayloadResponseInfo = null; + ResponseInfo safePayloadResponseInfo = null; + if (testCase.getTestCaseInput() instanceof HttpTestCaseInput) { + HttpTestCaseInput httpTestCaseInput = + (HttpTestCaseInput) testCase.getTestCaseInput(); + + // TestExecutor attackTestExecutor = + // httpTestCase.getTestCaseInput().buildAttackExecutor(); + // TestExecutor safeTestExecutor = + // httpTestCase.getTestCaseInput().buildSafeExecutor(); + + // FIXME: What would the executable testcase's attackRequest look like? + HttpUriRequest attackRequest = httpTestCaseInput.buildAttackRequest(); + HttpUriRequest safeRequest = httpTestCaseInput.buildSafeRequest(); + attackExecutor = new HttpExecutor(attackRequest); + safeExecutor = new HttpExecutor(safeRequest); + + // Send the next test case request with its attack payload + attackPayloadResponseInfo = sendRequest(httpClient, attackRequest); + responseInfoList.add(attackPayloadResponseInfo); + + // Log the response + log(attackPayloadResponseInfo); + + safePayloadResponseInfo = null; + if (!testCase.isUnverifiable()) { + // Send the next test case request with its safe payload + safePayloadResponseInfo = sendRequest(httpClient, safeRequest); + responseInfoList.add(safePayloadResponseInfo); + + // Log the response + log(safePayloadResponseInfo); + } + + // TestCaseVerificationResults result = + // new TestCaseVerificationResults( + // attackExecutor, + // safeExecutor, + // httpTestCase, + // attackPayloadResponseInfo, + // safePayloadResponseInfo); + // results.add(result); + // + // // Verify the response + // if (RegressionTesting.isTestingEnabled) { + // handleResponse(result); + // } + } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { + ExecutableTestCaseInput executableTestCaseInput = + (ExecutableTestCaseInput) testCase.getTestCaseInput(); + + // FIXME: A bit of a hack + CliRequest attackRequest = executableTestCaseInput.buildAttackRequest(); + CliRequest safeRequest = executableTestCaseInput.buildSafeRequest(); + // attackExecutor = new CliExecutor(attackRequest); + // safeExecutor = new CliExecutor(safeRequest); + + // Send the next test case request with its attack payload + attackPayloadResponseInfo = execute(attackRequest); + //// executeArgs.add(payload); + // ProcessBuilder builder = new + // ProcessBuilder(executeArgs); + // final Process process = builder.start(); + // int exitValue = process.waitFor(); + // attackPayloadResponseInfo = new ResponseInfo(); + // System.out.printf("Program terminated with return code: + // %s%n", exitValue); + responseInfoList.add(attackPayloadResponseInfo); + + // Log the response + log(attackPayloadResponseInfo); + + safePayloadResponseInfo = null; + if (!testCase.isUnverifiable()) { + // Send the next test case request with its safe payload + safePayloadResponseInfo = execute(safeRequest); + responseInfoList.add(safePayloadResponseInfo); + + // Log the response + log(safePayloadResponseInfo); + } + + // TestCaseVerificationResults result = + // new TestCaseVerificationResults( + // attackRequest, + // safeRequest, + // cliTestCase, + // attackPayloadResponseInfo, + // safePayloadResponseInfo); + // results.add(result); + // + // // Verify the response + // if (RegressionTesting.isTestingEnabled) { + // handleResponse(result); + // } + } + TestCaseVerificationResults result = + new TestCaseVerificationResults( + attackExecutor, + safeExecutor, + testCase, + attackPayloadResponseInfo, + safePayloadResponseInfo); + results.add(result); + + // Verify the response + if (RegressionTesting.isTestingEnabled) { + handleResponse(result); + } + } - // Log the elapsed time for all test cases - long stop = System.currentTimeMillis(); - int seconds = (int) (stop - start) / 1000; - - Date now = new Date(); + // Log the elapsed time for all test cases + long stop = System.currentTimeMillis(); + int seconds = (int) (stop - start) / 1000; - completionMessage = - String.format( - "Verification crawl ran on %tF % 0) { - System.out.printf( - "Details of non-discriminatory test cases written to: %s%n", - FILE_NON_DISCRIMINATORY_LOG); - } - if (FILE_ERRORS_LOG.length() > 0) { - System.out.printf( - "Details of errors/exceptions in test cases written to: %s%n", FILE_ERRORS_LOG); + if (FILE_NON_DISCRIMINATORY_LOG.length() > 0) { + System.out.printf( + "Details of non-discriminatory test cases written to: %s%n", + FILE_NON_DISCRIMINATORY_LOG); + } + if (FILE_ERRORS_LOG.length() > 0) { + System.out.printf( + "Details of errors/exceptions in test cases written to: %s%n", + FILE_ERRORS_LOG); + } + if (FILE_UNVERIFIABLE_LOG.length() > 0) { + System.out.printf( + "Details of unverifiable test cases written to: %s%n", + FILE_UNVERIFIABLE_LOG); + } + + System.out.printf("Test case time measurements written to: %s%n", FILE_TIMES_LOG); + + RegressionTesting.printCrawlSummary(results); + System.out.println(); + System.out.println(completionMessage); } - if (FILE_UNVERIFIABLE_LOG.length() > 0) { - System.out.printf( - "Details of unverifiable test cases written to: %s%n", FILE_UNVERIFIABLE_LOG); + + // FIXME: Use a finally to cleanup all setup resources that were required + // cleanupSetups(setups); + } + + private List getTestCaseSetups(TestSuite testSuite) { + List testCaseSetups = new ArrayList(); + for (TestCase testCase : testSuite.getTestCases()) { + TestCaseSetup testCaseSetup = testCase.getTestCaseSetup(); + if (testCaseSetup != null) { + testCaseSetups.add(testCaseSetup); + } } - System.out.printf("Test case time measurements written to: %s%n", FILE_TIMES_LOG); + return testCaseSetups; + } + + private void initializeSetups(List testCaseSetups) + throws TestCaseSetupException { + for (TestCaseSetup testCaseSetup : testCaseSetups) { + testCaseSetup.setup(); + } + } - RegressionTesting.printCrawlSummary(results); - System.out.println(); - System.out.println(completionMessage); + private void cleanupSetups(List testCaseSetups) throws TestCaseSetupException { + for (TestCaseSetup testCaseSetup : testCaseSetups) { + testCaseSetup.close(); + } } private void log(ResponseInfo responseInfo) throws IOException { @@ -230,19 +356,19 @@ private void log(ResponseInfo responseInfo) throws IOException { "--> (%d : %d sec)%n", responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); try { - if (isTimingEnabled) { - if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } - } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + if (isTimingEnabled) { + if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } else { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } /** @@ -271,7 +397,7 @@ protected static void handleResponse(TestCaseVerificationResults result) * @param args - args passed to main(). * @return specified crawler file if valid command line arguments provided. Null otherwise. */ - private void processCommandLineArgs(String[] args) { + protected void processCommandLineArgs(String[] args) { // Set default attack crawler file String crawlerFileName = new File(Utils.DATA_DIR, "benchmark-attack-http.xml").getPath(); @@ -350,10 +476,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { } public static void main(String[] args) { - - BenchmarkCrawlerVerification crawler = new BenchmarkCrawlerVerification(); - crawler.processCommandLineArgs(args); - crawler.load(); - crawler.run(); + // thisInstance can be set from execute() or here, depending on how this class is invoked + // (via maven or command line) + if (thisInstance == null) { + thisInstance = new BenchmarkCrawler(); + } + thisInstance.processCommandLineArgs(args); + thisInstance.load(); + thisInstance.run(); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CalculateToolCodeBlocksSupport.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CalculateToolCodeBlocksSupport.java index 1e5655b3..9ee70b15 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CalculateToolCodeBlocksSupport.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CalculateToolCodeBlocksSupport.java @@ -40,6 +40,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.owasp.benchmarkutils.entities.TestCase; import org.owasp.benchmarkutils.helpers.CodeblockUtils; import org.owasp.benchmarkutils.helpers.Utils; import org.owasp.benchmarkutils.score.TestCaseResult; @@ -83,100 +84,6 @@ public void setScorecardResultsFile(String targetFileName) } } - /** - * Process the command line arguments that make any configuration changes. - * - * @param args - args passed to main(). - * @return specified crawler file if valid command line arguments provided. Null otherwise. - */ - @Override - protected void processCommandLineArgs(String[] args) { - - // 1. Load testsuite-attack XML file so we have the codeblocks for every test case - DONE - // 1a. TestSuite instance has all the TestCases - // 1b. Codeblocks are attributes of each instance of TestCase, created/read in from XML. - - // Example: -DcrawlerFile=data/benchmark-attack-http.xml - - // 2. Load the .csv results for the selected tool - DONE - - // Example: - // -DresultsCSVFile=../../BenchmarkJavaBaseApp/scorecard/Benchmark_v1.3_Scorecard_for_Contrast_Assess_v4.8.0.csv - - // Do the work in run(). - - // Set default attack crawler file, if it exists - // This value can be changed by the -f parameter for other test suites with different names - File defaultAttackCrawlerFile = new File(Utils.DATA_DIR, "benchmark-attack-http.xml"); - if (defaultAttackCrawlerFile.exists()) { - setCrawlerFile(defaultAttackCrawlerFile.getPath()); - } - - RegressionTesting.isTestingEnabled = true; - - // Create the command line parser - CommandLineParser parser = new DefaultParser(); - - HelpFormatter formatter = new HelpFormatter(); - - // Create the Options - Options options = new Options(); - options.addOption( - Option.builder("f") - .longOpt("file") - .desc("a TESTSUITE-attack-http.xml file") - .hasArg() - .required() - .build()); - options.addOption( - Option.builder("r") - .longOpt("file") - .desc("a scorecard generated toolResults.csv file") - .hasArg() - .required() - .build()); - /* options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); - options.addOption( - Option.builder("n") - .longOpt("name") - .desc("tescase name (e.g. BenchmarkTestCase00025)") - .hasArg() - .build()); - options.addOption( - Option.builder("t") - .longOpt("time") - .desc("testcase timeout (in seconds)") - .hasArg() - .type(Integer.class) - .build()); - */ - try { - // Parse the command line arguments - CommandLine line = parser.parse(options, args); - - if (line.hasOption("f")) { - // Following throws a RuntimeException if the attack crawler file doesn't exist - setCrawlerFile(line.getOptionValue("f")); - } - if (line.hasOption("r")) { - setScorecardResultsFile(line.getOptionValue("r")); - } - /* if (line.hasOption("h")) { - formatter.printHelp("BenchmarkCrawlerVerification", options, true); - } - if (line.hasOption("n")) { - selectedTestCaseName = line.getOptionValue("n"); - } - if (line.hasOption("t")) { - maxTimeInSeconds = (Integer) line.getParsedOptionValue("t"); - } - */ - } catch (ParseException | IOException e) { - formatter.printHelp("CalculateToolCodeBlocksSupport", options); - throw new RuntimeException("Error parsing arguments: ", e); - } - } - /** Calculate the code block support for the specified tool for the specified test suite. */ @Override protected void run() { @@ -198,7 +105,7 @@ protected void run() { new TestSuiteResults(this.testSuite.getName(), false, ToolType.SAST); // Get all the TestCase info loaded from TESTSUITE-attack-http.xml file - List theTestcases = this.testSuite.getTestCases(); + List theTestcases = this.testSuite.getTestCases(); int testSuiteSize = theTestcases.size(); try { @@ -634,6 +541,99 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + /** + * Process the command line arguments that make any configuration changes. + * + * @param args - args passed to main(). + * @return specified crawler file if valid command line arguments provided. Null otherwise. + */ + protected void processCommandLineArgs(String[] args) { + + // 1. Load testsuite-attack XML file so we have the codeblocks for every test case - DONE + // 1a. TestSuite instance has all the TestCases + // 1b. Codeblocks are attributes of each instance of TestCase, created/read in from XML. + + // Example: -DcrawlerFile=data/benchmark-attack-http.xml + + // 2. Load the .csv results for the selected tool - DONE + + // Example: + // -DresultsCSVFile=../../BenchmarkJavaBaseApp/scorecard/Benchmark_v1.3_Scorecard_for_Contrast_Assess_v4.8.0.csv + + // Do the work in run(). + + // Set default attack crawler file, if it exists + // This value can be changed by the -f parameter for other test suites with different names + File defaultAttackCrawlerFile = new File(Utils.DATA_DIR, "benchmark-attack-http.xml"); + if (defaultAttackCrawlerFile.exists()) { + setCrawlerFile(defaultAttackCrawlerFile); + } + + RegressionTesting.isTestingEnabled = true; + + // Create the command line parser + CommandLineParser parser = new DefaultParser(); + + HelpFormatter formatter = new HelpFormatter(); + + // Create the Options + Options options = new Options(); + options.addOption( + Option.builder("f") + .longOpt("file") + .desc("a TESTSUITE-attack-http.xml file") + .hasArg() + .required() + .build()); + options.addOption( + Option.builder("r") + .longOpt("file") + .desc("a scorecard generated toolResults.csv file") + .hasArg() + .required() + .build()); + /* options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption( + Option.builder("n") + .longOpt("name") + .desc("tescase name (e.g. BenchmarkTestCase00025)") + .hasArg() + .build()); + options.addOption( + Option.builder("t") + .longOpt("time") + .desc("testcase timeout (in seconds)") + .hasArg() + .type(Integer.class) + .build()); + */ + try { + // Parse the command line arguments + CommandLine line = parser.parse(options, args); + + if (line.hasOption("f")) { + // Following throws a RuntimeException if the attack crawler file doesn't exist + setCrawlerFile(new File(line.getOptionValue("f"))); + } + if (line.hasOption("r")) { + setScorecardResultsFile(line.getOptionValue("r")); + } + /* if (line.hasOption("h")) { + formatter.printHelp("BenchmarkCrawlerVerification", options, true); + } + if (line.hasOption("n")) { + selectedTestCaseName = line.getOptionValue("n"); + } + if (line.hasOption("t")) { + maxTimeInSeconds = (Integer) line.getParsedOptionValue("t"); + } + */ + } catch (ParseException | IOException e) { + formatter.printHelp("CalculateToolCodeBlocksSupport", options); + throw new RuntimeException("Error parsing arguments: ", e); + } + } + public static void main(String[] args) { // thisInstance can be set from execute() or here, depending on how this class is invoked // (via maven or command line) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java deleted file mode 100644 index 2fdf7a3e..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details - * - * @author Juan Gama - * @created 2017 - */ -package org.owasp.benchmarkutils.tools; - -import java.io.UnsupportedEncodingException; - -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.core5.http.io.entity.StringEntity; -import org.apache.hc.core5.http.message.BasicClassicHttpRequest; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.owasp.benchmarkutils.helpers.RequestVariable; - -@XmlDiscriminatorValue("JERSEYWS") -public class JerseyTestCaseRequest extends AbstractTestCaseRequest { - - public JerseyTestCaseRequest() {} - - @Override - void buildQueryString() { - setQuery(""); - } - - @Override - HttpUriRequestBase createRequestInstance(String URL) { - // Apparently all Jersey Requests are POSTS. Never any query string params per buildQuery() - // above. - HttpPost httpPost = new HttpPost(URL); - return httpPost; - } - - @Override - void buildHeaders(HttpUriRequestBase request) { - request.addHeader("Content-Type", "application/xml; charset=utf-8"); - for (RequestVariable header : getHeaders()) { - String name = header.getName(); - String value = header.getValue(); - // System.out.println("Header:" + name + "=" + value); - request.addHeader(name, value); - } - } - - @Override - void buildCookies(HttpUriRequestBase request) { - for (RequestVariable cookie : getCookies()) { - String name = cookie.getName(); - String value = cookie.getValue(); - // System.out.println("Cookie:" + name + "=" + value); - request.addHeader("Cookie", name + "=" + value); - } - } - - @Override - void buildBodyParameters(HttpUriRequestBase request) { - String params = ""; - for (RequestVariable field : getFormParams()) { - String name = field.getName(); - String value = field.getValue(); - params += "<" + name + ">" + escapeXML(value) + ""; - } - params += ""; - StringEntity paramsEnt = new StringEntity(params); - ((BasicClassicHttpRequest) request).setEntity(paramsEnt); - } - - private static String escapeXML(String value) { - value = value.replace("&", "&"); - value = value.replace("\"", """); - value = value.replace("'", "'"); - value = value.replace("<", "<"); - value = value.replace(">", ">"); - - return value; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 00c9a0c8..4884fbe4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -34,7 +34,9 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpMessage; -import org.apache.hc.core5.http.ParseException; +import org.owasp.benchmarkutils.entities.HttpTestCaseInput; +import org.owasp.benchmarkutils.entities.ResponseInfo; +import org.owasp.benchmarkutils.entities.TestCase; /** * Test all supported test cases to verify that the results are as expected and write the report to @@ -64,10 +66,8 @@ public class RegressionTesting { static SortedMultiset nonDiscriminatorySinks = TreeMultiset.create(); static SortedMultiset failSinks = TreeMultiset.create(); - static Map failedTruePositivesList = - new LinkedHashMap(); - static Map failedFalsePositivesList = - new LinkedHashMap(); + static Map failedTruePositivesList = new LinkedHashMap<>(); + static Map failedFalsePositivesList = new LinkedHashMap<>(); // TODO: Make this flag configurable via command line parameter private static boolean isVerbosityOn = false; @@ -101,10 +101,12 @@ public static void genFailedTCFile(List results, St totalCount = results.size(); for (TestCaseVerificationResults result : results) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); + // AbstractTestCaseRequest requestTemplate = + // result.getRequestTemplate(); + TestCase testCase = result.getTestCase(); String sink = null; - String sinkMetaDataFilePath = requestTemplate.getSinkFile(); + String sinkMetaDataFilePath = testCase.getSinkFile(); if (sinkMetaDataFilePath != null) { String sinkMetaDataFilename = new File(sinkMetaDataFilePath).getName(); sink = sinkMetaDataFilename.substring(0, sinkMetaDataFilename.indexOf('.')); @@ -117,17 +119,17 @@ public static void genFailedTCFile(List results, St undeclaredUnverifiable++; if (sink == null) { System.out.printf( - "ERROR: No sink for request %s%n", requestTemplate.getName()); + "ERROR: No sink for request %s%n", testCase.getName()); } else { undeclaredUnverifiableSinks.add(sink); } } } else { if (result.isPassed()) { - if (requestTemplate.isVulnerability()) truePositivePassedCount++; + if (testCase.isVulnerability()) truePositivePassedCount++; else falsePositivePassedCount++; } else { - if (requestTemplate.isVulnerability()) truePositiveFailedCount++; + if (testCase.isVulnerability()) truePositiveFailedCount++; else falsePositiveFailedCount++; } verifiedCount++; @@ -136,26 +138,30 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); - if (isIncludedInTest(requestTemplate)) { + // AbstractTestCaseRequest requestTemplate = + // result.getRequestTemplate(); + TestCase testCase = result.getTestCase(); + if (isIncludedInTest(testCase)) { if (isVerbosityOn) { System.out.println(); System.out.printf( "Test case request %s (category: %s, isVulnerability: %b, isNonverifiable: %b, isPassed: %b)%n", - requestTemplate.getName(), - requestTemplate.getCategory().toString(), - requestTemplate.isVulnerability(), + testCase.getName(), + testCase.getCategory().toString(), + testCase.isVulnerability(), result.isUnverifiable(), result.isPassed()); - System.out.println(requestTemplate.getFullURL()); + HttpTestCaseInput httpTestCaseInput = + (HttpTestCaseInput) testCase.getTestCaseInput(); + System.out.println(httpTestCaseInput.getUrl()); } if (!result.isUnverifiable() && !result.isPassed()) { System.out.printf( "FAILURE: %s positive %s test case request %s%n", - requestTemplate.isVulnerability() ? "True" : "False", - requestTemplate.getCategory().toString(), - requestTemplate.getName()); + testCase.isVulnerability() ? "True" : "False", + testCase.getCategory().toString(), + testCase.getName()); } } } @@ -163,8 +169,10 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); - if (isIncludedInTest(requestTemplate)) { + // AbstractTestCaseRequest requestTemplate = + // result.getRequestTemplate(); + TestCase testCase = result.getTestCase(); + if (isIncludedInTest(testCase)) { if (!result.isUnverifiable() && !result.isPassed()) { ftcLogger.print("FAILURE: "); printTestCaseDetails(result, ftcLogger); @@ -200,31 +208,31 @@ private static void printHttpRequest(HttpMessage request, Logger out) { } private static void printTestCaseDetails(TestCaseVerificationResults result, Logger out) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); + // AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); + TestCase testCase = result.getTestCase(); ResponseInfo attackResponseInfo = result.getResponseToAttackValue(); ResponseInfo safeResponseInfo = result.getResponseToSafeValue(); out.printf( "%s positive %s test case request %s%n", - requestTemplate.isVulnerability() ? "True" : "False", - requestTemplate.getCategory().toString(), - requestTemplate.getName()); + testCase.isVulnerability() ? "True" : "False", + testCase.getCategory().toString(), + testCase.getName()); // Print out all the attributes of the request, including the templates used to create it - out.println(requestTemplate.toString()); + out.println(testCase.toString()); out.println(); out.println("Attack request:"); - printHttpRequest(result.getAttackRequest(), out); + out.printf(result.getAttackTestExecutor().getExecutorDescription(), out); out.println(); out.printf("Attack response: [%d]:%n", attackResponseInfo.getStatusCode()); out.println(attackResponseInfo == null ? "null" : attackResponseInfo.getResponseString()); out.println(); out.println("Safe request:"); - printHttpRequest(result.getSafeRequest(), out); + out.printf(result.getSafeTestExecutor().getExecutorDescription(), out); out.println(); out.printf("Safe response: [%d]:%n", attackResponseInfo.getStatusCode()); out.println(safeResponseInfo == null ? "null" : safeResponseInfo.getResponseString()); out.println(); - out.printf( - "Attack success indicator: -->%s<--%n", requestTemplate.getAttackSuccessString()); + out.printf("Attack success indicator: -->%s<--%n", testCase.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); } @@ -318,11 +326,12 @@ public static void verifyTestCase(TestCaseVerificationResults result) result.setUnverifiable(false); // Default result.setDeclaredUnverifiable(false); // Default - if (result.getRequestTemplate().isUnverifiable()) { + TestCase testCase = result.getTestCase(); + if (testCase.isUnverifiable()) { // Count this as "declared unverifiable" and return result.setUnverifiable(true); result.setDeclaredUnverifiable(true); - } else if (result.getRequestTemplate().getAttackSuccessString() == null) { + } else if (testCase.getAttackSuccessString() == null) { // Count this as "undeclared unverifiable" and return result.setUnverifiable(true); result.setDeclaredUnverifiable(false); @@ -333,7 +342,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) List reasons = new ArrayList<>(); String sink = null; - String sinkMetaDataFilePath = result.getRequestTemplate().getSinkFile(); + String sinkMetaDataFilePath = testCase.getSinkFile(); if (sinkMetaDataFilePath != null) { String sinkMetaDataFilename = new File(sinkMetaDataFilePath).getName(); sink = sinkMetaDataFilename.substring(0, sinkMetaDataFilename.indexOf('.')); @@ -343,12 +352,12 @@ public static void verifyTestCase(TestCaseVerificationResults result) boolean isAttackValueVerified = verifyResponse( result.getResponseToAttackValue().getResponseString(), - result.getRequestTemplate().getAttackSuccessString()); + testCase.getAttackSuccessString()); boolean isSafeValueVerified = verifyResponse( result.getResponseToSafeValue().getResponseString(), - result.getRequestTemplate().getAttackSuccessString()); - if (result.getRequestTemplate().isVulnerability()) { + testCase.getAttackSuccessString()); + if (testCase.isVulnerability()) { // True positive success? if (isAttackValueVerified) { result.setPassed(true); @@ -356,8 +365,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) ndLogger.printf( "Non-discriminatory true positive test %s: The attack-success-string: \"%s\" was found in the response to both the safe and attack requests.%n" + "\tTo verify that a test case is a true positive, the attack-success-string should be in the attack response, and not%n\tthe safe response. Please change the attack-success-string and/or the test case sink itself to ensure that the%n\tattack-success-string response is present only in a response to a successful attack.%n", - result.getRequestTemplate().getName(), - result.getRequestTemplate().getAttackSuccessString()); + testCase.getName(), testCase.getAttackSuccessString()); printTestCaseDetails(result, ndLogger); nonDiscriminatorySinks.add(sink); } @@ -376,8 +384,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) ndLogger.printf( "Non-discriminatory false positive test %s: The attack-success-string: \"%s\" was found in the response to the safe request.%n" + "\tTo verify that a test case is a false positive, the attack-success-string should not be in any response to this test%n\tcase. Please change the attack-success-string and/or the test case sink itself to ensure that the%n\tattack-success-string response is present only in a response to a successful attack.%n", - result.getRequestTemplate().getName(), - result.getRequestTemplate().getAttackSuccessString()); + testCase.getName(), testCase.getAttackSuccessString()); printTestCaseDetails(result, ndLogger); nonDiscriminatorySinks.add(sink); } @@ -390,17 +397,17 @@ public static void verifyTestCase(TestCaseVerificationResults result) String compositeReason = "\t- " + String.join(", ", reasons); - if (result.getRequestTemplate().isVulnerability()) { + if (testCase.isVulnerability()) { truePositives++; if (hasErrors) { failedTruePositives++; - failedTruePositivesList.put(result.getRequestTemplate(), compositeReason); + failedTruePositivesList.put(testCase, compositeReason); } } else { falsePositives++; if (hasErrors) { failedFalsePositives++; - failedFalsePositivesList.put(result.getRequestTemplate(), compositeReason); + failedFalsePositivesList.put(testCase, compositeReason); } } } @@ -447,8 +454,8 @@ public static boolean verifyResponse(String response, String attackSuccessIndica return response.contains(attackSuccessIndicator); } - private static boolean isIncludedInTest(AbstractTestCaseRequest testCaseRequestTemplate) { - return CATEGORIES_INCLUDED_IN_TEST.contains(testCaseRequestTemplate.getCategory().getId()) - || (testCaseRequestTemplate.getAttackSuccessString() != null); + private static boolean isIncludedInTest(TestCase testCase) { + return CATEGORIES_INCLUDED_IN_TEST.contains(testCase.getCategory().getId()) + || (testCase.getAttackSuccessString() != null); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java deleted file mode 100644 index c58aa684..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details - * - * @author Juan Gama - * @created 2017 - */ -package org.owasp.benchmarkutils.tools; - -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; -import org.apache.hc.core5.http.NameValuePair; -import org.apache.hc.core5.http.message.BasicClassicHttpRequest; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.owasp.benchmarkutils.helpers.RequestVariable; - -/* - * This class is used by the crawlers to test the target Benchmark style web application. It tests Servlet style - * web applications that use traditional GET parameters in URLs, POST body parameters, header name/values, cookies, - * etc. Nothing fancy, specific to particular frameworks, like parameters embedded in the URL path, etc. - */ - -@XmlDiscriminatorValue("SERVLET") -public class ServletTestCaseRequest extends AbstractTestCaseRequest { - - public ServletTestCaseRequest() {} - - @SuppressWarnings("deprecation") - @Override - void buildQueryString() { - setQuery(""); - boolean first = true; - for (RequestVariable field : getGetParams()) { - if (first) { - setQuery("?"); - first = false; - } else { - setQuery(getQuery() + "&"); - } - String name = field.getName(); - String value = field.getValue(); - // System.out.println(query); - setQuery(getQuery() + (name + "=" + URLEncoder.encode(value))); - } - } - - @Override - HttpUriRequestBase createRequestInstance(String URL) { - // If there are query parameters, this must be a GET, otherwise a POST. - if (getQuery().length() == 0) { - return new HttpPost(URL); - } else { - return new HttpGet(URL); - } - } - - @Override - void buildHeaders(HttpUriRequestBase request) { - for (RequestVariable header : getHeaders()) { - String name = header.getName(); - String value = header.getValue(); - // System.out.println("Header:" + name + "=" + value); - request.addHeader(name, value); - } - } - - @SuppressWarnings("deprecation") - @Override - void buildCookies(HttpUriRequestBase request) { - for (RequestVariable cookie : getCookies()) { - String name = cookie.getName(); - String value = cookie.getValue(); - // Note: URL encoding of a space becomes a +, which is OK for Java, but - // not other languages. So after URLEncoding, replace all + with %20, which is the - // standard URL encoding for a space char. - request.addHeader("Cookie", name + "=" + URLEncoder.encode(value).replace("+", "%20")); - } - } - - @Override - void buildBodyParameters(HttpUriRequestBase request) { - List fields = new ArrayList<>(); - for (RequestVariable formParam : getFormParams()) { - fields.add(formParam.getNameValuePair()); - } - - // Add the body parameters to the request if there were any - if (fields.size() > 0) { - ((BasicClassicHttpRequest) request) - .setEntity(new UrlEncodedFormEntity(fields)); - } - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java deleted file mode 100644 index d0a4989f..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details - * - * @author Juan Gama - * @created 2017 - */ -package org.owasp.benchmarkutils.tools; - -import org.apache.hc.client5.http.classic.methods.HttpPost; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.apache.hc.core5.http.io.entity.StringEntity; -import org.apache.hc.core5.http.message.BasicClassicHttpRequest; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; -import org.owasp.benchmarkutils.helpers.RequestVariable; - -@XmlDiscriminatorValue("SPRINGWS") -public class SpringTestCaseRequest extends AbstractTestCaseRequest { - - public SpringTestCaseRequest() {} - - @Override - void buildQueryString() { - setQuery(""); - } - - @Override - HttpUriRequestBase createRequestInstance(String URL) { - // Apparently all Spring Requests are POSTS. Never any query string params per buildQuery() - // above. - HttpPost httpPost = new HttpPost(URL); - return httpPost; - } - - @Override - void buildHeaders(HttpUriRequestBase request) { - request.addHeader("Content-type", "application/json"); - for (RequestVariable header : getHeaders()) { - String name = header.getName(); - String value = header.getValue(); - System.out.println("Header:" + name + "=" + value); - request.addHeader(name, value); - } - } - - @Override - void buildCookies(HttpUriRequestBase request) { - for (RequestVariable cookie : getCookies()) { - String name = cookie.getName(); - String value = cookie.getValue(); - // System.out.println("Cookie:" + name + "=" + value); - request.addHeader("Cookie", name + "=" + value); - } - } - - @Override - void buildBodyParameters(HttpUriRequestBase request) { - boolean first = true; - String params = "{"; - for (RequestVariable field : getFormParams()) { - String name = field.getName(); - String value = field.getValue(); - // System.out.println(name+"="+value); - if (first) { - first = false; - } else { - params = params + ","; - } - params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); - } - params += "}"; - StringEntity paramsEnt = new StringEntity(params); - ((BasicClassicHttpRequest) request).setEntity(paramsEnt); - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java deleted file mode 100644 index 6ce885dc..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequest.java +++ /dev/null @@ -1,485 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details - * - * @author Juan Gama - * @created 2017 - */ -package org.owasp.benchmarkutils.tools; - -import java.io.File; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import javax.validation.constraints.NotNull; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlTransient; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; -import org.eclipse.persistence.oxm.annotations.XmlReadOnly; -import org.owasp.benchmarkutils.helpers.Category; -import org.owasp.benchmarkutils.helpers.CategoryAdapter; -import org.owasp.benchmarkutils.helpers.RequestVariable; -import org.owasp.benchmarkutils.helpers.TestCaseInput; - -public class TestCaseRequest { - - /* - * The 1st three are Java. - */ - public enum TestCaseType { - JERSEYWS, - SERVLET, - SPRINGWS, - NODEEXPRESS - } - - public static Comparator getNameComparator() { - return new Comparator() { - - @Override - public int compare(TestCaseRequest o1, TestCaseRequest o2) { - if (!o1.name.equalsIgnoreCase(o2.name)) return o1.name.compareTo(o2.name); - return 0; - } - }; - } - - private TestCaseInput testCaseInput; - private Category category; - private List cookies = new ArrayList(); - private String dataflowFile; - private List formParams = new ArrayList(); - private String fullURL; - private List getParams = new ArrayList(); - private List headers = new ArrayList(); - private String notAutoverifiableReason; - private boolean isUnverifiable; - private boolean isVulnerability; - private String attackSuccessString; - private String name; - private String query; - private String sinkFile; - private String sourceFile; - private String sourceUIType; - private TestCaseType tcType; - private String templateFile; - private String uiTemplateFile; - - public TestCaseRequest() {} - - // /** - // * This class contains enough information to generate an HttpUriRequest for a generated - // test - // * case. - // * - // * @param fullURL - // * @param tcType - // * @param category - // * @param name - // * @param uiTemplateFile - // * @param templateFile - // * @param sourceFile - // * @param sourceUIType - // * @param dataflowFile - // * @param sinkFile - // * @param isUnverifiable - // * @param isVulnerability - // * @param attackSuccessString - // * @param headers - // * @param cookies - // * @param getParams - // * @param formParams - // */ - // public AbstractTestCaseRequest( - // String fullURL, - // TestCaseType tcType, - // Category category, - // String name, - // String uiTemplateFile, - // String templateFile, - // String sourceFile, - // String sourceUIType, - // String dataflowFile, - // String sinkFile, - // boolean isUnverifiable, - // boolean isVulnerability, - // String attackSuccessString, - // List headers, - // List cookies, - // List getParams, - // List formParams) { - // super(); - // this.fullURL = fullURL; - // this.tcType = tcType; - // this.category = category; - // this.name = name; - // this.uiTemplateFile = uiTemplateFile; - // this.templateFile = templateFile; - // this.sourceFile = sourceFile; - // this.sourceUIType = sourceUIType; - // this.dataflowFile = dataflowFile; - // this.sinkFile = sinkFile; - // this.isUnverifiable = isUnverifiable; - // this.isVulnerability = isVulnerability; - // this.attackSuccessString = attackSuccessString; - // this.headers = headers; - // this.cookies = cookies; - // this.getParams = getParams; - // this.formParams = formParams; - // - // // // Figure out if ANY of the values in the request include an attack value. - // // this.isSafe = true; - // // // Bitwise AND is done on all parameters isSafe() values. If ANY of them are - // // unsafe, isSafe - // // // set to False. - // // for (RequestVariable header : getHeaders()) { - // // this.isSafe &= header.isSafe(); - // // } - // // - // // for (RequestVariable cookie : getCookies()) { - // // this.isSafe &= cookie.isSafe(); - // // } - // // - // // for (RequestVariable getParam : getGetParams()) { - // // this.isSafe &= getParam.isSafe(); - // // } - // // - // // for (RequestVariable formParam : getFormParams()) { - // // this.isSafe &= formParam.isSafe(); - // // } - // } - - /** Defines what parameters in the body will be sent. */ - public void buildBodyParameters(HttpUriRequestBase request) { - testCaseInput.buildBodyParameters(request); - } - - /** Defines what cookies will be sent. */ - public void buildCookies(HttpUriRequestBase request) { - testCaseInput.buildCookies(request); - } - - /** Defines what headers will be sent. */ - public void buildHeaders(HttpUriRequestBase request) { - testCaseInput.buildHeaders(request); - } - - /** Defines how to construct URL query string. */ - public void buildQueryString() { - testCaseInput.buildQueryString(); - } - - /** - * TODO: Make this class a POJO TestCase and pass it as an arg to another class TestCaseRequest - * that can build an actual HttpUriRequest. - * - * @return - */ - public HttpUriRequestBase buildRequest() { - buildQueryString(); - HttpUriRequestBase request = createRequestInstance(fullURL + query); - buildHeaders(request); - buildCookies(request); - buildBodyParameters(request); - return request; - } - - public HttpUriRequestBase buildAttackRequest() { - setSafe(false); - return buildRequest(); - } - - public HttpUriRequestBase buildSafeRequest() { - setSafe(true); - return buildRequest(); - } - - /** - * Method to create a POST, GET, DELETE, HEAD, OPTIONS, TRACE request object. - * - * @return an instance of a subclass of HttpUriRequestBase - */ - abstract HttpUriRequestBase createRequestInstance(String URL); - - @XmlAttribute(name = "tcAttackSuccess") - public String getAttackSuccessString() { - return this.attackSuccessString; - } - - @XmlAttribute(name = "tcCategory", required = true) - @XmlJavaTypeAdapter(CategoryAdapter.class) - @NotNull - public Category getCategory() { - return this.category; - } - - @XmlElement(name = "cookie") - @NotNull - public List getCookies() { - return this.cookies; - } - - @XmlAttribute(name = "tcDataflowFile", required = true) - @NotNull - public String getDataflowFile() { - return this.dataflowFile; - } - - @XmlElement(name = "formparam") - @NotNull - public List getFormParams() { - return this.formParams; - } - - @XmlAttribute(name = "URL", required = true) - @NotNull - public String getFullURL() { - return this.fullURL; - } - - @XmlElement(name = "getparam") - @NotNull - public List getGetParams() { - return this.getParams; - } - - @XmlElement(name = "header") - @NotNull - public List getHeaders() { - return this.headers; - } - - @XmlAttribute(name = "tcName", required = true) - @NotNull - public String getName() { - return this.name; - } - - @XmlTransient - public String getQuery() { - return this.query; - } - - @XmlAttribute(name = "tcSinkFile", required = true) - @NotNull - public String getSinkFile() { - return this.sinkFile; - } - - @XmlAttribute(name = "tcSourceFile", required = true) - @NotNull - public String getSourceFile() { - return this.sourceFile; - } - - @XmlAttribute(name = "tcSourceUIType", required = true) - @NotNull - public String getSourceUIType() { - return this.sourceUIType; - } - - @XmlAttribute(name = "tcTemplateFile", required = true) - @NotNull - public String getTemplateFile() { - return this.templateFile; - } - - @XmlAttribute(name = "tcType", required = true) - @XmlReadOnly - @NotNull - public TestCaseType getType() { - return this.tcType; - } - - @XmlAttribute(name = "tcUITemplateFile", required = true) - @NotNull - public String getUiTemplateFile() { - return this.uiTemplateFile; - } - - public boolean isUnverifiable() { - return getNotAutoverifiableReason() != null; - } - - @XmlAttribute(name = "tcNotAutoverifiable") - public String getNotAutoverifiableReason() { - return this.notAutoverifiableReason; - } - - @XmlAttribute(name = "tcVulnerable", required = true) - public boolean isVulnerability() { - return this.isVulnerability; - } - - public boolean isSafe() { - - boolean isSafe = true; - // Bitwise AND is done on all parameters isSafe() values. If ANY of them are unsafe, isSafe - // set to False. - for (RequestVariable header : getHeaders()) { - isSafe &= header.isSafe(); - } - - for (RequestVariable cookie : getCookies()) { - isSafe &= cookie.isSafe(); - } - - for (RequestVariable getParam : getGetParams()) { - isSafe &= getParam.isSafe(); - } - - for (RequestVariable formParam : getFormParams()) { - isSafe &= formParam.isSafe(); - } - - return isSafe; - } - - public String setAttackSuccessString(String attackSuccessString) { - return this.attackSuccessString = attackSuccessString; - } - - public void setCategory(Category category) { - this.category = category; - } - - public void setCookies(List cookies) { - this.cookies = cookies; - } - - public void setDataflowFile(String dataflowFile) { - this.dataflowFile = dataflowFile; - } - - public void setFormParams(List formParams) { - this.formParams = formParams; - } - - public void setFullURL(String fullURL) { - this.fullURL = fullURL; - } - - public void setGetParams(List getParams) { - this.getParams = getParams; - } - - public void setHeaders(List headers) { - this.headers = headers; - } - - public void setName(String name) { - this.name = name; - } - - public void setQuery(String query) { - this.query = query; - } - - public void setSinkFile(String sinkFile) { - this.sinkFile = sinkFile; - } - - public void setSourceFile(String sourceFile) { - this.sourceFile = sourceFile; - } - - public void setSourceUIType(String sourceUIType) { - this.sourceUIType = sourceUIType; - } - - public void setTemplateFile(String templateFile) { - this.templateFile = templateFile; - } - - public void setType(TestCaseType type) { - this.tcType = type; - } - - public void setUiTemplateFile(String uiTemplateFile) { - this.uiTemplateFile = uiTemplateFile; - } - - public void setNotAutoverifiableReason(String notAutoverifiableReason) { - this.notAutoverifiableReason = notAutoverifiableReason; - } - - public void setVulnerability(boolean isVulnerability) { - this.isVulnerability = isVulnerability; - } - - public void setSafe(boolean isSafe) { - // this.isSafe = isSafe; - for (RequestVariable header : getHeaders()) { - // setSafe() considers whether attack and safe values exist for this parameter before - // setting isSafe true or false. So you don't have to check that here. - header.setSafe(isSafe); - } - for (RequestVariable cookie : getCookies()) { - cookie.setSafe(isSafe); - } - for (RequestVariable getParam : getGetParams()) { - getParam.setSafe(isSafe); - } - for (RequestVariable formParam : getFormParams()) { - formParam.setSafe(isSafe); - } - } - - @Override - public String toString() { - return this.getClass().getSimpleName() - + " [category=" - + category - + ", name=" - + name - + ", uiTemplateFile=" - + new File(uiTemplateFile).getName() - + ", templateFile=" - + new File(templateFile).getName() - + ", sourceFile=" - + sourceFile - + ", sourceUIType=" - + sourceUIType - + ", dataflowFile=" - + dataflowFile - + ", sinkFile=" - + sinkFile - + ", fullURL=" - + fullURL - + ", getParams=" - + getParams - + ", headers=" - + headers - + ", cookies=" - + cookies - + ", formParams=" - + formParams - + ", isUnverifiable=" - + isUnverifiable - + ", isVulnerability=" - + isVulnerability - + ", attackSuccessString=" - + attackSuccessString - + ", isSafe=" - + isSafe() - + ", query=" - + query - + ", tcType=" - + tcType - + "]"; - } -} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseRequestFileParseException.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequestFileParseException.java similarity index 96% rename from plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseRequestFileParseException.java rename to plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequestFileParseException.java index 49c9ed57..68b10c9a 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCaseRequestFileParseException.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseRequestFileParseException.java @@ -15,7 +15,7 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.tools; public class TestCaseRequestFileParseException extends Exception { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java index f83c6ebe..57478f9b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -1,6 +1,7 @@ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.owasp.benchmarkutils.entities.ResponseInfo; +import org.owasp.benchmarkutils.entities.TestCase; /** Not a great class name. */ public class TestCaseVerificationResults { @@ -15,22 +16,22 @@ public class TestCaseVerificationResults { private boolean isPassed; - private HttpUriRequestBase attackRequest; + private TestExecutor attackTestExecutor; - private HttpUriRequestBase safeRequest; + private TestExecutor safeTestExecutor; - private AbstractTestCaseRequest requestTemplate; + private TestCase testCase; public TestCaseVerificationResults( - HttpUriRequestBase attackRequest, - HttpUriRequestBase safeRequest, - AbstractTestCaseRequest requestTemplate, + TestExecutor attackTestExecutor, + TestExecutor safeTestExecutor, + TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue) { this( - attackRequest, - safeRequest, - requestTemplate, + attackTestExecutor, + safeTestExecutor, + testCase, responseToAttackValue, responseToSafeValue, true, @@ -39,18 +40,18 @@ public TestCaseVerificationResults( } public TestCaseVerificationResults( - HttpUriRequestBase attackRequest, - HttpUriRequestBase safeRequest, - AbstractTestCaseRequest requestTemplate, + TestExecutor attackTestExecutor, + TestExecutor safeTestExecutor, + TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue, boolean isUnverifiable, boolean isDeclaredVerifiable, boolean isPassed) { super(); - this.attackRequest = attackRequest; - this.safeRequest = safeRequest; - this.requestTemplate = requestTemplate; + this.attackTestExecutor = attackTestExecutor; + this.safeTestExecutor = safeTestExecutor; + this.testCase = testCase; this.responseToAttackValue = responseToAttackValue; this.responseToSafeValue = responseToSafeValue; this.isUnverifiable = isUnverifiable; @@ -98,27 +99,27 @@ public boolean isPassed() { return isPassed; } - public HttpUriRequestBase getAttackRequest() { - return attackRequest; + public TestExecutor getAttackTestExecutor() { + return attackTestExecutor; } - public void setAttackRequest(HttpUriRequestBase attackRequest) { - this.attackRequest = attackRequest; + public void setAttackTestExecutor(TestExecutor attackTestExecutor) { + this.attackTestExecutor = attackTestExecutor; } - public HttpUriRequestBase getSafeRequest() { - return safeRequest; + public TestExecutor getSafeTestExecutor() { + return safeTestExecutor; } - public void setSafeRequest(HttpUriRequestBase safeRequest) { - this.safeRequest = safeRequest; + public void setSafeTestExecutor(TestExecutor safeTestExecutor) { + this.safeTestExecutor = safeTestExecutor; } - public AbstractTestCaseRequest getRequestTemplate() { - return requestTemplate; + public TestCase getTestCase() { + return testCase; } - public void setRequestTemplate(AbstractTestCaseRequest requestTemplate) { - this.requestTemplate = requestTemplate; + public void setTestCase(TestCase testCase) { + this.testCase = testCase; } } From 40da0fd44deb4c3fd31ce1df5282a74bd1a4cf90 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 16 Feb 2024 12:19:00 -0500 Subject: [PATCH 05/28] Add missing source files and fix bugs. --- .../entities/JerseyTestCaseInput.java | 68 +++++++++++++++ .../entities/ServletTestCaseInput.java | 83 +++++++++++++++++++ .../entities/SpringTestCaseInput.java | 67 +++++++++++++++ .../entities/TestCaseInput.java | 5 +- .../tools/BenchmarkCrawlerVerification.java | 6 +- .../benchmarkutils/tools/CliExecutor.java | 35 ++++++++ .../benchmarkutils/tools/HttpExecutor.java | 53 ++++++++++++ .../tools/RegressionTesting.java | 4 +- .../benchmarkutils/tools/TestExecutor.java | 5 ++ 9 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java new file mode 100644 index 00000000..3ecb0d7f --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java @@ -0,0 +1,68 @@ +package org.owasp.benchmarkutils.entities; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("Jersey") +// @XmlType(name = "HttpPostTestCaseInput") +public class JerseyTestCaseInput extends HttpTestCaseInput { + + @Override + void buildQueryString() { + setQueryString(""); + } + + @Override + void buildHeaders(HttpUriRequestBase request) { + request.addHeader("Content-Type", "application/xml; charset=utf-8"); + for (RequestVariable header : getHeaders()) { + String name = header.getName(); + String value = header.getValue(); + // System.out.println("Header:" + name + "=" + value); + request.addHeader(name, value); + } + } + + @Override + void buildCookies(HttpUriRequestBase request) { + for (RequestVariable cookie : getCookies()) { + String name = cookie.getName(); + String value = cookie.getValue(); + // System.out.println("Cookie:" + name + "=" + value); + request.addHeader("Cookie", name + "=" + value); + } + } + + @Override + void buildBodyParameters(HttpUriRequestBase request) { + String params = ""; + for (RequestVariable field : getFormParameters()) { + String name = field.getName(); + String value = field.getValue(); + params += "<" + name + ">" + escapeXML(value) + ""; + } + params += ""; + StringEntity paramsEnt = new StringEntity(params); + request.setEntity(paramsEnt); + } + + private static String escapeXML(String value) { + value = value.replace("&", "&"); + value = value.replace("\"", """); + value = value.replace("'", "'"); + value = value.replace("<", "<"); + value = value.replace(">", ">"); + + return value; + } + + @Override + HttpUriRequestBase createRequestInstance(String url) { + // Apparently all Jersey Requests are POSTS. Never any query string params per buildQuery() + // above. + HttpPost httpPost = new HttpPost(url); + return httpPost; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java new file mode 100644 index 00000000..5c125b23 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java @@ -0,0 +1,83 @@ +package org.owasp.benchmarkutils.entities; + +import java.util.ArrayList; +import java.util.List; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("Servlet") +public class ServletTestCaseInput extends HttpTestCaseInput { + + @Override + void buildQueryString() { + setQueryString(""); + boolean first = true; + for (RequestVariable field : getGetParameters()) { + if (first) { + setQueryString("?"); + first = false; + } else { + setQueryString(getQueryString() + "&"); + } + String name = field.getName(); + String value = field.getValue(); + // System.out.println(query); + setQueryString(getQueryString() + (name + "=" + urlEncode(value))); + } + } + + @Override + void buildHeaders(HttpUriRequestBase request) { + // AJAX does: text/plain;charset=UTF-8, while HTML Form: application/x-www-form-urlencoded + // request.addHeader("Content-Type", ";charset=UTF-8"); --This BREAKS BenchmarkCrawling + request.addHeader( + "Content-Type", "application/x-www-form-urlencoded"); // Works for both though + + for (RequestVariable header : getHeaders()) { + String name = header.getName(); + String value = header.getValue(); + // System.out.println("Header:" + name + "=" + value); + request.addHeader(name, value); + } + } + + @Override + void buildCookies(HttpUriRequestBase request) { + for (RequestVariable cookie : getCookies()) { + String name = cookie.getName(); + String value = cookie.getValue(); + // Note: URL encoding of a space becomes a +, which is OK for Java, but + // not other languages. So after URLEncoding, replace all + with %20, which is the + // standard URL encoding for a space char. + request.addHeader("Cookie", name + "=" + urlEncode(value).replace("+", "%20")); + } + } + + @Override + void buildBodyParameters(HttpUriRequestBase request) { + List fields = new ArrayList<>(); + for (RequestVariable formParam : getFormParameters()) { + fields.add(formParam.getNameValuePair()); + } + + // Add the body parameters to the request if there were any + if (fields.size() > 0) { + request.setEntity(new UrlEncodedFormEntity(fields)); + } + } + + @Override + HttpUriRequestBase createRequestInstance(String url) { + HttpUriRequestBase httpUriRequestBase; + if (getQueryString().length() == 0) { + httpUriRequestBase = new HttpPost(url); + } else { + httpUriRequestBase = new HttpGet(url); + } + return httpUriRequestBase; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java new file mode 100644 index 00000000..5991fbad --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java @@ -0,0 +1,67 @@ +package org.owasp.benchmarkutils.entities; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("Spring") +// @XmlType(name = "HttpPostTestCaseInput") +public class SpringTestCaseInput extends HttpTestCaseInput { + + @Override + void buildQueryString() { + setQueryString(""); + } + + @Override + void buildHeaders(HttpUriRequestBase request) { + request.addHeader("Content-type", "application/json"); // Should this add ;charset=utf-8? + // No: "Designating the encoding is somewhat redundant for JSON, since the default encoding + // for JSON is UTF-8." + for (RequestVariable header : getHeaders()) { + String name = header.getName(); + String value = header.getValue(); + System.out.println("Header:" + name + "=" + value); + request.addHeader(name, value); + } + } + + @Override + void buildCookies(HttpUriRequestBase request) { + for (RequestVariable cookie : getCookies()) { + String name = cookie.getName(); + String value = cookie.getValue(); + // System.out.println("Cookie:" + name + "=" + value); + request.addHeader("Cookie", name + "=" + value); + } + } + + @Override + void buildBodyParameters(HttpUriRequestBase request) { + boolean first = true; + String params = "{"; + for (RequestVariable field : getFormParameters()) { + String name = field.getName(); + String value = field.getValue(); + // System.out.println(name+"="+value); + if (first) { + first = false; + } else { + params = params + ","; + } + params = params + String.format("\"%s\":\"%s\"", name, value.replace("\"", "\\\"")); + } + params += "}"; + StringEntity paramsEnt = new StringEntity(params); + request.setEntity(paramsEnt); + } + + @Override + HttpUriRequestBase createRequestInstance(String url) { + // Apparently all Spring Requests are POSTS. Never any query string params per buildQuery() + // above. + HttpPost httpPost = new HttpPost(url); + return httpPost; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java index e10f0d25..f19a9481 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java @@ -8,8 +8,9 @@ CliFileExecutableTestCaseInput.class, ExecutableTestCaseInput.class, HttpTestCaseInput.class, - HttpGetTestCaseInput.class, - HttpPostTestCaseInput.class, + JerseyTestCaseInput.class, + ServletTestCaseInput.class, + SpringTestCaseInput.class, StdinExecutableTestCaseInput.class, TcpSocketTestCaseInput.class }) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index e71f4011..22d16d74 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -178,8 +178,8 @@ protected void crawl(TestSuite testSuite) throws Exception { // FIXME: A bit of a hack CliRequest attackRequest = executableTestCaseInput.buildAttackRequest(); CliRequest safeRequest = executableTestCaseInput.buildSafeRequest(); - // attackExecutor = new CliExecutor(attackRequest); - // safeExecutor = new CliExecutor(safeRequest); + attackExecutor = new CliExecutor(attackRequest); + safeExecutor = new CliExecutor(safeRequest); // Send the next test case request with its attack payload attackPayloadResponseInfo = execute(attackRequest); @@ -479,7 +479,7 @@ public static void main(String[] args) { // thisInstance can be set from execute() or here, depending on how this class is invoked // (via maven or command line) if (thisInstance == null) { - thisInstance = new BenchmarkCrawler(); + thisInstance = new BenchmarkCrawlerVerification(); } thisInstance.processCommandLineArgs(args); thisInstance.load(); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java new file mode 100644 index 00000000..86dbca0b --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java @@ -0,0 +1,35 @@ +package org.owasp.benchmarkutils.tools; + +import java.util.ArrayList; +import java.util.List; +import org.owasp.benchmarkutils.entities.CliRequest; +import org.owasp.benchmarkutils.entities.RequestVariable; + +public class CliExecutor implements TestExecutor { + CliRequest cliRequest; + + public CliExecutor(CliRequest cliRequest) { + super(); + this.cliRequest = cliRequest; + } + + public CliRequest getCliRequest() { + return cliRequest; + } + + public void setCliRequest(CliRequest cliRequest) { + this.cliRequest = cliRequest; + } + + public String getExecutorDescription() { + List commandTokens = new ArrayList<>(); + commandTokens.add(cliRequest.getCommand()); + for (RequestVariable requestVariable : cliRequest.getArgs()) { + commandTokens.add( + String.format( + "%s:%s%n", requestVariable.getName(), requestVariable.getValue())); + } + + return commandTokens.toString(); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java new file mode 100644 index 00000000..f18dd354 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java @@ -0,0 +1,53 @@ +package org.owasp.benchmarkutils.tools; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; + +public class HttpExecutor implements TestExecutor { + HttpUriRequest httpRequest; + + public HttpExecutor(HttpUriRequest httpRequest) { + super(); + this.httpRequest = httpRequest; + } + + public HttpUriRequest getHttpRequest() { + return httpRequest; + } + + public void setHttpRequest(HttpUriRequest httpRequest) { + this.httpRequest = httpRequest; + } + + public String getExecutorDescription() { + StringWriter stringWriter = new StringWriter(); + PrintWriter out = new PrintWriter(stringWriter); + + out.println(httpRequest.toString()); + for (Header header : httpRequest.getHeaders()) { + out.printf("%s:%s%n", header.getName(), header.getValue()); + } + if (httpRequest instanceof HttpPost) { + HttpPost postHttpRequest = (HttpPost) httpRequest; + out.println(); + try { + HttpEntity entity = postHttpRequest.getEntity(); + if (entity != null) { + out.println(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); + } + } catch (IOException e) { + System.out.println("ERROR: Could not parse HttpPost entities"); + e.printStackTrace(); + } + } + out.flush(); + return stringWriter.toString(); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 4884fbe4..948ebcb9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -221,13 +221,13 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log out.println(testCase.toString()); out.println(); out.println("Attack request:"); - out.printf(result.getAttackTestExecutor().getExecutorDescription(), out); + out.println(result.getAttackTestExecutor().getExecutorDescription()); out.println(); out.printf("Attack response: [%d]:%n", attackResponseInfo.getStatusCode()); out.println(attackResponseInfo == null ? "null" : attackResponseInfo.getResponseString()); out.println(); out.println("Safe request:"); - out.printf(result.getSafeTestExecutor().getExecutorDescription(), out); + out.println(result.getSafeTestExecutor().getExecutorDescription()); out.println(); out.printf("Safe response: [%d]:%n", attackResponseInfo.getStatusCode()); out.println(safeResponseInfo == null ? "null" : safeResponseInfo.getResponseString()); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java new file mode 100644 index 00000000..3becdd06 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java @@ -0,0 +1,5 @@ +package org.owasp.benchmarkutils.tools; + +interface TestExecutor { + public String getExecutorDescription(); +} From a07197a629750c80e6226dc26c8b50a62ca3291d Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 19 Feb 2024 22:22:57 -0500 Subject: [PATCH 06/28] Fix bugs in CLI test case execution. --- .../entities/CliResponseInfo.java | 42 ++++++++++++++ .../entities/HttpResponseInfo.java | 46 +++++++++++++++ .../benchmarkutils/entities/ResponseInfo.java | 41 +------------ .../owasp/benchmarkutils/helpers/Utils.java | 5 +- .../tools/BenchmarkCrawler.java | 28 ++++++++- .../tools/BenchmarkCrawlerVerification.java | 57 +++++++++++++------ 6 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java new file mode 100644 index 00000000..f329ec21 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java @@ -0,0 +1,42 @@ +package org.owasp.benchmarkutils.entities; + +public class CliResponseInfo implements ResponseInfo { + private int seconds; + private String output; + private int returnCode; + private CliRequest request; + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + @Override + public int getTimeInSeconds() { + return seconds; + } + + @Override + public void setTimeInSeconds(int seconds) { + this.seconds = seconds; + } + + public int getReturnCode() { + return returnCode; + } + + public void setReturnCode(int returnCode) { + this.returnCode = returnCode; + } + + public CliRequest getRequest() { + return request; + } + + public void setRequest(CliRequest request) { + this.request = request; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java new file mode 100644 index 00000000..0e113019 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java @@ -0,0 +1,46 @@ +package org.owasp.benchmarkutils.entities; + +import java.net.URISyntaxException; + +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; + +public class HttpResponseInfo implements ResponseInfo { + private String responseString; + private int seconds; + private int statusCode; + private HttpUriRequest requestBase; + + public String getResponseString() { + return responseString; + } + + public void setResponseString(String responseString) { + this.responseString = responseString; + } + + @Override + public int getTimeInSeconds() { + return seconds; + } + + @Override + public void setTimeInSeconds(int seconds) { + this.seconds = seconds; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public HttpUriRequest getRequestBase() { + return requestBase; + } + + public void setRequestBase(HttpUriRequest request) { + this.requestBase = request; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index 7dfe9375..22b8d43b 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -1,42 +1,7 @@ package org.owasp.benchmarkutils.entities; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +public interface ResponseInfo { + public int getTimeInSeconds(); -public class ResponseInfo { - private String responseString; - private int seconds; - private int statusCode; - private HttpUriRequest requestBase; - - public String getResponseString() { - return responseString; - } - - public void setResponseString(String responseString) { - this.responseString = responseString; - } - - public int getTimeInSeconds() { - return seconds; - } - - public void setTimeInSeconds(int seconds) { - this.seconds = seconds; - } - - public int getStatusCode() { - return statusCode; - } - - public void setStatusCode(int statusCode) { - this.statusCode = statusCode; - } - - public HttpUriRequest getRequestBase() { - return requestBase; - } - - public void setRequestBase(HttpUriRequest request) { - this.requestBase = request; - } + public void setTimeInSeconds(int seconds); } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 095b00b2..58e9f801 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -204,13 +204,12 @@ public static TestSuite parseHttpFile(File file) spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - + // Do unmarshall operation - String crawlerFileName = new File(Utils.DATA_DIR, CRAWLER_CONFIG_FILE).getPath(); Source xmlSource = new SAXSource( spf.newSAXParser().getXMLReader(), - new InputSource(new FileReader(crawlerFileName))); + new InputSource(new FileReader(file))); JAXBContext context = JAXBContextFactory.createContext(new Class[] {TestSuite.class}, null); Unmarshaller unmarshaller = context.createUnmarshaller(); unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index e5c1b7fa..2768409e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -17,9 +17,11 @@ */ package org.owasp.benchmarkutils.tools; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -29,6 +31,10 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.StringJoiner; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.commons.cli.CommandLine; @@ -59,6 +65,7 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.owasp.benchmarkutils.entities.CliRequest; +import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; import org.owasp.benchmarkutils.entities.HttpTestCaseInput; import org.owasp.benchmarkutils.entities.RequestVariable; @@ -254,7 +261,7 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient() * @param request - THe HTTP request to issue */ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { - ResponseInfo responseInfo = new ResponseInfo(); + HttpResponseInfo responseInfo = new HttpResponseInfo(); responseInfo.setRequestBase(request); CloseableHttpResponse response = null; @@ -308,7 +315,7 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r * @param request - THe HTTP request to issue */ static ResponseInfo execute(CliRequest request) { - ResponseInfo responseInfo = new ResponseInfo(); + CliResponseInfo responseInfo = new CliResponseInfo(); // responseInfo.setRequestBase(request); CloseableHttpResponse response = null; @@ -319,17 +326,24 @@ static ResponseInfo execute(CliRequest request) { } // executeArgs.addAll(request.getArgs()); System.out.println(String.join(" ", executeArgs)); - + StopWatch watch = new StopWatch(); watch.start(); try { // response = httpclient.execute(request); ProcessBuilder builder = new ProcessBuilder(executeArgs); + builder.inheritIO(); final Process process = builder.start(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringJoiner sj = new StringJoiner(System.getProperty("line.separator")); + String output = sj.toString(); + responseInfo.setOutput(output); int exitValue = process.waitFor(); // attackPayloadResponseInfo = new ResponseInfo(); // System.out.printf("Program terminated with return code: %s%n", exitValue); + responseInfo.setReturnCode(exitValue); + } catch (IOException | InterruptedException e) { e.printStackTrace(); } @@ -359,6 +373,14 @@ static ResponseInfo execute(CliRequest request) { // } return responseInfo; } + + private static void handleStream(InputStream inputStream) { + try (BufferedReader stdOutReader = new BufferedReader(new InputStreamReader(inputStream))) { + while (stdOutReader.readLine() != null) { + // + } + } + } /** * Process the command line arguments that make any configuration changes. diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 22d16d74..3c117ad2 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -38,7 +38,9 @@ import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.owasp.benchmarkutils.entities.CliRequest; +import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; +import org.owasp.benchmarkutils.entities.HttpResponseInfo; import org.owasp.benchmarkutils.entities.HttpTestCaseInput; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestCase; @@ -349,26 +351,49 @@ private void cleanupSetups(List testCaseSetups) throws TestCaseSe } private void log(ResponseInfo responseInfo) throws IOException { - // Log the response - HttpUriRequest requestBase = responseInfo.getRequestBase(); - String outputString = - String.format( - "--> (%d : %d sec)%n", - responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); - try { + if (responseInfo instanceof HttpResponseInfo) { + HttpResponseInfo httpResponseInfo = (HttpResponseInfo) responseInfo; + + // Log the response + HttpUriRequest requestBase = httpResponseInfo.getRequestBase(); + String outputString = + String.format( + "--> (%d : %d sec)%n", + httpResponseInfo.getStatusCode(), httpResponseInfo.getTimeInSeconds()); + try { + if (isTimingEnabled) { + if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } else { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else if (responseInfo instanceof CliResponseInfo) { + CliResponseInfo cliResponseInfo = (CliResponseInfo) responseInfo; + + // Log the response + CliRequest request = cliResponseInfo.getRequest(); + String responseString = + String.format( + "--> (%d : %d sec)%n", + cliResponseInfo.getReturnCode(), cliResponseInfo.getTimeInSeconds()); if (isTimingEnabled) { - if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); + if (cliResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(request.getCommand()); + tLogger.println(responseString); } } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); + tLogger.println(request.getCommand()); + tLogger.println(responseString); } - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + + } } /** From e21f491d32af9609873353e9e0b4b1940ed2963b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 19 Feb 2024 22:53:30 -0500 Subject: [PATCH 07/28] More CLI test case execution fixes. --- .../entities/CliResponseInfo.java | 12 ++-- .../entities/HttpResponseInfo.java | 4 +- .../entities/HttpTestCaseInput.java | 2 +- .../benchmarkutils/entities/ResponseInfo.java | 4 ++ .../owasp/benchmarkutils/helpers/Utils.java | 5 +- .../tools/BenchmarkCrawler.java | 27 +++----- .../tools/BenchmarkCrawlerVerification.java | 67 +++++++++---------- .../tools/RegressionTesting.java | 51 ++++++++++++-- 8 files changed, 104 insertions(+), 68 deletions(-) diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java index f329ec21..b3dfb808 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java @@ -2,16 +2,18 @@ public class CliResponseInfo implements ResponseInfo { private int seconds; - private String output; + private String responseString; private int returnCode; private CliRequest request; - public String getOutput() { - return output; + @Override + public String getResponseString() { + return responseString; } - public void setOutput(String output) { - this.output = output; + @Override + public void setResponseString(String responseString) { + this.responseString = responseString; } @Override diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java index 0e113019..fff335c1 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java @@ -1,7 +1,5 @@ package org.owasp.benchmarkutils.entities; -import java.net.URISyntaxException; - import org.apache.hc.client5.http.classic.methods.HttpUriRequest; public class HttpResponseInfo implements ResponseInfo { @@ -10,10 +8,12 @@ public class HttpResponseInfo implements ResponseInfo { private int statusCode; private HttpUriRequest requestBase; + @Override public String getResponseString() { return responseString; } + @Override public void setResponseString(String responseString) { this.responseString = responseString; } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java index 0e6190d9..851a2259 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java @@ -220,7 +220,7 @@ String urlEncode(String input) { * @param request - THe HTTP request to issue */ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { - ResponseInfo responseInfo = new ResponseInfo(); + HttpResponseInfo responseInfo = new HttpResponseInfo(); responseInfo.setRequestBase(request); CloseableHttpResponse response = null; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index 22b8d43b..4bba4a76 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -1,6 +1,10 @@ package org.owasp.benchmarkutils.entities; public interface ResponseInfo { + public String getResponseString(); + + public void setResponseString(String responseString); + public int getTimeInSeconds(); public void setTimeInSeconds(int seconds); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 58e9f801..53e02df2 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -204,12 +204,11 @@ public static TestSuite parseHttpFile(File file) spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - + // Do unmarshall operation Source xmlSource = new SAXSource( - spf.newSAXParser().getXMLReader(), - new InputSource(new FileReader(file))); + spf.newSAXParser().getXMLReader(), new InputSource(new FileReader(file))); JAXBContext context = JAXBContextFactory.createContext(new Class[] {TestSuite.class}, null); Unmarshaller unmarshaller = context.createUnmarshaller(); unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 2768409e..8a8c36b2 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -32,9 +32,6 @@ import java.util.Date; import java.util.List; import java.util.StringJoiner; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.commons.cli.CommandLine; @@ -67,6 +64,7 @@ import org.owasp.benchmarkutils.entities.CliRequest; import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; +import org.owasp.benchmarkutils.entities.HttpResponseInfo; import org.owasp.benchmarkutils.entities.HttpTestCaseInput; import org.owasp.benchmarkutils.entities.RequestVariable; import org.owasp.benchmarkutils.entities.ResponseInfo; @@ -261,7 +259,7 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient() * @param request - THe HTTP request to issue */ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { - HttpResponseInfo responseInfo = new HttpResponseInfo(); + HttpResponseInfo responseInfo = new HttpResponseInfo(); responseInfo.setRequestBase(request); CloseableHttpResponse response = null; @@ -319,14 +317,15 @@ static ResponseInfo execute(CliRequest request) { // responseInfo.setRequestBase(request); CloseableHttpResponse response = null; - List executeArgs = Arrays.asList(request.getCommand().split(" ")); + ArrayList executeArgs = + new ArrayList<>(Arrays.asList(request.getCommand().split(" "))); for (RequestVariable arg : request.getArgs()) { executeArgs.add(arg.getName()); executeArgs.add(arg.getValue()); } // executeArgs.addAll(request.getArgs()); System.out.println(String.join(" ", executeArgs)); - + StopWatch watch = new StopWatch(); watch.start(); @@ -335,15 +334,17 @@ static ResponseInfo execute(CliRequest request) { ProcessBuilder builder = new ProcessBuilder(executeArgs); builder.inheritIO(); final Process process = builder.start(); - final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + final BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); StringJoiner sj = new StringJoiner(System.getProperty("line.separator")); + reader.lines().iterator().forEachRemaining(sj::add); String output = sj.toString(); - responseInfo.setOutput(output); + responseInfo.setResponseString(output); int exitValue = process.waitFor(); // attackPayloadResponseInfo = new ResponseInfo(); // System.out.printf("Program terminated with return code: %s%n", exitValue); responseInfo.setReturnCode(exitValue); - + } catch (IOException | InterruptedException e) { e.printStackTrace(); } @@ -373,14 +374,6 @@ static ResponseInfo execute(CliRequest request) { // } return responseInfo; } - - private static void handleStream(InputStream inputStream) { - try (BufferedReader stdOutReader = new BufferedReader(new InputStreamReader(inputStream))) { - while (stdOutReader.readLine() != null) { - // - } - } - } /** * Process the command line arguments that make any configuration changes. diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 3c117ad2..3c61a957 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -351,38 +351,38 @@ private void cleanupSetups(List testCaseSetups) throws TestCaseSe } private void log(ResponseInfo responseInfo) throws IOException { - if (responseInfo instanceof HttpResponseInfo) { - HttpResponseInfo httpResponseInfo = (HttpResponseInfo) responseInfo; - - // Log the response - HttpUriRequest requestBase = httpResponseInfo.getRequestBase(); - String outputString = - String.format( - "--> (%d : %d sec)%n", - httpResponseInfo.getStatusCode(), httpResponseInfo.getTimeInSeconds()); - try { - if (isTimingEnabled) { - if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } - } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } else if (responseInfo instanceof CliResponseInfo) { - CliResponseInfo cliResponseInfo = (CliResponseInfo) responseInfo; - - // Log the response - CliRequest request = cliResponseInfo.getRequest(); - String responseString = - String.format( - "--> (%d : %d sec)%n", - cliResponseInfo.getReturnCode(), cliResponseInfo.getTimeInSeconds()); + if (responseInfo instanceof HttpResponseInfo) { + HttpResponseInfo httpResponseInfo = (HttpResponseInfo) responseInfo; + + // Log the response + HttpUriRequest requestBase = httpResponseInfo.getRequestBase(); + String outputString = + String.format( + "--> (%d : %d sec)%n", + httpResponseInfo.getStatusCode(), httpResponseInfo.getTimeInSeconds()); + try { + if (isTimingEnabled) { + if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } else { + tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(outputString); + } + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } else if (responseInfo instanceof CliResponseInfo) { + CliResponseInfo cliResponseInfo = (CliResponseInfo) responseInfo; + + // Log the response + CliRequest request = cliResponseInfo.getRequest(); + String responseString = + String.format( + "--> (%d : %d sec)%n", + cliResponseInfo.getReturnCode(), cliResponseInfo.getTimeInSeconds()); if (isTimingEnabled) { if (cliResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { tLogger.println(request.getCommand()); @@ -392,8 +392,7 @@ private void log(ResponseInfo responseInfo) throws IOException { tLogger.println(request.getCommand()); tLogger.println(responseString); } - - } + } } /** diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 948ebcb9..bbd3352c 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -34,6 +34,8 @@ import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpMessage; +import org.owasp.benchmarkutils.entities.CliResponseInfo; +import org.owasp.benchmarkutils.entities.HttpResponseInfo; import org.owasp.benchmarkutils.entities.HttpTestCaseInput; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestCase; @@ -223,14 +225,43 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log out.println("Attack request:"); out.println(result.getAttackTestExecutor().getExecutorDescription()); out.println(); - out.printf("Attack response: [%d]:%n", attackResponseInfo.getStatusCode()); - out.println(attackResponseInfo == null ? "null" : attackResponseInfo.getResponseString()); + if (attackResponseInfo instanceof HttpResponseInfo) { + out.printf( + "Attack response: [%d]:%n", + ((HttpResponseInfo) attackResponseInfo).getStatusCode()); + out.println( + attackResponseInfo == null + ? "null" + : ((HttpResponseInfo) attackResponseInfo).getResponseString()); + } else if (attackResponseInfo instanceof CliResponseInfo) { + out.printf( + "Attack response: [%d]:%n", + ((CliResponseInfo) attackResponseInfo).getReturnCode()); + out.println( + attackResponseInfo == null + ? "null" + : ((CliResponseInfo) attackResponseInfo).getResponseString()); + } out.println(); out.println("Safe request:"); out.println(result.getSafeTestExecutor().getExecutorDescription()); out.println(); - out.printf("Safe response: [%d]:%n", attackResponseInfo.getStatusCode()); - out.println(safeResponseInfo == null ? "null" : safeResponseInfo.getResponseString()); + if (safeResponseInfo instanceof HttpResponseInfo) { + out.printf( + "Safe response: [%d]:%n", + ((HttpResponseInfo) safeResponseInfo).getStatusCode()); + out.println( + safeResponseInfo == null + ? "null" + : ((HttpResponseInfo) safeResponseInfo).getResponseString()); + } else if (safeResponseInfo instanceof CliResponseInfo) { + out.printf( + "Safe response: [%d]:%n", ((CliResponseInfo) safeResponseInfo).getReturnCode()); + out.println( + safeResponseInfo == null + ? "null" + : ((CliResponseInfo) safeResponseInfo).getResponseString()); + } out.println(); out.printf("Attack success indicator: -->%s<--%n", testCase.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); @@ -425,8 +456,16 @@ private static List findErrors(ResponseInfo responseInfo, String prefix) List reasons = new ArrayList<>(); if (responseInfo != null) { - if (responseInfo.getStatusCode() != 200) { - reasons.add(prefix + " response code: " + responseInfo.getStatusCode()); + if (responseInfo instanceof HttpResponseInfo) { + int statusCode = ((HttpResponseInfo) responseInfo).getStatusCode(); + if (statusCode != 200) { + reasons.add(prefix + " response code: " + statusCode); + } + } else if (responseInfo instanceof CliResponseInfo) { + int returnCode = ((CliResponseInfo) responseInfo).getReturnCode(); + if (returnCode != 0) { + reasons.add(prefix + " response code: " + returnCode); + } } if (responseInfo.getResponseString().toLowerCase().contains("error")) { reasons.add(prefix + " response contains: error"); From 42770df4d9f42fd1b78abb64e8f9bad6ceb22789 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 19 Feb 2024 23:21:53 -0500 Subject: [PATCH 08/28] Fix NullPointerException --- .../java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 8a8c36b2..e6de16c4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -314,6 +314,7 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r */ static ResponseInfo execute(CliRequest request) { CliResponseInfo responseInfo = new CliResponseInfo(); + responseInfo.setRequest(request); // responseInfo.setRequestBase(request); CloseableHttpResponse response = null; From 4ca0d584125e8d14555c947a29f55392be4d4571 Mon Sep 17 00:00:00 2001 From: david Date: Tue, 20 Feb 2024 11:18:24 -0500 Subject: [PATCH 09/28] Fix setSafe() side effect bug. --- .../CliArgExecutableTestCaseInput.java | 23 ++++++++++++++----- .../benchmarkutils/entities/CliRequest.java | 14 +++++++++-- .../entities/RequestVariable.java | 13 +++++++++++ .../tools/BenchmarkCrawlerVerification.java | 2 ++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java index e059bdc3..2e9c96b5 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java @@ -33,7 +33,9 @@ public List getArgs() { } public void setArgs(List args) { - this.args = args; + // Copy the given list so setSafe() does not affect other CliArgExecutableTestCaseInput + // objects. + this.args = new ArrayList<>(args); } public void addArg(RequestVariable arg) { @@ -48,14 +50,23 @@ public CliRequest buildAttackRequest() { // // FIXME: This will break if the command string has arguments that contain spaces. // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); // executeArgs.addAll(getArgs()); - - setSafe(false); - return new CliRequest(getCommand(), getArgs()); + ArrayList argsCopy = new ArrayList<>(); + for (RequestVariable arg : args) { + RequestVariable argCopy = new RequestVariable(arg); + argCopy.setSafe(false); + argsCopy.add(argCopy); + } + return new CliRequest(getCommand(), argsCopy); } public CliRequest buildSafeRequest() { - setSafe(true); - return new CliRequest(getCommand(), getArgs()); + ArrayList argsCopy = new ArrayList<>(); + for (RequestVariable arg : args) { + RequestVariable argCopy = new RequestVariable(arg); + argCopy.setSafe(true); + argsCopy.add(argCopy); + } + return new CliRequest(getCommand(), argsCopy); } public void setSafe(boolean isSafe) { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java index 26ecebfb..fa18b0b2 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -12,12 +12,14 @@ public class CliRequest { public CliRequest(String command, List args) { super(); this.command = command; - this.args = args; + this.args = new ArrayList(args); } public CliRequest(String command, RequestVariable arg) { super(); this.command = command; + // Make a copy of the given args list so that when setSafe() changes elements, the changes + // do not affect other CliRequest objects. this.args = new ArrayList(Arrays.asList(arg)); } @@ -34,7 +36,7 @@ public List getArgs() { } public void setArgs(List args) { - this.args = args; + this.args = new ArrayList(args); } // public List getExecuteArgs() { @@ -42,4 +44,12 @@ public void setArgs(List args) { // executeArgs.addAll(getArgs()); // return executeArgs; // } + + public String toString() { + ArrayList executeArgs = new ArrayList<>(Arrays.asList(command.split(" "))); + for (RequestVariable arg : args) { + executeArgs.add(arg.getValue()); + } + return String.join(" ", executeArgs); + } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java index 70fd2d21..04170116 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java @@ -45,6 +45,19 @@ public RequestVariable( isSafe = name.equals(safeName) && value.equals(safeValue); } + public RequestVariable(RequestVariable otherRequestVariable) { + super(); + this.name = otherRequestVariable.getName(); + this.value = otherRequestVariable.getValue(); + this.attackName = otherRequestVariable.getAttackName(); + this.attackValue = otherRequestVariable.getAttackValue(); + this.safeName = otherRequestVariable.getSafeName(); + this.safeValue = otherRequestVariable.getSafeValue(); + if (name == null) throw new NullPointerException("name parameter cannot be null"); + if (value == null) throw new NullPointerException("value parameter cannot be null"); + isSafe = name.equals(safeName) && value.equals(safeValue); + } + @XmlAttribute @NotNull public String getName() { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 3c61a957..49c05c89 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -184,6 +184,7 @@ protected void crawl(TestSuite testSuite) throws Exception { safeExecutor = new CliExecutor(safeRequest); // Send the next test case request with its attack payload + System.out.println("Executing attack request: " + attackRequest); attackPayloadResponseInfo = execute(attackRequest); //// executeArgs.add(payload); // ProcessBuilder builder = new @@ -201,6 +202,7 @@ protected void crawl(TestSuite testSuite) throws Exception { safePayloadResponseInfo = null; if (!testCase.isUnverifiable()) { // Send the next test case request with its safe payload + System.out.println("Executing safe request: " + safeRequest); safePayloadResponseInfo = execute(safeRequest); responseInfoList.add(safePayloadResponseInfo); From 161e1658f7770de23e61ca4801298c43d8880463 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 21 Feb 2024 03:29:38 -0500 Subject: [PATCH 10/28] Correctly set the working directory when executing Python test cases. --- .../tools/BenchmarkCrawler.java | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index e6de16c4..77220de9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -309,22 +309,19 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r * and the global variable timeString the URL tested, the time required to execute and the * response code. * - * @param httpclient - The HTTP client to use to make the request - * @param request - THe HTTP request to issue + * @param request - THe CLI request to issue */ static ResponseInfo execute(CliRequest request) { CliResponseInfo responseInfo = new CliResponseInfo(); responseInfo.setRequest(request); // responseInfo.setRequestBase(request); - CloseableHttpResponse response = null; ArrayList executeArgs = new ArrayList<>(Arrays.asList(request.getCommand().split(" "))); for (RequestVariable arg : request.getArgs()) { - executeArgs.add(arg.getName()); +// System.out.println("Adding arg: " + arg.getValue()); executeArgs.add(arg.getValue()); } - // executeArgs.addAll(request.getArgs()); System.out.println(String.join(" ", executeArgs)); StopWatch watch = new StopWatch(); @@ -333,6 +330,7 @@ static ResponseInfo execute(CliRequest request) { try { // response = httpclient.execute(request); ProcessBuilder builder = new ProcessBuilder(executeArgs); + builder.directory(new File("../../julietpy/testcode")); builder.inheritIO(); final Process process = builder.start(); final BufferedReader reader = @@ -351,28 +349,6 @@ static ResponseInfo execute(CliRequest request) { } watch.stop(); - // try { - // HttpEntity entity = response.getEntity(); - // int statusCode = response.getCode(); - // responseInfo.setStatusCode(statusCode); - // int seconds = (int) watch.getTime() / 1000; - // responseInfo.setTimeInSeconds(seconds); - // System.out.printf("--> (%d : %d sec)%n", statusCode, seconds); - // - // try { - // responseInfo.setResponseString(EntityUtils.toString(entity)); - // EntityUtils.consume(entity); - // } catch (IOException | org.apache.hc.core5.http.ParseException e) { - // e.printStackTrace(); - // } - // } finally { - // if (response != null) - // try { - // response.close(); - // } catch (IOException e) { - // e.printStackTrace(); - // } - // } return responseInfo; } From b68d4c38d9398f405bf998134f43283da24085eb Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 29 Feb 2024 10:19:01 -0500 Subject: [PATCH 11/28] Redirect stdout of executed test case correctly. Also, add some support for the TCP/IP socket input type. --- .../CliArgExecutableTestCaseInput.java | 4 +- .../CliFileExecutableTestCaseInput.java | 4 +- .../benchmarkutils/entities/CliRequest.java | 39 +++++++++++++------ .../StdinExecutableTestCaseInput.java | 16 +++++--- .../entities/TcpSocketTestCaseInput.java | 4 +- .../tools/BenchmarkCrawler.java | 39 ++++++++++++------- .../benchmarkutils/tools/CliExecutor.java | 4 +- 7 files changed, 72 insertions(+), 38 deletions(-) diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java index 2e9c96b5..2cf550b1 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java @@ -56,7 +56,7 @@ public CliRequest buildAttackRequest() { argCopy.setSafe(false); argsCopy.add(argCopy); } - return new CliRequest(getCommand(), argsCopy); + return new CliRequest(getCommand(), argsCopy, null); } public CliRequest buildSafeRequest() { @@ -66,7 +66,7 @@ public CliRequest buildSafeRequest() { argCopy.setSafe(true); argsCopy.add(argCopy); } - return new CliRequest(getCommand(), argsCopy); + return new CliRequest(getCommand(), argsCopy, null); } public void setSafe(boolean isSafe) { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java index 13439b3e..18732c25 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java @@ -23,12 +23,12 @@ public CliRequest buildAttackRequest() { // executeArgs.addAll(getArgs()); setSafe(false); - return new CliRequest(getCommand(), getFileArgs()); + return new CliRequest(getCommand(), getFileArgs(), null); } public CliRequest buildSafeRequest() { setSafe(true); - return new CliRequest(getCommand(), getFileArgs()); + return new CliRequest(getCommand(), getFileArgs(), null); } public void setSafe(boolean isSafe) { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java index fa18b0b2..2f1c08de 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -5,23 +5,28 @@ import java.util.List; public class CliRequest { - String command; + private String command; - List args; + private List args; - public CliRequest(String command, List args) { + private RequestVariable stdinData; + + public CliRequest(String command, List args, RequestVariable stdinData) { super(); this.command = command; this.args = new ArrayList(args); + this.stdinData = stdinData; } - public CliRequest(String command, RequestVariable arg) { - super(); - this.command = command; - // Make a copy of the given args list so that when setSafe() changes elements, the changes - // do not affect other CliRequest objects. - this.args = new ArrayList(Arrays.asList(arg)); - } + // public CliRequest(String command, RequestVariable arg, RequestVariable stdinData) { + // super(); + // this.command = command; + // // Make a copy of the given args list so that when setSafe() changes elements, the + // changes + // // do not affect other CliRequest objects. + // this.args = new ArrayList(Arrays.asList(arg)); + // this.stdinData = stdinData; + // } public String getCommand() { return command; @@ -45,11 +50,23 @@ public void setArgs(List args) { // return executeArgs; // } + public RequestVariable getStdinData() { + return stdinData; + } + + public void setStdinData(RequestVariable stdinData) { + this.stdinData = stdinData; + } + public String toString() { ArrayList executeArgs = new ArrayList<>(Arrays.asList(command.split(" "))); for (RequestVariable arg : args) { executeArgs.add(arg.getValue()); } - return String.join(" ", executeArgs); + String s = String.join(" ", executeArgs); + if (getStdinData() != null) { + s += " stdin: " + getStdinData().getValue(); + } + return s; } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java index e6f3637b..d3af511f 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java @@ -4,9 +4,15 @@ @XmlDiscriminatorValue("Stdin") public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { + + RequestVariable stdinData; + public RequestVariable getStdinData() { - // FIXME - return null; + return stdinData; + } + + public void setStdinData(RequestVariable stdinData) { + this.stdinData = stdinData; } public CliRequest buildAttackRequest() { @@ -15,13 +21,13 @@ public CliRequest buildAttackRequest() { // executeArgs.addAll(Arrays.asList(getCommand().split(" "))); // executeArgs.addAll(getArgs()); - setSafe(false); - return new CliRequest(getCommand(), getStdinData()); + stdinData.setSafe(false); + return new CliRequest(getCommand(), null, getStdinData()); } public CliRequest buildSafeRequest() { setSafe(true); - return new CliRequest(getCommand(), getStdinData()); + return new CliRequest(getCommand(), null, getStdinData()); } public void setSafe(boolean isSafe) { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java index 2c9608eb..a0c298b6 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java @@ -17,12 +17,12 @@ public CliRequest buildAttackRequest() { // executeArgs.addAll(getArgs()); setSafe(false); - return new CliRequest(getCommand(), getTcpSocketData()); + return new CliRequest(getCommand(), null, getTcpSocketData()); } public CliRequest buildSafeRequest() { setSafe(true); - return new CliRequest(getCommand(), getTcpSocketData()); + return new CliRequest(getCommand(), null, getTcpSocketData()); } public void setSafe(boolean isSafe) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 77220de9..24ce8609 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -18,10 +18,12 @@ package org.owasp.benchmarkutils.tools; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -319,7 +321,7 @@ static ResponseInfo execute(CliRequest request) { ArrayList executeArgs = new ArrayList<>(Arrays.asList(request.getCommand().split(" "))); for (RequestVariable arg : request.getArgs()) { -// System.out.println("Adding arg: " + arg.getValue()); + // System.out.println("Adding arg: " + arg.getValue()); executeArgs.add(arg.getValue()); } System.out.println(String.join(" ", executeArgs)); @@ -331,18 +333,29 @@ static ResponseInfo execute(CliRequest request) { // response = httpclient.execute(request); ProcessBuilder builder = new ProcessBuilder(executeArgs); builder.directory(new File("../../julietpy/testcode")); - builder.inheritIO(); - final Process process = builder.start(); - final BufferedReader reader = - new BufferedReader(new InputStreamReader(process.getInputStream())); - StringJoiner sj = new StringJoiner(System.getProperty("line.separator")); - reader.lines().iterator().forEachRemaining(sj::add); - String output = sj.toString(); - responseInfo.setResponseString(output); - int exitValue = process.waitFor(); - // attackPayloadResponseInfo = new ResponseInfo(); - // System.out.printf("Program terminated with return code: %s%n", exitValue); - responseInfo.setReturnCode(exitValue); + builder.redirectErrorStream(true); + Process process = builder.start(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedWriter writer = + new BufferedWriter( + new OutputStreamWriter(process.getOutputStream())); ) { + if (request.getStdinData() != null) { + writer.write(request.getStdinData().getValue()); + writer.flush(); + writer.close(); + } + + StringJoiner sj = new StringJoiner(System.getProperty("line.separator")); + reader.lines().iterator().forEachRemaining(sj::add); + String output = sj.toString(); + responseInfo.setResponseString(output); + int exitValue = process.waitFor(); + // attackPayloadResponseInfo = new ResponseInfo(); + // System.out.printf("Program terminated with return code: %s%n", + // exitValue); + responseInfo.setReturnCode(exitValue); + } } catch (IOException | InterruptedException e) { e.printStackTrace(); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java index 86dbca0b..8c24001b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java @@ -25,9 +25,7 @@ public String getExecutorDescription() { List commandTokens = new ArrayList<>(); commandTokens.add(cliRequest.getCommand()); for (RequestVariable requestVariable : cliRequest.getArgs()) { - commandTokens.add( - String.format( - "%s:%s%n", requestVariable.getName(), requestVariable.getValue())); + commandTokens.add(String.format("%s%n", requestVariable.getValue())); } return commandTokens.toString(); From 8307dd198cfe73c3e6cfd88947b2ece6928a7625 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 10 Mar 2024 20:52:11 -0400 Subject: [PATCH 12/28] Fix NullPointerException when logging a CliRequest object. Also, add the testCaseName option to the Maven plugin entery-point. --- .../benchmarkutils/entities/CliRequest.java | 12 ++++++++++-- .../entities/StdinExecutableTestCaseInput.java | 4 ++++ ...va => TcpSocketExecutableTestCaseInput.java} | 2 +- .../benchmarkutils/entities/TestCaseInput.java | 2 +- .../benchmarkutils/tools/BenchmarkCrawler.java | 17 ++++++++++++++--- .../tools/BenchmarkCrawlerVerification.java | 17 +++++++++++++++-- 6 files changed, 45 insertions(+), 9 deletions(-) rename library/src/main/java/org/owasp/benchmarkutils/entities/{TcpSocketTestCaseInput.java => TcpSocketExecutableTestCaseInput.java} (94%) diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java index 2f1c08de..8b7f0b68 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -14,7 +14,11 @@ public class CliRequest { public CliRequest(String command, List args, RequestVariable stdinData) { super(); this.command = command; - this.args = new ArrayList(args); + if (args == null) { + this.args = new ArrayList(); + } else { + this.args = new ArrayList(args); + } this.stdinData = stdinData; } @@ -41,7 +45,11 @@ public List getArgs() { } public void setArgs(List args) { - this.args = new ArrayList(args); + if (args == null) { + this.args = new ArrayList(); + } else { + this.args = new ArrayList(args); + } } // public List getExecuteArgs() { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java index d3af511f..ac85b5ab 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java @@ -1,5 +1,7 @@ package org.owasp.benchmarkutils.entities; +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlElement; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("Stdin") @@ -7,6 +9,8 @@ public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { RequestVariable stdinData; + @XmlElement(name = "stdinData", required = true) + @NotNull public RequestVariable getStdinData() { return stdinData; } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java similarity index 94% rename from library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java index a0c298b6..b97a7ab6 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java @@ -3,7 +3,7 @@ import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; @XmlDiscriminatorValue("TcpSocket") -public class TcpSocketTestCaseInput extends ExecutableTestCaseInput { +public class TcpSocketExecutableTestCaseInput extends ExecutableTestCaseInput { public RequestVariable getTcpSocketData() { // FIXME diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java index f19a9481..b35b4bd7 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java @@ -12,7 +12,7 @@ ServletTestCaseInput.class, SpringTestCaseInput.class, StdinExecutableTestCaseInput.class, - TcpSocketTestCaseInput.class + TcpSocketExecutableTestCaseInput.class }) @XmlDiscriminatorNode("@type") public abstract class TestCaseInput { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 24ce8609..91e6b249 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -85,6 +85,9 @@ public class BenchmarkCrawler extends AbstractMojo { @Parameter(property = "crawlerFile") String pluginFilenameParam; + @Parameter(property = "testCaseName") + String pluginTestCaseNameParam; + /* * Attaching the @Parameter property to the crawlerFile variable directly didn't work for some * reason. So I attached it to a new String variable, and set it later. No clue why it doesn't @@ -324,7 +327,7 @@ static ResponseInfo execute(CliRequest request) { // System.out.println("Adding arg: " + arg.getValue()); executeArgs.add(arg.getValue()); } - System.out.println(String.join(" ", executeArgs)); + // System.out.println(String.join(" ", executeArgs)); StopWatch watch = new StopWatch(); @@ -332,6 +335,7 @@ static ResponseInfo execute(CliRequest request) { try { // response = httpclient.execute(request); ProcessBuilder builder = new ProcessBuilder(executeArgs); + // FIXME: Do not hardcode this path builder.directory(new File("../../julietpy/testcode")); builder.redirectErrorStream(true); Process process = builder.start(); @@ -424,8 +428,15 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.pluginFilenameParam) { System.out.println("ERROR: A crawlerFile parameter must be specified."); } else { - String[] mainArgs = {"-f", this.pluginFilenameParam}; - main(mainArgs); + // String[] mainArgs = {"-f", this.pluginFilenameParam}; + List mainArgs = new ArrayList<>(); + mainArgs.add("-f"); + mainArgs.add(this.pluginFilenameParam); + if (this.pluginTestCaseNameParam != null) { + mainArgs.add("-n"); + mainArgs.add(this.pluginTestCaseNameParam); + } + main(mainArgs.stream().toArray(String[]::new)); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 49c05c89..039155bd 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -121,6 +121,13 @@ protected void crawl(TestSuite testSuite) throws Exception { for (TestCase testCase : testSuite.getTestCases()) { + // if (this.selectedTestCaseName != null) { + // if + // (!testCase.getName().equals(this.selectedTestCaseName)) { + // continue; + // } + // } + // TestCaseVerificationResults result = testCase.execute(); // results.add(result); @@ -496,8 +503,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.pluginFilenameParam) { System.out.println("ERROR: A crawlerFile parameter must be specified."); } else { - String[] mainArgs = {"-f", this.pluginFilenameParam}; - main(mainArgs); + List mainArgs = new ArrayList<>(); + mainArgs.add("-f"); + mainArgs.add(this.pluginFilenameParam); + if (this.pluginTestCaseNameParam != null) { + mainArgs.add("-n"); + mainArgs.add(this.pluginTestCaseNameParam); + } + main(mainArgs.stream().toArray(String[]::new)); } } From 8a866cb922c6e27a594b060339f3e1b9afd1fb74 Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Fri, 15 Mar 2024 11:03:14 -0400 Subject: [PATCH 13/28] Centralize version management for shared dependencies in dependencyManagement section of parent pom, eliminate declared but unused dependencies, and upgrade all dependencies and plugins. --- library/pom.xml | 59 +++++------------------------------- plugin/pom.xml | 80 +++++++++++++++++++------------------------------ pom.xml | 66 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 98 insertions(+), 107 deletions(-) diff --git a/library/pom.xml b/library/pom.xml index 8d874944..887be99f 100755 --- a/library/pom.xml +++ b/library/pom.xml @@ -17,81 +17,42 @@ commons-lang commons-lang - 2.6 - - - - com.fasterxml.jackson.core - jackson-annotations - ${version.fasterxml.jackson} - - - - com.fasterxml.jackson.core - jackson-core - ${version.fasterxml.jackson} - - - - com.fasterxml.jackson.core - jackson-databind - ${version.fasterxml.jackson} - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - ${version.fasterxml.jackson} com.google.guava guava - 33.0.0-jre - javax.xml.bind - jaxb-api - 2.4.0-b180830.0359 - - - - org.eclipse.persistence - org.eclipse.persistence.core - ${version.eclipse.persistence} + javax.validation + validation-api + 2.0.1.Final - - org.eclipse.persistence - org.eclipse.persistence.moxy - ${version.eclipse.persistence} + javax.xml.bind + jaxb-api org.apache.httpcomponents.client5 httpclient5 - 5.3 org.apache.httpcomponents.core5 httpcore5 - 5.2.4 - javax.validation - validation-api - 2.0.1.Final + org.eclipse.persistence + org.eclipse.persistence.core xml-apis xml-apis - - 1.4.01 @@ -113,11 +74,5 @@ - - 2.16.1 - - 2.7.14 - - diff --git a/plugin/pom.xml b/plugin/pom.xml index 68810518..47cc9fd9 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -20,6 +20,23 @@ 1.3 + + commons-cli + commons-cli + 1.6.0 + + + + commons-io + commons-io + 2.15.1 + + + + commons-lang + commons-lang + + com.contrastsecurity java-sarif @@ -53,31 +70,19 @@ com.google.guava guava - 33.0.0-jre + + javax.xml.bind jaxb-api - 2.4.0-b180830.0359 - - - - commons-cli - commons-cli - 1.6.0 - - - - commons-io - commons-io - 2.15.1 - - - - commons-lang - commons-lang - 2.6 @@ -95,13 +100,11 @@ org.apache.httpcomponents.client5 httpclient5 - 5.3 org.apache.httpcomponents.core5 httpcore5 - 5.2.4 @@ -114,21 +117,13 @@ org.apache.maven.plugin-tools maven-plugin-annotations - 3.10.2 + 3.11.0 provided - - org.eclipse.persistence - org.eclipse.persistence.core - ${version.eclipse.persistence} - - - org.eclipse.persistence org.eclipse.persistence.moxy - ${version.eclipse.persistence} @@ -140,7 +135,7 @@ org.json json - 20231013 + 20240303 @@ -149,27 +144,12 @@ 2.2 - - - - javax.validation - validation-api - 2.0.1.Final - - xml-apis xml-apis - - 1.4.01 + org.junit.jupiter junit-jupiter-api @@ -198,10 +178,10 @@ - 2.16.1 + 2.17.0 2.7.14 - 5.10.1 + 5.10.2 diff --git a/pom.xml b/pom.xml index 69ba8d98..8b8d3e53 100755 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,59 @@ plugin + + + + com.google.guava + guava + 33.1.0-jre + + + + commons-lang + commons-lang + 2.6 + + + + javax.xml.bind + jaxb-api + 2.4.0-b180830.0359 + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + + + + org.apache.httpcomponents.core5 + httpcore5 + 5.2.4 + + + + + org.eclipse.persistence + org.eclipse.persistence.core + ${version.eclipse.persistence} + + + org.eclipse.persistence + org.eclipse.persistence.moxy + ${version.eclipse.persistence} + + + + xml-apis + xml-apis + + 1.4.01 + + + + benchmarkutils @@ -43,7 +96,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.6.0 + 3.7.0 org.apache.maven.plugins @@ -53,7 +106,7 @@ org.apache.maven.plugins maven-plugin-plugin - 3.10.2 + 3.11.0 org.apache.maven.plugins @@ -121,7 +174,7 @@ org.codehaus.mojo extra-enforcer-rules - 1.7.0 + 1.8.0 @@ -172,7 +225,7 @@ org.apache.maven.plugins maven-jxr-plugin - 3.3.1 + 3.3.2 @@ -205,7 +258,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.3 + 3.2.5 @@ -362,6 +415,9 @@ ${project.build.directory}/log 2.0.0-M8 + + 2.7.14 + 1.3 From 5f32584e6ba7dfa777c4e7431c099a11ded05d72 Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Tue, 2 Apr 2024 11:24:40 -0400 Subject: [PATCH 14/28] Add JSON output for Response to failedTestCases.txt log. --- library/pom.xml | 12 ++ .../CliArgExecutableTestCaseInput.java | 17 +++ .../CliFileExecutableTestCaseInput.java | 17 +++ .../entities/CliResponseInfo.java | 53 +++++---- .../benchmarkutils/entities/CliTestCase.java | 17 +++ .../entities/ContentFormatEnum.java | 17 +++ .../entities/ExecutableTestCaseInput.java | 17 +++ .../entities/FileCopyConfig.java | 17 +++ .../entities/HttpClientConfig.java | 17 +++ .../entities/HttpGetTestCaseInput.java | 17 +++ .../entities/HttpPostTestCaseInput.java | 17 +++ .../entities/HttpResponseInfo.java | 54 +++++---- .../benchmarkutils/entities/HttpTestCase.java | 17 +++ .../entities/HttpTestCaseInput.java | 36 +++++- .../entities/JerseyTestCaseInput.java | 17 +++ .../benchmarkutils/entities/ResponseInfo.java | 103 +++++++++++++++++- .../entities/ServletTestCaseInput.java | 17 +++ .../entities/SpringTestCaseInput.java | 17 +++ .../entities/Sqlite3Config.java | 17 +++ .../StdinExecutableTestCaseInput.java | 17 +++ .../TcpSocketExecutableTestCaseInput.java | 17 +++ .../benchmarkutils/entities/TestCase.java | 17 +++ .../entities/TestCaseInput.java | 17 +++ .../entities/TestCaseSetup.java | 17 +++ .../entities/TestCaseSetupException.java | 17 +++ .../tools/BenchmarkCrawler.java | 41 +++++-- .../tools/BenchmarkCrawlerVerification.java | 6 +- .../tools/RegressionTesting.java | 16 ++- pom.xml | 2 +- 29 files changed, 585 insertions(+), 78 deletions(-) diff --git a/library/pom.xml b/library/pom.xml index 887be99f..c009e240 100755 --- a/library/pom.xml +++ b/library/pom.xml @@ -24,6 +24,18 @@ guava + + + + org.eclipse.persistence + org.eclipse.persistence.moxy + 2.7.14 + + javax.validation validation-api diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java index 2cf550b1..56fdd84d 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.util.ArrayList; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java index 18732c25..9009aed8 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.util.List; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java index b3dfb808..4fc4cdcb 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java @@ -1,39 +1,38 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; -public class CliResponseInfo implements ResponseInfo { - private int seconds; - private String responseString; - private int returnCode; - private CliRequest request; - - @Override - public String getResponseString() { - return responseString; - } - - @Override - public void setResponseString(String responseString) { - this.responseString = responseString; - } +import javax.xml.bind.annotation.XmlElement; - @Override - public int getTimeInSeconds() { - return seconds; - } +public class CliResponseInfo extends ResponseInfo { - @Override - public void setTimeInSeconds(int seconds) { - this.seconds = seconds; - } + private CliRequest request; - public int getReturnCode() { - return returnCode; + public CliResponseInfo() { + // Default is this is a normal, non-attack response + super(); } - public void setReturnCode(int returnCode) { - this.returnCode = returnCode; + public CliResponseInfo(boolean attackRequest) { + super(attackRequest); } + @XmlElement(required = true) public CliRequest getRequest() { return request; } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java index 2dda4236..b4a099c7 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.XmlElement; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java index be00bb65..eb886075 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.*; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java index 18477b46..1a18ff6a 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.validation.constraints.NotNull; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java index 09182a32..2327b926 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import com.google.common.io.Files; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java index d562c917..e9c0cb58 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.io.IOException; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java index c1727750..cdc744a3 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import org.apache.hc.client5.http.classic.methods.HttpGet; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java index 99890885..75ca8724 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import org.apache.hc.client5.http.classic.methods.HttpPost; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java index fff335c1..a7f622b8 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java @@ -1,41 +1,39 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; +import javax.xml.bind.annotation.XmlElement; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; -public class HttpResponseInfo implements ResponseInfo { - private String responseString; - private int seconds; - private int statusCode; - private HttpUriRequest requestBase; - - @Override - public String getResponseString() { - return responseString; - } - - @Override - public void setResponseString(String responseString) { - this.responseString = responseString; - } +public class HttpResponseInfo extends ResponseInfo { - @Override - public int getTimeInSeconds() { - return seconds; - } - - @Override - public void setTimeInSeconds(int seconds) { - this.seconds = seconds; - } + private HttpUriRequest requestBase; - public int getStatusCode() { - return statusCode; + public HttpResponseInfo() { + // Default is this is a normal, non-attack response + super(); } - public void setStatusCode(int statusCode) { - this.statusCode = statusCode; + public HttpResponseInfo(boolean attackRequest) { + super(attackRequest); } + @XmlElement(required = true) public HttpUriRequest getRequestBase() { return requestBase; } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java index 7d8a0f6a..2ce98cd0 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; public class HttpTestCase extends TestCase { diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java index 851a2259..e52435df 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.io.IOException; @@ -217,10 +234,25 @@ String urlEncode(String input) { * response code. * * @param httpclient - The HTTP client to use to make the request - * @param request - THe HTTP request to issue + * @param request - The HTTP request to issue */ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { - HttpResponseInfo responseInfo = new HttpResponseInfo(); + // The default is this is a normal, non-attack request, so send false as isAttack value + return sendRequest(httpclient, request, false); + } + + /** + * Issue the requested request, measure the time required to execute, then output both to stdout + * and the global variable timeString the URL tested, the time required to execute and the + * response code. + * + * @param httpclient - The HTTP client to use to make the request + * @param request - The HTTP request to issue + * @param attackRequest - Is the request an attack, or not + */ + static ResponseInfo sendRequest( + CloseableHttpClient httpclient, HttpUriRequest request, boolean attackRequest) { + HttpResponseInfo responseInfo = new HttpResponseInfo(attackRequest); responseInfo.setRequestBase(request); CloseableHttpResponse response = null; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java index 3ecb0d7f..03df0e95 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import org.apache.hc.client5.http.classic.methods.HttpPost; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index 4bba4a76..d9a2e6c3 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -1,11 +1,104 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; -public interface ResponseInfo { - public String getResponseString(); +import java.io.StringWriter; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import org.eclipse.persistence.jaxb.MarshallerProperties; +import org.eclipse.persistence.oxm.MediaType; - public void setResponseString(String responseString); +@XmlRootElement(name = "ResponseInfo") +public abstract class ResponseInfo { - public int getTimeInSeconds(); + // True if response to an attack request. False if response to normal request + private boolean isAttackResponse = false; // Default + private String responseString; + private int statusCode; + private int seconds; - public void setTimeInSeconds(int seconds); + public ResponseInfo() { + // Default is this is a normal, non-attack response + } + + public ResponseInfo(boolean attackRequest) { + this.isAttackResponse = attackRequest; + } + + public void setIsAttackResponse(boolean isAttackResponse) { + this.isAttackResponse = isAttackResponse; + } + + @XmlAttribute(required = true) + public boolean getIsAttackResponse() { + return isAttackResponse; + } + + public void setResponseString(String responseString) { + this.responseString = responseString; + } + + @XmlAttribute(required = true) + public String getResponseString() { + return responseString; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public int getTimeInSeconds() { + return seconds; + } + + public void setTimeInSeconds(int seconds) { + this.seconds = seconds; + } + + public String toJSON() { + try { + JAXBContext jaxbContext = + org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( + new Class[] {ResponseInfo.class}, null); + + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + + // Set JSON type + jaxbMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); + jaxbMarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true); + + // To format JSON + jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + // Print JSON String to Console + StringWriter sw = new StringWriter(); + jaxbMarshaller.marshal(this, sw); + return sw.toString(); + } catch (JAXBException e) { + e.printStackTrace(); + return ""; + } + } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java index 5c125b23..9db50238 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.util.ArrayList; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java index 5991fbad..6bb6d215 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import org.apache.hc.client5.http.classic.methods.HttpPost; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java index c40f9d17..e8413bec 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.io.BufferedReader; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java index ac85b5ab..e7e21e7d 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.validation.constraints.NotNull; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java index b97a7ab6..afc537f6 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java index 3f6d465d..a880cc71 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.util.Comparator; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java index b35b4bd7..d8200166 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.XmlSeeAlso; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java index aaf9afaf..59aa0377 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.XmlSeeAlso; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java index e5671bc1..b99ae3f3 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; public class TestCaseSetupException extends Exception { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 91e6b249..ec0cd30e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -202,7 +202,7 @@ protected void crawl(TestSuite testSuite) throws Exception { HttpUriRequest attackRequest = httpTestCaseInput.buildAttackRequest(); // Send the next test case request with its attack payload - sendRequest(httpClient, attackRequest); + sendRequest(httpClient, attackRequest, true); } else if (testCase.getTestCaseInput() instanceof ExecutableTestCaseInput) { ExecutableTestCaseInput executableTestCaseInput = (ExecutableTestCaseInput) testCase.getTestCaseInput(); @@ -258,13 +258,28 @@ static CloseableHttpClient createAcceptSelfSignedCertificateClient() /** * Issue the requested request, measure the time required to execute, then output both to stdout * and the global variable timeString the URL tested, the time required to execute and the - * response code. + * response code. This is for normal, non-attack requests. * * @param httpclient - The HTTP client to use to make the request * @param request - THe HTTP request to issue */ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest request) { - HttpResponseInfo responseInfo = new HttpResponseInfo(); + return sendRequest(httpclient, request, false); + } + + /** + * Issue the requested request, measure the time required to execute, then output both to stdout + * and the global variable timeString the URL tested, the time required to execute and the + * response code. + * + * @param httpclient - The HTTP client to use to make the request + * @param request - THe HTTP request to issue + * @param attackRequest - true if this response info is associated with an attack request, false + * otherwise + */ + static ResponseInfo sendRequest( + CloseableHttpClient httpclient, HttpUriRequest request, boolean attackRequest) { + HttpResponseInfo responseInfo = new HttpResponseInfo(attackRequest); responseInfo.setRequestBase(request); CloseableHttpResponse response = null; @@ -312,12 +327,24 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r /** * Issue the requested request, measure the time required to execute, then output both to stdout * and the global variable timeString the URL tested, the time required to execute and the - * response code. + * response code. By default, this assumes a normal 'safe' request. * - * @param request - THe CLI request to issue + * @param request - The CLI request to issue */ static ResponseInfo execute(CliRequest request) { - CliResponseInfo responseInfo = new CliResponseInfo(); + return execute(request, false); + } + + /** + * Issue the requested request, measure the time required to execute, then output both to stdout + * and the global variable timeString the URL tested, the time required to execute and the + * response code. + * + * @param request - The CLI request to issue + * @param attackRequest - True if executing an attack, false otherwise + */ + static ResponseInfo execute(CliRequest request, boolean attackRequest) { + CliResponseInfo responseInfo = new CliResponseInfo(attackRequest); responseInfo.setRequest(request); // responseInfo.setRequestBase(request); @@ -358,7 +385,7 @@ static ResponseInfo execute(CliRequest request) { // attackPayloadResponseInfo = new ResponseInfo(); // System.out.printf("Program terminated with return code: %s%n", // exitValue); - responseInfo.setReturnCode(exitValue); + responseInfo.setStatusCode(exitValue); } } catch (IOException | InterruptedException e) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 039155bd..35e960ec 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -151,7 +151,7 @@ protected void crawl(TestSuite testSuite) throws Exception { safeExecutor = new HttpExecutor(safeRequest); // Send the next test case request with its attack payload - attackPayloadResponseInfo = sendRequest(httpClient, attackRequest); + attackPayloadResponseInfo = sendRequest(httpClient, attackRequest, true); responseInfoList.add(attackPayloadResponseInfo); // Log the response @@ -192,7 +192,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // Send the next test case request with its attack payload System.out.println("Executing attack request: " + attackRequest); - attackPayloadResponseInfo = execute(attackRequest); + attackPayloadResponseInfo = execute(attackRequest, true); //// executeArgs.add(payload); // ProcessBuilder builder = new // ProcessBuilder(executeArgs); @@ -391,7 +391,7 @@ private void log(ResponseInfo responseInfo) throws IOException { String responseString = String.format( "--> (%d : %d sec)%n", - cliResponseInfo.getReturnCode(), cliResponseInfo.getTimeInSeconds()); + cliResponseInfo.getStatusCode(), cliResponseInfo.getTimeInSeconds()); if (isTimingEnabled) { if (cliResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { tLogger.println(request.getCommand()); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index bbd3352c..886612ed 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -74,6 +74,7 @@ public class RegressionTesting { // TODO: Make this flag configurable via command line parameter private static boolean isVerbosityOn = false; private static final String FILENAME_FAILEDTC = "failedTestCases.txt"; + private static final String FILENAME_FAILEDTC_JSON = "failedTestCases.json"; /** The list of categories that will be included in the regression test. */ private static final List CATEGORIES_INCLUDED_IN_TEST = @@ -95,6 +96,7 @@ public static void genFailedTCFile(List results, St final File FILE_FAILEDTC = new File(dataDir, FILENAME_FAILEDTC); SimpleFileLogger.setFile("FAILEDTC", FILE_FAILEDTC); + final File FILE_FAILEDTC_JSON = new File(dataDir, FILENAME_FAILEDTC_JSON); try (SimpleFileLogger ftc = SimpleFileLogger.getLogger("FAILEDTC")) { @@ -171,8 +173,6 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - // AbstractTestCaseRequest requestTemplate = - // result.getRequestTemplate(); TestCase testCase = result.getTestCase(); if (isIncludedInTest(testCase)) { if (!result.isUnverifiable() && !result.isPassed()) { @@ -225,6 +225,7 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log out.println("Attack request:"); out.println(result.getAttackTestExecutor().getExecutorDescription()); out.println(); +// out.println("DRW Request JSON: " + result.getAttackTestExecutor().toJSON()); if (attackResponseInfo instanceof HttpResponseInfo) { out.printf( "Attack response: [%d]:%n", @@ -236,15 +237,18 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log } else if (attackResponseInfo instanceof CliResponseInfo) { out.printf( "Attack response: [%d]:%n", - ((CliResponseInfo) attackResponseInfo).getReturnCode()); + ((CliResponseInfo) attackResponseInfo).getStatusCode()); out.println( attackResponseInfo == null ? "null" : ((CliResponseInfo) attackResponseInfo).getResponseString()); } out.println(); + out.println("DRW: Response JSON: " + attackResponseInfo.toJSON()); + out.println(); out.println("Safe request:"); out.println(result.getSafeTestExecutor().getExecutorDescription()); +// out.println("DRW Request JSON: " + result.getSafeTestExecutor().toJSON()); out.println(); if (safeResponseInfo instanceof HttpResponseInfo) { out.printf( @@ -256,13 +260,15 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log : ((HttpResponseInfo) safeResponseInfo).getResponseString()); } else if (safeResponseInfo instanceof CliResponseInfo) { out.printf( - "Safe response: [%d]:%n", ((CliResponseInfo) safeResponseInfo).getReturnCode()); + "Safe response: [%d]:%n", ((CliResponseInfo) safeResponseInfo).getStatusCode()); out.println( safeResponseInfo == null ? "null" : ((CliResponseInfo) safeResponseInfo).getResponseString()); } out.println(); + out.println("DRW: Response JSON: " + safeResponseInfo.toJSON()); + out.println(); out.printf("Attack success indicator: -->%s<--%n", testCase.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); } @@ -462,7 +468,7 @@ private static List findErrors(ResponseInfo responseInfo, String prefix) reasons.add(prefix + " response code: " + statusCode); } } else if (responseInfo instanceof CliResponseInfo) { - int returnCode = ((CliResponseInfo) responseInfo).getReturnCode(); + int returnCode = ((CliResponseInfo) responseInfo).getStatusCode(); if (returnCode != 0) { reasons.add(prefix + " response code: " + returnCode); } diff --git a/pom.xml b/pom.xml index 8b8d3e53..91ba41da 100755 --- a/pom.xml +++ b/pom.xml @@ -410,7 +410,7 @@ ${project.java.target} ${project.java.target} UTF-8 - 1.8 + 11 UTF-8 ${project.build.directory}/log 2.0.0-M8 From 0dc9acd1d1cb6cb0d0643011e6b03558cdb48e66 Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Tue, 2 Apr 2024 12:36:58 -0400 Subject: [PATCH 15/28] Update Java version error messages. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 91ba41da..2da88d80 100755 --- a/pom.xml +++ b/pom.xml @@ -187,7 +187,7 @@ ${project.java.target} - Dependencies shouldn't require Java 9+. + Dependencies shouldn't require Java 12+. warn @@ -202,7 +202,7 @@ ${project.java.target} - BenchmarkUtls is currently written to support Java 8. + BenchmarkUtls is currently written to support Java 11. From cb73d5d7e5f62bb4586690593fda490199030f53 Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Tue, 2 Apr 2024 12:54:58 -0400 Subject: [PATCH 16/28] Upgrade to Java 11 in maven CI. --- .github/workflows/maven.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index fb080981..036d130c 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -11,10 +11,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '11' distribution: 'adopt' cache: maven - name: Build and Install shared library From be7d1f5fc4b7e475ad4650b8699fbcb9e4c3e344 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 15 Apr 2024 19:29:53 -0400 Subject: [PATCH 17/28] Start adding support for verifying code fixes by comparing pre- and post-fix test results. --- .../entities/TestCaseResult.java | 32 ++++ .../benchmarkutils/helpers/Category.java | 5 + .../tools/BenchmarkCrawlerVerification.java | 158 ++++++++++++++++-- .../benchmarkutils/tools/VerifyFixOutput.java | 25 +++ 4 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java new file mode 100644 index 00000000..5d4195c3 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java @@ -0,0 +1,32 @@ +package org.owasp.benchmarkutils.entities; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "testcaseresult") +public class TestCaseResult { + + private String output; + + private boolean isPassed; + + public TestCaseResult(boolean isPassed, String output) { + this.isPassed = isPassed; + this.output = output; + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public boolean isPassed() { + return isPassed; + } + + public void setPassed(boolean isPassed) { + this.isPassed = isPassed; + } +} diff --git a/library/src/main/java/org/owasp/benchmarkutils/helpers/Category.java b/library/src/main/java/org/owasp/benchmarkutils/helpers/Category.java index c7b43204..21c5aa41 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/helpers/Category.java +++ b/library/src/main/java/org/owasp/benchmarkutils/helpers/Category.java @@ -29,6 +29,11 @@ public class Category implements Comparable { private final boolean isInjection; private final String shortName; // PATH + /** Unused but necessary to make JAXB happy. */ + public Category() { + this(null, null, -1, false, null); + } + /** * Create a vuln category. * diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 039155bd..a885b855 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -20,10 +20,14 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.StringWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -37,6 +41,8 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.eclipse.persistence.jaxb.MarshallerProperties; +import org.eclipse.persistence.oxm.MediaType; import org.owasp.benchmarkutils.entities.CliRequest; import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; @@ -60,13 +66,17 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static int maxTimeInSeconds = 2; private static boolean isTimingEnabled = false; + private boolean verifyFixed = false; + private String configurationDirectory = Utils.DATA_DIR; + private String outputDirectory = Utils.DATA_DIR; + private String beforeFixOutputDirectory = Utils.DATA_DIR; private static final String FILENAME_TIMES_ALL = "crawlerTimes.txt"; private static final String FILENAME_TIMES = "crawlerSlowTimes.txt"; private static final String FILENAME_NON_DISCRIMINATORY_LOG = "nonDiscriminatoryTestCases.txt"; private static final String FILENAME_ERRORS_LOG = "errorTestCases.txt"; private static final String FILENAME_UNVERIFIABLE_LOG = "unverifiableTestCases.txt"; - // The following is reconfigurable via parameters to main() - private static String CRAWLER_DATA_DIR = Utils.DATA_DIR; // default data dir + // The following is reconfigurable via parameters to main() + // private String CRAWLER_DATA_DIR = Utils.DATA_DIR; // default data dir SimpleFileLogger tLogger; SimpleFileLogger ndLogger; @@ -92,16 +102,16 @@ protected void crawl(TestSuite testSuite) throws Exception { new ArrayList(); final File FILE_NON_DISCRIMINATORY_LOG = - new File(CRAWLER_DATA_DIR, FILENAME_NON_DISCRIMINATORY_LOG); - final File FILE_ERRORS_LOG = new File(CRAWLER_DATA_DIR, FILENAME_ERRORS_LOG); + new File(getOutputDirectory(), FILENAME_NON_DISCRIMINATORY_LOG); + final File FILE_ERRORS_LOG = new File(getOutputDirectory(), FILENAME_ERRORS_LOG); final File FILE_TIMES_LOG; if (isTimingEnabled) { - FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES); + FILE_TIMES_LOG = new File(getOutputDirectory(), FILENAME_TIMES); } else { - FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES_ALL); + FILE_TIMES_LOG = new File(getOutputDirectory(), FILENAME_TIMES_ALL); } final File FILE_UNVERIFIABLE_LOG = - new File(CRAWLER_DATA_DIR, FILENAME_UNVERIFIABLE_LOG); + new File(getOutputDirectory(), FILENAME_UNVERIFIABLE_LOG); SimpleFileLogger.setFile("TIMES", FILE_TIMES_LOG); SimpleFileLogger.setFile("NONDISCRIMINATORY", FILE_NON_DISCRIMINATORY_LOG); SimpleFileLogger.setFile("ERRORS", FILE_ERRORS_LOG); @@ -246,6 +256,10 @@ protected void crawl(TestSuite testSuite) throws Exception { } } + // DEBUG + String output = objectToJson(testSuite); + System.out.println(output); + // Log the elapsed time for all test cases long stop = System.currentTimeMillis(); int seconds = (int) (stop - start) / 1000; @@ -260,7 +274,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // Report the verified results if (RegressionTesting.isTestingEnabled) { - RegressionTesting.genFailedTCFile(results, CRAWLER_DATA_DIR); + RegressionTesting.genFailedTCFile(results, getOutputDirectory()); if (!RegressionTesting.failedTruePositivesList.isEmpty() || !RegressionTesting.failedFalsePositivesList.isEmpty()) { @@ -334,6 +348,23 @@ protected void crawl(TestSuite testSuite) throws Exception { // cleanupSetups(setups); } + /** + * @param testSuite + * @throws Exception + */ + protected void crawlToVerifyFix(TestSuite testSuite, String beforeFixOutputDirectory) + throws Exception { + + // Get config directory for RUN 1 from CLI option + // Get output directory for RUN 1 + // Get test case directory of RUN 2 from CLI option + // Get output directory for RUN 2 + // Create a copy of TestSuite from the RUN 1 config + // Modify TestSuite object so that every TestCase has isVulnerability = false + // Call crawl(TestSuite) on the new TestSuite + // Compare output of RUN 1 to output of RUN 2 and report result + } + private List getTestCaseSetups(TestSuite testSuite) { List testCaseSetups = new ArrayList(); for (TestCase testCase : testSuite.getTestCases()) { @@ -359,6 +390,14 @@ private void cleanupSetups(List testCaseSetups) throws TestCaseSe } } + protected String getConfigurationDirectory() { + return configurationDirectory; + } + + protected String getOutputDirectory() { + return outputDirectory; + } + private void log(ResponseInfo responseInfo) throws IOException { if (responseInfo instanceof HttpResponseInfo) { HttpResponseInfo httpResponseInfo = (HttpResponseInfo) responseInfo; @@ -414,14 +453,92 @@ private void log(ResponseInfo responseInfo) throws IOException { * @throws FileNotFoundException * @throws LoggerConfigurationException */ - protected static void handleResponse(TestCaseVerificationResults result) + protected void handleResponse(TestCaseVerificationResults results) throws FileNotFoundException, LoggerConfigurationException { // Check to see if this specific test case has a specified expected response value. // If so, run it through verification using it's specific attackSuccessIndicator. // Note that a specific success indicator overrides any generic category tests, if // specified. - RegressionTesting.verifyTestCase(result); + RegressionTesting.verifyTestCase(results); + + if (verifyFixed) { + TestCaseVerificationResults beforeFixResults = + loadTestCaseVerificationResults(beforeFixOutputDirectory); + verifyFix(beforeFixResults, results); + } + } + + private TestCaseVerificationResults loadTestCaseVerificationResults(String directory) { + + // FIXME: Replace this with code to load results from JSON data files + TestExecutor attackTestExecutor = new HttpExecutor(null); + TestExecutor safeTestExecutor = new HttpExecutor(null); + TestCase testCase = new TestCase(); + ResponseInfo responseToAttackValue = new HttpResponseInfo(); // FIXME: Or CliResponseInfo + responseToAttackValue.setResponseString(""); + ResponseInfo responseToSafeValue = new HttpResponseInfo(); + responseToSafeValue.setResponseString(""); + TestCaseVerificationResults results = + new TestCaseVerificationResults( + attackTestExecutor, + safeTestExecutor, + testCase, + responseToAttackValue, + responseToSafeValue); + + return results; + } + + private boolean verifyFix( + TestCaseVerificationResults beforeFixResults, + TestCaseVerificationResults afterFixResults) { + + boolean wasExploited = + afterFixResults.getTestCase().isVulnerability() + && !afterFixResults.getTestCase().isUnverifiable() + && afterFixResults.isPassed(); + boolean wasBroken = + !beforeFixResults + .getResponseToSafeValue() + .getResponseString() + .equals(afterFixResults.getResponseToAttackValue().getResponseString()); + if (wasExploited) { + System.out.println("NOT FIXED: Vulnerability was exploited"); + } + if (wasBroken) { + System.out.println("NOT FIXED: Functionality was broken"); + } + + try { + VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); + verifyFixOutput.setWasExploited(wasExploited); + verifyFixOutput.setWasBroken(wasBroken); + String output = objectToJson(verifyFixOutput); + System.out.println(output); + } catch (JAXBException e) { + System.out.println("ERROR: Could not marshall VerifyFixOutput to JSON"); + e.printStackTrace(); + } + + return !wasExploited && !wasBroken; + } + + String objectToJson(Object object) throws JAXBException { + // final Class[] marshallableClasses = new Class[] {VerifyFixOutput.class}; + final Class[] marshallableClasses = new Class[] {TestSuite.class, VerifyFixOutput.class}; + JAXBContext jaxbContext = + org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( + marshallableClasses, null); + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + + jaxbMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); + jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + StringWriter writer = new StringWriter(); + jaxbMarshaller.marshal(object, writer); + + return writer.toString(); } /** @@ -445,6 +562,18 @@ protected void processCommandLineArgs(String[] args) { // Create the Options Options options = new Options(); + options.addOption( + Option.builder("b") + .longOpt("beforeFixOutputDirectory") + .desc("output directory of a previous crawl before fixes") + .hasArg() + .build()); + options.addOption( + Option.builder("d") + .longOpt("outputDirectory") + .desc("output directory") + .hasArg() + .build()); options.addOption( Option.builder("f") .longOpt("file") @@ -453,6 +582,7 @@ protected void processCommandLineArgs(String[] args) { .required() .build()); options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption("m", "", false, "verify fixed test suite"); options.addOption( Option.builder("n") .longOpt("name") @@ -471,13 +601,19 @@ protected void processCommandLineArgs(String[] args) { // Parse the command line arguments CommandLine line = parser.parse(options, args); + if (line.hasOption("b")) { + beforeFixOutputDirectory = line.getOptionValue("b"); + } + if (line.hasOption("d")) { + outputDirectory = line.getOptionValue("d"); + } if (line.hasOption("f")) { this.crawlerFile = line.getOptionValue("f"); File targetFile = new File(this.crawlerFile); if (targetFile.exists()) { setCrawlerFile(targetFile); // Crawler output files go into the same directory as the crawler config file - CRAWLER_DATA_DIR = targetFile.getParent() + File.separator; + configurationDirectory = targetFile.getParent(); } else { throw new RuntimeException( "Could not find crawler configuration file '" + this.crawlerFile + "'"); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java new file mode 100644 index 00000000..207fbeed --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java @@ -0,0 +1,25 @@ +package org.owasp.benchmarkutils.tools; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +class VerifyFixOutput { + private boolean wasExploited; + private boolean wasBroken; + + public boolean isWasExploited() { + return wasExploited; + } + + public void setWasExploited(boolean wasExploited) { + this.wasExploited = wasExploited; + } + + public boolean isWasBroken() { + return wasBroken; + } + + public void setWasBroken(boolean wasBroken) { + this.wasBroken = wasBroken; + } +} From 4704ae919b5e844cf4252f7a0b56e737bc6b1b30 Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Thu, 18 Apr 2024 15:32:19 -0500 Subject: [PATCH 18/28] Fix things so all the necessary verification results objects can be output to JSON and by default produce a single JSON file with the verification results for all the test cases. --- .../benchmarkutils/entities/CliRequest.java | 17 ++ .../entities/RequestVariable.java | 17 ++ .../benchmarkutils/entities/ResponseInfo.java | 31 --- .../benchmarkutils/entities/TestCase.java | 194 ++++++++---------- .../entities/TestCaseResult.java | 17 ++ .../owasp/benchmarkutils/helpers/Utils.java | 30 +++ .../tools/BenchmarkCrawlerVerification.java | 34 +-- .../benchmarkutils/tools/CliExecutor.java | 19 ++ .../tools/CodeBlockSupportResults.java | 17 ++ .../benchmarkutils/tools/HttpExecutor.java | 22 +- .../owasp/benchmarkutils/tools/Logger.java | 17 ++ .../tools/LoggerConfigurationException.java | 17 ++ .../tools/RegressionTesting.java | 76 ++++--- .../tools/SimpleFileLogger.java | 17 ++ .../tools/TestCaseVerificationResults.java | 41 +++- .../benchmarkutils/tools/TestExecutor.java | 26 ++- .../benchmarkutils/tools/VerifyFixOutput.java | 19 +- 17 files changed, 413 insertions(+), 198 deletions(-) diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java index 8b7f0b68..92fa3745 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import java.util.ArrayList; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java index 04170116..ee1526b8 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.validation.constraints.NotNull; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index d9a2e6c3..e9e3fcfb 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -17,14 +17,8 @@ */ package org.owasp.benchmarkutils.entities; -import java.io.StringWriter; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; -import org.eclipse.persistence.jaxb.MarshallerProperties; -import org.eclipse.persistence.oxm.MediaType; @XmlRootElement(name = "ResponseInfo") public abstract class ResponseInfo { @@ -76,29 +70,4 @@ public int getTimeInSeconds() { public void setTimeInSeconds(int seconds) { this.seconds = seconds; } - - public String toJSON() { - try { - JAXBContext jaxbContext = - org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( - new Class[] {ResponseInfo.class}, null); - - Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); - - // Set JSON type - jaxbMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); - jaxbMarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true); - - // To format JSON - jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - // Print JSON String to Console - StringWriter sw = new StringWriter(); - jaxbMarshaller.marshal(this, sw); - return sw.toString(); - } catch (JAXBException e) { - e.printStackTrace(); - return ""; - } - } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java index a880cc71..0ab03316 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java @@ -24,41 +24,36 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElements; +import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.eclipse.persistence.oxm.annotations.XmlPath; import org.eclipse.persistence.oxm.annotations.XmlPaths; import org.owasp.benchmarkutils.helpers.Category; import org.owasp.benchmarkutils.helpers.CategoryAdapter; +@XmlRootElement(name = "TestCase") public class TestCase { private Category category; + private String name; // Full name of the test case + private int number; // The number of this test case - private String dataflowFile; - - private String name; - - private int number; - - private String notAutoverifiableReason; - - private String sinkFile; + private boolean isVulnerability; private String sourceFile; - private String sourceUIType; + private String dataflowFile = "none"; + private String sinkFile; private String templateFile; - - private String type; - private String UITemplateFile; - private boolean isVulnerability; + private String notAutoverifiableReason; // Any value, e.g. "none" sets it notAutoverifiable - private String attackSuccessString; + private String verificationResult = "notYetKnown"; private TestCaseInput testCaseInput; + private String attackSuccessString; @XmlElements({ @XmlElement(type = HttpClientConfig.class), @@ -75,8 +70,8 @@ public class TestCase { // FIXME: These fields are not in the crawler config file, but they need to be captured when // running the verification crawler because we retrieve request details from them to write to // failedTestCases.txt. - // private TestCaseRequest attackTestCaseRequest; - // private TestCaseRequest safeTestCaseRequest; + // private TestExecutor safeTestExecutor; + // private TestExecutor attackTestExecutor; static final Pattern lastIntPattern = Pattern.compile("[^0-9]+([0-9]+)$"); @@ -84,7 +79,11 @@ public TestCase() { super(); } - // @XmlAttribute(name = "tcCategory", required = true) + @XmlAttribute(name = "AttackSuccessIndicator") + public String getAttackSuccessString() { + return this.attackSuccessString; + } + @XmlAttribute(name = "Category", required = true) @XmlJavaTypeAdapter(CategoryAdapter.class) @NotNull @@ -92,153 +91,75 @@ public Category getCategory() { return category; } - public void setCategory(Category category) { - this.category = category; - } - - public void setDataflowFile(String dataflowFile) { - this.dataflowFile = dataflowFile; - } - - public void setNotAutoverifiableReason(String notAutoverifiableReason) { - this.notAutoverifiableReason = notAutoverifiableReason; - } - - public void setSinkFile(String sinkFile) { - this.sinkFile = sinkFile; - } - - public void setSourceFile(String sourceFile) { - this.sourceFile = sourceFile; - } - - public void setSourceUIType(String sourceUIType) { - this.sourceUIType = sourceUIType; - } - - public void setTemplateFile(String templateFile) { - this.templateFile = templateFile; - } - - public void setUITemplateFile(String uITemplateFile) { - UITemplateFile = uITemplateFile; - } - - public void setVulnerability(boolean isVulnerability) { - this.isVulnerability = isVulnerability; - } - - public void setAttackSuccessString(String attackSuccessString) { - this.attackSuccessString = attackSuccessString; - } - - // @XmlAttribute(name = "tcDataflowFile", required = true) @XmlAttribute(name = "DataflowFile", required = true) @NotNull public String getDataflowFile() { return dataflowFile; } - // @XmlAttribute(name = "tcName", required = true) @XmlAttribute(name = "Name", required = true) @NotNull public String getName() { return name; } - public void setName(String name) { - this.name = name; - - // Auto extract the test case number from the name. - Matcher matcher = lastIntPattern.matcher(name); - if (matcher.find()) { - String someNumberStr = matcher.group(1); - this.number = Integer.parseInt(someNumberStr); - } else { - System.out.println( - "Warning: TestCaseRequest.setName() invoked with test case name: " - + name - + " that doesn't end with a test case number."); - } - } - public int getNumber() { return number; } - // @XmlAttribute(name = "tcNotAutoverifiable") @XmlAttribute(name = "NotAutoverifiable") public String getNotAutoverifiableReason() { return notAutoverifiableReason; } - // @XmlAttribute(name = "tcSinkFile", required = true) @XmlAttribute(name = "SinkFile", required = true) @NotNull public String getSinkFile() { return sinkFile; } - // @XmlAttribute(name = "tcSourceFile", required = true) @XmlAttribute(name = "SourceFile", required = true) @NotNull public String getSourceFile() { return sourceFile; } - // @XmlAttribute(name = "tcSourceUIType", required = true) @XmlAttribute(name = "SourceUIType", required = true) @NotNull public String getSourceUIType() { return sourceUIType; } - // @XmlAttribute(name = "tcTemplateFile", required = true) @XmlAttribute(name = "TemplateFile", required = true) @NotNull public String getTemplateFile() { return templateFile; } - // @XmlAttribute(name = "tcUITemplateFile", required = true) @XmlAttribute(name = "UITemplateFile", required = true) @NotNull public String getUITemplateFile() { return UITemplateFile; } - // @XmlAttribute(name = "tcType", required = true) - // @XmlReadOnly - // @NotNull - // public String getType() { - // return type; - // } - - // @XmlAttribute(name = "tcAttackSuccess") - @XmlAttribute(name = "AttackSuccess") - public String getAttackSuccessString() { - return this.attackSuccessString; - } - - // @XmlAttribute(name = "tcVulnerable", required = true) - @XmlAttribute(name = "Vulnerability", required = true) - public boolean isVulnerability() { - return isVulnerability; - } - @XmlElement(name = "Input", required = true) public TestCaseInput getTestCaseInput() { return testCaseInput; } - // public getTestCaseExecutor() { - // - // } + public TestCaseSetup getTestCaseSetup() { + return testCaseSetup; + } public boolean isUnverifiable() { return getNotAutoverifiableReason() != null; } + @XmlAttribute(name = "Vulnerability", required = true) + public boolean isVulnerability() { + return isVulnerability; + } + // public TestCaseRequest getAttackTestCaseRequest() { // return attackTestCaseRequest; // } @@ -255,12 +176,68 @@ public boolean isUnverifiable() { // this.safeTestCaseRequest = safeTestCaseRequest; // } + public void setAttackSuccessString(String attackSuccessString) { + this.attackSuccessString = attackSuccessString; + } + + public void setCategory(Category category) { + this.category = category; + } + + public void setDataflowFile(String dataflowFile) { + this.dataflowFile = dataflowFile; + } + + public void setName(String name) { + this.name = name; + + // Auto extract the test case number from the name. + Matcher matcher = lastIntPattern.matcher(name); + if (matcher.find()) { + String someNumberStr = matcher.group(1); + this.number = Integer.parseInt(someNumberStr); + } else { + System.out.println( + "Warning: TestCaseRequest.setName() invoked with test case name: " + + name + + " that doesn't end with a test case number."); + } + } + + public void setNotAutoverifiableReason(String notAutoverifiableReason) { + this.notAutoverifiableReason = notAutoverifiableReason; + } + + public void setSinkFile(String sinkFile) { + this.sinkFile = sinkFile; + } + + public void setSourceFile(String sourceFile) { + this.sourceFile = sourceFile; + } + + public void setSourceUIType(String sourceUIType) { + this.sourceUIType = sourceUIType; + } + + public void setTemplateFile(String templateFile) { + this.templateFile = templateFile; + } + public void setTestCaseInput(TestCaseInput testCaseInput) { this.testCaseInput = testCaseInput; } - public TestCaseSetup getTestCaseSetup() { - return testCaseSetup; + public void setUITemplateFile(String uITemplateFile) { + UITemplateFile = uITemplateFile; + } + + public void setVulnerability(boolean isVulnerability) { + this.isVulnerability = isVulnerability; + } + + public void setVerificationResult(String result) { + this.verificationResult = result; } public void setTestCaseSetup(TestCaseSetup testCaseSetup) { @@ -314,6 +291,11 @@ public void setTestCaseSetup(TestCaseSetup testCaseSetup) { // } // } + @XmlElement(name = "VerificationResult", required = true) + public String getVerificationResult() { + return this.verificationResult; + } + @Override public String toString() { return "TestCase [" diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java index 5d4195c3..0f0bffaf 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.XmlRootElement; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 53e02df2..add07cd8 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringWriter; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -46,6 +47,7 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -54,8 +56,13 @@ import javax.xml.transform.sax.SAXSource; import org.apache.commons.io.FileUtils; import org.eclipse.persistence.jaxb.JAXBContextFactory; +import org.eclipse.persistence.jaxb.MarshallerProperties; +import org.eclipse.persistence.oxm.MediaType; +import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.tools.TestCaseRequestFileParseException; +import org.owasp.benchmarkutils.tools.TestCaseVerificationResults; +import org.owasp.benchmarkutils.tools.VerifyFixOutput; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -401,4 +408,27 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) e.printStackTrace(); } } + + public static String objectToJson(Object object) throws JAXBException { + final Class[] marshallableClasses = + new Class[] { + ResponseInfo.class, + TestSuite.class, + TestCaseVerificationResults.class, + VerifyFixOutput.class + }; + JAXBContext jaxbContext = + org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( + marshallableClasses, null); + Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + + jaxbMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); + jaxbMarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, Boolean.TRUE); + jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + + StringWriter writer = new StringWriter(); + jaxbMarshaller.marshal(object, writer); + + return writer.toString(); + } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 758a95a5..1c76ce61 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -20,14 +20,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.StringWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -41,8 +38,6 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; -import org.eclipse.persistence.jaxb.MarshallerProperties; -import org.eclipse.persistence.oxm.MediaType; import org.owasp.benchmarkutils.entities.CliRequest; import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; @@ -256,10 +251,6 @@ protected void crawl(TestSuite testSuite) throws Exception { } } - // DEBUG - String output = objectToJson(testSuite); - System.out.println(output); - // Log the elapsed time for all test cases long stop = System.currentTimeMillis(); int seconds = (int) (stop - start) / 1000; @@ -274,6 +265,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // Report the verified results if (RegressionTesting.isTestingEnabled) { + // Generate all the standard text file verification results to different files RegressionTesting.genFailedTCFile(results, getOutputDirectory()); if (!RegressionTesting.failedTruePositivesList.isEmpty() @@ -318,6 +310,11 @@ protected void crawl(TestSuite testSuite) throws Exception { RegressionTesting.failedFalsePositivesList.get(request)); } } + + // Then generate JSON file with ALL verification results. This has to go at end + // because previous methods have some side affects that file in test case + // verification values. + RegressionTesting.genAllTCResultsToJsonFile(results, getOutputDirectory()); } } @@ -514,7 +511,7 @@ private boolean verifyFix( VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); verifyFixOutput.setWasExploited(wasExploited); verifyFixOutput.setWasBroken(wasBroken); - String output = objectToJson(verifyFixOutput); + String output = Utils.objectToJson(verifyFixOutput); System.out.println(output); } catch (JAXBException e) { System.out.println("ERROR: Could not marshall VerifyFixOutput to JSON"); @@ -524,23 +521,6 @@ private boolean verifyFix( return !wasExploited && !wasBroken; } - String objectToJson(Object object) throws JAXBException { - // final Class[] marshallableClasses = new Class[] {VerifyFixOutput.class}; - final Class[] marshallableClasses = new Class[] {TestSuite.class, VerifyFixOutput.class}; - JAXBContext jaxbContext = - org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( - marshallableClasses, null); - Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); - - jaxbMarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); - jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - - StringWriter writer = new StringWriter(); - jaxbMarshaller.marshal(object, writer); - - return writer.toString(); - } - /** * Process the command line arguments that make any configuration changes. * diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java index 8c24001b..11528398 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java @@ -1,10 +1,29 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.tools; import java.util.ArrayList; import java.util.List; +import javax.xml.bind.annotation.XmlRootElement; import org.owasp.benchmarkutils.entities.CliRequest; import org.owasp.benchmarkutils.entities.RequestVariable; +@XmlRootElement(name = "CliRequest") public class CliExecutor implements TestExecutor { CliRequest cliRequest; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CodeBlockSupportResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CodeBlockSupportResults.java index d4672f8d..8b70a7f7 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CodeBlockSupportResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CodeBlockSupportResults.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author Dave Wichers + * @created 2023 + */ package org.owasp.benchmarkutils.tools; /* diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java index f18dd354..b06bddd9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java @@ -1,15 +1,34 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.tools; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.charset.StandardCharsets; +import javax.xml.bind.annotation.XmlRootElement; import org.apache.commons.io.IOUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; +@XmlRootElement(name = "HttpRequest") public class HttpExecutor implements TestExecutor { HttpUriRequest httpRequest; @@ -36,11 +55,10 @@ public String getExecutorDescription() { } if (httpRequest instanceof HttpPost) { HttpPost postHttpRequest = (HttpPost) httpRequest; - out.println(); try { HttpEntity entity = postHttpRequest.getEntity(); if (entity != null) { - out.println(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); + out.print(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8)); } } catch (IOException e) { System.out.println("ERROR: Could not parse HttpPost entities"); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/Logger.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/Logger.java index b063ec8a..600c57e4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/Logger.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/Logger.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author Juan Gama + * @created 2017 + */ package org.owasp.benchmarkutils.tools; public interface Logger { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/LoggerConfigurationException.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/LoggerConfigurationException.java index c9213b02..45e05709 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/LoggerConfigurationException.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/LoggerConfigurationException.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author Juan Gama + * @created 2017 + */ package org.owasp.benchmarkutils.tools; public class LoggerConfigurationException extends Exception { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 886612ed..57f0f38f 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -29,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.xml.bind.JAXBException; import org.apache.commons.io.IOUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.core5.http.Header; @@ -39,6 +40,7 @@ import org.owasp.benchmarkutils.entities.HttpTestCaseInput; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestCase; +import org.owasp.benchmarkutils.helpers.Utils; /** * Test all supported test cases to verify that the results are as expected and write the report to @@ -74,14 +76,42 @@ public class RegressionTesting { // TODO: Make this flag configurable via command line parameter private static boolean isVerbosityOn = false; private static final String FILENAME_FAILEDTC = "failedTestCases.txt"; - private static final String FILENAME_FAILEDTC_JSON = "failedTestCases.json"; + private static final String FILENAME_TC_VERIF_RESULTS_JSON = "testCaseVerificationResults.json"; /** The list of categories that will be included in the regression test. */ private static final List CATEGORIES_INCLUDED_IN_TEST = Arrays.asList(new String[] {"xss", "xxe"}); - // TODO: Since this is static, we might only be able to use (close) it once. + // TODO: Since these logs are static, we might only be able to use (close) it once. + static SimpleFileLogger tcJsonLogger; static SimpleFileLogger ftcLogger; + /** + * Write a log file containing the verification results of all test cases, as JSON. + * + * @param results + * @param dataDir + * @throws IOException + * @throws LoggerConfigurationException + */ + public static void genAllTCResultsToJsonFile( + List results, String dataDir) + throws IOException, LoggerConfigurationException { + + final File FILE_TC_VERIF_RESULTS_JSON = new File(dataDir, FILENAME_TC_VERIF_RESULTS_JSON); + SimpleFileLogger.setFile("TC_VERIF_RESULTS_JSON", FILE_TC_VERIF_RESULTS_JSON); + + try (SimpleFileLogger tcJSON = SimpleFileLogger.getLogger("TC_VERIF_RESULTS_JSON")) { + + tcJsonLogger = tcJSON; + + // Create JSON version of verification results for ALL test cases + tcJsonLogger.println(Utils.objectToJson(results)); + } catch (JAXBException e) { + System.out.println("Fatal Error trying to convert verification results to JSON"); + e.printStackTrace(); + System.exit(-1); + } + } /** * Write a log file containing the details of all failed test cases. @@ -96,7 +126,6 @@ public static void genFailedTCFile(List results, St final File FILE_FAILEDTC = new File(dataDir, FILENAME_FAILEDTC); SimpleFileLogger.setFile("FAILEDTC", FILE_FAILEDTC); - final File FILE_FAILEDTC_JSON = new File(dataDir, FILENAME_FAILEDTC_JSON); try (SimpleFileLogger ftc = SimpleFileLogger.getLogger("FAILEDTC")) { @@ -142,8 +171,6 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - // AbstractTestCaseRequest requestTemplate = - // result.getRequestTemplate(); TestCase testCase = result.getTestCase(); if (isIncludedInTest(testCase)) { if (isVerbosityOn) { @@ -160,12 +187,17 @@ public static void genFailedTCFile(List results, St System.out.println(httpTestCaseInput.getUrl()); } - if (!result.isUnverifiable() && !result.isPassed()) { - System.out.printf( - "FAILURE: %s positive %s test case request %s%n", - testCase.isVulnerability() ? "True" : "False", - testCase.getCategory().toString(), - testCase.getName()); + if (!result.isUnverifiable()) { + if (result.isPassed()) { + testCase.setVerificationResult("VERIFIED"); + } else { + testCase.setVerificationResult("FAILURE"); + System.out.printf( + "FAILURE: %s positive %s test case request %s%n", + testCase.isVulnerability() ? "True" : "False", + testCase.getCategory().toString(), + testCase.getName()); + } } } } @@ -177,7 +209,7 @@ public static void genFailedTCFile(List results, St if (isIncludedInTest(testCase)) { if (!result.isUnverifiable() && !result.isPassed()) { ftcLogger.print("FAILURE: "); - printTestCaseDetails(result, ftcLogger); + printTestCaseDetailsAsText(result, ftcLogger); } } } @@ -209,8 +241,8 @@ private static void printHttpRequest(HttpMessage request, Logger out) { } } - private static void printTestCaseDetails(TestCaseVerificationResults result, Logger out) { - // AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); + private static void printTestCaseDetailsAsText(TestCaseVerificationResults result, Logger out) { + TestCase testCase = result.getTestCase(); ResponseInfo attackResponseInfo = result.getResponseToAttackValue(); ResponseInfo safeResponseInfo = result.getResponseToSafeValue(); @@ -219,13 +251,11 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log testCase.isVulnerability() ? "True" : "False", testCase.getCategory().toString(), testCase.getName()); - // Print out all the attributes of the request, including the templates used to create it + // Print out all attributes of the request, including the templates used to create it out.println(testCase.toString()); out.println(); out.println("Attack request:"); out.println(result.getAttackTestExecutor().getExecutorDescription()); - out.println(); -// out.println("DRW Request JSON: " + result.getAttackTestExecutor().toJSON()); if (attackResponseInfo instanceof HttpResponseInfo) { out.printf( "Attack response: [%d]:%n", @@ -244,12 +274,8 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log : ((CliResponseInfo) attackResponseInfo).getResponseString()); } out.println(); - out.println("DRW: Response JSON: " + attackResponseInfo.toJSON()); - out.println(); out.println("Safe request:"); out.println(result.getSafeTestExecutor().getExecutorDescription()); -// out.println("DRW Request JSON: " + result.getSafeTestExecutor().toJSON()); - out.println(); if (safeResponseInfo instanceof HttpResponseInfo) { out.printf( "Safe response: [%d]:%n", @@ -267,8 +293,6 @@ private static void printTestCaseDetails(TestCaseVerificationResults result, Log : ((CliResponseInfo) safeResponseInfo).getResponseString()); } out.println(); - out.println("DRW: Response JSON: " + safeResponseInfo.toJSON()); - out.println(); out.printf("Attack success indicator: -->%s<--%n", testCase.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); } @@ -373,7 +397,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) result.setUnverifiable(true); result.setDeclaredUnverifiable(false); uLogger.print("UNVERIFIABLE: "); - printTestCaseDetails(result, uLogger); + printTestCaseDetailsAsText(result, uLogger); } List reasons = new ArrayList<>(); @@ -403,7 +427,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) "Non-discriminatory true positive test %s: The attack-success-string: \"%s\" was found in the response to both the safe and attack requests.%n" + "\tTo verify that a test case is a true positive, the attack-success-string should be in the attack response, and not%n\tthe safe response. Please change the attack-success-string and/or the test case sink itself to ensure that the%n\tattack-success-string response is present only in a response to a successful attack.%n", testCase.getName(), testCase.getAttackSuccessString()); - printTestCaseDetails(result, ndLogger); + printTestCaseDetailsAsText(result, ndLogger); nonDiscriminatorySinks.add(sink); } } else { @@ -422,7 +446,7 @@ public static void verifyTestCase(TestCaseVerificationResults result) "Non-discriminatory false positive test %s: The attack-success-string: \"%s\" was found in the response to the safe request.%n" + "\tTo verify that a test case is a false positive, the attack-success-string should not be in any response to this test%n\tcase. Please change the attack-success-string and/or the test case sink itself to ensure that the%n\tattack-success-string response is present only in a response to a successful attack.%n", testCase.getName(), testCase.getAttackSuccessString()); - printTestCaseDetails(result, ndLogger); + printTestCaseDetailsAsText(result, ndLogger); nonDiscriminatorySinks.add(sink); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java index 64d4ab86..8447fdf3 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/SimpleFileLogger.java @@ -1,3 +1,20 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2021 + */ package org.owasp.benchmarkutils.tools; import java.io.Closeable; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java index 57478f9b..74b7d1a2 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -1,9 +1,30 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2021 + */ package org.owasp.benchmarkutils.tools; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestCase; /** Not a great class name. */ +@XmlRootElement(name = "TestCaseVerificationResult") public class TestCaseVerificationResults { private ResponseInfo responseToAttackValue; @@ -22,6 +43,10 @@ public class TestCaseVerificationResults { private TestCase testCase; + public TestCaseVerificationResults() { + super(); + } + public TestCaseVerificationResults( TestExecutor attackTestExecutor, TestExecutor safeTestExecutor, @@ -59,6 +84,7 @@ public TestCaseVerificationResults( this.isPassed = isPassed; } + @XmlElement(name = "AttackResponseInfo") public ResponseInfo getResponseToAttackValue() { return responseToAttackValue; } @@ -67,6 +93,7 @@ public void setResponseToAttackValue(ResponseInfo responseToAttackValue) { this.responseToAttackValue = responseToAttackValue; } + @XmlElement(name = "SafeResponseInfo", required = true) public ResponseInfo getResponseToSafeValue() { return responseToSafeValue; } @@ -75,6 +102,7 @@ public void setResponseToSafeValue(ResponseInfo responseToSafeValue) { this.responseToSafeValue = responseToSafeValue; } + @XmlAttribute(name = "Unverifiable") public boolean isUnverifiable() { return isUnverifiable; } @@ -83,6 +111,7 @@ public void setUnverifiable(boolean isUnverifiable) { this.isUnverifiable = isUnverifiable; } + @XmlAttribute(name = "DeclaredUnverifiable") public boolean isDeclaredUnverifiable() { return isDeclaredUnverifiable; } @@ -91,14 +120,16 @@ public void setDeclaredUnverifiable(boolean isDeclaredUnverifiable) { this.isDeclaredUnverifiable = isDeclaredUnverifiable; } - public void setPassed(boolean isPassed) { - this.isPassed = isPassed; - } - + @XmlAttribute(name = "Passed") public boolean isPassed() { return isPassed; } + public void setPassed(boolean isPassed) { + this.isPassed = isPassed; + } + + @XmlElement(name = "AttackRequestInfo") public TestExecutor getAttackTestExecutor() { return attackTestExecutor; } @@ -107,6 +138,7 @@ public void setAttackTestExecutor(TestExecutor attackTestExecutor) { this.attackTestExecutor = attackTestExecutor; } + @XmlElement(name = "SafeRequestInfo") public TestExecutor getSafeTestExecutor() { return safeTestExecutor; } @@ -115,6 +147,7 @@ public void setSafeTestExecutor(TestExecutor safeTestExecutor) { this.safeTestExecutor = safeTestExecutor; } + @XmlElement(name = "TestCase") public TestCase getTestCase() { return testCase; } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java index 3becdd06..6fa30026 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java @@ -1,5 +1,29 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.tools; -interface TestExecutor { +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "TestCaseRequest") +public interface TestExecutor { + @XmlAttribute(name = "RequestDescription", required = true) + @NotNull public String getExecutorDescription(); } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java index 207fbeed..d9536b0b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java @@ -1,9 +1,26 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ package org.owasp.benchmarkutils.tools; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement -class VerifyFixOutput { +public class VerifyFixOutput { private boolean wasExploited; private boolean wasBroken; From 65b445b3bcc4fc174d7515cd8ac1f778ff95cdfc Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Wed, 1 May 2024 12:11:02 -0500 Subject: [PATCH 19/28] Add -j=true option for Verification crawler which causes it to generate a json version of the verification results for every test case. --- .../tools/BenchmarkCrawler.java | 1 - .../tools/BenchmarkCrawlerVerification.java | 28 ++++++++++++++--- .../tools/RegressionTesting.java | 31 +++++++++++-------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index ec0cd30e..b5d9ec1b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -455,7 +455,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.pluginFilenameParam) { System.out.println("ERROR: A crawlerFile parameter must be specified."); } else { - // String[] mainArgs = {"-f", this.pluginFilenameParam}; List mainArgs = new ArrayList<>(); mainArgs.add("-f"); mainArgs.add(this.pluginFilenameParam); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 1c76ce61..2cd5d2e0 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -38,6 +38,7 @@ import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.owasp.benchmarkutils.entities.CliRequest; import org.owasp.benchmarkutils.entities.CliResponseInfo; import org.owasp.benchmarkutils.entities.ExecutableTestCaseInput; @@ -78,6 +79,9 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { SimpleFileLogger eLogger; SimpleFileLogger uLogger; + @Parameter(property = "json") + String generateJSONResults; + BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. // The theCrawlerFile has to be instantiated before a crawl can be done. @@ -311,10 +315,13 @@ protected void crawl(TestSuite testSuite) throws Exception { } } - // Then generate JSON file with ALL verification results. This has to go at end - // because previous methods have some side affects that file in test case - // verification values. - RegressionTesting.genAllTCResultsToJsonFile(results, getOutputDirectory()); + // Then generate JSON file with ALL verification results if generateJSONResults + // is enabled. This has to go at end because previous methods have some side + // affects that fill in test case verification values. + RegressionTesting.genAllTCResultsToJsonFile( + results, + getOutputDirectory(), + Boolean.parseBoolean(generateJSONResults)); } } @@ -562,6 +569,12 @@ protected void processCommandLineArgs(String[] args) { .required() .build()); options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption( + Option.builder("j") + .longOpt("json") + .desc("generate json version of verification results") + .hasArg() + .build()); options.addOption("m", "", false, "verify fixed test suite"); options.addOption( Option.builder("n") @@ -602,6 +615,9 @@ protected void processCommandLineArgs(String[] args) { if (line.hasOption("h")) { formatter.printHelp("BenchmarkCrawlerVerification", options, true); } + if (line.hasOption("j")) { + generateJSONResults = line.getOptionValue("j"); + } if (line.hasOption("n")) { selectedTestCaseName = line.getOptionValue("n"); } @@ -626,6 +642,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { mainArgs.add("-n"); mainArgs.add(this.pluginTestCaseNameParam); } + if (this.generateJSONResults != null) { + mainArgs.add("-j"); + mainArgs.add(this.generateJSONResults); + } main(mainArgs.stream().toArray(String[]::new)); } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 57f0f38f..4b6796f4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -88,28 +88,33 @@ public class RegressionTesting { /** * Write a log file containing the verification results of all test cases, as JSON. * - * @param results - * @param dataDir + * @param results The verification results to log + * @param dataDir The directory to write the logfile to + * @param generate If true, generate log file, otherwise skip * @throws IOException * @throws LoggerConfigurationException */ public static void genAllTCResultsToJsonFile( - List results, String dataDir) + List results, String dataDir, boolean generate) throws IOException, LoggerConfigurationException { - final File FILE_TC_VERIF_RESULTS_JSON = new File(dataDir, FILENAME_TC_VERIF_RESULTS_JSON); - SimpleFileLogger.setFile("TC_VERIF_RESULTS_JSON", FILE_TC_VERIF_RESULTS_JSON); + if (generate) { + + final File FILE_TC_VERIF_RESULTS_JSON = + new File(dataDir, FILENAME_TC_VERIF_RESULTS_JSON); + SimpleFileLogger.setFile("TC_VERIF_RESULTS_JSON", FILE_TC_VERIF_RESULTS_JSON); - try (SimpleFileLogger tcJSON = SimpleFileLogger.getLogger("TC_VERIF_RESULTS_JSON")) { + try (SimpleFileLogger tcJSON = SimpleFileLogger.getLogger("TC_VERIF_RESULTS_JSON")) { - tcJsonLogger = tcJSON; + tcJsonLogger = tcJSON; - // Create JSON version of verification results for ALL test cases - tcJsonLogger.println(Utils.objectToJson(results)); - } catch (JAXBException e) { - System.out.println("Fatal Error trying to convert verification results to JSON"); - e.printStackTrace(); - System.exit(-1); + // Create JSON version of verification results for ALL test cases + tcJsonLogger.println(Utils.objectToJson(results)); + } catch (JAXBException e) { + System.out.println("Fatal Error trying to convert verification results to JSON"); + e.printStackTrace(); + System.exit(-1); + } } } From a5fdb8f51ceea55fa585dfa7ecb12fdca849e68e Mon Sep 17 00:00:00 2001 From: Dave Wichers Date: Wed, 1 May 2024 13:38:21 -0500 Subject: [PATCH 20/28] Tweak so no value has to be supplied with the -j parameter when invoked from the command line. --- .../owasp/benchmarkutils/tools/BenchmarkCrawler.java | 5 +++++ .../tools/BenchmarkCrawlerVerification.java | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index b5d9ec1b..af753024 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -450,6 +450,11 @@ protected void processCommandLineArgs(String[] args) { } } + /** + * The execute() method is invoked when this class is invoked as a maven plugin, rather than via + * the command line. So what we do here is set up the command line parameters and then invoke + * main() so this can be called both as a plugin, or via the command line. + */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.pluginFilenameParam) { diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 2cd5d2e0..c75804ff 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -573,7 +573,6 @@ protected void processCommandLineArgs(String[] args) { Option.builder("j") .longOpt("json") .desc("generate json version of verification results") - .hasArg() .build()); options.addOption("m", "", false, "verify fixed test suite"); options.addOption( @@ -616,7 +615,7 @@ protected void processCommandLineArgs(String[] args) { formatter.printHelp("BenchmarkCrawlerVerification", options, true); } if (line.hasOption("j")) { - generateJSONResults = line.getOptionValue("j"); + generateJSONResults = "true"; } if (line.hasOption("n")) { selectedTestCaseName = line.getOptionValue("n"); @@ -630,6 +629,11 @@ protected void processCommandLineArgs(String[] args) { } } + /** + * The execute() method is invoked when this class is invoked as a maven plugin, rather than via + * the command line. So what we do here is set up the command line parameters and then invoke + * main() so this can be called both as a plugin, or via the command line. + */ @Override public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.pluginFilenameParam) { @@ -643,8 +647,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { mainArgs.add(this.pluginTestCaseNameParam); } if (this.generateJSONResults != null) { + // At the command line, only the -j is required, with no value needed mainArgs.add("-j"); - mainArgs.add(this.generateJSONResults); } main(mainArgs.stream().toArray(String[]::new)); } From ff19425f12e071e0195914c768611e6b6fc64836 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 2 Jul 2024 05:14:20 -0400 Subject: [PATCH 21/28] Add a fix verification feature that compares new test results with old test results read from a JSON file. --- .../benchmarkutils/entities/CliRequest.java | 2 + .../entities/HttpResponseInfo.java | 33 +++- .../entities/HttpTestCaseInput.java | 20 ++- .../benchmarkutils/entities/ResponseInfo.java | 4 + plugin/pom.xml | 10 ++ .../owasp/benchmarkutils/helpers/Utils.java | 32 ++++ .../tools/BenchmarkCrawler.java | 12 +- .../tools/BenchmarkCrawlerVerification.java | 144 +++++++++++++----- .../benchmarkutils/tools/CliExecutor.java | 4 +- .../benchmarkutils/tools/HttpExecutor.java | 4 +- .../tools/RegressionTesting.java | 10 +- .../tools/TestCaseVerificationResults.java | 36 ++--- ...TestCaseVerificationResultsCollection.java | 20 +++ .../benchmarkutils/tools/TestExecutor.java | 6 +- pom.xml | 5 + 15 files changed, 264 insertions(+), 78 deletions(-) create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResultsCollection.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java index 92fa3745..61b59352 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -28,6 +28,8 @@ public class CliRequest { private RequestVariable stdinData; + public CliRequest() {} + public CliRequest(String command, List args, RequestVariable stdinData) { super(); this.command = command; diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java index a7f622b8..ad00d190 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java @@ -18,11 +18,14 @@ package org.owasp.benchmarkutils.entities; import javax.xml.bind.annotation.XmlElement; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; public class HttpResponseInfo extends ResponseInfo { - private HttpUriRequest requestBase; + // private HttpUriRequest requestBase; + + private String method; + + private String uri; public HttpResponseInfo() { // Default is this is a normal, non-attack response @@ -33,12 +36,30 @@ public HttpResponseInfo(boolean attackRequest) { super(attackRequest); } + // @XmlElement(required = true) + // public HttpUriRequest getRequestBase() { + // return requestBase; + // } + // + // public void setRequestBase(HttpUriRequest request) { + // this.requestBase = request; + // } + + @XmlElement(required = true) + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + @XmlElement(required = true) - public HttpUriRequest getRequestBase() { - return requestBase; + public String getUri() { + return uri; } - public void setRequestBase(HttpUriRequest request) { - this.requestBase = request; + public void setUri(String uri) { + this.uri = uri; } } diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java index e52435df..51a932be 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java @@ -18,6 +18,7 @@ package org.owasp.benchmarkutils.entities; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.security.KeyManagementException; @@ -64,7 +65,7 @@ public abstract class HttpTestCaseInput extends TestCaseInput { private List headers; - private static CloseableHttpClient httpClient; + // private static CloseableHttpClient httpClient; void beforeMarshal(Marshaller marshaller) { // System.out.println("Before marshal"); @@ -253,15 +254,22 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r static ResponseInfo sendRequest( CloseableHttpClient httpclient, HttpUriRequest request, boolean attackRequest) { HttpResponseInfo responseInfo = new HttpResponseInfo(attackRequest); - responseInfo.setRequestBase(request); - CloseableHttpResponse response = null; - - boolean isPost = request instanceof HttpPost; + // responseInfo.setRequestBase(request); + responseInfo.setMethod(request.getMethod()); + URI uri = null; try { - System.out.println((isPost ? "POST " : "GET ") + request.getUri()); + uri = request.getUri(); } catch (URISyntaxException e) { + // TODO Auto-generated catch block e.printStackTrace(); } + responseInfo.setUri(uri.toString()); + + CloseableHttpResponse response = null; + + boolean isPost = request instanceof HttpPost; + System.out.println((isPost ? "POST " : "GET ") + uri); + StopWatch watch = new StopWatch(); watch.start(); diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java index e9e3fcfb..14f1ae1f 100644 --- a/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -19,8 +19,12 @@ import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; @XmlRootElement(name = "ResponseInfo") +@XmlSeeAlso({CliResponseInfo.class, HttpResponseInfo.class}) +@XmlDiscriminatorNode("@type") public abstract class ResponseInfo { // True if response to an attack request. False if response to normal request diff --git a/plugin/pom.xml b/plugin/pom.xml index 47cc9fd9..041bc269 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -148,6 +148,16 @@ xml-apis xml-apis + + javax.json + javax.json-api + 1.1.4 + + + org.glassfish + javax.json + 1.1.4 + diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index add07cd8..9ece0b0b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -57,11 +57,13 @@ import org.apache.commons.io.FileUtils; import org.eclipse.persistence.jaxb.JAXBContextFactory; import org.eclipse.persistence.jaxb.MarshallerProperties; +import org.eclipse.persistence.jaxb.UnmarshallerProperties; import org.eclipse.persistence.oxm.MediaType; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.tools.TestCaseRequestFileParseException; import org.owasp.benchmarkutils.tools.TestCaseVerificationResults; +import org.owasp.benchmarkutils.tools.TestCaseVerificationResultsCollection; import org.owasp.benchmarkutils.tools.VerifyFixOutput; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -415,6 +417,7 @@ public static String objectToJson(Object object) throws JAXBException { ResponseInfo.class, TestSuite.class, TestCaseVerificationResults.class, + TestCaseVerificationResultsCollection.class, VerifyFixOutput.class }; JAXBContext jaxbContext = @@ -431,4 +434,33 @@ public static String objectToJson(Object object) throws JAXBException { return writer.toString(); } + + public static TestCaseVerificationResultsCollection jsonToTestCaseVerificationResultsList( + File file) + throws JAXBException, FileNotFoundException, SAXException, + ParserConfigurationException { + + // Disable XXE + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + // Do unmarshall operation + // Source jsonSource = + // new SAXSource( + // spf.newSAXParser().getXMLReader(), new InputSource(new + // FileReader(file))); + JAXBContext context = + JAXBContextFactory.createContext( + new Class[] {TestCaseVerificationResultsCollection.class}, null); + Unmarshaller unmarshaller = context.createUnmarshaller(); + unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON); + unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, Boolean.TRUE); + unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); + TestCaseVerificationResultsCollection resultsList = + (TestCaseVerificationResultsCollection) unmarshaller.unmarshal(file); + + return resultsList; + } } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index af753024..8d934b4d 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.net.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -280,7 +281,16 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r static ResponseInfo sendRequest( CloseableHttpClient httpclient, HttpUriRequest request, boolean attackRequest) { HttpResponseInfo responseInfo = new HttpResponseInfo(attackRequest); - responseInfo.setRequestBase(request); + // responseInfo.setRequestBase(request); + responseInfo.setMethod(request.getMethod()); + URI uri = null; + try { + uri = request.getUri(); + } catch (URISyntaxException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + responseInfo.setUri(uri.toString()); CloseableHttpResponse response = null; boolean isPost = request instanceof HttpPost; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index c75804ff..92b5dd82 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -20,11 +20,11 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.xml.bind.JAXBException; +import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -50,6 +50,7 @@ import org.owasp.benchmarkutils.entities.TestCaseSetupException; import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.helpers.Utils; +import org.xml.sax.SAXException; /** * TODO: Refactor this class. There is way too much duplication of code in BenchmarkCrawler here. @@ -62,15 +63,19 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static int maxTimeInSeconds = 2; private static boolean isTimingEnabled = false; - private boolean verifyFixed = false; + // private boolean verifyFixed = false; // DEBUG private String configurationDirectory = Utils.DATA_DIR; private String outputDirectory = Utils.DATA_DIR; - private String beforeFixOutputDirectory = Utils.DATA_DIR; + // private String beforeFixOutputDirectory = + // new File(new File(Utils.DATA_DIR).getParent(), "before_data") + // .getAbsolutePath(); // DEBUG: Utils.DATA_DIR; private static final String FILENAME_TIMES_ALL = "crawlerTimes.txt"; private static final String FILENAME_TIMES = "crawlerSlowTimes.txt"; private static final String FILENAME_NON_DISCRIMINATORY_LOG = "nonDiscriminatoryTestCases.txt"; private static final String FILENAME_ERRORS_LOG = "errorTestCases.txt"; private static final String FILENAME_UNVERIFIABLE_LOG = "unverifiableTestCases.txt"; + // FIXME: This constant is also used by RegressionUtils and should not be duplicated. + private static final String FILENAME_TC_VERIF_RESULTS_JSON = "testCaseVerificationResults.json"; // The following is reconfigurable via parameters to main() // private String CRAWLER_DATA_DIR = Utils.DATA_DIR; // default data dir @@ -79,8 +84,14 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { SimpleFileLogger eLogger; SimpleFileLogger uLogger; - @Parameter(property = "json") - String generateJSONResults; + @Parameter(property = "json", defaultValue = "false") + private String generateJSONResults; + + @Parameter(property = "verifyFixed", defaultValue = "false") + private String verifyFixed; + + @Parameter(property = "beforeFixOutputDirectory") + private String beforeFixOutputDirectory; BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. @@ -242,8 +253,8 @@ protected void crawl(TestSuite testSuite) throws Exception { } TestCaseVerificationResults result = new TestCaseVerificationResults( - attackExecutor, - safeExecutor, + attackExecutor.getExecutorDescription(), + safeExecutor.getExecutorDescription(), testCase, attackPayloadResponseInfo, safePayloadResponseInfo); @@ -254,6 +265,9 @@ protected void crawl(TestSuite testSuite) throws Exception { handleResponse(result); } } + TestCaseVerificationResultsCollection resultsCollection = + new TestCaseVerificationResultsCollection(); + resultsCollection.setResultsObjects(results); // Log the elapsed time for all test cases long stop = System.currentTimeMillis(); @@ -319,7 +333,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // is enabled. This has to go at end because previous methods have some side // affects that fill in test case verification values. RegressionTesting.genAllTCResultsToJsonFile( - results, + resultsCollection, getOutputDirectory(), Boolean.parseBoolean(generateJSONResults)); } @@ -407,25 +421,21 @@ private void log(ResponseInfo responseInfo) throws IOException { HttpResponseInfo httpResponseInfo = (HttpResponseInfo) responseInfo; // Log the response - HttpUriRequest requestBase = httpResponseInfo.getRequestBase(); + // HttpUriRequest requestBase = httpResponseInfo.getRequestBase(); String outputString = String.format( "--> (%d : %d sec)%n", httpResponseInfo.getStatusCode(), httpResponseInfo.getTimeInSeconds()); - try { - if (isTimingEnabled) { - if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); - tLogger.println(outputString); - } - } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + if (isTimingEnabled) { + if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(httpResponseInfo.getMethod() + " " + httpResponseInfo.getUri()); tLogger.println(outputString); } - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } else { + tLogger.println(httpResponseInfo.getMethod() + " " + httpResponseInfo.getUri()); + tLogger.println(outputString); } + } else if (responseInfo instanceof CliResponseInfo) { CliResponseInfo cliResponseInfo = (CliResponseInfo) responseInfo; @@ -466,30 +476,47 @@ protected void handleResponse(TestCaseVerificationResults results) // specified. RegressionTesting.verifyTestCase(results); - if (verifyFixed) { - TestCaseVerificationResults beforeFixResults = + if (Boolean.parseBoolean(verifyFixed)) { + TestCaseVerificationResultsCollection beforeFixResultsCollection = loadTestCaseVerificationResults(beforeFixOutputDirectory); + TestCaseVerificationResults beforeFixResults = + beforeFixResultsCollection.getResultsObjects().get(0); verifyFix(beforeFixResults, results); } } - private TestCaseVerificationResults loadTestCaseVerificationResults(String directory) { + private TestCaseVerificationResultsCollection loadTestCaseVerificationResults( + String directory) { + + TestCaseVerificationResultsCollection results = null; + try { + results = + Utils.jsonToTestCaseVerificationResultsList( + new File(directory, FILENAME_TC_VERIF_RESULTS_JSON)); + } catch (JAXBException + | FileNotFoundException + | SAXException + | ParserConfigurationException e) { + System.out.println("ERROR: Could not unmarshall JSON file content."); + e.printStackTrace(); + } // FIXME: Replace this with code to load results from JSON data files - TestExecutor attackTestExecutor = new HttpExecutor(null); - TestExecutor safeTestExecutor = new HttpExecutor(null); - TestCase testCase = new TestCase(); - ResponseInfo responseToAttackValue = new HttpResponseInfo(); // FIXME: Or CliResponseInfo - responseToAttackValue.setResponseString(""); - ResponseInfo responseToSafeValue = new HttpResponseInfo(); - responseToSafeValue.setResponseString(""); - TestCaseVerificationResults results = - new TestCaseVerificationResults( - attackTestExecutor, - safeTestExecutor, - testCase, - responseToAttackValue, - responseToSafeValue); + // TestExecutor attackTestExecutor = new HttpExecutor(null); + // TestExecutor safeTestExecutor = new HttpExecutor(null); + // TestCase testCase = new TestCase(); + // ResponseInfo responseToAttackValue = new HttpResponseInfo(); // FIXME: Or + // CliResponseInfo + // responseToAttackValue.setResponseString(""); + // ResponseInfo responseToSafeValue = new HttpResponseInfo(); + // responseToSafeValue.setResponseString(""); + // TestCaseVerificationResults results = + // new TestCaseVerificationResults( + // attackTestExecutor, + // safeTestExecutor, + // testCase, + // responseToAttackValue, + // responseToSafeValue); return results; } @@ -528,6 +555,30 @@ private boolean verifyFix( return !wasExploited && !wasBroken; } + private boolean verifyFixes( + TestCaseVerificationResultsCollection beforeFixResultsCollection, + TestCaseVerificationResultsCollection afterFixResultsCollection) { + + boolean isVerified = true; + + // This assumes that the results are ordered. + if (beforeFixResultsCollection.getResultsObjects().size() + != afterFixResultsCollection.getResultsObjects().size()) { + System.out.println("ERROR: Results lists are not the same size"); + isVerified = false; + } else { + int count = beforeFixResultsCollection.getResultsObjects().size(); + for (int i = 0; i < count; i++) { + TestCaseVerificationResults beforeFixResults = + beforeFixResultsCollection.getResultsObjects().get(i); + TestCaseVerificationResults afterFixResults = + afterFixResultsCollection.getResultsObjects().get(i); + isVerified &= verifyFix(beforeFixResults, afterFixResults); + } + } + return isVerified; + } + /** * Process the command line arguments that make any configuration changes. * @@ -535,6 +586,7 @@ private boolean verifyFix( * @return specified crawler file if valid command line arguments provided. Null otherwise. */ protected void processCommandLineArgs(String[] args) { + System.out.println("Maven user selected verifyFixed=" + verifyFixed); // Set default attack crawler file String crawlerFileName = new File(Utils.DATA_DIR, "benchmark-attack-http.xml").getPath(); @@ -574,7 +626,9 @@ protected void processCommandLineArgs(String[] args) { .longOpt("json") .desc("generate json version of verification results") .build()); - options.addOption("m", "", false, "verify fixed test suite"); + // options.addOption("m", "verifyFixed", false, "verify fixed test suite"); + options.addOption( + Option.builder("m").longOpt("verifyFixed").desc("verify fixed test suite").build()); options.addOption( Option.builder("n") .longOpt("name") @@ -617,6 +671,10 @@ protected void processCommandLineArgs(String[] args) { if (line.hasOption("j")) { generateJSONResults = "true"; } + if (line.hasOption("m")) { + System.out.println("User selected to verify fixes"); + verifyFixed = "true"; + } if (line.hasOption("n")) { selectedTestCaseName = line.getOptionValue("n"); } @@ -642,11 +700,19 @@ public void execute() throws MojoExecutionException, MojoFailureException { List mainArgs = new ArrayList<>(); mainArgs.add("-f"); mainArgs.add(this.pluginFilenameParam); + if (this.beforeFixOutputDirectory != null) { + mainArgs.add("-b"); + mainArgs.add(this.beforeFixOutputDirectory); + } if (this.pluginTestCaseNameParam != null) { mainArgs.add("-n"); mainArgs.add(this.pluginTestCaseNameParam); } - if (this.generateJSONResults != null) { + if (Boolean.parseBoolean(this.verifyFixed)) { + // At the command line, only the -m is required, with no value needed + mainArgs.add("-m"); + } + if (Boolean.parseBoolean(this.generateJSONResults)) { // At the command line, only the -j is required, with no value needed mainArgs.add("-j"); } diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java index 11528398..9e45f347 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java @@ -24,9 +24,11 @@ import org.owasp.benchmarkutils.entities.RequestVariable; @XmlRootElement(name = "CliRequest") -public class CliExecutor implements TestExecutor { +public class CliExecutor extends TestExecutor { CliRequest cliRequest; + public CliExecutor() {} + public CliExecutor(CliRequest cliRequest) { super(); this.cliRequest = cliRequest; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java index b06bddd9..b4c535d6 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java @@ -29,9 +29,11 @@ import org.apache.hc.core5.http.HttpEntity; @XmlRootElement(name = "HttpRequest") -public class HttpExecutor implements TestExecutor { +public class HttpExecutor extends TestExecutor { HttpUriRequest httpRequest; + public HttpExecutor() {} + public HttpExecutor(HttpUriRequest httpRequest) { super(); this.httpRequest = httpRequest; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java index 4b6796f4..43cd04cd 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -95,7 +95,9 @@ public class RegressionTesting { * @throws LoggerConfigurationException */ public static void genAllTCResultsToJsonFile( - List results, String dataDir, boolean generate) + TestCaseVerificationResultsCollection resultsCollection, + String dataDir, + boolean generate) throws IOException, LoggerConfigurationException { if (generate) { @@ -109,7 +111,7 @@ public static void genAllTCResultsToJsonFile( tcJsonLogger = tcJSON; // Create JSON version of verification results for ALL test cases - tcJsonLogger.println(Utils.objectToJson(results)); + tcJsonLogger.println(Utils.objectToJson(resultsCollection)); } catch (JAXBException e) { System.out.println("Fatal Error trying to convert verification results to JSON"); e.printStackTrace(); @@ -260,7 +262,7 @@ private static void printTestCaseDetailsAsText(TestCaseVerificationResults resul out.println(testCase.toString()); out.println(); out.println("Attack request:"); - out.println(result.getAttackTestExecutor().getExecutorDescription()); + out.println(result.getAttackTestExecutorDescription()); if (attackResponseInfo instanceof HttpResponseInfo) { out.printf( "Attack response: [%d]:%n", @@ -280,7 +282,7 @@ private static void printTestCaseDetailsAsText(TestCaseVerificationResults resul } out.println(); out.println("Safe request:"); - out.println(result.getSafeTestExecutor().getExecutorDescription()); + out.println(result.getSafeTestExecutorDescription()); if (safeResponseInfo instanceof HttpResponseInfo) { out.printf( "Safe response: [%d]:%n", diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java index 74b7d1a2..c3ebb603 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -37,9 +37,9 @@ public class TestCaseVerificationResults { private boolean isPassed; - private TestExecutor attackTestExecutor; + private String attackTestExecutorDescription; - private TestExecutor safeTestExecutor; + private String safeTestExecutorDescription; private TestCase testCase; @@ -48,14 +48,14 @@ public TestCaseVerificationResults() { } public TestCaseVerificationResults( - TestExecutor attackTestExecutor, - TestExecutor safeTestExecutor, + String attackTestExecutorDescription, + String safeTestExecutorDescription, TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue) { this( - attackTestExecutor, - safeTestExecutor, + attackTestExecutorDescription, + safeTestExecutorDescription, testCase, responseToAttackValue, responseToSafeValue, @@ -65,8 +65,8 @@ public TestCaseVerificationResults( } public TestCaseVerificationResults( - TestExecutor attackTestExecutor, - TestExecutor safeTestExecutor, + String attackTestExecutorDescription, + String safeTestExecutorDescription, TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue, @@ -74,8 +74,8 @@ public TestCaseVerificationResults( boolean isDeclaredVerifiable, boolean isPassed) { super(); - this.attackTestExecutor = attackTestExecutor; - this.safeTestExecutor = safeTestExecutor; + this.attackTestExecutorDescription = attackTestExecutorDescription; + this.safeTestExecutorDescription = safeTestExecutorDescription; this.testCase = testCase; this.responseToAttackValue = responseToAttackValue; this.responseToSafeValue = responseToSafeValue; @@ -130,21 +130,21 @@ public void setPassed(boolean isPassed) { } @XmlElement(name = "AttackRequestInfo") - public TestExecutor getAttackTestExecutor() { - return attackTestExecutor; + public String getAttackTestExecutorDescription() { + return attackTestExecutorDescription; } - public void setAttackTestExecutor(TestExecutor attackTestExecutor) { - this.attackTestExecutor = attackTestExecutor; + public void setAttackTestExecutorDescription(String attackTestExecutor) { + this.attackTestExecutorDescription = attackTestExecutorDescription; } @XmlElement(name = "SafeRequestInfo") - public TestExecutor getSafeTestExecutor() { - return safeTestExecutor; + public String getSafeTestExecutorDescription() { + return safeTestExecutorDescription; } - public void setSafeTestExecutor(TestExecutor safeTestExecutor) { - this.safeTestExecutor = safeTestExecutor; + public void setSafeTestExecutorDescription(String safeTestExecutor) { + this.safeTestExecutorDescription = safeTestExecutorDescription; } @XmlElement(name = "TestCase") diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResultsCollection.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResultsCollection.java new file mode 100644 index 00000000..38d301cb --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResultsCollection.java @@ -0,0 +1,20 @@ +package org.owasp.benchmarkutils.tools; + +import java.util.List; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "TestCaseVerificationResultsCollection") +public class TestCaseVerificationResultsCollection { + + private List resultsObjects; + + @XmlElement + public List getResultsObjects() { + return resultsObjects; + } + + public void setResultsObjects(List resultsObjects) { + this.resultsObjects = resultsObjects; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java index 6fa30026..03fc4443 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java @@ -20,10 +20,12 @@ import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; @XmlRootElement(name = "TestCaseRequest") -public interface TestExecutor { +@XmlSeeAlso({CliExecutor.class, HttpExecutor.class}) +public abstract class TestExecutor { @XmlAttribute(name = "RequestDescription", required = true) @NotNull - public String getExecutorDescription(); + public abstract String getExecutorDescription(); } diff --git a/pom.xml b/pom.xml index 2da88d80..51657f79 100755 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,11 @@ 1.4.01 + + javax.json + javax.json-api + 1.1.4 + From 5b441905d5cf0379346286c97e5380d63d125938 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 2 Jul 2024 10:54:39 -0400 Subject: [PATCH 22/28] Add a command-line option to select a single testcase by name for verification. --- .../tools/BenchmarkCrawlerVerification.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 92b5dd82..138659f9 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.cli.CommandLine; @@ -93,6 +94,9 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { @Parameter(property = "beforeFixOutputDirectory") private String beforeFixOutputDirectory; + @Parameter(property = "testCaseName") + private String selectedTestCaseName; + BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. // The theCrawlerFile has to be instantiated before a crawl can be done. @@ -139,7 +143,18 @@ protected void crawl(TestSuite testSuite) throws Exception { uLogger = ul; tLogger = tl; - for (TestCase testCase : testSuite.getTestCases()) { + List filteredList; + if (selectedTestCaseName != null) { + filteredList = + testSuite.getTestCases().stream() + .filter( + testCase -> + testCase.getName().equals(selectedTestCaseName)) + .collect(Collectors.toList()); + } else { + filteredList = testSuite.getTestCases(); + } + for (TestCase testCase : filteredList) { // if (this.selectedTestCaseName != null) { // if From dbce0f288bb9acdfc903db85057a40472f3cef74 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 3 Jul 2024 10:21:30 -0400 Subject: [PATCH 23/28] Write the verifyFix output to a file and fix a bug that broke determining wasBroken. --- .../tools/BenchmarkCrawlerVerification.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 138659f9..4063c61e 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -17,8 +17,10 @@ */ package org.owasp.benchmarkutils.tools; +import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -77,6 +79,7 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static final String FILENAME_UNVERIFIABLE_LOG = "unverifiableTestCases.txt"; // FIXME: This constant is also used by RegressionUtils and should not be duplicated. private static final String FILENAME_TC_VERIF_RESULTS_JSON = "testCaseVerificationResults.json"; + private static final String FILENAME_VERIFY_FIX_RESULT = "verifyFixedResult.json"; // The following is reconfigurable via parameters to main() // private String CRAWLER_DATA_DIR = Utils.DATA_DIR; // default data dir @@ -496,7 +499,15 @@ protected void handleResponse(TestCaseVerificationResults results) loadTestCaseVerificationResults(beforeFixOutputDirectory); TestCaseVerificationResults beforeFixResults = beforeFixResultsCollection.getResultsObjects().get(0); - verifyFix(beforeFixResults, results); + if (beforeFixResults.getTestCase().getName().equals(results.getTestCase().getName())) { + verifyFix(beforeFixResults, results); + } else { + System.out.println( + "WARNING: After fix testcase is " + + results.getTestCase().getName() + + " but before fix testcase is " + + beforeFixResults.getTestCase().getName()); + } } } @@ -548,7 +559,7 @@ private boolean verifyFix( !beforeFixResults .getResponseToSafeValue() .getResponseString() - .equals(afterFixResults.getResponseToAttackValue().getResponseString()); + .equals(afterFixResults.getResponseToSafeValue().getResponseString()); if (wasExploited) { System.out.println("NOT FIXED: Vulnerability was exploited"); } @@ -556,12 +567,18 @@ private boolean verifyFix( System.out.println("NOT FIXED: Functionality was broken"); } - try { + File verifyFixResultFile = new File(getOutputDirectory(), FILENAME_VERIFY_FIX_RESULT); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(verifyFixResultFile))) { VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); verifyFixOutput.setWasExploited(wasExploited); verifyFixOutput.setWasBroken(wasBroken); String output = Utils.objectToJson(verifyFixOutput); - System.out.println(output); + // System.out.println(output); + writer.write(output); + } catch (IOException e) { + System.out.println( + "ERROR: Could not write VerifyFixOutput to file " + verifyFixResultFile); + e.printStackTrace(); } catch (JAXBException e) { System.out.println("ERROR: Could not marshall VerifyFixOutput to JSON"); e.printStackTrace(); From 84968a51fd6e98a0f214a1d2a8a93e6990576f78 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 3 Jul 2024 11:13:55 -0400 Subject: [PATCH 24/28] Add the outputDirectory CLI option. --- .../tools/BenchmarkCrawlerVerification.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 4063c61e..fd568a36 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -68,10 +68,10 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static boolean isTimingEnabled = false; // private boolean verifyFixed = false; // DEBUG private String configurationDirectory = Utils.DATA_DIR; - private String outputDirectory = Utils.DATA_DIR; - // private String beforeFixOutputDirectory = - // new File(new File(Utils.DATA_DIR).getParent(), "before_data") - // .getAbsolutePath(); // DEBUG: Utils.DATA_DIR; + private String defaultOutputDirectory = Utils.DATA_DIR; + private String defaultBeforeFixOutputDirectory = + new File(new File(Utils.DATA_DIR).getParent(), "before_data") + .getAbsolutePath(); // DEBUG: Utils.DATA_DIR; private static final String FILENAME_TIMES_ALL = "crawlerTimes.txt"; private static final String FILENAME_TIMES = "crawlerSlowTimes.txt"; private static final String FILENAME_NON_DISCRIMINATORY_LOG = "nonDiscriminatoryTestCases.txt"; @@ -97,6 +97,9 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { @Parameter(property = "beforeFixOutputDirectory") private String beforeFixOutputDirectory; + @Parameter(property = "outputDirectory") + private String outputDirectory; + @Parameter(property = "testCaseName") private String selectedTestCaseName; @@ -681,9 +684,13 @@ protected void processCommandLineArgs(String[] args) { if (line.hasOption("b")) { beforeFixOutputDirectory = line.getOptionValue("b"); + } else { + beforeFixOutputDirectory = defaultBeforeFixOutputDirectory; } if (line.hasOption("d")) { outputDirectory = line.getOptionValue("d"); + } else { + outputDirectory = defaultOutputDirectory; } if (line.hasOption("f")) { this.crawlerFile = line.getOptionValue("f"); @@ -732,6 +739,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { List mainArgs = new ArrayList<>(); mainArgs.add("-f"); mainArgs.add(this.pluginFilenameParam); + if (this.outputDirectory != null) { + mainArgs.add("-d"); + mainArgs.add(this.outputDirectory); + } if (this.beforeFixOutputDirectory != null) { mainArgs.add("-b"); mainArgs.add(this.beforeFixOutputDirectory); From 9623bccb616aa0958244c79b83a4a5e216769ad4 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 8 Jan 2025 06:20:31 +0000 Subject: [PATCH 25/28] Modified the fix verification feature of the Benchmark verification crawler based on feedback from Dave Wichers. --- .../tools/BenchmarkCrawlerVerification.java | 139 +++++++++++++----- .../benchmarkutils/tools/VerifyFixOutput.java | 9 ++ 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index fd568a36..ae95e04f 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -22,9 +22,13 @@ import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; @@ -68,10 +72,12 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static boolean isTimingEnabled = false; // private boolean verifyFixed = false; // DEBUG private String configurationDirectory = Utils.DATA_DIR; - private String defaultOutputDirectory = Utils.DATA_DIR; - private String defaultBeforeFixOutputDirectory = - new File(new File(Utils.DATA_DIR).getParent(), "before_data") - .getAbsolutePath(); // DEBUG: Utils.DATA_DIR; + private String defaultOutputDirectory = configurationDirectory; + // private String defaultFixedOutputDirectory = Paths.get(Utils.DATA_DIR, + // "fixstatus").toString(); + // private String defaultUnfixedSrcDirectory = + // new File(new File(Utils.DATA_DIR).getParent(), "before_data") + // .getAbsolutePath(); // DEBUG: Utils.DATA_DIR; private static final String FILENAME_TIMES_ALL = "crawlerTimes.txt"; private static final String FILENAME_TIMES = "crawlerSlowTimes.txt"; private static final String FILENAME_NON_DISCRIMINATORY_LOG = "nonDiscriminatoryTestCases.txt"; @@ -88,14 +94,17 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { SimpleFileLogger eLogger; SimpleFileLogger uLogger; - @Parameter(property = "json", defaultValue = "false") + @Parameter(property = "generateJSONResults", defaultValue = "false") private String generateJSONResults; @Parameter(property = "verifyFixed", defaultValue = "false") private String verifyFixed; - @Parameter(property = "beforeFixOutputDirectory") - private String beforeFixOutputDirectory; + @Parameter(property = "unfixedSrcDirectory") + private String unfixedSourceDirectory; + + @Parameter(property = "fixedSrcDirectory") + private String fixedSourceDirectory; @Parameter(property = "outputDirectory") private String outputDirectory; @@ -103,6 +112,9 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { @Parameter(property = "testCaseName") private String selectedTestCaseName; + private Map testCaseNameToTestCaseVerificationResultsMap = + new HashMap<>(); + BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. // The theCrawlerFile has to be instantiated before a crawl can be done. @@ -352,7 +364,7 @@ protected void crawl(TestSuite testSuite) throws Exception { // Then generate JSON file with ALL verification results if generateJSONResults // is enabled. This has to go at end because previous methods have some side - // affects that fill in test case verification values. + // effects that fill in test case verification values. RegressionTesting.genAllTCResultsToJsonFile( resultsCollection, getOutputDirectory(), @@ -391,7 +403,7 @@ protected void crawl(TestSuite testSuite) throws Exception { * @param testSuite * @throws Exception */ - protected void crawlToVerifyFix(TestSuite testSuite, String beforeFixOutputDirectory) + protected void crawlToVerifyFix(TestSuite testSuite, String unfixedOutputDirectory) throws Exception { // Get config directory for RUN 1 from CLI option @@ -481,7 +493,7 @@ private void log(ResponseInfo responseInfo) throws IOException { /** * For the verification crawler, processing the result means verifying whether the test case is * actually vulnerable or not, relative to whether it is supposed to be vulnerable. This method - * has a side-affect of setting request.setPassed() for the current test case. Passing means it + * has a side-effect of setting request.setPassed() for the current test case. Passing means it * was exploitable for a True Positive and appears to not be exploitable for a False Positive. * * @param result - The results required to verify this test case. @@ -489,7 +501,7 @@ private void log(ResponseInfo responseInfo) throws IOException { * @throws LoggerConfigurationException */ protected void handleResponse(TestCaseVerificationResults results) - throws FileNotFoundException, LoggerConfigurationException { + throws FileNotFoundException, IOException, LoggerConfigurationException { // Check to see if this specific test case has a specified expected response value. // If so, run it through verification using it's specific attackSuccessIndicator. @@ -498,18 +510,46 @@ protected void handleResponse(TestCaseVerificationResults results) RegressionTesting.verifyTestCase(results); if (Boolean.parseBoolean(verifyFixed)) { - TestCaseVerificationResultsCollection beforeFixResultsCollection = - loadTestCaseVerificationResults(beforeFixOutputDirectory); - TestCaseVerificationResults beforeFixResults = - beforeFixResultsCollection.getResultsObjects().get(0); - if (beforeFixResults.getTestCase().getName().equals(results.getTestCase().getName())) { - verifyFix(beforeFixResults, results); + String unfixedOutputDirectory = configurationDirectory; + + TestCaseVerificationResults fixedResults = results; + TestCaseVerificationResultsCollection unfixedResultsCollection = + loadTestCaseVerificationResults(unfixedOutputDirectory); + TestCaseVerificationResults unfixedResults = + testCaseNameToTestCaseVerificationResultsMap.get( + fixedResults.getTestCase().getName()); + if (unfixedResults + .getTestCase() + .getName() + .equals(fixedResults.getTestCase().getName())) { + // FIXME: Generalize this so it can support languages other than Java and multiple + // source files per testcase. + String unfixedSourceFile = + Paths.get(unfixedSourceDirectory, unfixedResults.getTestCase().getName()) + .toString() + + ".java"; + String fixedSourceFile = + Paths.get(fixedSourceDirectory, fixedResults.getTestCase().getName()) + .toString() + + ".java"; + String unfixedSourceFileContents = + new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); + String fixedSourceFileContents = + new String(Files.readAllBytes(Paths.get(fixedSourceFile))); + if (!unfixedSourceFileContents.equals(fixedSourceFileContents)) { + verifyFix(unfixedResults, fixedResults); + } else { + System.out.println( + "WARNING: Testcase " + + fixedResults.getTestCase().getName() + + " source file unmodified"); + } } else { System.out.println( "WARNING: After fix testcase is " - + results.getTestCase().getName() + + fixedResults.getTestCase().getName() + " but before fix testcase is " - + beforeFixResults.getTestCase().getName()); + + unfixedResults.getTestCase().getName()); } } } @@ -522,6 +562,11 @@ private TestCaseVerificationResultsCollection loadTestCaseVerificationResults( results = Utils.jsonToTestCaseVerificationResultsList( new File(directory, FILENAME_TC_VERIF_RESULTS_JSON)); + for (TestCaseVerificationResults testCaseResults : results.getResultsObjects()) { + testCaseNameToTestCaseVerificationResultsMap.put( + testCaseResults.getTestCase().getName(), testCaseResults); + } + } catch (JAXBException | FileNotFoundException | SAXException @@ -554,6 +599,10 @@ private boolean verifyFix( TestCaseVerificationResults beforeFixResults, TestCaseVerificationResults afterFixResults) { + boolean wasNotVerfiable = + afterFixResults.getTestCase().isVulnerability() + && afterFixResults.getTestCase().isUnverifiable() + && afterFixResults.isPassed(); boolean wasExploited = afterFixResults.getTestCase().isVulnerability() && !afterFixResults.getTestCase().isUnverifiable() @@ -563,6 +612,9 @@ private boolean verifyFix( .getResponseToSafeValue() .getResponseString() .equals(afterFixResults.getResponseToSafeValue().getResponseString()); + if (wasNotVerfiable) { + System.out.println("NOT FIXED: Vulnerability could not be verified"); + } if (wasExploited) { System.out.println("NOT FIXED: Vulnerability was exploited"); } @@ -573,6 +625,7 @@ private boolean verifyFix( File verifyFixResultFile = new File(getOutputDirectory(), FILENAME_VERIFY_FIX_RESULT); try (BufferedWriter writer = new BufferedWriter(new FileWriter(verifyFixResultFile))) { VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); + verifyFixOutput.setWasNotVerfiable(wasNotVerfiable); verifyFixOutput.setWasExploited(wasExploited); verifyFixOutput.setWasBroken(wasBroken); String output = Utils.objectToJson(verifyFixOutput); @@ -587,7 +640,7 @@ private boolean verifyFix( e.printStackTrace(); } - return !wasExploited && !wasBroken; + return !wasNotVerfiable && !wasExploited && !wasBroken; } private boolean verifyFixes( @@ -638,12 +691,18 @@ protected void processCommandLineArgs(String[] args) { Options options = new Options(); options.addOption( Option.builder("b") - .longOpt("beforeFixOutputDirectory") - .desc("output directory of a previous crawl before fixes") + .longOpt("unfixedSrcDirectory") + .desc("source directory before fixes") + .hasArg() + .build()); + options.addOption( + Option.builder("a") + .longOpt("fixedSrcDirectory") + .desc("source directory after fixes") .hasArg() .build()); options.addOption( - Option.builder("d") + Option.builder("o") .longOpt("outputDirectory") .desc("output directory") .hasArg() @@ -658,7 +717,7 @@ protected void processCommandLineArgs(String[] args) { options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); options.addOption( Option.builder("j") - .longOpt("json") + .longOpt("generateJSONResults") .desc("generate json version of verification results") .build()); // options.addOption("m", "verifyFixed", false, "verify fixed test suite"); @@ -682,16 +741,19 @@ protected void processCommandLineArgs(String[] args) { // Parse the command line arguments CommandLine line = parser.parse(options, args); - if (line.hasOption("b")) { - beforeFixOutputDirectory = line.getOptionValue("b"); - } else { - beforeFixOutputDirectory = defaultBeforeFixOutputDirectory; - } - if (line.hasOption("d")) { - outputDirectory = line.getOptionValue("d"); + if (line.hasOption("o")) { + outputDirectory = line.getOptionValue("o"); } else { outputDirectory = defaultOutputDirectory; } + // Required if in verifyFix mode + if (line.hasOption("b")) { + unfixedSourceDirectory = line.getOptionValue("b"); + } + // Required if in verifyFix mode + if (line.hasOption("a")) { + fixedSourceDirectory = line.getOptionValue("a"); + } if (line.hasOption("f")) { this.crawlerFile = line.getOptionValue("f"); File targetFile = new File(this.crawlerFile); @@ -720,6 +782,11 @@ protected void processCommandLineArgs(String[] args) { if (line.hasOption("t")) { maxTimeInSeconds = (Integer) line.getParsedOptionValue("t"); } + + // The default is different if we are in verifyFix mode + if (Boolean.parseBoolean(this.verifyFixed)) { + outputDirectory = Paths.get(Utils.DATA_DIR, "fixstatus").toString(); + } } catch (ParseException e) { formatter.printHelp("BenchmarkCrawlerVerification", options); throw new RuntimeException("Error parsing arguments: ", e); @@ -740,12 +807,16 @@ public void execute() throws MojoExecutionException, MojoFailureException { mainArgs.add("-f"); mainArgs.add(this.pluginFilenameParam); if (this.outputDirectory != null) { - mainArgs.add("-d"); + mainArgs.add("-o"); mainArgs.add(this.outputDirectory); } - if (this.beforeFixOutputDirectory != null) { + if (this.unfixedSourceDirectory != null) { mainArgs.add("-b"); - mainArgs.add(this.beforeFixOutputDirectory); + mainArgs.add(this.unfixedSourceDirectory); + } + if (this.fixedSourceDirectory != null) { + mainArgs.add("-a"); + mainArgs.add(this.fixedSourceDirectory); } if (this.pluginTestCaseNameParam != null) { mainArgs.add("-n"); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java index d9536b0b..cafadd77 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java @@ -21,9 +21,18 @@ @XmlRootElement public class VerifyFixOutput { + private boolean wasNotVerfiable; private boolean wasExploited; private boolean wasBroken; + public boolean isWasNotVerfiable() { + return wasNotVerfiable; + } + + public void setWasNotVerfiable(boolean wasNotVerfiable) { + this.wasNotVerfiable = wasNotVerfiable; + } + public boolean isWasExploited() { return wasExploited; } From c657f838396b19f057581467a49c88263006e5cb Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 9 Jan 2025 15:30:12 +0000 Subject: [PATCH 26/28] Fixes to the fix verification feature to make it work for multiple testcases. --- .../entities/VerifyFixOutput.java | 96 +++++++++++++++++++ .../owasp/benchmarkutils/helpers/Utils.java | 2 +- .../tools/BenchmarkCrawler.java | 2 +- .../tools/BenchmarkCrawlerVerification.java | 40 ++++++-- .../benchmarkutils/tools/VerifyFixOutput.java | 51 ---------- 5 files changed, 131 insertions(+), 60 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixOutput.java delete mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixOutput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixOutput.java new file mode 100644 index 00000000..c23df234 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixOutput.java @@ -0,0 +1,96 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ +package org.owasp.benchmarkutils.entities; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class VerifyFixOutput { + private String testCaseName; + private ResponseInfo unfixedSafeResponseInfo; + private ResponseInfo unfixedAttackResponseInfo; + private ResponseInfo fixedSafeResponseInfo; + private ResponseInfo fixedAttackResponseInfo; + private boolean wasNotVerifiable; + private boolean wasExploited; + private boolean wasBroken; + + public String getTestCaseName() { + return testCaseName; + } + + public void setTestCaseName(String testCaseName) { + this.testCaseName = testCaseName; + } + + public ResponseInfo getUnfixedSafeResponseInfo() { + return unfixedSafeResponseInfo; + } + + public void setUnfixedSafeResponseInfo(ResponseInfo unfixedSafeResponseInfo) { + this.unfixedSafeResponseInfo = unfixedSafeResponseInfo; + } + + public ResponseInfo getUnfixedAttackResponseInfo() { + return unfixedAttackResponseInfo; + } + + public void setUnfixedAttackResponseInfo(ResponseInfo unfixedAttackResponseInfo) { + this.unfixedAttackResponseInfo = unfixedAttackResponseInfo; + } + + public ResponseInfo getFixedSafeResponseInfo() { + return fixedSafeResponseInfo; + } + + public void setFixedSafeResponseInfo(ResponseInfo fixedSafeResponseInfo) { + this.fixedSafeResponseInfo = fixedSafeResponseInfo; + } + + public ResponseInfo getFixedAttackResponseInfo() { + return fixedAttackResponseInfo; + } + + public void setFixedAttackResponseInfo(ResponseInfo fixedAttackResponseInfo) { + this.fixedAttackResponseInfo = fixedAttackResponseInfo; + } + + public boolean isWasNotVerifiable() { + return wasNotVerifiable; + } + + public void setWasNotVerifiable(boolean wasNotVerifiable) { + this.wasNotVerifiable = wasNotVerifiable; + } + + public boolean isWasExploited() { + return wasExploited; + } + + public void setWasExploited(boolean wasExploited) { + this.wasExploited = wasExploited; + } + + public boolean isWasBroken() { + return wasBroken; + } + + public void setWasBroken(boolean wasBroken) { + this.wasBroken = wasBroken; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 9ece0b0b..7e1c5b2b 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -61,10 +61,10 @@ import org.eclipse.persistence.oxm.MediaType; import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestSuite; +import org.owasp.benchmarkutils.entities.VerifyFixOutput; import org.owasp.benchmarkutils.tools.TestCaseRequestFileParseException; import org.owasp.benchmarkutils.tools.TestCaseVerificationResults; import org.owasp.benchmarkutils.tools.TestCaseVerificationResultsCollection; -import org.owasp.benchmarkutils.tools.VerifyFixOutput; import org.xml.sax.InputSource; import org.xml.sax.SAXException; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java index 8d934b4d..0be47e24 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -139,7 +139,7 @@ void load() { new Categories(categoriesFileStream); this.testSuite = Utils.parseHttpFile(this.theCrawlerFile); - System.out.println("Test suite: " + this.testSuite); + // System.out.println("Test suite: " + this.testSuite); Collections.sort( this.testSuite.getTestCases(), TestCase.getNameComparator()); // Probably not necessary diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index ae95e04f..f60f4c41 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -56,6 +56,7 @@ import org.owasp.benchmarkutils.entities.TestCaseSetup; import org.owasp.benchmarkutils.entities.TestCaseSetupException; import org.owasp.benchmarkutils.entities.TestSuite; +import org.owasp.benchmarkutils.entities.VerifyFixOutput; import org.owasp.benchmarkutils.helpers.Utils; import org.xml.sax.SAXException; @@ -115,6 +116,10 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private Map testCaseNameToTestCaseVerificationResultsMap = new HashMap<>(); + private List exploitedFixedTestcases = new ArrayList<>(); + private List brokenFixedTestcases = new ArrayList<>(); + private List notVerifiableFixedTestcases = new ArrayList<>(); + BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. // The theCrawlerFile has to be instantiated before a crawl can be done. @@ -133,6 +138,7 @@ protected void crawl(TestSuite testSuite) throws Exception { List results = new ArrayList(); + Files.createDirectories(Paths.get(getOutputDirectory())); final File FILE_NON_DISCRIMINATORY_LOG = new File(getOutputDirectory(), FILENAME_NON_DISCRIMINATORY_LOG); final File FILE_ERRORS_LOG = new File(getOutputDirectory(), FILENAME_ERRORS_LOG); @@ -391,6 +397,7 @@ protected void crawl(TestSuite testSuite) throws Exception { System.out.printf("Test case time measurements written to: %s%n", FILE_TIMES_LOG); RegressionTesting.printCrawlSummary(results); + printFixVerificationSummary(); System.out.println(); System.out.println(completionMessage); } @@ -399,6 +406,15 @@ protected void crawl(TestSuite testSuite) throws Exception { // cleanupSetups(setups); } + private void printFixVerificationSummary() { + System.out.println("Fix verification summary:"); + System.out.println(); + System.out.println("\tExploited fixed test cases:\t" + exploitedFixedTestcases.size()); + System.out.println("\tBroken fixed test cases:\t" + brokenFixedTestcases.size()); + System.out.println( + "\tNot verifiable fixed test cases:\t" + notVerifiableFixedTestcases.size()); + } + /** * @param testSuite * @throws Exception @@ -599,7 +615,7 @@ private boolean verifyFix( TestCaseVerificationResults beforeFixResults, TestCaseVerificationResults afterFixResults) { - boolean wasNotVerfiable = + boolean wasNotVerifiable = afterFixResults.getTestCase().isVulnerability() && afterFixResults.getTestCase().isUnverifiable() && afterFixResults.isPassed(); @@ -612,22 +628,32 @@ private boolean verifyFix( .getResponseToSafeValue() .getResponseString() .equals(afterFixResults.getResponseToSafeValue().getResponseString()); - if (wasNotVerfiable) { + + VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); + verifyFixOutput.setTestCaseName(afterFixResults.getTestCase().getName()); + verifyFixOutput.setUnfixedSafeResponseInfo(beforeFixResults.getResponseToSafeValue()); + verifyFixOutput.setUnfixedAttackResponseInfo(beforeFixResults.getResponseToAttackValue()); + verifyFixOutput.setFixedSafeResponseInfo(afterFixResults.getResponseToSafeValue()); + verifyFixOutput.setFixedAttackResponseInfo(afterFixResults.getResponseToAttackValue()); + verifyFixOutput.setWasNotVerifiable(wasNotVerifiable); + verifyFixOutput.setWasExploited(wasExploited); + verifyFixOutput.setWasBroken(wasBroken); + + if (wasNotVerifiable) { System.out.println("NOT FIXED: Vulnerability could not be verified"); + notVerifiableFixedTestcases.add(verifyFixOutput); } if (wasExploited) { System.out.println("NOT FIXED: Vulnerability was exploited"); + exploitedFixedTestcases.add(verifyFixOutput); } if (wasBroken) { System.out.println("NOT FIXED: Functionality was broken"); + brokenFixedTestcases.add(verifyFixOutput); } File verifyFixResultFile = new File(getOutputDirectory(), FILENAME_VERIFY_FIX_RESULT); try (BufferedWriter writer = new BufferedWriter(new FileWriter(verifyFixResultFile))) { - VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); - verifyFixOutput.setWasNotVerfiable(wasNotVerfiable); - verifyFixOutput.setWasExploited(wasExploited); - verifyFixOutput.setWasBroken(wasBroken); String output = Utils.objectToJson(verifyFixOutput); // System.out.println(output); writer.write(output); @@ -640,7 +666,7 @@ private boolean verifyFix( e.printStackTrace(); } - return !wasNotVerfiable && !wasExploited && !wasBroken; + return !wasNotVerifiable && !wasExploited && !wasBroken; } private boolean verifyFixes( diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java deleted file mode 100644 index cafadd77..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/VerifyFixOutput.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * OWASP Benchmark Project - * - *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For - * details, please see https://owasp.org/www-project-benchmark/. - * - *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms - * of the GNU General Public License as published by the Free Software Foundation, version 2. - * - *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * @author David Anderson - * @created 2024 - */ -package org.owasp.benchmarkutils.tools; - -import javax.xml.bind.annotation.XmlRootElement; - -@XmlRootElement -public class VerifyFixOutput { - private boolean wasNotVerfiable; - private boolean wasExploited; - private boolean wasBroken; - - public boolean isWasNotVerfiable() { - return wasNotVerfiable; - } - - public void setWasNotVerfiable(boolean wasNotVerfiable) { - this.wasNotVerfiable = wasNotVerfiable; - } - - public boolean isWasExploited() { - return wasExploited; - } - - public void setWasExploited(boolean wasExploited) { - this.wasExploited = wasExploited; - } - - public boolean isWasBroken() { - return wasBroken; - } - - public void setWasBroken(boolean wasBroken) { - this.wasBroken = wasBroken; - } -} From 0c9a3e2f6eb85639df3ac86c35e4142eae358bcb Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 14 Jan 2025 08:35:57 +0000 Subject: [PATCH 27/28] More changes to the fix verification feature to address feedback from Dave Wichers. --- .../entities/VerifyFixesOutput.java | 35 +++ .../owasp/benchmarkutils/helpers/Utils.java | 4 +- .../tools/BenchmarkCrawlerVerification.java | 239 ++++++++++++------ 3 files changed, 206 insertions(+), 72 deletions(-) create mode 100644 library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixesOutput.java diff --git a/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixesOutput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixesOutput.java new file mode 100644 index 00000000..6671ca72 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/VerifyFixesOutput.java @@ -0,0 +1,35 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * @author David Anderson + * @created 2024 + */ +package org.owasp.benchmarkutils.entities; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class VerifyFixesOutput { + private List list = new ArrayList<>(); + + public List getList() { + return this.list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java index 7e1c5b2b..ccb1b0db 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/helpers/Utils.java @@ -62,6 +62,7 @@ import org.owasp.benchmarkutils.entities.ResponseInfo; import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.entities.VerifyFixOutput; +import org.owasp.benchmarkutils.entities.VerifyFixesOutput; import org.owasp.benchmarkutils.tools.TestCaseRequestFileParseException; import org.owasp.benchmarkutils.tools.TestCaseVerificationResults; import org.owasp.benchmarkutils.tools.TestCaseVerificationResultsCollection; @@ -418,7 +419,8 @@ public static String objectToJson(Object object) throws JAXBException { TestSuite.class, TestCaseVerificationResults.class, TestCaseVerificationResultsCollection.class, - VerifyFixOutput.class + VerifyFixOutput.class, + VerifyFixesOutput.class }; JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext( diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index f60f4c41..6c6fc9ee 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -57,6 +57,7 @@ import org.owasp.benchmarkutils.entities.TestCaseSetupException; import org.owasp.benchmarkutils.entities.TestSuite; import org.owasp.benchmarkutils.entities.VerifyFixOutput; +import org.owasp.benchmarkutils.entities.VerifyFixesOutput; import org.owasp.benchmarkutils.helpers.Utils; import org.xml.sax.SAXException; @@ -116,9 +117,16 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private Map testCaseNameToTestCaseVerificationResultsMap = new HashMap<>(); - private List exploitedFixedTestcases = new ArrayList<>(); - private List brokenFixedTestcases = new ArrayList<>(); - private List notVerifiableFixedTestcases = new ArrayList<>(); + private List vulnerableTestcases = new ArrayList<>(); + private List notVulnerableTestcases = new ArrayList<>(); + private List notVerifiableModifiedVulnerableTestcases = new ArrayList<>(); + private List exploitedModifiedVulnerableTestcases = new ArrayList<>(); + private List brokenModifiedVulnerableTestcases = new ArrayList<>(); + private List notVerifiableModifiedNotVulnerableTestcases = new ArrayList<>(); + private List exploitedModifiedNotVulnerableTestcases = new ArrayList<>(); + private List brokenModifiedNotVulnerableTestcases = new ArrayList<>(); + + private List verifyFixesOutputList = new ArrayList<>(); BenchmarkCrawlerVerification() { // A default constructor required to support Maven plugin API. @@ -394,10 +402,30 @@ protected void crawl(TestSuite testSuite) throws Exception { FILE_UNVERIFIABLE_LOG); } + VerifyFixesOutput verifyFixesOutput = new VerifyFixesOutput(); + verifyFixesOutput.setList(verifyFixesOutputList); + + File verifyFixResultFile = new File(getOutputDirectory(), FILENAME_VERIFY_FIX_RESULT); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(verifyFixResultFile))) { + String output = Utils.objectToJson(verifyFixesOutput); + // System.out.println(output); + writer.write(output); + } catch (IOException e) { + System.out.println( + "ERROR: Could not write VerifyFixOutputList to file " + + verifyFixResultFile); + e.printStackTrace(); + } catch (JAXBException e) { + System.out.println("ERROR: Could not marshall VerifyFixOutputList to JSON"); + e.printStackTrace(); + } + System.out.printf("Test case time measurements written to: %s%n", FILE_TIMES_LOG); RegressionTesting.printCrawlSummary(results); - printFixVerificationSummary(); + if (Boolean.parseBoolean(verifyFixed)) { + printFixVerificationSummary(); + } System.out.println(); System.out.println(completionMessage); } @@ -406,13 +434,51 @@ protected void crawl(TestSuite testSuite) throws Exception { // cleanupSetups(setups); } + private boolean isTestCaseModified( + String unfixedSourceDirectory, String fixedSourceDirectory, String testCaseName) + throws IOException { + // FIXME: Generalize this so it can support languages other than Java and multiple + // source files per testcase. + String unfixedSourceFile = + Paths.get(unfixedSourceDirectory, testCaseName).toString() + ".java"; + String fixedSourceFile = Paths.get(fixedSourceDirectory, testCaseName).toString() + ".java"; + String unfixedSourceFileContents = + new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); + String fixedSourceFileContents = new String(Files.readAllBytes(Paths.get(fixedSourceFile))); + + // Skip testcase in verifyFixed mode if fixed source code is unchanged. + return !unfixedSourceFileContents.equals(fixedSourceFileContents); + } + private void printFixVerificationSummary() { - System.out.println("Fix verification summary:"); System.out.println(); - System.out.println("\tExploited fixed test cases:\t" + exploitedFixedTestcases.size()); - System.out.println("\tBroken fixed test cases:\t" + brokenFixedTestcases.size()); + System.out.println("Fix verification summary"); + System.out.println(); + System.out.println("Total vulnerable test cases: " + vulnerableTestcases.size()); + System.out.println( + "\tProperly fixed (not exploitable):\t" + + (vulnerableTestcases.size() + - exploitedModifiedVulnerableTestcases.size())); System.out.println( - "\tNot verifiable fixed test cases:\t" + notVerifiableFixedTestcases.size()); + "\tNot correctly fixed (still exploitable):\t" + + exploitedModifiedVulnerableTestcases.size()); + System.out.println( + "\tNot auto-verifiable (can't tell if exploitable):\t" + + notVerifiableModifiedVulnerableTestcases.size()); + System.out.println( + "\tFunctionality broken/modified:\t" + brokenModifiedVulnerableTestcases.size()); + System.out.println(); + System.out.println("Total not vulnerable test cases: " + notVulnerableTestcases.size()); + System.out.println( + "\tStill not exploitable:\t" + + (notVulnerableTestcases.size() + - exploitedModifiedNotVulnerableTestcases.size())); + System.out.println("\tNow exploitable:\t" + exploitedModifiedNotVulnerableTestcases.size()); + System.out.println( + "\tNot auto-verifiable (can't tell if exploitable):\t" + + notVerifiableModifiedNotVulnerableTestcases.size()); + System.out.println( + "\tFunctionality broken/modified:\t" + brokenModifiedNotVulnerableTestcases.size()); } /** @@ -538,27 +604,39 @@ protected void handleResponse(TestCaseVerificationResults results) .getTestCase() .getName() .equals(fixedResults.getTestCase().getName())) { - // FIXME: Generalize this so it can support languages other than Java and multiple - // source files per testcase. - String unfixedSourceFile = - Paths.get(unfixedSourceDirectory, unfixedResults.getTestCase().getName()) - .toString() - + ".java"; - String fixedSourceFile = - Paths.get(fixedSourceDirectory, fixedResults.getTestCase().getName()) - .toString() - + ".java"; - String unfixedSourceFileContents = - new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); - String fixedSourceFileContents = - new String(Files.readAllBytes(Paths.get(fixedSourceFile))); - if (!unfixedSourceFileContents.equals(fixedSourceFileContents)) { + + // // FIXME: Generalize this so it can support languages other than Java and + // multiple + // // source files per testcase. + // String unfixedSourceFile = + // Paths.get(unfixedSourceDirectory, unfixedResults.getTestCase().getName()) + // .toString() + // + ".java"; + // String fixedSourceFile = + // Paths.get(fixedSourceDirectory, fixedResults.getTestCase().getName()) + // .toString() + // + ".java"; + // String unfixedSourceFileContents = + // new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); + // String fixedSourceFileContents = + // new String(Files.readAllBytes(Paths.get(fixedSourceFile))); + + // // Skip testcase in verifyFixed mode if fixed source code is unchanged. + // if (Boolean.parseBoolean(verifyFixed) + // && unfixedSourceFileContents.equals(fixedSourceFileContents)) { + // // System.out.println( + // // "WARNING: Testcase " + // // + fixedResults.getTestCase().getName() + // // + " source file unmodified"); + // } else { + // verifyFix(unfixedResults, fixedResults); + // } + if (Boolean.parseBoolean(verifyFixed) + && isTestCaseModified( + unfixedSourceDirectory, + fixedSourceDirectory, + unfixedResults.getTestCase().getName())) { verifyFix(unfixedResults, fixedResults); - } else { - System.out.println( - "WARNING: Testcase " - + fixedResults.getTestCase().getName() - + " source file unmodified"); } } else { System.out.println( @@ -612,59 +690,78 @@ private TestCaseVerificationResultsCollection loadTestCaseVerificationResults( } private boolean verifyFix( - TestCaseVerificationResults beforeFixResults, - TestCaseVerificationResults afterFixResults) { + TestCaseVerificationResults unfixedResults, TestCaseVerificationResults fixedResults) { + + // DEBUG + try { + String unfixedResultsJson = Utils.objectToJson(unfixedResults); + System.out.println("unfixedResults JSON: " + unfixedResultsJson); + } catch (Exception e) { + e.printStackTrace(); + } - boolean wasNotVerifiable = - afterFixResults.getTestCase().isVulnerability() - && afterFixResults.getTestCase().isUnverifiable() - && afterFixResults.isPassed(); + boolean isVulnerable = fixedResults.getTestCase().isVulnerability(); + // boolean wasNotVerifiable = + // fixedResults.getTestCase().isVulnerability() + // && fixedResults.getTestCase().isUnverifiable() + // && fixedResults.isPassed(); + boolean wasNotVerifiable = fixedResults.getTestCase().isUnverifiable(); boolean wasExploited = - afterFixResults.getTestCase().isVulnerability() - && !afterFixResults.getTestCase().isUnverifiable() - && afterFixResults.isPassed(); - boolean wasBroken = - !beforeFixResults - .getResponseToSafeValue() - .getResponseString() - .equals(afterFixResults.getResponseToSafeValue().getResponseString()); + fixedResults.getTestCase().isVulnerability() + && !fixedResults.getTestCase().isUnverifiable() + && fixedResults.isPassed(); + boolean wasBroken = false; + if (fixedResults.getTestCase().isUnverifiable()) { + wasBroken = false; + } else { + // There will be no safe response info if testcase is not verifiable. + wasBroken = + !unfixedResults + .getResponseToSafeValue() + .getResponseString() + .equals(fixedResults.getResponseToSafeValue().getResponseString()); + } VerifyFixOutput verifyFixOutput = new VerifyFixOutput(); - verifyFixOutput.setTestCaseName(afterFixResults.getTestCase().getName()); - verifyFixOutput.setUnfixedSafeResponseInfo(beforeFixResults.getResponseToSafeValue()); - verifyFixOutput.setUnfixedAttackResponseInfo(beforeFixResults.getResponseToAttackValue()); - verifyFixOutput.setFixedSafeResponseInfo(afterFixResults.getResponseToSafeValue()); - verifyFixOutput.setFixedAttackResponseInfo(afterFixResults.getResponseToAttackValue()); + verifyFixOutput.setTestCaseName(fixedResults.getTestCase().getName()); + verifyFixOutput.setUnfixedSafeResponseInfo(unfixedResults.getResponseToSafeValue()); + verifyFixOutput.setUnfixedAttackResponseInfo(unfixedResults.getResponseToAttackValue()); + verifyFixOutput.setFixedSafeResponseInfo(fixedResults.getResponseToSafeValue()); + verifyFixOutput.setFixedAttackResponseInfo(fixedResults.getResponseToAttackValue()); verifyFixOutput.setWasNotVerifiable(wasNotVerifiable); verifyFixOutput.setWasExploited(wasExploited); verifyFixOutput.setWasBroken(wasBroken); - if (wasNotVerifiable) { - System.out.println("NOT FIXED: Vulnerability could not be verified"); - notVerifiableFixedTestcases.add(verifyFixOutput); - } - if (wasExploited) { - System.out.println("NOT FIXED: Vulnerability was exploited"); - exploitedFixedTestcases.add(verifyFixOutput); - } - if (wasBroken) { - System.out.println("NOT FIXED: Functionality was broken"); - brokenFixedTestcases.add(verifyFixOutput); + if (isVulnerable) { + vulnerableTestcases.add(verifyFixOutput); + if (wasNotVerifiable) { + System.out.println("NOT FIXED: Vulnerability could not be verified"); + notVerifiableModifiedVulnerableTestcases.add(verifyFixOutput); + } else { + if (wasExploited) { + System.out.println("NOT FIXED: Vulnerability was exploited"); + exploitedModifiedVulnerableTestcases.add(verifyFixOutput); + } + if (wasBroken) { + System.out.println("NOT FIXED: Functionality was broken"); + brokenModifiedVulnerableTestcases.add(verifyFixOutput); + } + } + } else { + notVulnerableTestcases.add(verifyFixOutput); + if (wasNotVerifiable) { + notVerifiableModifiedNotVulnerableTestcases.add(verifyFixOutput); + } else { + if (wasExploited) { + exploitedModifiedNotVulnerableTestcases.add(verifyFixOutput); + } + if (wasBroken) { + brokenModifiedNotVulnerableTestcases.add(verifyFixOutput); + } + } } - File verifyFixResultFile = new File(getOutputDirectory(), FILENAME_VERIFY_FIX_RESULT); - try (BufferedWriter writer = new BufferedWriter(new FileWriter(verifyFixResultFile))) { - String output = Utils.objectToJson(verifyFixOutput); - // System.out.println(output); - writer.write(output); - } catch (IOException e) { - System.out.println( - "ERROR: Could not write VerifyFixOutput to file " + verifyFixResultFile); - e.printStackTrace(); - } catch (JAXBException e) { - System.out.println("ERROR: Could not marshall VerifyFixOutput to JSON"); - e.printStackTrace(); - } + verifyFixesOutputList.add(verifyFixOutput); return !wasNotVerifiable && !wasExploited && !wasBroken; } From b9bddf668562853ef7c5095a1dd6c3056447d820 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 21 Jan 2025 10:06:26 +0000 Subject: [PATCH 28/28] The verification crawler in verifyFixed mode now does not execute a test if the fixed testcase code is identical to the unfixed code. --- .../tools/BenchmarkCrawlerVerification.java | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java index 6c6fc9ee..1ed37949 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -176,16 +176,28 @@ protected void crawl(TestSuite testSuite) throws Exception { tLogger = tl; List filteredList; - if (selectedTestCaseName != null) { + + if (Boolean.parseBoolean(verifyFixed)) { filteredList = testSuite.getTestCases().stream() .filter( testCase -> - testCase.getName().equals(selectedTestCaseName)) + !isTestCaseIdentical( + unfixedSourceDirectory, + fixedSourceDirectory, + testCase.getName())) .collect(Collectors.toList()); } else { filteredList = testSuite.getTestCases(); } + if (selectedTestCaseName != null) { + filteredList = + filteredList.stream() + .filter( + testCase -> + testCase.getName().equals(selectedTestCaseName)) + .collect(Collectors.toList()); + } for (TestCase testCase : filteredList) { // if (this.selectedTestCaseName != null) { @@ -434,20 +446,40 @@ protected void crawl(TestSuite testSuite) throws Exception { // cleanupSetups(setups); } - private boolean isTestCaseModified( - String unfixedSourceDirectory, String fixedSourceDirectory, String testCaseName) - throws IOException { + private boolean isTestCaseIdentical( + String unfixedSourceDirectory, String fixedSourceDirectory, String testCaseName) { // FIXME: Generalize this so it can support languages other than Java and multiple // source files per testcase. String unfixedSourceFile = Paths.get(unfixedSourceDirectory, testCaseName).toString() + ".java"; String fixedSourceFile = Paths.get(fixedSourceDirectory, testCaseName).toString() + ".java"; - String unfixedSourceFileContents = - new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); - String fixedSourceFileContents = new String(Files.readAllBytes(Paths.get(fixedSourceFile))); + String unfixedSourceFileContents = null; + try { + unfixedSourceFileContents = + new String(Files.readAllBytes(Paths.get(unfixedSourceFile))); + } catch (IOException e) { + System.out.println("ERROR: Could not read testcase source file " + unfixedSourceFile); + e.printStackTrace(); + } + String fixedSourceFileContents = null; + try { + fixedSourceFileContents = new String(Files.readAllBytes(Paths.get(fixedSourceFile))); + } catch (IOException e) { + System.out.println("ERROR: Could not read testcase source file " + fixedSourceFile); + e.printStackTrace(); + } + // DEBUG + // System.out.println( + // testCaseName + // + ": isTestCaseIdentical() returning " + // + (unfixedSourceFileContents != null + // && fixedSourceFileContents != null + // && unfixedSourceFileContents.equals(fixedSourceFileContents))); // Skip testcase in verifyFixed mode if fixed source code is unchanged. - return !unfixedSourceFileContents.equals(fixedSourceFileContents); + return unfixedSourceFileContents != null + && fixedSourceFileContents != null + && unfixedSourceFileContents.equals(fixedSourceFileContents); } private void printFixVerificationSummary() { @@ -583,7 +615,7 @@ private void log(ResponseInfo responseInfo) throws IOException { * @throws LoggerConfigurationException */ protected void handleResponse(TestCaseVerificationResults results) - throws FileNotFoundException, IOException, LoggerConfigurationException { + throws FileNotFoundException, LoggerConfigurationException { // Check to see if this specific test case has a specified expected response value. // If so, run it through verification using it's specific attackSuccessIndicator. @@ -631,13 +663,14 @@ protected void handleResponse(TestCaseVerificationResults results) // } else { // verifyFix(unfixedResults, fixedResults); // } - if (Boolean.parseBoolean(verifyFixed) - && isTestCaseModified( - unfixedSourceDirectory, - fixedSourceDirectory, - unfixedResults.getTestCase().getName())) { - verifyFix(unfixedResults, fixedResults); - } + // if (Boolean.parseBoolean(verifyFixed) + // && !isTestCaseIdentical( + // unfixedSourceDirectory, + // fixedSourceDirectory, + // unfixedResults.getTestCase().getName())) { + // verifyFix(unfixedResults, fixedResults); + // } + verifyFix(unfixedResults, fixedResults); } else { System.out.println( "WARNING: After fix testcase is " @@ -732,6 +765,14 @@ private boolean verifyFix( verifyFixOutput.setWasExploited(wasExploited); verifyFixOutput.setWasBroken(wasBroken); + // DEBUG + try { + String verifyFixOutputJson = Utils.objectToJson(verifyFixOutput); + System.out.println("verifyFixOutput JSON: " + verifyFixOutputJson); + } catch (Exception e) { + e.printStackTrace(); + } + if (isVulnerable) { vulnerableTestcases.add(verifyFixOutput); if (wasNotVerifiable) {