Skip to content

Commit

Permalink
Merge pull request #5787 from psiinon/dev/seq/perf
Browse files Browse the repository at this point in the history
  • Loading branch information
kingthorin authored Oct 4, 2024
2 parents 59a4349 + 4fbda62 commit 990aacc
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 0 deletions.
1 change: 1 addition & 0 deletions addOns/dev/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions addOns/dev/src/main/java/org/zaproxy/addon/dev/TestPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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<UUID, Integer> 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("<p>\n");
sb.append("Sorry, but you have to <a href=\"seq\">Start Again</a>");
sb.append("<p>\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("<p>\n");
sb.append("Sorry, but you have to <a href=\"seq\">Start Again</a>");
sb.append("<p>\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("<p>\n");
sb.append("The final value supplied was ");
sb.append(super.getFormParameter(msg, "vuln"));
sb.append("<p>\n");
sb.append("<a href=\"seq\">Start Again</a>");
sb.append("<p>\n");

if (checkSequence) {
seqMap.remove(seqUuid);
}
} else {
seqStep++;
if (checkSequence) {
seqMap.put(seqUuid, seqStep);
}

sb.append("<H3>Step ");
sb.append(seqStep);
sb.append("</H3>\n");
sb.append("<form method=\"POST\">\n");
appendInput(sb, "hidden", "step" + seqStep, Integer.toString(seqStep));
appendInput(sb, "hidden", "seqId", seqUuid.toString());
appendInput(sb, "hidden", "seqStep", Integer.toString(seqStep));

sb.append("<table style=\"border: none;\">\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("<tr>\n");
sb.append("\t<td></td>\n");
sb.append("\t<td><button>Next</button></td>\n");
sb.append("</tr>\n");
sb.append("</table>\n");
sb.append("</form>\n");
}
body = body.replace("<!-- CONTENT -->", 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("<tr>\n");
sb.append("\t<td>");
sb.append(desc);
sb.append(":\n");
sb.append("\t<td>");
appendInput(sb, type, name, value);
sb.append("\t</td>\n");
sb.append("</tr>\n");
}

private static void appendInput(StringBuilder sb, String type, String name, String value) {
sb.append("<input id=\"");
sb.append(name);
sb.append("\" name=\"");
sb.append(name);
sb.append("\" type=\"");
sb.append(type);
sb.append("\" value=\"");
sb.append(value);
sb.append("\">\n");
}

@Override
public PerformanceDir getParent() {
return (PerformanceDir) super.getParent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ <H2>Authentication Pages</H2>
<H2>HTML Pages</H2>
A set of pages which store various types of data in localStorage and sessionStorage.

<H2>Sequence Pages</H2>
A set of pages for testing the sequence add-on.

</BODY>
</HTML>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>ZAP Test Server</title>
<link href="/tutorial.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="roundContainer">
<h1>ZAP Test Server</H1>
<h2>Sequence Performence Test</h2>

A sequence of forms that must be accessed in order.<br>
A unique id for each sequence is maintained in a hidden "seqId" parameter.<br>
The sequence step id is held both in a hidden "seqStep" parameter and is associated with the sequence id on
the server.
<p>
<ul>
<li><a href="/seq">..</a>
<li><a href="seq">Start Sequence</a>
</ul>

The test characteristics can be changed via the following static method calls:

<ul>
<li><pre>org.zaproxy.addon.dev.seq.performance.SequencePage.setNumberOfSteps(int)</pre>
<li><pre>org.zaproxy.addon.dev.seq.performance.SequencePage.setNumberOfFields(int)</pre>
<li><pre>org.zaproxy.addon.dev.seq.performance.SequencePage.setCheckSequence(boolean)</pre>
</ul>


<div class="buttonsDiv">
<a href="/"><button>Index page</button></a>
</div>
</div>

</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>ZAP Test Server</title>
<link href="/tutorial.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="roundContainer">
<h1>ZAP Test Server</H1>
<h2>Sequence Performence Test</h2>

<!-- CONTENT -->

<div class="buttonsDiv">
<a href="/"><button>Index page</button></a>
</div>
</div>

</body>
</html>

0 comments on commit 990aacc

Please sign in to comment.