若依CMS代码审计

admin 2023年5月29日13:57:03评论160 views字数 6306阅读21分1秒阅读模式

1.安装

安装过程 ruoyi-adminsrcmainresourcesapplication-druid.yml配置数据库等信息

若依CMS代码审计

2.审计过程

2.1 文件下载漏洞(v4.7.6)

在com.ruoyi.web.controller.common.resourceDownload存在文件下载

若依CMS代码审计

首先简单分析下其代码:请求url如:http://127.0.0.1/common/download/resource?resource=1.htm

跟进checkAllowDownload校验的方法,发现是白名单校验,这里htm也在名单中,所以通过校验

若依CMS代码审计

可以发现在RuoYiConfig.getProfile();获取到的路径为D:/ruoyi/uploadPath若依CMS代码审计

此路径从配置文件中获取若依CMS代码审计

而后面代码就是下载,在最后的writeBytes后,就是将D:/ruoyi/uploadPath写入到response中。

// 数据库资源地址  downloadPath为D:/ruoyi/uploadPath
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
// 下载名称 downloadName为uploadPath
String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, downloadName);
FileUtils.writeBytes(downloadPath, response.getOutputStream());

但是若依有一处定时任务,该定时任务可以直接调用Bean其对应方法若依CMS代码审计

而在com.ruoyi.common.config包中有一个RuoYiConfig配置类。可以看到该类是与application.yml有关联。因为在application.yml中发现了profile设置了文件下载的路径,于是乎就可以通过定时任务的set方法来对其进行更改若依CMS代码审计

D盘先准备个txt若依CMS代码审计

ruoYiConfig.setProfile("要下载的文件");若依CMS代码审计

稍后执行即可若依CMS代码审计

但不是所有的Bean都能执行的。 

跟进package com.ruoyi.quartz.controller.SysJobController中 

可以看到其校验若依CMS代码审计

跟进这两处判断看一下

else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
{
    return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规");
}
else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
{
    return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
}

首先第一处是黑名单校验若依CMS代码审计

在containsIgnoreCase中,是一个黑名单校验。对传入的字符串进行校验。若依CMS代码审计

第二处就是白名单校验,首先获取到RuoYiConfig的bean。

return处的containsAnyIgnoreCase功能是:查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写若依CMS代码审计

因为在其这里进行了取反,所以该判断也就绕过了。若依CMS代码审计

继续看文件下载 

请求url:http://127.0.0.1/common/download/resource?resource=1.pdf

此时RuoYiConfig.getProfile();为D://1.txt若依CMS代码审计

最后会将D://1.txt下载若依CMS代码审计

这里的文件名后缀必须是checkAllowDownload中白名单校验的后缀若依CMS代码审计

2.3 新版SQL注入被修复(v4.7.7)

在com.ruoyi.system.mapper.SysDeptMapper中若依CMS代码审计

都过该Mapper定位到了service层SysDeptServiceImpl若依CMS代码审计

在往上就跟到了SysDeptController若依CMS代码审计

可以发现自定义了一个DataScope注解,根据这个DataScope找到了一个aop。若依CMS代码审计

在这个类中。handleDataScope对传入的dataScope进行了过滤

protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
{
    // 获取当前的用户
    SysUser currentUser = ShiroUtils.getSysUser();
    if (currentUser != null)
    {
        // 如果是超级管理员,则不过滤数据
        if (!currentUser.isAdmin())
        {
            String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext());
            dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                            controllerDataScope.userAlias(), permission);
        }
    }
}

继续跟进dataScopeFilter方法看一下,可以看到在获取role.getDataScope();之后,进行了判断值。并将创建了一个新的sql语句。大致流程就是service层加入了DataScope注解,就会跑到aop中进行过滤。

public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission)
{
    StringBuilder sqlString = new StringBuilder();
    List<String> conditions = new ArrayList<String>();

    for (SysRole role : user.getRoles())
        {
            String dataScope = role.getDataScope();
            if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope))
            {
                continue;
            }
            if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions())
                && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))
            {
                continue;
            }
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                conditions.add(dataScope);
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                    role.getRoleId()));
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                    " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                    deptAlias, user.getDeptId(), user.getDeptId()));
            }
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StringUtils.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
                }
            }
            conditions.add(dataScope);
        }

    // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据
    if (StringUtils.isEmpty(conditions))
    {
        sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias));
    }

    if (StringUtils.isNotBlank(sqlString.toString()))
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
        }
    }
}

2.4 定时任务修复(v4.7.7)

com.ruoyi.quartz.controller#editSave进行了白名单校验。若依CMS代码审计

跟进校验的函数

public static boolean whiteList(String invokeTarget)
{
    String packageName = StringUtils.substringBefore(invokeTarget, "(");
 int count = StringUtils.countMatches(packageName, ".");
if (count > 1)
{
    return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
}
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
String beanPackageName = obj.getClass().getPackage().getName();
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
    && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
}

违规字符串如下:若依CMS代码审计

在上方中,可以看见String beanPackageName = obj.getClass().getPackage().getName();进行了获取包名

当传入ruoyiConfig类之后获取其包名com.ruoyi.common.config此时匹配成功为true,但是最后return中进行了取反操作。 

最外层又进行了取反,最终结果是true。会error。

wx

若依CMS代码审计

原文始发于微信公众号(红队蓝军):若依CMS代码审计

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月29日13:57:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   若依CMS代码审计http://cn-sec.com/archives/1764053.html

发表评论

匿名网友 填写信息