賽題來自:BUUCTFphp
By:Mirror王宇陽html
打開賽題的頁面源碼(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.txt
flask
看到的頁面(源碼)確實得不到信息,從頁面的功能中發現了註冊|登陸功能,因而註冊一個帳號登陸,以便得到更多的信息。數組
登陸後看了各個頁面的源代碼,在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"
咱們發現了改密(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庫函數的特色。
未解決