如何测试加密站点
2023-7-3 14:35:0 Author: xz.aliyun.com(查看原文) 阅读量:34 收藏

0x1 前言

在对一次小程序的测试过程中,发现返回的数据部分是加密的,目前一般通过burpsuite的插件和mitmproxy模块来对数据进行加密解密。今天就来介绍一下这两种方法,当然这两种方法都需要用到python,不得不说python是世界上最好的语言(php:? 你小子在说什么)

0x2 burpy

咋们进入正题,在对小程序的某个接口进行测试时,发现会返回当前用户的个人信息,通过将电话号码置空,返回了所有用户的信息,可惜的是关键敏感信息如姓名、电话号码、身份证号等信息都是加密的

得尽量解密数据将它升级为高危漏洞

通过逆向小程序找到了加密算法和key,就差解密了,解密使用的是github上的一个项目burpy

地址:https://github.com/mr-m0nst3r/Burpy

这个插件可以很方便地对通过bp的数据进行加密和解密,项目也给出了示例脚本,我们一般情况下只需要编写encrytpdecrypt两个函数就可以。

对于全数据包加密的数据处理起来更方便一点,而这种局部加密的数据就得多加几行代码。

因为这里我只需解密返回数据,只编写decrypt函数就可以

def decrypt(self, header, body):
        '''
        Auto Enc/Dec feature require this function
        '''

方法传递的header和body就是burp拦截到的原始header和body,包括请求包和响应包,脚本拿到这些数据包,处理好return一下就行了。

完整代码

import json
from Crypto.Cipher import AES
import base64
class Burpy:
    '''
    header is dict
    body is string
    '''
    def __init__(self):
        '''
        here goes some code that will be kept since "start server" clicked, for example, webdriver, which usually takes long time to init
        '''
        pass

    def encrypt(self, header, body):
        '''
        Auto Enc/Dec feature require this function
        '''
        header["Cookie"] = "admin=1"
        return header,body

    def decrypt(self, header, body):
        new_data = json.loads(body)       
        for i in new_data['data']['records']:
            i["visitorName"] = str(aesd(i['visitorName']))
            i["visitorTelephone"] = str(aesd(i['visitorTelephone']))
            i["visitorIdentityNumber"] = str(aesd(i['visitorIdentityNumber']))
            i["personName"] = str(aesd(i['personName']))
            i["personTelephone"] = str(aesd(i['personTelephone']))
        data = json.dumps(new_data,indent=4,ensure_ascii=False)
        return header,data


