吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 497|回复: 0
收起左侧

[原创] FileRise-ADS-文件名绕过

  [复制链接]
rony244 发表于 2026-10-2 00:55

一个冒号,绕过了 FileRise 的上传黑名单

去年年底我在自己的服务器上装了 FileRise,一个开源的私有网盘面板,拿来放些杂七杂八的文件。用着用着就开始手痒:它那套权限到底管不管用?于是在自己的环境里,拿着上游源码,当成一个正经目标拆了一遍。后来找到了一个能绕过文件名黑名单的东西,上报了,作者在 v3.33.0 修了。这篇把过程写下来,包括代码、复现步骤和真实的报错输出。

先说没成的那些

我最早盯上的是 Windows 的 junction。

手上有台 Windows 服务器,就琢磨:上传目录要是能用目录联接接到别的地方去,是不是就能顺着它读到我本来没权限的文件夹?听起来很美。折腾了一阵,发现没戏——FileRise 的普通账号建不了 junction,这操作得先有本地权限。说白了,你想打这个洞,得先有别的洞。作者后来回信也是这个意思。行,那这条划掉。

第二个是会话固定。测的时候有几次看着像是能借别人的身份登录,我还挺兴奋,以为自己摸到大鱼了。结果发现是虚惊——登录接口有失败限流,连续错几次就返回 429,节奏被我弄乱了,看着像漏洞其实是把测试自己绊倒了。换个干净环境重来一遍,什么都没有。差点就报了个假的出去。

第三个是 7z 解压的通配符。如果压缩包里的文件名带通配符,理论上可能操纵解压落地路径,写到不该写的地方。结果解压那层按字面名字处理,直接给我挡住了。

三个方向,全是死路。正面攻不动,我就知道得换个思路了。

换个方向:看校验,也看它怎么落地

读这种代码我基本不看功能,专门找那些名字里带 isSafe、validate、can、check 的函数。那些地方才是作者心里真正设过防的位置。

FileRise 管上传文件名的叫 UploadNamePolicy,核心是 isAllowedForWrite()。把关键部分拎出来(v3.32.0):

public static function isAllowedForWrite(string $fileName, ?string $mode = null): bool
{
    $fileName = trim($fileName);
    for ($i = 0; $i < 3; $i++) {                 // ① URL 解码,最多三次
        $decoded = rawurldecode($fileName);
        if ($decoded === $fileName) { break; }
        $fileName = $decoded;
    }
    if (
        $fileName === ''
        || str_contains($fileName, '/')          // ② 拦路径符号
        || str_contains($fileName, '\\')
        || basename($fileName) !== $fileName
    ) {
        return false;
    }
    if ($fileName === '' || !preg_match((string)REGEX_FILE_NAME, $fileName)) {
        return false;                            // ③ 正则白名单
    }
    if (str_ends_with($fileName, '.')) {         // ④ 拦尾随点
        return false;
    }
    $lowerName = strtolower($fileName);
    if (in_array($lowerName, self::ALWAYS_BLOCKED_FILENAMES, true)) {
        return false;                            // ⑤ 永久禁止名单
    }
    ...
    $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
    if ($ext !== '' && in_array($ext, self::STRICT_BLOCKED_EXTENSIONS, true)) {
        return false;                            // ⑥ 扩展名黑名单
    }
    return true;
}

两个黑名单:

private const ALWAYS_BLOCKED_FILENAMES = ['.htaccess', '.user.ini', 'web.config'];

private const STRICT_BLOCKED_EXTENSIONS = [
    'asp','aspx','bash','cgi','fcgi','jsp','jspx','phar','php','php3','php4',
    'php5','php7','php8','pht','phps','phtml','pl','py','sh','shtml',
];

看得出作者是真花心思了:试过 URL 编码绕过(所以解三次)、试过路径符号、试过尾随点(shell.php. 那种在部分系统上会被规整成 shell.php,所以专门拦)。③ 那个正则 REGEX_FILE_NAME 是 /^[^\x00-\x1F\/\\]{1,255}$/u,意思是"长度 1–255,不含控制字符、/、\"。

但你注意,这个正则允许 : 和 $。

于是问题就来了:它眼里这个文件名,和后面真正落盘的代码眼里这个文件名,是不是同一回事?

这两拨人经常对不上。校验用一套规则判断,落盘用另一套规则执行。规则一旦有出入,就能造一个"过得了检查、干得成坏事"的输入。

那个冒号

取扩展名,它用的是 PHP 的 pathinfo($fileName, PATHINFO_EXTENSION)。

那我把文件名写成 evil.php::$DATA 呢?

pathinfo 读出来的扩展名,是从最后一个点后面的东西——也就是 php::$DATA。黑名单里只有 php,没有 php::$DATA,于是⑥放行。前面的正则也过(: 和 $ 都允许)。

可这名字递给 NTFS,就是另一回事了。

Windows 的 NTFS 文件系统里,一个文件除了默认数据流,还能挂若干命名数据流(Alternate Data Stream,ADS),写法是 文件名:流名。比如 readme.txt:hidden 就是给 readme.txt 挂了个叫 hidden 的隐藏流,默认看不到。而 ::$DATA 恰好是默认数据流本身的规范名。所以 evil.php::$DATA 在 NTFS 眼里,就是"evil.php 的默认数据流"。创建它,等于创建了 evil.php。

