Skip to content

Commit 48c98ca

Browse files
committed
ascanrules: Add Example Alerts for Time Based SQLi rules
Signed-off-by: kingthorin <kingthorin@users.noreply.github.com>
1 parent 9c87497 commit 48c98ca

11 files changed

Lines changed: 324 additions & 82 deletions

addOns/ascanrules/CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

66
## Unreleased
77
### Changed
8-
- The Cross Site Scripting (Persistent) rule now includes example alert functionality for documentation generation purposes (Issue 6119) and alert references (Issue 7100).
8+
- The Cross Site Scripting (Persistent) rule now includes alert references (Issue 7100).
9+
- The following scan rules now include example alert functionality for documentation generation purposes (Issue 6119):
10+
- SQL Injection - Hypersonic SQL (Time Based)
11+
- SQL Injection - MsSQL (Time Based)
12+
- SQL Injection - MySQL (Time Based)
13+
- SQL Injection - Oracle (Time Based)
14+
- SQL Injection - PostgreSQL (Time Based)
15+
- Cross Site Scripting (Persistent)
916

1017
## [81] - 2026-04-14
1118
### Changed

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

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,7 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
298298
message.compareAndSet(null, msg);
299299

300300
String finalPayload =
301-
sleepPayload
302-
.replace(ORIG_VALUE_TOKEN, paramValue)
303-
// Time in milliseconds for the SQL function.
304-
.replace(
305-
SLEEP_TOKEN,
306-
String.valueOf(
307-
TimeUnit.SECONDS.toMillis((long) x)));
301+
assembleTimingPayload(sleepPayload, paramValue, (long) x);
308302

309303
setParameter(msg, paramName, finalPayload);
310304
LOGGER.debug("Testing [{}] = [{}]", paramName, finalPayload);
@@ -329,18 +323,13 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
329323
paramName,
330324
attack.get());
331325

332-
newAlert()
333-
.setConfidence(Alert.CONFIDENCE_MEDIUM)
334-
.setUri(getBaseMsg().getRequestHeader().getURI().toString())
335-
.setParam(paramName)
336-
.setAttack(attack.get())
337-
.setOtherInfo(
338-
Constant.messages.getString(
339-
"ascanrules.sqlinjection.alert.timebased.extrainfo",
340-
attack.get(),
341-
message.get().getTimeElapsedMillis(),
342-
paramValue,
343-
getBaseMsg().getTimeElapsedMillis()))
326+
buildAlert(
327+
getBaseMsg().getRequestHeader().getURI().toString(),
328+
paramName,
329+
paramValue,
330+
attack.get(),
331+
message.get().getTimeElapsedMillis(),
332+
getBaseMsg().getTimeElapsedMillis())
344333
.setMessage(message.get())
345334
.raise();
346335
break;
@@ -361,6 +350,49 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
361350
}
362351
}
363352

353+
private static String assembleTimingPayload(
354+
String template, String paramValue, long sleepSeconds) {
355+
return template.replace(ORIG_VALUE_TOKEN, paramValue)
356+
.replace(SLEEP_TOKEN, String.valueOf(TimeUnit.SECONDS.toMillis(sleepSeconds)));
357+
}
358+
359+
private AlertBuilder buildAlert(
360+
String uri,
361+
String paramName,
362+
String paramValue,
363+
String attackValue,
364+
long attackElapsedMillis,
365+
long originalElapsedMillis) {
366+
return newAlert()
367+
.setConfidence(Alert.CONFIDENCE_MEDIUM)
368+
.setUri(uri)
369+
.setParam(paramName)
370+
.setAttack(attackValue)
371+
.setOtherInfo(
372+
Constant.messages.getString(
373+
"ascanrules.sqlinjection.alert.timebased.extrainfo",
374+
attackValue,
375+
attackElapsedMillis,
376+
paramValue,
377+
originalElapsedMillis));
378+
}
379+
380+
@Override
381+
public List<Alert> getExampleAlerts() {
382+
return List.of(
383+
buildAlert(
384+
"https://example.com/?name=test",
385+
"name",
386+
"test",
387+
assembleTimingPayload(
388+
SQL_HYPERSONIC_TIME_REPLACEMENTS.get(3),
389+
"test",
390+
DEFAULT_SLEEP_TIME),
391+
TimeUnit.SECONDS.toMillis(DEFAULT_SLEEP_TIME),
392+
100L)
393+
.build());
394+
}
395+
364396
void setTimeSleepSeconds(int timeSleepSeconds) {
365397
this.timeSleepSeconds = timeSleepSeconds;
366398
}

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

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,7 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
243243
message.compareAndSet(null, msg);
244244

