Candidate number one on the Module 3 prioritized list is a probable SQL injection in tienda.technova.lab/producto.php?id=, TechNova's PHP/MySQL e-commerce application. It's also our primary target: the store is on the Internet and concentrates the customer data. In this lesson we validate that candidate and walk through the most relevant web vulnerability classes of the OWASP Top 10 / WSTG, applying the controlled cycle from 04-01: mechanism, low-impact PoC, and remediation with secure code.

A reminder of the framing, which matters especially here because the store handles fictitious personal data: everything happens in TechNova's authorized lab, within scope and with impact under control. We don't dump the customer database, we don't delete anything, we don't leave persistent scripts behind. Each vulnerability is taught with its mechanism (why it exists), a PoC that only demonstrates (reading a version, an id, a sentinel value), and its fix. The pentester's goal is to report and help fix, not to cause damage.

Contents

  1. Why the web is the most exposed surface
  2. SQL injection and its responsible automation (sqlmap)
  3. Cross-Site Scripting (XSS): reflected and stored
  4. Cross-Site Request Forgery (CSRF)
  5. File inclusion: LFI and RFI
  6. Insecure file upload
  7. IDOR and broken access control
  8. SSRF (Server-Side Request Forgery)
  9. Insecure deserialization (overview)
  10. Detection and tooling
  11. Common mistakes and tips
  12. Exercises
  13. Conclusion

  1. Why the web is the most exposed surface

Web applications are, by definition, exposed to the Internet and process untrusted input from the user on every request. Almost every web vulnerability springs from the same original sin: mixing attacker-controlled data with code or commands (SQL, HTML, file paths, HTTP requests) without properly separating them. Understanding that pattern makes every class that follows click into place.

  1. SQL injection and its responsible automation

Mechanism. SQL injection happens when user input is concatenated directly into a SQL query. The attacker escapes the "data" context and starts injecting SQL "code."

