此处用户传入的 RelatedFileName 参数没有进行路径遍历字符的过滤或清洗,导致持有 /api/admin/cms/templates/templatesEditor/actions/settings 接口访问权限的用户,可以构造如 "relatedFileName":"/../../../../../../../../.././etc/cron.d/e2scrub_all" 进行传参,请求的中 relatedFileName 参数将会进入到如下位置。
cms/src/SSCMS.Web/Controllers/Admin/Cms/Templates
/TemplatesEditorController.Settings.cs

在 TemplatesEditorController.Settings.cs 的 L100,含有路径穿越的内容的 RelatedFileName 的值又赋给了 template.RelatedFileName 中,然后 template 被传入了到 WriteContentToTemplateFileAsync() 函数.
1 | await _pathManager.WriteContentToTemplateFileAsync(site, template, request.Content, _authManager.AdminId); |

WriteContentToTemplateFileAsync 函数位于如下路径。
cms/src/SSCMS.Core/Services
/PathManager.Template.cs
传入的 template 又被传入到了 GetTemplateFilePathAsync() 函数中

在 GetTemplateFilePathAsync() 中 template.RelatedFileName 又被传入到 GetSitePathAsync() 函数。
src/SSCMS.Core/Services/PathManager.Template.cs

GetSitePathAsync() 定义如下
cms/src/SSCMS.Core/Services
/PathManager.Site.cs

传入的 template.RelatedFileName 首先进入到了 PathUtils.Combine(returnPath, path);

在这里与siteId项目路径路径进行拼接
比如
1 | url1 = "/var/www/html/111" |
最终将得到 /var/www/html/111/index.html
但因为我们传入的是 path 的 template.RelatedFileName 内容为 ../../../../../../../../../etc/cron.d/test,所以此处返回的是
1 | returnPath = "/var/www/html/111/../../../../../../../../../etc/cron.d/test" |
然后 returnPath 进入到了 DirectoryUtils.IsInDirectory(_settingsManager.WebRootPath, returnPath) 函数

IsInDirectory() 函数定义如下
cms/src/SSCMS/Utils
/DirectoryUtils.cs

此处 IsInDirectory 是该漏洞的主要成因,该函数用于判断传入的 path 是否在 web 规定的目录沙箱内,但是此处仅使用 path.StartsWith 来进行判断。
1 | return parentDirectoryPath == path || path.StartsWith(parentDirectoryPath); |
但我们传入的 path 此时如下。
1 | path = "/var/www/html/111/../../../../../../../../../etc/cron.d/test" |
这恰好符合了 path.StartsWith(parentDirectoryPath); 从而导致我们的路径穿越被认为在合法web路径范围内。
然后 path 的值返回到了 cms/src/SSCMS.Core/Services /PathManager.Template.cs 的 WriteContentToTemplateFileAsync() 函数,并被被赋予到其中 filePath 参数。

路径穿越的值又进入到了文件写入函数 FileUtils.WriteTextAsync(filePath, content); ,并于此处触发漏洞,文件被写入到系统指定位置下。

于linux系统下,可以通过此种方式将文件写入到定时任务最终实现rce。

查看 /etc/cron.d/e2scrub_all ,确认写入成功。

等待1分钟之后触发rce.
