3636import java .util .regex .Pattern ;
3737import org .apache .commons .httpclient .InvalidRedirectLocationException ;
3838import org .apache .commons .httpclient .URI ;
39+ import org .apache .commons .lang3 .StringUtils ;
3940import org .apache .logging .log4j .LogManager ;
4041import org .apache .logging .log4j .Logger ;
4142import org .parosproxy .paros .Constant ;
4243import org .parosproxy .paros .control .Control ;
4344import org .parosproxy .paros .core .scanner .AbstractAppParamPlugin ;
4445import org .parosproxy .paros .core .scanner .Alert ;
4546import org .parosproxy .paros .core .scanner .Category ;
47+ import org .parosproxy .paros .network .HttpHeader ;
4648import org .parosproxy .paros .network .HttpMessage ;
4749import org .zaproxy .addon .commonlib .CommonAlertTag ;
4850import org .zaproxy .zap .extension .authentication .ExtensionAuthentication ;
@@ -937,6 +939,9 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
937939 mResBodyNormalUnstripped = refreshedmessage .getResponseBody ().toString ();
938940 mResBodyNormalStripped = this .stripOff (mResBodyNormalUnstripped , origParamValue );
939941
942+ String locationHeaderNormal =
943+ refreshedmessage .getResponseHeader ().getHeader (HttpHeader .LOCATION );
944+
940945 // boolean booleanBasedSqlInjectionFoundForParam = false;
941946
942947 // try each of the AND syntax values in turn.
@@ -980,6 +985,8 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
980985 String resBodyANDTrueStripped =
981986 stripOffOriginalAndAttackParam (
982987 resBodyANDTrueUnstripped , origParamValue , sqlBooleanAndTrueValue );
988+ String headerANDTrue =
989+ msg2 .getResponseHeader ().getHeader (HttpHeader .LOCATION );
983990
984991 // set up two little arrays to ease the work of checking the unstripped output, and
985992 // then the stripped output
@@ -1037,6 +1044,8 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
10371044 resBodyANDFalseUnstripped ,
10381045 origParamValue ,
10391046 sqlBooleanAndFalseValue );
1047+ String headerANDFalse =
1048+ msg2_and_false .getResponseHeader ().getHeader (HttpHeader .LOCATION );
10401049
10411050 String andFalseBodyOutput [] = {
10421051 resBodyANDFalseUnstripped , resBodyANDFalseStripped
@@ -1098,8 +1107,22 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
10981107
10991108 sqlInjectionFoundForUrl = true ;
11001109
1101- break ; // No further need to loop through SQL_AND
1110+ break ; // No further need to loop through header check
11021111
1112+ } else if (StringUtils .compare (locationHeaderNormal , headerANDTrue ) == 0 &&
1113+ StringUtils .compare (headerANDTrue , headerANDFalse ) != 0 ) {
1114+ // or we get redirected, likely an SQL Injection too
1115+ sqlInjectionAttack = sqlBooleanAndTrueValue ;
1116+ newAlert ()
1117+ .setConfidence (Alert .CONFIDENCE_MEDIUM )
1118+ .setParam (param )
1119+ .setAttack (sqlInjectionAttack )
1120+ .setMessage (msg2 )
1121+ .raise ();
1122+
1123+ sqlInjectionFoundForUrl = true ;
1124+
1125+ break ; // No further need to loop through SQL_AND
11031126 } else {
11041127 // the results of the always false condition are the same as for the
11051128 // original unmodified parameter
@@ -1146,14 +1169,17 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
11461169 String resBodyORTrueStripped =
11471170 stripOffOriginalAndAttackParam (
11481171 resBodyORTrueUnstripped , origParamValue , orValue );
1172+ String headerORTrue =
1173+ msg2_or_true .getResponseHeader ().getHeader (HttpHeader .LOCATION );
11491174
11501175 String orTrueBodyOutput [] = {
11511176 resBodyORTrueUnstripped , resBodyORTrueStripped
11521177 };
11531178
11541179 int compareOrToOriginal =
11551180 orTrueBodyOutput [booleanStrippedUnstrippedIndex ].compareTo (
1156- normalBodyOutput [booleanStrippedUnstrippedIndex ]);
1181+ normalBodyOutput [booleanStrippedUnstrippedIndex ]) |
1182+ StringUtils .compare (locationHeaderNormal , headerORTrue );
11571183 if (compareOrToOriginal != 0 ) {
11581184 log .debug (
11591185 "Check 2, {} html output for OR TRUE condition [{}] different to (refreshed) original results for {}" ,
@@ -1306,12 +1332,14 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
13061332 countBooleanBasedRequests ++;
13071333
13081334 String resBodyORTrueUnstripped = msg2 .getResponseBody ().toString ();
1335+ String locationHeaderORTrue =
1336+ msg2 .getResponseHeader ().getHeader (HttpHeader .LOCATION );
13091337
13101338 // if the results of the "OR 1=1" exceed the original query (unstripped, by more
1311- // than a 20% size difference, say), we may be onto something.
1339+ // than a 20% size difference, say) or got redirected , we may be onto something.
13121340 // TODO: change the percentage difference threshold based on the alert threshold
1313- if ((resBodyORTrueUnstripped .length ()
1314- > ( mResBodyNormalUnstripped . length () * 1.2 ) )) {
1341+ if ((resBodyORTrueUnstripped .length () > ( mResBodyNormalUnstripped . length () * 1.2 ))
1342+ || ( StringUtils . compare ( locationHeaderNormal , locationHeaderORTrue ) != 0 )) {
13151343 log .debug (
13161344 "Check 2a, unstripped html output for OR TRUE condition [{}] produced sufficiently larger results than the original message" ,
13171345 sqlBooleanOrTrueValue );
@@ -1337,14 +1365,21 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
13371365 resBodyANDFalseUnstripped ,
13381366 origParamValue ,
13391367 sqlBooleanAndFalseValue );
1368+ String headerANDFalse =
1369+ msg2_and_false .getResponseHeader ().getHeader (HttpHeader .LOCATION );
13401370
13411371 // does the "AND 1=2" version produce the same as the original (for
13421372 // stripped/unstripped versions)
13431373 boolean verificationUsingUnstripped =
13441374 resBodyANDFalseUnstripped .compareTo (mResBodyNormalUnstripped ) == 0 ;
13451375 boolean verificationUsingStripped =
13461376 resBodyANDFalseStripped .compareTo (mResBodyNormalStripped ) == 0 ;
1347- if (verificationUsingUnstripped || verificationUsingStripped ) {
1377+ boolean verificationUsingLocationHeader =
1378+ StringUtils .compare (headerANDFalse , locationHeaderORTrue ) != 0 ;
1379+
1380+ if (verificationUsingUnstripped
1381+ || verificationUsingStripped
1382+ || verificationUsingLocationHeader ) {
13481383 log .debug (
13491384 "Check 2, {} html output for AND FALSE condition [{}] matches the (refreshed) original results" ,
13501385 (verificationUsingStripped ? "STRIPPED" : "UNSTRIPPED" ),
@@ -1358,7 +1393,7 @@ && matchBodyPattern(msg1, errorPattern, sb)) {
13581393 sqlBooleanOrTrueValue ,
13591394 sqlBooleanAndFalseValue ,
13601395 "" );
1361- } else {
1396+ } else if ( verificationUsingUnstripped ) {
13621397 extraInfo =
13631398 Constant .messages .getString (
13641399 MESSAGE_PREFIX + "alert.booleanbased.extrainfo" ,
0 commit comments