▶문제

▶풀이
문제는 admin 페이지에 접근하는 것 같고...guest의 계정은 주어졌다. 우선 admin page로 들어가보았다.

로그인하라는 페이지가 나왔고 일단 내가 알고 있는 guest 계정으로 로그인 해보았다.

로그인은 되었지만 admin이 아니라는 문구가 나온다. 그렇다면 다시 로그아웃 후 아이디와 비번에 임의의 값을 넣어보았다.
-> 실패했다는 문구와 뭐가 함께 나올 줄 알았는데...아무것도 안나왔다. 혹시 몰라서 개발자 도구를 켜보았는데도 딱히 나온게 없었다...그래서 프록시 서버를 먼저 보려고 로그인 취소를 눌렀는데

이런 화면이 나왔다! view-source를 들어가보니

이런 PHP 코드가 나왔다. 여기서 필요한 부분만 뽑아서 해석해보았다.
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;
}
사용자가 로그인을 하면 "hi 로그인 세션"을 출력한다.
만약에 admin으로 로그인하고 접속한 IP 주소가 172.17.0.* 형식이라면 flag 값을 출력한다. 만약 해당 IP 형식이 아니라면 가상 IP 주소로만 접근할 수 있다는 문구를 출력한다.
-> 즉,
1. admin 계정으로 로그인
2. 172.17.0.* 형식의 가상 IP 주소에서만 접근해야 flag를 획득할 수 있다!
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";
}
}
뭔가 로그인하지 않았을 때 인증 관련한 코드인 것 같은데...해석이 잘 안되어서 이 부분은 검색으로 해결하였다.
if(!$_SESSION['login']){
if(preg_match("/logout=1/",$_SERVER['HTTP_REFERER'])){
header('WWW-Authenticate: Basic realm="Protected Area"');
header('HTTP/1.0 401 Unauthorized');
}
사용자가 이전에 접속했던 페이지의 URL(즉, $_SERVER['HTTP_REFERER'])에 logout=1이 포함되어 있는지 확인한다. 즉, 로그아웃 후 다시 로그인 하려는 경우에 대한 절차이다.
- 로그아웃 후 로그인하려는 경우, 기본 인증을 요청하는 HTTP 헤더를 보낸다. WWW-Authenticate 헤더는 브라우저가 사용자에게 사용자 이름과 비밀번호를 요청하도록 한다.
- 401 Unauthorized 상태 코드를 보내서 인증이 필요함을 브라우저에게 알린다.
if($_SERVER['PHP_AUTH_USER']){
$id = $_SERVER['PHP_AUTH_USER'];
$pw = $_SERVER['PHP_AUTH_PW'];
$pw = md5($pw);
사용자가 인증 창에 입력한 사용자 이름과 비밀번호를 각각 $id와 $pw 변수에 저장한다. 비밀번호는 바로 md5() 함수로 해시하여 안전하게 처리한다.
$db = dbconnect();
$query = "select id from member where id='{$id}' and pw='{$pw}'";
$result = mysqli_fetch_array(mysqli_query($db,$query));
데이터베이스에 연결한 후, 사용자가 입력한 사용자 이름($id)과 해시된 비밀번호($pw)가 데이터베이스에 저장된 정보와 일치하는지 확인하는 SQL 쿼리를 실행한다.
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";
}
}
사용자가 인증에 성공한 경우, 로그인 처리를 완료하고 다른 페이지로 이동시키고 로그인에 실패한 경우 다시 한 번 기본 인증을 요청하고, "Login Fail"이라는 메시지를 출력한다.
먼저, SQLi 필터링 우회를 통해 admin 페이지에 로그인해본다.
※SQL Injection(SQLi)
해커에 의해 조작된 SQL 쿼리문이 데이터베이스에 그대로 전달되어 비정상적 명령을 실행시키는 공격 기법
-> 필터링 우회 방법
- 공백 문자 우회
- 논리 연산자, 비교 연산자 우회
- 함수 우회
- 주석 처리
- 싱글 쿼터(') 우회
여기서 나는 싱글 쿼터(')와 주석 처리를 통해 우회하여 admin page에 접근해보았다.
아이디는 admin'#을 입력하고 비번은 아무렇게나 1234로 입력해보았다.

위와 같이 admin 계정으로 로그인 되었으나 위에서 말한 가상의 IP(172.17.0.*)이 아니다. (나의 IP로 접속함)
이 상태에서 이제 프록시로 가보았다.
데통네 시간에 프록시에 대해 배웠지만 기억이 가물가물해서...개념부터 다시 복습해보았다.
※프록시
요청에 대한 응답 시간을 줄이기 위해 클라이언트와 서버 중간에 proxy 서버를 둔다.
- 클라이언트의 웹 브라우저는 HTTP request 메시지를 cache 서버에 보낸다.
- 만약 cache에 요청한 object가 있으면 cache가 바로 클라이언트에 그 object를 보낸다.(원래의 서버에 접속할 필요가 없다.)
2-1. cache에 요청한 object가 없다면 cache는 원래의 서버에 request 메시지를 보내고 응답을 받아와 client에 전달한다.
줄이기 위해 클라이언트와 서버 중간에 proxy 서버를 둔다.

현재 프록시를 사용할 때 세션 쿠키가 제대로 전달되지 않아서 인증되지 않은 상태로 관리자 페이지에 접근한 것이다.
정리하자면
- PHPSESSID: PHP 웹 애플리케이션에서 사용자의 세션을 식별하기 위해 서버가 생성하는 쿠키이다. 이 세션 쿠키가 없으면, 서버는 사용자가 누구인지 알 수 없고, 로그인된 상태를 유지하지 못한다.
- 따라서 세션 쿠키(PHPSESSID)가 없는 상태에서 관리자(admin) 페이지에 접근하려 하면, 서버는 사용자가 인증되지 않은 상태로 인식하여 접근을 허용하지 않는다. 즉, 로그인되지 않았거나 인증이 만료된 상태로 간주되는 것이다.
따라서 프록시 서버에서 HTTP Header Injection을 통해 PHPSESSID 쿠키를 추가하면 서버는 해당 세션이 인증된 사용자와 연관되어 있다고 판단하고, 관리 페이지에 접근을 허용하게 된다!
이때, 쿠키는 admin의 세션 쿠키 값이다!
※처음에 어떤 쿠키 값을 넣어야할지 고민했는데 아래 코드를 보면
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의 쿠키 값을 넣어야한다는 것을 알 수 있었다...
이제 수행할 공격은 HEADER INJECTION이다. 이 공격에 대해서는 아는게 없어서 따로 개념을 찾아보았다.
※HEADER INJECTION
공격자가 헤더에 개행문자(%0d%0a)를 삽입하여 그뒤에 헤더를 추가하여 공격하는 수동적인 공격방식
이 공격으로 임의의 HTML을 보여주거나 다른 URL로 리다이텍트시킬수 있고, 쿠키를 임의로 생성할 수 있다.
아무튼 HEADER INJECTION을 통해 admin 세션의 쿠키값을 넣어주면 FLAG를 획득할 수 있다.
먼저, 프록시 서버에서 ?page=/admin/라고 입력하면 아래와 같이 Request 패킷이 작성된다.

HTTP 헤더는 %0d%0a(CRLF)를 기준으로 헤더를 구분한다. 위의 페이지의 url 뒤에 %0d%0a를 입력해주면

위와 같이 헤더가 나뉘는 것을 볼 수 있다. 이제 이를 이용해서 헤더를 추가로 삽입할 수 있다. 앞서 admin 세션의 쿠키 값을 넣어야하므로 내가 입력한 아이디 admin'#과 비번인 1234를 base 64로 인코딩한 값을 넣어준다.


위와 같이 인코딩할 수 있다.
※Base64 인코딩할 때, 문자열을 입력하면 그 문자열 자체가 인코딩된다. 이때, 로그인 시스템에서 종종 아이디와 비밀번호를 연결하여 하나의 문자열로 만든 후 처리할 때, 두 값을 구분하기 위해 :와 같은 구분자를 사용한다.
이제 아래와 같이 헤더를 추가해보았다.
?page=/admin/ HTTP/1.1%0d%0aAuthorization: Basic YWRtaW4nIyA6MTIzNA==%0d%0aUser-Agent:
실행을 해보면 다음의 화면이 잠깐 나왔다가 바로 redirect 되버린다. (너무 빨라서 캡처하지 못함...)
왜 그런지 코드를 살펴보니 요청시에 세션이 포함되도록 쿼리를 작성해야한다는 것을 알 수 있었다. 세션에 대한 정보는 쿠키의 PHPSESSID에 저장되는데 잠깐 빨리 지나간 화면에 "Set-Cookie: PHPSESSID=" 이렇게 세션이 나왔다.

이 세션 값을 포함해서 재요청을 해보았다.
/admin/ HTTP/1.1%0d%0aCookie: PHPSESSID=04k73v75qv69l5l31vjkk28bfn%0d%0aUser-Agent:

FLAG를 획득하였다!!!
'2024 SWLUG > WebHacking' 카테고리의 다른 글
5주차_웹해킹 수업 정리 (0) | 2024.11.19 |
---|---|
4주차_웹해킹 수업 정리 (0) | 2024.11.13 |
3주차_웹해킹 수업 정리 (0) | 2024.10.30 |
2주차_웹해킹 수업 실습 (0) | 2024.10.01 |
1주차_웹해킹 수업 정리 (0) | 2024.09.16 |