LDAP 注入与防御
0x00:介紹
LDAP 全英文:Lightweight Directory Access Protocol,翻譯過來就是輕量級的目錄訪問協議。其實就是訪問目錄,瀏覽目錄。有很多企業存儲一些數據信息,例如部門信息,部門里成員的信息,公司的可用設備信息等,這些信息單獨放在類似于網站的那種數據庫中的話,會顯的有點大材小用,而把它們放在目錄中,文本中最合適。好比在文檔中搜索指定的內容,在目錄中搜索指定的文件一樣。
LDAP 也有自己指定的語法,也可理解為它是一個存儲信息的數據庫,為了搜索方便,很多網站提供了其查詢的接口,和普通的搜索框無異,對于指定的搜索內容,在沒有嚴格過濾的情況下,便可以造成 LDAP 注入。
0x01:語法
了解 LDAP 語法之前,應該先了解下它的目錄樹結構,LDAP 目錄是以樹狀的層次結構來存儲數據的,如下圖所示。
dn 表示一條記錄所處的位置。dc 表示一條記錄所屬的區域。ou 代表一條記錄所屬的組織。cn 表示一條記錄的名字。這些理解起來也很容易,LDAP 就好比我們程序用的關系型數據庫,關系型數據庫我們一般用數據庫名稱、表名、行來定位一條數據記錄,而 LDAP 首先要說明是哪一棵樹 (dc),然后是從樹根到目標所經過的所有分叉 (ou),最后就是目標的名字 (cn)。
了解 LDAP 的目錄結構后,我們來簡單看下語法,也就是用指定的語法在 LDAP 目錄結構中查找指定的數據。
=:等于的作用,例如查找 Name 等于 weiketang 的對象:
(Name=weiketang)&:多條件滿足查詢,例如 Name 等于 weiketang 的,同時性別為男的,這里用 1 表示:
(&(Name=weiketang)(sex=1))|:或查詢,例如 Name 等于 weiketang,或性別為男的:
(!(Name=weiketang)(sex=1))!:非查詢,用來排除對象,例如 Name 不等于 weiketang 的:
(!Name=weiketang)*:星號代表通配符,可表示任何內容,例如查詢 Name 有內容的,相當于不為空,基本上是查詢所有:
(Name=*)以上就是常用的語法,可相互結合使用,例如姓名以 wei 開頭的:(Name=wei),再例如姓名以 wei 開頭的,且家住北京或者上海的:(&(Name=wei)(|(addr=beijing)(addr=shanghai))).
0x02:環境
LDAP 環境不像 SQL 那么多,這里就簡單的搭建一個做示例方便點,下載 OpenLDAP 后進行安裝,默認一直下一步即可,安裝好 OpenLDAP 后,可以使用 LDAP Admin 連接測試一下,然后新建幾條記錄,如下圖。
這里結合上文中說的 LDAP 目錄結構可以看一下,dn 樹根就是 dc=maxcrc,dc=com,命名時一般都是域名來命名的,ou 是組織單元,這里是開發部分,最后具體的記錄數據是 cn,也叫 uid,也就是用戶以及用戶的信息。
我們用 LDAP Admin 來進行一下簡單的搜索,即姓名包含 lu 關鍵字的,如下圖。
可以看到其搜索語句為 (|(uid=lu)(displayName=lu)(cn=lu)(sn=lu)),也就是多個或的意思。
0x03:bwapp
然后我們使用 bwapp 自帶的 ldap 練習平臺來做示例,首先需要填入相應的連接信息,連接的 php 文件是 ldap_connect.php,我們可以簡單看一下文件的內容。
$message = ""; $login = "bee@bwapp.local"; $password = ""; $server = ""; $dn = "DC=bwapp,DC=local";if(isset($_REQUEST["clear"])){// Clears the LDAP settings$_SESSION["ldap"] = array();$message = "<font color=\"green\">Settings cleared successfully!</font>"; }if(isset($_REQUEST["set"]) && isset($_REQUEST["login"]) && isset($_REQUEST["password"]) && isset($_REQUEST["server"]) && isset($_REQUEST["dn"])) { // LDAP connection settings$login = $_REQUEST["login"];$password = $_REQUEST["password"];$server = $_REQUEST["server"];$dn = $_REQUEST["dn"];if($login == "" || $password == "" || $server == "" || $dn == ""){$message = "<font color=\"red\">Please enter all fields!</font>"; }else{ // Connects and binds to the LDAP server$ds = ldap_connect($server);ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);// Sets the LDAP protocol used by the AD serviceldap_set_option($ds, LDAP_OPT_REFERRALS, 0);$r = @ldap_bind($ds, $login, $password);// $r = @ldap_bind($ds, $domain . "\\" . $login, $password);// Pre-Windows 2000 username// $r = @ldap_bind($ds);// Anonymous login. Needs some adjustments in AD!// Debugging// Prints TRUE if the credentials are valid// print_r($r);if(!$r){ $message = "<font color=\"red\">Invalid credentials or invalid server!</font>";}else{$filter = "(cn=*)"; // Checks if the base DN has a valid syntaxif(!($search=@ldap_search($ds, $dn, $filter))){$message = "<font color=\"red\">Base DN invalid syntax!</font>"; }else{// Checks if the base DN is valid$number_returned = ldap_count_entries($ds,$search);if($number_returned == 0){ $message = "<font color=\"red\">Base DN invalid!</font>"; }// If the connection settings are validelse{$_SESSION["ldap"]["login"] = $login;$_SESSION["ldap"]["password"] = $password;$_SESSION["ldap"]["server"] = $server;$_SESSION["ldap"]["dn"] = $dn; // $message = "<font color=\"green\">Valid connection settings!</font>";header("Location: ldapi.php");exit; } }}ldap_close($ds); } }以上是連接 ldap 服務器的核心代碼,其在接收網頁傳來的連接參數后,使用了 ldap_connect 函數進行了連接。我們填入相應的連接參數連接即可,如下圖。
這里的登錄用戶名和密碼是 OpenLDAP 的配置文件中指定的。然后輸入服務器地址和 dn 即可。這里注意的是,如果 dn 中沒有相應的記錄,ldap 連接會報錯提示你 dn 無效。連接成功后,會跳到 ldapi.php 文件,這個文件用來進行查詢,例如輸入關鍵字 lu,查詢結果如下。
而 ldapi 文件處理搜索的時候,是直接將搜索的關鍵字進行查詢的,并沒有進行過濾處理,如果輸入 * 號則可查出所有的用戶信息,如下圖。
我們可以查看下 ldapi.php 文件的內容。以下是核心代碼。
$search_for = $_REQUEST["user"];// The string to find$search_for = ldapi($search_for);$search_field_1 = "givenname";// The LDAP field to search for the string$search_field_2 = "sn";// The LDAP field to search for the string$search_field_3 = "userprincipalname";// The LDAP field to search for the string//$search_field = "userprincipalname";// The LDAP field to search for the string // Filters the LDAP search// $filter = "CN=*";// Searches all the 'Common Names'// $filter = "($search_field=$search_for*)";// Wildcard is *. Remove it if you want an exact match// $filter = "($search_field=$search_for)";// Exact match// $filter = "(objectClass=user)";// Searches all the users// $filter = "(&($search_field=$search_for)(objectClass=user))";// Searches a specific user// $filter = "(&($search_field=$search_for)(objectClass=user)(objectCategory=person))";// Searches a specific user//$filter = "(|($search_field=$search_for))";// Injection!!! $filter = "(|($search_field_1=$search_for)($search_field_2=$search_for)($search_field_3=$search_for))";// Injection!!!// Common LDAP queries// http://www.google.com/support/enterprise/static/gapps/docs/admin/en/gads/admin/ldap.5.4.html// Retrieves only specific attributes$ldap_fields_to_find = array("objectsid", "samaccountname", "userprincipalname", "cn", "displayname");// Searches the LDAP database// $sr = ldap_search($ds, $dn, $filter);$sr = ldap_search($ds, $dn, $filter, $ldap_fields_to_find);程序會通過 request 獲取 user 的內容,然后直接賦給 search_for 變量拼接到了 filter 搜索語句中,所以造成了 ldap 注入,原理和 sql 注入一樣,未過濾而直接拼接。這里可以看到 ldap 中很多注釋的語句,都是不同的查詢語法,便于我們切換語法進行練習。
0x04:防御
程序在收到 user 內容后交給了 ldapi 函數進行處理,我們可以看到 ldapi 函數內容。
function ldapi($data) { switch($_COOKIE["security_level"]){ case "0" : $data = no_check($data); break; case "1" : $data = ldapi_check_1($data);break; case "2" : $data = ldapi_check_1($data); break; default : $data = no_check($data); break; } return $data; }是一個過濾內容的函數,0,1,2 分別代表用不同的函數來處理內容,0 即低,也就是不做任何處理,1 是中級的 2 是高級的,我們根據 ldapi.php 頭部引入的文件可找到這些檢查函數在 function_external.php 文件中。
function no_check($data) { return $data; }no_check 函數未作任何過濾,會直接原封不動的返回內容。
function ldapi_check_1($data) {// Replaces dangerous characters: ( ) = & | * WHITESPACE$input = str_replace("(", "", $data);$input = str_replace(")", "", $input);$input = str_replace("=", "", $input);$input = str_replace("&", "", $input);$input = str_replace("|", "", $input);$input = str_replace("*", "", $input);$input = str_replace(" ", "", $input); return $input; }ldapi_check_1 函數過濾掉了一些 ldap 語法中的特殊符號,這是中級的過濾代碼,也就意味著直接輸入不會返回全部內容。而高級內容也是用的 ldapi_check_1 函數,這時我們輸入查詢結果為空。
可見其防御方法需要過濾掉 ldap 語法中的一些關鍵字符。(、)、=、&、|、*、!等。
0x05:總結
ldap 不是很常見,測試過程中可以通過抓包、查看一些返回信息、提示標識等來判斷是普通的查詢還是 ldap 查詢。ldap 可能內網會多點,但也有很多開了外網的查詢接口,默認的端口是 389,加密的端口是 636,信息搜集的時候也可以關注下。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 公眾號推薦:aFa攻防實驗室
? ? ? ? ? ? ? ? ? ? ? ? ??分享關于信息搜集、Web安全、內網安全、代碼審計、紅藍對抗、Java、Python等方面的東西。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
總結
以上是生活随笔為你收集整理的LDAP 注入与防御的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vs code vue项目保存浏览器自动
- 下一篇: 网络电话显示服务器拒绝,云安全日报201