Skip to content

Commit 3d50e7e

Browse files
committed
ascanrules: SQLi SQLite split timing tests to new scan rule
Signed-off-by: kingthorin <[email protected]>
1 parent f7c538d commit 3d50e7e

File tree

7 files changed

+953
-648
lines changed

7 files changed

+953
-648
lines changed

addOns/ascanrules/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
77
### Changed
88
- Maintenance changes.
99
- Depends on an updated version of the Common Library add-on.
10+
- The SQL Injection SQLite scan rule has been broken into two rules; one union based, and one time based (Issue 7341). This includes assigning the timing rule ID 90038, and updating the add-on's help content.
1011

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

addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionSqLiteScanRule.java

Lines changed: 116 additions & 531 deletions
Large diffs are not rendered by default.

addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionSqLiteTimingScanRule.java

Lines changed: 600 additions & 0 deletions
Large diffs are not rendered by default.

addOns/ascanrules/src/main/javahelp/org/zaproxy/zap/extension/ascanrules/resources/help/contents/ascanrules.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ <H2 id="id-40024">SQL Injection - SQLite</H2>
433433
<br>
434434
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/40024/">40024</a>.
435435

436+
<H2 id="id-90038">SQL Injection - SQLite (Time Based)</H2>
437+
This active scan rule attempts to inject SQLite specific commands into parameter values and analyzes the timing of server responses to see if the commands were effectively executed on the server (indicating a successful SQL injection attack).
438+
<p>
439+
Latest code: <a href="https://github.com/zaproxy/zap-extensions/blob/main/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionSqLiteTimingScanRule.java">SqlInjectionSqLiteTimingScanRule.java</a>
440+
<br>
441+
Alert ID: <a href="https://www.zaproxy.org/docs/alerts/90038/">90038</a>.
442+
436443
<H2 id="id-40029">Trace.axd Information Leak</H2>
437444
Tests to see if Trace Viewer (trace.axd) is available. Although this component is convenient for developers and
438445
other stakeholders it can leak a significant amount of information which a security analyst or malicious individual

addOns/ascanrules/src/main/resources/org/zaproxy/zap/extension/ascanrules/resources/Messages.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ ascanrules.sqlinjection.postgres.name = SQL Injection - PostgreSQL
190190
ascanrules.sqlinjection.refs = https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
191191
ascanrules.sqlinjection.soln = Do not trust client side input, even if there is client side validation in place.\nIn general, type check all data on the server side.\nIf the application uses JDBC, use PreparedStatement or CallableStatement, with parameters passed by '?'\nIf the application uses ASP, use ADO Command Objects with strong type checking and parameterized queries.\nIf database Stored Procedures can be used, use them.\nDo *not* concatenate strings into queries in the stored procedure, or use 'exec', 'exec immediate', or equivalent functionality!\nDo not create dynamic SQL queries using simple string concatenation.\nEscape all data received from the client.\nApply an 'allow list' of allowed characters, or a 'deny list' of disallowed characters in user input.\nApply the principle of least privilege by using the least privileged database user possible.\nIn particular, avoid using the 'sa' or 'db-owner' database users. This does not eliminate SQL injection, but minimizes its impact.\nGrant the minimum database access that is necessary for the application.
192192
ascanrules.sqlinjection.sqlite.alert.errorbased.extrainfo = The following known SQLite error message was provoked: [{0}].
193-
ascanrules.sqlinjection.sqlite.alert.timebased.extrainfo = The query time is controllable using parameter value [{0}], which caused the request to take [{1}] milliseconds, parameter value [{2}], which caused the request to take [{3}] milliseconds, when the original unmodified query with value [{4}] took [{5}] milliseconds.
193+
ascanrules.sqlinjection.sqlite.alert.timing.extrainfo = The query time is controllable using parameter value [{0}], which caused the request to take [{1}] milliseconds, parameter value [{2}], which caused the request to take [{3}] milliseconds, when the original unmodified query with value [{4}] took [{5}] milliseconds.
194194
ascanrules.sqlinjection.sqlite.alert.versionnumber.extrainfo = Using a UNION based SQL Injection attack, and by exploiting SQLite's dynamic typing mechanism, the SQLite version was determined to be [{0}].\nWith string-based injection points, full SQLite version information can be extracted, but with numeric injection points, only partial SQLite version information can be extracted.\nMore information on SQLite version [{0}] is available at https://www.sqlite.org/changes.html
195195
ascanrules.sqlinjection.sqlite.name = SQL Injection - SQLite
196+
ascanrules.sqlinjection.sqlite.timing.name = SQL Injection - SQLite (Time Based)
196197

