챌린지/Wargame.kr

bughela - login filtering

하루히즘 2020. 12. 11. 16:57

wargame.kr의 네 번째 문제인 login filtering이다. 차단된 계정에 로그인하는 것이 목적이다.

Start 버튼을 눌러 문제를 확인해보면 다음과 같은 간단한 로그인 폼이 나타난다.

따로 자바스크립트로 필터링하는 부분이 없기 때문에 백엔드 단에서 입력값을 검증하고 있다는 것을 알 수 있다. 개발자 도구로 소스를 확인해보면 주석으로 어떤 계정의 아이디와 비밀번호가 두 쌍 적혀있는데 아마 이 계정이 차단된 계정이라 추측할 수 있다.

해당 아이디와 비밀번호로 로그인을 시도하면 차단되었다는 메시지가 나타난다.

다른 요소를 조사해봐도 별다른 정보를 얻을 수 없었기 때문에 get source 링크를 클릭해서 소스 코드를 분석해보자.

<?php

if (isset($_GET['view-source'])) {
    show_source(__FILE__);
    exit();
}

/*
create table user(
 idx int auto_increment primary key,
 id char(32),
 ps char(32)
);
*/

 if(isset($_POST['id']) && isset($_POST['ps'])){
  include("../lib.php"); # include for auth_code function.

  mysql_connect("localhost","login_filtering","login_filtering_pz");
  mysql_select_db ("login_filtering");
  mysql_query("set names utf8");

  $key = auth_code("login filtering");

  $id = mysql_real_escape_string(trim($_POST['id']));
  $ps = mysql_real_escape_string(trim($_POST['ps']));

  $row=mysql_fetch_array(mysql_query("select * from user where id='$id' and ps=md5('$ps')"));

  if(isset($row['id'])){
   if($id=='guest' || $id=='blueh4g'){
    echo "your account is blocked";
   }else{
    echo "login ok"."<br />";
    echo "Password : ".$key;
   }
  }else{
   echo "wrong..";
  }
 }
?>

주석에서 계정을 저장하는 테이블의 정보를 알려주고 있다. 기본키와 인덱스로 사용되는 int형 idx 컬럼, char형 32글자짜리 id, ps 컬럼으로 구성되어 있다고 하는데 여기서 특별히 어떤 정보를 얻을 수는 없다. 아래쪽의 php코드를 좀 더 확인해보면 mysql 관련 함수들을 이용해 DB에 연결하고 사용자 입력값을 필터링하는 것을 볼 수 있다. 그렇다면 이 mysql_real_escape_string() 함수를 우회해야 하는 걸까? 문제의 난이도로 보면 그건 아닐 것이다. 사실 나도 여기서 저 함수를 우회해야 하는 줄 알고 한참동안 삽질했던 경험이 있는데, 일단 아래를 확인해보자.

$row=mysql_fetch_array(mysql_query("select * from user where id='$id' and ps=md5('$ps')"));

if(isset($row['id'])){
 if($id=='guest' || $id=='blueh4g'){
  echo "your account is blocked";
 }else{
  echo "login ok"."<br />";
  echo "Password : ".$key;
 }
}else{
 echo "wrong..";
}

mysql_fetch_array()는 mysql_query() 수행 결과로 얻은 데이터들의 집합을 받아오는 함수다. 그래서 row 변수에는 이 "select * from user where id='$id' and ps=md5('$ps')" 쿼리 실행 결과가 담겨있는데 만약 실행 결과가 비어있지 않다면 아래의 검증 로직을 수행하게 된다. 그런데 여기서 살펴볼 것은 쿼리 실행 결과인 row['id']가 존재하는지, 즉 사용자가 입력한 아이디와 비밀번호로 조회한 계정 데이터(그 중에서도 아이디)가 존재하는지 확인한 다음 갑자기 현재 php 심볼 테이블의 id 변수로 계정 차단 로직을 수행하고 있다는 것이다.

사용자 입력값으로 쿼리 실행 결과까지 받아왔다면 위처럼 결과 테이블의 id 컬럼을 확인해서 guest나 blueh4g가 있는지 확인하고 로그인을 차단하는게 더 합당하지 않을까? 이 부분이 문제 풀이의 열쇠가 된다. 지금 계정의 로그인 차단에 결정적인 역할을 하는 로직은 if ($id == 'guest' || $id == 'blueh4g') 블록이다. php에서 문자열 비교는 대소문자를 구별하기 때문에 일단 사용자가 입력 폼에 guest나 blueh4g를 입력한 이상 이는 절대로 통과할 수 없다. 하지만 mysql에서는 어떨까?

위의 실행 결과에서 볼 수 있듯이 대소문자를 구별하지 않는다. php에서는 guest와 GUEST를 다른 값으로 보지만 mysql에서는 동일하게 처리한다는 것이다. 그렇다면 이를 어떻게 사용할 수 있을까? 단순하게 아이디 입력폼에 GUEST를 넣어주고 비밀번호 입력폼에 guest를 넣어서 로그인해주면 된다.

이렇게 로그인한다면 login OK라는 메시지와 함께 해시값을 볼 수 있다.

만약 이렇게 대소문자 구문이 없는걸 원하지 않는다면 SQL 쿼리에서 BINARY 키워드를 다음과 같이 사용할 수도 있다.

id가 guest와 GUEST가 일치하지 않아 아무런 결과가 출력되지 않는 것을 볼 수 있다.

 

 

풀고나니 쉽지만 이번 문제는 정말 삽질을 많이 했다. mysql_real_escape_string()을 우회하는 방법같은 SQL Injection으로 푸는 문제인 줄 알고 뭐 인코딩을 맞추니 싱글바이트, 멀티바이트 문자의 차이점을 악용하느니 같은 방법을 찾아보곤 했는데 너무 어려워서 힌트를 보니 mysql에서는 대소문자를 구분하지 않는다는 특징을 이용하는 문제인 걸 알고 엄청 허탈했던 기억이 있다.

너무 허탈해서 카카오톡 오픈채팅방에 얘기했더니 저런 조언을 얻을 수 있었다. 사실 다른 글에서도 얘기했지만 내가 데이터베이스쪽에 지식이 부족해서 이런 일이 발생한 것 같다. 아직 DB, SQL에 대해서 배울 것이 많다는 생각이 든다.

'챌린지 > Wargame.kr' 카테고리의 다른 글

bughela - fly me to the moon  (0) 2020.12.16
bughela - WTF_CODE  (0) 2020.12.14
bughela - QR CODE PUZZLE  (0) 2020.12.09
bughela - flee button  (0) 2020.12.07
bughela - already got  (0) 2020.12.06