赛题来自: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库函数的特色。
未解决