一个字符串,两套理解:

  • isAllowedForWrite() 看到的扩展名是 php::$DATA,不在黑名单,放行;
  • NTFS 看到的是 evil.php,老老实实把它写出来。

检查的是 A,执行的是 B。

而且不止扩展名这一层。"永久禁止"那几个名字一个道理:.user.ini::$DATA 在校验层不等于 .user.ini,到了磁盘上就是 .user.ini。.htaccess、web.config 同理。

一个冒号,两道防线一起过。

(前提绕不开:只在 Windows / NTFS 上成立。Linux 文件名里冒号就是个普通字符,压根没有 ADS 这套东西。所以开发者在自己的 Linux 机器上测,可能一辈子碰不到。)

在自己服务器上跑一遍

纸上推完,得动手。环境是 Windows Server + IIS + PHP 8.3,站点根 D:\FileRise\public,上传目录 D:\FileRise\uploads(在站点根外面)。

登录拿 cookie 和 CSRF:

$u = "http://192.168.100.144"
Set-Content login.json '{"username":"alice","password":"***"}'
curl.exe -s -c ck.txt -X POST -H "Content-Type: application/json" --data-binary "@login.json" "$u/api/auth/auth.php"
$tok = (curl.exe -s -b ck.txt "$u/api/auth/token.php" | ConvertFrom-Json).csrf_token

这里提一句:IIS 默认禁止 PUT,所以能写内容的 saveFile 接口(PUT)直接返回 405,调不动。只能改用 createFile(POST),它建的是空文件。

先来个对照,普通危险名字,应该被拒:

POST /api/file/createFile.php   {"folder":"share","name":"evil.php"}
{"success":false,"error":"Invalid filename"}   [400]

然后同一个名字,尾巴上加 ::$DATA:

POST /api/file/createFile.php   {"folder":"share","name":"evil.php::$DATA"}
{"success":true}   [200]

完整命令(a.json 装的是对照请求体,b.json 装的是绕过请求体):

Set-Content a.json '{"folder":"share","name":"evil.php"}'
curl.exe -s -w "[%{http_code}]" -b ck.txt -X POST -H "X-CSRF-Token: $tok" -H "Content-Type: application/json" --data-binary "@a.json" "$u/api/file/createFile.php"

Set-Content b.json '{"folder":"share","name":"evil.php::$DATA"}'
curl.exe -s -w "[%{http_code}]" -b ck.txt -X POST -H "X-CSRF-Token: $tok" -H "Content-Type: application/json" --data-binary "@b.json" "$u/api/file/createFile.php"

cmd /c dir /b "D:\FileRise\uploads\share"

再看磁盘:

2.txt
3.txt
4.txt
evil.php
新建文本文档.txt

evil.php 就躺在那儿。不该出现的东西,出现了。

ads-iis-repro.jpg

对照组 400、绕过组 200、磁盘上多一个文件,齐了。

影响,以及它到底有多大

这里得说清楚,免得听着比实际厉害。

能确证的:一个普通账号,对一个文件夹有写权限,就能造出管理员明令禁止的文件名。扩展名黑名单和"永久禁止"名单,同时失效。

到不了哪儿:我的部署里上传目录在站点根外面(/uploads/ 返回 404),而且 IIS 默认不让用 PUT,那个能写内容的 saveFile 接口根本调不动,只剩 createFile,而它只能建空文件。所以在我这儿这不是 getshell,就是个文件名策略绕过。

但换个部署就不一样了。如果上传目录会被 Web 服务器解析(Apache 配 .htaccess、PHP-FPM 读 .user.ini 那类),写进去的配置文件就会被执行。作者的部署文档里,历史上就有过把 uploads 软链进站点根的操作——那种配置下,这条请求就从"策略绕过"变成了"写入危险后缀文件"。

所以这洞是真的,但我没给它安一个我这儿证明不了的帽子。

和作者来回

走的是 GitHub 私有漏洞报告。

过程不算顺。前面 junction 那条,作者驳了,而且驳得对——我撤了原来的定级,把"远程低权"的说法也收回了,那条最后改成了文档澄清。认账。

ADS 这条他认了,要我单独提一份,写清三件事:哪个接口受影响、落盘叫什么名、证明了什么影响。照做,还主动写明了"不是 RCE"这个边界。

然后就是修。v3.33.0 的更新日志写得很直接:

File creation and upload paths now reject NTFS alternate-stream syntax before extension and reserved-name policy checks.

修法就是最朴素的那个——在校验之前,先把含冒号的文件名拒掉。源码里就是多了一行 str_contains($fileName, ':'):

if (
    $fileName === ''
    || str_contains($fileName, '/')
    || str_contains($fileName, '\\')
    || str_contains($fileName, ':')     // <- 新增,一行堵死
    || basename($fileName) !== $fileName
) {
    return false;
}

我复测了,同一个 evil.php::$DATA,3.33.0 上返回 400,磁盘干干净净。

补一句

这洞只在 Windows 上成立。Linux 的文件名里带个冒号屁事没有,作者八成用自己的机器测过一遍,什么都没发生,然后就把这块放过去了。我能撞上,纯粹因为我拿 Windows 服务器当靶子。换个环境,这洞可能到现在还躺在那儿。

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Poetry9 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-17 02:16

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表