LOADING

加载过慢请开启缓存 浏览器默认开启

Galaxy实现HTTP自动加解密

2025/5/5 JS

‌Galaxy插件‌是一款强大的加解密工具,特别适用于Burp Suite环境,能够帮助渗透测试人员在处理加密报文时提供极大的便利。

Galaxy

项目地址:https://github.com/outlaws-bai/Galaxy

Galaxy可以HOOK如下4个过程,插件会根据自定义的HOOK脚本自动化加解密后续代理的流量,并且可以与sqlmap、xray等扫描工具联动。

微信截图_20250505185708

request.host修改成需要加解密的网站

img

AES固定密钥

js看到默认key和iv是1234567890123456,加密模式为AES-CBC

img

测试过程中发现加密的body在经过AES—BASE64后还需要url编码一下,不然会失败。

img

由于不存在响应加解密,只需要实现hook_request_to_burp和hook_request_to_server函数即可。

import base64
from urllib.parse import unquote_to_bytes, quote_from_bytes

from fastapi import FastAPI
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from _base_classes import *

KEY = b"1234567890123456"
IV = b"1234567890123456"
JSON_KEY = "data"
app = FastAPI()

@app.post("/hookRequestToBurp", response_model=RequestModel)
async def hook_request_to_burp(request: RequestModel):
    print("------------------------------------------浏览器发送请求------------------------------------------------------")
    """HTTP请求从客户端到达Burp时被调用。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。"""
    # 获取需要解密的数据
    encrypted_data: bytes = get_data(request.content)
    # 调用函数解密
    if encrypted_data:
        data: bytes = decrypt(encrypted_data)
    else:
        print("此次请求body为空")
        data: bytes = b""
    # 更新body为已解密的数据
    request.content = data
    return request

@app.post("/hookRequestToServer", response_model=RequestModel)
async def hook_request_to_server(request: RequestModel):
    """HTTP请求从Burp将要发送到Server时被调用,在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。"""
    # 获取并加密请求体
    data: bytes = request.content
    print(data)
    encryptedData: bytes = encrypt(data)
    body: bytes = to_data(encryptedData)
    # 对 body 进行 URL 编码
    url_encoded_body = quote_from_bytes(body).encode()
    new_body = b"encryptedData=" + url_encoded_body
    request.content = new_body
    return request

def decrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return unpad(cipher.decrypt(content), AES.block_size)

def encrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return cipher.encrypt(pad(content, AES.block_size))

def get_data(content: bytes) -> bytes:
    parts = content.split(b"encryptedData=")
    if len(parts) > 1:
        decoded_encrypted_part = unquote_to_bytes(parts[1])
        print(decoded_encrypted_part)
        return base64.b64decode(decoded_encrypted_part)
    else:
        print("未找到 encryptedData= 相关内容")
        return content

def to_data(contnet: bytes) -> bytes:

    return base64.b64encode(contnet)

if __name__ == "__main__":
    # 多进程启动
    # uvicorn aes_cbc:app --host 0.0.0.0 --port 5000 --workers 4
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=5000)

Burp重放为明文,发送后python自动加密,服务端也返回了正确的响应。

img

img

AES服务端获取Key

首先获取一个key和iv

img

然后利用获取的key和iv去发送请求

img

和上次的代码相比只是增加了key和iv的base64解码,提取内容换成json

import base64
import json
import typing

from fastapi import FastAPI
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from _base_classes import *

KEYBase64 = "n+l5EahgQMBysLZ+EBxvNg=="
IVBase64 = "9igMfnRW3OM+9tyU\/dQUIA=="

KEY = base64.b64decode(KEYBase64)
IV = base64.b64decode(IVBase64)

JSON_KEY = "encryptedData"
app = FastAPI()

@app.post("/hookRequestToBurp", response_model=RequestModel)
async def hook_request_to_burp(request: RequestModel):
    print("------------------------------------------浏览器发送请求------------------------------------------------------")
    """HTTP请求从客户端到达Burp时被调用。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。"""
    # 获取需要解密的数据
    encrypted_data: bytes = get_data(request.content)
    # 调用函数解密
    if encrypted_data:
        data: bytes = decrypt(encrypted_data)
    else:
        print("此次请求body为空")
        data: bytes = b""
    # 更新body为已解密的数据
    request.content = data
    return request

@app.post("/hookRequestToServer", response_model=RequestModel)
async def hook_request_to_server(request: RequestModel):
    """HTTP请求从Burp将要发送到Server时被调用,在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。"""
    # 获取并加密请求体
    data: bytes = request.content
    print(data)
    encryptedData: bytes = encrypt(data)
    body: bytes = to_data(encryptedData)
    request.content = body
    return request

def decrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return unpad(cipher.decrypt(content), AES.block_size)

def encrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return cipher.encrypt(pad(content, AES.block_size))

def get_data(content: bytes) -> bytes:
    body_json: typing.Dict = json.loads(content)
    return base64.b64decode(body_json[JSON_KEY])

def to_data(contnet: bytes) -> bytes:
    body_json = {}
    body_json[JSON_KEY] = base64.b64encode(contnet).decode()
    return json.dumps(body_json).encode()

if __name__ == "__main__":
    # 多进程启动
    # uvicorn aes_cbc:app --host 0.0.0.0 --port 5000 --workers 4
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=5000)

img img

测试案例

测试地址:http://39.98.108.20:8085/

请求体有三个参数Requestid、Sign、timestamp

timestamp是一个时间戳,现在需要找Requestid和Sign

img

Requestid来源

img

