Skip to content

Commit

Permalink
Merge pull request #5499 from kingthorin/ssti-examples
Browse files Browse the repository at this point in the history
ascanrules: Add SSTI example alerts
  • Loading branch information
psiinon authored Jun 17, 2024
2 parents 7ad6968 + 4e7840e commit 29e6ab8
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 23 deletions.
5 changes: 4 additions & 1 deletion addOns/ascanrules/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ All notable changes to this add-on will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

### Changed
- The following rules now includes example alert functionality for documentation generation purposes (Issue 6119), as well as now including Alert Tags (OWASP Top 10, WSTG, and updated CWE):
- Server Side Template Injection
- Server Side Template Injection (Blind)

## [66] - 2024-05-07
### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import java.io.IOException;
import java.net.SocketException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.configuration.ConversionException;
import org.apache.logging.log4j.LogManager;
Expand All @@ -32,6 +34,7 @@
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.core.scanner.Plugin;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.addon.commonlib.timing.TimingUtils;
import org.zaproxy.addon.oast.ExtensionOast;
import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam;
Expand All @@ -47,6 +50,12 @@ public class SstiBlindScanRule extends AbstractAppParamPlugin implements CommonA
/** Prefix for internationalised messages used by this rule */
private static final String MESSAGE_PREFIX = "ascanrules.sstiblind.";

private static final Map<String, String> ALERT_TAGS =
CommonAlertTag.toMap(
CommonAlertTag.OWASP_2021_A03_INJECTION,
CommonAlertTag.OWASP_2017_A01_INJECTION,
CommonAlertTag.WSTG_V42_INPV_18_SSTI);

private static final String SECONDS_PLACEHOLDER = "X_SECONDS_X";

// Most of the exploits have been created by James Kettle @albinowax and the Tplmap creator
Expand Down Expand Up @@ -133,7 +142,8 @@ public String getReference() {

@Override
public int getCweId() {
return 74; // CWE - 74 : Failure to Sanitize Data into a Different Plane ('Injection')
return 1336; // CWE-1336: Improper Neutralization of Special Elements Used in a Template
// Engine
}

@Override
Expand Down Expand Up @@ -250,11 +260,10 @@ private boolean checkIfCausesTimeDelay(String paramName, String payloadFormat) {
attack.get());

// raise the alert
newAlert()
.setConfidence(Alert.CONFIDENCE_HIGH)
.setUri(getBaseMsg().getRequestHeader().getURI().toString())
.setParam(paramName)
.setAttack(attack.get())
createAlert(
getBaseMsg().getRequestHeader().getURI().toString(),
paramName,
attack.get())
.setMessage(message.get())
.raise();
return true;
Expand Down Expand Up @@ -368,4 +377,24 @@ private void sendPayloadsToMakeCallBack(String paramName, String[] commandExecPa
}
}
}

private AlertBuilder createAlert(String url, String param, String attack) {
return newAlert()
.setConfidence(Alert.CONFIDENCE_HIGH)
.setUri(url)
.setParam(param)
.setAttack(attack);
}

@Override
public List<Alert> getExampleAlerts() {
return List.of(
createAlert("http://example.com/profile/?name=foo", "name", "#{%x(sleep 2)}")
.build());
}

