Projects

Web Application Security Assessment

OWASP-guided assessment demonstrating SQL injection, reflected and stored XSS, session exposure, executable file upload, server-side execution, and repeated unsafe patterns in PHP source.

  • OWASP WSTG
  • Burp Suite
  • OWASP ZAP
  • PHP
  • MariaDB
  • Nmap
  • Nikto
  • Wireshark
  • SQLmap
  • Gobuster
On this page

At a glance

Authorised scope
Individual third-year assessment of Astley's Shop, a deliberately vulnerable PHP and MariaDB application supplied inside the university lab.
My contribution
I mapped the application, tested identity, session, input and upload boundaries, validated findings manually and with supporting tools, reviewed the PHP source, and wrote the remediation plan.
Technical focus
  • OWASP WSTG
  • SQL injection
  • Reflected and stored XSS
  • Session exposure
  • Executable file upload
  • PHP source review
Demonstrated outcome
I demonstrated administration access, database record extraction, browser-side execution, exposure of session material and server-side execution through the profile-image upload path.

Project overview

This project was a complete web application security assessment of Astley’s Shop, a deliberately vulnerable shopping application supplied inside the university lab for CMP319 Ethical Hacking 2. I used the OWASP Web Security Testing Guide v4.2 to structure the work and combined manual testing, Burp-assisted request inspection, browser analysis, packet capture, automated enumeration and a later review of the PHP source.

The assessment moved from the external application surface into the way the application handled identity, authentication, sessions, database queries, browser output, file uploads, errors and business functions. I followed each finding from discovery through manual validation, source review and remediation.

The most important findings were not isolated scanner results. They were weaknesses that reinforced one another:

  • account responses disclosed whether an identity existed;
  • the application accepted extremely weak passwords;
  • authentication had no effective attempt limit;
  • credentials and reusable cookies travelled over plaintext HTTP;
  • a custom cookie exposed username and password-derived data through reversible encoding;
  • reflected and stored cross-site scripting could execute in another user’s browser;
  • several PHP routes constructed SQL queries from request values;
  • the application used a highly privileged database account with hard-coded credentials;
  • upload validation trusted request metadata and stored accepted files inside an executable web path;
  • diagnostic pages, schema files and verbose errors exposed useful platform information.

The controlled assessment demonstrated administration access through SQL injection, extraction of records from the teaching database, browser-side execution through reflected and stored XSS, exposure of session material, and server-side execution through the profile-image upload path. A separate database-to-filesystem route was attempted but did not succeed because the relevant service context appeared unable to write into the target directory. I recorded that failed step because it defined the demonstrated boundary rather than treating every theoretical impact as achieved.

The source review changed the scope of the results. Runtime testing proved that specific fields were vulnerable; the code showed that the same unsafe database pattern appeared across public, account, order and administration functions. It also connected the upload, cookie and stored-XSS behaviour to the implementation that caused each result.

The assessment was completed against a historical university lab image. The platform versions, addresses and test accounts are not current deployment guidance. The security value of the project is in the method: establish the attack surface, validate behaviour manually, connect related weaknesses, review the implementation, define the demonstrated impact precisely, and recommend a design correction rather than a payload-specific patch.

Assessment context

This CMP319 project was a full web application security assessment against Astley’s Shop, a deliberately vulnerable application supplied inside the university lab. I used the OWASP Web Security Testing Guide v4.2 to organise the work, then combined manual testing, proxy-assisted inspection, automated scanning, packet analysis, and a review of the PHP source.

The assessment followed the application from initial mapping through authentication, session management, input handling, business logic, server configuration, source review, and remediation. It identified weaknesses that could be chained rather than treating every scanner result as an isolated issue.

The lab application used Apache, PHP, and MariaDB. Its functions included registration and login, product search, product reviews, order information, profile images, an administration area, and database-backed account data.

Cover of the Web Application Security Test project
The CMP319 project combined assessment method, practical validation, source review and remediation.

The discovered host exposed FTP on port 21, HTTP on port 80, and MariaDB on port 3306. Service fingerprinting reported Apache 2.4.3 and PHP 5.4.7 on a Unix-like system. Those versions belong to the supplied historical image; they helped explain the age and configuration of the lab rather than serving as current deployment guidance.

