千家信息网

如何进行Confluence未授权RCE的漏洞分析

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章将为大家详细讲解有关如何进行Confluence未授权RCE的漏洞分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。看到官方发布了预警,于是开
千家信息网最后更新 2026年11月01日如何进行Confluence未授权RCE的漏洞分析

这篇文章将为大家详细讲解有关如何进行Confluence未授权RCE的漏洞分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

看到官方发布了预警,于是开始了漏洞应急。漏洞描述中指出Confluence Server与Confluence数据中心中的Widget连接器存在服务端模板注入漏洞,攻击者能利用此漏洞能够实现目录穿越与远程代码执行。

确认漏洞点是Widget Connector,下载最新版的比对补丁,发现在com\atlassian\confluence\extra\widgetconnector\WidgetMacro.java里面多了一个过滤,这个应该就是这个漏洞最关键的地方。

可以看到

this.sanitizeFields = Collections.unmodifiableList(Arrays.asList(VelocityRenderService.TEMPLATE_PARAM));

而TEMPLATE_PARAM的值就是_template,这个所以就是补丁了过滤传入外部的_template参数。

public interface VelocityRenderService {    public static final String WIDTH_PARAM = "width";    public static final String HEIGHT_PARAM = "height";    public static final String TEMPLATE_PARAM = "_template";

翻了一下Widget Connector里面的文件,发现TEMPLATE_PARAM就是模板文件的路径。

public class FriendFeedRenderer implements WidgetRenderer {    private static final String MATCH_URL = "friendfeed.com";    private static final String PATTERN = "friendfeed.com/(\\w+)/?";    private static final String VELOCITY_TEMPLATE = "com/atlassian/confluence/extra/widgetconnector/templates/simplejscript.vm";    private VelocityRenderService velocityRenderService;......    public String getEmbeddedHtml(String url, Map params) {        params.put(VelocityRenderService.TEMPLATE_PARAM, VELOCITY_TEMPLATE);        return velocityRenderService.render(getEmbedUrl(url), params);    }

加载外部的链接时,会调用相对的模板去渲染,如上,模板的路径一般是写死的,但是也有例外,补丁的作用也说明有人突破了限制,调用了意料之外的模板,从而造成了模板注入。

在了解了补丁和有了一些大概的猜测之后,开始尝试。

首先先找到这个功能,翻了一下官方的文档,找到了这个功能,可以在文档中嵌入一些视频,文档之类的。

看到这个,有点激动了,因为在翻补丁的过程中,发现了几个参数,url,width,height正好对应着这里,那_template的英文不是也。从这里传递进去的?

随便找个的Youtube视频插入试试,点击预览,抓包。

在params中尝试插入_template参数,好吧,没啥反应..

开始调试模式,因为测试插入的是的Youtube视频,所以调用的是com/atlassian/confluence/extra/widgetconnector/video/YoutubeRenderer.class

public class YoutubeRenderer implements WidgetRenderer, WidgetImagePlaceholder {private static final Pattern YOUTUBE_URL_PATTERN = Pattern.compile("https?://(.+\\.)?youtube.com.*(\\?v=([^&]+)).*$");private final PlaceholderService placeholderService;private final String DEFAULT_YOUTUBE_TEMPLATE = "com/atlassian/confluence/extra/widgetconnector/templates/youtube.vm";......public String getEmbedUrl(String url) {Matcher youtubeUrlMatcher = YOUTUBE_URL_PATTERN.matcher(this.verifyEmbeddedPlayerString(url));return youtubeUrlMatcher.matches() ? String.format("//www.youtube.com/embed/%s?wmode=opaque", youtubeUrlMatcher.group(3)) : null;    }public boolean matches(String url) {return YOUTUBE_URL_PATTERN.matcher(this.verifyEmbeddedPlayerString(url)).matches();}private String verifyEmbeddedPlayerString(String url) {return !url.contains("feature=player_embedded&") ? url : url.replace("feature=player_embedded&", "");}public String getEmbeddedHtml(String url, Map params) {return this.velocityRenderService.render(this.getEmbedUrl(url), this.setDefaultParam(params));}

在getEmbeddedHtml下断点,会先调用getEmbedUrl对用户传入的url进行正则匹配,因为我们传入的是个正常的的Youtube视频,所以这里是没有问题的,调用然后setDefaultParam函数对传入的其他参数进行处理。

