diff --git a/.mvn/jvm.config b/.mvn/jvm.config index 630d3320..1b1e6402 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1,2 +1,3 @@ --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-opens java.base/java.lang=ALL-UNNAMED 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/library/pom.xml b/library/pom.xml index 81c5c79f..c009e240 100755 --- a/library/pom.xml +++ b/library/pom.xml @@ -12,6 +12,63 @@ 1.3 + + + + commons-lang + commons-lang + + + + com.google.guava + guava + + + + + + org.eclipse.persistence + org.eclipse.persistence.moxy + 2.7.14 + + + + javax.validation + validation-api + 2.0.1.Final + + + + javax.xml.bind + jaxb-api + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.apache.httpcomponents.core5 + httpcore5 + + + + org.eclipse.persistence + org.eclipse.persistence.core + + + + xml-apis + xml-apis + + + + benchmarkutils @@ -19,6 +76,12 @@ ${basedir}/src/main/resources + + ${basedir}/src/main/java + + **/*.java + + 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..56fdd84d --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliArgExecutableTestCaseInput.java @@ -0,0 +1,130 @@ +/** + * 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.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) { + // Copy the given list so setSafe() does not affect other CliArgExecutableTestCaseInput + // objects. + this.args = new ArrayList<>(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()); + ArrayList argsCopy = new ArrayList<>(); + for (RequestVariable arg : args) { + RequestVariable argCopy = new RequestVariable(arg); + argCopy.setSafe(false); + argsCopy.add(argCopy); + } + return new CliRequest(getCommand(), argsCopy, null); + } + + public CliRequest buildSafeRequest() { + ArrayList argsCopy = new ArrayList<>(); + for (RequestVariable arg : args) { + RequestVariable argCopy = new RequestVariable(arg); + argCopy.setSafe(true); + argsCopy.add(argCopy); + } + return new CliRequest(getCommand(), argsCopy, null); + } + + 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..9009aed8 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliFileExecutableTestCaseInput.java @@ -0,0 +1,87 @@ +/** + * 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; +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(), null); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), getFileArgs(), null); + } + + 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..61b59352 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliRequest.java @@ -0,0 +1,99 @@ +/** + * 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.Arrays; +import java.util.List; + +public class CliRequest { + private String command; + + private List args; + + private RequestVariable stdinData; + + public CliRequest() {} + + public CliRequest(String command, List args, RequestVariable stdinData) { + super(); + this.command = command; + if (args == null) { + this.args = new ArrayList(); + } else { + this.args = new ArrayList(args); + } + this.stdinData = stdinData; + } + + // 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; + } + + public void setCommand(String command) { + this.command = command; + } + + public List getArgs() { + return args; + } + + public void setArgs(List args) { + if (args == null) { + this.args = new ArrayList(); + } else { + this.args = new ArrayList(args); + } + } + + // public List getExecuteArgs() { + // List executeArgs = Arrays.asList(getCommand().split(" ")); + // executeArgs.addAll(getArgs()); + // 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()); + } + 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/CliResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java new file mode 100644 index 00000000..4fc4cdcb --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliResponseInfo.java @@ -0,0 +1,43 @@ +/** + * 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; + +public class CliResponseInfo extends ResponseInfo { + + private CliRequest request; + + public CliResponseInfo() { + // Default is this is a normal, non-attack response + super(); + } + + public CliResponseInfo(boolean attackRequest) { + super(attackRequest); + } + + @XmlElement(required = true) + public CliRequest getRequest() { + return request; + } + + public void setRequest(CliRequest request) { + this.request = 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 new file mode 100644 index 00000000..b4a099c7 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/CliTestCase.java @@ -0,0 +1,70 @@ +/** + * 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; + +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/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java new file mode 100644 index 00000000..eb886075 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ContentFormatEnum.java @@ -0,0 +1,27 @@ +/** + * 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.*; + +@XmlType(name = "ContentFormat") +@XmlEnum +public enum ContentFormatEnum { + JSON, + XML; +} 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..1a18ff6a --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ExecutableTestCaseInput.java @@ -0,0 +1,103 @@ +/** + * 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; +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/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java new file mode 100644 index 00000000..2327b926 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/FileCopyConfig.java @@ -0,0 +1,63 @@ +/** + * 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; +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); + } + } + + public void close() throws TestCaseSetupException { + // Do nothing + } + + @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/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java new file mode 100644 index 00000000..e9c0cb58 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpClientConfig.java @@ -0,0 +1,86 @@ +/** + * 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; +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; +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; + +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(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(); + + // 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); + 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(); + } + + 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); + } + } + } + + 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/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java new file mode 100644 index 00000000..cdc744a3 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpGetTestCaseInput.java @@ -0,0 +1,53 @@ +/** + * 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; +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(""); + 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(HttpUriRequestBase request) { + // No request body + } + + @Override + HttpUriRequestBase createRequestInstance(String url) { + HttpGet httpGet = new HttpGet(url); + return 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 new file mode 100644 index 00000000..75ca8724 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpPostTestCaseInput.java @@ -0,0 +1,72 @@ +/** + * 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; +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() { + 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(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); + ((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/HttpResponseInfo.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java new file mode 100644 index 00000000..ad00d190 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpResponseInfo.java @@ -0,0 +1,65 @@ +/** + * 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; + +public class HttpResponseInfo extends ResponseInfo { + + // private HttpUriRequest requestBase; + + private String method; + + private String uri; + + public HttpResponseInfo() { + // Default is this is a normal, non-attack response + super(); + } + + 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 String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } +} 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..2ce98cd0 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCase.java @@ -0,0 +1,66 @@ +/** + * 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 { + + // @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/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java new file mode 100644 index 00000000..51a932be --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/HttpTestCaseInput.java @@ -0,0 +1,397 @@ +/** + * 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; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +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.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; +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; + +public abstract class HttpTestCaseInput extends TestCaseInput { + + private String url; + + protected ContentFormatEnum contentFormat; + + private String queryString; + + private List formParameters; + + private List getParameters; + + private List cookies; + + private List headers; + + // 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(); + } + + @XmlElement(name = "url", required = true) + @NotNull + public String getUrl() { + return url; + } + + public String getQueryString() { + return queryString; + } + + @XmlElementWrapper(name = "formParams", required = false) + @XmlElement(name = "formParam", required = false) + @NotNull + public List getFormParameters() { + return formParameters; + } + + @XmlElementWrapper(name = "getParams", required = false) + @XmlElement(name = "getParam", required = false) + @NotNull + public List getGetParameters() { + return getParameters; + } + + @XmlElementWrapper(name = "cookies", required = false) + @XmlElement(name = "cookie", required = false) + @NotNull + public List getCookies() { + return cookies; + } + + @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; + } + + 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 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; + } + + public void setContentFormat(ContentFormatEnum contentFormat) { + this.contentFormat = contentFormat; + } + + /** Defines what parameters in the body will be sent. */ + abstract void buildBodyParameters(HttpUriRequestBase request); + + /** Defines what cookies will be sent. */ + 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")); + } + } + + /** Defines what headers will be sent. */ + 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") + 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(); + // } + // + // 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 + * 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) { + // 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); + 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; + System.out.println((isPost ? "POST " : "GET ") + uri); + + 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 + * that can build an actual HttpUriRequest. + * + * @return + */ + public HttpUriRequestBase buildRequest() { + buildQueryString(); + 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(); + } + + public HttpUriRequestBase 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 : getGetParameters()) { + getParam.setSafe(isSafe); + } + for (RequestVariable formParam : getFormParameters()) { + 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(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(); + + // 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); + 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(); + } + + @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/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCase.java similarity index 95% 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 bd50f3bc..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,7 +15,7 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java similarity index 83% rename from plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java index 1ed2df56..03df0e95 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/JerseyTestCaseRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/JerseyTestCaseInput.java @@ -10,35 +10,25 @@ * *

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 + * PURPOSE. See the GNU General Public License for more details. * - * @author Juan Gama - * @created 2017 + * @author David Anderson + * @created 2024 */ -package org.owasp.benchmarkutils.tools; +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; -import org.owasp.benchmarkutils.helpers.RequestVariable; -@XmlDiscriminatorValue("JERSEYWS") -public class JerseyTestCaseRequest extends AbstractTestCaseRequest { - - public JerseyTestCaseRequest() {} +@XmlDiscriminatorValue("Jersey") +// @XmlType(name = "HttpPostTestCaseInput") +public class JerseyTestCaseInput extends HttpTestCaseInput { @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; + setQueryString(""); } @Override @@ -65,7 +55,7 @@ void buildCookies(HttpUriRequestBase request) { @Override void buildBodyParameters(HttpUriRequestBase request) { String params = ""; - for (RequestVariable field : getFormParams()) { + for (RequestVariable field : getFormParameters()) { String name = field.getName(); String value = field.getValue(); params += "<" + name + ">" + escapeXML(value) + ""; @@ -84,4 +74,12 @@ private static String escapeXML(String value) { 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/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java similarity index 83% 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 17bd2d78..ee1526b8 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/RequestVariable.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/RequestVariable.java @@ -13,13 +13,14 @@ * PURPOSE. See the GNU General Public License for more details. * * @author David Anderson - * @created 2021 + * @created 2024 */ -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; @@ -61,6 +62,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() { @@ -121,6 +135,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 55% 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 b4e0b4a3..14f1ae1f 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ResponseInfo.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ResponseInfo.java @@ -12,33 +12,51 @@ * 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 + * @author David Anderson + * @created 2024 */ -package org.owasp.benchmarkutils.tools; +package org.owasp.benchmarkutils.entities; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +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; -class ResponseInfo { +@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 + private boolean isAttackResponse = false; // Default private String responseString; - private int seconds; private int statusCode; - private HttpUriRequest requestBase; + private int seconds; - public String getResponseString() { - return responseString; + public ResponseInfo() { + // Default is this is a normal, non-attack response } - public void setResponseString(String responseString) { - this.responseString = responseString; + public ResponseInfo(boolean attackRequest) { + this.isAttackResponse = attackRequest; } - public int getTimeInSeconds() { - return seconds; + public void setIsAttackResponse(boolean isAttackResponse) { + this.isAttackResponse = isAttackResponse; } - public void setTimeInSeconds(int seconds) { - this.seconds = seconds; + @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() { @@ -49,11 +67,11 @@ public void setStatusCode(int statusCode) { this.statusCode = statusCode; } - public HttpUriRequest getRequestBase() { - return requestBase; + public int getTimeInSeconds() { + return seconds; } - public void setRequestBase(HttpUriRequest request) { - this.requestBase = request; + public void setTimeInSeconds(int seconds) { + this.seconds = seconds; } } 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 95% 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 defa0567..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,7 +15,7 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java similarity index 64% rename from plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java index b4e7889f..9db50238 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/ServletTestCaseRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/ServletTestCaseInput.java @@ -10,14 +10,13 @@ * *

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 + * PURPOSE. See the GNU General Public License for more details. * - * @author Juan Gama - * @created 2017 + * @author David Anderson + * @created 2024 */ -package org.owasp.benchmarkutils.tools; +package org.owasp.benchmarkutils.entities; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -26,45 +25,25 @@ import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.core5.http.NameValuePair; 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 { +@XmlDiscriminatorValue("Servlet") +public class ServletTestCaseInput extends HttpTestCaseInput { - public ServletTestCaseRequest() {} - - @SuppressWarnings("deprecation") @Override void buildQueryString() { - setQuery(""); + setQueryString(""); boolean first = true; - for (RequestVariable field : getGetParams()) { + for (RequestVariable field : getGetParameters()) { if (first) { - setQuery("?"); + setQueryString("?"); first = false; } else { - setQuery(getQuery() + "&"); + setQueryString(getQueryString() + "&"); } 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); + setQueryString(getQueryString() + (name + "=" + urlEncode(value))); } } @@ -72,7 +51,8 @@ HttpUriRequestBase createRequestInstance(String URL) { 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 + request.addHeader( + "Content-Type", "application/x-www-form-urlencoded"); // Works for both though for (RequestVariable header : getHeaders()) { String name = header.getName(); @@ -82,7 +62,6 @@ void buildHeaders(HttpUriRequestBase request) { } } - @SuppressWarnings("deprecation") @Override void buildCookies(HttpUriRequestBase request) { for (RequestVariable cookie : getCookies()) { @@ -91,14 +70,14 @@ void buildCookies(HttpUriRequestBase request) { // 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")); + request.addHeader("Cookie", name + "=" + urlEncode(value).replace("+", "%20")); } } @Override void buildBodyParameters(HttpUriRequestBase request) { List fields = new ArrayList<>(); - for (RequestVariable formParam : getFormParams()) { + for (RequestVariable formParam : getFormParameters()) { fields.add(formParam.getNameValuePair()); } @@ -107,4 +86,15 @@ void buildBodyParameters(HttpUriRequestBase request) { 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/plugin/src/main/java/org/owasp/benchmarkutils/helpers/SpringTestCase.java b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCase.java similarity index 95% 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 147cd025..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,7 +15,7 @@ * @author David Anderson * @created 2021 */ -package org.owasp.benchmarkutils.helpers; +package org.owasp.benchmarkutils.entities; import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java similarity index 83% rename from plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java rename to library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java index a583c5d4..6bb6d215 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/SpringTestCaseRequest.java +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/SpringTestCaseInput.java @@ -10,35 +10,25 @@ * *

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 + * PURPOSE. See the GNU General Public License for more details. * - * @author Juan Gama - * @created 2017 + * @author David Anderson + * @created 2024 */ -package org.owasp.benchmarkutils.tools; +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; -import org.owasp.benchmarkutils.helpers.RequestVariable; -@XmlDiscriminatorValue("SPRINGWS") -public class SpringTestCaseRequest extends AbstractTestCaseRequest { - - public SpringTestCaseRequest() {} +@XmlDiscriminatorValue("Spring") +// @XmlType(name = "HttpPostTestCaseInput") +public class SpringTestCaseInput extends HttpTestCaseInput { @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; + setQueryString(""); } @Override @@ -68,7 +58,7 @@ void buildCookies(HttpUriRequestBase request) { void buildBodyParameters(HttpUriRequestBase request) { boolean first = true; String params = "{"; - for (RequestVariable field : getFormParams()) { + for (RequestVariable field : getFormParameters()) { String name = field.getName(); String value = field.getValue(); // System.out.println(name+"="+value); @@ -83,4 +73,12 @@ void buildBodyParameters(HttpUriRequestBase request) { 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/Sqlite3Config.java b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java new file mode 100644 index 00000000..e8413bec --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/Sqlite3Config.java @@ -0,0 +1,93 @@ +/** + * 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; +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); + } + } + + public void close() throws TestCaseSetupException { + // Do nothing + } + + @XmlAttribute(name = "script") + @NotNull + public String getScriptFile() { + return scriptFile; + } + + public void setScriptFile(String scriptFile) { + this.scriptFile = scriptFile; + } +} 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..e7e21e7d --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/StdinExecutableTestCaseInput.java @@ -0,0 +1,92 @@ +/** + * 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; +import javax.xml.bind.annotation.XmlElement; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; + +@XmlDiscriminatorValue("Stdin") +public class StdinExecutableTestCaseInput extends ExecutableTestCaseInput { + + RequestVariable stdinData; + + @XmlElement(name = "stdinData", required = true) + @NotNull + public RequestVariable getStdinData() { + return stdinData; + } + + public void setStdinData(RequestVariable stdinData) { + this.stdinData = stdinData; + } + + 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()); + + stdinData.setSafe(false); + return new CliRequest(getCommand(), null, getStdinData()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), null, 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/TcpSocketExecutableTestCaseInput.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java new file mode 100644 index 00000000..afc537f6 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TcpSocketExecutableTestCaseInput.java @@ -0,0 +1,60 @@ +/** + * 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; + +@XmlDiscriminatorValue("TcpSocket") +public class TcpSocketExecutableTestCaseInput 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(), null, getTcpSocketData()); + } + + public CliRequest buildSafeRequest() { + setSafe(true); + return new CliRequest(getCommand(), null, 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..0ab03316 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCase.java @@ -0,0 +1,337 @@ +/** + * 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; +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.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 boolean isVulnerability; + + private String sourceFile; + private String sourceUIType; + private String dataflowFile = "none"; + private String sinkFile; + + private String templateFile; + private String UITemplateFile; + + private String notAutoverifiableReason; // Any value, e.g. "none" sets it notAutoverifiable + + private String verificationResult = "notYetKnown"; + + private TestCaseInput testCaseInput; + private String attackSuccessString; + + @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 TestExecutor safeTestExecutor; + // private TestExecutor attackTestExecutor; + + static final Pattern lastIntPattern = Pattern.compile("[^0-9]+([0-9]+)$"); + + public TestCase() { + super(); + } + + @XmlAttribute(name = "AttackSuccessIndicator") + public String getAttackSuccessString() { + return this.attackSuccessString; + } + + @XmlAttribute(name = "Category", required = true) + @XmlJavaTypeAdapter(CategoryAdapter.class) + @NotNull + public Category getCategory() { + return category; + } + + @XmlAttribute(name = "DataflowFile", required = true) + @NotNull + public String getDataflowFile() { + return dataflowFile; + } + + @XmlAttribute(name = "Name", required = true) + @NotNull + public String getName() { + return name; + } + + public int getNumber() { + return number; + } + + @XmlAttribute(name = "NotAutoverifiable") + public String getNotAutoverifiableReason() { + return notAutoverifiableReason; + } + + @XmlAttribute(name = "SinkFile", required = true) + @NotNull + public String getSinkFile() { + return sinkFile; + } + + @XmlAttribute(name = "SourceFile", required = true) + @NotNull + public String getSourceFile() { + return sourceFile; + } + + @XmlAttribute(name = "SourceUIType", required = true) + @NotNull + public String getSourceUIType() { + return sourceUIType; + } + + @XmlAttribute(name = "TemplateFile", required = true) + @NotNull + public String getTemplateFile() { + return templateFile; + } + + @XmlAttribute(name = "UITemplateFile", required = true) + @NotNull + public String getUITemplateFile() { + return UITemplateFile; + } + + @XmlElement(name = "Input", required = true) + public TestCaseInput getTestCaseInput() { + return testCaseInput; + } + + 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; + // } + // + // public void setAttackTestCaseRequest(TestCaseRequest attackTestCaseRequest) { + // this.attackTestCaseRequest = attackTestCaseRequest; + // } + // + // public TestCaseRequest getSafeTestCaseRequest() { + // return safeTestCaseRequest; + // } + // + // public void setSafeTestCaseRequest(TestCaseRequest safeTestCaseRequest) { + // 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 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) { + 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); + // } + // } + + @XmlElement(name = "VerificationResult", required = true) + public String getVerificationResult() { + return this.verificationResult; + } + + @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..d8200166 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseInput.java @@ -0,0 +1,77 @@ +/** + * 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; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; + +@XmlSeeAlso({ + CliArgExecutableTestCaseInput.class, + CliFileExecutableTestCaseInput.class, + ExecutableTestCaseInput.class, + HttpTestCaseInput.class, + JerseyTestCaseInput.class, + ServletTestCaseInput.class, + SpringTestCaseInput.class, + StdinExecutableTestCaseInput.class, + TcpSocketExecutableTestCaseInput.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/TestCaseResult.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java new file mode 100644 index 00000000..0f0bffaf --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseResult.java @@ -0,0 +1,49 @@ +/** + * 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(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/entities/TestCaseSetup.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java new file mode 100644 index 00000000..59aa0377 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetup.java @@ -0,0 +1,28 @@ +/** + * 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; + +@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/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java new file mode 100644 index 00000000..b99ae3f3 --- /dev/null +++ b/library/src/main/java/org/owasp/benchmarkutils/entities/TestCaseSetupException.java @@ -0,0 +1,25 @@ +/** + * 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 { + + public TestCaseSetupException(String message, Exception e) { + super(message, e); + } +} 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/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/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/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/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/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/pom.xml b/plugin/pom.xml index 78b8e0fc..e5f4ebe1 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -20,6 +20,12 @@ 1.3 + + com.contrastsecurity + java-sarif + 2.0 + + com.fasterxml.jackson.core jackson-annotations @@ -50,6 +56,14 @@ 33.3.1-jre + + javax.xml.bind jaxb-api @@ -112,17 +126,9 @@ provided - - org.eclipse.persistence - org.eclipse.persistence.core - ${version.eclipse.persistence} - - - org.eclipse.persistence org.eclipse.persistence.moxy - ${version.eclipse.persistence} @@ -143,27 +149,22 @@ 2.3 - - - - javax.validation - validation-api - 2.0.1.Final - - xml-apis xml-apis - - 1.4.01 + + + javax.json + javax.json-api + 1.1.4 + + + org.glassfish + javax.json + 1.1.4 + org.junit.jupiter junit-jupiter-api 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 c4e98a8c..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestCase.java +++ /dev/null @@ -1,201 +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.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.adapters.XmlJavaTypeAdapter; -import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; - -@XmlSeeAlso({ServletTestCase.class, JerseyTestCase.class, SpringTestCase.class}) -@XmlDiscriminatorNode("@tcType") -public abstract class TestCase { - - private String url; - - 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 UITemplateFile; - - private boolean isVulnerable; - - private List formParameters; - - private List getParameters; - - private List cookies; - - private List headers; - - @XmlAttribute(name = "URL", required = true) - @NotNull - public String getUrl() { - return url; - } - - @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 = "tcVulnerable", required = true) - public boolean isVulnerable() { - return isVulnerable; - } - - @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 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; - } - - @Override - public String toString() { - return "TestCase [url=" - + url - + ", category=" - + category - + ", dataflowFile=" - + dataflowFile - + ", name=" - + name - + ", notAutoverifiableReason=" - + notAutoverifiableReason - + ", sinkFile=" - + sinkFile - + ", sourceFile=" - + sourceFile - + ", sourceUIType=" - + sourceUIType - + ", templateFile=" - + templateFile - + ", UITemplateFile=" - + UITemplateFile - + ", isVulnerable=" - + isVulnerable - + ", formParameters=" - + formParameters - + ", getParameters=" - + getParameters - + ", cookies=" - + cookies - + ", headers=" - + headers - + "]"; - } -} 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 5599483e..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/helpers/TestSuite.java +++ /dev/null @@ -1,74 +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.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 version; - - @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; - } - - /** - * 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()); - } - - @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 9c15a056..ccb1b0db 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; @@ -44,12 +45,29 @@ 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.Marshaller; 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.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.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; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; public class Utils { @@ -59,7 +77,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 +94,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 +119,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,16 +201,31 @@ 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 { - - TestSuite testSuite = null; + public static TestSuite parseHttpFile(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 xmlSource = + new SAXSource( + 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()); - testSuite = (TestSuite) unmarshaller.unmarshal(new FileReader(file)); + JAXBElement testSuite = + (JAXBElement) unmarshaller.unmarshal(xmlSource); - return testSuite; + return testSuite.getValue(); } /** @@ -369,4 +411,58 @@ 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, + TestCaseVerificationResultsCollection.class, + VerifyFixOutput.class, + VerifyFixesOutput.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(); + } + + 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/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/score/parsers/sarif/DatadogSastReader.java b/plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/sarif/DatadogSastReader.java index 3f4727d8..23e1d7f4 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/sarif/DatadogSastReader.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/score/parsers/sarif/DatadogSastReader.java @@ -20,9 +20,9 @@ import org.owasp.benchmarkutils.score.ResultFile; /** - * This reader is made for the datadog-static-analyzer available on - * .... - * It uses the SARIF file produces by the tool. + * This reader is made for the datadog-static-analyzer available on .... It uses the SARIF file + * produces by the tool. */ public class DatadogSastReader extends SarifReader { 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 7d368d40..00000000 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/AbstractTestCaseRequest.java +++ /dev/null @@ -1,434 +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 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.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode; -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; - - // 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 query; - private String sinkFile; - private String sourceFile; - private String sourceUIType; - private TestCaseType tcType; - private String templateFile; - private String uiTemplateFile; - - public AbstractTestCaseRequest() {} - - /** 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 = "tcAttackSuccessPresent") - public boolean getAttackSuccessStringPresent() { - return this.attackSuccessStringPresent; - } - - @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; - } - - // 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; - } - - @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 boolean setAttackSuccessStringPresent(boolean attackSuccessStringPresent) { - return this.attackSuccessStringPresent = attackSuccessStringPresent; - } - - 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; - } - - 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) { - 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) { - 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 bc768bd2..0be47e24 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler.java @@ -17,18 +17,24 @@ */ 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.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; 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; -import java.util.concurrent.TimeUnit; +import java.util.StringJoiner; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.commons.cli.CommandLine; @@ -41,7 +47,6 @@ 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; @@ -49,9 +54,8 @@ 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.client5.http.ssl.TrustAllStrategy; 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.maven.plugin.AbstractMojo; @@ -60,8 +64,16 @@ 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; +import org.owasp.benchmarkutils.entities.HttpResponseInfo; +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.TestSuite; import org.owasp.benchmarkutils.helpers.Utils; import org.owasp.benchmarkutils.score.BenchmarkScore; @@ -71,20 +83,44 @@ 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; + + @Parameter(property = "testCaseName") + String pluginTestCaseNameParam; /* - * 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 { @@ -103,15 +139,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); @@ -127,64 +164,79 @@ 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 - long start = System.currentTimeMillis(); + // 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, true); + } 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); + } + } - for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { + // Log the elapsed time for all test cases + long stop = System.currentTimeMillis(); + int seconds = (int) (stop - start) / 1000; - HttpUriRequest request = requestTemplate.buildSafeRequest(); + Date now = new Date(); - // Send the next test case request - sendRequest(httpclient, request); + System.out.printf( + "Crawl ran on %tF % (%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 | org.apache.hc.core5.http.ParseException e) { + e.printStackTrace(); } } finally { if (response != null) @@ -296,6 +334,78 @@ static ResponseInfo sendRequest(CloseableHttpClient httpclient, HttpUriRequest r return responseInfo; } + /** + * 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. By default, this assumes a normal 'safe' request. + * + * @param request - The CLI request to issue + */ + static ResponseInfo execute(CliRequest request) { + 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); + + ArrayList executeArgs = + new ArrayList<>(Arrays.asList(request.getCommand().split(" "))); + for (RequestVariable arg : request.getArgs()) { + // System.out.println("Adding arg: " + arg.getValue()); + executeArgs.add(arg.getValue()); + } + // System.out.println(String.join(" ", executeArgs)); + + StopWatch watch = new StopWatch(); + + watch.start(); + 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(); + 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.setStatusCode(exitValue); + } + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + watch.stop(); + + return responseInfo; + } + /** * Process the command line arguments that make any configuration changes. * @@ -329,7 +439,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); @@ -343,21 +460,30 @@ 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 (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}; - 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)); } } 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) + // (via maven or command line) if (thisInstance == null) { thisInstance = new BenchmarkCrawler(); } 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..1ed37949 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification.java @@ -17,13 +17,21 @@ */ 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.net.URISyntaxException; +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; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -37,8 +45,21 @@ 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.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.HttpResponseInfo; +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.entities.VerifyFixOutput; +import org.owasp.benchmarkutils.entities.VerifyFixesOutput; 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. @@ -51,225 +72,763 @@ public class BenchmarkCrawlerVerification extends BenchmarkCrawler { private static int maxTimeInSeconds = 2; private static boolean isTimingEnabled = false; + // private boolean verifyFixed = false; // DEBUG + private String configurationDirectory = 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"; 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 + // 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 SimpleFileLogger tLogger; SimpleFileLogger ndLogger; 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. - */ - @Override - protected void crawl(TestSuite testSuite) throws Exception { - CloseableHttpClient httpclient = - createAcceptSelfSignedCertificateClient(MAX_NETWORK_TIMEOUT); - 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); + @Parameter(property = "generateJSONResults", defaultValue = "false") + private String generateJSONResults; - String completionMessage = null; + @Parameter(property = "verifyFixed", defaultValue = "false") + private String verifyFixed; - try (SimpleFileLogger nl = SimpleFileLogger.getLogger("NONDISCRIMINATORY"); - SimpleFileLogger el = SimpleFileLogger.getLogger("ERRORS"); - SimpleFileLogger ul = SimpleFileLogger.getLogger("UNVERIFIABLE"); - SimpleFileLogger tl = SimpleFileLogger.getLogger("TIMES")) { + @Parameter(property = "unfixedSrcDirectory") + private String unfixedSourceDirectory; - ndLogger = nl; - eLogger = el; - uLogger = ul; - tLogger = tl; + @Parameter(property = "fixedSrcDirectory") + private String fixedSourceDirectory; - for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { + @Parameter(property = "outputDirectory") + private String outputDirectory; - HttpUriRequest attackRequest = requestTemplate.buildAttackRequest(); - HttpUriRequest safeRequest = requestTemplate.buildSafeRequest(); + @Parameter(property = "testCaseName") + private String selectedTestCaseName; - // Send the next test case request with its attack payload - ResponseInfo attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); - responseInfoList.add(attackPayloadResponseInfo); + private Map testCaseNameToTestCaseVerificationResultsMap = + new HashMap<>(); - // Log the response - log(attackPayloadResponseInfo); + 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<>(); - ResponseInfo safePayloadResponseInfo = null; - if (!requestTemplate.isUnverifiable()) { - // Send the next test case request with its safe payload - safePayloadResponseInfo = sendRequest(httpclient, safeRequest); - responseInfoList.add(safePayloadResponseInfo); + private List verifyFixesOutputList = new ArrayList<>(); - // Log the response - log(safePayloadResponseInfo); - } + BenchmarkCrawlerVerification() { + // A default constructor required to support Maven plugin API. + // The theCrawlerFile has to be instantiated before a crawl can be done. + } - TestCaseVerificationResults result = - new TestCaseVerificationResults( - attackRequest, - safeRequest, - requestTemplate, - attackPayloadResponseInfo, - safePayloadResponseInfo); - results.add(result); + @Override + protected void crawl(TestSuite testSuite) throws Exception { - // 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(); + + 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); + final File FILE_TIMES_LOG; + if (isTimingEnabled) { + FILE_TIMES_LOG = new File(getOutputDirectory(), FILENAME_TIMES); + } else { + FILE_TIMES_LOG = new File(getOutputDirectory(), FILENAME_TIMES_ALL); } + final File FILE_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); + 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; + + List filteredList; + + if (Boolean.parseBoolean(verifyFixed)) { + filteredList = + testSuite.getTestCases().stream() + .filter( + testCase -> + !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) { + // if + // (!testCase.getName().equals(this.selectedTestCaseName)) { + // continue; + // } + // } + + // 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, true); + 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 + System.out.println("Executing attack request: " + attackRequest); + attackPayloadResponseInfo = execute(attackRequest, true); + //// 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 + System.out.println("Executing safe request: " + safeRequest); + 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.getExecutorDescription(), + safeExecutor.getExecutorDescription(), + testCase, + attackPayloadResponseInfo, + safePayloadResponseInfo); + results.add(result); + + // Verify the response + if (RegressionTesting.isTestingEnabled) { + handleResponse(result); + } + } + TestCaseVerificationResultsCollection resultsCollection = + new TestCaseVerificationResultsCollection(); + resultsCollection.setResultsObjects(results); - // 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(); - 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_UNVERIFIABLE_LOG.length() > 0) { + System.out.printf( + "Details of unverifiable test cases written to: %s%n", + 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); + if (Boolean.parseBoolean(verifyFixed)) { + printFixVerificationSummary(); + } + System.out.println(); + System.out.println(completionMessage); } - if (FILE_NON_DISCRIMINATORY_LOG.length() > 0) { - System.out.printf( - "Details of non-discriminatory test cases written to: %s%n", - FILE_NON_DISCRIMINATORY_LOG); + // FIXME: Use a finally to cleanup all setup resources that were required + // cleanupSetups(setups); + } + + 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 = 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(); } - if (FILE_ERRORS_LOG.length() > 0) { - System.out.printf( - "Details of errors/exceptions in test cases written to: %s%n", FILE_ERRORS_LOG); + 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(); } - if (FILE_UNVERIFIABLE_LOG.length() > 0) { - System.out.printf( - "Details of unverifiable test cases written to: %s%n", FILE_UNVERIFIABLE_LOG); + // 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 != null + && fixedSourceFileContents != null + && unfixedSourceFileContents.equals(fixedSourceFileContents); + } + + private void printFixVerificationSummary() { + System.out.println(); + 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 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()); + } + + /** + * @param testSuite + * @throws Exception + */ + protected void crawlToVerifyFix(TestSuite testSuite, String unfixedOutputDirectory) + 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()) { + 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; + } - RegressionTesting.printCrawlSummary(results); - System.out.println(); - System.out.println(completionMessage); + private void initializeSetups(List testCaseSetups) + throws TestCaseSetupException { + for (TestCaseSetup testCaseSetup : testCaseSetups) { + testCaseSetup.setup(); + } + } + + private void cleanupSetups(List testCaseSetups) throws TestCaseSetupException { + for (TestCaseSetup testCaseSetup : testCaseSetups) { + testCaseSetup.close(); + } + } + + protected String getConfigurationDirectory() { + return configurationDirectory; + } + + protected String getOutputDirectory() { + return outputDirectory; } 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()); if (isTimingEnabled) { - if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + if (httpResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(httpResponseInfo.getMethod() + " " + httpResponseInfo.getUri()); tLogger.println(outputString); - } // else do nothing + } } else { - tLogger.println(requestBase.getMethod() + " " + requestBase.getUri()); + tLogger.println(httpResponseInfo.getMethod() + " " + httpResponseInfo.getUri()); tLogger.println(outputString); } - } catch (URISyntaxException e) { - String errMsg = requestBase.getMethod() + " COULDN'T LOG URI due to URISyntaxException"; - tLogger.println(errMsg); - tLogger.println(outputString); - System.out.println(errMsg); - 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.getStatusCode(), cliResponseInfo.getTimeInSeconds()); + if (isTimingEnabled) { + if (cliResponseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(request.getCommand()); + tLogger.println(responseString); + } + } else { + tLogger.println(request.getCommand()); + tLogger.println(responseString); + } } } /** * 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. * @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 (Boolean.parseBoolean(verifyFixed)) { + 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))); + + // // 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) + // && !isTestCaseIdentical( + // unfixedSourceDirectory, + // fixedSourceDirectory, + // unfixedResults.getTestCase().getName())) { + // verifyFix(unfixedResults, fixedResults); + // } + verifyFix(unfixedResults, fixedResults); + } else { + System.out.println( + "WARNING: After fix testcase is " + + fixedResults.getTestCase().getName() + + " but before fix testcase is " + + unfixedResults.getTestCase().getName()); + } + } + } + + private TestCaseVerificationResultsCollection loadTestCaseVerificationResults( + String directory) { + + TestCaseVerificationResultsCollection results = null; + try { + 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 + | 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); + + return results; + } + + private boolean verifyFix( + TestCaseVerificationResults unfixedResults, TestCaseVerificationResults fixedResults) { + + // DEBUG + try { + String unfixedResultsJson = Utils.objectToJson(unfixedResults); + System.out.println("unfixedResults JSON: " + unfixedResultsJson); + } catch (Exception e) { + e.printStackTrace(); + } + + boolean isVulnerable = fixedResults.getTestCase().isVulnerability(); + // boolean wasNotVerifiable = + // fixedResults.getTestCase().isVulnerability() + // && fixedResults.getTestCase().isUnverifiable() + // && fixedResults.isPassed(); + boolean wasNotVerifiable = fixedResults.getTestCase().isUnverifiable(); + boolean wasExploited = + 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(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); + + // 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) { + 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); + } + } + } + + verifyFixesOutputList.add(verifyFixOutput); + + return !wasNotVerifiable && !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; } /** @@ -278,15 +837,12 @@ 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) { + System.out.println("Maven user selected verifyFixed=" + verifyFixed); - // 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; @@ -297,14 +853,40 @@ protected void processCommandLineArgs(String[] args) { // Create the Options Options options = new Options(); + options.addOption( + Option.builder("b") + .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("o") + .longOpt("outputDirectory") + .desc("output directory") + .hasArg() + .build()); options.addOption( Option.builder("f") .longOpt("file") - .desc("a TESTSUITE-attack-http.xml file") + .desc("a TESTSUITE-crawler-http.xml file") .hasArg() .required() .build()); options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption( + Option.builder("j") + .longOpt("generateJSONResults") + .desc("generate json version of verification results") + .build()); + // 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") @@ -323,42 +905,102 @@ protected void processCommandLineArgs(String[] args) { // Parse the command line arguments CommandLine line = parser.parse(options, args); + 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")) { - // 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 + configurationDirectory = targetFile.getParent(); + } else { + throw new RuntimeException( + "Could not find crawler configuration file '" + this.crawlerFile + "'"); + } } if (line.hasOption("h")) { formatter.printHelp("BenchmarkCrawlerVerification", options, true); } + 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"); } 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); } } + /** + * 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 (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}; - main(mainArgs); + List mainArgs = new ArrayList<>(); + mainArgs.add("-f"); + mainArgs.add(this.pluginFilenameParam); + if (this.outputDirectory != null) { + mainArgs.add("-o"); + mainArgs.add(this.outputDirectory); + } + if (this.unfixedSourceDirectory != null) { + mainArgs.add("-b"); + mainArgs.add(this.unfixedSourceDirectory); + } + if (this.fixedSourceDirectory != null) { + mainArgs.add("-a"); + mainArgs.add(this.fixedSourceDirectory); + } + if (this.pluginTestCaseNameParam != null) { + mainArgs.add("-n"); + mainArgs.add(this.pluginTestCaseNameParam); + } + 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"); + } + main(mainArgs.stream().toArray(String[]::new)); } } 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) + // (via maven or command line) if (thisInstance == null) { thisInstance = new BenchmarkCrawlerVerification(); } 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/CliExecutor.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java new file mode 100644 index 00000000..9e45f347 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CliExecutor.java @@ -0,0 +1,54 @@ +/** + * 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 extends TestExecutor { + CliRequest cliRequest; + + public CliExecutor() {} + + 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%n", requestVariable.getValue())); + } + + return commandTokens.toString(); + } +} 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 new file mode 100644 index 00000000..b4c535d6 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/HttpExecutor.java @@ -0,0 +1,73 @@ +/** + * 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 extends TestExecutor { + HttpUriRequest httpRequest; + + public HttpExecutor() {} + + 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; + try { + HttpEntity entity = postHttpRequest.getEntity(); + if (entity != null) { + out.print(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/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 0383cf45..43cd04cd 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/RegressionTesting.java @@ -29,11 +29,18 @@ 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; 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; +import org.owasp.benchmarkutils.helpers.Utils; /** * Test all supported test cases to verify that the results are as expected and write the report to @@ -63,21 +70,55 @@ 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; private static final String FILENAME_FAILEDTC = "failedTestCases.txt"; + 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 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( + TestCaseVerificationResultsCollection resultsCollection, + String dataDir, + boolean generate) + throws IOException, LoggerConfigurationException { + + 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")) { + + tcJsonLogger = tcJSON; + + // Create JSON version of verification results for ALL test cases + tcJsonLogger.println(Utils.objectToJson(resultsCollection)); + } 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. @@ -100,10 +141,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('.')); @@ -116,17 +159,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++; @@ -135,26 +178,33 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); - if (isIncludedInTest(requestTemplate)) { + 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()); + 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()); + } } } } @@ -162,11 +212,11 @@ public static void genFailedTCFile(List results, St if (truePositiveFailedCount + falsePositiveFailedCount > 0) { for (TestCaseVerificationResults result : results) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); - if (isIncludedInTest(requestTemplate)) { + TestCase testCase = result.getTestCase(); + if (isIncludedInTest(testCase)) { if (!result.isUnverifiable() && !result.isPassed()) { ftcLogger.print("FAILURE: "); - printTestCaseDetails(result, ftcLogger); + printTestCaseDetailsAsText(result, ftcLogger); } } } @@ -198,35 +248,59 @@ 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(); out.printf( "%s positive %s test case request %s%n", - requestTemplate.isVulnerability() ? "True" : "False", - requestTemplate.getCategory().toString(), - requestTemplate.getName()); - // Print out all the attributes of the request, including the templates used to create it - out.println(requestTemplate.toString()); + testCase.isVulnerability() ? "True" : "False", + testCase.getCategory().toString(), + testCase.getName()); + // Print out all attributes of the request, including the templates used to create it + out.println(testCase.toString()); out.println(); out.println("Attack request:"); - printHttpRequest(result.getAttackRequest(), out); - out.println(); - out.printf("Attack response: [%d]:%n", attackResponseInfo.getStatusCode()); - out.println(attackResponseInfo == null ? "null" : attackResponseInfo.getResponseString()); + out.println(result.getAttackTestExecutorDescription()); + 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).getStatusCode()); + out.println( + attackResponseInfo == null + ? "null" + : ((CliResponseInfo) attackResponseInfo).getResponseString()); + } out.println(); out.println("Safe request:"); - printHttpRequest(result.getSafeRequest(), out); - out.println(); - out.printf("Safe response: [%d]:%n", attackResponseInfo.getStatusCode()); - out.println(safeResponseInfo == null ? "null" : safeResponseInfo.getResponseString()); + out.println(result.getSafeTestExecutorDescription()); + 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).getStatusCode()); + out.println( + safeResponseInfo == null + ? "null" + : ((CliResponseInfo) safeResponseInfo).getResponseString()); + } out.println(); - String negatedAttackSuccessString = - (requestTemplate.getAttackSuccessStringPresent() ? "" : "Failure "); - out.printf( - "Attack success %sindicator: -->%s<--%n", - negatedAttackSuccessString, requestTemplate.getAttackSuccessString()); + out.printf("Attack success indicator: -->%s<--%n", testCase.getAttackSuccessString()); out.printf("-----------------------------------------------------------%n%n"); } @@ -320,40 +394,38 @@ 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); uLogger.print("UNVERIFIABLE: "); - printTestCaseDetails(result, uLogger); + printTestCaseDetailsAsText(result, uLogger); } 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('.')); } if (!result.isUnverifiable()) { - AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); boolean isAttackValueVerified = verifyResponse( result.getResponseToAttackValue().getResponseString(), - requestTemplate.getAttackSuccessString(), - requestTemplate.getAttackSuccessStringPresent()); + testCase.getAttackSuccessString()); boolean isSafeValueVerified = verifyResponse( result.getResponseToSafeValue().getResponseString(), - requestTemplate.getAttackSuccessString(), - requestTemplate.getAttackSuccessStringPresent()); - if (result.getRequestTemplate().isVulnerability()) { + testCase.getAttackSuccessString()); + if (testCase.isVulnerability()) { // True positive success? if (isAttackValueVerified) { result.setPassed(true); @@ -361,9 +433,8 @@ 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()); - printTestCaseDetails(result, ndLogger); + testCase.getName(), testCase.getAttackSuccessString()); + printTestCaseDetailsAsText(result, ndLogger); nonDiscriminatorySinks.add(sink); } } else { @@ -381,9 +452,8 @@ 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()); - printTestCaseDetails(result, ndLogger); + testCase.getName(), testCase.getAttackSuccessString()); + printTestCaseDetailsAsText(result, ndLogger); nonDiscriminatorySinks.add(sink); } } @@ -395,17 +465,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); } } } @@ -423,8 +493,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).getStatusCode(); + if (returnCode != 0) { + reasons.add(prefix + " response code: " + returnCode); + } } if (responseInfo.getResponseString().toLowerCase().contains("error")) { reasons.add(prefix + " response contains: error"); @@ -442,21 +520,18 @@ 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) { - 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/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 8e01abf7..c3ebb603 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestCaseVerificationResults.java @@ -17,9 +17,14 @@ */ package org.owasp.benchmarkutils.tools; -import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +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; @@ -32,22 +37,26 @@ public class TestCaseVerificationResults { private boolean isPassed; - private HttpUriRequest attackRequest; + private String attackTestExecutorDescription; - private HttpUriRequest safeRequest; + private String safeTestExecutorDescription; - private AbstractTestCaseRequest requestTemplate; + private TestCase testCase; + + public TestCaseVerificationResults() { + super(); + } public TestCaseVerificationResults( - HttpUriRequest attackRequest, - HttpUriRequest safeRequest, - AbstractTestCaseRequest requestTemplate, + String attackTestExecutorDescription, + String safeTestExecutorDescription, + TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue) { this( - attackRequest, - safeRequest, - requestTemplate, + attackTestExecutorDescription, + safeTestExecutorDescription, + testCase, responseToAttackValue, responseToSafeValue, true, @@ -56,18 +65,18 @@ public TestCaseVerificationResults( } public TestCaseVerificationResults( - HttpUriRequest attackRequest, - HttpUriRequest safeRequest, - AbstractTestCaseRequest requestTemplate, + String attackTestExecutorDescription, + String safeTestExecutorDescription, + TestCase testCase, ResponseInfo responseToAttackValue, ResponseInfo responseToSafeValue, boolean isUnverifiable, boolean isDeclaredVerifiable, boolean isPassed) { super(); - this.attackRequest = attackRequest; - this.safeRequest = safeRequest; - this.requestTemplate = requestTemplate; + this.attackTestExecutorDescription = attackTestExecutorDescription; + this.safeTestExecutorDescription = safeTestExecutorDescription; + this.testCase = testCase; this.responseToAttackValue = responseToAttackValue; this.responseToSafeValue = responseToSafeValue; this.isUnverifiable = isUnverifiable; @@ -75,6 +84,7 @@ public TestCaseVerificationResults( this.isPassed = isPassed; } + @XmlElement(name = "AttackResponseInfo") public ResponseInfo getResponseToAttackValue() { return responseToAttackValue; } @@ -83,6 +93,7 @@ public void setResponseToAttackValue(ResponseInfo responseToAttackValue) { this.responseToAttackValue = responseToAttackValue; } + @XmlElement(name = "SafeResponseInfo", required = true) public ResponseInfo getResponseToSafeValue() { return responseToSafeValue; } @@ -91,6 +102,7 @@ public void setResponseToSafeValue(ResponseInfo responseToSafeValue) { this.responseToSafeValue = responseToSafeValue; } + @XmlAttribute(name = "Unverifiable") public boolean isUnverifiable() { return isUnverifiable; } @@ -99,6 +111,7 @@ public void setUnverifiable(boolean isUnverifiable) { this.isUnverifiable = isUnverifiable; } + @XmlAttribute(name = "DeclaredUnverifiable") public boolean isDeclaredUnverifiable() { return isDeclaredUnverifiable; } @@ -107,35 +120,39 @@ 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 HttpUriRequest getAttackRequest() { - return attackRequest; + public void setPassed(boolean isPassed) { + this.isPassed = isPassed; + } + + @XmlElement(name = "AttackRequestInfo") + public String getAttackTestExecutorDescription() { + return attackTestExecutorDescription; } - public void setAttackRequest(HttpUriRequest attackRequest) { - this.attackRequest = attackRequest; + public void setAttackTestExecutorDescription(String attackTestExecutor) { + this.attackTestExecutorDescription = attackTestExecutorDescription; } - public HttpUriRequest getSafeRequest() { - return safeRequest; + @XmlElement(name = "SafeRequestInfo") + public String getSafeTestExecutorDescription() { + return safeTestExecutorDescription; } - public void setSafeRequest(HttpUriRequest safeRequest) { - this.safeRequest = safeRequest; + public void setSafeTestExecutorDescription(String safeTestExecutor) { + this.safeTestExecutorDescription = safeTestExecutorDescription; } - public AbstractTestCaseRequest getRequestTemplate() { - return requestTemplate; + @XmlElement(name = "TestCase") + public TestCase getTestCase() { + return testCase; } - public void setRequestTemplate(AbstractTestCaseRequest requestTemplate) { - this.requestTemplate = requestTemplate; + public void setTestCase(TestCase testCase) { + this.testCase = 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 new file mode 100644 index 00000000..03fc4443 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/TestExecutor.java @@ -0,0 +1,31 @@ +/** + * 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.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; + +@XmlRootElement(name = "TestCaseRequest") +@XmlSeeAlso({CliExecutor.class, HttpExecutor.class}) +public abstract class TestExecutor { + @XmlAttribute(name = "RequestDescription", required = true) + @NotNull + public abstract String getExecutorDescription(); +} diff --git a/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/PrecautionReaderTest.java b/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/PrecautionReaderTest.java index 2733c810..efb8ada4 100644 --- a/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/PrecautionReaderTest.java +++ b/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/PrecautionReaderTest.java @@ -28,7 +28,6 @@ import org.owasp.benchmarkutils.score.TestHelper; import org.owasp.benchmarkutils.score.TestSuiteResults; import org.owasp.benchmarkutils.score.parsers.ReaderTestBase; -import org.owasp.benchmarkutils.score.parsers.sarif.PrecautionReader; class PrecautionReaderTest extends ReaderTestBase { diff --git a/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/SarifReaderTest.java b/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/SarifReaderTest.java index 99455f0d..f5019161 100644 --- a/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/SarifReaderTest.java +++ b/plugin/src/test/java/org/owasp/benchmarkutils/score/parsers/sarif/SarifReaderTest.java @@ -21,7 +21,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.owasp.benchmarkutils.score.parsers.sarif.SarifReader; public class SarifReaderTest { diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755 index 8596b6d7..4d9654f7 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,64 @@ 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 + + + javax.json + javax.json-api + 1.1.4 + + + + benchmarkutils @@ -358,6 +416,10 @@ UTF-8 11 ${project.build.directory}/log + + + 2.7.14 + 2.0.1