Ea5ter's Bolg

2018HCTF Web部分赛题分析

字数统计: 1.4k阅读时长: 6 min
2018/11/13 Share

混与躺轮回不止,在感叹自己菜的同时分析了两道题。

Warm up

由hint和link:http://warmup.2018.hctf.io/index.php?file=hint.php:

flag not here, and flag in ffffllllaaaagggg

根据提示 source.php 输入 file=source.php 看到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?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'];
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}

得到 FLAG 的方式只有:

include $_REQUEST[‘file’];

但需要 checkFile 判断为真,发现有截取:

1
2
3
4
5
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);

一开始以为突破口是 url 二次编码

\$_page = urldecode($page);

但无论如何构造,只有出现 hint.php?source.php? 才能使判断为真。

最后利用?截取hint.php,利用/使hint.php?成为一个不存在的目录:

1
http://warmup.2018.hctf.io/?file=hint.php?/../../../../..../ffffllllaaaagggg

因为php会将前面 hint.php? 当成目录,所以要很多 ../ 来跨出目录读到 flag 。

hide and seek

再随便输一个非 admin 的用户名即可进入后台,提示上传 ZIP 文件。上传的zip会被解压并读取。

一开始还以为要上传 php 代码执行什么的,但php代码都被注释掉了。转念传 JavaScript 代码,最后也是无果。

后面看了 WP 才知道,应该上传软链接文件。真是 too young too simple ,甚至连软链接文件都不知道是什么……

简单说一下,Linux 的软链接文件就像 Windows 的快捷方式。通过 ln -s [目标文件] [生成文件] 生成。

而这道题 hide and seek 就是通过读取文件 hide 的信息来 seek flag。接下来是解题流程:

解题流程

首先读取 /proc/self/environ 文件。wait wait 对于我这菜鸡来说这一步实在有些跳跃,为什么一上来就指名道姓要看这个文件?赶紧偷偷看看 /proc 是什么东西……

什么是proc目录

  • 首先,它不是一个真正的文件系统, 而是一个虚拟的文件系统。

  • 其次,proc 文件系统是动态从系统内核读出所需信息的。

  • 最后,proc 存的是系统的信息, 如内存使用情况, cpu使用情况, 进程信息等等这些。

而 proc 下的 self 目录是到当前进程/proc目录的符号链接,通过这个目录可以获取当前运行进程的信息。其中的文件 environ 则是进程环境变量列表。

不用说,当前的运行的进程肯定有这个 Web 应用。所以在 /proc/self/environ 我们就可以发现 uWSGI 配置文件

可这 uWSGI 配置文件又是什么鬼?赶紧再偷偷看看……

uWSGI 配置文件

这是在 python Web 开发时所用到的。它是一个web服务器,实现了WSGI协议、uwsgi协议、http协议等。

总之,在一些 python web 框架部署时需要这么个东西。like this

好的,拿到了配置文件的位置,我们就可以得到更多的信息

然后是 main.py 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# -*- 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)

这段代码告诉了我们两点:

  • 登陆 admin 的账户即可得到 flag
  • 登陆 admin 需要伪造 session

这里告诉了我们 SECREY_KEY 的生成方式 — 通过随机数种子生成的随机数

UUID:通用唯一标识符 ( Universally Unique Identifier ),对于所有的UUID它可以保证在空间和时间上的唯一性,也称为GUID。它的唯一性和一致性特点,使得可以无需注册过程就能够产生一个新的UUID;UUID可以被用作多种用途, 既可以用来短时间内标记一个对象,也可以可靠的辨别网络中的持久性对象。

这里用于生成随机数的函数 uuid.getnode() 则是由 MAC 地址生成的

通过读取 /sys/class/net/eth0/address 可以得到然 MAC 地址,再带入便得到了 SECREY_KEY

最后伪造 session 登入 admin 得到 flag 。

总结

因为比赛时就这两道看的比较多,其他的基本没点开也不好分析。现在看来,错过了那几道质量如此好的赛题,实在有点可惜。

CATALOG
  1. 1. Warm up
  2. 2. hide and seek
    1. 2.1. 解题流程
  3. 3. 总结