参考文章:

[0day]织梦DedeCMS 0click劫持导致的前台rce getshell

环境搭建:

源码下载:

https://www.dedecms.com/download

按照 readme.txt 进行安装:

前置准备:

首先需要注册一个账号,

默认会员功能是关闭的,建议将其开启:

包括会员使用权限也可以关闭,不然后续会很麻烦

http://dedecms:9876/member/index.php

在该页面进行注册

漏洞 1 - 前台 XSS

相关设置

在写文章的过程中,有“隶属栏目”是必选项,但是默认不存在任何栏目,需要管理员在后台添加:

漏洞复现

功能点:/member/article_add.php

标题和内容写入 XSS payload:<img src=x onerror=alert(document.cookie)>

当然,直接在此处编辑的内容都会被转义,前端富文本编辑器会自动将 <编码为 &lt;,需要用 Burp Suite 等工具直接构造 POST 请求,绕过前端编码。发送到服务器的数据已经“变形”了,所以最好是抓包后进行修改:

对 body 处进行写入 payload:

成功发布文章

访问对应的文章:/plus/view.php?aid={id}

可以看到触发了 XSS 漏洞:

漏洞分析

用户登录后,访问 /member/article_add.php 进行发布文章,拦截 POST 请求之后,对数据包中的 body 和 title ,写入 payload:<img src=b onerror=alert('xss')>(payload 构造后续分析),在服务器返回响应的过程中,代码是如何实现的?

uploads/member/article_add.php

在提交 POST 请求时可以注意到有 dopost save 字段

源码中也存在对应的代码

也就是说用户: POST (dopost=save)

-> include(DEDEMEMBER.’/inc/archives_check.php’); 此处是执行过滤的关键点

-> 处理附加表字段 ($dede_addonfields)

跟进:/inc/archives_check.php

重点关注以下四行:

这些都是对用户输入的数据调用**HtmlReplace()**函数进行处理

过滤函数 HtmlReplace()

继续跟进**HtmlReplace()**

uploads/include/helpers/filter.helper.php

$rptype = 0 表示仅替换 html标记

dede_htmlspecialchars()的作用是对字符< > " ' &进行转义

$rptype = 1 表示替换 html标记同时去除连续空白字符

$rptype = 2 表示替换 html标记同时去除所有空白字符

$rptype = -1 表示仅替换 html 危险的标记

rptype = -1 存在很大的安全隐患

  • 未过滤的标签:、、、、 等

  • 未过滤的属性:所有 on* 事件处理器(onerror、onload、ontoggle 等)

  • 未调用 dede_htmlspecialchars(),< > 原样保留

$body 的处理

知道了过滤函数后,需要对用户输入的数据进行分析,判断其如何处理、如何绕过过滤函数。

找到对 $body 参数的处理,同样调用了 HtmlReplace 函数,过滤模式为 仅替换 html 危险的标记

$body = AnalyseHtmlBody($body, $description);这一步是对HTML文本、自动摘要、自动获取缩略图等进行处理,不涉及 XSS 过滤

之后进入:$body = HtmlReplace($body, -1);

在此模式下仅过滤 script/link/meta/ifr/fra,所以 XSS 还是有希望的

然后 $body 被存入数据库(附加表),并且可以携带恶意代码:

最后当我们去访问文章时,plus/view.php?aid={id}该路径从数据库进行读取,在前端进行渲染,触发 XSS 代码。

总结漏洞利用链

  1. 入口:**member/article_add.php (dopost=save)**

用户 POST 提交文章,$body 包含恶意 HTML。需使用 Burp/curl 直接发包,绕过前端编辑器的实体编码转义。

  1. 过滤:**HtmlReplace($body, -1)**

rptype=-1 进入 else 分支,仅做三项处理:

  • 合并空白字符

  • “script” → 全角 “script”

  • 删除 link/meta/ifr/fra 标签

    未调用 dede_htmlspecialchars(),未过滤 /

    Payload:

  • img 不在黑名单

  • onerror 不含 “script”

  • < > 未被转义,原样通过

  1. 存储:**INSERT INTO dede_addonarticle(..., body)**

恶意代码原样写入数据库。

  1. 触发:**plus/view.php?aid={id}**

从数据库读取 body 内容,未做二次编码,直接渲染到 HTML 页面。浏览器解析<img src=x>,加载失败触发 onerror,执行alert(document.cookie)。

存储型 XSS 攻击成功。

漏洞根因: HtmlReplace() 在 rptype=-1 时采用不完善的黑名单策略,且未对 HTML 特殊字符做实体编码,导致大量可执行脚本的 HTML 标签和事件属性未被过滤。

漏洞 2 - 后台 RCE

漏洞复现

来到后台 - 采集 - 采集节点管理 - 增加新节点

其他内容随意填写,编码要选择 UTF8

保存信息

预览网址:

预览网址的域名必须和 DedeCMS 站点一致,路径可以随便写(404 也行),但域名不能错。

比如我的站点是 http://dedecms:9876,那预览网址就填: http://dedecms:9876/test.html

自定义处理接口 要写入 webshell

保存配置并预览:成功上传 shell.php

漏洞分析

通过搜索危险函数确定入口点

在 php 代码的审计中,当然不可能一行一行代码的看过去,所以对危险函数进行搜索至关重要。