My test plan followed the application through these WSTG areas:

Phase What I examined
Information gathering Services, technologies, metadata, files, directories, entry points, and execution paths
Configuration and deployment Diagnostic exposure, administration interfaces, HTTP methods, and transport security
Identity and authentication Registration, account enumeration, password policy, login attempts, and credential transport
Session management Session identifiers, custom cookies, attributes, contents, and browser exposure
Input validation Reflected and stored XSS, SQL injection, file inclusion, and delayed or incubated effects
Error handling Version and platform detail disclosed through error responses
Business logic Request forgery, repeatable functions, and unexpected file types
Source review Query construction, credentials, cookies, upload handling, and output rendering

This case study concentrates on findings and implementation, while retaining the requests, payload forms and commands that materially explain the demonstrated behaviour.

Scope and working method

I treated the application as a connected system rather than only a collection of forms. The work covered:

  • the externally reachable host and services;
  • the web server, PHP runtime and database exposure;
  • unauthenticated and authenticated application functions;
  • identity creation and login behaviour;
  • session cookies and browser security attributes;
  • user-controlled input in URLs, forms, headers and files;
  • administrative and database-backed workflows;
  • business functions that could be repeated or forged;
  • error messages and diagnostic exposure;
  • the PHP implementation supplied after testing.

The starting position included authorised credentials for the lab application, but I also tested the unauthenticated paths because the application exposed registration, login, search, category, product and administration functions before a normal user session existed.

I kept automated tools in a supporting role. Nmap, Nikto, Gobuster, Wappalyzer and ZAP helped establish coverage. Burp Suite, the browser, direct HTTP requests and Wireshark were used to confirm what the application accepted and returned. SQLmap and password-testing tools were only used after the corresponding behaviour had been identified and within the supplied lab.

For each finding I wanted to answer five questions:

  1. Which request, field or configuration introduced the condition?
  2. What did the server or browser do with that value?
  3. What access or information was demonstrated?
  4. Which other weakness increased the impact?
  5. What design change would remove the underlying cause?

That prevented me from treating a tool alert as proof. It also allowed me to record negative or limited results, including the absence of dangerous HTTP methods, the lack of sensitive developer comments, and the failed attempt to turn database injection into filesystem write access.

Finding summary

Area Demonstrated condition Risk relationship
Platform exposure Public schema, phpinfo, administration and database-management paths Improved later targeting and exposed internal configuration
Identity Duplicate registration, weak passwords and account-disclosing errors Made account discovery and password guessing more practical
Authentication No effective rate limit or temporary block Allowed repeated attempts against known identities
Transport and sessions HTTP-only traffic and reversible custom cookie Exposed reusable session and password-derived material
Browser output Reflected and stored XSS Could execute in another user’s session and access script-readable cookies
Database access SQL injection across several workflows Expanded from authentication bypass to record extraction
Service privilege Hard-coded root database credentials Increased the possible impact of query injection
File handling MIME metadata trusted and uploads stored in executable paths Converted a profile-image function into server-side execution
Error handling Verbose 404 and diagnostic pages Reinforced platform and filesystem information disclosure
Source design Unsafe patterns repeated across routes Showed that local fixes would leave equivalent paths exposed

Mapping the application before testing it

I began by establishing the server and application surface:

  • service and version fingerprinting
  • review of robots.txt, web metadata, comments, and exposed files
  • directory and endpoint discovery
  • identification of administrative interfaces
  • mapping of forms, query parameters, cookies, uploads, and user-controlled values
  • browser-assisted crawling to record reachable paths
  • checks for unnecessary HTTP methods, absent transport controls, and verbose error responses

The manual entry-point inventory included:

  • index.php
  • login.php
  • category and subcategory parameters
  • track-orders.php
  • the authenticated my-account.php
  • billing and shipping address forms
  • search-result.php
  • product reviews, profile pictures, cart actions, and order details

ZAP’s spider recorded 284 in-scope URL and method combinations. That figure included repeated products, category identifiers, assets, POST actions, and parameter variants, so it was an attack-surface inventory rather than 284 unique vulnerabilities. External framework and licence links were marked out of scope in the crawl output.