245245
String finalPayload =
246-
sleepPayload
247-
.replace(ORIG_VALUE_TOKEN, paramValue)
248-
.replace(SLEEP_TOKEN, getSleepToken((int) x));
246+
assembleTimingPayload(sleepPayload, paramValue, (int) x);
249247

250248
setParameter(msg, paramName, finalPayload);
251249
LOGGER.debug("Testing [{}] = [{}]", paramName, finalPayload);
@@ -270,20 +268,13 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
270268
paramName,
271269
attack.get());
272270

273-
String extraInfo =
274-
Constant.messages.getString(
275-
"ascanrules.sqlinjection.mssql.alert.timebased.extrainfo",
271+
buildAlert(
272+
getBaseMsg().getRequestHeader().getURI().toString(),
273+
paramName,
274+
paramValue,
276275
attack.get(),
277276
message.get().getTimeElapsedMillis(),
278-
paramValue,
279-
getBaseMsg().getTimeElapsedMillis());
280-
281-
newAlert()
282-
.setConfidence(Alert.CONFIDENCE_MEDIUM)
283-
.setUri(getBaseMsg().getRequestHeader().getURI().toString())
284-
.setParam(paramName)
285-
.setAttack(attack.get())
286-
.setOtherInfo(extraInfo)
277+
getBaseMsg().getTimeElapsedMillis())
287278
.setMessage(message.get())
288279
.raise();
289280
break;
@@ -304,6 +295,49 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
304295
}
305296
}
306297

