buildz库里实现了这些网络功能,可以直接调用,需注意,buildz里面是用python代码写的,python性能不是很好,单人或少人可以正常使用,多人最好还是使用编译好的程序
buildz库安装:pip install buildz
1 tcp端口映射逻辑
1.1 逻辑
开启TCP连接服务器,有客户端TCP连接过来后,服务器新建到要映射端口的连接,然后循环接收客户端和映射端口上的连接数据,发到另一方
编写端口映射代码只需要用到python自带的socket库和select库(select库是为了判断tcp连接对面是否有数据发过来,避免阻塞,也可以用多线程threading库实现)
1.2 buildz调用
直接用写好的(buildz里):
python -m buildz.netz.tcp 监听ip 监听端口 要映射的ip 要映射的端口
假设在电脑windows里开了wsl,在wsl里启动了一个web服务器172.0.0.2:80,现在需要在手机上连这个服务器,但是电脑和手机是在局域网192.168.0.*上的,手机连不到wsl里,这时候就可以用端口映射,假设windows的ip是192.168.0.1,要开8080端口:
python -m buildz.netz.tcp 192.168.0.1 8080 172.0.0.2 80
或者
from buildz.netz.tcp import MiddleServer
addr = ("192.168.0.1", 8080)
remote_addr = ("172.0.0.2", 80)
server = MiddleServer(addr, remote_addr)
server()
2 http/https正向代理逻辑
2.1 http报文
http请求的逻辑是tcp连接到服务器上,然后发http报文
假设要访问的url是http://so.youkuaiyun.com/so/search?q=test,用的GET方法
普通http请求报文(报文首行(请求行):请求类型 空格 请求url 空格 http协议版本\r\n):
GET /so/search?q=test HTTP/1.1\r\n
Content-Length: ???\r\n
...多行报文头\r\n
\r\n
数据
(注:报文数据实际只有一行,这里为了展示方便写成多行;报文头后面接两个“\r\n”表示报文头结束,这时候如果报文头里有Content-Length字段,并且Content-Length大于0,则会再读取Content-Length对应的值的长度的数据作为报文体数据,报文体数据后面不用跟\r\n,因为是根据Content-Length定的长度)
普通http请求报文,因为已经tcp连接到对应的服务器了,这时候url里的域名已经不需要了
当http请求发到代理服务器上时,http报文变成了:
GET so.youkuaiyun.com/so/search?q=test HTTP/1.1\r\n
Content-Length: ???\r\n
...多行报文头\r\n
\r\n
数据
发到代理服务器上的请求带有域名,是希望代理服务器连接到该域名服务器上,再向域名服务器发普通http报文,然后代理服务器接收到域名服务器上的数据后,返回给客户端
2.2 http代理逻辑
http代理服务器的运行逻辑就是:
1,接收客户端tcp连接,读取http请求报文
2,提取报文里的域名字段,连接域名服务器
3,生成新的http请求报文(基本就是把客户端http请求里的域名删掉),发送域名服务器
4,接收域名服务器返回的响应报文,发给客户端
2.3 特殊报文:chunked报文
http报文里的报文头里有Transfer-Encoding字段,并且取值chunked:
请求报文头里有chunked
GET /so/search?q=test HTTP/1.1\r\n
Content-Length: ???\r\n
Transfer-Encoding: chunked\r\n
...多行报文头\r\n
\r\n
数据
或者响应报文头里有chunked(响应报文只和请求报文首行(请求行)格式不一样,响应报文请求和格式是:http版本 空格 响应码 空格 响应消息\r\n)
HTTP/1.1 200 OK\r\n
Content-Length: ???\r\n
Transfer-Encoding: chunked\r\n
...多行报文头\r\n
\r\n
数据
有chunked表示数据还没发完,发送方发送该报文后,还会再发送chunk数据
chunk格式:
数据长度(16进制)\r\n
具体数据\r\n
例
b\r\n
12345678901\r\n
b是16进制,10进制是11,表示后面跟11个字节的数据
chunk发送完后会再发一个0长度的chunk报文,表示发送完成
0\r\n
\r\n
http代理逻辑加上chunk处理:
1,接收客户端tcp连接,读取http请求报文
2,提取报文里的域名字段,连接域名服务器
3,生成新的http请求报文(基本就是把客户端http请求里的域名删掉),发送域名服务器
3.1,判断客户端http请求头部里有没有标记chunked,有的话,循环读取客户端的chunk数据发给域名服务器
4,接收域名服务器返回的响应报文,发给客户端
4.1,判断域名服务器返回的http响应头部里有没有标记chunked,有的话,循环读取域名服务器的chunk数据发给客户端
用python代码写,也是只要python自带的socket库和select库/threading库
2.4 特殊报文:connect
请求报文:
CONNECT so.youkuaiyun.com HTTP/1.1\r\n
...
该报文表示客户端希望代理服务器向域名服务器创建连接,建立后代理服务器返回200响应报文给客户端,然后代理服务器把自己当作端口映射服务器,只管把客户端发的数据直接发给域名服务器,把域名服务器发的数据直接发给客户端,别进行对http报文的相关代理处理逻辑,CONNECT基本上是发https请求时候用的,因为https请求是加密后的http报文数据,代理服务器没办法处理这些加密数据,也只能转发了
2.5 buildz调用
直接用buildz里写好的:
from buildz.netz.mhttp import Proxy
proxy_addr = ('127.0.0.1',9999)
proxy = Proxy(proxy_addr)
proxy()
3 http/https抓包
抓包实际上就是启动代理服务器,客户端往代理服务器发的http请求,以及域名服务器返回的http响应都是明文,代理服务器自然可以把这些明文数据拿出来
3.1 https代理抓包
3.1.1 https连接原理
https本质上也是发的http请求报文和响应报文,只不过客户端向https服务器建立的连接不是tcp连接,而是在tcp之上的ssl连接,ssl连接对数据做了加密
ssl连接过程(这里只写主要逻辑,具体细节没写,并且只包含客户端验证服务器证书的逻辑,因为一般https就是只有客户端验证服务器证书是否有效,ssl还包括服务器验证客户端证书是否有效的流程,这个流程是可选的,并且https不会使用):
1 客户端向服务器建立tcp连接
2 服务器向客户端发送它的证书(包含服务器的公钥)
3 客户端验证证书的域名是不是自己连接的服务器的域名(证书包含有域名信息,该项验证可选,域名不一致时浏览器会展示警告页面或者直接访问不了(看用户自己设置))
4 客户端验证服务器证书是否有效
(客户端预先存储了可信根证书列表,服务器证书实际上是一个证书链文件:
根证书私钥签名的根证书
根证书私钥签名的证书1
证书1私钥签名的证书2
。。。
最后一个证书就是服务器的证书了,
每个证书里都包括签名该证书的上一层证书的信息以及该证书自己的公钥,客户端先判断根证书是不是在自己的可信根证书列表里,然后用根证书的公钥验证根证书是否有效,用根证书公钥验证证书1是否有效,用证书1公钥验证证书2是否有效,依此类推,最后验证服务器证书是否有效)
5 客户端生成一个密码,用服务器证书里的公钥加密该密码和一些信息,发给服务器
6 服务器用自己的私钥对数据进行解密,得到密码和信息,用密码加密这些信息发给客户端
7 客户端用密码解密,判断信息是否和自己发送的一致,一致表明服务器是证书的持有者(拥有证书的私钥),之后客户端和服务器发送的数据都会通过该密码加密
前面说过,客户端通过代理发https请求的时候,是发的http里的CONNECT请求,然后客户端就通过代理服务器,和域名服务器建立ssl连接了,因为ssl连接发送的数据是加密的,代理读取不了这些加密数据
3.1.2 https抓包逻辑
https抓包还是用的代理服务器,但代理服务器在收到客户端的CONNECT请求的时候,不会按客户端想的去做,而是冒充域名服务器,逻辑如下
1 客户端向代理服务器发CONNECT请求
2 代理服务器连接域名服务器,返回给客户端200响应报文
3 代理服务器生成一个证书,里面包含域名服务器的域名,然后用自己的根证书签名该证书,把该证书发送给客户端
4 客户端按照ssl流程验证代理服务器就是域名服务器(在客户端的可信根证书列表里加入代理服务器的根证书,这时候客户端就会认为代理服务器的证书有效)
5 代理服务器把自己当作客户端,通过ssl连接域名服务器
6 客户端通过自己和代理服务器的ssl连接发送http请求报文
7 代理服务器通过自己和域名服务器的ssl连接发送http请求报文
。。。
主要是两点:
1)代理服务器可以生成和签名证书
2)客户端的可信根证书列表里加入代理服务器的证书或者签名该证书的根证书,这一步代理服务器是办不到的,只能在客户端手动操作,比如要做安卓手机的https抓包,一是要在手机上配置代理服务器,二是要手动把代理服务器的证书加到安卓手机可信证书列表里,这些都可以在网上查,基本就是把证书传手机上,然后点击安装)
用python实现的话,要python自带的socket库,select库/threading库,ssl库(ssl连接验证),除此之外还需要cryptography库来生成证书(linux下python好像自带这个库,windows里没有自带,要用pip下载),如果不用cryptography库,也可以用其他可以生成公钥证书的库
3.2 buildz调用
运行前,如果没有cryptography,需要手动安装cryptography库: pip install cryptography
buildz有写好的demo可以直接运行(该方法只会把抓包的报文头打印出来,报文体数据不管)
python -m buildz.netz.mhttp.test ip port
运行这个会在当前目录下创建res文件夹,在里面生成根证书ca.crt,需要在客户端把这个证书加入可信根证书列表
也可以写代码运行
from buildz.netz import mhttp
addr = ('127.0.0.1',9999)
'''
buildz.netz.sslz也有提供生成证书的方法:
from buildz.netz import sslz
sslz.gen_prv(fp_prv, pwd)生成私钥文件,pwd是文件加密密码,可选,默认为空
sslz.gen_cert(fp_cert, fp_prv, conf, pwd)生成证书文件,conf是要生成的证书的内容,可以参考buildz.netz.test.test里的代码
'''
fp_cert=代理服务器根证书路径
fp_prv=代理服务器私钥文件路径
ca_pwd=代理服务器私钥文件密码(可选,没有密码就传None)
class Record(mhttp.MsgRecord):
'''
CapsProxy会在接收到http报文的时候调用该方法,传入报文数据,
需要使用者手动实现该类里的方法,自己决定要怎么使用这些报文数据
需要实现的方法见buildz.netz.mhttp.record里的MsgRecord
'''
pass
'''
mhttp.CapsProxy还可以传入参数cafile,capath,cadata以及check_hostname,前三者默认都是None,check_hostname默认True,
前三者是给代理服务器ssl连接域名服务器的时候,验证域名服务器的证书是否有效的可信根证书列表,
三个只要其中有一个有值,代理服务器都会验证域名服务器的证书是否有效,
否则代理服务器直接认为域名服务器的证书有效,
check_hostname=True的时候,代理服务器会验证域名服务器的证书里的域名列表里有没有代理服务器连接域名服务器用的域名
'''
proxy = mhttp.CapsProxy(addr, fp_cert, fp_prv, ca_pwd, record=Record())
proxy()
4 反向代理
4.1 逻辑
反向代理是客户端把代理服务器当作域名服务器访问,发送的http请求报文就是普通的http请求报文(请求url里不包含域名),代理服务器再根据请求报文里的参数(主要是url),判断要发给哪个域名服务器,再向域名服务器转发请求(这时候需要修改请求报文里的一些头部参数,比如Host字段,客户端里发送的报文里如果有该字段,一般设置的是代理服务器的ip和端口,代理服务器要填充域名服务器的ip和端口),然后把域名服务器返回的数据发给客户端
反向代理服务器本质上就是域名服务器了,只不过该服务器接收请求后,会向其他域名服务器再发请求
用python实现,需要socket库和select/threading库,如果有https请求,还需要ssl库,都是python自带的库
4.2 buildz调用
直接调用buildz写好的代码:
创建配置文件,假设命名conf.js:
# 配置文件
addr=(127.0.0.1,9999)
# 转发规则,根据客户端发送的请求里的url做正则匹配,匹配上就发对应的服务器,全匹配不上就返回404
rules:[
# match:正则匹配,replace:把正则匹配上的字段替换成该字段,ssl:要发的服务器是否是https请求,默认否
{match="^/baidu", replace='www.baidu.com',ssl=true}
# 简写
("^/csdn", 'www.youkuaiyun.com',true)
("^/csdn_http", 'www.youkuaiyun.com')
]
命令行运行:
python -m buildz.netz.mhttp.gateway conf.js
或者写python代码
from buildz.netz import mhttp
addr = ('127.0.0.1',9999)
rules = []
# 也可以写json字符串,用json库读取json字符串转rules参数
rules.append(["^/csdn", 'www.youkuaiyun.com',True])
proxy = mhttp.Gateway(addr, rules)
proxy()