愚人杯
| 2023-5-7
0  |  Read Time 0 min

easy_signin

点击,看到一个滑稽,同时观察到url变化了
notion image
很明显img参数就是base64,解码发现内容是face.png,这下懂了,就是内容读取,所以直接改成index.php并进行base64编码,内容是aW5kZXgucGhw,用img请求,右键查看源代码得到index.php的base64编码内容,再丢去解码得到flag
notion image
notion image

easy_ssti

关键词:Jinja2, SSTI, Flask

代码

前置知识

思考

因为我之前是没有接触过flask的,一上来看到ssti有点懵逼,不知道是啥,所以先参考了一下前置知识,了解到了flask/jinja2中使用{{}}可以进行表达式运算,甚至可以动态引用库从而获得flag。
return render_template_string('hello %s' % name)中,使用{{ }}语法也是基于Jinja2模板引擎的特性。

做题

在首页查看源代码,我们可以发现html注释,通过访问 app.zip 来下载源码,得到要访问 /hello/xxx来应用jinja模板
直接传入{{}}可以得到错误信息,虽然没多大用处
尝试传入{{config.items()}}失败,因为题目代码过滤了f
这也就表明想要通过 {{config}}获取信息就不可能了
做题前先了解一下Flask相关的SSTI原理,为了避免占太大的篇幅,就放到下面这个子页面说了
SSTI Flask 前置知识

subprocess.Popen

我们的目的是执行其他命令,所以我们要找到这一个类并且运行
找到了,它在这
notion image
那我怎么知道它是第几个?全局搜索<然后数格子哇
notion image
这里他在第396,想必用它就是在[395]位置了
notion image
一访问,还真是,舒服啦
尝试
输出失败,原因不明

stdout=-1

原因找到了
stdout=1表示将标准输出重定向到文件描述符1,这是一个整数,表示标准输出。在Python中,标准输出通常被重定向到终端窗口或控制台。
然而,由于Jinja2模板中的代码并不是在控制台上执行,因此无法直接输出到终端窗口。此外,Jinja2模板中的代码被包含在HTML页面中,因此还需要考虑输出到HTML页面的方式。
为了在Jinja2模板中输出命令执行结果,可以将stdout参数设置为-1,这会将标准输出重定向到管道,从而使命令输出能够被重定向到communicate()方法返回的元组中。例如:
在上述代码中,使用stdout=-1将标准输出重定向到管道,并通过communicate()方法获取命令执行结果。

在无法使用 / 的情况下拿到/flag

因为url一传 / ,就会被当路径给分隔掉,所以用一种奇淫巧计,就是pwd获得当前路径,然后分隔第一个字符,再拼接flag不就可以了吗~
由于$ { }这种字符还可能会被当成url的奇怪参数,所以最好编码一下
notion image
这就是,你心心念念的flag.jpg

被遗忘的反序列化

源码

思路

看最底下,是通过HTTP请求头并反序列化来搞的,所以只要在HTTP请求头放入序列化的类就好了。然后关键在于gBoBg这个类,有使用一个叫 new $this→coos($this→file)的,其中 this→coos和$this→file都可控。
所以认为整个的思路应该是,通过 w_wuw_w 的 __invoke 调用EeE,通过EeE的__get调用gBoBg,同时clone可以用于调用cycycycy中的aaa。然后aaa可以直接eval,拿shell。但是我目前不知道cipher到底是干嘛的,因为不是原生函数,所以第一步应该还是要让gBoBg工作。
同时,还要让类里的某一个变量嵌套另一对象,这样来实现一个类调用另一个类的东西。比如说,我让EeE的变量eeee等于另一个类,并且serialize出来,这样unserialize就可以互相调用啦

参考资料

解题方法1

获取txt文件名名称