197198
ascanrules.ssti.alert.otherinfo = Proof found at [{0}]\ncontent:\n[{1}]
198199
ascanrules.ssti.desc = When the user input is inserted in the template instead of being used as argument in rendering is evaluated by the template engine. Depending on the template engine it can lead to remote code execution.

addOns/ascanrules/src/test/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionSQLiteScanRuleUnitTest.java

Lines changed: 3 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,17 @@
1919
*/
2020
package org.zaproxy.zap.extension.ascanrules;
2121

22-
import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse;
2322
import static org.hamcrest.MatcherAssert.assertThat;
2423
import static org.hamcrest.Matchers.equalTo;
2524
import static org.hamcrest.Matchers.is;
26-
import static org.hamcrest.Matchers.startsWith;
2725

28-
import fi.iki.elonen.NanoHTTPD.IHTTPSession;
29-
import fi.iki.elonen.NanoHTTPD.Response;
3026
import java.util.Map;
3127
import org.junit.jupiter.api.Test;
32-
import org.parosproxy.paros.core.scanner.Alert;
3328
import org.parosproxy.paros.core.scanner.Plugin;
34-
import org.parosproxy.paros.network.HttpMessage;
3529
import org.zaproxy.addon.commonlib.CommonAlertTag;
3630
import org.zaproxy.addon.commonlib.PolicyTag;
3731
import org.zaproxy.zap.model.Tech;
3832
import org.zaproxy.zap.model.TechSet;
39-
import org.zaproxy.zap.testutils.NanoServerHandler;
4033

4134
/** Unit test for {@link SqlInjectionSqLiteScanRule}. */
4235
class SqlInjectionSQLiteScanRuleUnitTest extends ActiveScannerTest<SqlInjectionSqLiteScanRule> {
@@ -54,11 +47,11 @@ protected int getRecommendMaxNumberMessagesPerParam(Plugin.AttackStrength streng
5447
return NUMBER_MSGS_ATTACK_STRENGTH_LOW;
5548
case MEDIUM:
5649
default:
57-
return NUMBER_MSGS_ATTACK_STRENGTH_MEDIUM + 2;
50+
return NUMBER_MSGS_ATTACK_STRENGTH_MEDIUM;
5851
case HIGH:
59-
return NUMBER_MSGS_ATTACK_STRENGTH_HIGH + 10;
52+
return NUMBER_MSGS_ATTACK_STRENGTH_HIGH;
6053
case INSANE:
61-
return NUMBER_MSGS_ATTACK_STRENGTH_INSANE + 22;
54+
return NUMBER_MSGS_ATTACK_STRENGTH_INSANE + 126;
6255
}
6356
}
6457

@@ -82,112 +75,6 @@ void shouldNotTargetNonSqLiteSQLTechs() throws Exception {
8275
assertThat(targets, is(equalTo(false)));
8376
}
8477

