轉自:http://blog.nsfocus.net/learning-guide-java-serialization-de-serialization-vulnerability-remediation
JAVA序列化和反序列化是啥?
在現有很多的應用當中,需要對某些對象進行序列化,讓它們離開內存空間,入駐物理硬盤,以便可以長期保存,其中最常見的是Web服務器中的Session對象。對象的序列化一般有兩種用途:把對象的字節序列永久地保存到硬盤上,通常存放在一個指定文件中;或者在網絡上傳送對象的字節序列。
而把字節序列恢復爲對象的過程稱爲對象的反序列化。當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據,而且無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換爲字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復爲Java對象。
其實,在不同的計算機語言中,數據結構、對象以及二進制串的表示方式並不相同。對於像Java這種完全面向對象的語言,程序員所操作的一切都是對象,來自於類的實例化。
JAVA序列化和反序列化實例
在Java語言中最接近數據結構的概念,就是 POJO(Plain Old Java Object)或者Javabean。小編更熟悉Java語言,還是以此爲例說明一下序列化和反序列化的實現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public static void main(String[] args) throws Exception { SerializeObject(); //序列化Object對象 Object o = DeserializeObject(); //反序列Object對象 System.out.println(MessageFormat.format("name={0},age={1}, sex={2}", o.getName(),o.getSex(),o.getAge(),o.getHobby())); } /** * MethodName: SerializeObject * Description: 序列化Object對象 * @author Haom * @throws FileNotFoundException * @throws IOException */ private static void SerializeObject() throws FileNotFoundException, IOException { Object object = new Object(); object.setName("haom"); object.setSex("Female"); object.setAge(18); object.setHobby("Taekwondo"); // 對於ObjectOutputStream 對象輸出流,將Object對象存儲到M盤的object.txt文件中,完成對Object對象的序列化操作 ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("M:/object.txt"))); oo.writeObject(object); System.out.println("Object Serialization success!"); oo.close(); } /** * MethodName: DeserializeObject * Description: 反序列Object對象 * @author Haom * @throws Exception * @throws IOException */ private static Object DeserializeObject() throws Exception, IOException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream( new File("M:/object.txt"))); Object object = (Object) ois.readObject(); System.out.println("Object deserialization success!"); return Object; } |
以上代碼說明:序列化Object成功後在M盤生成了一個object.txt文件,而反序列化Object是讀取M盤的Object.txt後生成了一個Object對象。
當然,並不是一個實現了序列化接口的類的所有字段及屬性,都是可以序列化的:
- 如果該類有父類,則分兩種情況來考慮:如果該父類已經實現了可序列化接口,則其父類的相應字段及屬性的處理和該類相同;如果該類的父類沒有實現可序列化接口,則該類的父類所有的字段屬性將不會序列化,並且反序列化時會調用父類的默認構造函數來初始化父類的屬性,而子類卻不調用默認構造函數,而是直接從流中恢復屬性的值。
- 如果該類的某個屬性標識爲static類型的,則該屬性不能序列化。
- 如果該類的某個屬性採用transient關鍵字標識,則該屬性不能序列化。
那麼,在什麼情況下,需要自定義序列化的方式? 先舉個簡單的例子,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public
class
SeriDemo1
implements
Serializable
{
private
String
name;
transient
private
String
password;
//
瞬態,不可序列化狀態,該字段的生命週期僅存於調用者的內存中
public
SeriDemo1()
{
}
public
SeriDemo1(String
name,
String
password)
{
this.name
=
name;
this.password
=
password;
}
//模擬對密碼進行加密
private
String
change(String
password)
{
return
password
+
"minna";
}
//寫入
private
void
writeObject(ObjectOutputStream
outStream)
throws
IOException
{
outStream.defaultWriteObject();
outStream.writeObject(change(password));
}
//讀取
private
void
readObject(ObjectInputStream
inStream)
throws
IOException,
ClassNotFoundException
{
inStream.defaultReadObject();
String
strPassowrd
=
(String)
inStream.readObject();
//模擬對密碼解密
password
=
strPassowrd.substring(0,
strPassowrd.length()
-
5);
}
//返回一個“以文本方式表示”此對象的字符串
public
String
toString()
{
return
"SeriDemo1
[name="
+
name
+
",
password="
+
password
+
"]";
}
//靜態的main
public
static
void
main(String[]
args)
throws
Exception
{
SeriDemo1
demo
=
new
SeriDemo1("haom",
"0123");
ByteArrayOutputStream
buf
=
new
ByteArrayOutputStream();
ObjectOutputStream
out
=
new
ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream
in
=
new
ObjectInputStream(new
ByteArrayInputStream(buf.toByteArray()));
demo
=
(SeriDemo1)
in.readObject();
System.out.println(demo);
}
}
|
如上代碼說明,可以得知,以下情況需要自定義序列化的方式:
-
爲了確保序列化的安全性,可以對於一些敏感信息加密;
-
確保對象的成員變量符合正確的約束條件;
-
確保需要優化序列化的性能。
在序列化選型的過程中,安全性的考慮往往發生在跨局域網訪問的場景。當通訊發生在公司之間或者跨機房的時候,出於安全的考慮,對於跨局域網的訪問往往被限制爲基於HTTP/HTTPS的80和443端口。如果使用的序列化協議沒有兼容而成熟的HTTP傳輸層框架支持,可能會導致以下幾種結果:
-
因爲訪問限制而降低服務可用性。
-
被迫重新實現安全協議而導致實施成本升高。
-
開放更多的防火牆端口和協議訪問,但是是以犧牲安全性爲前提。
反序列化漏洞危害
當應用代碼從用戶接受序列化數據,並試圖反序列化改數據進行下一步處理時,會產生反序列化漏洞,其中最有危害性的就是遠程代碼注入。
這種漏洞產生原因是,java類ObjectInputStream在執行反序列化時,並不會對自身的輸入進行檢查,這就說明惡意攻擊者可能也可以構建特定的輸入,在 ObjectInputStream類反序列化之後會產生非正常結果,利用這一方法就可以實現遠程執行任意代碼。
這個漏洞的嚴重風險在於,即使你的代碼裏沒有使用到Apache Commons Collections裏的類,只要Java應用的Classpath裏有Apache Commons Collections的jar包,都可以遠程代碼執行。
漏洞的根本問題其實並不是Java序列化的問題,而是Apache Commons Collections允許鏈式的任意的類函數反射調用。攻擊者通過允許Java序列化協議的端口,把攻擊代碼上傳到服務器上,再由Apache Commons Collections裏的TransformedMap來執行。
反序列化漏洞補救
現在,Apache Commons Collections在 3.2.2版本中做了一定的安全處理,對這些不安全的Java類的序列化支持增加了開關,默認爲關閉狀態。
涉及的類包括:CloneTransformer,ForClosure, InstantiateFactory, InstantiateTransformer, InvokerTransformer, PrototypeCloneFactory,PrototypeSerializationFactory, WhileClosure。
RedHat發佈JBoss相關產品的解決方案:https://access.redhat.com/solutions/2045023。
嚴格意義說起來,Java相對來說安全性問題比較少,出現的一些問題大部分是利用反射,最終用Runtime.exec(String cmd)函數來執行外部命令的。如果可以禁止JVM執行外部命令,未知漏洞的危害性會大大降低,可以大大提高JVM的安全性。
比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
SecurityManager
originalSecurityManager
=
System.getSecurityManager();
if
(originalSecurityManager
==
null)
{
//
創建自己的SecurityManager
SecurityManager
sm
=
new
SecurityManager()
{
private
void
check(Permission
perm)
{
//
禁止exec
if
(perm
instanceof
java.io.FilePermission)
{
String
actions
=
perm.getActions();
if
(actions
!=
null
&&
actions.contains("execute"))
{
throw
new
SecurityException("execute
denied!");
}
}
//
禁止設置新的SecurityManager
if
(perm
instanceof
java.lang.RuntimePermission)
{
String
name
=
perm.getName();
if
(name
!=
null
&&
name.contains("setSecurityManager"))
{
throw
new
SecurityException(
"System.setSecurityManager
denied!");
}
}
}
@Override
public
void
checkPermission(Permission
perm)
{
check(perm);
}
@Override
public
void
checkPermission(Permission
perm,
Object
context)
{
check(perm);
}
};
System.setSecurityManager(sm);
}
|
如上所示,只要在Java代碼裏簡單加一段程序,就可以禁止執行外部程序了。
禁止JVM執行外部命令,是一個簡單有效的提高JVM安全性的辦法。可以考慮在代碼安全掃描時,加強對Runtime.exec相關代碼的檢測。
小結
本文只是初步進行JAVA序列化和反序列化的科普,讓大家對此問題及相關補救方式有個直觀的印象和簡單瞭解,下一步深入解還請繼續關注綠盟技術博客。使用JAVA反序列化增多了數據的種類,但是還需要儘量避免使用反序列化的交互操作,減少風險的增加。目前,綠盟科技蜂巢社區啓動應急機制,已經實現遠程代碼執行漏洞的在線檢測。在社區中,大家可以進行網絡安全掃描插件的開發及討論。
其他相關參考資料:
Java反序列化漏洞被忽略的大規模殺傷利用