Skip to content

Instantly share code, notes, and snippets.

@Mikej81
Last active July 28, 2025 12:15
Show Gist options
  • Select an option

  • Save Mikej81/e6bf3c769ed150ce2ab8ef99d64991d2 to your computer and use it in GitHub Desktop.

Select an option

Save Mikej81/e6bf3c769ed150ce2ab8ef99d64991d2 to your computer and use it in GitHub Desktop.
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())
@Mikej81
Copy link
Author

Mikej81 commented Jul 22, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment