PentesterLab 新手教程(三) :LDAP攻击 – 作者:婷儿小跟班✧

PentesterLab 简介

web for pentester是国外安全研究者开发的的一款渗透测试平台,包含以下主要的漏洞:

Code injection (代码注入)

Commands injection(命令行注入)

XSS(跨站脚本)

SQL injections(sql注入)

File include(文件包含)

LDAP attacks(LDAP攻击)

File Upload(文件上传)

XML attacks(XML攻击)

个人感觉还是不错的,但是国内基本上搜不到教程,官网上的教程确实有点价格不菲,所以在此打算写一个pentesterLab的全套教程,PentesterLab上面的web漏洞感觉比较典型而且比较基础非常适合新手,因为本教程面向新手,所以有些地方别嫌我啰嗦,嘿嘿。

系列教程:

PentesterLab新手教程(一):代码注入

PentesterLab新手教程(二):XML注入

PentesterLab安装

安装方法真是很简单了,官网下载ios镜像,虚拟机里面直接安装就好。

官网地址&下载地址

前言

这篇文章将要介绍的是LDAP attacks,听起来是不是感觉很新奇?我们先简单介绍一下ldap。

LDAP是轻量目录访问协议,英文全称是Lightweight Directory Access Protocol,一般都简称为LDAP。它是基于X.500标准的,但是简单多了并且可以根据需要定制。

LDAP目录以树状的层次结构来存储数据。如果你对自顶向下的DNS树或UNIX文件的目录树比较熟悉,也就很容易掌握LDAP目录树这个概念了。

可以把他和数据库类比,ldap是一个为查询、浏览、搜索而优化的专业分布式数据库,它成树状结构组织数据,就好像Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以ldap天生是用来查询的。

exampl1

后端代码

<?php
  require "../header.php" ; 
  $ld = ldap_connect("localhost") or die("Could not connect to LDAP server");
  ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3); 
  ldap_set_option($ld, LDAP_OPT_REFERRALS, 0);
  if ($ld) {
   if (isset($_GET["username"])) { 
     $user = "uid=".$_GET["username"]."ou=people,dc=pentesterlab,dc=com";
   }
   $lb = @ldap_bind($ld, $user,$_GET["password"]);

    if ($lb) {
       echo "AUTHENTICATED";
    }
    else {
       echo "NOT AUTHENTICATED";
    }
  }
  require "../footer.php" ; 
?>

代码分析

ldap_set_option():

bool ldap_set_option ( resource $link_identifier , int $option , mixed $newval )
获取/设置LDAP选项的值。

$link_identifier
ldap_connect()函数返回的LDAP连接标识符
$option
LDAP选项名称,主要的选项及其数据类型如下:
LDAP_OPT_DEREF(int)
搜索的时候如何处理别名,取值范围如下:LDAP_DEREF_NEVER(0,默认值), LDAP_DEREF_SEARCHING(1), LDAP_DEREF_FINDING(2), LDAP_DEREF_ALWAYS(3)
LDAP_OPT_NETWORK_TIMEOUT(int)
网络超时秒数,LDAP_NO_LIMIT(0,默认值)表示永不超时。
LDAP_OPT_PROTOCOL_VERSION(int)
指定使用的LDAP协议版本,取值范围如下:LDAP_VERSION2(2,默认值), LDAP_VERSION3 (3)。
LDAP_OPT_REFERRALS(bool)
LDAP库是否自动追随LDAP服务器返回的引用,取值范围如下:TRUE(1,默认值), FALSE(0)。
&$retval
接受选项值的变量
$newval
选项的新值

ldap_blind()

bool ldap_bind ( resource $link_identifier [, string $bind_rdn = NULL [, string $bind_password = NULL ]] )
使用指定的RDN($bind_rdn)和密码($bind_password)绑定到LDAP目录。
$link_identifier
    ldap_connect()函数返回的LDAP连接标识符
$bind_rdn
$bind_password
    绑定时使用的RDN(Relative Distinguished Name)和密码。如果未指定则使用匿名认证。
绑定成功返回 TRUE 否则返回 FALSE 。

这里先了解一下ldap的两次绑定认证方法,分为下面五步:

从客户端得到登陆名和密码。注意这里的登陆名和密码一开始并没有被用到。

先匿名绑定到LDAP服务器,如果LDAP服务器没有启用匿名绑定,一般会提供一个默认的用户,用这个用户进行绑定即可。

之前输入的登陆名在这里就有用了,当上一步绑定成功以后,需要执行一个搜索,而filter就是用登陆名来构造,形如: “(|(uid=$login)(mail=$login))” ,这里的login就是登陆名。搜索执行完毕后,需要对结果进行判断,如果只返回一个entry,这个就是包含了该用户信息的entry,可以得到该entry的DN,后面使用。如果返回不止一个或者没有返回,说明用户名输入有误,应该退出验证并返回错误信息。

如果能进行到这一步,说明用相应的用户,而上一步执行时得到了用户信息所在的entry的DN,这里就需要用这个DN和第一步中得到的password重新绑定LDAP服务器。

执行完上一步,验证的主要过程就结束了,如果能成功绑定,那么就说明验证成功,如果不行,则应该返回密码错误的信息。

现在回归上面的代码,可以看到上面的代码其实就是登陆验证。

利用方法