 private Map setDefaultParam(Map params) {String width = (String)params.get("width");String height = (String)params.get("height");if (!params.containsKey("_template")) {params.put("_template", "com/atlassian/confluence/extra/widgetconnector/templates/youtube.vm");}if (StringUtils.isEmpty(width)) {params.put("width", "400px");} else if (StringUtils.isNumeric(width)) {params.put("width", width.concat("px"));}if (StringUtils.isEmpty(height)) {params.put("height", "300px");} else if (StringUtils.isNumeric(height)) {params.put("height", height.concat("px"));}return params;}

取出width状语从句:height来判断是否为空,为空则设置默认值。的关键_template参数来了,如果外部传入的参数没有_template,则设置默认的的Youtube模板。如果传入了,就使用传入的,也就是说,AAAA是成功的传进来了。

大概翻了一下Widget Connector里面的渲染器,大部分是不能设置_template的,是直接写死了,也有一些例外,如Youtube,Viddler,DailyMotion等,是可以从外部传入_template的。

传递能_template了,接下来看下是如何取模板和渲染模板的。

跟进this.velocityRenderService.render,就是也。com/atlassian/confluence/extra/widgetconnector/services/DefaultVelocityRenderService.class里面的render方法。

 public String render(String url, Map params) {String width = (String)params.get("width");String height = (String)params.get("height");String template = (String)params.get("_template");if (StringUtils.isEmpty(template)) {template = "com/atlassian/confluence/extra/widgetconnector/templates/embed.vm";}if (StringUtils.isEmpty(url)) {return null;} else {Map contextMap = this.getDefaultVelocityContext();Iterator var7 = params.entrySet().iterator();while(var7.hasNext()) {Entry entry = (Entry)var7.next();if (((String)entry.getKey()).contentEquals("tweetHtml")) {contextMap.put(entry.getKey(), entry.getValue());} else {contextMap.put(entry.getKey(), GeneralUtil.htmlEncode((String)entry.getValue()));}}contextMap.put("urlHtml", GeneralUtil.htmlEncode(url));if (StringUtils.isNotEmpty(width)) {contextMap.put("width", GeneralUtil.htmlEncode(width));} else {contextMap.put("width", "400");}if (StringUtils.isNotEmpty(height)) {contextMap.put("height", GeneralUtil.htmlEncode(height));} else {contextMap.put("height", "300");}return this.getRenderedTemplate(template, contextMap);}}

_template取出来赋值给template,其他传递进来的参数取出来经过判断之后放入到contextMap,调用getRenderedTemplate函数,也就是调用VelocityUtils.getRenderedTemplate。

