1. 周末简单看了看TestLink,觉得它的登录过程还比较有意思,涉及到了常见的开发错误
  2. 因此简单整理下,抛砖引玉
  3. 如果你也有阅读Bug report的习惯,相信会有共鸣

0x00 安装

git clone https://github.com/TestLinkOpenSourceTRMS/testlink-code
git branch -a 
git checkout -b testlink_1_9_20_fixed  origin/testlink_1_9_20_fixed

其后,需要修改两个路径

  • 日志目录 ( $tlCfg->log_path)
  • 上传目录 ( $g_repositoryPath)

最后附上我的环境
● Apache 2.4.39 + PHP 7.4.3nts
● MySQL 5.7.26
● extension ldap enabled(None-Default)

0x01 风险概览

与其说是“代码审计”,不如说是“代码风险评估”。
一套系统的安全水位,从"安装”的时候就可初见端倪

(1)默认密码

成功安装后,TestLink Administrator默认的账号密码,登录后不强制要求更改密码
○ login name: admin
○ password : admin

(2)默认重装漏洞

默认重装漏洞, 安装好之后不会自动删除/install/文件夹,也没有.lock之类的文件来记录“安装状态”,因此即便之前已经安装好了——只要运维没删文件夹,就能重新安装(默认如此)

0x02 基础

这套CMS中, 不涉及路由转换, 直接访问文件即可。
倒是接受参数的形式,可以介绍下:定义了init_args()方法,规定了参数的类型、范围等

  • 一眼看过去,全是String,基本等于没校验嘛。
  • 需要注意151-152行的,urldecode()。常见的二次解码,若参数filter使用顺序不当,极易造成二次编码绕过

0x03 漏洞

(1)开放重定向->XSS

直接参考这个pr即可
vuln OpenRedirect 导致 login.php 中的 XSS 攻击

  1. redirect_url限制不当,导致任意重定向;
  2. 重定向由<SCript>标签实现,XSS filter不当,最终导致XSS;

(2)登录处用户名LDAP注入

LDAP注入不太常见,一般认为危害没有SQL注入大,可参考LDAP_Injection_Prevention_Cheat_Sheet

前置要求:

直接上代码
lib/functions/ldap_api.php#161

攻击者可能通过此漏洞:

  1. 构造模糊查询,如*,耗尽LDAP服务器资源
  2. 更改LDAP查询语句的逻辑,进而间接控制查询结果为True、False
    See:https://cloud.tencent.com/developer/article/1584896

我这里还希望举例说明一种不常见的利用方案:Heavy Query
将用户名改为*,使该查询匹配当前cn下的所有用户,危害跟LDAP中的用户规模有关

发包过去,服务器处理了四十多秒...

(3)第三方认证后的设计缺陷:硬编码

在特定配置下,系统会将所有通过LDAP/Oauth登录的用户数据,在DB做插入,且会设置一个固定的密码。相关代码在:
lib/functions/doAuthorize.php#L137

# lib/functions/doAuthorize.php#L137
    if( $forceUserCreation ) {
      // first & last name are mandatory
      $user->firstName = trim($user->firstName);
      $user->lastName = trim($user->lastName);
      $porsi = explode('@',$user->emailAddress);

      if ($user->firstName == '') {
        $user->firstName = 'DynGen ' . trim($porsi[0]);
      }
      if ($user->lastName == '') {
        $user->lastName = 'DynGen ' . trim($porsi[1]);
      }

      // Anyway, write a password on the DB.
      $fake = md5('the quick brown fox jumps over the lazy dog');//【默认密码!】
      $fake = md5(md5($fake));
      $user->setPassword($fake);  
      $doLogin = ($user->writeToDB($db) == tl::OK);
    }

若满足下面的任意一种条件,即会触发自动添加账号的流程:

  1. 采用LDAP认证且配置开启了ldap_automatic_user_creation。
  2. 采用Oauth认证。这一项,也是需要在config.inc.php里面配置滴。

要进入【添加账户】的流程,首先,要确保$loginExists为False,采用LDAP认证肯定就查不到当前用户嘛...

// Think not using else make things a little bit clear
  // Will Try To Create a New User
  if( FALSE == $loginExists ) {//【condition 1】
    $authCfg = config_get('authentication');
    $forceUserCreation = false;

    $user = new tlUser(); 
    $user->login = $login;
    $user->isActive = true;

    if ($isOauth){//【condition 2'】
      $forceUserCreation = true;
      $user->authentication = 'OAUTH';
      $user->emailAddress = $login;
      $user->firstName = $options->givenName;
      $user->lastName = $options->familyName;

    } else {
      if( $authCfg['ldap_automatic_user_creation'] ) {
        $user->authentication = 'LDAP';  // force for auth_does_password_match
        $check = auth_does_password_match($db,$user,$pwd);

        if( $check->status_ok ) {
          $forceUserCreation = true;//【condition 2''】
          $uf = getUserFieldsFromLDAP($user->login,
                  $authCfg['ldap'][$check->ldap_index]);

          $user->emailAddress = $uf->emailAddress;
          $user->firstName = $uf->firstName;
          $user->lastName = $uf->lastName;
        }  
      }
    }  

    if( $forceUserCreation ) {//【在DB中添加用户+默认密码】

这个例子,不禁让我想起Sam Curry在他那篇《We Hacked Apple for 3 Months...》中的漏洞,(Full Compromise of Apple Distinguished Educators Program via Authentication and Authorization Bypass)[https://samcurry.net/hacking-apple/#vuln1]

只不过那个场景更加明显,是在Form中携带了硬编码的密码。

但漏洞的原理都是一样的,开发者希望在不同的认证间做同步。

0x04 总结&&闲聊

总结

简单总结了以下登录的过程

闲聊

开源项目,很多都是“用爱发电”,,以TestLink为例,从2011年就开始开发了,

十年转瞬烟云,很多代码,连开发者自己都忘了吧。
更别提写代码的时候还可能犯困

(I'm very tired)......

而且LDAP注入的转义函数,开发是定义了的呀,为啥没用上。。。

# lib/functions/ldap_api.php
/**
 * Escapes the LDAP string to disallow injection.
 *
 * @param string $p_string The string to escape.
 * @return string The escaped string.
 */
function ldap_escape_string( $p_string ) 
{
  $t_find = array( '\\', '*', '(', ')', '/', "\x00" );
  $t_replace = array( '\5c', '\2a', '\28', '\29', '\2f', '\00' );

  $t_string = str_replace( $t_find, $t_replace, $p_string );

  return $t_string;
}

所以也是想分享一下体会:刚开始时,大可不必妄自菲薄,哪怕有文化差异(TestLink的界面不怎么符合我审美),慢慢钻进去,不断分析,肯定能找到漏洞的。

0x04 参考资料

点击收藏 | 0 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