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

0x01 前言

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

通达OA作为历史上出现漏洞较多的OA,在经过多轮的迭代之后已经很少前台的RCE漏洞了。一般来说通达OA是通过auth.inc.php文件来进行鉴权,如图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

虽然有了利用链,但如何利用一直是个难题。本文的目的是寻找反序列化利用点并构造反序列化利用链达到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\" content=\"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

图片

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/65623.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis实战案例27-UV统计

1. Redis的HyperLogLog的统计功能 示例&#xff1a; 表明HyperLogLog不管加入重复元素多少次都不会让count&#xff0c;不会计数重复元素&#xff0c;所以适合做UV计数 2. 简单实现UV测试 通过单元测试&#xff0c;向 HyperLogLog 中添加 100 万条数据&#xff0c;看看内存占…

RocketMQ使用

说明&#xff1a;本文介绍RocketMQ的消费模式&消息类型&#xff0c;RocketMQ的安装参考及简单使用&#xff0c;参考&#xff1a;http://t.csdn.cn/BKFPj 消费模式 RocketMQ与RabbitMQ最大的区别在于&#xff0c;RocketMQ是根据消息的Topic锁定消费者的&#xff0c;Topic属…

黑马机器学习day1

1.sklearn数据集 sklearn.datasets datasets.load_*() 获取小规模的数据集 datasets.fetch_*(data_homeNone) 获取大规模数据集 函数的第一个参数是data_home,标识数据集下载目录&#xff0c;默认/scikit_learn_data/ 1.1sklearn小数据集 sklearn.da…

【JAVA】类和对象

作者主页&#xff1a;paper jie的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVASE语法系列》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精…

使用eXosip+ffmpeg、ffplay命令行实现sip客户端

文章目录 前言一、关键实现1、主要流程2、解决端口冲突&#xff08;1&#xff09;、出现原因&#xff08;2&#xff09;、解决方法 3、解析sdp&#xff08;1&#xff09;、定义实体&#xff08;2&#xff09;、解析视频&#xff08;3&#xff09;、解析音频 4、命令行推拉流&am…

Python中搭建IP代理池的妙招

在Python的爬虫世界里&#xff0c;你是否也想搭建一个功能强大的IP代理池&#xff0c;让你的爬虫无忧无虑地畅游各大网站&#xff1f;今天&#xff0c;我就来教你使用Scrapy框架搭建IP代理池&#xff0c;让你的爬虫更加智能、高效&#xff01;跟着我一步一步来&#xff0c;轻松…

(力扣)用两个队列实现栈---C语言

分享一首歌曲吧&#xff0c;希望在枯燥的刷题生活中带给你希望和勇气&#xff0c;加油&#xff01; 题目&#xff1a; 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#…

【单片机】51单片机,TLC2543,驱动程序,读取adc

TLC2543 是一款 12 位精密模数转换器 (ADC)。 1~9、11、12——AIN0&#xff5e;AIN10为模拟输入端&#xff1b; 15——CS 为片选端&#xff1b; 17——DIN 为串行数据输入端&#xff1b;&#xff08;控制字输入端&#xff0c;用于选择转换及输出数据格式&#xff09; 16——…

大数据课程G2——Hbase的基本架构

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握Hbase的基本架构; ⚪ 掌握Hbase的读写流程; ⚪ 掌握Hbase的设计与优化; 一、基本架构 1. HRegion 1. 在HBase中,会将一个表从行键方向上进行切分,切分成1个或者多个HRegion。 …

C++入门(小白篇1—编译器安装-代码注释等)

前言&#xff1a; 最近想学一下一下C看了一些博客内容写的倒是很充实&#xff0c;但是&#xff0c;细节不到位&#xff0c;我是有Python基础的&#xff0c;所以学习来蛮快的&#xff0c;但是对于小白的话&#xff0c;有好多小细节大多数博客还是不够详细&#xff0c;由此我想写…

对话Sam Altman与Greg Brockman:初心和过去,信念和现在,责任和未来