先看题目,题目中说有一个txt文件,但是用dirsearch好像扫不出来,我们就要利用题目中的反序列化条件,我的思路是,通过DirectoryIterator创建类,传入参数 glob://./*.txt 来获取当前目录下的所有txt文件名
看题目,可被执行 __wakeup 的类有两个,分别是EeE和w_wuw_w。但是w_wuw_w似乎没法通过file_get_contents来获取文件名,遂作罢。目光转向EeE,我原本在想这个wakeup到底能怎么利用,看了writeup以后发现,原来text可以是一个对象我们的目的不是要使等式成立,而是要通过 $this→text来调用gBoBg中的toString,从而通过传入 $coos 和 $file 来new任意类,那 DirectoryIterator 配合 glob 就可以满足这个工作啦~
所以写下第一个代码,用于获取txt文件名
获得序列化的字符串 O:3:"EeE":2:{s:4:"text";O:5:"gBoBg":2:{s:4:"file";N;s:4:"coos";s:17:"DirectoryIterator";}s:4:"eeee";N;}
通过断点我们可以发现,它先执行了EeE的wakeup,然后经过 $this→text 调用了gBoBg的 __toString,然后通过 new $this→coos($this→file) 获取了当前目录下所有txt文件内容,并且echo出来
然后通过请求头设置AAAAAA,内容为这个序列化的字符串,就可以拿到txt文件名啦
notion image
访问后获得提示

得到eval权限

