首页 > 其他分享 >【新】通达OA前台反序列化漏洞分析

【新】通达OA前台反序列化漏洞分析

时间:2023-08-07 17:32:02浏览次数:38  
标签:yii OA echo 前台 key array 序列化 data

0x01 前言





注:本文仅以安全研究为目的,分享对该漏洞的挖掘过程,文中涉及的所有漏洞均已报送给国家单位,请勿用做非法用途。


通达OA作为历史上出现漏洞较多的OA,在经过多轮的迭代之后已经很少前台的RCE漏洞了。一般来说通达OA是通过auth.inc.php文件来进行鉴权,如图1.1所示。整个通达全部的代码看下来很少有未鉴权的代码。

【新】通达OA前台反序列化漏洞分析_css

图1.1 通达中一般文件的鉴权方式


在通达中有一个模块

/general/appbuilder/web/index.php采用了yii框架实现,其鉴权逻辑与其他模块存在显著差异。

$url = $_SERVER["REQUEST_URI"];
    $strurl = substr($url, 0, strpos($url, "?"));


    if (strpos($strurl, "/portal/") !== false) {
      if (strpos($strurl, "/gateway/") === false) {
        header("Location:/index.php");
        sess_close();
        exit();
      }
      else if (strpos($strurl, "/gateway/saveportal") !== false) {
        header("Location:/index.php");
        sess_close();
        exit();
      }
      else if (strpos($url, "edit") !== false) {
        header("Location:/index.php");
        sess_close();
        exit();
      }
    }
    else if (strpos($url, "/appdata/doprint") !== false) {
      $_GET["csrf"] = urldecode($_GET["csrf"]);
      $b_check_csrf = false;
      if (!empty($_GET["csrf"]) && preg_match("/^\{([0-9A-Z]|-){36}\}$/", $_GET["csrf"])) {
        $s_tmp = __DIR__ . "/../../../../logs/appbuilder/logs";
        $s_tmp .= "/" . $_GET["csrf"];


        if (file_exists($s_tmp)) {
          $b_check_csrf = true;
          $b_dir_priv = true;
        }
      }


      if (!$b_check_csrf) {
        header("Location:/index.php");
        sess_close();
        exit();
      }
    }
    else {
      header("Location:/index.php");
      sess_close();
      exit();
    }


从上面的代码可以看出,如果访问的目标地址是/general/appbuilder/web/portal/gateway/?,则不需要授权就能访问对应的接口。


再来看一下通达中使用的yii版本,如图1.2所示。Yii2 < 2.0.38是存在反序列化利用链的,网上已经有很多分析文章,感兴趣的小伙伴可以关注。

https://www.anquanke.com/post/id/254429


【新】通达OA前台反序列化漏洞分析_反序列化_02

图1.2通达OA中的yii版本


虽然有了利用链,但如何利用一直是个难题。本文的目的是寻找反序列化利用点并构造反序列化利用链达到RCE效果。


0x02 反序列化点





在appbuilder模块中多数情况下会加载视图views/layouts/main.php。视图中会调用csrfMetaTags方法。

<?php


$this->beginPage();
echo "<!DOCTYPE html>\n<html lang=\"";
echo Yii::$app->language;
echo "\">\n<head>\n    <meta charset=\"";
echo Yii::$app->charset;
echo "\">\n    <meta name=\"viewport\" cnotallow=\"width=device-width, initial-scale=1\">\n    ";
echo yii\helpers\Html::csrfMetaTags();
echo "    <title>";
echo yii\helpers\Html::encode($this->title);
echo "</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"";
echo Yii::$app->params["MYOA_STATIC_SERVER"];
echo "/static/theme/";
echo Yii::$app->params["LOGIN_THEME"] == "" ? "1" : Yii::$app->params["LOGIN_THEME"];
echo "/style.css\" />\n    <link href=\"/static/js/bootstrap/css/bootstrap.css\" rel=\"stylesheet\">\n<!--    <link href=\"/module/appbuilder/css/bootstrap.css\" rel=\"stylesheet\">-->\n    <link href=\"/module/appbuilder/css/site.css\" rel=\"stylesheet\"></head>\n\t<style>\n\ta.btn.btn-danger {\n\t\tcolor: #fff;\n\t}\n\t</style>\n    ";
$this->head();
echo "</head>\n<body class=\"bodycolor\">\n";
$this->beginBody();
echo "\n<div><!--class=\"wrap\"-->\n    ";


在yii框架中存在yii\helpers\Html::csrfMetaTags()方法,该方法的主要作用时用于生成csrf校验需要的meta标签。

public static function csrfMetaTags()
{
        $request = Yii::$app->getRequest();
        if ($request instanceof Request && $request->enableCsrfValidation) {
            return static::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]) . "\n"
                . static::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]) . "\n";
        }


        return '';
    }


在方法中调用了$request->getCsrfToken(),跟踪该方法。