The mapping phase found more than a list of URLs. robots.txt instructed crawlers not to visit /schema.sql, but that path was still publicly retrievable and exposed the application’s database schema. A public phpinfo() page disclosed processor, operating-system, kernel, PHP, document-root, and server configuration detail. Directory discovery also identified /admin and the phpMyAdmin database-management interface.

phpinfo output disclosing PHP version, operating system and document root
The diagnostic page exposed both the historical PHP platform and the local filesystem path used by the web application.

Reviewing comments and page metadata did not reveal a separate credential or developer-note finding. Recording that negative result kept me from implying that every test category produced a weakness.

The supported HTTP methods were GET, HEAD, POST, and OPTIONS; the test did not find TRACE, PUT, or DELETE enabled. The site was HTTP-only, so an HSTS header could not provide a secure transport boundary in the first place. Unnecessary methods were not the issue, while absent HTTPS was.

OWASP ZAP, Nikto, Nmap, Gobuster, Wappalyzer, curl, and browser inspection were used as supporting tools. I treated their output as leads to verify manually. The application crawl produced hundreds of URLs, many of which were repeated parameter variations rather than distinct security findings.

The scanners also had different roles. Nmap and direct HTTP requests established services and response behaviour. Gobuster tested likely paths. Wappalyzer identified client-side technologies including Bootstrap and jQuery. Nikto and ZAP raised configuration and application leads. Burp Suite and browser developer tools let me change and replay requests while observing exactly which field reached a response or backend function.

Service fingerprinting, raw HTTP inspection, robots.txt and directory enumeration evidence
The initial discovery established the exposed services and identified paths that the normal navigation did not advertise, including the database schema and administration interfaces.
Technology identification, ZAP crawl, Nikto output and HTTP control checks
The crawl and scanning output was used as an attack-surface inventory. Each meaningful result was reviewed manually before it became a finding.

The discovery sequence produced several different classes of information:

network service
  -> application technology
      -> hidden or diagnostic path
          -> user-controlled entry point
              -> request and response behaviour
                  -> targeted validation

The exposed /schema.sql file was particularly useful because it changed the database structure from something I had to infer into something the application published directly. robots.txt did not protect it; the file provided the path.

The public phpinfo() page added the local document root and detailed runtime configuration. That information later informed the attempted database-to-filesystem route. The write did not succeed, but the exposed path still reduced uncertainty and would have helped an attacker choose other platform-specific tests.

The application crawl required interpretation. ZAP recorded 284 URL and method combinations, but repeated product identifiers, parameter variants and assets were not separate vulnerabilities. I used the result to confirm coverage and to identify where one coding pattern appeared in several routes.

Identity and authentication

Registration accepted duplicate account details and very weak passwords. Error messages also distinguished between an unknown account and a known account with an incorrect password, making account enumeration possible.

The registration checks were inconsistent. A malformed email address was rejected, showing that some format validation existed, but the same email and account details could be registered repeatedly. A single-character password was accepted. This meant the form enforced the shape of one field while leaving uniqueness and password strength effectively uncontrolled.

The login response then exposed account state. An unknown email produced a different message from a valid email paired with an incorrect password. That distinction could be automated to build a list of registered accounts before password testing began.

Different login responses for unknown and known application accounts
The application disclosed whether an email address existed by returning a distinct response before authentication succeeded.

The problem was not that one error message contained sensitive data. It was that the responses formed a reliable classification system:

unknown account
  -> one response

known account + incorrect password
  -> different response

An automated client could use that difference to separate valid accounts from invalid ones. The weak password policy and unlimited attempts then made the resulting account list more useful.

A generic response should be accompanied by controls at several levels: uniqueness checks during registration, password-strength requirements, per-account and per-source attempt throttling, monitoring of distributed attempts, and additional verification for high-risk authentication. A permanent lockout would create its own denial-of-service problem, so the remediation needed to manage rate and risk rather than simply disable an account forever.

The login function had no effective attempt limit or lockout mechanism. In the controlled lab, more than ten thousand attempts against the test account continued without a delay or temporary block before the chosen test password was reached. The significance was not the particular wordlist position; it was that the server applied no visible control as the request pattern repeated.

