javascript:链接如何拖垮你的站点:Ammonia URL协议白名单与UrlRelative配置实战
【免费下载链接】ammoniaRepair and secure untrusted HTML项目地址: https://gitcode.com/gh_mirrors/am/ammonia
如果你的网站允许用户提交 HTML、评论或富文本,一条javascript:链接可能就是拖垮整站的隐形炸弹。本文带你用 Rust 生态中流行的HTML 净化库 Ammonia(基于白名单机制,专为防止 XSS、布局破坏和点击劫持而设计),通过URL 协议白名单(url_schemes)和UrlRelative 相对链接策略两项核心配置,快速构建一条可靠的安全防线。🛡️
一条 javascript: 链接能做什么坏事?
想象评论区里这样的内容:
<a href="javascript:fetch('/api/secret')">点我看答案</a>用户一点,浏览器直接在你的域名下执行脚本——读取 Cookie、调用你的 API、发起伪造请求。更阴险的写法还有:
HREF属性值中混入换行和大小写绕过(如javAscript:、javascript:)data:协议内嵌 HTML 页面,实现点击劫持(clickjacking)- 相对链接
//evil.com伪装成站内资源,把用户流量导到恶意站点
这些攻击的共同点:不是攻击标签,而是攻击链接里的 URL。所以 HTML 净化的关键一步,就是管住href和src里能写什么。
💡 Ammonia 使用 html5ever 按 HTML5 标准解析输入,解析方式和浏览器一致,因此对这类"语法混淆"非常免疫——攻击者骗过 naive 正则清洗器的写法,在 Ammonia 眼里都是普通 HTML。
第一道防线:URL 协议白名单(url_schemes)
在 src/lib.rs 中,Ammonia 对每个href/src属性都会先解析 URL,再检查协议是否出现在白名单里,不在名单内的协议,属性整个被删掉。
默认白名单里有什么?
Ammonia 的默认协议白名单比较"宽容",包含 25 个协议(见 src/lib.rs 中Builder::new()的默认值):
bitcoin, ftp, ftps, geo, http, https, im, irc, ircs, magnet, mailto, mms, mx, news, nntp, openpgp4fpr, sip, sms, smsto, ssh, tel, url, webcal, wtai, xmpp注意两点:
- ✅
javascript、data、vbscript都不在默认名单中,天然被拦截 - ⚠️
ftp、ssh、magnet、tel等默认都在——对普通内容站点来说偏宽了
实战:收紧到最小集合
一般内容站建议只保留http、https、mailto:
use ammonia::Builder; use maplit::hashset; let safe = Builder::new() .url_schemes(hashset!["http", "https", "mailto"]) .clean(user_html) .to_string();配套还有两个细粒度方法:
| 方法 | 作用 | 使用建议 |
|---|---|---|
url_schemes(set) | 整体替换白名单 | 明确自己只需要哪些协议时 |
add_url_schemes(&[...]) | 在默认名单上追加 | 业务需要自定义协议(如my-scheme:)时 |
rm_url_schemes(&[...]) | 从默认名单中移除 | 只想去掉个别危险协议(如ftp)时 |
对于javascript:链接的清洗效果是:链接文字保留,href被删除,变成无害的普通文本,既安全又不影响阅读。
另外,Ammonia 默认会给外链自动加上rel="noopener noreferrer"(link_rel配置),防止反向标签劫持,这一层防护通常是免费白送的。👍
第二道防线:UrlRelative 相对链接策略
协议白名单只管"带协议的绝对 URL"。而相对链接(如href="/admin"、href="//evil.com/x"、href="../secret")不含协议,走的是另一套逻辑——url_relative()配置的UrlRelative策略。
相对链接为什么危险?
//evil.com/page是"协议相对链接",浏览器会用当前页面的协议直接访问别人的域名,常被用来钓鱼- 站点内容被嵌入 iframe 或转发到别处时,站内相对路径会悄悄指向宿主域名下的敏感接口
Ammonia 提供 5 种策略(定义见 src/lib.rs 中的UrlRelative枚举):
| 策略 | 对相对链接的处理 | 适用场景 |
|---|---|---|
Deny | 全部删除 | 最严格,适合完全不可信的 UGC |
PassThrough(默认) | 原样保留 | 内容确定渲染在自己站内时 |
RewriteWithBase(base_url) | 按基准 URL 改写成绝对链接 | 归档、离线分发内容 |
RewriteWithRoot { root, path } | 强制归入指定目录 | 让内容更易移植 |
Custom(闭包) | 返回Some(新值)改写,None删除 | 需要自定义规则的灵活业务 |
⚠️最容易被忽略的一点:默认策略是PassThrough,也就是说什么都不配的话,//evil.com这类协议相对链接会被原样放行。如果你的场景面向外部投稿,建议显式收紧。
三种典型配置组合
🔒 组合一:评论区/论坛(最严格)
Builder::new() .url_schemes(hashset!["http", "https"]) .url_relative(UrlRelative::Deny) // 相对链接一律删除📰 组合二:博客/文档站(站内互链友好)
Builder::new() .url_schemes(hashset!["http", "https", "mailto"]) .url_relative(UrlRelative::RewriteWithBase( Url::parse("https://your-site.com/")? )) // 相对链接统一改写成你站点的绝对链接🧩 组合三:自定义规则(例如只允许以 / 开头的站内路径)
用UrlRelative::Custom传入一个判断函数,返回Some(改写结果)保留、返回None删除,文档里的示例(src/lib.rs 的UrlRelative注释)演示了如何识别"绝对路径"并加上统一前缀,思路完全可以照搬。
上手只需三步
- 引入依赖:在
Cargo.toml中声明ammonia = "4.1"(项目最低要求 Rust 1.80,见 Cargo.toml) - 配置 Builder:按上文组合设置
url_schemes与url_relative - 先净化,后渲染:所有外部 HTML 一律走
clean()再入页
想动手试试?仓库自带了一个命令行小工具 examples/ammonia-cat.rs,把 HTML 从标准输入读入、净化后输出到标准输出,方便你用真实脏数据观察清洗效果。也可以先阅读 README.md 了解定位,并对照 CHANGELOG.md 关注安全修复记录——例如 4.1.3 就修复了一个由 MathML 命名空间切换引发的 mXSS 问题,生产环境请务必使用最新版本。
常见误区与避坑清单
- ❌ 以为"默认配置就安全"——默认协议名单含
ftp/ssh/tel等,且相对链接默认放行,请显式收紧 - ❌ 只在入口做一次净化,之后又拼接了用户原始 HTML——净化结果才是唯一可信输出
- ❌ 用正则替代解析器做预过滤——浏览器解析出的"坑"(嵌套标签、异常命名空间)正则是扫不全的,Ammonia 的 html5ever 解析就是为此而生
- ✅ 补充意识:
javascript:之外,还要靠url_schemes一并挡掉data:、vbscript:等"非浏览器协议",它们是点击劫持和离线攻击的常客 - ✅ 性能无忧:构建 DOM → 过滤节点 → 重新序列化,官方基准测试显示其速度约为 Python bleach 的 15 倍(数据见 README.md 与 benchmarks/)
写在最后
防御javascript:这类链接攻击,本质上就是回答两个问题:允许哪些协议?相对链接怎么办?Ammonia 用url_schemes白名单和UrlRelative策略把这两个问题变成了两行配置,再配合 HTML5 标准解析带来的混淆免疫能力,让"净化不可信 HTML"这件事对新手也足够友好。把本文的两道防线放进你的内容管线,恶意链接就再也拖不垮你的站点了。🚀
【免费下载链接】ammoniaRepair and secure untrusted HTML项目地址: https://gitcode.com/gh_mirrors/am/ammonia
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考