既然已经拿到了key,看提示应该是move2到4位,所以就是要猜,应该是移位密码。
看了下writeup,最终要得到eval的执行权限,所以我们应该调用 cycycycy 的aaa(),cipher函数的工作原理我们不清楚,但是根据hint大概可以推得,需要使用移位密码的帮助,至于是往左移还是往右移。。。我也不知道啊,设成-4~4呗,都试一遍总不会出问题了吧~
需要调用aaa(),我们可以看到EeE的__clone里面有一个调用$a→aaa(),同时也是由EeE调用的,那我们就要想怎么调用得了EeE的clone,再看下面,有 w_wuw_w调用__invoke,可以调用得了 clone new EeE,所以问题就变成了怎么调用 w_wuw_w 的__invoke。
invoke的调用方法要取得其他方法的new,刚好gBoBg可以自定义new的类,那我们就将 coos 设成 w_wuw_w ,$this→file 不设置,$this→name 随意设置就行了面,问题变成了怎么调用gBoBg的toString。
上面EeE的中,有调用 text 哇,将EeE的text设置成gBoBg就好了,那怎么调用EeE的__wakeup()呢
调锤子调,直接unserialize的是EeE就行了hhh
所以最终的流程图大概就是这样(但是是错的hhh)
notion image
然后发现是错的,想法很美好,实际上根本没有调用到 __invoke(),但是确实new了w_wuw_w,却没有直接调用这个类方法(要使用 $a = new w_wuw_w; $a(); 这种方法才能调用__invoke()。),所以并没有调用得到 __invoke()。
既然这个流程不正确,我们需要修改一下这个流程。实际上,我们不一定要执行gBoBg的第一个判断,第二个参数因为没有其他参数有内部存在 →name 的,所以可以直接执行第三个判断条件,即存在 $aa() 的这个
notion image
最终的效果就是这样了(后记:但这样还是错的,主要的原因是,注意isset($this→file),如果为空的话,就会进到第二个判断块,而且要在class内设,不能在外面用 $a→file来设,不然进去还是null)
notion image
不知道为什么这样设置以后,会唤醒 w_wuw_w 的 file_get_contents ,然后就进行不到下一步了。(后面有解决)
下面的代码是最终可用的序列化代码
去掉禁用报错提醒以后发现,在PHP8.2.0下,file_get_contents如果不传入参数,php就会直接宕掉,但是禁用了报错提醒的结果就是,前面的show_source是正常执行的,你压根不知道为啥会寄。。。
注意这里容易混淆的是 if(!isset($this -> file)) ,想要绕过这个if,就要设置非空$file!跳到最后一个else块,让它能执行aa();,这样才能访问到invoke()
序列化的结果是 O:3:"EeE":1:{s:4:"text";O:5:"gBoBg":2:{s:4:"file";s:4:"test";s:4:"coos";O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";N;s:4:"file";s:1:"a";}}}
notion image
这下对了,剩下的工作就是猜key了

猜key

猜key我们用执行phpinfo()判断文件长度来猜测。
用Go来写下面的程序,思路是修改get参数判断文件长度,如果它最与众不同,那就是它了,move说是2~4,但是我们不知道是左移还是右移,所以都要尝试一遍
这里要注意, req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"} 这个一定不能丢,我刚开始还以为是burpsuite问题,为啥post上去PHP接不到post参数,拿了两个小时调试以后才发现请求头丢了这一个。。。不要图简洁就把所有请求头删了,一定要检查一下是干嘛的hhh
notion image
找到实际的密钥为 fe1ka1ele1efp
得到key了,直接上burpsuite,改eval开搞!
可以看到,返回内容
flag这不就找到了嘛,令system参数为 cat f1agaaa,拿到flag咯~
最终拿到flag的请求如下

解题方法2

这里感谢 pip0x0 给出的非预期解,发现还有其他方法
之前提到的 DirectoryIterator ,其实能够知道文件名是f开头的话,也可以用这个来找文件名
序列化结果是: O:3:"EeE":1:{s:4:"text";O:5:"gBoBg":3:{s:4:"name";s:4:"test";s:4:"file";s:11:"glob:///f";s:4:"coos";s:17:"DirectoryIterator";}}
可以知道文件名是 f1agaaa
然后很可惜,这题里面的file_get_contents并没有用,是干扰项
为何不再调用一个可以查看文件的类呢~
偷看pip同学的wp后发现,可以通过 SplFileObject 来读取文件,并且输出文件内容,简单修改一下上面的php文件就可以了
序列化结果是: O:3:"EeE":1:{s:4:"text";O:5:"gBoBg":3:{s:4:"name";s:4:"test";s:4:"file";s:8:"/f1agaaa";s:4:"coos";s:13:"SplFileObject";}}
这样更简单,而且一样可以拿到flag。
再次感谢pip同学!

总结

这道题还是有点难顶,但是也学到了很多。其中学到的东西包括,类里面的变量可以传一个对象,以及__invoke()、__wakeup()等函数是什么时候被调用的,以及怎么巧妙地利用__tostring(),还有学会了利用DirectoryIterator类来获取文件名等等。总之是对自己很有帮助的一题,虽然用的时间很长,用了几天来解决这一道题。

Easy_Flask

参考资料

做题

先注册一个账号,再登录,登录以后看到代码,发现有个secret_key,用的是session。我一开始以为Flask的session和php的一样,全部存在服务器没法看的,但是搜索后发现,数据只是以 base64 的形式存储在cookie中,然后用secretkey签个名而已。
访问 /profile ,发现role为admin有特殊服务,所以就是想办法拿到admin权限
notion image
访问 /show/, 获得代码和secret_key,得知要将role改为admin,可以用
这个工具来修改。
将role改成admin,再来一次
修改cookie再请求,发现可以下载flag了,但是是假flag,文件名却可以修改
修改文件名为 app.py ,查看源代码如下
可知/hello处有一个eval的执行,可以动态引入os来拿到flag
注意不能用 os.system() ,而应该用 os.popen(),因为os.system()会直接将输出打印到控制台,而只会拿到返回的int结果。
所以我们构造参数eval为 __import__("os").popen("ls%20/").readlines()
得到返回结果
hello,['app\n', 'bin\n', 'dev\n', 'etc\n', 'flag_is_h3re\n', 'home\n', 'lib\n', 'media\n', 'mnt\n', 'opt\n', 'proc\n', 'root\n', 'run\n', 'sbin\n', 'srv\n', 'sys\n', 'tmp\n', 'usr\n', 'var\n']
然后构造参数eval为 __import__("os").popen("cat%20/flag_is_h3re").readlines()
拿到flag

easy_php

代码

做题

这里正则拦截了O和a开头的序列化字符串,所以我们就要参考一下序列化的格式到底有什么。很显然,序列化不单只是O和a开头的,还有其他的形式,比如我们这里用的C(custom object),在反序列化的时候会解释为一个新的类而不是对象,这个类即使是名字相同,但是和原来的ctfshow类不是同一个类了。
我们的目的应该是使类ctfshow能执行到__destruct,并且使$this→ctfshow可控,还不能碰到wakeup

参考资料

  • GitTalk
Catalog