On its own, that increased exposure to password guessing. Combined with single-character passwords, duplicate registration behaviour, and account-disclosure responses, it created a much more practical authentication weakness. Rate limiting needed to be applied by account and source context, with monitoring and careful handling of denial-of-service risk rather than a simple permanent lockout.

Credentials and session material were transmitted over HTTP. Packet inspection showed both the PHP session identifier and a custom authentication cookie travelling without transport encryption. The custom cookie was not an opaque random token: it contained account information and a password hash that had been encoded rather than protected cryptographically.

The source later showed how SecretCookie was assembled: username, MD5 password hash, and current time were joined, Base64 encoded, then converted to hexadecimal. Both operations changed representation without supplying confidentiality or integrity. Anyone who obtained the cookie through packet capture or browser-side script could reverse the encoding and recover the component values.

At the time I considered hashing that assembled string before setting the cookie. That would conceal the plaintext components but would not by itself create a sound session design. A maintained application should issue a cryptographically random opaque identifier whose state is held and expired server-side, or use a correctly designed authenticated token. Password hashes should not be placed in client-controlled session material at all.

Plaintext HTTP cookie capture, reversible decoding and password-hash review with lab values removed
The custom cookie changed representation through hexadecimal and Base64 but did not gain confidentiality or integrity. Captured lab values have been removed from the public figure.

The custom cookie crossed three trust boundaries incorrectly:

  1. account and password-derived information was assembled on the server;
  2. the value was handed to the browser as client-controlled state;
  3. the browser transmitted it over plaintext HTTP and exposed it to script access.

Encoding addressed none of those boundaries. Even replacing the encoding with a one-way hash would leave replay and lifecycle problems if the resulting value remained a bearer credential.

A stronger session design would create a random identifier after authentication, bind it to server-side state, rotate it after privilege changes, expire it predictably, revoke it at logout, and set Secure, HttpOnly and an appropriate SameSite policy. The server should never need the browser to carry a password hash in order to recognise an authenticated user.

The assessment therefore treated the authentication design as a chain:

  1. account responses disclosed whether a user existed
  2. weak passwords were permitted
  3. login attempts were not rate limited
  4. the application used HTTP
  5. sensitive cookie material could be decoded and analysed
  6. cookie attributes did not sufficiently restrict browser access or cross-site use

The cookie review also separated transport, script access, and cross-site behaviour. Secure would restrict transmission to HTTPS, HttpOnly would prevent ordinary JavaScript access, and SameSite would constrain some cross-site requests. Those attributes reduce exposure, but none compensates for predictable or sensitive cookie content.

Input handling and browser-side execution

The product search reflected the supplied search term into the response without appropriate output encoding. Product reviews were stored in the database and returned to later visitors in the same unsafe way. These produced reflected and stored cross-site scripting findings.

The initial validation used a minimal browser-execution marker in both fields:

<script>alert(1)</script>

The value appeared as markup rather than text in the response. I then used the stored path to validate the more consequential relationship with script-readable session material inside the lab.

The search result was inserted into the generated HTML to show the user what they had searched for. The product-review field followed a longer path: the application accepted the review through a POST request, stored it in productreviews, then returned it in raw form on later GET requests. In both cases, the browser could no longer distinguish user-supplied text from application code.

The distinction affected the risk. Reflected input required a victim to follow or submit a crafted request, while the stored review remained in the application and was served to anyone viewing the affected product. I verified that a stored review could cause a browser to send its accessible cookies to the separate lab host. Because the application’s custom authentication cookie and PHP session cookie lacked sufficient attributes, the session-management weakness increased the impact of the XSS.

Reflected and stored cross-site scripting evidence from the Astley's Shop lab
The search term and product-review content reached browser output without a consistent encoding boundary, producing both reflected and persistent execution paths.
Controlled listener receiving a browser request caused by stored script execution
The stored browser-side execution reached a separate lab listener. Cookie values have been removed from the public figure.

The browser was the execution environment, but the cause was server-side output handling. The application stored or reflected text and later inserted it into HTML as if it were trusted markup.