def aesd(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"xxxxxxxxxxxxxxxx"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()

通过设置好插件的python路径和脚本路径,启动插件,就可以邮件解密数据了,原谅我码较厚。

通过右键解密会弹出一个窗口来显示解密数据,如果想在bp的原始窗口显示解密数据你可以开启自动加密解密功能

0x3 mitmproxy

还有一种便是通过python的mitmproxy来编写代理拦截脚本,对数据进行加密和解密。

想必这也是大家的老朋友了,中间人攻击的居家旅行必备良药。

3.1 响应包解密

这里拿有道翻译举个例子

有道翻译的返回数据都是经过加密的,如图

通过分析加密代码发现加密方式为aes加密,找到了加密的key和IV就可以编写脚本了

这里只处理返回数据,编写response函数即可

我们可以编写一个类,通过类方法来处理加密数据,使用flow.response.get_text函数可获取响应包,然后将响应包进行解密,通过response.set_text函数将解密后的数据替换掉响应数据

完整代码

from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow
# import json
import base64

class Mimit():


    def response(self,flow):
        resp = flow.response.get_text()
        rep = AES_Decrypt(resp)
        flow.response.set_text(rep)


def AES_Decrypt(data):
    key=b'\x08\x14\x9d\xa7\x3c\x59\xce\x62\x55\x5b\x01\xe9\x2f\x34\xe8\x38'
    iv=b'\xd2\xbb\x1b\xfd\xe8\x3b\x38\xc3\x44\x36\x63\x57\xb7\x9c\xae\x1c'
    aes = AES.new(key,AES.MODE_CBC,iv)
    rep = aes.decrypt(base64.urlsafe_b64decode(data)).decode('utf-8')
    return rep



addons = [Mimit(),]

启动mitm

mitmdump -p 9090 -s .\test.py --ssl-insecure

我们再将burpsuite的上游代理设置为9090端口,这样就能看到明文数据了

3.2 请求体和响应体解密

网上找了几个没发现请求体和响应体都同时加密的站,索性简单的写了一个测试站

可以看到数据都是加密过的

想要在bp里看到明文的请求数据和响应数据,就需要结合bp的上游代理和下游代理了

加密数据发送到下游代理,再解密数据发送到bp,bp便能看到明文的请求数据

同样的,服务器返回的数据到bp的上游代理,再解密数据,bp便能看到明文的返回数据

这其中有个小问题

从请求方向看,下游代理解密数据后,数据一直以明文的形式发送到服务器,服务器肯定要按照设定的程序对数据进行解密,解密必定是失败的,所以无法返回正常数据。

从响应方向看,响应数据在上游代理被解密为明文后,数据一直以明文的形式返回到浏览器,前端代码对数据解密也必然是失败的,也会报错。

所以我们需要在上游代理对明文数据进行加密后发送给服务器,在下游代理将数据加密发送给浏览器客户端,如下图

这样就可以正常查看明文数据了,同样的可以结合xray来使用这种方法。

另外,遇到的一种情况有些接口是明文发送,不用加密,为了防止二次加密,编写一个函数来识别数据是否需要解密和加密,由于本站是使用json来传输数据,可以使用json.loads()来识别数据是否是明文和密文。

下游代理代码

from mitmproxy import flowfilter,ctx
from mitmproxy.http import HTTPFlow
from Crypto.Util.Padding import pad
import json
from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow
# import json
import base64

class Mimit():

    def request(self,flow):
        if flow.request.host=="127.0.0.1":
            req = flow.request.get_text()
            ctx.log.info("请求数据 => "+req)
            if IsJson(req):
                flow.request.set_text(req)
            else:
                data = AesDecrypt(req)
                flow.request.set_text(data)


    def response(self,flow):
        rep = flow.response.get_text()       
        ctx.log.info("响应数据 => "+flow.response.get_text())
        if IsJson(rep):
            rep = Encrypt_data(rep,"aa834085ebd67a24")
            flow.response.set_text(rep)
        else:
            flow.response.set_text(rep)

def AesDecrypt(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"aa834085ebd67a24"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()

def Encrypt_data(data, key):
    aes = AES.new(key.encode("utf-8"), AES.MODE_ECB)
    pad_pkcs7 = pad(data.encode('utf-8'), AES.block_size, style='pkcs7')
    # 加密函数,使用pkcs7补全
    res = aes.encrypt(pad_pkcs7)
    # 转换为base64
    msg = str(base64.b64encode(res), encoding="utf-8")
    return msg

def IsJson(data):
    try:
        data = json.loads(data) 
        return True
    except: 
        return False


addons = [Mimit(),]

上游代理代码

from mitmproxy import flowfilter,ctx
from mitmproxy.http import HTTPFlow
from Crypto.Util.Padding import pad
import json
from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow
# import json
import base64

class Mimit():

    def request(self,flow):
        if flow.request.host=="127.0.0.1":
            req = flow.request.get_text()
            ctx.log.info("请求数据 => "+req)
            #如果使用bp的repeater模式,判断是否为明文
            if IsJson(req):
                data = Encrypt_data(req,"aa834085ebd67a24")
                flow.request.set_text(data)

            else:
                flow.request.set_text(req)


    def response(self,flow):
        resp = flow.response.get_text()
        ctx.log.info("返回数据 => "+resp)
        if IsJson(resp):
            flow.response.set_text(resp)
        else:
            resp = AesDecrypt(resp)
            flow.response.set_text(resp)



def AesDecrypt(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"aa834085ebd67a24"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()

def Encrypt_data(data, key):
    aes = AES.new(key.encode("utf-8"), AES.MODE_ECB)
    pad_pkcs7 = pad(data.encode('utf-8'), AES.block_size, style='pkcs7')
    # 加密函数,使用pkcs7补全
    res = aes.encrypt(pad_pkcs7)
    # 转换为base64
    msg = str(base64.b64encode(res), encoding="utf-8")
    return msg

def IsJson(data):
    try:
        data = json.loads(data) 
        return True
    except: 
        return False


addons = [Mimit(),]

启动下游代理,将数据发送到bp的8080端口

mitmdump -p 7070 -s .\test.py --mode upstream:https://127.0.0.1:8080 --ssl-insecure

启动上游代理

mitmdump -p 9090 -s .\test.py --ssl-insecure

bp设置上游代理到9090端口,这样就可以查看请求和响应的明文了

0x4 工具结合

4.1 sqlmap

在使用sqlmap配合加密数据时,只需要设置一层上游代理即可,代码可以上面的bp上游代码共用,但前提是你需要将解密好的playload给sqlmap,sqlmap将数据发送给上游代理,上游代理加密后发送给服务器,服务器返回数据给上游代理,解密后明文还给sqlmap。但如果你要将原始密文数据给sqlmap,就需要再来一层下游代理了,后面讲xray的时候会将。

代码

from mitmproxy import flowfilter,ctx
from mitmproxy.http import HTTPFlow
from Crypto.Util.Padding import pad
import json
from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow

import base64
from urllib.parse import unquote

class Mimit():

    def request(self,flow):
        if flow.request.host=="127.0.0.1":
            req = flow.request.get_text()
            ctx.log.info("请求数据 => "+unquote(req))
            #如果使用bp的repeater模式,判断是否为明文
            if IsJson(req):
                data = Encrypt_data(req,"aa834085ebd67a24")
                flow.request.set_text(data)

            else:
                flow.request.set_text(req)


    def response(self,flow):
        resp = flow.response.get_text()
        ctx.log.info("返回数据 => "+resp)
        if IsJson(resp):
            flow.response.set_text(resp)
        else:
            resp = AesDecrypt(resp)
            flow.response.set_text(resp)



def AesDecrypt(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"aa834085ebd67a24"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()

def Encrypt_data(data, key):
    aes = AES.new(key.encode("utf-8"), AES.MODE_ECB)
    pad_pkcs7 = pad(data.encode('utf-8'), AES.block_size, style='pkcs7')
    # 加密函数,使用pkcs7补全
    res = aes.encrypt(pad_pkcs7)
    # 转换为base64
    msg = str(base64.b64encode(res), encoding="utf-8")
    return msg

def IsJson(data):
    try:
        data = json.loads(data) 
        return True
    except: 
        return False


addons = [Mimit(),]

启动代理

mitmdump -p 9090 -s .\test.py --ssl-insecure

sqlmap

sqlmap -r sql.txt -proxy http://192.168.1.5:9090

4.2 xray

xray的话还是需要设置2层代理,我们在浏览器点击一个链接时发送的数据是密文的,需要经过一次解密再发送给xray。

但这个第一层代理就只管解密一次,可以简单点

from mitmproxy import flowfilter,ctx
from mitmproxy.http import HTTPFlow
from Crypto.Util.Padding import pad
import json
from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow

import base64
from urllib.parse import unquote

class Mimit():

    def request(self,flow):
        if flow.request.host=="127.0.0.1":
            req = flow.request.get_text()
            ctx.log.info("请求数据 => "+unquote(req))
            #如果使用bp的repeater模式,判断是否为明文
            if IsJson(req):
                flow.request.set_text(req)

            else:
                data = AesDecrypt(req)
                flow.request.set_text(data)


    def response(self,flow):
        pass


def AesDecrypt(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"aa834085ebd67a24"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()


def IsJson(data):
    try:
        data = json.loads(data) 
        return True
    except: 
        return False


addons = [Mimit(),]

上游代理

from mitmproxy import flowfilter,ctx
from mitmproxy.http import HTTPFlow
from Crypto.Util.Padding import pad
import json
from Crypto.Cipher import AES 
from mitmproxy import flowfilter
from mitmproxy.http import HTTPFlow

import base64
from urllib.parse import unquote

class Mimit():

    def request(self,flow):
        if flow.request.host=="127.0.0.1":
            req = flow.request.get_text()
            ctx.log.info("请求数据 => "+unquote(req))
            #如果使用bp的repeater模式,判断是否为明文
            if IsJson(req):
                data = Encrypt_data(req,"aa834085ebd67a24")
                flow.request.set_text(data)

            else:
                flow.request.set_text(req)


    def response(self,flow):
        resp = flow.response.get_text()
 #       ctx.log.info("返回数据 => "+resp)
        if IsJson(resp):
            flow.response.set_text(resp)
        else:
            resp = AesDecrypt(resp)
            flow.response.set_text(resp)



def AesDecrypt(s):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])
    unpad = lambda s : s[0:-s[-1]]
    key = b"aa834085ebd67a24"
    ciphertext_b64 = s

    cipher = AES.new(key, AES.MODE_ECB)
    ciphertext = base64.b64decode(ciphertext_b64)
    plaintext_padded = cipher.decrypt(ciphertext)
    plaintext = unpad(plaintext_padded)
    return plaintext.decode()

def Encrypt_data(data, key):
    aes = AES.new(key.encode("utf-8"), AES.MODE_ECB)
    pad_pkcs7 = pad(data.encode('utf-8'), AES.block_size, style='pkcs7')
    # 加密函数,使用pkcs7补全
    res = aes.encrypt(pad_pkcs7)
    # 转换为base64
    msg = str(base64.b64encode(res), encoding="utf-8")
    return msg

def IsJson(data):
    try:
        data = json.loads(data) 
        return True
    except: 
        return False


addons = [Mimit(),]

启动下游代理,将数据转发到xray的端口

mitmdump -p 7070 -s .\test.py --mode upstream:https://127.0.0.1:8090 --ssl-insecure

启动上游代理

mitmdump -p 9090 -s .\test.py --ssl-insecure

配置xray的代理,修改config文件

启动xray

xray.exe -ws --listen 127.0.0.1:8090

接下来配置浏览器代理端口为7777即可

但xray这里没能跑出来sql注入,应该是xray对json的解析有问题,无法解析json的值,将payload直接拼接在了后面,不是json传输的站点应该没问题。

测试代码

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户查询</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

</head>
<body>


    <script>
function decryptAES(data, key) {  
    var key  = CryptoJS.enc.Utf8.parse(key);
    var decrypt = CryptoJS.AES.decrypt(data, key, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return CryptoJS.enc.Utf8.stringify(decrypt).toString();

};


        function sendNumber() {
          // 获取输入框中的数字
          var num = $('#numberInput').val();

          // 创建一个包含数字的 JSON 对象
          var data = {
            number: num
          };
          var datas = JSON.stringify(data);

           const aesKey = CryptoJS.enc.Utf8.parse("aa834085ebd67a24");
           var encrypted = CryptoJS.AES.encrypt(datas, aesKey, { 
  mode: CryptoJS.mode.ECB,
  padding: CryptoJS.pad.Pkcs7
}).toString();;
console.log(encrypted);
          // 发送 AJAX 请求
          $.ajax({
            url: 'query.php', // 替换为你的 PHP 文件路径
            type: 'POST',
            data: encrypted,

            success: function(response) {
              // 请求成功后的处理逻辑
              var result =  decryptAES(response, "aa834085ebd67a24");


              document.getElementById("result").innerHTML = result;

            },
            error: function(xhr, status, error) {
              // 请求失败后的处理逻辑
              console.log(error);
            }
          });
        }
        </script>


<input type="text" id="numberInput">
<button onclick="sendNumber()">发送</button>

<div id="result">结果:</div>>

</body>
</html>

query.php

<?php
function aes_encrypt($data, $key) {
    $cipher = "aes-128-ecb"; // 使用 AES 128位 ECB模式
    $options = OPENSSL_RAW_DATA; // 使用原始数据输出

    // 对密钥进行处理
    $key = substr($key, 0, 16); // 只使用前16字节作为密钥

    // 加密数据并返回结果
    $result = openssl_encrypt($data, $cipher, $key, $options);

    return base64_encode($result);
}

function aes_decrypt($data, $key) {
    $cipher = "aes-128-ecb"; // 使用 AES 128位 ECB模式
    $options = OPENSSL_RAW_DATA; // 使用原始数据输出

    // 对密钥进行处理
    $key = substr($key, 0, 16); // 只使用前16字节作为密钥

    // 解密数据并返回结果
    $result = openssl_decrypt(base64_decode($data), $cipher, $key, $options);
    return $result;
}


// 获取发送的 JSON 数据
$data = file_get_contents('php://input');
$number = aes_decrypt($data,"aa834085ebd67a24");


// 提取数字

$num = json_decode($number,true)['number'];


// 进行处理(这里只是一个示例)

// 返回响应



$con=mysqli_connect("localhost","root","123456","db_name");
// 检测连接
if (mysqli_connect_errno())
{
    echo "连接失败: " . mysqli_connect_error();
}

$result = mysqli_query($con,"SELECT * FROM v9_admin WHERE userid={$num}");

while($row = mysqli_fetch_array($result))
{
    $str = array('username' =>  $row['username'],   'password' => $row['password'],'lastloginip' => $row['lastloginip'],'email' => $row['email'] );

}
//$results = aes_decrypt(json_decode(json_encode($str)),"aa834085ebd67a24");
$a = json_encode($str);
$results = aes_encrypt($a,"aa834085ebd67a24");
echo $results;

?>

0x4 总结

其实对于数据全加密的站点还好,要是部分接口加密,部分接口又不加密,这种就比较恼火,还要判断数据是否为明文,json格式传输的站点还好判断,其他的貌似就不好判断是否是明文还是密文了。另外就是要对算法的逆向和调试了,只有先找到了算法和相关信息,才能编写解密脚本。

0x5 参考链接

https://mp.weixin.qq.com/s/C0RoOau5Twc9Pkyggi98hw

https://mp.weixin.qq.com/s/9MijUowOd5eAkAwSQievFw

https://blog.csdn.net/freeking101/article/details/83901842


文章来源: https://xz.aliyun.com/t/12652
如有侵权请联系:admin#unsafe.sh