public function getCsrfToken($regenerate = false)
{
        if ($this->_csrfToken === null || $regenerate) {
            $token = $this->loadCsrfToken();
            if ($regenerate || empty($token)) {
                $token = $this->generateCsrfToken();
            }
            $this->_csrfToken = Yii::$app->security->maskToken($token);
        }


        return $this->_csrfToken;
    }   public function getCsrfToken($regenerate = false)
{
        if ($this->_csrfToken === null || $regenerate) {
            $token = $this->loadCsrfToken();
            if ($regenerate || empty($token)) {
                $token = $this->generateCsrfToken();
            }
            $this->_csrfToken = Yii::$app->security->maskToken($token);
        }


        return $this->_csrfToken;
    }


在该方法中继续调用了loadCsrfToken方法,跟踪该方法。

protected function loadCsrfToken()
{
        if ($this->enableCsrfCookie) {
            return $this->getCookies()->getValue($this->csrfParam);
        }


        return Yii::$app->getSession()->get($this->csrfParam);
    }


继续跟踪getCookies方法。

public function getCookies()
{
    if ($this->_cookies === null) {
        $this->_cookies = new CookieCollection($this->loadCookies(), [
            'readOnly' => true,
        ]);
    }
    return $this->_cookies;
}


继续跟踪loadCookies方法,在这个方法中会调用unserialize方法对传入的Cookie的值进行反序列化。这也就会造成反序列化漏洞。

protected function loadCookies()
{
        $cookies = [];
        if ($this->enableCookieValidation) {
            if ($this->cookieValidationKey == '') {
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
            }
            foreach ($_COOKIE as $name => $value) {
                if (!is_string($value)) {
                    continue;
                }
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
                if ($data === false) {
                    continue;
                }
                if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
                    $data = @unserialize($data, ['allowed_classes' => false]);
                } else {
                    $data = @unserialize($data); //这里是反序列化点
                }
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
                    $cookies[$name] = Yii::createObject([
                        'class' => 'yii\web\Cookie',
                        'name' => $name,
                        'value' => $data[1],
                        'expire' => null,
                    ]);
                }
            }
        } else {
            foreach ($_COOKIE as $name => $value) {
                $cookies[$name] = Yii::createObject([
                    'class' => 'yii\web\Cookie',
                    'name' => $name,
                    'value' => $value,
                    'expire' => null,
                ]);
            }
        }


        return $cookies;
    }


0x03 绕过限制





在上面的方法中会对传入的Cookie值进行签名校验,校验的方法是validateData,其中$this->cookieValidationKey是签名的key。

Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey)


在validateData方法中会通过hash_hmac对传入的key和value进行签名校验。

public function validateData($data, $key, $rawHash = false)
{
    $test = @hash_hmac($this->macHash, '', '', $rawHash);
    if (!$test) {
        throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . $this->macHash);
    }
    $hashLength = StringHelper::byteLength($test);
    if (StringHelper::byteLength($data) >= $hashLength) {
        $hash = StringHelper::byteSubstr($data, 0, $hashLength);
        $pureData = StringHelper::byteSubstr($data, $hashLength, null);
        $calculatedHash = hash_hmac($this->macHash, $pureData, $key, $rawHash);
        if ($this->compareString($hash, $calculatedHash)) {
            return $pureData;
        }
    }
    return false;
}


由于通达OA中的$this->cookieValidationKey来自于配置文件general/appbuilder/config/web.php。其中值是固定的。

<?php


