URL 验证的2种方案

1,212 阅读5分钟

前言

聊一则趣事:

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 天

点击查看活动详情