HTB Epsilon (Medium) - Writeup
Difficulty: Medium
AWS keys were found exposed in a git repository on the webserver.
Using those keys, the AWS command line was exploited to access cloud functions and retrieve a secret.
That secret was then used to exploit the site for code execution with SSTI and gain an initial shell.
Finally, a backup script was abused to escalate to root and capture the flag.
—
Nmap
The nmap scan revealed three open ports:
Port 80 - Website
As can be seen on nmap scan this website contains .git directory.
It can be dumped to kali using git-dumper tool.
https://github.com/arthaud/git-dumper
Directory contains source code:
Good thing to start with when enumerating .git directory is to look at git commits.
It can be done with git log command:
With git diff we can compare previous commits and look at the changes done.
One that turned out to be intresting was:
git diff c622771686bd74c16ece91193d29f85b5f9ffa91 7cf92a7a09e523c1c667d13847c9ba22464412f3
It exposed AWS secret key:
- aws_access_key_id=’AQLA5M37BDN6FJP76TDC’
- aws_secret_access_key=’OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A’
I will also add cloud.epsilon.htb to /etc/hosts.
Now I’ll analyze source code and after that we can try to use aws key.
I’ll paste the code here:
#!/usr/bin/python3
import jwt
from flask import *
app = Flask(__name__)
secret = '<secret_key>'
def verify_jwt(token,key):
try:
username=jwt.decode(token,key,algorithms=['HS256',])['username']
if username:
return True
else:
return False
except:
return False
@app.route("/", methods=["GET","POST"])
def index():
if request.method=="POST":
if request.form['username']=="admin" and request.form['password']=="admin":
res = make_response()
username=request.form['username']
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
res.headers['location']='/home'
return res,302
else:
return render_template('index.html')
else:
return render_template('index.html')
@app.route("/home")
def home():
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('home.html')
else:
return redirect('/',code=302)
@app.route("/track",methods=["GET","POST"])
def track():
if request.method=="POST":
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('track.html',message=True)
else:
return redirect('/',code=302)
else:
return render_template('track.html')
@app.route('/order',methods=["GET","POST"])
def order():
if verify_jwt(request.cookies.get('auth'),secret):
if request.method=="POST":
costume=request.form["costume"]
message = '''
Your order of "{}" has been placed successfully.
'''.format(costume)
tmpl=render_template_string(message,costume=costume)
return render_template('order.html',message=tmpl)
else:
return render_template('order.html')
else:
return redirect('/',code=302)
app.run(debug='true')
There are a few routes but every one of them calls verify_jwt() function.
This function look like this:
def verify_jwt(token,key):
try:
username=jwt.decode(token,key,algorithms=['HS256',])['username']
if username:
return True
else:
return False
except:
return False
I tried logging in to the website on port 5000 with admin:admin, but it didn’t work.
The code must have changed since then.
Another thing that came from reading the code is the possibility of SSTI vulnerability in /order.
@app.route('/order',methods=["GET","POST"])
def order():
if verify_jwt(request.cookies.get('auth'),secret):
if request.method=="POST":
costume=request.form["costume"]
message = '''
Your order of "{}" has been placed successfully.
'''.format(costume)
tmpl=render_template_string(message,costume=costume)
return render_template('order.html',message=tmpl)
else:
return render_template('order.html')
else:
return redirect('/',code=302)
It takes “costume” parameter as user input and passes it to render_template_string which is a dangerous function.
AWS command line tool - exploitation
I will install awscli to talk with the server, then configure secrets that we previously found in .git directory:
We will start with listing functions:
./aws lambda list-functions --profile exploit --endpoint-url http://cloud.epsilon.htb
There is one lambda function called “costume_shop_v1”.
To get more info about this function we can run:
./aws lambda get-function --function-name costume_shop_v1 --endpoint-url http://cloud.epsilon.htb --profile exploit
We now know the location of the source code.
We can go to this url and download source code:
http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code
Let’s unzip it and view:
It exposed a secret:
secret='RrXCv`mrNe!K!4+5`wYq'
It was mentioed before in website source code.
We can encode this secret as JWT token, and use it to authenticate to the website.
Now we have cookie value but we don’t know the cookie name.
To get cookie name we can go back to the website code:
cat server.py
[...]
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
[...]
Cookie is called auth, now go to website on port 5000.
Press f12 and add a cookie named auth and paste JWT token as value.
Port 5000 - Exploiting SSTI
Now with cookie set we can access /order directory.
We can now try to exploit previously identified SSTI vulnerability.
Server-Side Template Injection (SSTI) is a type of security vulnerability that occurs when user input is improperly handled within a server-side template engine.
We can now catch a request with burp and then manipulate costume parameter which wouldn’t be possible directly on the website.
Basic payload to test for SSTI is , if it returns as 49 it means we have SSTI working.
Now we can try to achieve code execution with this payload:
``
It worked, let’s now encode a reverse shell payload:
And now run place it into SSTI payload and start a listener.
We got a connection back!
Priv Esc
We can retrieve a flag.
First thing that came to my mind was to check current application code to look for credentials.
4d_09@fhgRTdws2
Unfortunately it is not reused, there is no second user.
There are also two new ports open 4566 and 38047, I discovered it with netstat -nvlp command.
I’ll leave them for now and proceed to look for cron jobs with pspy64.
https://github.com/DominicBreuker/pspy/releases/tag/v1.2.1
We successfully found some cron jobs running:
The one that is intresting to us is:
- /bin/bash /usr/bin/backup.sh
- /usr/bin/tar -cvf /opt/backups/923048034.tar /var/www/app/
Let’s take a look at this script:
The second-to-last line of code contains a security risk.
It uses tar command with -h option, meaning it will follow symlinks.
We need to wait for checksum being created and then we can create a symlink on it to any file:
There is only 5 second window to overwrite checksum file.
Now it will get archived into a .tar file which we can view without extracting it:
tar -xOf 518221472.tar opt/backups/checksum
It works, now we will try to retrieve root’s ssh key:
ln -sf /root/.ssh/id_rsa /opt/backups/checksum
And now we can view it:
With the key we can connect via ssh:
Lastly we will retrieve a flag:
Thank you for reading!