From 4fbda6283bdf2b803d3e502b6355e5c8358b2a5a Mon Sep 17 00:00:00 2001 From: Simon Bennetts Date: Thu, 3 Oct 2024 17:47:10 +0100 Subject: [PATCH] Dev: Sequence performance test page A configurable set of sequence pages. Signed-off-by: Simon Bennetts --- addOns/dev/CHANGELOG.md | 1 + .../java/org/zaproxy/addon/dev/TestPage.java | 10 + .../zaproxy/addon/dev/TestProxyServer.java | 5 + .../dev/seq/performance/PerformanceDir.java | 32 +++ .../dev/seq/performance/SequencePage.java | 224 ++++++++++++++++++ .../dev/resources/help/contents/dev.html | 3 + .../dev-add-on/seq/performance/index.html | 37 +++ .../dev-add-on/seq/performance/seq.html | 20 ++ 8 files changed, 332 insertions(+) create mode 100644 addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/PerformanceDir.java create mode 100644 addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/SequencePage.java create mode 100644 addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/index.html create mode 100644 addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/seq.html diff --git a/addOns/dev/CHANGELOG.md b/addOns/dev/CHANGELOG.md index 1b5bd1be440..18fd5e9b436 100644 --- a/addOns/dev/CHANGELOG.md +++ b/addOns/dev/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased ### Added - Extra protected pages to simple-json-cookie to ensure spidering really works. +- Sequence performance test. ### Fixed - Issue where folder level pages without a trailing slash did not link correctly to sub pages. diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestPage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestPage.java index 70d47c5fc1c..105a3675e7f 100644 --- a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestPage.java +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestPage.java @@ -19,6 +19,8 @@ */ package org.zaproxy.addon.dev; +import org.parosproxy.paros.network.HtmlParameter; +import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.addon.network.server.HttpMessageHandler; public abstract class TestPage implements HttpMessageHandler { @@ -47,4 +49,12 @@ public void setParent(TestDirectory parent) { public TestProxyServer getServer() { return server; } + + public String getFormParameter(HttpMessage msg, String name) { + return msg.getFormParams().stream() + .filter(p -> p.getName().equals(name)) + .findFirst() + .map(HtmlParameter::getValue) + .orElse(null); + } } diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java index 5c7ca010fa8..ba0b4ae753a 100644 --- a/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/TestProxyServer.java @@ -42,6 +42,7 @@ import org.zaproxy.addon.dev.auth.simpleJsonBearerCookie.SimpleJsonBearerCookieDir; import org.zaproxy.addon.dev.auth.simpleJsonBearerJsCookie.SimpleJsonBearerJsCookieDir; import org.zaproxy.addon.dev.auth.simpleJsonCookie.SimpleJsonCookieDir; +import org.zaproxy.addon.dev.seq.performance.PerformanceDir; import org.zaproxy.addon.network.ExtensionNetwork; import org.zaproxy.addon.network.server.HttpMessageHandler; import org.zaproxy.addon.network.server.HttpMessageHandlerContext; @@ -94,9 +95,13 @@ public TestProxyServer(ExtensionDev extension, ExtensionNetwork extensionNetwork htmlDir.addDirectory(locStoreDir); htmlDir.addDirectory(sessStoreDir); + TestDirectory seqDir = new TestDirectory(this, "seq"); + seqDir.addDirectory(new PerformanceDir(this, "performance")); + root.addDirectory(authDir); root.addDirectory(apiDir); root.addDirectory(htmlDir); + root.addDirectory(seqDir); } private Server getServer() { diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/PerformanceDir.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/PerformanceDir.java new file mode 100644 index 00000000000..7bfca7707ad --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/PerformanceDir.java @@ -0,0 +1,32 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.seq.performance; + +import org.zaproxy.addon.dev.TestAuthDirectory; +import org.zaproxy.addon.dev.TestProxyServer; + +/** A sequence of forms which must be accessed in order. */ +public class PerformanceDir extends TestAuthDirectory { + + public PerformanceDir(TestProxyServer server, String name) { + super(server, name); + this.addPage(new SequencePage(server)); + } +} diff --git a/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/SequencePage.java b/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/SequencePage.java new file mode 100644 index 00000000000..35e5a32f5db --- /dev/null +++ b/addOns/dev/src/main/java/org/zaproxy/addon/dev/seq/performance/SequencePage.java @@ -0,0 +1,224 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2024 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.addon.dev.seq.performance; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.stream.IntStream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.network.HttpMalformedHeaderException; +import org.parosproxy.paros.network.HttpMessage; +import org.parosproxy.paros.network.HttpRequestHeader; +import org.zaproxy.addon.dev.TestPage; +import org.zaproxy.addon.dev.TestProxyServer; +import org.zaproxy.addon.network.server.HttpMessageHandlerContext; + +public class SequencePage extends TestPage { + + private static final Logger LOGGER = LogManager.getLogger(SequencePage.class); + private TestProxyServer server; + + private static int numberOfSteps = 3; + private static int numberOfFields = 3; + private static boolean checkSequence = true; + + private Map seqMap = new HashMap<>(); + + public SequencePage(TestProxyServer server) { + super(server, "seq"); + this.server = server; + } + + /** + * Set the number of steps to be used in the sequence, default is 3. This is designed to be + * called from a script. + * + * @param numberOfSteps + */ + public static void setNumberOfSteps(int numberOfSteps) { + SequencePage.numberOfSteps = numberOfSteps; + } + + /** + * Set the number of fields to be used in each step, default is 3. This is designed to be called + * from a script. + * + * @param numberOfFields + */ + public static void setNumberOfFields(int numberOfFields) { + SequencePage.numberOfFields = numberOfFields; + } + + /** + * If true then the sequence ordering will be enforces, if false then the steps can be submitted + * in any order. + * + * @param checkSequence + */ + public static void setCheckSequence(boolean checkSequence) { + SequencePage.checkSequence = checkSequence; + } + + @Override + public void handleMessage(HttpMessageHandlerContext ctx, HttpMessage msg) { + String body = server.getTextFile(this.getParent(), "seq.html"); + + UUID seqUuid = null; + int seqStep = 0; + + if (HttpRequestHeader.POST.equals(msg.getRequestHeader().getMethod())) { + String seqId = super.getFormParameter(msg, "seqId"); + if (seqId != null) { + try { + seqUuid = UUID.fromString(seqId); + } catch (Exception e) { + // Ignore + } + } + String seqStepStr = super.getFormParameter(msg, "seqStep"); + if (seqStepStr != null) { + try { + seqStep = Integer.parseInt(seqStepStr); + } catch (NumberFormatException e) { + // Ignore + } + } + } + if (seqUuid == null) { + // Its the start of a new sequence + seqUuid = UUID.randomUUID(); + if (checkSequence) { + seqMap.put(seqUuid, seqStep); + } + } + + try { + StringBuilder sb = new StringBuilder(); + String responseStatus = TestProxyServer.STATUS_OK; + + Integer expectedStep = seqMap.get(seqUuid); + + if (checkSequence && expectedStep == null) { + // Unknown sequence + sb.append("Unregistered sequence!\n"); + sb.append("