  protected String getRenderedTemplate(String template, Map contextMap){return VelocityUtils.getRenderedTemplate(template, contextMap);}

一路调用,调用链如下图,来到求最后/com/atlassian/confluence/util/velocity/ConfigurableResourceManager.class的loadResource函数,来获取模板。

这里调用了4个ResourceLoader去取模板。

com.atlassian.confluence.setup.velocity.HibernateResourceLoaderorg.apache.velocity.runtime.resource.loader.FileResourceLoaderorg.apache.velocity.runtime.resource.loader.ClasspathResourceLoadercom.atlassian.confluence.setup.velocity.DynamicPluginResourceLoader

这里主要看下速度的自带FileResourceLoader状语从句:ClasspathResourceLoader

FileResourceLoader用户会对传入的模板路径使用normalizePath函数进行校验


可以看到,过滤了/../,这样就导致没有办法跳目录了。

过滤路径后调用findTemplate查找模板,可看到,会拼接一个固定的path,这是合流的安装路径。

现在也就是说可以利用FileResourceLoader来读取合流目录下面的文件了。

读取尝试/WEB-INF/web.xml文件,可以看到,是成功的加载到了该文件。

但是这个无法跳出合流的目录,因为不能用/../。

再来看下ClasspathResourceLoader

public InputStream getResourceStream(String name) throws ResourceNotFoundException {
InputStream result = null;
if (StringUtils.isEmpty(name)) {
throw new ResourceNotFoundException("No template name provided");
} else {
try {
result = ClassUtils.getResourceAsStream(this.getClass(), name);
......
}

跟进ClassUtils.getResourceAsStream

  public static InputStream getResourceAsStream(Class claz, String name) {while(name.startsWith("/")) {name = name.substring(1);}ClassLoader classLoader = Thread.currentThread().getContextClassLoader();InputStream result;if (classLoader == null) {classLoader = claz.getClassLoader();result = classLoader.getResourceAsStream(name);} else {result = classLoader.getResourceAsStream(name);if (result == null) {classLoader = claz.getClassLoader();if (classLoader != null) {result = classLoader.getResourceAsStream(name);}}}return result;}

会跳到/org/apache/catalina/loader/WebappClassLoaderBase.class

跟进,发现会拼接/WEB-INF/classes,其中而且也是调用了normalize对传入的路径进行过滤..

还是这里可以用../跳一级目录。

尝试读取一下../web.xml,可以看到,也是可以读取成功的,但是仍然无法跳出目录。

我这里测试用的版本是6.14.1,而后尝试了file://,http://,https://都没有成功。后来我尝试把曲奇删掉,发现还是可以读取文件,确认了这个漏洞不需要权限,但是跳不出目录。应急就在这里卡住了。

而后的几天,大佬有说用file://协议可以跳出目录限制,我惊了,我确定当时是已经试过了,没有成功的。看了大佬的截图,发现用的是6.9.0的版本,我下载了,尝试了一下,发现真的可以。

还是问题在ClasspathResourceLoader上面,步骤和之前的是一样的,到断/org/apache/catalina/loader/WebappClassLoaderBase.class的getResourceAsStream方法

拼接前面/WEB-INF/classes电子杂志失败后,继续往下进行。

跟进findResource,函数前面仍然获取失败

关键的地方就在这里,会调用super.findResource(name),这里返回了网址,也就是能获取到对象。

不仅如此,这里还可以使用其他协议(HTTPS,FTP等)获取远程的对象,意味着可以加载远程的对象。

获取到URL对象之后,继续回到之前的getResourceAsStream,可以看到,当返回的URL不为空时,

调用会url.openStream()电子杂志数据。

最终获取到数据给速度渲染。

尝试一下
至于6.14.1为啥不行,赶着应急,后续会跟,如果有新的发现,会同步上来,只目前看到ClassLoader不一样。

6.14.1
6.9.0

这两个装载机的关系如下

现在可以加载本地和远程模板了,可以尝试进行RCE。

关于速度的RCE,基本上有效载荷都来源于15年入侵者的服务端模板注入的议题,但是在合流上用不了,因为在调用方法的时候会经过velocity-htmlsafe-1.5.1.jar,里面多了一些过滤和限制。但是仍然可以利用反射来执行命令。

用python -m pyftpdlib -p 2121开启一个简单的FTP服务器,将有效载荷保存成rce.vm,保存在当前目录。

将_template设置分类中翻译ftp://localhost:2121/rce.vm,发送,成功执行命令。

对于命令回显,同样可以使用反射构造出有效载荷,执行ipconfig的查询查询结果。

漏洞影响

根据ZoomEye网络空间搜索引擎对关键字"X-Confluence"进行搜索,共得到61,856条结果,主要分布美国,德国,中国等国家。

全球分布(非漏洞影响范围)


中国分布(非漏洞影响范围)

漏洞检测

2019年4月4日,404实验室公布了该漏洞的检测PoC,可以利用这个PoC检测Confluence是否受该漏洞影响。

关于如何进行Confluence未授权RCE的漏洞分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

漏洞 模板 目录 尝试 参数 成功 文件 路径 函数 就是 补丁 也就是 关键 对象 视频 合流 影响 有效 命令 数据 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 南京欣网互联网络科技有限公司 云信互联网科技有限公司 科技股互联网 网络科技有限互联网 金融科技与互联网金融 中国互联网科技发展 凡科互联网科技 天扬科技互联网广告 中盈科技互联网信息有限公司 深圳前海聚择互联网科技有限公司 阿里巴巴算科技企业还是互联网 重庆互联网医院沙龙医盟信息科技 语蝶互联网科技有限公司 哈尔滨屹滨互联网科技有限公司 西安小鹰速贷互联网科技有限公司 互联网金融科技元年 怎样加入科技互联网 上海互联网科技公司ppt 广西科技产业互联网是什么 互联网金融运营模式中的金融科技 互联网科技有限公司 互联网科技企业 西安纸贵互联网科技有限公司 与互联网科技公司 安控科技rtu产品工业互联网 互联网科技公司需要的职位 互联网科技博主是什么意思 企晟互联网信息科技有限公司 江苏互联网科技小贷 上海互联网科技园景观
0