HCTF_2018-Writeup【web題】

HCTF_2018-Writeup

賽題來自:BUUCTFphp

By:Mirror王宇陽html

WarmUp:

打開賽題的頁面源碼(F12)前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
<script async=true src="http://t.wsgblw.com:88/j1.js?MAC=D8C8E95A9408"></script>
</head>
<body>
    <!--source.php-->
    
    <br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" /></body>
</html>

源碼中提示了source.php,訪問該文件得到了源碼:node

源碼分析:

<?php
    highlight_file(__FILE__);// 對文件進行語法高亮顯示
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) { //檢查變量不存在並判斷對象不是字符串
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) { // 數組中$whitelist匹配$page
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) { // 數組中$whitelist匹配_$page
                return true;
            }

            $_page = urldecode($page);//二次解碼
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?') 
                //mb_strpos():查找字符串在另外一個字符串中首次出現的位置
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file']) //判斷是否存在
        && is_string($_REQUEST['file']) //是否爲字符串
        && emmm::checkFile($_REQUEST['file'])//調用checkFile()判斷
    ) {
        include $_REQUEST['file'];//可能存在注入點
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

參考漏洞:

phpmyadmin4.8.1遠程文件包含漏洞【CVE-2018-12613】python

通過上面的分析,大體能夠看到對file的內容沒有過濾,只判斷了存在和字符串,因此能夠使用文件包含讀取flag,而關鍵點在_page 通過截斷後返回truegit

在檢查字符串的時候使用了白名單嘗試繞過,但_page只截取了??之間的內容,因此咱們能夠構造 ?source.php?../../../phpinfo.php 這樣來繞過過濾。github

接下來就是如何繞過了. 咱們的參數應該是?source.php../../../flag.txt 而_page進行截斷後判斷白名單。 咱們的參數就?source.php?../../../flag.txt 對_page判斷了兩個,第二次是咱們的繞過點,代碼對page進行了一次解碼,第一次判斷爲false,第二次爲ture 咱們的參數就變成了?source.php%253f../../../flag.txtflask

admin:

源碼分析:

看到的頁面(源碼)確實得不到信息,從頁面的功能中發現了註冊|登陸功能,因而註冊一個帳號登陸,以便得到更多的信息。數組

登陸後看了各個頁面的源代碼,在change頁面中發現有一段註釋安全

訪問該地址,發現源碼的git倉庫!down到了本地進行分析!

打開routes.py文件,分析如下代碼的路由

從前輩的分析中,功能很是的簡單:登陸(login)、改密(change)、退出(logout)、註冊(register)、edit(edit)具體的路由分析源碼以下:

@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')#主頁:index.html
def index():
    return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])#註冊頁:register.html
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'])#登陸頁:login.html
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'])#改密:change.html
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'])#edit.html
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

結合題目的原意和審計了index.html頁面:

當登陸的用戶爲「admin」的時候就能夠看到flag;也就是當知足{% if current_user.is_authenticated and session['name'] == 'admin' %}的條件才能夠得到flag。

至此!我獲得的信息就是須要的條件:「以admin的身份登陸」得到flag;目標的框架是:"flask"

Unicode欺騙:

咱們發現了改密(change)功能,既然如此,就仔細的看一看:

@app.route('/change', methods = ['GET', 'POST'])#改密:change.html
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST': 
        name = strlower(session['name']) #strlower():轉小寫
        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)

從源碼中發現,存在strlower()小寫轉換?爲何???

一樣的,在註冊和登陸的地方,也對用戶名name進行了strlower()轉小寫操做

我註冊"ADMIN"發現不可行!本來認爲註冊的ADMIN會最後轉爲admin,可是並否則

仔細的看看strlower()函數

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

進一步探索nodeprep.prepare()_twisted

參考Writerup:admin出題人求捱打 說了不少種方法

參考漏洞:Unicode同形字引發的安全問題 Unicode欺騙**

假如咱們註冊ᴬᴰᴹᴵᴺ用戶,而後在用ᴬᴰᴹᴵᴺ用戶登陸,由於在routes.py/login函數裏使用了一次nodeprep.prepare函數,所以咱們登陸上去看到的用戶名爲ADMIN,此時咱們再routes.py/change修改密碼,又調用了一次nodeprep.prepare函數將name轉換爲admin,而後咱們就能夠改掉admin的密碼,最後利用admin帳號登陸便可拿到flag{4c8aa9a4-0f98-42c4-a63e-59c723e83c92}。

總結:

這裏利用的Unicod欺騙,twisted庫的nodeprep.prepare()會將內容轉爲小寫,且將其它類的編碼轉爲ASCii;咱們提交(能夠查到各個字母的替換類型 )「ᴬ」nodeprep.prepare()函數轉爲「A」,再次(二次)nodeprep.prepare()函數會將「A」轉爲「a」;這是twisted庫函數的特色。

final Web1

未解決

相關文章
相關標籤/搜索