导读 近日&#xff0c;硅谷著名投资人Reid Hoffman和Aria Finger联手对Sam Altman和Greg Brockman进行了一场访谈&#xff0c;访谈涉及到主题有&#xff1a;OpenAI的使命&#xff0c;人工智能对教育、医疗等行业的变革性影响&#xff0c;人工智能如何面对监管&#xff0c;OpenA…

十九、docker学习-Dockerfile

Dockerfile 官网地址 https://docs.docker.com/engine/reference/builder/Dockerfile其实就是我们用来构建Docker镜像的源码&#xff0c;当然这不是所谓的编程源码&#xff0c;而是一些命令的集合&#xff0c;只要理解它的逻辑和语法格式&#xff0c;就可以很容易的编写Docke…

Effective Java笔记(28)列表优于数组

数组与泛型相比&#xff0c;有两个重要的不同点 。 首先&#xff0c;数组是协变的&#xff08; covariant &#xff09; 。 这个词听起来有点吓人&#xff0c;其实只是表示如果 Sub 为 Super 的子类型&#xff0c;那么数组类型 Sub[ ]就是Super[ ]的子类型。 相反&#xff0c;泛…

【maven】构建项目前clean和不clean的区别

其实很简单&#xff0c;但是百度搜了一下&#xff0c;还是没人能简单说明白。 搬用之前做C项目时总结结论&#xff1a; 所以自己在IDE里一遍遍测试程序能否跑通的时候&#xff0c;不需要clean&#xff0c;因为反正还要改嘛。 但是这个项目测试好了&#xff0c;你要打成jar包给…

【Vue3】动态组件

动态组件的基本使用 动态组件&#xff08;Dynamic Components&#xff09;是一种在 Vue 中根据条件或用户输入来动态渲染不同组件的技术。 在 Vue 中使用动态组件&#xff0c;可以使用 元素&#xff0c;并通过 is 特性绑定一个组件的名称或组件对象。通过在父组件中改变 is 特…

AirServer2023最新Mac苹果电脑系统投屏软件

AirServer是一个Mac专用投屏工具&#xff0c;功能强大&#xff0c;并且可以通过网络和其他平台同步视频内容。可以使用多个设备进行投屏&#xff0c;快速查看同一局域网内的视频。支持的设备&#xff1a;苹果系统。支持 Windows、 Mac、 Android、 iOS、 windows平台。通过这款…

C++笔记之两个类的实例之间传递参数——通过构造函数传递类对象的方法详细探究

C笔记之两个类的实例之间传递参数——通过构造函数传递类对象的方法详细探究 code review! 文章目录 C笔记之两个类的实例之间传递参数——通过构造函数传递类对象的方法详细探究1.传递对象的const引用——ClassB的实例只能访问ClassA的实例&#xff0c;但不会修改ClassA的实…

js:Markdown编辑器Vue3版本md-editor-v3

文档 https://github.com/imzbf/md-editor-v3https://imzbf.github.io/md-editor-v3/zh-CN/index 安装 npm install md-editor-v3使用 <template><MdEditor v-model"text" /> </template><script setup> import { ref } from vue; impor…

EFLFK——ELK日志分析系统+kafka+filebeat架构(3)

zookeeperkafka分布式消息队列集群的部署 紧接上期&#xff0c;在ELFK的基础上&#xff0c;添加kafka做数据缓冲 附kafka消息队列 nginx服务器配置filebeat收集日志&#xff1a;192.168.116.40&#xff0c;修改配置将采集到的日志转发给kafka&#xff1b; kafka集群&#xff…

RabbitMQ在CentOS下的安装

RabbitMQ的版本是3.8.2 1.环境配置&#xff1a;CentOs 7.6以上版本&#xff0c;我的版本是7.9&#xff0c;不要对yum换源&#xff0c;否则可能会安装失败。 echo "export LC_ALLen_US.UTF-8" >> /etc/profile source /etc/profile 以上命令&#xff0c;是…