随着软件复杂度的提升,其攻击面——即潜在漏洞利用入口点的数量——也随之扩大。此外,新功能的快速上线可能导致代码安全性不足,而旧功能则可能存在无人维护或已被弃用的代码。这些都为漏洞的产生提供了机会,因为开发者在面对数百万行代码时,难以全面保证安全,错误在所难免。此外,这些漏洞的影响往往非线性叠加:一些看似轻微的问题可能连锁反应,演变为严重的安全隐患。简言之,目标越复杂,潜在漏洞越多。
例如,微软 Excel 不仅处理 Excel 工作簿文件格式(.xls、.xlsx),还支持符号链接(.slk)、dBase(.dbf)、数据交换格式(.dif)等多种文件格式。这些仅仅是文件输入向量,还需考虑进程间通信(IPC)和其他网络向量。例如,其他进程可能通过组件对象模型(COM)接口控制 Excel,或通过外部数据连接从互联网获取数据,均可能成为可利用的漏洞源。
现代软件庞大的攻击面令人望而生畏。经验不足的漏洞研究者可能尝试测试每个可能的源点,陷入无数死胡同,浪费大量精力。虽然在黑盒测试中,研究者仅能枚举目标的外部攻击面,这种策略尚可理解,但代码审查提供了更高效的缩小搜索范围的途径。例如,在研究 Web 应用时,不必耗费大量时间暴力破解路由并躲避限速和防火墙,可以直接查看相关的路由代码。
本章将带你了解一些最常见的攻击向量,并讲解如何识别与利用它们。我们将从远程攻击向量开始,如 Web 及其他网络协议,随后探讨本地进程间通信方法。最后,深入分析文件格式及其潜在的漏洞实现。每种攻击面都会配备暴露该面向的示例源代码,同时介绍揭示潜在弱点的常见模式及其识别方法。现在,让我们从最大的攻击面——互联网——开始。
互联网
过去,原生应用和网页应用处于两个截然不同的世界:原生应用被编译成运行于特定设备和平台的机器码二进制文件,而网页应用主要使用网页开发语言,向浏览器传递 HTML、JavaScript 和 CSS。因此,它们的攻击面和漏洞利用向量有着极大差异,源代码的获取与分析手段也各不相同。
然而,现代软件开发使得许多网页技术进入了传统非网页环境。从 Node.js 到 WebAssembly,原生应用和浏览器内运行的应用之间的界限日益模糊,软件也不断集成网页功能以支持诸如备份和远程控制等新特性。这进一步凸显了理解网页攻击面和易受攻击代码模式的重要性。从客户端到服务器端漏洞,你将学习如何在代码审查中识别这些漏洞。
网页客户端漏洞
网页服务器因其普遍且易于访问,成为最常见的攻击目标之一。然而,客户端漏洞同样普遍,尤其是在原生设备上运行的软件,如桌面或移动应用。当软件试图从网页加载数据但处理方式不安全时,就可能产生客户端漏洞。接下来,我们将探讨一些常见网页客户端漏洞及其发现方法。
攻击向量
攻击向量取决于软件如何解析数据,从简单获取 JSON 文档,到运行带有 JavaScript 的无头浏览器不等。攻击面还受软件连接目标是否及程度的攻击者控制影响。这些因素决定了你代码分析的范围及潜在漏洞类型。
例如,若软件仅连接固定的域名或 URL,利用漏洞往往需中间人攻击(MITM),攻击者拦截并篡改服务器发给客户端的数据。这要求攻击者对软件运行的网络或设备具备一定控制能力。
2017年,研究人员发现任天堂 Switch 游戏主机使用基于过时 WebKit 的浏览器加载 Wi-Fi 验证门户(如酒店或机场的登录页面),该浏览器存在 CVE-2016-4657 内存破坏漏洞,可导致任意代码执行。Switch 会访问 http://conntest.nintendowifi.net
,比对响应是否为预期字符串 This is test.html page.
。验证门户通常会重定向请求到其登录页,返回不同响应体,促使 Switch 在浏览器中加载该登录页。攻击者可篡改 Switch 或其路由器的 DNS 设置,将请求导向自己控制的服务器,托管利用 CVE-2016-4657 的恶意负载。
对于部分目标,MITM 要求过高或不切实际。但 Switch 允许越狱,即在本应受限设备中执行任意指令,使该攻击向量值得探索。
当攻击者部分或完全控制客户端请求的 URL 时,利用空间更大。例如,在测试 Facebook Gameroom 桌面应用时,我发现其注册了自定义 URI 方案 fbgame://gameid/
,可被操纵让应用在嵌入的基于 Chromium 的浏览器中访问 apps.facebook.com 的不同页面。通过利用该域名下的重定向链,我成功将浏览器重定向至我控制的其他域名上的恶意负载,触发 Chromium 旧版本中的内存破坏漏洞(CVE-2018-6056)。详情可见:spaceraccoon.dev/applying-of…。
本地输入向量(如自定义 URI 方案)与网页工具链(如 apps.facebook.com 的重定向)结合,成为日益常见的利用模式,因嵌入浏览器在桌面应用中普遍且多为过时版本。
识别与分类
你可通过搜索源码中的网页相关 API 和库函数调用,识别并分类网页客户端功能。开发者常用库简化网页请求及响应解析,且库函数和 API 多有公开文档。许多库作为更大框架或 SDK 的一部分分发。例如,微软开发的开源 .NET 框架包含 System.Net.Requests.dll
中的 WebRequest
类,官方文档详见:learn.microsoft.com/en-us/dotne…。
随着经验积累,你会识别流行库和 SDK,快速判断网页攻击面范围。例如,我确认 Facebook Gameroom 含嵌入浏览器,是因为其引用了 CefSharp.dll
—— Chromium Embedded Framework 的 .NET 包装器。通过追踪反编译的 C# 代码中 CefSharp
API 的使用,定位了构成应用网页客户端攻击面的关键代码段。
示例代码:CefSharp 离屏客户端
看清单 2-1,示例展示如何使用 CefSharp 离屏渲染加载网页。
using System;
using CefSharp;
using CefSharp.OffScreen;
class Program
{
static void Main(string[] args)
{
const string testUrl = "https://www.google.com/";
Cef.Initialize(new CefSettings());
var browser = new ChromiumWebBrowser();
➊ browser.Load(testUrl);
Console.ReadKey();
Cef.Shutdown();
}
}
清单 2-1:简单的 CefSharp 离屏客户端
该示例中,ChromiumWebBrowser.Load
API 调用➊ 是识别攻击向量的重要代码,可利用攻击者控制的 URL 触发潜在漏洞。根据 CefSharp 文档,ChromiumWebBrowser.LoadUrlAsync
也是加载 URL 的另一方法。
这些 API 调用技术上更接近“汇”而非“源”,这强调了一个要点:在宏观层面识别软件攻击面时,源与汇的界限模糊,更应聚焦识别可由外部输入触达的代码。
威胁建模帮助你优先关注关键漏洞类别和相关源码区块,随后可运用汇点到源点追踪技术构造实际利用。
扩展思路
这一识别 HTTP 客户端库及其用法的方法,适用于各种代码库。HTTP 客户端不仅限于客户端软件,服务器端请求伪造(SSRF)漏洞的存在即因服务器软件常需发起网络请求。
Web 服务器漏洞
从物联网固件到网页应用,各类软件普遍部署了某种形式的 Web 服务器。完整梳理网页漏洞种类需要一本书的篇幅,本节重点从源代码层面识别和映射网页攻击面。
Web 框架
复杂的网页应用通常依赖 Web 框架,封装和规范许多常见的开发代码模式,减少开发者的编码和维护负担。看清单 2-2,这是一个使用 Node.js 标准 http
库的简单 Web 服务器示例:
const http = require('http');
const server = http.createServer((req, res) => {
res.statusCode = 200;
➊ if (req.method === 'GET') {
if (req.url === '/') {
return res.end('index');
}
if (req.url === '/items') {
return res.end('read all items');
}
➋ if (req.url.startsWith('/items/')) {
const id = req.url.split('/')[2];
return res.end(`read item ${id}`);
}
} else if (req.method === 'POST') {
if (req.url === '/items') {
return res.end('create an item');
}
}
res.statusCode = 404;
return res.end();
});
server.listen(8080, () => {
console.log('Server running at http://localhost:8080/');
});
清单 2-2:基础 Node.js Web 服务器
此示例展示了无框架支持下维护大型 Web 应用代码的困难。区分 GET 和 POST 路由依赖笨拙的嵌套条件判断➊,提取路径参数(如用户 ID)使用易碎的字符串操作➋。相比之下,清单 2-3 使用了 Express 框架:
const express = require('express');
const app = express();
const itemsRouter = express.Router();
➊ itemsRouter.get('/', (req, res) => {
res.send('read all items');
});
itemsRouter.post('/', (req, res) => {
res.send('create an item');
});
itemsRouter.get('/:id', (req, res) => {
➋ const { id } = req.params;
res.send(`read item ${id}`);
});
app.get('/', (req, res) => {
res.send('index');
});
app.use('/items', itemsRouter);
app.listen(8080, () => {
console.log('Server running at http://localhost:8080/');
});
清单 2-3:基于 Express 框架的 Web 服务器
Express 不仅代码更简洁,还封装了常见任务,如请求方法检测➊、路径参数提取➋以及处理不存在的路由。
Web 框架便于重构代码,如将 /items
下的嵌套路由迁移至单独文件,使代码对开发者和漏洞研究者更易读。
模型-视图-控制器(MVC)架构
Web 框架中常见模式是 MVC 架构,将代码分为三部分:
- 模型(Model) :处理业务逻辑,如数据结构
- 视图(View) :处理用户界面,如布局和模板
- 控制器(Controller) :负责请求的控制流,调度相应模型和视图
路由代码通常位于控制器组件。若将清单 2-3 的 Express 服务器转换为 Java 的 Spring MVC,控制器代码类似清单 2-4:
@Controller
➊ @RequestMapping("/items")
public class ItemController {
private final ItemService itemService;
@Autowired
public ItemController(ItemService itemService) {
this.itemService = itemService;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Item> readAllItems() {
return itemService.getAllItems();
}
@RequestMapping(method = RequestMethod.POST)
➋ public String createItem(ItemForm item) {
itemService.createItem(item);
return "redirect:/items";
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Map<String, Item> readItemForId(
@PathVariable Int id,
Model model
) {
return itemService.getItemById(id);
}
}
清单 2-4:Spring MVC 控制器部分代码
从中可观察几个分析 Web 框架的关键挑战:
首先,框架封装大量重复样板代码,但代价是可读性和透明度降低,除非熟悉框架约定,否则难以理解代码功能。比如,@RequestMapping
注解将方法映射到请求路由➊较易理解,但 @Autowired
用于自动注入依赖,未深入了解 Spring 框架则难以理解其细节。
注意 createItem
方法中返回的 "redirect:/items"
➋,redirect:
前缀表明请求应重定向至后续 URL /items
。许多框架用类似前缀或路径字符串规则标识特殊功能和变量(如路径参数),需按框架约定解释路由字符串。
此外,路由定义不一定集中在单一文件,可能继承或扩展其他组件。例如,若路由 /items/123
存在,需同时解析 ItemController
类和其方法上的 @RequestMapping
注解。若新增如下继承控制器(清单 2-5):
@Controller
@RequestMapping("/things")
➊ public class ThingController extends ItemController {
➋ @RequestMapping(value = "/price", method = RequestMethod.GET)
public ModelAndView getPrice() {
// 控制器代码
}
}
清单 2-5:继承控制器类
此控制器定义了 /things/price
路由➋,同时继承了 ItemController
的所有路由和方法➊。因此,分析框架代码需全面,尤其是面向对象语言。
未知或不熟悉的框架
尽管你将逐渐熟悉多种主流 Web 框架,有些应用可能使用定制、修改过的框架,甚至不使用任何框架,不遵循 MVC 等知名模式。为有效分析这类代码,聚焦所有 Web 应用必须实现的常见路由和控制器逻辑。
首先识别代码如何处理 HTTP 请求的基本构件。比如,以下是一个简单的请求示例:
POST /items HTTP/1.1
Host: localhost
Content-Type: application/json
{
"name": "Apple",
"price": 1
}
应用需解析:
- 请求方法
代码如何区分 GET、POST 等?可能是简单字符串比较,也可能使用复杂装饰器如@GetMapping
。可搜索GET
或POST
关键词获得线索。 - URI
为快速定位路由,搜索 URI 格式字符串。若有可用实例,尝试匹配路由行为与对应处理代码。许多应用使用声明式路由,如app.get('/items')
,理解其声明式规则至关重要。一些框架(如 Ruby on Rails)甚至将路由集中管理于特定文件(如config/routes.rb
)。 - 请求头
应用是否检查特定请求头?可搜索常见头如Origin
、Content-Type
,头部解析逻辑可能在控制器代码之外。 - 参数
代码如何从请求中提取参数?除了请求 URI,参数是最常见的外部输入源,可能来源于请求体(如示例中 JSON 内容)、查询字符串或路径中。
接着识别代码如何发送 HTTP 响应。例如,创建了示例请求中的商品并存入数据库后,应用可能发送:
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-cache
{
"id": 1337,
"name": "Apple",
"price": 1
}
应用代码需处理状态码、响应头及 JSON 响应体,有时还会渲染部分数据为 HTML 页面。依然聚焦 HTTP 响应的基本构件,并映射至处理它们的代码。
通过上述方法,你可以直观推断任意框架的代码模式,足以基于可达路由绘制网页攻击面。同时,若有框架文档可读,能大幅节省时间与精力。
非传统的 Web 攻击面
软件应用的 Web 攻击面不仅限于 HTTP 端点。它可能使用诸如 Web 分布式创作与版本控制(WebDAV)、RSS(Really Simple Syndication)等基于 HTTP 或扩展 HTTP 的协议,也可能涉及 WebSocket、Web 实时通信(WebRTC)等其他网络协议。这意味着你需要跳出传统 Web 攻击向量的思维框架。
此外,Web 攻击面并不意味着你只寻找 SQL 注入等典型 Web 漏洞。例如,在 2019 年的 Pwn2Own 东京赛上,安全研究者 “d4rkn3ss” 利用了 NETGEAR Nighthawk R6700v3 路由器 httpd Web 服务中的经典堆溢出漏洞(www.zerodayinitiative.com/blog/2020/6…)。
由于智能设备计算和存储资源有限,这类设备上运行成熟的 Web 框架相对罕见。相反,更常见的是将 Web 服务器和应用逻辑编译为二进制文件。这增加了在 Web 组件中发现经典非 Web 漏洞(如内存破坏)的可能性。同时,这也意味着分析固件的 Web 攻击面通常需采用二进制分析和逆向工程技术,而非源代码审查。
最后,务必关注软件暴露 Web 攻击面的其他、更隐蔽的方式。尽管像文件压缩工具这类桌面实用程序看似不直接与网络交互,但其自动更新或许可证校验等功能可能涉及 Web。
某些应用可能会启动临时 Web 服务器用于进程间通信。软件有时要求用户通过浏览器完成 OAuth 流程登录。例如,清单 2-6 展示了 GitHub 命令行接口(CLI)如何利用 github.com/cli/oauth
包触发 Web 应用的 OAuth 登录流程,该流程在启动浏览器访问初始 OAuth URL 之前,会启动一个本地 HTTP 服务器。
// 2020 GitHub, Inc.
func (oa *Flow) WebAppFlow() (*api.AccessToken, error) {
--snip--
params := webapp.BrowserParams{
ClientID: oa.ClientID,
➊ RedirectURI: oa.CallbackURI,
Scopes: oa.Scopes,
AllowSignup: true,
}
browserURL, err := flow.BrowserURL(host.AuthorizeURL, params)
// 启动本地 HTTP 服务器
go func() {
_ = flow.StartServer(oa.WriteSuccessHTML)
}()
--snip--
// 启动浏览器
err = browseURL(browserURL)
if err != nil {
return nil, fmt.Errorf("error opening the web browser: %w", err)
}
--snip--
// 等待 OAuth 回调,启动 HTTP 客户端
➋ return flow.Wait(
context.TODO(),
httpClient,
host.TokenURL,
webapp.WaitOptions{
ClientSecret: oa.ClientSecret,
}
)
}
清单 2-6:GitHub oauth 包中的 WebAppFlow 函数
用户在浏览器成功认证后,OAuth 流程会将临时授权码和状态重定向至本地 HTTP 服务器上的回调 URL ➊。随后程序通过 HTTP 客户端 ➋ 向 GitHub OAuth 服务的令牌端点发起 POST 请求,使用授权码换取访问令牌。
Web 攻击面涵盖了从客户端到服务器端的多样功能。随着 Web 功能不断渗透进各种软件,潜在的安全隐患也大幅增加,给漏洞发现和利用提供了诸多机会。
网络协议
软件除了 HTTP,还可能使用多种其他网络协议进行通信。与 HTTP 类似,这些协议通常可以通过常见的网络相关 API、库以及其数据结构和流程来识别。
TCP/IP 模型将系统间通信协议分为四层:
- 应用层:处理应用间通信(如 HTTP、DNS、FTP)
- 传输层:处理主机间通信(TCP 和 UDP)
- 网络层:处理网络间通信(IP 和 ICMP)
- 链路层:处理物理设备间通信(MAC)
TCP/IP 模型根据抽象层次排序,应用层涵盖大量定制和标准协议。各层依赖下层功能。大多数软件将低层数据处理委托给操作系统 API 或标准库,在这些层发现漏洞影响巨大。
你遇到的大多数代码多涉及应用层,如第一章提及的 SONiC 中的 dhcp6relay 服务器。SONiC 运行于交换机等网络设备,是映射软件网络协议攻击面的良好参考。
若查看较新版 SONiC 代码(github.com/sonic-net/s…),可见多个目录处理知名网络协议,包括 ntp、openssh、snmpd 等。多数基于开源代码实现,考虑到正确安全实现网络协议难度大,常建议复用现有库。例如实现链路层发现协议(LLDP)的 lldpd,只含补丁目录和 Makefile,负责下载 Debian lldpd 包源码、应用补丁并构建。
识别网络协议攻击向量
从最基本的 API 调用入手:创建网络套接字、监听并接收数据。例如,清单 2-7 展示了 src/iccpd/src/scheduler.c 中的 Inter-Chassis Communication Protocol(ICCP)服务器初始化代码:
/* 服务器套接字初始化 */
void scheduler_server_sock_init()
{
int optval = 1;
struct System* sys = NULL;
struct sockaddr_in src_addr;
if ((sys = system_get_instance()) == NULL)
return;
sys->server_fd = socket(PF_INET, SOCK_STREAM, 0); ➊
bzero(&(src_addr), sizeof(src_addr));
src_addr.sin_family = PF_INET;
src_addr.sin_port = htons(ICCP_TCP_PORT); ➋
src_addr.sin_addr.s_addr = INADDR_ANY; ➌
--snip--
if (bind(sys->server_fd, (struct sockaddr*)&(src_addr), sizeof(src_addr)) < 0)
{
ICCPD_LOG_INFO(__FUNCTION__, "Bind socket failed. Error");
return;
}
}
清单 2-7:iccpd 服务器初始化代码
攻击面分析示例
无需完全理解 iccpd 功能或其协议,也能学到许多协议攻击面信息。首先注意 socket
调用中的 PF_INET
和 SOCK_STREAM
参数➊。第一个参数定义通信域(协议族),PF_INET
与 AF_INET
同义,依据 C 标准库文档指 IPv4 协议,说明这确实是网络协议。第二参数 SOCK_STREAM
定义套接字类型,此处为 TCP。
下一行显示套接字绑定端口 ICCP_TCP_PORT
➋,定义于 src/iccpd/include/iccp_csm.h
,端口号为 8888。同时绑定地址为 INADDR_ANY
➌,即运行程序系统的所有网络接口或 IP。仅凭几行代码,你便识别了该网络协议的类型、端口和暴露接口。
RFC 文档
多数标准协议都有由互联网工程任务组(IETF)发布的请求注释(RFC)文档,描述协议设计与实现。研究目标软件所用协议时,应优先查阅 RFC,因其包含重要的协议实现指导(开发者实现协议或格式时同样参考 RFC)。
识别目标实现中的漏洞或简化实现有助于发现漏洞。
自定义协议实现优先级
开发者通常仅在协议足够专有或小众,需要定制实现时才自行编码。应优先审查这些自定义实现,因其较开源库更可能存在测试或审核不足问题。
协议代码审查重点
审查协议代码时,重点关注两大特征:数据结构和处理流程。
数据结构
数据结构定义网络协议中的数据格式及解析方式。
以 sonic-snmpagent
中实现的 Agent 扩展协议(AgentX)为例。AgentX 的 RFC 2741(www.ietf.org/rfc/rfc2741…)详细定义了协议数据单元(PDU)头部及不同 PDU 格式。根据 RFC,AgentX PDU 头部是“固定格式的 20 字节结构”,其中前 4 字节为 h.version
、h.type
、h.flags
及保留字段。对应代码 sonic-snmpagent/src/ax_interface/pdu.py
中的 PDUHeaderTags
类及其 from_bytes
方法(见清单 2-8):
class PDUHeaderTags(namedtuple('_PDUHeaderTags', ('version', 'type_', 'flags', 'reserved'))):
--snip--
@classmethod
def from_bytes(cls, byte_string):
return cls(
*struct.unpack('!BBBB', byte_string[:4])
)
清单 2-8:sonic-snmpagent 的 PDU 头部解析代码
该方法利用 Python 标准库 struct
,按 !BBBB
格式解析字节,表示以网络字节序(大端)解释为 4 个 1 字节无符号字符,返回对应实例。此格式与 RFC 定义匹配。
数据结构差异导致漏洞
代码使用的数据结构与协议预期结构不符时,可能引发漏洞。
如第一章中 dhcp6relay
漏洞即因将 option->option_length
解析为无符号 16 位整数(最大值 65535),用作拷贝至固定 4096 字节缓冲区的字节数,超出协议预期。
参见 RFC 8415(www.ietf.org/rfc/rfc8415…),定义 IPv6 DHCP 协议,明确指出“DHCP 选项格式”中选项长度字段为 2 字节无符号整数,变量长度选项数据字段的长度由 option-len
指定。dhcp6relay
虽正确解析长度字段,但未充分处理可变长度数据,导致安全隐患。
协议文档关键字
注意协议文档中诸如 “MUST”、“MUST NOT” 等关键字,标识重要实现要求。不遵守可能引发严重漏洞。
例如 RFC 2741 关于八位字节串的描述:
八位字节串由连续字节组成,起始为 4 字节整数(依据头部的 NETWORK_BYTE_ORDER 位编码),数值为字节串长度,后跟实际字节。称为 Octet String。若最后字节未对齐至 4 字节偏移,需填充零字节保证后续数据对齐。即便八位字节串为 PDU 最末项,亦须填充。
若开发者未正确添加填充检查,而是按 4 字节递增读取 PDU,可能造成越界读取漏洞,导致读取攻击者发送的 PDU 之外数据。许多隐含假设即使未在 RFC 明确指出,也可能导致安全问题。
流程(Procedures)
网络协议的流程定义了通信的规则和约定,包括客户端或服务器预期的消息顺序和所采取的操作。与数据结构类似,流程中的差异或缺陷也可能导致漏洞。
数据结构的问题通常引发内存破坏,而流程的问题则多影响更高层的业务逻辑,如认证和授权。
正确识别网络协议中的安全边界,是判断业务逻辑漏洞的关键。多数 RFC 中会有相关讨论。例如,RFC 2741 的“安全考虑”部分指出,AgentX 协议中未定义访问控制机制,建议 AgentX 子代理始终与主代理运行在同一主机上;若通过网络传输,协议本身无内置安全机制防止恶意子代理进行未授权更改。因此,授权缺失属于设计决定,而非(特定实现的)漏洞。
一些典型流程示例包括:
- 握手:最初交换消息以建立通信
- 会话管理:跟踪两端的独立会话
- 状态管理:控制单个会话的状态
- 流控:管理数据传输的速率和顺序
- 错误处理:从无效数据中恢复或终止
- 加密:保证通信的隐私性、真实性和完整性
- 会话终止:有序关闭和清理会话
以上部分内容涵盖在 RFC 2741 第7节“流程元素”中。举例,7.2.2节“子代理处理”说明:
子代理初步处理收到的 AgentX PDU,如为 agentx-Response-PDU:
- 若解析或解释出错,则静默丢弃
- 否则根据 h.packetID 与原始请求匹配,并以实现特定方式处理
该节说明子代理应根据接收的 PDU 类型切换状态,包括对无效 PDU 的处理。
流程代码示例:sonic-snmpagent
将上述理论映射到 sonic-snmpagent
代码(清单 2-9):
import asyncio
from . import logger, constants, exceptions
from .encodings import ObjectIdentifier
from .pdu import PDUHeader, PDUStream
from .pdu_implementations import RegisterPDU, ResponsePDU, OpenPDU
class AgentX(asyncio.Protocol):
--snip--
def data_received(self, data):
self.counter += 1
if not (self.counter % constants.REPORTING_FREQUENCY):
# Stayin' alive...Stayin' alive...
# Ahh, ahh, ahh, ahh
logger.debug("Parsed {} PDUs...".format(self.counter))
try:
# 每种 PDU 类型有各自子类,构造时推断
pdu_stream = PDUStream(data)
for pdu in pdu_stream:
if isinstance(pdu, ResponsePDU): ➊
# 解析响应
self.parse_response(pdu)
else:
# 若当前 PDU 需响应,则返回响应 PDU
response_pdu = pdu.make_response(self.mib_table)
self.transport.write(response_pdu.encode())
except exceptions.PDUUnpackError: ➋
logger.exception('decode_error[{}]'.format(data))
except exceptions.PDUPackError:
logger.exception('encode_error[{}]'.format(data))
except Exception:
logger.exception("Uncaught AgentX proto error! [{}]".format(data))
清单 2-9:sonic-snmpagent 的 PDU 流程代码
代码正确判断 PDU 是否为 agentx-Response-PDU
➊,并相应处理。解析该类型 PDU 出错时静默丢弃,捕获异常并记录日志➋。
不过,对于其他 PDU 类型,代码似乎未检查 h.sessionID
是否对应已建立会话,若无则应设置 res.error
为 notOpen
。
练习
跟踪代码(github.com/sonic-net/s…)确认 sonic-snmpagent
是否确实执行了该检查。提示:sonic-snmpagent
是否维护了当前已建立会话列表?
本节同 HTTP 章节类似,先给出攻击面宏观模型(框架、协议),再拆解关键组成(控制器、数据结构、流程),识别实现潜在漏洞点。此方法有效覆盖源码中最大数量的潜在薄弱环节。
本地攻击面
虽然 TCP、UDP 和 SCTP 等网络协议处理网络中主机间通信,进程间通信(IPC)则通常发生在同一主机上的不同进程或线程之间。需要注意,进程是程序的一个运行实例,因此 IPC 可以用来让多个运行相同代码的进程实例相互通信,这构成本地攻击面。
一些协议(如 AgentX)既可以通过网络通信,也可以通过 IPC 运行。RFC 2741 建议 AgentX 子代理和主代理在同一主机上通信时,采用共享内存、命名管道、套接字等本地机制,这为同一协议打开了新的攻击面。
从攻击者角度看,网络传输协议暴露远程攻击向量,而本地传输协议暴露(惊喜!)本地攻击向量。不过网络和本地传输协议有时会重叠,比如 Windows 上可通过网络访问命名管道。通常,IPC 是本地权限提升利用的关键安全边界。RFC 2741 也指出:
当使用本地传输机制且子代理和主代理在同一主机上运行时,连接授权可委托给操作系统功能。换言之,“只有子代理具备足够权限时,操作系统才允许连接”。
此外,利用本地传输机制时可进行网络上难以或无法实现的攻击,如竞态条件和时间攻击。有效利用这些机制需熟悉操作系统的具体实现和保护措施。
进程间通信中的文件
开发者可用文件暴露多种输入/输出资源,作为统一通信渠道。例如,读命名管道就像读普通文件,尽管功能不同。本节讨论用于 IPC 的常规文件。
尽管文件能用于两个进程间交换数据,但磁盘 I/O 开销导致性能远逊于内存中 IPC 方法,如命名管道。
因此,文件用于需要持久化或对速度要求较低的 IPC。一种特殊用途是锁文件,用于标识某资源已被运行进程占用,防止多实例同时修改同一文件。文件操作非原子性使得此类保护尤为重要。
例如,文本编辑器中如果同时在不同实例编辑同一文件,未锁定可能导致前者工作被后者一次误保存完全覆盖。
Vim 编辑器(macOS 和 Ubuntu 预装,通常为简化版 vi)即内置此保护。打开两个终端分别编辑同一文件 vi test
,会看到提示:
E325: ATTENTION
Found a swap file by the name ".test.swp"
owned by: raccoon dated: Mon Mar 13 ...
file name: /test
modified: no
user name: raccoon host name: raccoon.local
process ID: 5968 (STILL RUNNING)
While opening file "test"
CANNOT BE FOUND
(1) Another program may be editing the same file...
(2) An edit session for this file crashed...
该信息称之为交换文件(swap file)而非锁文件,因为交换文件保存临时数据(草稿)。但 Vim 同时利用该交换文件作为锁,提醒用户避免开启多个编辑会话。
利用 Apport 中的硬编码路径
当开发者未正确验证关键文件路径(如锁文件)时,攻击者可利用该弱点控制文件读写。
Ubuntu 中的 Apport 程序(用户空间崩溃检测和日志工具)中 check_lock
函数存在一个特权提升漏洞(CVE-2020-8831)。代码见清单 2-10:
def check_lock():
'''如果已有另一个 Apport 实例运行,则中止避免系统崩溃'''
# 创建锁文件目录
try:
os.mkdir("/var/lock/apport", mode=0o744) ➊
except FileExistsError:
pass
# 创建锁文件
try:
fd = os.open("/var/lock/apport/lock", os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW) ➋
except OSError as e:
error_log('cannot create lock file (uid %i): %s' % (os.getuid(), str(e)))
sys.exit(1)
def error_running(*args):
error_log('another apport instance is already running, aborting')
sys.exit(1)
original_handler = signal.signal(signal.SIGALRM, error_running)
signal.alarm(30) # 超时 30 秒
try:
fcntl.lockf(fd, fcntl.LOCK_EX) ➌
except IOError:
error_running()
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, original_handler)
清单 2-10:Apport 中的 check_lock 函数
该函数执行时,会创建锁文件目录➊(如不存在),并尝试对锁文件加锁➌。fcntl.lockf
是 POSIX 标准调用,操作系统维护锁列表,防止重复创建锁。
符号链接攻击
程序使用硬编码路径(如 /var/lock/apport/lock
)风险较大,攻击者可提前劫持对应文件。攻击方式是符号链接(symlink)攻击,符号链接文件指向其他文件或目录,操作系统会自动解析。
例如,若符号链接 a
指向文件 b
,执行 cat a
即输出文件 b
内容,无需额外程序处理。这种透明机制令硬编码路径程序易遭攻击者用符号链接重定向至恶意或敏感路径。
这是典型“混淆代理问题”,攻击诱骗高权限程序执行未授权操作。许多本地权限提升漏洞均基于此类问题。
Linux 的 open
系统调用支持多种文件创建标志,其中 O_NOFOLLOW
会阻止打开符号链接(当路径最后一个组件是符号链接时,open
调用失败并返回错误 ELOOP
)。Apport 代码中启用了该标志➋,但依然存在漏洞,原因是 O_NOFOLLOW
只阻止路径最后一个组件是符号链接,路径中其他部分仍可被解析。
在 Ubuntu 中,/var/lock
是指向 /run/lock
的符号链接,后者对所有用户可读写。攻击者可在 /var/lock/apport
创建符号链接指向任意目录,Apport 运行时会在该目录创建锁文件。锁文件默认权限为 0o777
,任何用户均可读写。
总结来说,攻击者可借此诱骗 Apport 在攻击者控制目录创建全局可写文件,进一步导致本地权限提升,如覆盖 cron
或启动脚本目录。
演练
你可以尝试在 Ubuntu 中安装受影响版本 Apport,详情见 CVE-2020-8831 的安全更新页面(ubuntu.com/security/CV…),选择补丁前版本安装。
竞态条件攻击示例:Paramiko
由于文件 IPC 非原子且基于慢速磁盘 I/O,容易遭遇竞态条件漏洞。
例如,Python SSH 库 Paramiko 存在 CVE-2022-24302 竞态条件漏洞。程序用 Paramiko 生成并保存 RSA 私钥(见清单 2-11):
import paramiko
# 生成 RSA 私钥
pkey = paramiko.rsakey.RSAKey.generate(1024)
# 写私钥到文件
pkey.write_private_key_file('/tmp/testkey.pem')
清单 2-11:Paramiko 生成并保存 RSA 私钥示例
但 Paramiko 内部 _write_private_key_file
函数存在竞态条件,代码片段(清单 2-12):
def _write_private_key_file(self, filename, key, format, password=None):
➊ with open(filename, "w") as f:
# 竞态条件发生处
➋ os.chmod(filename, o600)
self._write_private_key(f, key, format, password=password)
函数先用默认全局可读权限创建文件➊,随后更改为严格权限➋。两调用间隙,攻击者可打开文件并读取,即使后续更改权限及写入数据。因为文件权限只在打开时检查,权限变更不影响已打开文件描述符,直到下一次打开才生效。
利用竞态条件的示例脚本(清单 2-13):
while True:
try:
f = open('/tmp/testkey.pem', 'r')
input('file descriptor opened! press ENTER to read file')
print(f.read())
break
except:
continue
安装受影响 Paramiko 版本:
sudo pip install paramiko==2.10.0
以 root 用户运行生成私钥脚本:
sudo python gen_save_key.py
非特权用户尝试读私钥文件,应失败:
cat /tmp/testkey.pem
# Permission denied
随后以非特权用户运行竞态脚本,同时以 root 删除文件并重新生成:
sudo rm /tmp/testkey.pem
sudo python gen_save_key.py
竞态脚本可能成功读取私钥内容。
文件 IPC 若被攻击者劫持通信通道(如写入已知文件路径),可引发多种漏洞。特殊文件属性(符号链接、非原子操作)使得漏洞利用更为多样化,务必关注类似 CVE-2020-8831 和 CVE-2022-24302 的典型案例。
套接字(Sockets)
套接字是允许进程间通信的端点。我们在第1章中看到了一个远程套接字的例子——一个简单易受攻击的TCP服务器。套接字是最常见的IPC通道之一,因此蕴含丰富的潜在攻击向量。
Unix操作系统还支持Unix域套接字(UDS),这是一种本地套接字变体,支持流式、数据报和有序数据包模式,分别对应TCP、UDP和SCTP。然而,UDS不需经过网络协议层的额外开销,因此速度更快。遵循Unix的“万物皆文件”哲学,套接字在操作系统中可以表现为文件,而网络套接字则需通过IP地址和端口号寻址。但将UDS绑定到文件系统路径时,会暴露给文件IPC中的命名空间劫持等问题。此外,UDS依赖文件系统的访问控制,这可能导致不当的文件权限风险。
CVE-2022-21950是发生在日本Kana-Kanji服务器Canna中的漏洞,原因是Canna硬编码使用了目录/tmp/.iroha_unix
存放其UDS。根据该漏洞报告(bugzilla.suse.com/show_bug.cg…),openSUSE通过修改Canna的systemd服务配置,在启动前后删除该目录:
ExecPre=/bin/rm -rf /tmp/.iroha_unix
ExecStart=/usr/sbin/cannaserver -s -u wnn -r /var/lib/canna
ExecStopPost=/bin/rm -rf /tmp/.iroha_unix
不幸的是,这留下了时间窗口,允许其他用户创建/tmp/.iroha_unix
目录并赋予全局写权限。此前该目录由root在启动时创建,低权限用户无法覆盖。若Canna在该目录创建套接字,攻击者可替换为自己控制的套接字,实现中间人攻击,截获系统中的日语输入。
Unix手册页(unix(7))中描述了UDS可防止此类攻击的机制:
“UNIX域套接字支持使用辅助数据传递文件描述符或进程凭据(struct ucred格式)给其他进程。”
struct ucred
结构定义如下:
struct ucred {
pid_t pid; /* 发送进程ID */
uid_t uid; /* 发送进程用户ID */
gid_t gid; /* 发送进程组ID */
};
例如,监听套接字的特权程序可以验证收到消息的发送者属于特权用户组,从而增加访问控制层级。该机制在内核层实现,难以伪造。
Windows于2017年支持UDS(devblogs.microsoft.com/commandline…)。随着操作系统不断引入和更新IPC机制,软件的潜在攻击面也随之扩大。
命名管道(Named Pipes)
命名管道也是进程间通信的文件类机制。但Windows的命名管道拥有独立于文件系统的访问控制模型,增加了授权问题复杂度。
Windows命名管道文件系统
Unix命名管道一次只允许一个读进程和一个写进程访问,而Windows允许同一命名管道的服务器与多个客户端通信。Windows命名管道具有特殊的命名空间,不同进程可同时创建同一命名管道的多个服务器实例。
CreateNamedPipe
函数用于创建命名管道实例:
HANDLE CreateNamedPipeA(
[in] LPCSTR lpName,
[in] DWORD dwOpenMode,
[in] DWORD dwPipeMode,
[in] DWORD nMaxInstances,
[in] DWORD nOutBufferSize,
[in] DWORD nInBufferSize,
[in] DWORD nDefaultTimeOut,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
参数nMaxInstances
指定允许的最大实例数,范围1到PIPE_UNLIMITED_INSTANCES
(255)。多实例支持多线程命名管道服务器或重叠I/O操作,以同时服务多个客户端,但也允许进程劫持命名管道。
举例,一个高权限程序创建命名管道服务器和客户端用于IPC。若低权限攻击者先创建了该命名管道服务器,则可能拦截客户端消息。更糟的是,若客户端基于服务器回复执行命令,可能导致权限提升。
创建顺序重要,因为客户端以先入先出(FIFO)顺序连接服务器实例。此外,dwOpenMode
不能包含FILE_FLAG_FIRST_PIPE_INSTANCE
标志(0x00080000),该标志阻止创建额外实例。CVE-2022-21893就是利用Windows远程桌面服务(RDS)中命名管道IPC的权限提升漏洞,攻击者截获了RDS消息。
命名管道安全配置错误
Windows命名管道依赖开发者通过lpSecurityAttributes
设置访问控制列表(ACL),而非依赖文件系统,若配置错误会导致权限泄露,进而权限提升。
默认lpSecurityAttributes
允许Everyone组和匿名账户读访问。若开发者通过命名管道发送敏感数据,低权限攻击者可读取。此外,配置不当的ACL可能让攻击者连接高权限命名管道服务器并发送任意消息。如果服务器基于消息执行特权操作,将突破安全边界。
CVE-2022-24286描述了Acer QuickAccess软件中的本地权限提升漏洞:服务以系统权限运行,通过命名管道与用户进程通信。命名管道对普通用户开放读写权限,且服务未验证用户身份,导致攻击者发送恶意命令实现权限提升。
清单 2-14 展示了如何用 C# 创建一个全局可读写的命名管道:
using System;
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;
public class Program
{
static void Main(string[] args)
{
// 创建全局读写ACL
SecurityIdentifier securityIdentifier = new SecurityIdentifier(
WellKnownSidType.WorldSid,
null
);
PipeAccessRule pipeAccessRule = new PipeAccessRule(
securityIdentifier,
PipeAccessRights.ReadWrite,
AccessControlType.Allow
);
PipeSecurity pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(pipeAccessRule);
// 创建命名管道服务器并应用ACL
NamedPipeServerStream pipeServer = NamedPipeServerStreamAcl.Create(
"worldRWPipe",
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
0,
0,
pipeSecurity
);
pipeServer.WaitForConnection();
// 处理不可信输入的危险操作...
}
}
清单 2-14:全局读写命名管道示例
Unix系统中的命名管道
Unix通过mkfifo
系统调用创建命名管道,第一个参数是路径名,第二个参数是文件权限模式。像其他文件创建API一样,权限模式会被umask掩码修改,最终访问权限由文件系统管理。
其他IPC方法
操作系统和第三方软件不断新增IPC机制,常见非详尽列表:
- 共享内存
- 系统信号
- 消息队列
- 内存映射文件
- 远程过程调用(RPC)
- 组件对象模型(COM,Windows专有)
- 动态数据交换(DDE,Windows专有)
- 剪贴板
- D-Bus(Linux专有)
- MailSlot(Windows专有)
开发者常以创新(但可能不安全)方式使用这些API。例如,我分析过一个应用利用Windows的SendMessage
函数(通常用于桌面窗口间单向消息传递)传递复杂序列化数据结构。该应用用FindWindow
函数决定目标窗口,lpClassName
参数设为NULL
,导致FindWindow
返回第一个匹配窗口标题的窗口。此行为比命名管道更不安全,因为返回窗口未按FIFO顺序,攻击者可轻易对该信道进行中间人攻击。
对不寻常IPC实现保持警觉。不同IPC机制虽用途相同(进程间消息传递),但代码中往往呈现类似客户端/服务器监听模式,有助于识别。作为攻击面映射工作的一部分,尝试列举目标使用的所有IPC方法。
文件格式
几乎所有软件都需要处理文件。从以换行符分隔的配置文件到视频片段,我们使用各种格式编码数据。和网络协议类似,文件格式要求软件以标准化的方式解析数据结构。不幸的是,开发者在实现中偶尔会犯错,导致安全漏洞;某些文件格式本身在设计时未充分考虑安全,迫使开发者事后修补缺陷。
许多文件格式在RFC中有文档规范,但专有或较旧格式可能需要更多调查。你会逐渐熟悉一些常见类型和组成部分。例如,文件格式通常大致划分为三部分:
- 文件头(Header)
位于文件开头,通常以一组唯一字节开始,方便软件识别文件格式。包含元数据,如功能标志、版本号及解析所需的其他信息。 - 文件主体(Body)
包含与格式相关的主要数据,通常被分成多个数据块,方便软件解析。 - 文件尾(Footer)
包含附加元数据,比如校验和,用以确保数据完整性。
格式差异巨大。例如,XML格式基于标记,使用一套符号指示文件中各部分如何处理。标记格式多用于文本文件,比如本书的LaTeX源码。XML中最重要的符号是<>
,用于定义标签:
<?xml version="1.0"?>
<greeting>Hello, world!</greeting>
该XML示例中无文件尾。
其他格式则更为多样,如基于目录的格式,将数据组织为目录结构中的多个文件。例如,微软Office文档(如.docx和.pptx)本质上是ZIP压缩文件,包含XML文档、图片等资源和元数据文件。你可以用7-Zip等归档工具打开.docx文件,因为其原始字节以ZIP格式排列。Microsoft Word通过文件扩展名区分DOCX和ZIP。但你不能随意创建ZIP文件,改名为.docx就能打开,DOCX格式对ZIP归档内文件的存在、组织和内容有额外要求。
鉴于文件格式多样,我将重点介绍一些常见且值得重点关注的模式。
类型-长度-值(Type–Length–Value,TLV)
TLV模式在协议和文件格式中都很常见。我们用TLV表示主体中的分块数据,因为它方便解析器识别并消费可变长度的数据块。结构包含三部分:
- Type(类型) :数据字段的种类
- Length(长度) :数据字段大小
- Value(值) :数据本身
流行的PNG格式即使用TLV模式。PNG文件主体由一系列数据块组成,每个数据块包含4部分:长度(4字节)、块类型(4字节)、块数据(长度字节)、循环冗余校验(CRC,4字节)。例如,表2-1展示了PNG的IHDR头部块的解析情况。
部分 | 十六进制字节 | 值 |
---|---|---|
Length | 00 00 00 0d | 13 |
Type | 49 48 44 52 | IHDR |
Data | 00 00 00 01 | 宽度:1 |
Data | 00 00 00 01 | 高度:1 |
Data | 08 | 位深度:8 |
Data | 00 | 颜色类型:0 |
Data | 00 | 压缩方法:0 |
Data | 00 | 过滤方法:0 |
Data | 00 | 隔行扫描:0 |
CRC | 3a 7e 9b 55 | CRC-32校验值 |
在实现TLV时,开发者有时会忘记校验数据块的类型和长度是否匹配。比如,PNG定义IHDR块应含13字节元数据,但粗心开发者可能直接信任长度字段,试图从攻击者控制的长度(最大可达2,147,483,647)复制数据,导致缓冲区溢出。
我曾在Apache OpenOffice中观察到类似漏洞(CVE-2021-33035),其支持dBase数据库文件(DBF格式)。DBF文件头包含字段描述符数组,每个描述符定义字段类型(1字节)和大小(1字节)。OpenOffice代码信任这两个值,对于字段类型为整型(I)时,分配4字节缓冲区(对应Int32类型),但却复制攻击者控制的大小字节到缓冲区:
// nType来源字段描述符类型值
else if ( DataType::INTEGER == nType )
{
// sal_Int32类型大小4字节
sal_Int32 nValue = 0;
// nLen来源字段描述符大小值
memcpy(&nValue, pData, nLen);
*(_rRow->get())[i] = nValue;
}
由于大小字段只有1字节,最大值255,导致溢出251字节,覆盖了栈上的返回指针,进而实现代码执行漏洞(spaceraccoon.dev/all-your-d-…)。
许多文件格式和网络协议都用TLV结构,务必检测因类型与长度不匹配引发的漏洞。
基于目录的格式(Directory-Based)
部分文件格式基于目录结构,文件实际上是多个文件的容器。通常基于目录的格式包含清单(manifest),存储关于内部文件及其位置的元数据。这类格式常暴露两类漏洞:文件遍历和子格式漏洞。
- 文件遍历:如果软件不安全地解析目录数据,攻击者可利用路径穿越。例如ZIP格式构成许多目录型格式基础,结构大致如下:
[local file header 1]
[file data 1]
[data descriptor 1]
...
[local file header n]
[file data n]
[data descriptor n]
[archive decryption header]
[archive extra data record]
[central directory]
[zip64 end of central directory record]
[zip64 end of central directory locator]
[end of central directory record]
文件头中含有文件名字段,文件名可以含有相对路径,比如nested/file
会被解压至./nested/file
。但若文件名含有路径穿越(如../../../../tmp/file
),且解析器未作限制,可能将文件解压到敏感目录,如cron任务目录或应用工作目录。
- 子格式漏洞:目录格式通常包含XML清单文件,描述如何解析其它文件。若解析器不安全,可能遭受XML外部实体(XXE)注入攻击。XXE允许攻击者利用外部实体强制泄露本地文件数据。
CVE-2022-0219即JADX反编译工具中的XXE漏洞。Android APK包必须含AndroidManifest.xml清单文件,攻击者在该文件中植入XXE负载,导致JADX在导出反编译结果时泄露本地文件。修补方法是使用不解析外部实体的安全XML解析器。
有时这两类漏洞会同时出现。我遇到一个自定义的ZIP基础包格式,含XML清单。通过串联ZIP路径穿越和XXE漏洞,我成功枚举文件系统并上传了Web Shell,实现远程代码执行(spaceraccoon.dev/a-tale-of-t…)。
自定义字段
文件格式通常包含保留字节或可扩展字段,允许开发者添加自定义功能。自定义功能往往文档不全,添加了未知特性,因此可能特别危险。
例如,几乎所有日历软件(从 Outlook 到 Apple Calendar)都使用的 iCalendar(ICS)格式,通过以 X-
为前缀的非标准属性,提供了“做非标准事情的标准机制”。这导致了许多超出默认ICS属性(如事件位置、时间、名称)之外的有趣行为。比如,早期的 Microsoft Office 支持 X-MS-OLK-COLLABORATEDOC
属性,该属性会在事件开始时自动打开一个会议协作文档。由于事件可通过邀请远程创建,这可能导致危险情况,比如强制用户打开网络共享上的恶意文件。
有时,开发者会通过不同于标准定义的方式解析数据,临时拼凑自定义字段。例如,HTML格式定义了 <link>
元素,用于指定当前HTML文档相关的外部资源。关系类型由 rel
属性标识。比如,指示样式表位置为 main.css
的HTML文档可能包含:
<link href="main.css" rel="stylesheet">
HTML标准定义了一系列支持的 rel
标记并规范了行为。然而,WeasyPrint(一个HTML转PDF引擎)扩展了 <link>
的功能,支持一个标准中没有的自定义 attachment
值。利用此值,开发者可以将本地文件作为附件包含在输出的PDF中:
<link href="file:///etc/passwd" rel="attachment">
这本身是一个功能,而非漏洞,但若开发者在使用WeasyPrint时未考虑这一行为,可能会在其软件中引入漏洞。
要识别这类自定义实现,请注意代码中与文件格式规范偏离的地方,不仅仅是实现错误。虽然成熟标准通常通过公开审查过程考虑各种安全问题,但自定义扩展可能缺乏此类审查,容易重复常见错误。
总结
本章我们探讨了多种潜在攻击向量,从网络协议到进程间通信。你学会了如何识别定义和暴露这些攻击向量的源代码,了解了文件格式中的常见模式及相关漏洞。
软件的攻击面会因威胁模型和环境大不相同。Windows本地攻击者可利用如窗口消息的IPC机制,而远程攻击者只能访问暴露的网络协议和启用网络的IPC机制,如命名管道。
攻击向量的可行性很大程度上取决于是否越过了安全边界。在从代码中枚举攻击面时,请利用此区分准确识别漏洞,这将帮助你快速聚焦于可被利用的场景。
运用本章介绍的各种技术,你将更好地评估应用攻击面、构建现实威胁模型,然后再深入代码审查。随着下一章扩展到更大规模的变种分析,缩小搜索空间至可达攻击面将对结果准确性至关重要。