Assessing Astley’s Shop
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 final report covered 54 pages. It followed the application from initial mapping through authentication, session management, input handling, business logic, server configuration, source review, and remediation. The assessment 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.
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 |
The public write-up concentrates on findings and implementation rather than reproducing the exploit appendix.
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.phplogin.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.
Reviewing comments and page metadata did not reveal a separate credential or developer-note finding. Recording that negative result prevented the report from implying that every test category produced a weakness.
The supported HTTP methods were reported as 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. Both observations were retained: 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.
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.
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.
The report originally 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.
The assessment therefore treated the authentication design as a chain:
- account responses disclosed whether a user existed
- weak passwords were permitted
- login attempts were not rate limited
- the application used HTTP
- sensitive cookie material could be decoded and analysed
- 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 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. The report 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.
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 retained text from becoming markup. The original report 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. The report did not need to claim that every theoretical impact had been achieved.
The later source review showed that the problem was broader than one form. Searching for query construction identified direct request use across:
login.phpcategory.phppending-orders.phpproduct-details.phporder-details.phpsub-category1.phpmy-account.phpappendage.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 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.
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.
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.
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.
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.
The report 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.
I would verify the remediation in the same order as the findings:
- repeat the original request and confirm the vulnerable behaviour is gone
- test equivalent fields and code paths for the same pattern
- inspect the changed source for a shared design correction
- exercise malformed and boundary input
- confirm error handling does not reveal new detail
- rerun targeted automated scans
- check that logging records blocked attempts without retaining secrets
That is stronger than treating a code diff as proof that the risk is closed.
This page does not reproduce the lab exploitation commands or payloads. 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. The stack and tool versions are historical to 2022/23, while the secure-design failures remain recognisable in current web assessments.
