Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions addOns/ascanrules/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- SQL Injection - Hypersonic
- SQL Injection - SQLite
- SQL Injection - PostgreSQL
- The Remote OS Command Injection scan rule has been broken into two rules; one feedback based, and one time based (Issue 7341). This includes assigning the time based rule ID 90037.

### Added
- Rules (as applicable) have been tagged in relation to HIPAA and PCI DSS.
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2025 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.zap.extension.ascanrules;

import java.io.IOException;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
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;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.addon.commonlib.CommonAlertTag;
import org.zaproxy.addon.commonlib.timing.TimingUtils;
import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam;
import org.zaproxy.zap.model.Tech;

/** Active scan rule for time based Command Injection testing and verification. */
public class CommandInjectionTimingScanRule extends CommandInjectionScanRule
implements CommonActiveScanRuleInfo {

private static final int PLUGIN_ID = 90037;

/** The name of the rule config to obtain the time, in seconds, for time-based attacks. */
private static final String RULE_SLEEP_TIME = RuleConfigParam.RULE_COMMON_SLEEP_TIME;

private static final Map<String, String> ALERT_TAGS;

static {
Map<String, String> alertTags =
new HashMap<>(CommonAlertTag.toMap(CommonAlertTag.TEST_TIMING));
alertTags.putAll(CommandInjectionScanRule.ALERT_TAGS);
ALERT_TAGS = Collections.unmodifiableMap(alertTags);
}

/** The default number of seconds used in time-based attacks (i.e. sleep commands). */
private static final int DEFAULT_TIME_SLEEP_SEC = 5;

// limit the maximum number of requests sent for time-based attack detection
private static final int BLIND_REQUESTS_LIMIT = 4;

// error range allowable for statistical time-based blind attacks (0-1.0)
private static final double TIME_CORRELATION_ERROR_RANGE = 0.15;
private static final double TIME_SLOPE_ERROR_RANGE = 0.30;

// *NIX Blind OS Command constants
private static final String NIX_BLIND_TEST_CMD = "sleep {0}";
// Windows Blind OS Command constants
private static final String WIN_BLIND_TEST_CMD = "timeout /T {0}";
// PowerSHell Blind Command constants
private static final String PS_BLIND_TEST_CMD = "start-sleep -s {0}";

// OS Command payloads for blind command Injection testing
private static final List<String> NIX_BLIND_OS_PAYLOADS = new LinkedList<>();
private static final List<String> WIN_BLIND_OS_PAYLOADS = new LinkedList<>();
private static final List<String> PS_BLIND_PAYLOADS = new LinkedList<>();

static {
// No quote payloads
NIX_BLIND_OS_PAYLOADS.add("&" + NIX_BLIND_TEST_CMD + "&");
NIX_BLIND_OS_PAYLOADS.add(";" + NIX_BLIND_TEST_CMD + ";");
WIN_BLIND_OS_PAYLOADS.add("&" + WIN_BLIND_TEST_CMD);
WIN_BLIND_OS_PAYLOADS.add("|" + WIN_BLIND_TEST_CMD);
PS_BLIND_PAYLOADS.add(";" + PS_BLIND_TEST_CMD);

// Double quote payloads
NIX_BLIND_OS_PAYLOADS.add("\"&" + NIX_BLIND_TEST_CMD + "&\"");
NIX_BLIND_OS_PAYLOADS.add("\";" + NIX_BLIND_TEST_CMD + ";\"");
WIN_BLIND_OS_PAYLOADS.add("\"&" + WIN_BLIND_TEST_CMD + "&\"");
WIN_BLIND_OS_PAYLOADS.add("\"|" + WIN_BLIND_TEST_CMD);
PS_BLIND_PAYLOADS.add("\";" + PS_BLIND_TEST_CMD);

// Single quote payloads
NIX_BLIND_OS_PAYLOADS.add("'&" + NIX_BLIND_TEST_CMD + "&'");
NIX_BLIND_OS_PAYLOADS.add("';" + NIX_BLIND_TEST_CMD + ";'");
WIN_BLIND_OS_PAYLOADS.add("'&" + WIN_BLIND_TEST_CMD + "&'");
WIN_BLIND_OS_PAYLOADS.add("'|" + WIN_BLIND_TEST_CMD);
PS_BLIND_PAYLOADS.add("';" + PS_BLIND_TEST_CMD);

// Special payloads
NIX_BLIND_OS_PAYLOADS.add("\n" + NIX_BLIND_TEST_CMD + "\n"); // force enter
NIX_BLIND_OS_PAYLOADS.add("`" + NIX_BLIND_TEST_CMD + "`"); // backtick execution
NIX_BLIND_OS_PAYLOADS.add("||" + NIX_BLIND_TEST_CMD); // or control concatenation
NIX_BLIND_OS_PAYLOADS.add("&&" + NIX_BLIND_TEST_CMD); // and control concatenation
NIX_BLIND_OS_PAYLOADS.add("|" + NIX_BLIND_TEST_CMD + "#"); // pipe & comment
// FoxPro for running os commands
WIN_BLIND_OS_PAYLOADS.add("run " + WIN_BLIND_TEST_CMD);
PS_BLIND_PAYLOADS.add(";" + PS_BLIND_TEST_CMD + " #"); // chain & comment

// uninitialized variable waf bypass
String insertedCMD = CommandInjectionScanRule.insertUninitVar(NIX_BLIND_TEST_CMD);
// No quote payloads
NIX_BLIND_OS_PAYLOADS.add("&" + insertedCMD + "&");
NIX_BLIND_OS_PAYLOADS.add(";" + insertedCMD + ";");
// Double quote payloads
NIX_BLIND_OS_PAYLOADS.add("\"&" + insertedCMD + "&\"");
NIX_BLIND_OS_PAYLOADS.add("\";" + insertedCMD + ";\"");
// Single quote payloads
NIX_BLIND_OS_PAYLOADS.add("'&" + insertedCMD + "&'");
NIX_BLIND_OS_PAYLOADS.add("';" + insertedCMD + ";'");
// Special payloads
NIX_BLIND_OS_PAYLOADS.add("\n" + insertedCMD + "\n");
NIX_BLIND_OS_PAYLOADS.add("`" + insertedCMD + "`");
NIX_BLIND_OS_PAYLOADS.add("||" + insertedCMD);
NIX_BLIND_OS_PAYLOADS.add("&&" + insertedCMD);
NIX_BLIND_OS_PAYLOADS.add("|" + insertedCMD + "#");
}

private static final Logger LOGGER = LogManager.getLogger(CommandInjectionTimingScanRule.class);

/** The number of seconds used in time-based attacks (i.e. sleep commands). */
private int timeSleepSeconds = DEFAULT_TIME_SLEEP_SEC;

@Override
public int getId() {
return PLUGIN_ID;
}

@Override
public String getName() {
return Constant.messages.getString(MESSAGE_PREFIX + "time.name");
}

@Override
public void init() {
try {
timeSleepSeconds = this.getConfig().getInt(RULE_SLEEP_TIME, DEFAULT_TIME_SLEEP_SEC);
} catch (ConversionException e) {
LOGGER.debug(
"Invalid value for '{}': {}",
RULE_SLEEP_TIME,
this.getConfig().getString(RULE_SLEEP_TIME));
}
LOGGER.debug("Sleep set to {} seconds", timeSleepSeconds);
}

/**
* Gets the number of seconds used in time-based attacks.
*
* <p><strong>Note:</strong> Method provided only to ease the unit tests.
*
* @return the number of seconds used in time-based attacks.
*/
int getTimeSleep() {
return timeSleepSeconds;
}

@Override
public Map<String, String> getAlertTags() {
return CommandInjectionTimingScanRule.ALERT_TAGS;
}

@Override
public void scan(HttpMessage msg, String paramName, String value) {
LOGGER.debug(
"Checking [{}][{}], parameter [{}] for OS Command Injection Vulnerabilities",
msg.getRequestHeader().getMethod(),
msg.getRequestHeader().getURI(),
paramName);

int blindTargetCount = 0;
switch (this.getAttackStrength()) {
case LOW:
blindTargetCount = 2;
break;
case MEDIUM:
blindTargetCount = 6;
break;
case HIGH:
blindTargetCount = 12;
break;
case INSANE:
blindTargetCount =
Math.max(
PS_BLIND_PAYLOADS.size(),
(Math.max(
NIX_BLIND_OS_PAYLOADS.size(),
WIN_BLIND_OS_PAYLOADS.size())));
break;
default:
// Default to off
}

if (inScope(Tech.Linux) || inScope(Tech.MacOS)) {
if (testCommandInjection(paramName, value, blindTargetCount, NIX_BLIND_OS_PAYLOADS)) {
return;
}
}

if (isStop()) {
return;
}

if (inScope(Tech.Windows)) {
// Windows Command Prompt
if (testCommandInjection(paramName, value, blindTargetCount, WIN_BLIND_OS_PAYLOADS)) {
return;
}
// Check if the user has stopped the scan
if (isStop()) {
return;
}
// Windows PowerShell
testCommandInjection(paramName, value, blindTargetCount, PS_BLIND_PAYLOADS);
}
}

/**
* Tests for injection vulnerabilities with the given payloads.
*
* @param paramName the name of the parameter that will be used for testing for injection
* @param value the value of the parameter that will be used for testing for injection
* @param blindTargetCount the number of requests for blind payloads
* @param blindOsPayloads the blind payloads
* @return {@code true} if the vulnerability was found, {@code false} otherwise.
*/
private boolean testCommandInjection(
String paramName, String value, int blindTargetCount, List<String> blindOsPayloads) {

String paramValue;

// -----------------------------------------------
// Check: Time-based Blind OS Command Injection
// -----------------------------------------------
// Check for a sleep shell execution by using
// linear regression to check for a correlation
// between requested delay and actual delay.
// -----------------------------------------------

Iterator<String> it = blindOsPayloads.iterator();

for (int i = 0; it.hasNext() && (i < blindTargetCount); i++) {
AtomicReference<HttpMessage> message = new AtomicReference<>();
String sleepPayload = it.next();
paramValue = value + sleepPayload.replace("{0}", String.valueOf(timeSleepSeconds));

TimingUtils.RequestSender requestSender =
x -> {
HttpMessage msg = getNewMsg();
message.set(msg);
String finalPayload =
value + sleepPayload.replace("{0}", String.valueOf(x));
setParameter(msg, paramName, finalPayload);
LOGGER.debug("Testing [{}] = [{}]", paramName, finalPayload);

sendAndReceive(msg, false);
return msg.getTimeElapsedMillis() / 1000.0;
};

boolean isInjectable;
try {
try {
// use TimingUtils to detect a response to sleep payloads
isInjectable =
TimingUtils.checkTimingDependence(
BLIND_REQUESTS_LIMIT,
timeSleepSeconds,
requestSender,
TIME_CORRELATION_ERROR_RANGE,
TIME_SLOPE_ERROR_RANGE);
} catch (SocketException ex) {
LOGGER.debug(
"Caught {} {} when accessing: {}.\n The target may have replied with a poorly formed redirect due to our input.",
ex.getClass().getName(),
ex.getMessage(),
message.get().getRequestHeader().getURI());
continue; // Something went wrong, move to next blind iteration
}

if (isInjectable) {
LOGGER.debug(
"[Blind OS Command Injection Found] on parameter [{}] with value [{}]",
paramName,
paramValue);

// Attach this alert to the last sent message
buildAlert(paramName, paramValue, message.get()).raise();

return true;
}
} catch (IOException ex) {
LOGGER.warn(
"Blind Command Injection vulnerability check failed for parameter [{}] and payload [{}] due to an I/O error",
paramName,
paramValue,
ex);
}
if (isStop()) {
return false;
}
}
return false;
}

AlertBuilder buildAlert(String param, String attack, HttpMessage msg) {
return buildAlert(param, attack, "", msg)
.setOtherInfo(
Constant.messages.getString(MESSAGE_PREFIX + "time.otherinfo", attack));
}

@Override
public List<Alert> getExampleAlerts() {
return List.of(buildAlert("qry", "sleep 5", null).build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,27 @@ <H2 id="id-90019">Code Injection</H2>
<br>
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/90019/">90019</a>.

<H2 id="id-90020">Command Injection</H2>
<H2 id="id-90020">Remote OS Command Injection</H2>

This rule submits *NIX and Windows OS commands as URL parameter values to determine whether or not the web application is passing unchecked
This rule injects *NIX and Windows OS commands to determine whether or not the web application is passing unchecked
user input directly to the underlying OS. The injection strings consist of meta-characters that may be interpreted by the OS
as join commands along with a payload that should generate output in the response if the application is vulnerable. If the content of a response body
matches the payload, the scanner raises an alert and returns immediately. In the event that none of the error-based matching attempts
return output in the response, the scanner will attempt a blind injection attack by submitting sleep instructions as the payload and comparing the elapsed time between sending the request
and receiving the response against a heuristic time-delay lower limit. If the elapsed time is greater than this limit, an alert is raised with medium confidence
and the scanner returns immediately.
as join commands along with a payload that should generate output in the response if the application is vulnerable.
<p>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/CommandInjectionScanRule.java">CommandInjectionScanRule.java</a>
<br>
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/90020/">90020</a>.

<H2 id="id-90037">Remote OS Command Injection (Time Based)</H2>

This rule injects *NIX and Windows OS commands to determine whether or not the web application is passing unchecked
user input directly to the underlying OS. The rule will attempt blind injection attack(s) by submitting sleep instructions as the payload and comparing the elapsed time between sending the request
and receiving the response against a heuristic time-delay lower limit.
<br>
Post 2.5.0 you can change the length of time used for the blind injection attack by changing the <code>rules.common.sleep</code> parameter via the Options 'Rule configuration' panel.
<p>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/CommandInjectionScaRule.java">CommandInjectionScaRule.java</a>
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/CommandInjectionTimingScanRule.java">CommandInjectionTimingScanRule.java</a>
<br>
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/90020/">90020</a>.
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/90037/">90037</a>.

<H2 id="id-40012">Cross Site Scripting (Reflected)</H2>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ ascanrules.codeinjection.soln = Do not trust client side input, even if there is

ascanrules.commandinjection.desc = Attack technique used for unauthorized execution of operating system commands. This attack is possible when an application accepts untrusted input to build operating system commands in an insecure manner involving improper data sanitization, and/or improper calling of external programs.
ascanrules.commandinjection.name = Remote OS Command Injection
ascanrules.commandinjection.otherinfo.feedback-based = The scan rule was able to retrieve the content of a file or command by sending [{0}] to the operating system running this application.
ascanrules.commandinjection.otherinfo.time-based = The scan rule was able to control the timing of the application response by sending [{0}] to the operating system running this application.
ascanrules.commandinjection.otherinfo = The scan rule was able to retrieve the content of a file or command by sending [{0}] to the operating system running this application.
ascanrules.commandinjection.refs = https://cwe.mitre.org/data/definitions/78.html\nhttps://owasp.org/www-community/attacks/Command_Injection

ascanrules.commandinjection.time.name = Remote OS Command Injection (Time Based)
ascanrules.commandinjection.time.otherinfo = The scan rule was able to control the timing of the application response by sending [{0}] to the operating system running this application.

ascanrules.crlfinjection.desc = Cookie can be set via CRLF injection. It may also be possible to set arbitrary HTTP response headers. In addition, by carefully crafting the injected response using cross-site script, cache poisoning vulnerability may also exist.
ascanrules.crlfinjection.name = CRLF Injection
ascanrules.crlfinjection.refs = https://owasp.org/www-community/vulnerabilities/CRLF_Injection\nhttps://cwe.mitre.org/data/definitions/113.html
Expand Down
Loading