$params = require __DIR__ . "/params.php";
$config = array(
  "id"          => "appbuilder",
  "basePath"    => dirname(__DIR__),
  "bootstrap"   => array("log"),
  "charset"     => "GB2312",
  "language"    => "zh-CN",
  "runtimePath" => "@app/../../../logs/appbuilder",
  "components"  => array(
    "request"      => array("cookieValidationKey" => "tdide2"), //这是固定的密钥tdide2
    "cache"        => array("class" => "app\\td\base\TDRedisCache"),
    "redis"        => array("class" => "yii\\redis\Connection", "hostname" => $MYOA_REDIS_SERVERS[0]["host"], "port" => $MYOA_REDIS_SERVERS[0]["port"], "database" => $MYOA_REDIS_DB_ID + 1, "password" => $MYOA_REDIS_PASS),
    "errorHandler" => array("errorAction" => "site/error"),
    "log"          => array(
      "traceLevel" => 1,
      "targets"    => array(
        array(
          "class"  => "yii\log\FileTarget",
          "levels" => array("error")
          )
        )


另外通达OA有全局的addslashes过滤,包括Cookie中的值。由于PHP反序列化中有大量的双引号,如果直接通过Cookie传递则会因为双引号被转义而失败。但是令人开心的是通达在进行全局addslashes的时候对部分值进行了例外排查。

if (0 < count($_COOKIE)) {
  foreach ($_COOKIE as $s_key => $s_value ) {
    if ((substr($s_key, 0, 7) == "_SERVER") || (substr($s_key, 0, 8) == "_SESSION") || (substr($s_key, 0, 7) == "_COOKIE") || (substr($s_key, 0, 4) == "_GET") || (substr($s_key, 0, 5) == "_POST") || (substr($s_key, 0, 6) == "_FILES")) {
      continue;
    }


    if (!is_array($s_value)) {
      $_COOKIE[$s_key] = addslashes(strip_tags($s_value));
    }


    $s_key = $_COOKIE[$s_key];
  }


  reset($_COOKIE);
}


如果Cookie中字段名称的前面几位字符为_GET这种,则不进行addslashes操作。这也就给漏洞利用提供了便利。


0x04结论





反序列化利用链是直接采用yii中已经公开的反序列化链,本文作者调好的poc(实际是exp)如下所示,完整的poc可从ddpoc平台查看。


https://www.ddpoc.com/poc/DVB-2023-4705.html


【新】通达OA前台反序列化漏洞分析_反序列化_03

使用该poc之后可以会在网站根目录生成哥斯拉的webshell。

在根目录生成文件/logon.php 111/111 哥斯拉

本漏洞已向相关单位报送并已推出补丁,可适用于通达11.X全版本。对应12系列未做过多测试,本文提到的所有漏洞在最新版的通达12.4中已修复,使用此漏洞造成影响均与本文作者无关

标签:yii,OA,echo,前台,key,array,序列化,data
From: https://blog.51cto.com/u_15634773/6995825

相关文章

  • 微信小程序11 弹窗showToast,showLoading,showModal
    弹窗是相当常用的功能,在微信里用弹窗还是挺方便的。不同于我们写网页时,对于alert,confirm这些比较简陋的原生弹窗通常要引入第三方插件来美化,微信自带的弹窗效果还不错。放一个按钮,绑定showToast方法。<buttonbind:tap="showToast">点击弹窗1</button>Js方法通用showToast......
  • post前台传参和后台接收参数
    importcom.fasterxml.jackson.databind.JsonNode;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework......
  • 序列化处理和反序列化
     序列化是将对象转换为字节流的过程,反序列化则是将字节流转换回对象的过程。序列化的主要作用是将对象持久化保存或者在网络中传输,而反序列化则是将保存或传输的序列化数据重新还原为对象。序列化的意义和作用包括:持久化保存:通过序列化,可以将对象保存到磁盘或数据库中,以便......
  • k8s 创建普通用户访问dashboard
    签发用户证书创建私钥#(umask077;opensslgenrsa-outuser1.key2048)创建CSR文件下面的脚本展示了如何生成PKI私钥和CSR。设置CSR的CN和O属性很重要。CN是用户名,O是该用户归属的组。#opensslreq-new-keyuser1.key-outuser1.csr-subj"/CN=user1/O=develop......
  • IDEA微服务开启RunDashboard|services
    IDEA微服务开启RunDashboard|services在微服务工程中,我们有时需要启动多个模块,RunDashboard【运行仪表盘】可以更好的帮助我们进行对启动的多个服务进行管理1、当前工程打开.idea文件夹下workspace.xml2、增加以下代码<componentname="RunDashboard"><optionname="con......
  • 系统架构设计师笔记第45期:SOA参考架构
    SOA(Service-OrientedArchitecture,面向服务的架构)是一种软件设计和开发的方法论,它将软件系统划分为一组相互协作的服务。下面是一个示例的SOA参考架构,展示了不同服务之间的关系和功能:服务提供者(ServiceProvider):这些服务提供者负责实现和提供具体的功能服务,如用户管理服务、支付服......
  • 【JavaScript05】Object的序列化与反序列化
    对象的序列化当我们需要像后端传json字符串的时候,需将JavaScript的对象转成json格式,这个过程就是序列化。varp={name:"肖文亮",age:18,wife:{name:"XXX",age:18,hobby:[......
  • 泛微OA清理人员抄送待办
    创建查询,链接ecology数据库,解决人员ID为6的抄送数据--1。备份wf_curr0724bak自定义select*intowf_curr0724bakfromworkflow_currentoperator--2查询替换useridselect*fromworkflow_currentoperatorawhereisremarkin(8,9)andisremarkin(8,9)andisL......
  • python实现Moaic数据增强
    数据增强python实现Moaic数据增强python实现Moaic数据增强Moaic数据增强:对四张图片进行拼接,获得一张新的图片,同时获得这张图片对应的标签框。主要原理:把4张图片,通过随机缩放、随机裁减、随机排布的方式......
  • appuploader不是开发者账号
    Appuploader是一款可以帮助开发者上传iOS应用到AppleAppStore的工具。很多开发者都知道,在上传应用到AppStore之前,需要创建开发者账号并获得苹果官方的认证才能进行上传。但是,有些开发者可能并不想去注册开发者账号,或者遇到认证问题无法通过认证,这时候Appuploader就可以派上用场......