前言
聊一则趣事:
2019 年,当时一名攻击者在未经授权的情况下访问了Capital One(美国最大银行之一) 的数据。据《纽约时报》, 报道,攻击者获得了第一资本客户的 1 亿条客户记录、14 万个社会安全号码和 8 万个相关银行详细信息。
攻击者访问了由 Amazon Web Services (AWS) 托管的 Capital One 服务器。检测延迟表明攻击者对 AWS 基础设施有深入了解,并在托管安全服务提供商 (MODSEC) 的 Web 应用程序防火墙 (WAF) 中发现了一个可利用的漏洞。有了这些知识,犯罪者进行了 SSRF 攻击,并通过操纵易受攻击的 Web 服务器发出新的 HTTP 请求——从而成功获得对 AWS 元数据服务的访问权限。
SSRF (恶意脚本注入和服务器端请求伪造)。当我们在获取远程资源时不应用安全编码约定来验证用户提供的
URL
时,恶意行为者可以利用SSRF
攻击。2021 年 OWASP 将SSRF
列为Top 10,至今为止,他仍然是对基于JavaScript
(无论是前端还是Node.js
服务器端)的开发中的严重威胁之一
开始
SSRF 的漏洞触发就在于利用了 URL 的漏洞。
今天我们要探讨的技术-- URL进行验证
当我们处理不同形式的 URL
——例如浏览器历史导航、a标签的href
、查询参数时,经常被要求我们在前端对 URL 验证,
URL
一个典型的 URL 包含:协议(protocol)、域名(domain name)、主机名(hostname)、资源名称(resource name)、来源(origin)、端口port等。
主机名验证
首先将主机名分成单独的字符,以确保它们符合顶级域名规范。典型的主机名至少包含两个由点分隔的字符。例如www.juejin.cn
, 具有字符“www”、“juejin”和“com”。每个字符只能包含一个字母、数字、字符或者一个连字符,不区分大小写。
资源路径的验证
默认情况下资源路径 没有限制,在这里不做校验。
端口的验证
只能在 1 到 65536 的范围内。超出该范围的任何内容都会引发错误。我们还可以检查数字 IP 地址以判断它是 IPV4 地址还是 IPV6 地址。
用户密码
最后,虽然不重要,但我们还可以检查 URL 中的用户名和密码。此功能有助于遵守政策和凭证保护。
如何在 JavaScript 中执行 URL 验证
在 JavaScript 中执行 URL 验证的最简单方法是使用 new URL
构造函数。
它支持 Node.js 和大多数浏览器。
创建 URL
通过 URL 构造函数创建URL。
new URL (url,base)
使用最简单的方式来创建URL:
let adr = new URL("https://juejin.io/en-US/docs");
let host = adr.host;
let path = adr.pathname;
我们创建了一个名为 adr
的 URL 对象。然后,给 URL 的主机和路径名赋值,分别是 juejin.io
和 /en-US/docs
。
验证 URL
function checkUrl (string) {
let givenURL ;
try {
givenURL = new URL (string);
} catch (error) {
console.log ("error is", error);
return false;
}
return true;
}
-
举例一:如果将
www.juejin.cn
传递给此函数,它将返回 false,因为它不包含有效的 URL 协议。正确应该是https://juejin.cn
。 -
举例二:
maio:John.Lee@example.com
。这是一个有效的 URL,但如果您删除冒号,则是错误的。 -
举例三:
ftp://
。这是无效的,因为它不包含主机名。如果添加两个点 (..
)--ftp://..
,它就是有效,因为这些点将被视为主机名。
最后记住2个非常规但有效的 URL !
- new URL(“youtube://a.b.c.d”);
- new URL (“a://1.2.3.4@1.2.3.4”);
验证特定的 URL 协议(protocol):
function checkHttpUrl(string) {
let givenURL;
try {
givenURL = new URL(string);
} catch (error) {
console.log("error is",error)
return false;
}
return givenURL.protocol === "http:" || givenURL.protocol === "https:";
}
此函数验证 URL,然后检查它是否使用 HTTP 或 HTTPS 协议。这里, ftp://..
将无效,因为它不包含 HTTP 或 HTTPS。
正则表达式验证 URL
验证 URL 的另一种方法是使用正则表达式 (regex):
function isValidURL(string)
{
var res =
string.match(/(https?://(?:www.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-
]+[a-zA-Z0-9].[^\s]{2,}|www.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]
.[^\s]{2,}|https?://(?:www.|(?!www))[a-zA-Z0-9]+.[^\s]{2,}|w
ww.[a-zA-Z0-9]+.[^\s]{2,})/gi);
return (res !== null);
};
测试一下:
var tc1 = "http://helloworld.com"
console.log(isValidURL(tc1));
// true
var tc4 = "helloWorld";
console.log (isValidURL(tc4));
// false
上面函数检查 URL 是否以 http://
或 https://
开头,以及它是否包含域名。
tc1返回 true
,tc4返回 false
值,因为tc4不以任何允许的方案或子域开头,也不包含域名:
正则表达式相对简单,但不能正确识别。因为正则表达式无法充分验证 URL 的规则。它最多能做的就是匹配有效的 URL。此外,当正则表达式包含复杂的验证逻辑或冗长的输入字符串时,执行验证检查会非常耗时。
遇到复杂的正则表达式,为了满足定义的正则表达式验证检查,浏览器必须通过输入字符串回溯数百万次。如此多的回溯可能会导致 catastrophic backtracking,导致进程爆炸。
扩展
URL 不仅可以前端进行验证,也可以在以下几个方面验证,保证安全性
- 在服务器端验证 URL 来帮助减轻此类攻击。
- 将系统限制为仅使用几种协议(例如 HTTP 或 HTTPS)来避免 SSRF 攻击。
- 为了增加安全性,我们必须设置应用传递 URL 参数时受到监督。
- 设置允许访问和拒绝访问的列表用于过滤,降低 SSRF 的可能性。
- 允许访问的列表能够预定义的对象和服务器分类。拒绝访问的列表限制常用的主机名。
总结
URL 的安全风险不在于它们的有效性,而在于危险的 URL 方案。因此,我们需要确保服务器端应用程序执行验证。攻击者可以绕过客户端验证机制,因此完全依赖它是行不通的。
全文完
谢谢!
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天