@Override
public Map<String, String> getAlertTags() {
return ALERT_TAGS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.logging.log4j.LogManager;
Expand All @@ -33,6 +34,7 @@
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.core.scanner.Plugin;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.addon.commonlib.SourceSinkUtils;
import org.zaproxy.zap.extension.ascanrules.ssti.DjangoTemplateFormat;
import org.zaproxy.zap.extension.ascanrules.ssti.GoTemplateFormat;
Expand All @@ -52,6 +54,12 @@ public class SstiScanRule extends AbstractAppParamPlugin implements CommonActive
/** Prefix for internationalised messages used by this rule */
private static final String MESSAGE_PREFIX = "ascanrules.ssti.";

private static final Map<String, String> ALERT_TAGS =
CommonAlertTag.toMap(
CommonAlertTag.OWASP_2021_A03_INJECTION,
CommonAlertTag.OWASP_2017_A01_INJECTION,
CommonAlertTag.WSTG_V42_INPV_18_SSTI);

static final String DELIMITER = "zj";

private static final float SIMILARITY_THRESHOLD = 0.75f;
Expand Down Expand Up @@ -124,7 +132,8 @@ public String getReference() {

@Override
public int getCweId() {
return 94; // WE-94: Improper Control of Generation of Code ('Code Injection').
return 1336; // CWE-1336: Improper Neutralization of Special Elements Used in a Template
// Engine
}

@Override
Expand All @@ -137,6 +146,10 @@ public int getRisk() {
return Alert.RISK_HIGH;
}

private static String getOtherInfo(String url, String output) {
return Constant.messages.getString(MESSAGE_PREFIX + "alert.otherinfo", url, output);
}

/**
* Scan for Server Side Template Injection Vulnerabilities
*
Expand Down Expand Up @@ -218,7 +231,7 @@ private void efficientScan(HttpMessage msg, String paramName, String value) {
}
}

private InputPoint createInputPointWithAllSinks(
private static InputPoint createInputPointWithAllSinks(
HttpMessage originalMsg,
String originalValue,
HttpMessage referenceMsg,
Expand Down Expand Up @@ -386,18 +399,13 @@ private void searchForMathsExecution(

if (output.contains(renderResult) && output.matches(regex)) {

String attack =
Constant.messages.getString(
MESSAGE_PREFIX + "alert.otherinfo",
sink.getLocation(),
output);

this.newAlert()
.setConfidence(Alert.CONFIDENCE_HIGH)
.setUri(newMsg.getRequestHeader().getURI().toString())
.setParam(paramName)
.setAttack(renderTest)
.setOtherInfo(attack)
String attack = getOtherInfo(sink.getLocation(), output);

createAlert(
newMsg.getRequestHeader().getURI().toString(),
paramName,
renderTest,
attack)
.setMessage(newMsg)
.raise();
found = true;
Expand All @@ -415,4 +423,47 @@ private void searchForMathsExecution(
}
}
}

private AlertBuilder createAlert(String url, String param, String attack, String otherInfo) {
return newAlert()
.setConfidence(Alert.CONFIDENCE_HIGH)
.setUri(url)
.setParam(param)
.setAttack(attack)
.setOtherInfo(otherInfo);
}

@Override
public List<Alert> getExampleAlerts() {
String output =
"<!DOCTYPE html>\n"
+ "<html>\n"
+ " <head>\n"
+ " <title>Profile</title>\n"
+ " </head>\n"
+ " <body>\n"
+ " <form action=\"/\" method=\"post\">\n"
+ " First name:<br>\n"
+ " <input type=\"text\" name=\"name\" value=\"\">\n"
+ " <input type=\"submit\" value=\"Submit\">\n"
+ " </form>\n"
+ " <h2>Hello zj3790300zj</h2>\n"
+ " </body>\n"
+ "</html>Content-Type: text/html\n"
+ "Date: Mon, 10 Jun 2024 12:33:36 GMT\n"
+ "Connection: keep-alive\n"
+ "Content-Length: 328\n";
String otherInfo = getOtherInfo("http://example.com/profile/?name=test", output);

return List.of(
createAlert(
"http://example.com/profile/?name=zj%23set%28%24x%3D2614*1450%29%24%7Bx%7Dz",
"name", "zj#set($x=2614*1450)${x}zj", otherInfo)
.build());
}

@Override
public Map<String, String> getAlertTags() {
return ALERT_TAGS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@
import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

import fi.iki.elonen.NanoHTTPD.IHTTPSession;
import fi.iki.elonen.NanoHTTPD.Response;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Plugin;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.zap.testutils.NanoServerHandler;

class SstiBlindScanRuleUnitTest extends ActiveScannerTest<SstiBlindScanRule> {
Expand Down Expand Up @@ -127,4 +131,45 @@ protected Response serve(IHTTPSession session) {

assertThat(alertsRaised.size(), equalTo(0));
}

@Test
void shouldReturnExpectedMappings() {
// Given / When
int cwe = rule.getCweId();
int wasc = rule.getWascId();
Map<String, String> tags = rule.getAlertTags();
// Then
assertThat(cwe, is(equalTo(1336)));
assertThat(wasc, is(equalTo(20)));
assertThat(tags.size(), is(equalTo(3)));
assertThat(
tags.containsKey(CommonAlertTag.OWASP_2021_A03_INJECTION.getTag()),
is(equalTo(true)));
assertThat(
tags.containsKey(CommonAlertTag.OWASP_2017_A01_INJECTION.getTag()),
is(equalTo(true)));
assertThat(
tags.containsKey(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getTag()), is(equalTo(true)));
assertThat(
tags.get(CommonAlertTag.OWASP_2021_A03_INJECTION.getTag()),
is(equalTo(CommonAlertTag.OWASP_2021_A03_INJECTION.getValue())));
assertThat(
tags.get(CommonAlertTag.OWASP_2017_A01_INJECTION.getTag()),
is(equalTo(CommonAlertTag.OWASP_2017_A01_INJECTION.getValue())));
assertThat(
tags.get(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getTag()),
is(equalTo(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getValue())));
}

@Test
void shouldHaveExpectedExampleAlert() {
// Given / When
List<Alert> alerts = this.rule.getExampleAlerts();
Alert example = alerts.get(0);
// Then
assertThat(alerts.size(), is(equalTo(1)));
assertThat(example.getConfidence(), is(equalTo(Alert.CONFIDENCE_HIGH)));
assertThat(example.getParam(), is(equalTo("name")));
assertThat(example.getAttack(), is(equalTo("#{%x(sleep 2)}")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThanOrEqualTo;

import fi.iki.elonen.NanoHTTPD.IHTTPSession;
import fi.iki.elonen.NanoHTTPD.Response;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -43,6 +46,7 @@
import org.parosproxy.paros.core.scanner.Plugin.AttackStrength;
import org.parosproxy.paros.network.HttpMalformedHeaderException;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.zap.testutils.NanoServerHandler;
import org.zaproxy.zap.utils.ZapXmlConfiguration;

Expand Down Expand Up @@ -232,7 +236,48 @@ protected Response serve(IHTTPSession session) {
assertThat(alertsRaised.get(0).getConfidence(), equalTo(Alert.CONFIDENCE_HIGH));
}

private String templateRenderMock(String startTag, String endTag, String input)
@Test
void shouldReturnExpectedMappings() {
// Given / When
int cwe = rule.getCweId();
int wasc = rule.getWascId();
Map<String, String> tags = rule.getAlertTags();
// Then
assertThat(cwe, is(equalTo(1336)));
assertThat(wasc, is(equalTo(20)));
assertThat(tags.size(), is(equalTo(3)));
assertThat(
tags.containsKey(CommonAlertTag.OWASP_2021_A03_INJECTION.getTag()),
is(equalTo(true)));
assertThat(
tags.containsKey(CommonAlertTag.OWASP_2017_A01_INJECTION.getTag()),
is(equalTo(true)));
assertThat(
tags.containsKey(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getTag()), is(equalTo(true)));
assertThat(
tags.get(CommonAlertTag.OWASP_2021_A03_INJECTION.getTag()),
is(equalTo(CommonAlertTag.OWASP_2021_A03_INJECTION.getValue())));
assertThat(
tags.get(CommonAlertTag.OWASP_2017_A01_INJECTION.getTag()),
is(equalTo(CommonAlertTag.OWASP_2017_A01_INJECTION.getValue())));
assertThat(
tags.get(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getTag()),
is(equalTo(CommonAlertTag.WSTG_V42_INPV_18_SSTI.getValue())));
}

@Test
void shouldHaveExpectedExampleAlert() {
// Given / When
List<Alert> alerts = rule.getExampleAlerts();
Alert example = alerts.get(0);
// Then
assertThat(alerts.size(), is(equalTo(1)));
assertThat(example.getConfidence(), is(equalTo(Alert.CONFIDENCE_HIGH)));
assertThat(example.getParam(), is(equalTo("name")));
assertThat(example.getAttack(), is(equalTo("zj#set($x=2614*1450)${x}zj")));
}

private static String templateRenderMock(String startTag, String endTag, String input)
throws IllegalArgumentException {
if (!input.contains(startTag)) {
return input;
Expand All @@ -252,7 +297,8 @@ private String templateRenderMock(String startTag, String endTag, String input)
return prefix + expressionResult + suffix;
}

private String getSimpleArithmeticResult(String expression) throws IllegalArgumentException {
private static String getSimpleArithmeticResult(String expression)
throws IllegalArgumentException {
if (expression.contains("+")) {
String[] numbers = expression.split(Pattern.quote("+"), 2);
if (numbers.length == 1 && expression.endsWith("+")) {
Expand Down

0 comments on commit 29e6ab8

Please sign in to comment.