2025年陇剑杯决赛-webshell大派送-题解
webshell大派送是2025年陇剑杯决赛的关于WEB安全的Python沙箱逃逸题目。
陇剑杯决赛采用每30分钟一轮更换题目的机制,更换之后无法继续提交原来题目的答案。
比赛过程中这道题目只有杭州安恒的ZhaoWD战队做出来了。
题目分析
题目源代码见app.py。
import sys |
题目代码首先使用init_functions对os、subprocess模块的敏感函数进行替换,加固沙箱环境。然后使用Bottle框架创建了一个监听5555端口的简易WEB应用程序,定义了两个路由,根路由返回欢迎信息,/shell路由对请求query的cmd参数进行检验,校验通过后传入exec函数执行。
为了成功执行命令,需要绕过题目的两个限制:
- 一是单次请求的payload长度限制不超过18个字符
- 二是常见的一些执行命令的函数被替换为disabled。
此外,题目环境不出网,没有办法利用dnslog或者wget命令等流量带外的OOB方法拿到命令执行的结果,需要使用其他办法将命令执行结果显示在请求响应中。
绕过字符长度的限制
代码里默认有一些全局的变量,如sys、app等,可以通过设置这些全局变量的某个属性来实现参数的持久化。
app = Bottle() |
在这篇题解里提到可以逐字符叠加:
注意其中的exec函数,这个是可记忆的,所以只要逐步进行字符叠加,最后打一个内存马即可
按照这种方法可以添加需要执行的命令,但是很不优雅。每次请求除去固定的sys.x+=""(9个字符)仅能写9个字符,假设最终执行的命令长度是200,需要发送23次请求。
Gemini给出了一个可行的办法是通过HTTP请求的其他参数来传递payload,例如请求/shell?cmd=exec(request.query.x)&x=print('this is payload!'),实际的payload在GET请求的x参数,cmd参数负责去调用payload。
原理上可行、但是实际使用需要进行调整,因为exec(request.query.x)的长度是21、超过了18个字符的限制。
调整后的办法是:
- 发送请求
/shell?cmd=sys.x=request,将request对象存入全局变量sys.x; - 发送请求
/shell?cmd=sys.y=sys.x.query&c=print('this is payload!'),将payload写入当前请求的c参数中,同时将请求的GET参数写入系统的sys.y变量; - 发送请求
/shell?cmd=exec(sys.y['c'])执行上一个请求写入的payload。
payload需要第2个请求附加,因为写入sys.y的query在第二个请求时已固定。
绕过disabled函数
绕过命令的长度限制之后可以执行任意长度的命令,但是常见的系统命令均被替换成disabled函数。
这种替换是偏底层的,替换之后再导入对应模块、运行被替换的函数时会提示函数调用被禁用。
绕过的方法是使用importlib重新载入os模块。
import importlib; |
重新载入模块之后模块的各个函数都是干净的,没有被disabled替换。
命令执行回显
我想到的命令执行回显的方式是引入bottle库的response模块,通过HTTP响应头来传递结果。
import importlib; |
我尝试使用response.body直接将命令结果放在响应体中,并没有成功。
命令执行效果演示
命令执行效果如下:
其他信息
使用VSCode调试程序代码
为了在VSCode中调试bottle的app.py代码,需要设置如下调试配置文件.vscode/launch.json:
{ |
同时对代码进行如下两处调整,注释第一行代码、取消第二行代码的注释:
del __builtins__.__dict__['open'] |
Docker环境靶场
不同AI模型对题目漏洞信息的分析
提示词
请分析如下python代码的安全风险,并给出具体的可以实现命令执行的利用代码或请求。
初步分析代码存在如下限制:
1、单次请求传输的cmd参数大小不能超过18;
2、将常见的执行命令的函数替换为disabled
3、命令结果无回显、且不能OOB出网
问了下各个AI大语言模型,Gemini效果要好一些。
一个请求实现RCE并回显命令执行结果
通过request.json可以实现一个请求实现所有的步骤,超出我先前理解的是在GET请求中也可以设置content-type实现请求体的上传。
使用exec(request.json)整个payload长度刚好18,刚好可以完成任务。
解题情况
按照2025年陇剑杯决赛的赛制,每半个小时放两道题目,半小时结束之后进行题目轮换,原来的题目无法继续解答。
按照CTF-Archives的repo,比赛中有一只队伍成功做出了这一题,题目的质量我感觉非常高。
我查了下比赛的流量包,是杭州安恒的ZhaoWD战队在第11轮27分钟的时候解出来的。