Vulnerable code (TechNova's producto.php):

<?php
// VULNERABLE: the id parameter is concatenated, mixing data and code
$id = $_GET['id'];
$sql = "SELECT name, price, description FROM products WHERE id = $id";
$res = mysqli_query($conn, $sql);
?>

If producto.php?id=1 arrives, the query is normal. But if producto.php?id=1 OR 1=1 arrives, it becomes ... WHERE id = 1 OR 1=1, which returns all products: the input has changed the logic.

Low-impact validation PoC. Following the 04-01 framework, we don't dump data: we only prove that the input is interpreted as SQL.

# 1) Error test: a single quote breaks the syntax -> confirms injection
curl "http://tienda.technova.lab/producto.php?id=1'"
#   -> "You have an error in your SQL syntax..."  (input reaches the engine)

# 2) Boolean test: same page with TRUE, different/empty with FALSE
curl "http://tienda.technova.lab/producto.php?id=1 AND 1=1"   # product visible
curl "http://tienda.technova.lab/producto.php?id=1 AND 1=2"   # product absent

# 3) Extract ONE harmless value from the engine (the version), not customer data
curl "http://tienda.technova.lab/producto.php?id=-1 UNION SELECT @@version,2,3"
#   -> shows "5.5.62" in the name slot: access confirmed, no customers touched

Reading @@version (which also confirms the MySQL 5.5.62 from the inventory) proves control over the query without extracting a single customer record. That's enough for the report.

Responsible automation with sqlmap. To map the real scope (which databases and tables would be accessible) we use sqlmap, but in a controlled way:

# Enumerate structure only (databases), without dumping sensitive content
sqlmap -u "http://tienda.technova.lab/producto.php?id=1" --batch --dbs

Responsible use: enumerate structure to size the risk, not --dump-all. No --os-shell or writing unless the RoE explicitly authorize it. sqlmap is powerful and noisy; it's used within the agreed window.

Remediation (secure code):

<?php
// SECURE: prepared statement, the data is NEVER mixed with the SQL code
$stmt = $conn->prepare("SELECT name, price, description FROM products WHERE id = ?");
$stmt->bind_param("i", $id);   // "i" forces an integer: separates data from code
$stmt->execute();
$res = $stmt->get_result();
?>

Parameterized queries (prepared statements) send the query and the data separately; the engine never interprets the data as SQL. It's the definitive defense, plus the principle of least privilege on the DB account.

  1. Cross-Site Scripting (XSS): reflected and stored

Mechanism. XSS injects JavaScript into a page that other users will view, because the application returns user input without escaping it in the HTML. The code runs in the victim's browser.

Type Where the payload lives Example in TechNova
Reflected In the URL, reflected in the response Search box: buscar.php?q=...
Stored Saved in the DB, served to everyone A product comment/review
DOM In client-side JS, without touching the server A # fragment processed by JS

Vulnerable code (reflected search box):

<?php // VULNERABLE: direct echo of the parameter into the HTML
echo "<p>Results for: " . $_GET['q'] . "</p>";
?>

Low-impact PoC. We don't steal real sessions: we test with a harmless, visible payload.

http://tienda.technova.lab/buscar.php?q=<script>alert(document.domain)</script>

If the alert pops with tienda.technova.lab, the XSS is confirmed. An alert() proves execution without exfiltrating cookies; a screenshot is enough for the report.

Remediation (secure code):

<?php // SECURE: escape according to the output context (HTML)
echo "<p>Results for: " . htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8') . "</p>";
?>

Defenses: escape by context (htmlspecialchars in HTML), Content-Security-Policy (a header that restricts which scripts run), HttpOnly cookies (JS can't read them), and input validation. In modern frameworks, template auto-escaping covers most cases.

  1. Cross-Site Request Forgery (CSRF)

Mechanism. CSRF abuses the fact that the browser sends session cookies automatically. A malicious site makes an authenticated victim's browser send a legitimate request to TechNova without their intent (change email, shipping address...).

Vulnerable code (form without a token):

<!-- VULNERABLE: the action depends only on the session cookie -->
<form action="/cuenta/cambiar_email.php" method="POST">
  <input type="email" name="email">
  <input type="submit" value="Save">
</form>

Low-impact PoC. An external page with a self-submitting form aimed at TechNova; in the lab we test by changing the email to a fictitious sentinel value ([email protected]) to demonstrate the effect without harming real accounts.

Remediation (secure code):

<?php // SECURE: per-session CSRF token, validated on the server
$token = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $token; ?>
<form action="/cuenta/cambiar_email.php" method="POST">
  <input type="hidden" name="csrf" value="<?php echo $token; ?>">
  <input type="email" name="email">
</form>
<?php // On processing: constant-time comparison
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(403); exit('CSRF');
}

Defenses: a per-session anti-CSRF token, SameSite=Lax/Strict cookies, and re-authentication for sensitive actions.

  1. File inclusion: LFI and RFI

Mechanism. This occurs when a file path is built from user input. LFI (Local File Inclusion) includes files from the server itself; RFI (Remote File Inclusion) includes remote files (more serious: code execution).

Vulnerable code:

<?php $page = $_GET['page']; include($page . ".php"); ?>

Low-impact PoC. Read a non-sensitive but probative file with directory traversal:

http://tienda.technova.lab/index.php?page=../../../../etc/passwd%00

If lines from /etc/passwd appear (system usernames, not secrets), the LFI is confirmed. We don't read keys or private files: /etc/passwd is enough as proof.

Remediation (secure code):

<?php // SECURE: allowlist; input never forms the path directly
$allowed = ['home' => 'home.php', 'help' => 'help.php'];
$page = $allowed[$_GET['page']] ?? 'home.php';
include(__DIR__ . '/pages/' . $page);
?>

Defenses: an allowlist of values, basename(), disabling allow_url_include (kills RFI), and never building paths from raw input.

  1. Insecure file upload

Mechanism. If the application accepts files without validating type or destination, the attacker uploads an executable script (for example a .php) to a served folder and then invokes it: remote code execution.

Vulnerable code:

<?php // VULNERABLE: saves anything to a public, executable directory
move_uploaded_file($_FILES['f']['tmp_name'], "uploads/" . $_FILES['f']['name']);
?>

Low-impact PoC. Upload a harmless .php that only proves execution, not an operational web shell:

<?php echo "poc-technova: " . php_uname(); ?>

Visiting uploads/poc.php and seeing poc-technova with the kernel proves execution. We don't upload a full web shell or operate with it: the sentinel test is enough.

Remediation: validate the real type (MIME/magic bytes) against an allowlist, rename with a random name and a controlled extension, store outside the webroot or in non-executing storage, and disable PHP execution in uploads/.

  1. IDOR and broken access control

Mechanism. IDOR (Insecure Direct Object Reference) is accessing other users' objects by changing an identifier, because the server doesn't check ownership. It's the essence of "broken access control."

Vulnerable code:

<?php // VULNERABLE: serves the invoice by id without checking who it belongs to
$id = $_GET['invoice'];
$f = $db->query("SELECT * FROM invoices WHERE id = $id"); // also SQLi...
echo render($f);
?>

Low-impact PoC. Authenticated as a test user (customer01), change invoice=1001 to invoice=1002 and check whether you can see another fictitious user's invoice. Seeing one test invoice belonging to someone else is enough to confirm; no mass data is collected.

Remediation:

<?php // SECURE: ALWAYS filter by the session's owner
$stmt = $db->prepare("SELECT * FROM invoices WHERE id = ? AND user_id = ?");
$stmt->bind_param("ii", $_GET['invoice'], $_SESSION['user_id']);
?>

Defenses: check authorization per object on every access, use indirect or unpredictable references, and deny by default.

  1. SSRF (Server-Side Request Forgery)

Mechanism. The application makes an HTTP request to a URL the user controls. The attacker points that request at the internal network (which they can't reach) or at metadata services, using the server as a proxy.

Vulnerable code:

<?php // VULNERABLE: downloads whatever URL the user asks for
$img = file_get_contents($_GET['url']);
?>

Low-impact PoC. Request an internal lab URL and confirm the server reaches it:

http://tienda.technova.lab/proxy.php?url=http://10.10.10.20:8080/

If content comes back from an internal 10.10.10.0/24 service that's unreachable from outside, the SSRF is confirmed. The test is limited to reading a response, not attacking the internal service.

Remediation: an allowlist of permitted domains/hosts, resolving and validating the destination IP (block private ranges and 169.254.169.254), and disabling redirects and dangerous schemes (file://, gopher://).

  1. Insecure deserialization (overview)

Mechanism (high level). When an application deserializes user-controlled data (serialized PHP/Java/Python objects), an attacker can craft an object that, when reconstructed, triggers code (gadget chains). It's an advanced class; at this level it's enough to recognize the risk pattern: untrusted input → unserialize()/pickle.loads() → possible RCE.

Defensive rule: don't deserialize untrusted data. If you need to exchange data, use formats that carry data only (JSON) with schema validation, sign the payload (HMAC) to detect tampering, and avoid unserialize() on user input.

  1. Detection and tooling

These vulnerabilities are discovered and validated with intercepting proxies and scanners, covered in depth in Module 7:

  • Burp Suite (07-03): intercept, replay (Repeater), and automate requests (Intruder); map the store.
  • OWASP ZAP (07-04): a free alternative with passive/active scanning.
  • sqlmap: responsible SQLi automation (section 2).

From the defensive side, they're detected with a WAF, log review (spikes of SQL errors, ../ in parameters), SAST/DAST testing in the development cycle, and security headers (CSP, X-Content-Type-Options).

  1. Common Mistakes and Tips

  • Dumping the entire database "because you can." It breaks impact control and exposes personal data. Extract only the minimal value that proves the flaw.
  • Using an XSS to steal real session cookies. In the PoC an alert() is enough; stealing other people's sessions is unnecessary and dangerous.
  • Confusing LFI with RFI. RFI (remote file) usually yields direct RCE and is more serious; distinguish them in the report.
  • Uploading an operational web shell. A sentinel file that prints php_uname() already proves execution; a full web shell is excessive impact.
  • Reporting without remediation. Every web finding must come with its secure code. The report's value is in the fix, not in the scare.
  • Running sqlmap with --dump-all or --os-shell without authorization. It's intrusive and can exceed scope. Enumerate structure; dumping and execution require explicit permission.
  • Tip: almost all these classes share a root cause, mixing data with code. Internalize the "separate/escape/validate by context" pattern and you'll recognize them all.

  1. Exercises

Exercise 1. On tienda.technova.lab/producto.php?id=, describe the low-impact PoC sequence to confirm candidate #1's SQLi without extracting customer data, and write the remediated version of the query. Explain why the prepared statement closes the flaw.

Exercise 2. A product's review field stores the text and displays it to every visitor. Classify the type of XSS it represents, propose a harmless test payload to validate it, and give two remediation measures with their code.

Exercise 3. As customer01 you view your invoice at /factura.php?id=1001. Explain how you would check for an IDOR in a controlled way, what minimal evidence you would capture, and how you would fix it on the server.

Solutions

Solution 1. Sequence: (1) id=1' → a SQL error in the response confirms the input reaches the engine; (2) a boolean test id=1 AND 1=1 (product visible) vs. id=1 AND 1=2 (absent) confirms control of the logic; (3) id=-1 UNION SELECT @@version,2,3 shows 5.5.62, an engine value, without touching customers. Remediation: $stmt = $conn->prepare("SELECT name,price,description FROM products WHERE id = ?"); $stmt->bind_param("i",$id);. It closes the flaw because the query and the data travel through separate channels: the ? is a placeholder and the id is always treated as integer data, never as executable SQL, so 1 OR 1=1 is no longer interpreted as code.

Solution 2. It's a stored XSS (the payload is saved in the DB and served to every visitor), the most dangerous because it doesn't require tricking each victim. Harmless test payload: <script>alert(document.domain)</script> in the review; if it pops when the product page opens, it's confirmed (capture it as evidence, stealing nothing). Remediation: (1) escape on output, echo htmlspecialchars($review, ENT_QUOTES, 'UTF-8');; (2) a restrictive CSP header, e.g. Content-Security-Policy: default-src 'self', which prevents injected scripts from running even if text slips through. Complement: HttpOnly cookies.

Solution 3. Authenticated as customer01, change id=1001 to id=1002 (or another nearby value) and observe whether another fictitious user's invoice is displayed; seeing one invoice belonging to someone else is enough to confirm the IDOR (minimal evidence: the request with the altered id and the response showing another holder's data, without collecting more). Server-side fix: always query filtering by the session's owner, ... WHERE id = ? AND user_id = ? with user_id = $_SESSION['user_id'], so that someone else's id returns nothing. Deny by default and check ownership on every access.

Conclusion

We've turned Module 3's candidate number one into a fact: the SQLi in producto.php?id= is real and exploitable, validated with a low-impact PoC that only read the engine version. And we didn't stop there: we've walked the map of TechNova's web surface, reflected and stored XSS, CSRF, LFI/RFI, insecure upload, IDOR/broken access control, SSRF, and deserialization, seeing in each one the mechanism, a test that demonstrates without destroying, and, above all, the secure code that closes it. The thread running through them all: don't mix user data with code, and separate/escape/validate by context.

With the store audited, we turn toward the infrastructure. The next lesson, 04-03 Network Vulnerability Exploitation, comes down from the browser to the cables and the services: the inventory's services with an exploitable CVE, traffic interception (MITM/ARP), attacks on the internal 10.10.10.0/24 network's SMB, and weak configurations, always with their detection (IDS) and their hardening as the counterpart.

© Copyright 2026. All rights reserved