298+
private static String assembleTimingPayload(
299+
String template, String paramValue, int sleepSeconds) {
300+
return template.replace(ORIG_VALUE_TOKEN, paramValue)
301+
.replace(SLEEP_TOKEN, getSleepToken(sleepSeconds));
302+
}
303+
304+
private AlertBuilder buildAlert(
305+
String uri,
306+
String paramName,
307+
String paramValue,
308+
String attackValue,
309+
long attackElapsedMillis,
310+
long originalElapsedMillis) {
311+
return newAlert()
312+
.setConfidence(Alert.CONFIDENCE_MEDIUM)
313+
.setUri(uri)
314+
.setParam(paramName)
315+
.setAttack(attackValue)
316+
.setOtherInfo(
317+
Constant.messages.getString(
318+
"ascanrules.sqlinjection.mssql.alert.timebased.extrainfo",
319+
attackValue,
320+
attackElapsedMillis,
321+
paramValue,
322+
originalElapsedMillis));
323+
}
324+
325+
@Override
326+
public List<Alert> getExampleAlerts() {
327+
return List.of(
328+
buildAlert(
329+
"https://example.com/?name=test",
330+
"name",
331+
"test",
332+
assembleTimingPayload(
333+
SQL_MSSQL_TIME_REPLACEMENTS.get(4),
334+
"test",
335+
DEFAULT_SLEEP_TIME),
336+
TimeUnit.SECONDS.toMillis(DEFAULT_SLEEP_TIME),
337+
100L)
338+
.build());
339+
}
340+
307341
private static String getSleepToken(int totalTimeInSeconds) {
308342
long hoursInTotalTime = TimeUnit.SECONDS.toHours(totalTimeInSeconds);
309343
totalTimeInSeconds %= 3600;

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

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,7 @@ public void scan(HttpMessage originalMessage, String paramName, String originalP
304304
message.compareAndSet(null, msg);
305305

306306
String finalPayload =
307-
sleepPayload
308-
.replace(ORIG_VALUE_TOKEN, originalParamValue)
309-
.replace(SLEEP_TOKEN, Integer.toString((int) x));
307+
assembleTimingPayload(sleepPayload, originalParamValue, (int) x);
310308

311309
setParameter(msg, paramName, finalPayload);
312310
LOGGER.debug("Testing [{}] = [{}]", paramName, finalPayload);
@@ -331,18 +329,13 @@ public void scan(HttpMessage originalMessage, String paramName, String originalP
331329
paramName,
332330
attack.get());
333331

334-
newAlert()
335-
.setConfidence(Alert.CONFIDENCE_MEDIUM)
336-
.setUri(getBaseMsg().getRequestHeader().getURI().toString())
337-
.setParam(paramName)
338-
.setAttack(attack.get())
339-
.setOtherInfo(
340-
Constant.messages.getString(
341-
"ascanrules.sqlinjection.alert.timebased.extrainfo",
342-
attack.get(),
343-
message.get().getTimeElapsedMillis(),
344-
originalParamValue,
345-
getBaseMsg().getTimeElapsedMillis()))
332+
buildAlert(
333+
getBaseMsg().getRequestHeader().getURI().toString(),
334+
paramName,
335+
originalParamValue,
336+
attack.get(),
337+
message.get().getTimeElapsedMillis(),
338+
getBaseMsg().getTimeElapsedMillis())
346339
.setMessage(message.get())
347340
.raise();
348341
break;
@@ -363,6 +356,49 @@ public void scan(HttpMessage originalMessage, String paramName, String originalP
363356
}
364357
}
365358

359+
private static String assembleTimingPayload(
360+
String template, String paramValue, int sleepSeconds) {
361+
return template.replace(ORIG_VALUE_TOKEN, paramValue)
362+
.replace(SLEEP_TOKEN, Integer.toString(sleepSeconds));
363+
}
364+
365+
private AlertBuilder buildAlert(
366+
String uri,
367+
String paramName,
368+
String paramValue,
369+
String attackValue,
370+
long attackElapsedMillis,
371+
long originalElapsedMillis) {
372+
return newAlert()
373+
.setConfidence(Alert.CONFIDENCE_MEDIUM)
374+
.setUri(uri)
375+
.setParam(paramName)
376+
.setAttack(attackValue)
377+
.setOtherInfo(
378+
Constant.messages.getString(
379+
"ascanrules.sqlinjection.alert.timebased.extrainfo",
380+
attackValue,
381+
attackElapsedMillis,
382+
paramValue,
383+
originalElapsedMillis));
384+
}
385+
386+
@Override
387+
public List<Alert> getExampleAlerts() {
388+
return List.of(
389+
buildAlert(
390+
"https://example.com/?name=test",
391+
"name",
392+
"test",
393+
assembleTimingPayload(
394+
SQL_MYSQL_TIME_REPLACEMENTS.get(1),
395+
"test",
396+
DEFAULT_SLEEP_TIME),
397+
TimeUnit.SECONDS.toMillis(DEFAULT_SLEEP_TIME),
398+
100L)
399+
.build());
400+
}
401+
366402
public void setSleepInSeconds(int sleep) {
367403
this.timeSleepSeconds = sleep;
368404
}

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

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.util.Collections;
2424
import java.util.HashMap;
25+
import java.util.List;
2526
import java.util.Map;
2627
import java.util.concurrent.TimeUnit;
2728
import java.util.concurrent.atomic.AtomicReference;
@@ -224,13 +225,13 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
224225
return;
225226
}
226227
AtomicReference<HttpMessage> message = new AtomicReference<>();
227-
String payloadValue = PAYLOADS[payloadIndex].replace(ORIG_VALUE_TOKEN, paramValue);
228+
String payloadTemplate = PAYLOADS[payloadIndex];
228229
TimingUtils.RequestSender requestSender =
229230
x -> {
230231
HttpMessage timedMsg = getNewMsg();
231232
message.compareAndSet(null, timedMsg);
232233
String finalPayload =
233-
payloadValue.replace(SLEEP_TOKEN, String.valueOf((int) x));
234+
assembleTimingPayload(payloadTemplate, paramValue, (int) x);
234235
setParameter(timedMsg, paramName, finalPayload);
235236
sendAndReceive(timedMsg, false); // do not follow redirects
236237
return TimeUnit.MILLISECONDS.toSeconds(timedMsg.getTimeElapsedMillis());
@@ -256,31 +257,66 @@ public void scan(HttpMessage originalMessage, String paramName, String paramValu
256257

257258
if (isInjectable) {
258259
String finalPayloadValue =
259-
payloadValue.replace(SLEEP_TOKEN, String.valueOf(sleepInSeconds));
260+
assembleTimingPayload(payloadTemplate, paramValue, sleepInSeconds);
260261
LOGGER.debug(
261262
"Time Based Oracle SQL Injection - Found on parameter [{}] with value [{}]",
262263
paramName,
263264
paramValue);
264265

265-
newAlert()
266-
.setConfidence(Alert.CONFIDENCE_MEDIUM)
267-
.setUri(getBaseMsg().getRequestHeader().getURI().toString())
268-
.setParam(paramName)
269-
.setAttack(finalPayloadValue)
266+
buildAlert(
267+
getBaseMsg().getRequestHeader().getURI().toString(),
268+
paramName,
269+
paramValue,
270+
finalPayloadValue,
271+
message.get().getTimeElapsedMillis(),
272+
getBaseMsg().getTimeElapsedMillis())
270273
.setMessage(message.get())
271-
.setOtherInfo(
272-
Constant.messages.getString(
273-
"ascanrules.sqlinjection.alert.timebased.extrainfo",
274-
finalPayloadValue,
275-
message.get().getTimeElapsedMillis(),
276-
paramValue,
277-
getBaseMsg().getTimeElapsedMillis()))
278274
.raise();
279275
return;
280276
}
281277
}
282278
}
283279

