前言
真不容易~
qqcms
这题不难,侥幸拿了个一血(嘻嘻~
首先是后台的密码被改了,前台的功能并不多,翻遍了也没找到什么漏洞点,SQL都是预编译的,然后无意间看到一个函数:
突然想到在 seacms 中有 parseIf 做解析执行代码,但是这里的 if_Tmp
是没有eval的,回溯一下看看哪里调用的:
突然想起来在 seacms 中的 globals 的时候获取的seach,点进去发现果然有:
然后再看上张图,include_tmp 函数中可以任意文件读,但是他在 global 前就做完工作了,global_tmp 后面虽然函数很多大部分都很安全,最终运气好乱点发现 loop_Tmp:
看到这个 sql 一瞬间就能想到任意 SQL 执行了,直接利用:
1 | /index/search.html?Search=12345{/if}{{loop sql='INSERT INTO `qc_user` VALUES (7, 18888888887, 1, 1, "e10adc3949ba59abbe56e057f20f883e", "", "", 1, "", 2, 0.00, 0, 1, 1652334396, "127.0.0.1", 1, 1, 1, 1652334410, "127.0.0.1");'}}{{/loop}} |
好像用 firefox 访问会快很多,搞得我以为题目有问题呢。
成功后就可以直接登录了
然后这个时候想起来模板处有个 include,直接读flag吧:
不过一开始改的 footer,发现不起作用,也没细看了,就改这个detail_article 没问题:
前台随便点一个文章即可:
php_again
这题看了将近20个小时,看到凌晨五点,- -还是二血
step1 RCE
文件读取
首先第一步是一个php代码:
1 |
|
这里有个什么 0_0
啊 0o0
这种,php7会报错,但是php8却没问题。首先发现可以任意文件读,怎么做呢,上面会发现他有 readfile:
首先创建一个软连接:
1 | rm -f /tmp/haihai.zip && ln -s /etc/passwd www.zip&&zip --symlinks /tmp/haihai.zip www.zip && cat /tmp/haihai.zip |base64|tr -d '\n'&&echo |
把 www.zip 当成一个软链接,压缩进压缩包里,然后base64,然后解压出来的 www.zip 就是软连接了。
一开始以为是条件竞争,但是后来发现不是因为条件竞争。是因为创建了软链接以后
1 | zip -r /tmp/www.zip * |
这个代码就会报错,就不能覆盖www.zip了,自然就跟着软链接读取了。
本地拉一个 php8.2.2:
1 | php:8.2.2-apache |
对了,这里的什么 0_0
0o0_111
稍微贴一下都对应着什么:
然后如果想看 phpinfo
的话就两个 0
就好了:?action=00
然后上那个bash生成一段base64:
1 | import requests |
写得很粗糙,大概就行了,效果:
可以看到确实是不能覆盖了,就能读了。
但是我读了两个小时flag,尝试了各种名字,发现都不行,后来突然想起来给了个phpinfo,我相信题目里给的一切都是有用的(除非出题人🐎没了)
PHP8 中 opcache 文件夹名的研究
我对比了一下我本地默认的和远程,我发现多了个 opcache,突然想起来去年做过一道opcache的题,然后开始搜索,找到一个且是唯一一个github:
https://github.com/GoSecure/php7-opcache-override/
这里有个很明显的四个字符,php7
,这道题是 php8 的,使用上面的那个官方的php8.2.2 然后配置上 opcache(他默认自带了opcache我还在网上找了半天依赖)
在文件:/usr/local/etc/php/conf.d/docker-php-ext-sodium.ini(不一样的话看看phpinfo,这里用的是官方的php8镜像。)
1 | zend_extension=opcache |
会生成个什么文件夹呢:
这个md5是8131开头的,远程的是不一样的,为什么呢,这里用上面的github工具看看:
可以看到 Zend Extension ID
是唯一的差别,其他的都一样,更致命的是算出来的和本地的是不一样的,说明php8和php7改了的。
这个时候我想到了第一个方法:
我想到使用类似patch的方法把这个0829改成0929,当然,我本人不会:
patch 完后发现尽然不生成了。我还想试过把远程的 opcache 读取出来,发现也不行。无奈只能找源码了:
https://github.com/php/php-src/blob/php-8.2.2/ext/opcache/zend_file_cache.c
发现他的 system_id 其实不是在 opcache 中算出来的,在文件 Zend/zend_system_id.c
中:
会发现其实好像加进去的也差不多,但是我看来看去也没看出来到底多了什么少了什么,于是我想到最后一个方法,直接编译就完了,本地编译太麻烦了,这个时候就能想到宝塔了,宝塔安装的时候可以编译,但是宝塔的 ZEND_EXTENSION 也是 20220829 的,怎么办呢,我们找到这个 ZEND_EXTENSION_BUILD_ID 在哪里定义的:
然后宝塔安装的 8.2 是 8.2.4 ,我们需要 8.2.2,那也简单:
然后还要找到两个文件位置,提前写好一段代码,一个是因为 system_id 是经过 md5编译的,所以找到 MD5 函数:
还有一个就是一开始提到的 zend_system_id.c:
在算完以后把 system_id 写进去。
哦其实不写也可以,直接安装一个 opcache,就能看到缓存文件夹名字了,所以好像只用改两个版本。
然后宝塔就来了:
目录:/www/server/php,首先他会 wget 源码,然后解压,解压以后趁着编译之前,我们把版本号修改了就好啦~
这时还没解压好,解压好以后会变成 src:
然后就可以开始改了,理论上只需要改最上面两个版本即可,如果想看md5前是什么,就可以加上文件写的。最后在编译完成前改好,等待编译完即可:
这里有一些特殊字符,我是不知道到底怎么做的拼接,所以才改的版本~
算出 246104dd1c75c908e3152fa39e48dfb5 就可以去读取一下看看对不对了:
1 | rm -f /tmp/haihai.zip && ln -s /tmp/246104dd1c75c908e3152fa39e48dfb5/var/www/html/index.php.bin www.zip&&zip --symlinks /tmp/haihai.zip www.zip && cat /tmp/haihai.zip |base64|tr -d '\n'&&echo |
生成出的 base 替换一下脚本里的就好:
成功~
然后就本地生成一个 index.php.bin,替换掉远程的 timestamp 和 system_id 。
opcache getshell
因为这个目录应该是不能变的,所以我又回到了 php8 的官方镜像中:
把这个 bin 复制出来,然后用上面 github 中的 010editor 的模板,就能看到结构了:
然后获取远程的:
1 | wget http://eci-2ze7fgyivez44tjlxhrk.cloudeci1.ichunqiu.com/index.php\?action\=73 -O index.php.bin |
替换这两个就好了,checksum是不需要替换的。然后本地 tmp 文件里创建一串文件夹:
拿着这段base去替换脚本里的然后解压,然后访问就好了:
step2 提权
使用蚁剑以后发现没有权限:
suid也没有啥可用的,其他的提权CVE我也不熟悉,但是我看到个py_server.py,然后 ps aux也发现了这个进程,并且还是root用户启动的,代码:
1 | import multiprocessing |
这里可以看到 forkserver,猜测大概率是用这个提权,搜索一下:
发现了这个CVE:CVE-2022-42919
https://github.com/python/cpython/issues/97514
仔细阅读一下,发现说3.9以前有限制,题目用的刚好是3.9,然后找了半天也没找到EXP,估计没有公开的,只能自己写了,看看漏洞的描述:
说是通过命名空间发送pickle数据,说实话,没接触过,但是昨天凌晨跟AI聊天聊着聊着,他给了我这样的代码:
再看看这个 issue 是怎么推荐修复的:
可以看到这个代码:应该是监听了
1 | \0listener-pid-0 |
这个 {next(_mmap_counter)}
默认是 0,至于怎么知道的,当然是:
知道以后就可以连接然后发送数据了,这里直接贴 EXP吧:
1 | import socket |
可以看到文件属性改了,说明代码执行成功了。
我这里就不班门弄斧了,简单说一下函数的过程,有兴趣的师傅自己跟吧,首先已知 connection.py
里的arbitrary_address
返回监听地址。
然后回到 forkserver.py 的 ensure_running
:
在这里bind以后,在这一块代码地上和下面:
这里运行的当前文件的main函数:
首先上面是 reduction 的 recvfds:
这里是 recvmsg,必须要 sendmsg 发送,然后发送的格式也是结合网上和瞎试出来的,然后进入 _serve_one:
这里如果 fds 需要进行一下 unpack,所以要足够的个数(应该是这样),然后调用到 spawn 的 _main 方法:
可以看到在这里做了 pickle.load,触发漏洞了,这里猜测就是传入文件的内容。
对了,这里要提一点,exp中:
这个 \0x4
是下面有几个 fd
,一个就\x01
,但是如果只有一个的话会报错:
然后如果过了这个,就是把它改成 \x03
和三个 fd的话,又会报错另一个:
最后测试出来四个执行了。。
大概就是这样~
总结
说实话 php_agian 确实有点难了,最后感谢好兄弟的鼓励🙏(嘻嘻: