Sploitus

Exploit for web-app-pentest-travelbird

githubexploit · 2026-09-10

Exploit Code

README130 lines
## https://sploitus.com/exploit?id=82F24BE5-C4CE-5671-AE47-A3A6063D23C6
Web Application Penetration Test and Remediation: TravelBird

A full offense-to-defense engagement against TravelBird, a mock travel booking site running on a local XAMPP server. I found and exploited four separate vulnerability classes, then went back and fixed every one of them at the code and server level, and closed with a threat model for the LLM chatbot the company was planning to add.

The point of this project isn't just "found a bug." It's the full loop: confirm the flaw, prove impact with real exploitation, score it properly, then write and verify a fix that can't be trivially bypassed.

## Scope and ethics

All testing was performed against a local, intentionally vulnerable training application running on my own XAMPP instance. No real systems, real users, or real data were involved at any point. The credentials, names, and personal data referenced below (five mock customer accounts named after footballers) are fictional lab data seeded into the target application for this exercise.

## Skills demonstrated

Manual SQL injection (UNION-based) · sqlmap · Burp Suite (Proxy, Intruder, Repeater) · OWASP Top 10 · CVSS v3.1 scoring · secure code remediation (PDO/prepared statements, output encoding, MIME validation) · Apache and PHP hardening · UK GDPR / Data Protection Act 2018 / Computer Misuse Act 1990 / PCI-DSS analysis · LLM prompt injection threat modelling (STRIDE)

## Findings summary

| # | Vulnerability | CVSS v3.1 | Impact |
|---|----------------|-----------|--------|
| 1 | SQL Injection (`results.php`, `user_query` parameter) | 9.8 Critical | Full unauthenticated database dump |
| 2 | Unrestricted File Upload to RCE (`edit_account.php`) | 8.8 High | Remote code execution on the server |
| 3 | Reflected XSS (chained through the SQLi) | 6.1 Medium | Session/account compromise in victim's browser |
| 4 | Credential stuffing via OSINT wordlist | 7.5 High | Mass account takeover, no rate limiting |

---

## 1. SQL injection

`results.php` takes a `user_query` GET parameter and concatenates it directly into a MySQL query with no sanitisation:

```php
$get_pack = "SELECT * FROM packages WHERE package_keywords LIKE '%$search_query%'";
$run_pack = mysqli_query($con, $get_pack);
```
See [code-fixes/sql-injection/vulnerable-results.php](code-fixes/sql-injection/vulnerable-results.php).

I used a standard seven-step UNION-based methodology to go from a single quote to a full data dump:

1. Trigger a SQL error to confirm the injection point.
2. Use `ORDER BY` to find the column count (8 columns).
3. Use `UNION SELECT` to find which columns get reflected back on the page.
4. Pull the database name.
5. List every table via `information_schema`.
6. List the columns in the `customers` table.
7. Dump the table.



The final payload:

```sql
-1' UNION SELECT 1,2,3,CONCAT(customer_name,':',customer_email,':',customer_pass),5,6,7,8 FROM customers -- -
```






Every account came back with a plaintext password. I cross-checked the manual result against `sqlmap`, which independently confirmed three separate injection types on the same parameter (boolean-based blind, error-based, and time-based blind) and dumped the same table.

**Fix:** replace the string concatenation with a parameterised query. See [code-fixes/sql-injection/fixed-results.php](code-fixes/sql-injection/fixed-results.php). The query structure is now compiled independently of the user-supplied value, so the database treats the input as literal data rather than SQL syntax. There's no escaping trick or comment sequence that can change that.

## 2. Unrestricted file upload to remote code execution

The profile picture upload on the account page (`edit_account.php`) didn't check the file's contents or MIME type, and didn't restrict the extension. I uploaded a PHP web shell as a "profile image," then called it directly in the browser:



The shell could read arbitrary files accessible to the Apache process, including application source and (in a real deployment) database credentials. Chained with the credentials recovered in the SQL injection, this is effectively unauthenticated RCE.

**Fix:** validate both the file extension against a whitelist and the real MIME type read from the file's contents via `finfo` (not the browser-supplied `Content-Type`, which is trivial to spoof), write the file under a randomly generated name, and disable PHP execution in the upload directory as a second, independent layer. See [code-fixes/file-upload/fixed-upload-validation-excerpt.php](code-fixes/file-upload/fixed-upload-validation-excerpt.php) and [code-fixes/file-upload/customer_images.htaccess](code-fixes/file-upload/customer_images.htaccess).



## 3. Cross-site scripting, chained through the SQL injection

The search results page renders one of the reflected columns inside an `` tag with no output encoding. Because that same column is controlled through the SQL injection, I could inject a live `` payload directly through the database query:



A real attacker would swap the `alert()` for something that steals session cookies or performs actions as the victim.

**Fix:** wrap every output point that includes user- or database-derived data in `htmlspecialchars()` with `ENT_QUOTES` and UTF-8. See [code-fixes/xss/fixed-output-encoding.php](code-fixes/xss/fixed-output-encoding.php). Encoding happens at the final output stage, so it doesn't matter what the query returns. It can never be rendered as executable markup.



## 4. Credential stuffing via OSINT

The mock customer accounts were seeded using real footballer names with predictable `firstname@gmail.com` email addresses. I scraped a player list from Wikipedia to build a targeted email wordlist, paired it with the 1000 most common passwords, and ran a Cluster Bomb attack in Burp Intruder against the login endpoint. Sorting responses by length surfaced one immediately: a successful login has a distinctly different response size than a failed one.



**Fix:** two parts. Lock the account after five failed attempts within a window, and hash stored passwords with bcrypt via `password_hash()` / `password_verify()` so a database dump alone is no longer enough to get in. See [code-fixes/credential-stuffing/password-hashing.php](code-fixes/credential-stuffing/password-hashing.php).



---

## Server hardening

Beyond the application-level fixes, I hardened the underlying Apache/PHP stack with five independently verified measures: disabling directory listing, removing server and PHP version banners, blocking `/server-info` and `/server-status`, adding standard security response headers, and disabling the PHP functions (`exec`, `shell_exec`, `system`, and others) that turn a file-upload bug into full shell access.

Full config: [code-fixes/server-hardening/httpd-hardening.conf](code-fixes/server-hardening/httpd-hardening.conf) and [code-fixes/server-hardening/php-hardening.ini](code-fixes/server-hardening/php-hardening.ini).

## Legal and ethical analysis

Assuming TravelBird were a real production service handling holiday bookings and payment details, the findings above intersect with several UK legal and industry frameworks:

- **UK GDPR / Data Protection Act 2018**: the plaintext password storage breaches Article 5(1)(f) (integrity and confidentiality of personal data) and Article 32 (appropriate technical measures). A breach involving this data would trigger the 72-hour ICO notification duty under Article 33, with fines up to £17.5 million or 4% of global turnover for non-compliance.
- **Computer Misuse Act 1990**: the file-upload exploit, if performed against a live system without written authorisation, would fall under Section 3 (unauthorised acts with intent to impair). Any real testing needs signed authorisation first.
- **PCI-DSS**: if TravelBird processes card payments, the plaintext passwords breach Requirement 8.3 (strong cryptography for stored credentials) and the SQL injection breaches Requirement 6.3 (protecting custom code against known vulnerabilities).

## New trend: prompt injection against the planned LLM chatbot

TravelBird was planning to add an LLM-powered support chatbot. I modelled three realistic prompt injection scenarios against that pipeline and mapped each to STRIDE:

- **Direct prompt injection**: a user simply tells the model to ignore its instructions and reveal internal moderation rules.
- **Indirect prompt injection via stored content**: a malicious "review" left in the database contains an embedded instruction that fires later when another user's query pulls that review into context.
- **Role confusion**: a user asks the model to adopt an administrator persona and list restricted resources.

Proposed mitigations: input sanitisation for known injection patterns, using the API's dedicated system-role separation where available, content moderation on anything retrieved from storage before it re-enters the model's context, output filtering for sensitive data patterns, and least-privilege scoping so the chatbot never has access to anything it doesn't strictly need. None of these are complete solutions on their own. Keyword filtering is an evasion arms race, and privilege separation has already been shown to leak under adversarial pressure. The durable fix is architectural: keep the model away from sensitive resources entirely rather than relying on it to police its own output.

## Standards and tools referenced

OWASP Top Ten · PortSwigger Web Security Academy · CVSS v3.1 (FIRST) · sqlmap · UK GDPR / ICO guidance · Computer Misuse Act 1990 · PCI-DSS v4.0 · OWASP Top 10 for LLM Applications · MITRE STRIDE

---

All screenshots referenced above, plus the full set covering every step of both the exploitation and the fix verification, are in [screenshots/](screenshots/).