This required a sink-specific correction. A review displayed inside HTML text needs HTML encoding. A value inserted into an attribute, URL or JavaScript context needs the encoding appropriate to that context. Generic string replacement or a blacklist of script tags cannot guarantee that every sink is safe.

The cookie attributes were a separate control. HttpOnly would have reduced ordinary JavaScript access to the session cookie, but it would not have stopped script execution or actions performed through the victim’s browser. The XSS finding and session finding therefore needed independent remediation.

The defensive correction was not to blacklist a short list of script strings. User-controlled data needed context-aware output encoding, with server-side validation applied according to the type of data the field was intended to accept. Cookies also needed HttpOnly, Secure, and an appropriate SameSite policy.

For a review field, validation can enforce length and acceptable content, but HTML-context output encoding is what prevents stored text from becoming markup. I originally used PHP sanitisation examples common at the time. I would now prefer explicit validation followed by encoding at each output sink, rather than modifying the stored text with a generic filter and assuming it is safe in every browser context.

SQL injection and database trust

Manual testing identified an authentication-bypass condition in the administration login. Further testing found time-based SQL injection in an order-related parameter and demonstrated that database records could be extracted inside the lab.

The administration finding established that attacker-controlled text was changing the query’s Boolean logic. The order-details test established a blind or time-based path where the application did not need to print database rows for the query to be influenced. Automated verification then extracted records from the teaching database. A separate attempt to write through the database into the web directory failed, apparently because the database or web-service context lacked the required filesystem permission.

That failed step was useful scope information: SQL injection was confirmed, but one possible route from database control to server-side code execution was not. I did not claim that every theoretical impact had been achieved.

Administration access and database extraction evidence with lab records removed
The assessment demonstrated administration access and extraction from the teaching database. User records and credentials have been removed from the public figure.

The result had three different levels of demonstrated impact:

Level Demonstrated result
Query manipulation A crafted value changed the administration login decision
Data access Time-based testing and extraction returned records from the teaching database
Host-level execution through database file write Attempted but not achieved

Separating those levels mattered. SQL injection does not automatically mean operating-system control, and I did not collapse a confirmed database weakness into an unverified server compromise.

The database account increased the risk because the application connected with privileged credentials embedded directly in source. Even where a specific filesystem operation failed, an over-privileged account widened the range of database operations available to an injected query.

The correct fix was consistent use of prepared statements through a shared database layer. Replacing one vulnerable query would not address the direct concatenation pattern found across login, product, category, account and order functions. Database authorisation also had to remain separate from application authorisation: a numeric record identifier should be validated, parameterised and checked against the authenticated user’s permission before data is returned.

The later source review showed that the problem was broader than one form. Searching for query construction identified direct request use across:

  • login.php
  • category.php
  • pending-orders.php
  • product-details.php
  • order-details.php
  • sub-category1.php
  • my-account.php
  • appendage.php

The list crossed public, account, order, and administration functions. Fixing only the tested administration form would have left the same coding pattern elsewhere.

This changed the remediation from “filter the vulnerable parameter” to a design correction:

  • use prepared statements consistently
  • keep query structure separate from request data
  • validate values according to their expected type
  • run the application under a database account with only the privileges it requires
  • remove database credentials from source files and protect configuration outside the web root
  • replace MD5 password storage with a modern password-hashing API

The source also contained hard-coded database credentials and used a highly privileged database account. An injection flaw in that context had a larger potential impact than the same flaw under a tightly restricted account.

Prepared statements needed to be the default database API, not a patch around known payload characters. Numeric identifiers should also be validated as numeric and authorisation applied independently before records are returned or changed. Least privilege would limit the application account to the schema and operations it actually needed, while separate administration and migration accounts handled higher-risk tasks.

The source stored password values with MD5. Even if the injection flaws were removed, fast unsalted hashing would leave recovered database values exposed to efficient offline guessing. Password storage needed a dedicated adaptive password-hashing API with per-password salts and an upgrade path.

File upload handling

The profile-image function relied heavily on metadata supplied with the upload request. Renaming a server-side script to resemble an image was not sufficient on its own, but changing the declared content type allowed a script file through the validation path. When the application later served the uploaded file from an executable location, the lab test reached code execution on the web server.