280+
private static String assembleTimingPayload(
281+
String template, String paramValue, int sleepSeconds) {
282+
return template.replace(ORIG_VALUE_TOKEN, paramValue)
283+
.replace(SLEEP_TOKEN, String.valueOf(sleepSeconds));
284+
}
285+
286+
private AlertBuilder buildAlert(
287+
String uri,
288+
String paramName,
289+
String paramValue,
290+
String attackValue,
291+
long attackElapsedMillis,
292+
long originalElapsedMillis) {
293+
return newAlert()
294+
.setConfidence(Alert.CONFIDENCE_MEDIUM)
295+
.setUri(uri)
296+
.setParam(paramName)
297+
.setAttack(attackValue)
298+
.setOtherInfo(
299+
Constant.messages.getString(
300+
"ascanrules.sqlinjection.alert.timebased.extrainfo",
301+
attackValue,
302+
attackElapsedMillis,
303+
paramValue,
304+
originalElapsedMillis));
305+
}
306+
307+
@Override
308+
public List<Alert> getExampleAlerts() {
309+
return List.of(
310+
buildAlert(
311+
"https://example.com/?name=test",
312+
"name",
313+
"test",
314+
assembleTimingPayload(PAYLOADS[1], "test", DEFAULT_TIME_SLEEP_SEC),
315+
TimeUnit.SECONDS.toMillis(DEFAULT_TIME_SLEEP_SEC),
316+
100L)
317+
.build());
318+
}
319+
284320
public void setSleepInSeconds(int sleep) {
285321
this.sleepInSeconds = sleep;
286322
}

0 commit comments

Comments
 (0)