85-
@Test
86-
void shouldAlertIfSqlErrorReturned() throws Exception {
87-
String test = "/shouldReportSqlErrorMessage/";
88-
89-
this.nano.addHandler(
90-
new NanoServerHandler(test) {
91-
@Override
92-
protected Response serve(IHTTPSession session) {
93-
String name = getFirstParamValue(session, "name");
94-
String response = "<html><body></body></html>";
95-
if (name != null && name.contains(" randomblob(")) {
96-
response =
97-
"<html><body>SQL error: no such function: randomblob</body></html>";
98-
}
99-
return newFixedLengthResponse(response);
100-
}
101-
});
102-
103-
HttpMessage msg = this.getHttpMessage(test + "?name=test");
104-
105-
this.rule.init(msg, this.parent);
106-
107-
this.rule.scan();
108-
109-
assertThat(alertsRaised.size(), equalTo(1));
110-
assertThat(alertsRaised.get(0).getEvidence(), equalTo("no such function: randomblob"));
111-
assertThat(alertsRaised.get(0).getParam(), equalTo("name"));
112-
assertThat(
113-
alertsRaised.get(0).getAttack(),
114-
equalTo("case randomblob(100000) when not null then 1 else 1 end "));
115-
assertThat(alertsRaised.get(0).getRisk(), equalTo(Alert.RISK_HIGH));
116-
assertThat(alertsRaised.get(0).getConfidence(), equalTo(Alert.CONFIDENCE_MEDIUM));
117-
}
118-
119-
@Test
120-
void shouldAlertIfRandomBlobTimesGetLonger() throws Exception {
121-
String test = "/shouldReportSqlTimingIssue/";
122-
123-
this.nano.addHandler(
124-
new NanoServerHandler(test) {
125-
private int time = 100;
126-
127-
@Override
128-
protected Response serve(IHTTPSession session) {
129-
String name = getFirstParamValue(session, "name");
130-
String response = "<html><body></body></html>";
131-
if (name != null && name.contains(" randomblob(")) {
132-
try {
133-
Thread.sleep(time);
134-
} catch (InterruptedException e) {
135-
// Ignore
136-
}
137-
time += 100;
138-
}
139-
return newFixedLengthResponse(response);
140-
}
141-
});
142-
143-
HttpMessage msg = this.getHttpMessage(test + "?name=test");
144-
145-
this.rule.init(msg, this.parent);
146-
this.rule.setExpectedDelayInMs(90);
147-
148-
this.rule.scan();
149-
150-
assertThat(alertsRaised.size(), equalTo(1));
151-
assertThat(
152-
alertsRaised.get(0).getEvidence(),
153-
startsWith("The query time is controllable using parameter value"));
154-
assertThat(alertsRaised.get(0).getParam(), equalTo("name"));
155-
assertThat(alertsRaised.get(0).getAttack(), startsWith("case randomblob(100"));
156-
assertThat(alertsRaised.get(0).getRisk(), equalTo(Alert.RISK_HIGH));
157-
assertThat(alertsRaised.get(0).getConfidence(), equalTo(Alert.CONFIDENCE_MEDIUM));
158-
}
159-
160-
@Test
161-
void shouldNotAlertIfAllTimesGetLonger() throws Exception {
162-
String test = "/shouldReportSqlTimingIssue/";
163-
164-
this.nano.addHandler(
165-
new NanoServerHandler(test) {
166-
private int time = 100;
167-
168-
@Override
169-
protected Response serve(IHTTPSession session) {
170-
String response = "<html><body></body></html>";
171-
try {
172-
Thread.sleep(time);
173-
} catch (InterruptedException e) {
174-
// Ignore
175-
}
176-
time += 100;
177-
return newFixedLengthResponse(response);
178-
}
179-
});
180-
181-
HttpMessage msg = this.getHttpMessage(test + "?name=test");
182-
183-
this.rule.init(msg, this.parent);
184-
this.rule.setExpectedDelayInMs(90);
185-
186-
this.rule.scan();
187-
188-
assertThat(alertsRaised.size(), equalTo(0));
189-
}
190-
19178
@Test
19279
void shouldReturnExpectedMappings() {
19380
// Given / When

0 commit comments

Comments
 (0)