使用用户名和密码连接到LDAP服务器。在这情况下,LDAP不会对您的身份进行验证,因为凭据无效,但是,某些LDAP服务器授权NULL绑定,结合上面的两次绑定方法,我们知道,如果发送空值,则LDAP服务器将会继续绑定连接,并且php代码会认为平局是正确的,所以我们尝试使用空值,poc如下:

http://192.168.199.110/ldap/example1.php?username=&password=可以看还是显示认证失败

那我们把参数全部去掉然后尝试一下:

http://192.168.199.110/ldap/example1.php 页面显示认证成功

exampl2

后端代码

<?php
  require "../header.php" ; 
  $ld = ldap_connect("localhost") or die("Could not connect to LDAP server");
  ldap_set_option($ld, LDAP_OPT_PROTOCOL_VERSION, 3); 
  ldap_set_option($ld, LDAP_OPT_REFERRALS, 0);
  if ($ld) {
   $lb = @ldap_bind($ld, "cn=admin,dc=pentesterlab,dc=com", "pentesterlab");
    if ($lb) {
      $pass = "{MD5}".base64_encode(pack("H*",md5($_GET['password'])));
      $filter = "(&(cn=".$_GET['name'].")(userPassword=".$pass."))";
      if (!($search=@ldap_search($ld, "ou=people,dc=pentesterlab,dc=com", $filter))) {
      echo("Unable to search ldap server<br>");
      echo("msg:'".ldap_error($ld)."'</br>");
    } else {
      $number_returned = ldap_count_entries($ld,$search);
      $info = ldap_get_entries($ld, $search);
      if ($info["count"] < 1) {
         //NOK 
         echo "UNAUTHENTICATED";
      }
      else {
        echo "AUTHENTICATED as";
        echo(" ".htmlentities($info[0]['uid'][0]));
      }    
    }
   }
  }
  require "../footer.php" ; 
?>

代码分析

这段代码很明显就是LDAP注入,在这之前我们先了解一下LDAP查询的基本语法。

查询name为Tom的所有对象:(name=Tom)这里括号强调LDAP语句的开始和结束。

查询name为Tom并且passwd为123的对象:(&(name=Tom)(passwd=123)) 每个条件都在自己的括号里面,整个语句也要括号包裹起来。&表示逻辑与。

查询名字是T开头的所有对象:(name=T*) 通配符*可以表示任何值。

了解完了一些基本的查询,了解一下ldap注入:

LDAP注入攻击和SQL注入攻击相似,因此接下来的想法是利用用户引入的参数生成LDAP查询。一个安全的Web应用在构造和将查询发送给服务器前应该净化用户传入的参数。在有漏洞的环境中,这些参数没有得到合适的过滤,因而攻击者可以注入任意恶意代码。

再回看源代码,需要传入两个参数一个是name另一个是password,password由于在传入后被md5加密,所以基本不能注入,所以这里我们考虑name

LDAP经常使用通配符*字符来匹配任何值。还有需要注意的就是和别的注入相似我们也需要注释掉后面没用的代码,ldap可以使用NULL BYTE(即%00)来注释掉后面的代码。

下面我们就通配符*做一个简单的测试:

username=hacker&password=hacker 得到认证(这是正常的情况)。

username=hack*&password=hacker 得到认证(通配符匹配相同的值)。

username=hacker&password=hac* 没有得到认证(密码可能会被md5、hash加密过)。

利用方法

我们现在聚焦到这句代码上,我们参数就是要注入到这行代码中$filter = “(&(cn=”.$_GET[‘name’].”)(userPassword=”.$pass.”))”;

首先加上正常结束代码 得:name = hacker)

加上一个永真条件 得:name = hacker)(cn=*)

闭合整个ldap语句 得: name = hacker)(cn=*))

注释掉后面没用代码 得:name = hacker)(cn=*))%00

把得到得构造进上面的语句中,得到:$filter = (&(cn=hacker)(cn=*))%00)(userPassword=$pass));

实际上执行就只剩下了这一句$filter = (&(cn=hacker)(cn=*));那这句话就是永真,password不管传什么值都会显示被认证,也就是说你现在可以以任何密码登陆,poc如下:

http://192.168.199.110/ldap/example2.php?name=a*)(cn=*))%00password=123

你还可以使用通配符技巧来查找其他用户:

http://192.168.199.110/ldap/example2.php?name=a*)(cn=*))%00password=123

回显页面如下:

Notice: Undefined index: password in /var/www/ldap/example2.php on line 9 AUTHENTICATED as admin

可以看到你被认证为admin。

另外:在大多数情况下,LDAP注入只能绕过验证和授权检查,想要检索任意数据往往不太可能。

总结:

在LDAP attacks这个模块里面我们结合两个例子,大体给新同学讲了一下LDAP注入,算是一个入门吧。

LDAP注入的思路其实和SQL注入差不多,就是找到代码过滤不严格得地方,或者是过滤不完全的地方,来构造相应的代码,这个代码要做到先和前面组成正确的语句,然后插入自己的代码,最后注释掉系统自己加上的代码。思路都是一样的,最主要的就是要变通。

好了,基本上LDAP入门就是在这样子,如果大佬有什么想法欢迎评论区指出,如果新同学有什么疑问欢迎评论区提问.

*本文作者:婷儿小跟班✧,转载请注明来自FreeBuf.COM

来源:freebuf.com 2018-05-04 13:00:24 by: 婷儿小跟班✧

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论