什么是漏洞
OWASP 项目将漏洞定义为:漏洞是应用程序中的一个漏洞或弱点,这可能是设计缺陷或实现上的错误,允许攻击者对应用程序的利益相关者造成伤害。漏洞的定义取决于你的威胁模型(什么是威胁模型?我们将在下次分享中详细了解)。例如,一个简单的 DNS 泄露漏洞可能被奖励 700 美元,但在隐私保护软件的背景下,这种漏洞可能非常重要,甚至会危及用户安全。
同样,像 npm audit 这样的工具可能会报告大量依赖项中的漏洞。然而,实际上,即使你的软件使用了这些存在漏洞的依赖项,它本身也可能并不脆弱,因为可能并未调用那些有漏洞的函数,或者调用方式无法触发漏洞。
CWE 与 CVE
CVE(Common Vulnerabilities and Exposures,通用漏洞与暴露)是一个已知网络安全漏洞和暴露的公共记录列表,每条记录包含一个标识号、描述以及至少一个公共引用。你可以在 CVE Details 网站上找到现有 CVE 的列表。
CWE(Common Weakness Enumeration,通用弱点枚举)是一个社区开发的软硬件弱点类型列表。你可以在线访问 CWE 列表:CWE 官网。
因此,CWE 是可能导致 CVE 的一种模式。并非所有漏洞都有对应的 CVE ID,有时是因为发现漏洞的人认为没有必要提交,有时是因为他们不希望漏洞被公开披露。
漏洞与漏洞利用
漏洞是应用程序中的一个缺陷,而漏洞利用(Exploit)是利用这个问题进行攻击的代码或方法。编写漏洞利用程序被称为“武器化”:将软件缺陷转化为可操作的数字武器的过程。编写漏洞利用程序是一门需要深入技术知识的精细艺术。例如,编写一个 XSS 漏洞(我们将在后面讨论)的利用程序通常需要对 Web 和 JavaScript 生态系统有深入了解,以绕过漏洞的限制,例如字符数量限制或只允许特定字符集。
0 Day 与 CVE
并非所有漏洞都是公开的。一些漏洞被发现后会被秘密保留,以便武器化或出售给那些会将其武器化的人。一个非公开但为某些人所知的漏洞利用程序被称为 0 Day(零日漏洞)。关于零日漏洞市场的更多信息,可以参考 Wikipedia 上的优秀文章:Market for zero-day exploits。
Web 漏洞
我认为玩具式的漏洞示例并不能教会你什么。因此,与其为本书单独构造一些你在现实中几乎不会遇到的玩具示例,不如直接推荐我认为是关于真实产品漏洞发现和利用的最佳分析文章。
注入攻击
注入攻击是一类漏洞,攻击者可以向 Web 应用程序注入恶意数据,从而导致各种效果。所有注入攻击的根本原因都是 程序输入处理不当。
什么是程序输入?
1. 对于 Web 应用程序来说,可能是表单的输入字段。
2. 对于 VPN 服务器来说,可能是网络数据包。
3. 对于 Wi-Fi 客户端来说,可能是检测到的 Wi-Fi 网络名称。
4. 对于邮件应用程序来说,可能是邮件内容和附件。
5. 对于聊天应用来说,可能是消息、用户名或媒体文件。
6. 对于视频播放器来说,可能是视频内容。
7. 对于音乐播放器来说,可能是音乐文件及其元数据。
8. 对于终端来说,可能是用户输入和命令行应用程序的输出。
HTML 注入
HTML 注入是一种漏洞,攻击者可以将任意 HTML 代码注入到 Web 应用程序的响应中。它可以用于篡改页面内容或诱使用户执行有害操作,例如用恶意表单替换登录表单。
以下是一个存在 HTML 注入漏洞的伪代码示例:
function comment(req, res) {
letnew_comment = req.body.comment;
// 评论在保存到数据库时未进行任何过滤
save_to_db(new_comment);
letall_comments = db.find_comments();
lethtml = "";
// 堆代码 duidaima.com
// 评论在渲染时未进行任何过滤
for (comment in comments) {
html += "<div><p>" + comment + "</p></div>";
}
res.html(html);
}
SQL 注入
在2010年代,SQL 注入非常流行,主要是因为 PHP 的普及。如今,得益于 ORM 和其他 Web 框架提供的良好安全默认值,这类漏洞已经越来越少见。
以下是一个存在 SQL 注入漏洞的伪代码示例:
function get_comment(req, res) {
letcomment_id = req.query.id;
// 拼接字符串来构建 SQL 查询是致命的
letsql_query = "SELECT * FROM comments WHERE id = " + comment_id;
letcomment = db.execute_query(sql_query);
lethtml = template.render(comment);
res.html(html);
}
可以通过以下请求进行利用:
GET https://target.com/comments?id=1 UNION SELECT * FROM users
盲注(Blind SQL Injection)
盲注是指当 SQL 注入漏洞存在时,但应用程序并未将查询结果直接显示在页面上。 你可以在这里学习如何利用盲注漏洞:PortSwigger Blind SQL Injection。https://portswigger.net/web-security/sql-injection/blind
XSS
XSS(跨站脚本攻击)是一种攻击形式,攻击者可以将恶意脚本(通常是 JavaScript)注入到网站中。
以下是一个存在 XSS 漏洞的伪代码示例:
function post_comment(req, res) {
let comment = req.body.comment;
// 需要对输入进行过滤!
db.create_comment(comment);
res(comment);
}
XSS 的三种类型:
1. 反射型 XSS:漏洞仅在请求的生命周期内存在,通常出现在 URL 查询参数或 HTTP 头中。
2. 存储型 XSS:漏洞存在于服务器存储的内容中,例如博客评论。
3. 基于 DOM 的 XSS:漏洞存在于客户端的 DOM 操作中,例如通过修改 window.location 来触发脚本。
服务器端请求伪造(SSRF)
SSRF 漏洞允许攻击者从 Web 应用程序的服务器发起 HTTP 请求,并且大多数情况下能够读取响应内容。
以下是一个存在 SSRF 漏洞的伪代码示例:
function check_url(req, res) {
let url = req.body.url;
// 需要对 URL 进行白名单验证
let response = http_client.get(url);
// 不要直接返回 HTTP 请求的结果
res(response);
}
为什么 SSRF 很危险? SSRF 通常允许攻击者访问本应不可公开访问的内部服务,这些服务往往不需要身份验证。
跨站请求伪造(CSRF)
CSRF 漏洞允许攻击者强迫用户执行未授权的操作。以下是一个存在 CSRF 漏洞的伪代码示例:
$current_user = $_COOKIE["id"];
$role = $_POST["role"];
$username = $_POST["username"];
if (is_admin($current_user)) {
set_role($role, $username);
}
攻击者可以在其网站上托管以下表单:
<form action="https://target.com/admin" method="POST">
<input type="hidden" name="role" value="admin" />
<input type="hidden" name="username" value="attacker" />
</form>
<script>
document.forms[0].submit();
</script>
开放重定向
开放重定向漏洞允许攻击者将用户从合法网站重定向到另一个恶意网站。
以下是一个存在开放重定向漏洞的伪代码示例:
function do_something(req, res) {
let redirect_url = req.body.redirect;
// 需要对重定向目标进行白名单验证
res.redirect(redirect_url);
}
(子)域名接管
子域名接管漏洞通常是由于 DNS 记录指向了一个攻击者可以控制的公共云资源。
任意文件读取
任意文件读取漏洞允许攻击者读取本应保持私密的文件内容。
以下是一个存在漏洞的伪代码示例:
function get_asset(req, res) {
let asset_id = req.params.asset_id;
let asset_content = file.read('/assets' + asset_id);
res(asset_content);
}
攻击者可以通过以下请求利用该漏洞:
https://example.com/assets/../etc/passwd
拒绝服务(DoS)
DoS 攻击的目标是使服务对其合法用户不可用。
强行让你的服务器负载,目前几乎所有黑客的基础攻击都是Dos。
任意文件写入
任意文件写入漏洞允许攻击者覆盖本应保持完整的文件内容。
如果扫描发现 可以写入
../root/.ssh/authorized_keys
,那这是一件非常危险的信号。
内存漏洞
Rust 的一个重要优势是其内存安全性,能够避免大多数内存漏洞,例如缓冲区溢出、双重释放等。
缓冲区溢出
缓冲区溢出(Buffer Overflow)是一种常见的安全漏洞,主要发生在程序试图将超过缓冲区大小的数据写入内存时。这种漏洞在低级编程语言(如C、C++)中尤其常见,因为这些语言允许直接操作内存而没有边界检查。缓冲区是程序中用来存储数据的一块内存区域。例如,在编写代码时,你可能会定义一个固定大小的数组作为缓冲区,用来存储用户输入或其他数据。
以下是一个容易受到缓冲区溢出攻击的 C 程序:
#include <stdio.h>
#include <string.h>
voidvulnerable_function(char *input) {
char buffer[16]; // 定义一个16字节的缓冲区
strcpy(buffer, input); // 将输入复制到缓冲区
printf("Buffer content: %s\n", buffer);
}
intmain() {
char user_input[256];
printf("Enter some text: ");
gets(user_input); // 从用户读取输入(存在漏洞)
vulnerable_function(user_input);
return0;
}
攻击流程:
1. 程序漏洞:gets(user_input) 没有检查输入长度,strcpy(buffer, input) 也没有检查输入是否超出 buffer 的大小。
2. 攻击者输入:攻击者输入一段超长数据,其中包含恶意代码和覆盖返回地址的值。
3. 返回地址被覆盖:当 vulnerable_function 返回时,程序会跳转到攻击者指定的地址并执行恶意代码。
缓冲区溢出是一种经典的漏洞类型,尽管现代编程语言和工具已经提供了许多保护机制。但对于C / C++的语言来讲需要额外小心。
使用后释放(Use After Free)
使用后释放漏洞是指程序尝试使用已经被释放的内存。
双重释放(Double Free)
双重释放漏洞是指程序试图释放已经释放的内存。
格式化字符串问题
格式化字符串漏洞可能导致信息泄露或代码执行。
远程代码执行(RCE)
RCE 是指攻击者能够远程在目标机器上执行任意代码。
整数溢出(和下溢)
整数溢出漏洞发生在算术操作试图生成超出变量范围的值时。
逻辑错误
逻辑错误允许攻击者操控应用程序的业务逻辑。
竞争条件
竞争条件漏洞发生在程序依赖多个并发操作的顺序或时序时。
总结
本文从多个角度详细阐述了常见的漏洞类型及其危害,包括注入攻击、XSS、SQL 注入、SSRF、CSRF 等经典漏洞,同时也提到了一些更具技术深度的漏洞,如缓冲区溢出、任意文件读取与写入、开放重定向以及内存安全问题。通过这些实例,我们可以看到漏洞的本质在于输入处理不当或安全机制的缺失。
在讨论漏洞的同时,本文也强调了 Rust 的内存安全优势。Rust 通过其独特的所有权系统和严格的编译器检查,有效避免了如缓冲区溢出等传统低级语言中常见的漏洞,为开发者提供了一个更加安全的编程环境。安全是一个动态的过程,而非一劳永逸的结果。无论是开发者还是安全研究人员,都需要不断学习、实践和反思,才能在快速变化的技术环境中保持对漏洞的敏感性与防御能力。