-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
302 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
--- | ||
layout: post | ||
title: "加解密网关" | ||
subtitle: '凡所有相,皆是虚妄.' | ||
author: "taomujian" | ||
header-style: text | ||
tags: | ||
- 加密 | ||
- 漏洞修复 | ||
- 攻防 | ||
--- | ||
|
||
|
||
## 简述 | ||
|
||
> 四年前,我当时写的开源漏扫系统linbing前后端数据传输采用了RSA加解密,当时所在公司的领导,是入行多年的技术大牛,当时给我说所有的前后端数据传输进行加密都是画蛇添足,多此一举,应该直接面对问题,比如说为什么登陆时要对数据包进行加密,我当时回答加密是为了防止被抓包进行爆破,领导说那么真正要做的是设置复杂密码这种措施,而不是进行加密,因为这样对于解决问题毫无作用,所有的加密措施都会被破解. | ||
当时由于技术视野较低,并不能完全理解这些话所要表达的深意,经过这些年的技术沉淀,对前领导所说的话有了更深的理解. | ||
|
||
在早期的时候,前端传给后端的数据在应用层上是明文的,可以用Burpsuit这种抓包工具抓取明文数据包,然后通过对数据包进行修改、重放、删除等操作发现了许多漏洞. | ||
|
||
对于怎么修复这些漏洞,有的人认为只要把这些数据进行加密,让抓包者看不到明文,无法修改就可以修复漏洞了.有这样想法的人要么是偷懒,要么就是技术视野比较低,对技术一无所知. | ||
|
||
不管采用什么样的加解密方案,无论是采用单纯的对称加密算法或者是非对称加密算法还是对称加密算法和非对称加密算法混合的方案,都会被破解,加解密的这个壳被打碎后,所谓的加解密其实就是在裸奔.如果之前代码对于安全漏洞有预防措施还好,如果一点预防措施都没有,只靠加解密来预防安全漏洞的出现,那么系统就是千疮百孔,不堪一击. | ||
|
||
## 破解 | ||
|
||
对于怎么破解前端和后端加密数据传输方案的方式有2种. | ||
|
||
> 第1种方式比较麻烦,就是直接在浏览器中根据关键字符串打断点,然后把数据的加解密算法逆向出来,并把加密之前的数据通过打断点获取到,就可以用python脚本的方式模拟加解密过程,对数据进行加密进行发包,然后把收到的密文返回包解密即可. | ||
> 第2种方式是在浏览器中根据关键字符串打断点,把数据的加解密算法逆向出来之后,采用中间人攻击的方法,启动2个加解密网关(就是2个服务)对数据自动进行加解密,再结合Burpsuit,就可以实现在Burpsuit看到明文请求包、明文返回包,并可以在Burpsuit中随意修改数据并进行请求,这种所谓的加解密就失去了原先的作用,看着就是透明的一样. | ||
接下来就探讨下怎么实现这个加解密网关,让加解密方案彻底形同虚设. | ||
|
||
## 加解密网关 | ||
|
||
### 流程 | ||
|
||
如下图所示: | ||
|
||
![流程.jpg](https://taomujian.github.io/img/加解密网关/流程.jpg) | ||
|
||
``` | ||
1. 首先在客户端(浏览器)上设置代理,把数据发送给上游代理网关 | ||
2. 上游代理网关收到客户端发来的密文会根据加解密算法把密文还原为明文,然后把明文发送给Burpsuit | ||
3. 在Burpsuit上设置代理,代理地址为下游代理网关,Burpsuit收到明文数据后,把明文数据发送给下游代理网关 | ||
4. 下游代理网关收到明文数据后,会根据加密算法,把明文数据加密成密文数据,然后发给服务端(网站服务) | ||
5. 下游代理网关收到服务端发来的密文数据后,会根据解密算法,把密文数据解密成明文数据,然后发给Burpsuit | ||
6. Burpsuit收到下游代理网关发来的明文数据后,把收到的明文数据发给上游代理网关 | ||
7. 上游代理网关收到Burpsuit发来的明文数据后,会根据加密算法,把明文数据加密成密文数据,然后发给客户端 | ||
``` | ||
|
||
从上面的流程中可以看到,Burpsuit看到的都的请求包和返回包都是明文了. | ||
|
||
### 示例 | ||
|
||
假如一个网站采用的是AES加解密算,请求包和返回包都是密文,通过浏览器打断点发现采用的是AES的CBC模式,用的key和iv分别为294d317438m440ed、8044ccb1qd92n5f5 | ||
|
||
用python来实现这个上游代理网关和下游代理网关,主要使用mitmproxy这个框架来实现 | ||
|
||
#### 代码示例 | ||
|
||
##### aes.py | ||
|
||
```python | ||
#!/usr/bin/env python3 | ||
|
||
import base64 | ||
from Crypto.Cipher import AES | ||
|
||
class Aes_Crypto: | ||
def __init__(self, key, iv): | ||
self.key = key.encode() | ||
self.iv = iv if iv else (chr(0) * 16).encode() | ||
|
||
def pkcs7padding(self, text): | ||
|
||
""" | ||
明文使用PKCS7填充 | ||
最终调用AES加密方法时,传入的是一个byte数组,要求是16的整数倍,因此需要对明文进行处理 | ||
:param str text: 待加密内容(明文) | ||
:return: | ||
""" | ||
|
||
bs = AES.block_size # 16 | ||
length = len(text) | ||
bytes_length = len(bytes(text, encoding='utf-8')) | ||
# tips:utf-8编码时,英文占1个byte,而中文占3个byte | ||
padding_size = length if(bytes_length == length) else bytes_length | ||
padding = bs - padding_size % bs | ||
# tips:chr(padding)看与其它语言的约定,有的会使用'\0' | ||
padding_text = chr(padding) * padding | ||
return text + padding_text | ||
|
||
def pkcs7unpadding(self, text): | ||
|
||
""" | ||
处理使用PKCS7填充过的数据 | ||
:param str text: 解密后的字符串 | ||
:return: | ||
""" | ||
|
||
length = len(text) | ||
unpadding = ord(text[length-1]) | ||
return text[0:length-unpadding] | ||
|
||
def encrypt(self, content): | ||
|
||
""" | ||
AES加密 | ||
key,iv使用同一个 | ||
模式cbc | ||
填充pkcs7 | ||
:param str content: 待加密内容 | ||
:return: | ||
""" | ||
|
||
cipher = AES.new(self.key, AES.MODE_CBC, self.iv) | ||
# 处理明文 | ||
content_padding = self.pkcs7padding(content) | ||
# 加密 | ||
encrypt_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8')) | ||
# 重新编码 | ||
result = str(base64.b64encode(encrypt_bytes), encoding='utf-8') | ||
return result | ||
|
||
def decrypt(self, content): | ||
|
||
""" | ||
AES解密 | ||
key,iv使用同一个 | ||
模式cbc | ||
去填充pkcs7 | ||
:param str content: 待解密的内容 | ||
:return: | ||
""" | ||
|
||
cipher = AES.new(self.key, AES.MODE_CBC, self.iv) | ||
# base64解码 | ||
encrypt_bytes = base64.b64decode(content) | ||
# 解密 | ||
decrypt_bytes = cipher.decrypt(encrypt_bytes) | ||
# 重新编码 | ||
result = str(decrypt_bytes, encoding='utf-8') | ||
# 去除填充内容 | ||
result = self.pkcs7unpadding(result) | ||
return result | ||
|
||
if __name__ == '__main__': | ||
aes_crypto = Aes_Crypto('294d317438m440ed', '8044ccb1qd92n5f5') | ||
data = aes_crypto.encrypt('''12''') | ||
print(data) | ||
data = aes_crypto.decrypt(data) | ||
print(data) | ||
``` | ||
|
||
##### upstream.py | ||
|
||
```python | ||
import json | ||
from aes import Aes_Crypto | ||
from mitmproxy import ctx | ||
from mitmproxy.http import HTTPFlow | ||
|
||
class FilterFlow: | ||
def request(self, flow): | ||
if flow.request.url.startswith("https://test.net") and flow.request.method == 'POST': | ||
req = flow.request.get_text() | ||
try: | ||
req_json = json.loads(req) | ||
aes_crypto = Aes_Crypto('294d317438m440ed', '8044ccb1qd92n5f5') | ||
ctx.log.info("发送的加密数据为:") | ||
ctx.log.info(req_json) | ||
data = json.loads(aes_crypto.decrypt(req_json['encData'])) | ||
req_json['encData'] = data | ||
ctx.log.info("发送的数据解密后为:") | ||
ctx.log.info(req_json) | ||
flow.request.set_text(json.dumps(req_json)) | ||
except Exception as e: | ||
print(e) | ||
pass | ||
|
||
def response(self, flow:HTTPFlow): | ||
if flow.request.url.startswith("https://test.net") and flow.request.method == 'POST': | ||
ctx.log.info("当前请求url:" + flow.request.url) | ||
req = flow.response.get_text() | ||
try: | ||
resp_json = json.loads(req) | ||
ctx.log.info("接收到服务端传来的解密数据:") | ||
ctx.log.info(req) | ||
if type(resp_json['result']) == str: | ||
aes_crypto = Aes_Crypto('294d317438m440ed', '8044ccb1qd92n5f5') | ||
resp_json['result'] = aes_crypto.encrypt(json.dumps(resp_json['result'])) | ||
ctx.log.info(resp_json) | ||
flow.response.set_text(json.dumps(resp_json)) | ||
except Exception as e: | ||
print(e) | ||
pass | ||
|
||
addons = [FilterFlow(),] | ||
``` | ||
|
||
##### downstream.py | ||
|
||
```python | ||
import json | ||
from aes import Aes_Crypto | ||
from mitmproxy import ctx | ||
from mitmproxy.http import HTTPFlow | ||
|
||
class FilterFlow: | ||
def request(self, flow): | ||
if flow.request.url.startswith("https://test.net") and flow.request.method == 'POST': | ||
req = flow.request.get_text() | ||
try: | ||
req_json = json.loads(req) | ||
if 'encData' in req_json.keys(): | ||
aes_crypto = Aes_Crypto('294d317438m440ed', '8044ccb1qd92n5f5') | ||
ctx.log.info("发送的明文数据为:") | ||
ctx.log.info(req_json) | ||
data = aes_crypto.encrypt(json.dumps(req_json['encData']).replace(' ', '')) | ||
req_json['encData'] = data | ||
ctx.log.info(req_json) | ||
flow.request.set_text(json.dumps(req_json)) | ||
except Exception as e: | ||
pass | ||
|
||
def response(self, flow:HTTPFlow): | ||
if flow.request.url.startswith("https://test.net") and flow.request.method == 'POST': | ||
ctx.log.info("当前请求url:" + flow.request.url) | ||
req = flow.response.get_text() | ||
try: | ||
resp_json = json.loads(req) | ||
ctx.log.info(req) | ||
if type(resp_json['result']) == str: | ||
ctx.log.info("接收到服务端传来的解密数据:") | ||
ctx.log.info(req) | ||
if resp_json['result']: | ||
aes_crypto = Aes_Crypto('294d317438m440ed', '8044ccb1qd92n5f5') | ||
resp_json['result'] = json.loads(aes_crypto.decrypt(resp_json['result'])) | ||
ctx.log.info("数据解密完成") | ||
ctx.log.info(resp_json) | ||
flow.response.set_text(json.dumps(resp_json)) | ||
except Exception as e: | ||
pass | ||
|
||
addons = [FilterFlow(),] | ||
``` | ||
|
||
#### 安装依赖 | ||
|
||
##### 1、安装python依赖(python版本越高越好,建议python3.10以上) | ||
|
||
pip3 install mitmproxy | ||
pip3 install cryptography | ||
pip3 install pycryptodome | ||
|
||
##### 2、运行mitmproxy证书 | ||
|
||
> 下文中$PYTHON_HOME为python安装目录 | ||
> 首先到$PYTHON_HOME/Scripts目录下运行一下mitmdump.exe,完成之后在当前用户根目录(如C:\Users\Administrator)下的.mitmproxy文件夹下即会生成证书 | ||
> Windows安装证书:双击mitmproxy-ca-cert.p12----全部默认直接点"下一步"直到安装完成。 | ||
> Android安装证书: 把mitmproxy-ca-cert.cer通过usb复制到手机上----点击使用证书安装器安装证书 | ||
#### 运行mitmdump.exe | ||
|
||
> burpsuite的监听地址为: http://127.0.0.1:8080 | ||
> D:\前端加解密\upstream.py为upstream.py文件所在目录,根据实际修改 | ||
> 在$PYTHON_HOME/Scripts目录下运行mitmdump.exe -s D:\JS加解密\mitm\upstream.py -p 8081 --mode upstream:http://127.0.0.1:8080 --ssl-insecure | ||
> 在$PYTHON_HOME/Scripts目录下运行mitmdump.exe -s D:\JS加解密\mitm\downstream.py -p 8082 --ssl-insecure | ||
> 在burpsuite中设置代理 | ||
![设置代理.jpg](https://taomujian.github.io/img/加解密网关/设置代理.jpg) | ||
|
||
#### 触发流量 | ||
|
||
> 通过浏览器把请求的流量发送到http://127.0.0.1:8081 | ||
> 这时就可以在burpsuite中看到相关数据包请求时的明文参数和返回时的明文数据 | ||
## 总结 | ||
|
||
> 凡所有相,皆是虚妄.攻防总是处于不断的变化之中,每当有新的防御措施出来,就会有新的攻击手段出现,二者即是矛盾的,又相互促进发展,不断的移形. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.