\n"); + sb.append("Sorry, but you have to Start Again"); + sb.append("

\n"); + + responseStatus = TestProxyServer.STATUS_FORBIDDEN; + seqMap.remove(seqUuid); + } else if (checkSequence && seqStep != expectedStep) { + // Out of sequence + sb.append("Out of sequence step!\n"); + sb.append("Got "); + sb.append(seqStep); + sb.append(" but expected "); + sb.append(seqMap.get(seqUuid)); + sb.append("

\n"); + sb.append("Sorry, but you have to Start Again"); + sb.append("

\n"); + + responseStatus = TestProxyServer.STATUS_FORBIDDEN; + seqMap.remove(seqUuid); + + } else if (seqStep >= numberOfSteps) { + // All done, just need to output the vulnerable value.. + sb.append("You got to the end of the sequence!\n"); + sb.append("

\n"); + sb.append("The final value supplied was "); + sb.append(super.getFormParameter(msg, "vuln")); + sb.append("

\n"); + sb.append("Start Again"); + sb.append("

\n"); + + if (checkSequence) { + seqMap.remove(seqUuid); + } + } else { + seqStep++; + if (checkSequence) { + seqMap.put(seqUuid, seqStep); + } + + sb.append("

Step "); + sb.append(seqStep); + sb.append("