The decisive request retained the active filename while changing the browser-supplied MIME claim:

Content-Disposition: form-data; name="uploadedfile"; filename="php-reverse-shell.php"
Content-Type: image/png

The application accepted the request as an image because it trusted Content-Type, then stored the original PHP body under the executable web path.

The source explained why. changepicture.php checked whether the request claimed JPEG, JPG, or PNG content, moved accepted files into the web-accessible pictures/ directory, and used chmod to grant read, write, and execute permissions to every user. The browser-supplied content type controlled admission, while the server later interpreted the file according to its real contents and location.

Modified upload metadata followed by execution in the web-service context
The application admitted active content when the request claimed an image MIME type, then served the accepted file from an executable location. Session values and payload content have been removed.

The vulnerability therefore required more than “file extension filtering failed”:

untrusted request metadata
  -> accepted as image evidence
  -> original active content retained
  -> predictable web-accessible storage
  -> server-side execution permitted

Removing any one of those assumptions would reduce the result, while a robust design removes several.

The recommended design separated several controls that the application had collapsed:

  • verify type from the file content on the server, not only the name or request header
  • impose size and image-decoding limits
  • generate server-side names rather than retaining user-controlled filenames
  • store uploads outside executable application paths
  • serve media through a handler or storage service that cannot execute it
  • use restrictive ownership and permissions
  • scan and re-encode images where appropriate

Client-side validation could improve feedback, but it could not be treated as a security boundary because the request could be created without the browser interface.

Content inspection also needs care. Recognising a JPEG header is stronger than trusting a request field, but a polyglot or malformed image can still present risk. Decoding and re-encoding an accepted image, setting strict size and dimension limits, storing it outside the web root under a generated name, and serving it through a non-executable media path provide independent controls.

The upload workflow should have treated the original file as untrusted input throughout its lifecycle:

request received
  -> enforce request and file-size limits
  -> decode using an image library
  -> reject invalid dimensions or format
  -> re-encode a new image
  -> generate a server-side identifier
  -> store outside the executable application tree
  -> record owner and metadata
  -> serve through a non-executable media response

The application instead used browser-supplied metadata as evidence and preserved the active contents. The broad 0777 permissions were not required for displaying a profile image and made the server-side storage decision weaker.

Source review

After the practical assessment, I reviewed the application’s PHP source manually. Search tools helped locate database calls, cookie handling, upload functions, output points, and credential material, but each result was read in context.

PHP source evidence for privileged credentials, upload validation, cookie construction and browser output
The later source review connected the black-box results to repeated implementation patterns. The hard-coded teaching credential has been removed.

The source review confirmed:

  • direct insertion of request values into SQL queries
  • hard-coded database credentials
  • use of the database root account
  • unsanitised product reviews returned as executable browser content
  • reversible account information in a custom cookie
  • upload checks that trusted request metadata
  • uploaded files placed under the web root with executable permissions

The source linked each black-box symptom to a concrete implementation:

Observed behaviour Source explanation
SQL injection in several workflows Request values concatenated into query strings
Reversible authentication cookie Username, MD5 hash, and time encoded into client data
Stored XSS in reviews Raw review inserted into the database and returned without output encoding
Active upload accepted as an image Request MIME type trusted and file moved under the web root
Broad database impact Hard-coded root database account used by the application

This changed the confidence and scope of the findings. A successful test showed that one route was vulnerable; the code review showed whether the same pattern existed in other routes. Conversely, the source could reveal a dangerous pattern that still needed runtime verification before I described its impact as demonstrated.

I used source review to answer three different questions:

  • Cause: which operation made the observed behaviour possible?
  • Breadth: did the same pattern exist in other routes?
  • Remediation location: could one shared change correct several findings?

For SQL injection, the code changed the finding from one vulnerable form to a repeated query-construction pattern. For the upload, it showed that the client-provided MIME value controlled admission and that the accepted file was moved beneath the web root. For the custom cookie, it confirmed that reversible encoding was deliberate rather than an artefact of one captured response.

The source also helped avoid overstatement. A suspicious pattern was not automatically reported as exploited; it still required a reachable route, relevant data flow and practical validation before I assigned demonstrated impact.