那么通过搜索eval(,找到 uploads/include/dedecollection.class.php 文件:

eval() 是 PHP 中最危险的函数之一,能执行任意代码。发现它就要追问:$phpcode 从哪来?用户能控制吗?并且此处的 eval 仅做变量替换,无任何安全过滤

追踪函数RunPHP()

显然参数 $phpcode 对应的是 $sarr[‘f’],继续寻找 $sarr[‘f’] 的来源

$sarr 来自 $tmpLtKeys

继续寻找 $sarr 发现是 artNotes

因为 $sarr[‘function’] 参数还有一个 function,所以 artNotes 也应该提供 function,继续进行搜索function

找到 LoadItemConfig 函数,也就是从 $configString 字符串中解析出来的 function

继续跟进 LoadItemConfig 函数,发现是从数据库里载入了一个节点

现在已经明确:$configString 就是数据库表 #@__co_note 的 itemconfig字段。

追踪谁向数据库写入了 itemconfig

下一步就是去找哪里向数据库写入了 itemconfig

全局搜索谁对 co_note 表的 itemconfig 做了 INSERT 或 UPDATE:

uploads/dede/co_add.php

关键代码:

$GLOBALS["function_".$field] 直接对应用户 POST 提交的表单参数 function_title、function_body 等,没有任何过滤,直接拼入 itemconfig 存入数据库。

用户 POST function_xxx → co_add.php 拼接 → 写入 DB itemconfig → LoadItemConfig 解析 → artNotes[‘function’] → RunPHP() → eval()

从 eval() 一路回溯到用户输入,中间零过滤,这一条链路的分析已经完成。

写入点分析

全局搜索 itemconfig 时搜出了多个写入点,co_edit.php、co_edit_text.php、co_get_corule.php 属于同一个漏洞的不同触发路径

step=5 拼接字符串 $itemconfig,其中直接包含用户提交的 function_ 字段内容

引入类文件

接下来就是 dedecollection.class.php LoadItemConfig()从 $itemconfig 中提取 function 字段,在后续执行eval()。

总结利用链

阶段1 — 恶意代码存储

入口文件:uploads/dede/co_add.php(step=5)

权限检查:CheckPurview('co_AddNote'),需要后台管理员权限,无 CSRF Token 校验。

用户通过 POST 提交表单参数 function_xxx(如 function_title、function_body),在第 170 行被直接拼入 $itemconfig 字符串:

{dede:function}".$GLOBALS["function_".$field]."{/dede:function}

将 $itemconfig 直接写入数据库,零过滤:

$dsql->ExecuteNoneQuery("UPDATE #@__co_note SET itemconfig='$itemconfig' WHERE nid='$nid' ");

同一漏洞的其他写入路径:

  • co_edit.php:编辑采集规则,同样通过 function_xxx 参数写入

  • co_edit_text.php:专家模式,textarea 直接编辑整个 itemconfig 原文写入

  • co_get_corule.php:导入采集规则,从 textarea 粘贴的配置文本中解析写入

阶段2 — 恶意代码触发

触发文件:uploads/dede/co_gather_start_action.php

调用 $co->LoadNote($nid),从数据库读取 itemconfig 字段,进入 dedecollection.class.php 的 LoadItemConfig() 方法,在第 229 行解析出 {dede:function} 标签内容,存入$this->artNotes[$field]['function']。

之后调用 $co->DownUrl(),进入 GetPageFields() 方法,在检测到 function 字段非空后,将其存入 $tmpLtKeys[$k]['f']。

调用 $this->RunPHP($sarr['v'], $sarr['f']),执行:eval($phpcode.";");

漏洞本质

从用户 POST 输入到 eval() 执行,全链路零过滤、零白名单、零沙箱。虽然需要后台管理员权限,但由于无 CSRF 防护,攻击者可构造恶意页面诱导已登录管理员访问,间接注入恶意 PHP 代码,在下次采集执行时触发任意命令执行。

xss+后台rce组合拳 0click 劫持 导致的前台 rce getshell

漏洞复现

随便注册一个账号:

选择发表文章 /member/article_add.php

这里随便填入内容,并进行抓包

对数据包进行修改,在内容处插入 payload:

在后台管理页面中,test555 是插入 payload 的文章

管理员打开文章后弹出弹窗,并且在网站根目录下生成 shell.php

payload 分析

完整 payload:

逐层分析

1. XSS 触发方式

<details open ontoggle=...>

页面渲染时自动触发,不需要用户点击。管理员在后台审核文章时,打开页面即触发 ontoggle 事件执行 JavaScript。

2. 第一阶段 — 创建采集节点

向 co_add.php 发送 step=2&dopost=preview,进入 co_add.php:95 的 else 分支,在 #@__co_note 表中 INSERT 一条新记录,返回的 HTML 中包含 name=‘nid’ value=’…’,用正则提取 nid。

3. 第二阶段 — 注入恶意代码并触发执行

向 co_add.php 发送 step=5,此时:

  • function_body 的内容被拼入 {dede:function}…{/dede:function} 标签

  • 写入数据库 #@__co_note.itemconfig

  • 加载 co_add_step2_test.htm 模板

模板立即执行测试:

$dc->LoadNote($nid);

$dc->TestArt($previewurl); // → DownUrl() → GetPageFields() → RunPHP() → eval()