\n"); + sb.append("
\n"); + appendInput(sb, "hidden", "step" + seqStep, Integer.toString(seqStep)); + appendInput(sb, "hidden", "seqId", seqUuid.toString()); + appendInput(sb, "hidden", "seqStep", Integer.toString(seqStep)); + + sb.append("\n"); + IntStream.range(0, numberOfFields) + .forEach(i -> appendParam(sb, "Field " + i, "text", "field" + i, "")); + + if (seqStep == numberOfSteps) { + appendParam(sb, "Vulnerable Param", "text", "vuln", "Not Safe!"); + } + + sb.append("\n"); + sb.append("\t\n"); + sb.append("\t\n"); + sb.append("\n"); + sb.append("
\n"); + sb.append("
\n"); + } + body = body.replace("", sb.toString()); + + msg.setResponseBody(body); + msg.setResponseHeader( + TestProxyServer.getDefaultResponseHeader( + responseStatus, "text/html", msg.getResponseBody().length())); + } catch (HttpMalformedHeaderException e) { + LOGGER.error(e.getMessage(), e); + } + } + + private static void appendParam( + StringBuilder sb, String desc, String type, String name, String value) { + sb.append("\n"); + sb.append("\t"); + sb.append(desc); + sb.append(":\n"); + sb.append("\t"); + appendInput(sb, type, name, value); + sb.append("\t\n"); + sb.append("\n"); + } + + private static void appendInput(StringBuilder sb, String type, String name, String value) { + sb.append("\n"); + } + + @Override + public PerformanceDir getParent() { + return (PerformanceDir) super.getParent(); + } +} diff --git a/addOns/dev/src/main/javahelp/org/zaproxy/addon/dev/resources/help/contents/dev.html b/addOns/dev/src/main/javahelp/org/zaproxy/addon/dev/resources/help/contents/dev.html index b1b255d1f36..20853fffc21 100644 --- a/addOns/dev/src/main/javahelp/org/zaproxy/addon/dev/resources/help/contents/dev.html +++ b/addOns/dev/src/main/javahelp/org/zaproxy/addon/dev/resources/help/contents/dev.html @@ -21,5 +21,8 @@

Authentication Pages

HTML Pages

A set of pages which store various types of data in localStorage and sessionStorage. +

Sequence Pages

+A set of pages for testing the sequence add-on. + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/index.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/index.html new file mode 100644 index 00000000000..fbe09dec02b --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/index.html @@ -0,0 +1,37 @@ + + + + ZAP Test Server + + + +
+

ZAP Test Server

+

Sequence Performence Test

+ + A sequence of forms that must be accessed in order.
+ A unique id for each sequence is maintained in a hidden "seqId" parameter.
+ The sequence step id is held both in a hidden "seqStep" parameter and is associated with the sequence id on + the server. +

+

+ + The test characteristics can be changed via the following static method calls: + +
    +
  • org.zaproxy.addon.dev.seq.performance.SequencePage.setNumberOfSteps(int)
    +
  • org.zaproxy.addon.dev.seq.performance.SequencePage.setNumberOfFields(int)
    +
  • org.zaproxy.addon.dev.seq.performance.SequencePage.setCheckSequence(boolean)
    +
+ + + +
+ + + diff --git a/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/seq.html b/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/seq.html new file mode 100644 index 00000000000..328872bcb57 --- /dev/null +++ b/addOns/dev/src/main/zapHomeFiles/dev-add-on/seq/performance/seq.html @@ -0,0 +1,20 @@ + + + + ZAP Test Server + + + +
+

ZAP Test Server

+

Sequence Performence Test

+ + + + +
+ + +