Error handling and business logic

The application returned a verbose 404 page containing web-server and PHP detail.

Verbose error response disclosing server software information
The missing-page response disclosed platform versions already exposed through phpinfo. Individually this was lower impact, but it reinforced the available reconnaissance.

Error messages should help the legitimate user and provide an internal reference for support without returning software versions, filesystem paths, stack traces, queries or secrets. Detailed exception information belongs in protected server logs correlated with a request identifier.

The business-logic review considered whether legitimate functions could be used in ways the interface did not intend. The clearest examples were:

  • registration could be repeated with the same identity details;
  • authentication could be attempted continuously;
  • stored review content was delivered to other users;
  • the profile-image route accepted an object that was not a safe image;
  • browser requests could be changed or replayed outside the normal interface.

This was important because the application did not fail only when malformed syntax was supplied. Some of the weaknesses used valid application functions repeatedly or with altered request context. Security therefore had to exist on the server side and not depend on the sequence presented by the browser.

Connected risk paths

The strongest findings formed complete paths.

Identity discovery to account access

distinct login responses
  -> valid account list
  -> weak password accepted
  -> unlimited authentication attempts
  -> account access

Stored input to session exposure

review submitted
  -> stored without safe output boundary
  -> later visitor loads product
  -> browser executes attacker-controlled script
  -> script-readable session material exposed

Database input to broad data access

request value concatenated into SQL
  -> query logic changed
  -> administration access or blind query control
  -> privileged database account
  -> records extracted

Profile image to server-side execution

client MIME claim trusted
  -> active content accepted
  -> original file retained
  -> stored under executable web path
  -> request executes under web-service account

These paths are more useful for prioritisation than a flat vulnerability count. They identify the control whose failure changed the boundary and the secondary weakness that increased the impact.

Reporting and prioritisation

The application had many weaknesses, but they were not independent. I prioritised the combinations that changed the security boundary:

  • SQL injection combined with an over-privileged database account
  • stored XSS combined with script-accessible cookies
  • weak passwords combined with unlimited login attempts and account enumeration
  • upload validation combined with executable storage under the web root
  • verbose configuration pages combined with outdated server components
  • plaintext HTTP combined with reusable authentication material

Verbose 404 responses also disclosed Apache and PHP version detail. Individually that was lower impact than SQL injection or executable upload, but it reinforced the platform information already exposed by phpinfo(). robots.txt was another information path: it did not protect schema.sql; it advertised where to request it.

I recommended HTTPS, stronger session handling, consistent parameterised queries, server-side upload verification, least-privileged service accounts, generic authentication errors, attempt throttling, secure password storage, and removal of exposed diagnostic and schema files.

Remediation programme

I would organise remediation into four workstreams.

Immediate exposure reduction

  • remove phpinfo.php, schema.sql and unnecessary administration interfaces from public access;
  • disable direct database exposure where it is not operationally required;
  • deploy HTTPS across the application and redirect all HTTP traffic;
  • invalidate existing application sessions and replace the custom cookie;
  • remove executable permissions from uploaded media paths;
  • rotate the hard-coded database credential and stop using the root account.

Shared application corrections

  • replace direct SQL construction with a common prepared-statement layer;
  • use a modern password-hashing API and migrate hashes after successful authentication;
  • create opaque server-managed sessions with rotation and revocation;
  • apply context-specific output encoding through shared templates;
  • redesign uploads around decoding, re-encoding, generated names and non-executable storage;
  • return generic authentication responses and apply measured throttling.

Least privilege and isolation

  • create a dedicated database account limited to the required schema and operations;
  • keep configuration and secrets outside the web root and source repository;
  • separate uploaded media from application code;
  • restrict phpMyAdmin or equivalent administration to a protected management path;
  • ensure the web-service account cannot alter executable application files;
  • add restrictive filesystem ownership and permissions.

Monitoring and assurance

  • alert on repeated failed authentication across account and source patterns;
  • record blocked input and upload events without storing passwords or complete cookies;
  • monitor changes to executable web files and upload directories;
  • detect unusual database errors and high-volume time-based requests;
  • log session creation, rotation, logout and privileged actions;
  • schedule targeted security testing after shared code changes.

