Python发包的几种情况和注意事项

发布于 2022-10-10  516 次阅读



python发包的几种情况和注意事项

python自动化的第一步就是学会使用requests库,了解各种不同content-type下的发包情况有助于提高自动化的容错率,本篇文章对于requests库的基本使用进行略过,直接介绍post发包的几种形式。

一、HTTP content-type 介绍

Content-Type(内容类型),位于header部分,用于定义网络文件的类型和网页的编码。决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些 PHP 网页点击的结果却是下载一个文件或一张图片的原因。
在响应中,Content-Type标头告诉客户端实际返回的内容的内容类型。浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值;为了防止这种行为,可以将标题 X-Content-Type-Options 设置为 nosniff。

语法格式:

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml: XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json: JSON数据格式
  • application/pdf:pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : < form encType="" >中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

 

二、常见 HTTP content-type 对照表

文件扩展名 Content-Type(Mime-Type) 文件扩展名 Content-Type(Mime-Type)
.*( 二进制流,不知道下载文件类型) application/octet-stream .apk application/vnd.android.package-archive
.asp text/asp .dll application/x-msdownload
.doc application/msword .cer application/x-x509-ca-cert
.gif image/gif .crt application/x-x509-ca-cert
.html text/html .css text/css
.exe application/x-msdownload .img application/x-img
.ico image/x-icon .ico application/x-ico
.java java/* .jpe image/jpeg
.jpeg image/jpeg .jpg image/jpeg
.jpg application/x-jpg .js application/x-javascript
.jsp text/html .mhtml message/rfc822
.mp3 audio/mp3 .mp4 video/mpeg4
.pdf application/pdf .pdf application/pdf
.png application/x-png .png image/png
.ppt application/x-ppt .ppt application/vnd.ms-powerpoint
.rtf application/msword .rtf application/x-rtf
.xls application/x-xls .xls application/vnd.ms-excel
.xml text/xml .txt text/plain

 

三、不同 content-type 下的发包形式

requests库对于不同的 content-type 有不同的发包要求,如下是几种常见的发包样例:

  1. content-type 为 application/x-www-form-urlencoded
  • 默认情况下,content-type类型为application/x-www-form-urlencoded
  • 需提交data参数,data可以是字符串类型或者字典类型

data为字符串类型:

requests.post(url, headers={'content-type':'application/x-www-form-urlencoded'}, data='username=Fitar') 

data为字典类型:

requests.post(url, headers={'content-type':'application/x-www-form-urlencoded'}, data={'username':'Fitar'}) 

两者发送的数据包相同,如下:

POST / HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
content-type: application/x-www-form-urlencoded
Content-Length: 14

username=Fitar

image-20220915113237355

  1. content-type 为 application/json
  • 需提交data参数或者json参数

提交data参数时,类型需要为字符串,并且其格式能通过json.dumps进行转换:

requests.post(url=url, headers={'content-type':'application/json'}, data=json.dumps({'username':'Fitar'}))

提交json参数时,类型需要为字典,程序会自动将其转换为json提交:

requests.post(url, headers={'content-type':'application/json'}, json={'username':'Fitar'}) 

两者发送的数据包相同,如下:

POST / HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
content-type: application/json
Content-Length: 21

{"username": "Fitar"}

image-20220915114344440

  1. content-type 为 text/xml
  • 需提交data参数,data为字节类型

通常body的内容为上传的xml格式的文本,将其进行 utf-8 编码后即可进行上传:

requests.post(url=url, headers={'content-type':'text/xml'}, data='<xml......>'.encode("utf-8"))

发送的数据包如下:

POST / HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
content-type: text/xml
Content-Length: 11

<xml......>

image-20220915114812610

  1. content-type 为 multipart/form-data(常规requests)
  • 需提交files参数,files为字典类型
  • multipart/form-data一般为上传文件,后面还有boundary随机字符串后缀,作为上传操作的分隔符

multipart/form-data 可用于HTML表单从浏览器发送信息给服务器。作为多部分文档格式,它由边界线(一个由’–'开始的字符串)划分出的不同部分组成。每一部分有自己的实体,以及自己的 HTTP 请求头,Content-Disposition和 Content-Type 用于文件上传领域,最常用的 (Content-Length 因为边界线作为分隔符而被忽略)。

import requests

res = requests.post(url="http://127.0.0.1:8888",
                    # data={'username': 'Fitar', 'password': '123456'},
                    files={'file':
                           (
                               'test.png',                         # 文件名
                               open('../1.png', 'rb'),              # 本地读取的文件
                               'application/x-png',          # 文件类型
                               {'Expires': '0', 'file-header': 'test'}         # 请求头
                           )
                    },
                    headers={'Content-Type': 'multipart/form-data'} # 请求包整体的请求头
                    )
print(res.text)

image-20220914172136889

  1. content-type 为 multipart/form-data(requests_toolbelt)

还可以使用工具requests_toolbelt.MultipartEncoder

import requests
from requests_toolbelt import MultipartEncoder

file_data = MultipartEncoder({                          # post传输的数据
                                'username': 'Fitar',            # 参数“username”
                                'password': '123456',           # 参数“password”
                                'file': open('../1.png', 'rb')  # 本地读取的文件
                                })
print(file_data.content_type)
res = requests.post(url="http://127.0.0.1:8888",
                    data=file_data,
                    headers={'Content-Type': file_data.content_type}        # 类型自动填充
                    )
print(res.text)

image-20220915100534225

四、requests库的部分使用技巧和注意事项

  1. 快速将字典转换成字符串的方式
  • 使用json库(Unicode编码)
    • 生成的字符串仍是字典的形式
    • 会对字典中的字符进行Unicode编码
json.dumps({'username':'Fitar'})
  • 使用urllib库(URL编码)
    • 生成的字符串是key=value格式
    • 会对字典中的字符进行URL编码
urllib.parse.urlencode({'username':'Fitar'})

image-20220915164814628

  1. requests库高级使用
  • 流式上传

requests支持流式上传,我们可以通过流式上传发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,只需要为请求体提供一个类文件对象:

with open('massive-body') as f:
      requests.post(url, data=f)
  • 块编码请求

对于出去和进来的请求,requests 也支持分块传输编码。要发送一个块编码的请求,需要为请求体提供一个生成器(或任意没有具体长度的迭代器):

def gen():
      yield 'hi'
      yield 'there'
  
  requests.post(url, data=gen())

对于分块的编码请求,我们最好使用 Response.iter_content() 对其数据进行迭代。在理想情况下,request 会设置 stream=True,我们可以通过调用 iter_content 并将分块大小参数设为 None,从而进行分块的迭代。如果要设置分块的最大体积,可以把分块大小参数设为任意整数。

  • POST 多个分块编码的文件

我们可以在一个请求中发送多个文件。例如要上传多个图像文件到一个 HTML 表单,需要使用一个多文件 field:

<input type="file" name="images" multiple="true" required="true"/>

要实现这个功能,只需要把文件设到一个元组的列表中,其中元组结构为 (form_field_name, file_info)

import requests

url = "http://127.0.0.1:8888"
files = {
        "file":(                                        # 块名称
            "test.jpg",                                 # 上传文件名
            open(r'./1.png','rb'),                      # 读取文件名
            'application/x-png',                        # 上传块的 Content-Type
            {'Expires': '0', 'file-header': 'test'}     # 上传块的 请求头
            ),
        "test1": ('111', "222"),
        "test2": (None, "333"),
    }

r = requests.post(url, files=files)

image-20220915185300769

  1. 实战模拟
  • 通过burp对成功上传的数据包进行抓取

image-20220915174511123

  • 将数据包进行自动处理,转换为python代码,对其发送数据进行抓取,与burp数据包进行对比,请求包基本一致

image-20220915181208601

  • 对靶机进行正式发包测试,返回包一致,成功上传图片

image-20220915181557228

image-20220915181712298

请求代码如下:

import requests

url = 'http://127.0.0.1:5555/pikachu/vul/unsafeupload/servercheck.php'
headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0', 
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 
            'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3', 
            'Accept-Encoding': 'gzip, deflate', 
            'DNT': '1', 
            'Referer': 'http://127.0.0.1:5555/pikachu/vul/unsafeupload/servercheck.php', 
            'Cookie': 'bdshare_firstime=1545322813544; BEEFHOOK=C1QkJg7gSUzUd07Cqs8Zvq2k14bRoXGlKWdGLDwnKJYENmo0K8NavMOoGOskfKVpzmMTUog0PhhnnuoT; PHPSESSID=kqqm6ta909s1mmq8m71guk0b29', 
            'Connection': 'close', 
            'Upgrade-Insecure-Requests': '1', 
            # 'Content-Type': 'multipart/form-data; boundary=---------------------------4436792026967', 
            'Content-Length': '2281'
            }
files={'uploadfile':
        (
            '1.png',
            open('./1.png', 'rb'),
            'image/png',
        ),
        'submit': (None, u'开始上传'),
    }

res = requests.post(url=url, headers=headers, files=files) 

print(res.content.decode('utf-8'))
  1. 坑点
  • 上传文件的请求包不要自定义 Content-Type !!!
  • 上传文件的请求包不要自定义 Content-Type !!!
  • 上传文件的请求包不要自定义 Content-Type !!!

如果自定义了Content-Type,会导致上传失败,返回包如下:

image-20220915182338806

  1. 注意事项

建议通过二进制模式打开文件,因为 requests 可能会提供 header 中的 Content-Length,在这种情况下该值会被设为文件的字节数。如果用文本模式打开文件,就可能碰到错误。

 


人生就像赛跑,不在乎你是否第一个到达终点,而在乎你是否跑完全程。