生成一个符合 UUIDv4(UUID版本4)规范的随机字符串

img

Sign来源

Sign = MD5(请求体+Requestid+时间戳)

img

构造请求体的Requestid、Sign、timestamp可以正常获取数据

img

使用Galaxy提供的模板修改代码,实现更换headers三个参数以及请求体和响应体的加解密。

import hashlib
import json
import base64
import time
import random
import uuid

from fastapi import FastAPI
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from _base_classes import *


KEY = b"1234567891234567"
IV = b"1234567891234567"

app = FastAPI()


@app.post("/hookRequestToBurp", response_model=RequestModel)
async def hook_request_to_burp(request: RequestModel):
    print("------------------------------------------发送请求------------------------------------------------------")
    """HTTP请求从客户端到达Burp时被调用。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。"""
    # 获取需要解密的数据
    encrypted_data: bytes = get_data(request.content)
    # 调用函数解密
    if encrypted_data:
        data: bytes = decrypt(encrypted_data)
    else:
        print("此次请求body为空")
        data: bytes = b""
    # 更新body为已解密的数据
    request.content = data
    return request

@app.post("/hookRequestToServer", response_model=RequestModel)
async def hook_request_to_server(request: RequestModel):
    """HTTP请求从Burp将要发送到Server时被调用,在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。"""
    # 获取并修改 headers
    my_headers = dict(request.headers)  # 转换为可修改的字典

    #生成requesdid和timestamp
    request_id = uuid.uuid4().hex
    timestamp = str(int(time.time() * 1000))

    #获取body,获取生成的headers
    data: bytes = request.content
    headers = generate_sign(data,request_id,timestamp)

    # 添加 request-id,UUIDv4格式
    my_headers['requestId'] = [headers["requestId"]]

    # 新增时间戳
    my_headers['timestamp'] = [headers["timestamp"]]

    # 生成sign
    my_headers['sign'] = [headers["sign"]]

    print("sign:", my_headers['sign'])
    print("requestId:", my_headers['requestId'])
    print("timestamp:", my_headers['timestamp'])
    print("sign_source:", headers['sign_source'])
    # 获取并加密请求体
    encryptedData: bytes = encrypt(data)
    body: bytes = to_data(encryptedData)
    request.content = body

    # 更新 headers
    request.headers = my_headers
    
    return request

@app.post("/hookResponseToBurp", response_model=ResponseModel)
async def hook_response_to_burp(response: ResponseModel):
    print("------------------------------------------接受请求------------------------------------------------------")
    """HTTP响应从Server到达Burp时被调用。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。"""
    if not response.content:
        # 如果内容为空,直接返回
        return response
    try:
        # 获取需要解密的数据
        encryptedData: bytes = response.content

        # 响应体按 HTTP 报文可能分隔符查找
        raw_text = encryptedData.decode(errors="ignore")  # 允许忽略部分非 UTF-8 字节
        split_index = raw_text.find("HTTP/1.1")

        # 截取 HTTP/1.1 前的部分作为 base64 加密数据
        if split_index != -1:
            encrypted_b64 = raw_text[:split_index].strip()
        else:
            # 没有第二个 HTTP 报文,整段就是加密的
            encrypted_b64 = raw_text.strip()

        # 解密
        encrypted_b64 = get_data(encrypted_b64)
        data: bytes = decrypt(encrypted_b64)

        # 更新内容
        response.content = data

        print("未能成功解密,保持原响应体")
        return response
    except Exception as e:
        print(f"未能成功解密,保持原响应体: {e}")
        # 如果解密失败,也返回原始响应
        return response

    return response

@app.post("/hookResponseToClient", response_model=ResponseModel)
async def hook_response_to_client(response: ResponseModel):
    """HTTP响应从Burp将要发送到Client时被调用。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。"""
    # 获取需要加密的数据
    encrypted_data: bytes = encrypt(response.content)
    # 调用函数加密
    data: bytes = to_data(encrypted_data)
    # 更新body为已加密的数据
    response.content = data
    return response

def decrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return unpad(cipher.decrypt(content), AES.block_size)

def encrypt(content: bytes) -> bytes:
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    return cipher.encrypt(pad(content, AES.block_size))

def get_data(content: bytes) -> bytes:
    try:
        return base64.b64decode(content)
    except Exception as e:
        print("Base64 解码失败:", e)
        return b""


def to_data(contnet: bytes) -> bytes:
    return base64.b64encode(contnet)


#获取requestid
def generate_uuid_v4():
    t = "0123456789abcdef"
    e = [random.choice(t) for _ in range(32)]
    e[14] = "4"
    e[19] = random.choice("89ab")
    e[8] = e[13] = e[18] = e[23]
    return "".join(e)

#获取sign
def generate_sign(raw_body: bytes, request_id: str, timestamp: str) -> str:
    try:
        body_dict = json.loads(raw_body.decode('utf-8'))
    except Exception as e:
        print("请求体不是有效的 JSON:", e)
        body_dict = {}

    body_str = json.dumps(body_dict, separators=(',', ':'), ensure_ascii=False)

    sign_source = body_str + request_id + timestamp
    sign = hashlib.md5(sign_source.encode('utf-8')).hexdigest()

    return {
        "sign": sign,
        "requestId": request_id,
        "timestamp": timestamp,
        "sign_source": sign_source  
    }

if __name__ == "__main__":
    # 多进程启动
    # uvicorn aes_cbc:app --host 0.0.0.0 --port 5000 --workers 4
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=5000)

img

img

Tips:Burp大小写敏感,put到burp的参数要以http数据包为准,不然会出现多个参数。

img