Retest plan

I would verify the remediation in the same order as the findings:

  1. repeat the original request and confirm the vulnerable behaviour is gone
  2. test equivalent fields and code paths for the same pattern
  3. inspect the changed source for a shared design correction
  4. exercise malformed and boundary input
  5. confirm error handling does not reveal new detail
  6. rerun targeted automated scans
  7. check that logging records blocked attempts without retaining secrets

That is stronger than treating a code diff as proof that the risk is closed.

A successful retest would confirm that:

  • unknown and known accounts return indistinguishable unauthenticated responses;
  • duplicate identity details are handled according to the intended registration policy;
  • weak passwords are rejected;
  • repeated attempts trigger proportionate throttling and alerting;
  • all application traffic uses HTTPS and cookies carry the intended attributes;
  • the browser stores only an opaque session identifier;
  • reflected and stored values remain text in every relevant output context;
  • equivalent database routes use prepared statements;
  • the application database account cannot perform administration or filesystem operations;
  • uploaded files are decoded, renamed and stored outside executable paths;
  • diagnostic and schema files are not publicly retrievable;
  • error pages return a generic response and a non-sensitive support reference;
  • targeted ZAP, Nikto and manual checks do not introduce a new bypass.

The retest should cover equivalent routes, not only the original proof. A shared template or database wrapper may correct several paths, while an isolated patch may leave the same pattern elsewhere.

Project boundaries and limitations

Deliberately vulnerable environment

Astley’s Shop was a teaching application designed to contain security weaknesses. The frequency and age of the findings are not representative of a maintained production platform.

Historical technology

The Apache, PHP and operating-system versions belonged to the supplied 2022/23 lab image. They describe the tested environment, not current supported software.

Point-in-time assessment

The work recorded one state of the application. It did not include a remediation cycle, long-term monitoring period or production traffic baseline.

Limited organisational context

The scenario did not provide data classification, business owners, legal retention requirements, operational dependencies or a formal risk appetite. The technical priorities would need to be agreed with those owners in a real assessment.

Source supplied after practical testing

The black-box stage came first and the source review followed. This was useful for comparing observed behaviour with implementation, but it was not a complete secure-code review of every file and dependency.

Demonstrated and theoretical impact

I separated successful validation from possible escalation. Database injection and record extraction were demonstrated; database-assisted filesystem write was not. File upload reached the web-service account, but the project did not continue into wider operating-system privilege escalation.

Public presentation

The website version removes captured credentials, cookie values, password hashes and user records. It also avoids reproducing the exploitation appendix. The selected figures show the assessment method and results without turning the portfolio page into an operational attack guide.

What I developed through the project

This project developed practical experience in:

  • applying the OWASP Web Security Testing Guide as an assessment structure;
  • separating discovery from validation;
  • mapping forms, parameters, cookies, uploads and application routes;
  • using Burp Suite to inspect and change requests;
  • interpreting ZAP, Nikto, Nmap and Gobuster output;
  • analysing plaintext HTTP traffic in Wireshark;
  • reviewing session design and browser cookie controls;
  • validating reflected and stored browser-side execution;
  • assessing SQL query construction and database privilege;
  • evaluating file-upload trust and storage decisions;
  • connecting black-box results to PHP source;
  • distinguishing demonstrated access from theoretical impact;
  • writing remediation around shared design causes;
  • defining a focused retest plan.

The main lesson was that the most serious result rarely came from one line in a scanner report. Account enumeration became more useful because passwords were weak and attempts were unlimited. XSS became more damaging because reusable cookies were script-accessible and travelled without TLS. SQL injection had wider consequences because the application used a privileged account. File upload became server-side execution because untrusted metadata, active content, executable storage and broad permissions all aligned.

The source review made those relationships clearer. It showed where the application repeatedly crossed trust boundaries without a shared control and why fixing one visible field would not have been enough.

The technical value is in the assessment method, the verified findings, the code paths that caused them, and the way individual weaknesses combined into larger application risk. Relevant lab commands and payload forms are included where they explain those findings. The stack and tool versions are historical to 2022/23, while the secure-design failures remain recognisable in current web assessments.