From 1919a22510460601805fdab3896b775f79c51528 Mon Sep 17 00:00:00 2001 From: FiveOFive Date: Wed, 30 Oct 2024 15:41:05 -0700 Subject: [PATCH] unit tests for error and union based sql injection Signed-off-by: FiveOFive --- .../ascanrules/SqlInjectionScanRule.java | 4 +- .../SqlInjectionScanRuleUnitTest.java | 192 ++++++++++++++++++ 2 files changed, 194 insertions(+), 2 deletions(-) diff --git a/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRule.java b/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRule.java index 6cfa31a8e43..c31b86dc50e 100644 --- a/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRule.java +++ b/addOns/ascanrules/src/main/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRule.java @@ -120,7 +120,7 @@ public class SqlInjectionScanRule extends AbstractAppParamPlugin * maximise SQL errors Note that we do separate runs for each family of characters, in case one * family are filtered out, the others might still get past */ - private static final String[] SQL_CHECK_ERR = {"'", "\"", ";", "'(", ")", "(", "NULL", "'\""}; + static final String[] SQL_CHECK_ERR = {"'", "\"", ";", "'(", ")", "(", "NULL", "'\""}; /** * A collection of RDBMS with its error message fragments and {@code Tech}. @@ -449,7 +449,7 @@ private static List asList(String... strings) { * generic UNION statements. Hoping these will cause a specific error message that we will * recognise */ - private static String[] SQL_UNION_APPENDAGES = { + static String[] SQL_UNION_APPENDAGES = { " UNION ALL select NULL" + SQL_ONE_LINE_COMMENT, "' UNION ALL select NULL" + SQL_ONE_LINE_COMMENT, "\" UNION ALL select NULL" + SQL_ONE_LINE_COMMENT, diff --git a/addOns/ascanrules/src/test/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRuleUnitTest.java b/addOns/ascanrules/src/test/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRuleUnitTest.java index bd343731e8a..c31b3b9d07c 100644 --- a/addOns/ascanrules/src/test/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRuleUnitTest.java +++ b/addOns/ascanrules/src/test/java/org/zaproxy/zap/extension/ascanrules/SqlInjectionScanRuleUnitTest.java @@ -31,8 +31,10 @@ import fi.iki.elonen.NanoHTTPD.IHTTPSession; import fi.iki.elonen.NanoHTTPD.Response; import java.util.Map; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.parosproxy.paros.core.scanner.Alert; +import org.parosproxy.paros.core.scanner.Plugin.AlertThreshold; import org.parosproxy.paros.core.scanner.Plugin.AttackStrength; import org.zaproxy.addon.commonlib.CommonAlertTag; import org.zaproxy.zap.model.Tech; @@ -376,6 +378,196 @@ void shouldAlertByBodyComparisonIgnoringXmlEscapedPayload() throws Exception { assertThat(actual.getAttack(), is(equalTo(attackPayload))); } + @Nested + class ErrorBasedSqlInjection { + @Test + void shouldAlert_emptyPrefix() throws Exception { + // Given + final String param = "param"; + final String normalValue = "test"; + final String emptyPrefixErrorValue = "" + SqlInjectionScanRule.SQL_CHECK_ERR[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(emptyPrefixErrorValue) + .thenReturnHtml("You have an error in your SQL syntax") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(1)); + } + + @Test + void shouldAlert_originalParamPrefix() throws Exception { + // Given + final String param = "param"; + final String normalValue = "test"; + final String originalParamErrorValue = + normalValue + SqlInjectionScanRule.SQL_CHECK_ERR[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(originalParamErrorValue) + .thenReturnHtml("You have an error in your SQL syntax") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(1)); + } + + @Test + void shouldNotAlert_nonSqlMessage() throws Exception { + // Given + final String param = "param"; + final String normalValue = "test"; + final String originalParamErrorValue = + normalValue + SqlInjectionScanRule.SQL_CHECK_ERR[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(originalParamErrorValue) + .thenReturnHtml("Not a SQL error message") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(0)); + } + + @Test + void shouldAlert_genericRDBMSError() throws Exception { + // Given + rule.setAlertThreshold( + AlertThreshold.LOW); // Generic are currently only checked at the low threshold + final String param = "param"; + final String normalValue = "test"; + final String originalParamErrorValue = + normalValue + SqlInjectionScanRule.SQL_CHECK_ERR[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(originalParamErrorValue) + .thenReturnHtml("java.sql.SQLException") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(1)); + } + } + + @Nested + class UnionBasedSqlInjection { + @Test + void shouldAlert_RDBMSErrorMessage() throws Exception { + // Given + final String param = "param"; + final String normalValue = "test"; + final String unionValueString = + normalValue + SqlInjectionScanRule.SQL_UNION_APPENDAGES[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(unionValueString) + .thenReturnHtml("You have an error in your SQL syntax") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(1)); + } + + @Test + void shouldNotAlert_nonErrorMessageResponse() throws Exception { + // Given + final String param = "param"; + final String normalValue = "test"; + final String unionValueString = + normalValue + SqlInjectionScanRule.SQL_UNION_APPENDAGES[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(unionValueString) + .thenReturnHtml("This is not a sql error message") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(0)); + } + + @Test + void shouldNotRun_strengthLOW() throws Exception { + // Given + rule.setAttackStrength(AttackStrength.LOW); + final String param = "param"; + final String normalValue = "test"; + final String unionValueString = + normalValue + SqlInjectionScanRule.SQL_UNION_APPENDAGES[0]; + + final UrlParamValueHandler handler = + UrlParamValueHandler.builder() + .targetParam(param) + .whenParamValueIs(param) + .thenReturnHtml(normalValue) + .whenParamValueIs(unionValueString) + .thenReturnHtml("You have an error in your SQL syntax") + .build(); + nano.addHandler(handler); + rule.init(getHttpMessage("/?param=" + normalValue), parent); + + // When + rule.scan(); + + // Then + assertThat(alertsRaised, hasSize(0)); + } + } + private static class ExpressionBasedHandler extends NanoServerHandler { public enum Expression {