某邮件系统后台管理员任意登录分析

某邮件系统后台管理员任意登录分析

0x00 前言

近期拿到了某邮件系统的一套源码,看到是YII框架编写的,本着学习YII框架出发,顺带对该套系统进行了审计。

0x01 YII框架路由初识

​ YII框架支持两种URL构造格式,分别为默认的格式和漂亮格式,漂亮格式使用额外的路径跟在入口脚本名之后,来展现路由和相关参数,默认格式/index.php?r=post/view&id=100 的路由为post/view和参数id为100,使用漂亮格式则简化成/index.php/post/100

image-20210326115135883

​ YII框架使用MVC模式进行开发,如果不了解MVC模式可以参考链接,不再赘述:https://www.yiichina.com/doc/guide/1.1/basics.mvc

​ 想访问API目录下的controller 目录中的abccontroller.php内的public function add()方法,可如下构造

http://127.0.0.1/api/index.php?r=abc/add

0x02 漏洞挖掘-任意登录

在api/controllers/PostController.php中存在一个模拟登陆方法:

public function mockLogin() {
   $language = $this->getParamFromRequest( "language" );
   $name = $this->getParamFromRequest( 'domain' );
   if (isset( $name )  && isset( $language )) {
      $username = "admin";
      if (! in_array( $language, array ("cn", "tw", "en" ) )) {
         $this->returnErrorCode( CommonCode::COMMON_PARAM_ERROR, array ('{0}' => 'language' ) );
      }
      if (! ParameterChecker::checkLength( $name, Constants::DOMAIN_NAME_MAX_LENGTH )) {
         $this->returnErrorCode( CommonCode::COMMON_DOMAIN_LENGTH_WRONG );
      }
      $domain = ServiceFactory::getDomainService()->getDomainByName( PunyCode::encode( strtolower( $name ) ) );
      if ($domain == null) {
         $this->returnErrorCode( CommonCode::COMMON_DOMAIN_IS_NOT_EXISTS );
      }
      $domainName = PunyCode::encode( $name );
      $password = (empty($domain))?"":$domain["po_pwd"];
      $otime = time();
      $sysflag = 'sysmanage';
      $checksum = md5( $sysflag . $username . $domainName . $password. $language . time() . Config::MONI_CHECKSUM_KEY );
      $url = "http://" . ClientUtils::getHttpHost() . "/post/post.php?r=site/analogLogin&sysflag=$sysflag&lan=$language&domain=$domainName&username=$username&checksum=$checksum&otime=$otime";
      header( "location:$url" );
   } else {
      $this->returnErrorCode( CommonCode::COMMON_PARAM_INCOMPLETE );
   }
}

image-20210411141359477

获取参数language和domain确定语言与域,传入 经过校验以后与po_pwdMONI_CHECKSUM_KEY等结合起来构造校验的$checksum,成功进行登录。该接口需要验证,直接构造无法通过验证。

接下来跟到上文中提到的验证的位置,该位置为所有API方法调用时必须校验的方法:

api/classes/ApiController.php

private function checkServerTypeParams() {
   if (! in_array( ClientUtils::getClientIP(), Config::getApiAllowUserIps() )) {
      $this->returnErrorCode( CommonCode::COMMON_ILLEGAL_IP_SOURCE );
   }
   $id = $this->getParamFromRequest( 'id' );
   $time = $this->getParamFromRequest( 'otime' );
   if (! ParameterChecker::checkLength( $time, 20 )) {
      $this->returnErrorCode( CommonCode::COMMON_ILLEGAL_CHECK_PARAM );
   }
   if (! ParameterChecker::checkIsDate( $time )) {
      $this->returnErrorCode( CommonCode::COMMON_ILLEGAL_CHECK_PARAM );
   }
   $checkSum = $this->getParamFromRequest( 'ochecksum' );
   if (! ParameterChecker::checkLength( $checkSum, 32 )) {
      $this->returnErrorCode( CommonCode::COMMON_ILLEGAL_CHECK_PARAM );
   }
   $md5String = md5( $id . $time . Config::API_CHECKSUM_KEY );
   if ($md5String != $checkSum) {
      $this->returnErrorCode( CommonCode::COMMON_ILLEGAL_CHECKSUM );
   }
}

image-20210526154435460

可以看到该接口需要获取IP,并且与可允许的IP进行匹配,如果为同一个IP则进入该方法。 进入该验证方法以后前端传入ID,OTIMEONCHECKSUM进行验证,其中ID为固定ID,为模拟登陆模式,time为日期形式可以是随便一个日期只要符合条件就行,ONCHECKSUM则与ID,OTIMEAPI_CHECKSUM_KEY三者拼接起来的md5校验是否一致,最终校验通过以后验证通过,可以执行后续方法。

如图为ID固定ID:

image-20210411142840006

加密时需要一个API_CHECKSUM_KEY,全局搜索发现该key同样硬件key:

key写死

所以ONCHECKSUM成功可以构造。但是需要进入checkServerTypeParams方法,仍需要验证IP,我们跟一下getApiAllowUserIps,发现可以被绕过:

获取ip

看到了熟悉的获取x-forwarded-for,可以实现伪造,在看$realip的定义,直接固定ip写在代码里:

ip绕过

可以利用插件X-Forwarded-For Header伪造x-forwarded-for

img

结合以上所有点最终能够实现模拟登陆的功能

poc:


http://mail.a.com/api/index.php?r=Mailbox/getPassword

id=cm&otime=2021-03-11&ochecksum=083d71127d5ad99f8907358db2c8320a&language=cn&domain=a.com&mailbox=admin@a.com
  • 发表于 2021-05-26 17:05:02
  • 阅读 ( 7519 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
带头大哥
带头大哥

50 篇文章

站长统计