Serverless应用的持久化与状态攻击:在无服务器架构中植入“有状态”的后门
很多安全团队对Serverless的第一印象是"无状态、短生命周期、没法持久化",觉得函数执行完就销毁,攻击者就算打进来也落不了脚。这个认知放在两年前勉强成立,放到今天就非常危险了。Serverless架构虽然把计算资源抽象成了按需调度的函数粒度,但业务数据、配置、事件源、权限关系全都落在云平台上的托管服务里——这些外部依赖天然就是"有状态"的。敏感信息不会因为函数销毁而消失,事件源映射不会因为容器回收而重置,环境变量、层版本、状态机定义、消息队列里积压的任务,每一样都可以成为攻击者植入后门的载体。
这篇文章不写具体的利用工具,也不鼓励谁去踩红线,而是从架构视角拆解"在无服务器架构中植入有状态后门"这一类攻击的底层逻辑:攻击者到底把“状态”藏在哪儿,为什么传统入侵检测体系对这类隐蔽持久化几乎无感,以及作为云上应用的owner,该如何从检测和加固两个方向自保。内容适合云平台运维、DevSecOps、应用安全和做红队评估的同行阅读,看完你会明白一件事:Serverless不是没有状态,而是状态的形态变了,攻击者的着眼点也随之变了。
1. 无状态背后的“有状态”真相:Serverless的持久化困局
1.1 SLS的短暂容器与永久配置之间的矛盾
用一句话简化Serverless的运行时模型:业务代码跑在临时容器里,容器在函数调用前拉起、调用结束后回收,平台根据流量自动扩缩容,开发者不需要关心底层服务器。这就是"无状态"的标准叙事,听起来很美,但程序员真正依赖的东西——数据库连接串、消息队列地址、对象存储桶名、第三方API密钥——全都不是代码运行时的产物,而是预先定义好的"配置态"。
我常用一个类比来解释:函数就像一个不断换岗的临时工,每天在不同的工位上干活,干完就走。但工位上的电脑、门禁卡、桌子抽屉里放着的交接文档,并不会因为临时工离开而消失。这些持续存在的东西,就是Serverless的"状态"。攻击者真正关心的,不是那个每几秒就被回收一次的函数进程,而是这些能跨越函数生命周期长期存在的配置和资源关系。
更麻烦的是,Serverless架构把"运维"从主机层移到了配置层。传统服务器被入侵后,攻击者要塞一个常驻进程、写一个开机启动项,检测系统扫进程、查定时任务就能发现。但Serverless没有"主机"这个概念,函数代码和配置存储在云端的控制平面,攻击者只要拿到足够的云权限,就能通过修改配置来植入逻辑——这种变更在云平台上看起来就是一次普通的API调用,没有进程、没有端口、没有文件落地,传统EDR拿它一点办法都没有。
1.2 状态攻击的威胁建模起点
从攻击者的视角出发,Serverless应用的可持久化路径可以粗分为四类:
| 状态载体 | 具体形态 | 持久化能力 | 隐蔽性 |
|---|---|---|---|
| 函数代码与服务层 | 函数源码、容器镜像、Lambda Layer | 高,随调用持续生效 | 中,代码审计可发现 |
| 配置与变量 | 环境变量、函数超时/内存、并发预留 | 高,长期生效且隐蔽 | 高,极少被监控 |
| 事件驱动链路 | 事件源映射、定时触发器、死信队列、目标配置 | 高,可“休眠”后按条件激活 | 高,逻辑分散难关联 |
| 权限与信任关系 | IAM角色、跨账号信任、资源策略 | 极高,可横向扩展 | 高,权限审计门槛高 |
这四类载体共同构成了"有状态后门"的完整攻击面。接下来的内容会逐个展开,说明攻击者为什么盯上这些位置,以及这些位置为什么能躲过常规检测。
2. 攻击者视角:持久化载体拆解与状态植入路径
2.1 代码与镜像层的隐蔽持久化
最直观的持久化方式就是把恶意逻辑写进函数代码本身。攻击者获得代码仓库或CI/CD流水线的写入权限后,可以在函数逻辑里加一段"条件触发"的分支代码——比如只有请求头的User-Agent匹配某个特征时才执行后门逻辑,其余情况全部正常处理。这样即使安全团队review代码,看到的也只是一段正常的业务逻辑加一个看似无害的if判断,很难引起警觉。
类似的手段还有容器镜像投毒和Layer注入。云函数的依赖层(Layer)本质是一个共享的zip包,平台会把函数代码和依赖层解压拼接后一起部署。攻击者如果能在Layer里替换掉某个被广泛依赖的库文件,就能实现"一次投毒、处处生效"的效果。而且Layer是版本化的,旧版本不会立刻被清理,函数通过版本号引用历史Layer,导致恶意代码可以长期存在而不被发现。
这种方式的持久化能力很强,因为函数代码是每次调用的必加载内容,恶意逻辑能跟着每一次函数执行“复活”。检测难度在于——它不是独立的恶意文件,而是嵌入在正常业务逻辑内部的代码片段。常规的恶意文件扫描对这类"混在正常代码里的分支逻辑"基本无效。
2.2 配置态持久化:环境变量与函数策略的“后门开关”
函数的环境变量是一个非常经典的持久化载体。攻击者拿到配置写入权限后,可以把少量加密数据塞进环境变量里——比如一个经过变形的C2地址,或者一个用于解码恶意payload的密钥。环境变量不会影响函数现有功能,也不产生额外的API调用痕迹,所以极少被安全监控关注。
更微妙的是通过修改函数配置来实现"功能即后门"。举个例子,攻击者可以把函数的超时时间从3秒改到10分钟,然后把某个原本只处理轻量请求的接口改造成一个“长期驻留”的执行通道——只要有人触发这个函数,它就能在10分钟内持续向外部传输数据。再比如调整函数的预留并发容量,让某个恶意函数始终保持“热”状态,从而绕开冷启动检测,提升后门的响应速度。
这类配置态持久化最致命的一点在于:云平台的操作审计日志里,它只是一次UpdateFunctionConfiguration的API调用,和开发者正常的配置调整没有任何区别。没有安全团队会对每个配置变更都人工核对一次“这个超时时间是否合理”。看上去只是一次正常的下午茶时间配置改动,后门却已经植入了。
2.3 事件链路的“沉睡者”后门
事件驱动是Serverless最核心的集成范式,也是状态攻击中最有想象力的领域。攻击者可以在不触碰业务代码的前提下,通过修改事件源映射来植入后门。
举一个具体的例子:很多应用配置了定时触发器来做数据清理或报表生成,攻击者可以新增一个定时规则,让它每5分钟调用一次某个看起来正常的函数——但该函数的环境变量已经被改过了,它真正执行的是转发内部数据。在监控上看,这个函数本来就有定时调用的基线,新增的规则只是增加了调用频率,很难引起告警。
更隐蔽的做法是篡改函数的失败处理策略。云函数的异步调用支持配置Destination(成功/失败目标),正常情况下失败事件会转发到死信队列等待排查。攻击者可以把“失败目标”改成一个攻击者控制的另一个函数,然后故意触发目标函数报错——每次报错都会把事件内容(可能包含敏感数据)自动转发出来。整个过程不需要在原函数里写一行代码,只需要改一条配置。
我用“沉睡者”这个词来形容这类后门:它平时安静地躺在事件链路配置里,不产生任何主动行为,只有满足特定条件(某条消息进入队列、某个函数报错、某个定时器触发)才会被唤醒。这类后门和业务逻辑完全解耦,几乎不可能通过代码审计发现,只能靠配置漂移检测和异常行为分析来捕捉。
3. 为什么“有状态”后门难被检测:监控盲区与认知偏差
3.1 指标、日志、追踪三件套的集体失效
传统应用安全的监控主要依赖三个维度:主机指标、应用日志、链路追踪。在Serverless架构下,这三个维度全部面临失真问题。
主机指标直接失效——没有主机,CPU和内存是平台虚拟化后的指标,和具体业务没有直接关系。应用日志是函数打印到标准输出的文本,函数每秒钟可能被调用数百次,日志量大且碎片化,从中找出一个“某次调用多转发了一份数据”的异常,基本等于大海捞针。链路追踪能看到一次调用经过了哪些服务,但看不到“某个函数的环境变量里藏了一段Base64编码的C2配置”这种静态信息。
更根本的问题在于架构假设:传统架构里,持久化后门往往伴随“异常网络连接”或“未知进程”等运行时特征,而Serverless的调用全是经过平台网关的,攻击者控制函数向外传输数据时,网络的源IP都是云平台的出口地址,和正常业务流量混在一起,没有任何特征可以区分。
3.2 控制平面与数据平面的割裂
几轮实战对抗之后,我最大的感受是:安全团队关注的是“数据平面”,也就是业务流量、函数日志、API调用这些运行时数据;而攻击者利用的是“控制平面”,也就是云平台上的配置管理接口。做安全的人天天看函数执行日志,但很少有人会按月去Diff一遍所有函数的环境变量是否被改动过、事件源映射是否多了一条、IAM角色是不是被附加了新的策略。
这种割裂给了状态攻击天然的藏身空间。数据平面的检测手段再强,也只能看到“函数在做什么”,看不清“函数被配置成什么样”。攻击者在控制平面上的每次操作,在配置审计里都是孤立的事件,但放到一起看,就是一条完整的入侵链:获取凭证 -> 读取配置 -> 修改事件源映射 -> 植入恶意目标配置。没有把控制平面的配置变化当作一等公民来监控,就谈不上检测这类攻击。
3.3 “临时性”心态带来的防御盲区
做了多年安全的人,多少都有点“主机思维”残留:总觉得只要定期杀杀毒、扫扫漏洞、看看进程,系统就是干净的。到了Serverless时代,这种思维不仅无效,还会误导。
有个典型心态是“函数反正会被回收,攻击者没法持久化,所以打进来也没事”。前面分析的那么多持久化载体已经说明,函数被回收不影响配置留在云端,不影响事件映射躺在控制平面,不影响环境变量里的秘密继续存在。攻击者根本不需要在函数里常住,他只需要确保“下一次调用的瞬间,恶意逻辑能执行”就行了——持久化在Serverless里不是“长期运行”,而是“随时能激活”。
防御方必须建立新的心智模型:代码可以无状态,但配置有状态;函数可以被替换,但权限和信任关系是持久的;一次调用可能只存活几百毫秒,但恶意分支可以在这几百毫秒内完成数据外传和凭证窃取。带着这套新认知重新看安全的优先级,才会有意义。
4. 防御者实战:检测思路、加固手段与红队验证
4.1 配置漂移检测与基线的建立
既然攻击者主要动手脚的地方是控制平面,防御的核心也就应该放在控制平面的连续性监控上。我曾经在某个项目的攻防演练中验证过一个方法:把所有函数和相关资源的配置,用基础设施即代码的方式固化下来,每天定时生成一份实际配置的哈希摘要,和期望值比对。
具体做法可以分三步:第一步,梳理云账号下所有函数和关联资源,导出它们的环境变量、标签、超时时间、内存、事件源映射、Destination配置;第二步,把导出的配置转成规范化的JSON或YAML,存到独立的审计存储里,作为基线;第三步,写一个定时任务,每天重新拉取全量配置,和基线比对,任何差异都触发告警。
这个方案的实际效果很好,我在一次模拟演练中提前在测试环境改了某个函数的失败目标地址,第二天配置漂移检测立刻报出了变更,时间和API调用者信息都对应上了。唯一的痛点是初始基线建立阶段会有大量存量配置差异需要逐一确认,但只要度过前两周,后续的每日比对成本非常低。
这里有一个非常实用的技巧:对配置项设置“敏感度分级”。环境变量、IAM角色、事件源映射属于高敏感配置,一有变更就要告警;超时时间、内存大小这类相对低风险,可以在变更后只记录不通知。分级之后运维团队的告警疲劳会明显下降,真正重要的变更才不会被淹没在噪音里。
4.2 异常调用链路的模式识别
配置漂移检测能抓住“改了配置”这个行为,但有些后门不需要改配置——攻击者通过既有合法配置就能实现持久化,这种情况就需要另一道防线:行为基线分析。
以定时触发器为例。正常业务里,某个函数每天凌晨2点被调用一次做数据汇总,持续了几个月都是如此。攻击者拿到权限后直接新建一条每15分钟触发一次的规则,调用的还是同一个函数。配置审计能看到新增了一条规则,但如果没有“这条新规则是否正常”的判断逻辑,告警很容易被忽略。
我在实践中推荐的做法是,给每个函数建立“调用画像”:包括调用频率的峰谷时段、平均每次调用的数据量、涉及的外部API目标,以及高峰期的并发分布。当画像出现偏离——比如某函数突然出现从没见过的调用频率,或者单个函数单次执行时长突然从几百毫秒跳到几分钟——就触发专项排查。
这个方案的检测覆盖率不是100%,但它能抓住状态攻击里最典型的两个特征:节奏异常和资源消耗异常。攻击者植入后门后的一个通病是“太想保持活跃”,频繁触发的定时规则往往就是突破口。
4.3 加固实践:以最小权限和可审计为核心
聊完检测,再说加固。Serverless的加固原则和传统架构有不少相通之处,但侧重点完全不同。下面这几条是我在多个项目里验证过,性价比最高的措施。
第一,环境变量一律引用密钥管理服务的凭据名,而不是直接存明文值。云原生的密钥管理服务(比如各类云厂商的Secret Manager产品)支持版本轮换和细粒度访问控制,即使攻击者拿到了函数代码的读取权限,也看不到真实密钥,只能看到凭据引用名。这个习惯的养成只需要一次改造,收益却非常大。
第二,函数的执行角色遵循最小权限原则,并且定期审计角色的授权策略。我在评估中见过太多“函数角色绑定了管理员权限”的例子,开发者为了省事把权限拉满,一旦函数被攻击者控制,攻击者就能直接通过云API修改其他资源的配置。最小权限不是说不能用特权角色,而是特权角色要有明确的边界:只能访问特定前缀的资源、只能执行特定的API操作、必须有条件上下文限制。
第三,对函数代码和依赖层做内容级校验。在CI/CD流水线里加一道步骤,计算函数代码包的哈希,发布后记录到配置管理系统。每次函数版本更新时比对哈希,任何未经过流水线的代码变更都应该直接引起安全告警。Layer的引用也要固定版本号,禁止使用latest这种可变引用,防止依赖层被悄悄替换。
第四,网络出口收敛。默认情况下,函数是可以访问公网的,攻击者植入后门后,外传数据的路径非常顺畅。通过配置私有网络和NAT网关,把函数的公网访问能力收窄到特定域名或特定端口,能显著拉高攻击者外传数据的成本。这个改动对业务的影响需要评估,但大多数Serverless应用都有办法改造,无非是多一点网络配置的工作量。
4.4 红队验证:从防守方视角自测“持久化盲区”
如果条件允许,我强烈建议安全团队把自己当成攻击者,定期对自有Serverless应用做一次“持久化盲区”测试。测试的核心问题就一句话:如果攻击者拿到了当前云账号的配置写权限,他能在这套架构里植入多隐蔽的持久化后门?
测试流程可以参考我在一次模拟项目X中的做法。首先,准备一个独立的测试环境,复制线上应用的主要函数和事件链路;其次,尝试在代码层、配置层、事件层各植入一种后门形态,验证能否存活超过48小时而不被安全告警发现;最后,针对每种成功植入的后门,复盘检测缺口出在哪里,并补齐对应的监控规则。
你可能已经注意到,红队测试的价值不在于“证明我们被攻破了”,而在于精准暴露检测盲区和加固短板。我那次测试的结果让人印象深刻:代码层后门在第2天被发现(因为CI流水线有哈希校验),配置层后门持续存活了11天,最后是靠人工翻看审计日志才发现异常;事件层后门则直接没有被发现,直到主动清理时才移除。事后复盘时我们新增了三类检测规则,其中就有上文提到的配置漂移比对和事件源映射变更通知。这个成本其实不高,但比听一次安全培训有用得多。
5. 典型问题排查实录:我踩过的坑和解决思路
5.1 环境变量泄漏的排查经验
排查思路:先明确泄漏面。事件起因是某次代码仓库出现一段疑似被外部访问过的日志,里面竟然带着数据库连接串的明文。定位后发现,这个连接串是从函数的环境变量里打印出来的,而且环境变量里除了连接串还有一堆含敏感信息的配置。进一步看,所有团队成员理论上都有读取函数配置的权限,无法锁定单一责任人。
解决方式:不再在环境变量里放任何敏感明文,统一改成引用密钥管理服务的凭据名。同时为该函数配置了独立的只读权限,禁止非必要角色读取配置。另外把环境变量的读取行为加入了审计监控,并约定每季度轮换一次密钥。
一个细节值得提一下:环境变量在函数控制台上默认是明文展示的,截图、录屏、日志残留都可能造成泄漏。即便是“看起来只能查看一次”的密钥管理服务,也有批量读取的风险。因此“能少放就少放”,凡是能从运行时外部获取的配置,一律不让它进入环境变量。
5.2 定时触发器被新增的检测
排查思路:客户反馈某函数的调用账单异常上涨,但业务量没有明显增加。检查后发现该函数多了一条定时触发规则,每5分钟调用一次,而这条规则并没有对应的审批记录。
解决方式:在事件源映射的创建接口上加了审批流,任何人新建定时规则都需要运维负责人审批,审批通过后自动登记到事件源清单。另外配置了定时触发器的变更通知,每新增一条规则,安全团队都会收到一个包含调用者信息和变更说明的推送。
踩坑点:只做了通知没做审批,结果通知被当成噪音忽略,仍然有规则悄悄创建。后来改为“先审批后变更”,问题才真正解决。对于有条件的团队,还可以更进一步——把事件源映射的变更权限收归到一个专门的发布账号,普通开发账号不直接持有API权限。
5.3 死信队列和失败目标被改动的排查
排查思路:某异步处理函数在一段时间内频繁报错,但死信队列里却没有历史消息积压。业务方怀疑消息丢失,安全介入后发现失败目标被改到了一个从未见过的函数,原本应该积压的死信消息都被转发过去了。
解决方式:立即回收错误处理配置的修改权限,恢复原始失败目标。同时把“失败目标变更”列为高敏操作,任何修改必须走变更审批。针对这起事件,还把函数执行错误率在基线范围内的“正常噪音”改成异常告警,避免再次出现“看起来在报错、实际上是后门转移数据”的盲区。
这起事件最值得反思的一点是:错误处理配置平时很少有人关注,攻击者恰恰利用了“无人关注的地方最安全”这个规律。防御清单里一定要把这类低频配置(死信队列、失败目标、重试策略)单列出来,和主配置分开管理。
5.4 快速检测清单速查表
从实战经验里提炼了一张持久化检测清单,分享给同行直接参考。这张清单不需要一次全做到,按重要程度从高到低逐项落地即可。
| 检测项 | 检测方式 | 推荐频率 | 告警级别 |
|---|---|---|---|
| 环境变量变更 | 配置漂移比对 + 密钥服务审计 | 每日 | 高 |
| 事件源映射变更 | 事件源清单比对 + 变更通知 | 实时 | 高 |
| 失败目标/死信队列变更 | 配置文件哈希比对 | 每日 | 高 |
| 函数代码哈希校验 | CI/CD流水线内校验 | 每次发布 | 高 |
| IAM角色策略变更 | 权限百科 + 变更通知 | 实时 | 高 |
| 函数版本和Layer引用变化 | 版本清单比对 | 每日 | 中 |
| 定时触发器新增/修改 | 规则清单比对 + 审批流 | 实时 | 中 |
| 网络出口配置变化 | 网络ACL和NAT变更审计 | 每日 | 中 |
这张速查表是我们团队经过几轮红队对抗后沉淀出来的,里面每一项背后都对应过真实的“险些出事”或“已经出事”的经历。尤其是环境变量和失败目标这两项,个人建议优先做,属于投入小、收益大、立竿见影的品类。
6. 最后再分享一个迭代思路
如果精力允许,可以沿着这个方向把检测能力产品化:把上文的配置漂移、事件链路审计、权限变更监控整合成一套“Serverless持久化隐患巡检”的自动化剧本,每周跑一次,输出一份风险评分报告。评分项可以包括:存在明文敏感配置的函数数量、最近30天事件源映射变更次数、高风险角色的授权策略数量、函数版本的活跃度等。
我在实际项目里跑过类似的定期报告,效果最明显的地方不是“发现问题”,而是让开发团队开始留意自己的配置习惯。报告每一条都给到具体函数名和对应的风险描述,开发不再觉得安全是“找麻烦”,反而会主动在迭代里避免踩坑。作为一个安全从业者,我觉得这事比多发现几个后门更有长期价值。
后门攻防是道高一尺魔高一丈的长期博弈。Serverless的持久化与状态攻击这条路,攻击者才刚走了一段,远没到终点。但好消息是,防守方的工具箱也在变厚:配置漂移检测、行为基线画像、权限治理这些手段只要组合得当,足以把“有状态后门”的藏身空间压到很小。希望这篇偏实战取向的拆解,能帮同行们把防护清单上那几项一直拖着的补丁,往前挪一挪。