记一次代码审计中发现的几个漏洞
2023-7-10 17:28:0 Author: xz.aliyun.com(查看原文) 阅读量:35 收藏

0调试环境配置

该项目由springboot开发,我一开始是想在源码中debug,但是有些maven依赖的jar包在maven的中央仓库还没上传,导致不能用源码运行,后边用他们在docker中部署的环境来运行,这里介绍一下调试docker中jar文件的方式,因为我自己之前不会,就记录一下
1.在docker desktop中找到jar文件位置,保存到本地(有源码这一步就不用了)

2.Inspect中找到jar的路径

3.新建个java项目将保存的jar添加到依赖中(选中jar文件,右键Add as Library)
4.解压jar包放到项目根目录,然后右键BOOT-INF下的lib目录选择Add as Library将这些jar全部添加到依赖中


5.将要调试的class文件夹添加到依赖关系中,这里就是BOOT-INF目录下的所有文件

6.在idea配置调试环境,添加一个“Remote JVM Debug”,配置端口,选择对应jdk版本,添加项目类文件

7.复制要调试的jar包的docker启动命令

8.在复制好的docker启动参数末尾追加如下参数:-jar后面的jar包路径就是第一步中得到的docker中jar包的路径,
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar /opt/apps/backend-1.18.4.jar

9.在复制好的docker启动参数-p 8091:8081后追加如下参数:通过5005端口进行调试通信
-p 127.0.0.1:5005:5005

10.使用追加了参数的docker命令启动docker镜像

11.最后点击debug即可正常下断点调试jar代码

12.如果有源码,也可以像第三步中一样,直接在idea设置debug后,也可以正常debug

1查看通用配置

1、审计前先看下配置文件、过滤器、拦截器、shiro这些通用配置。
系统用了一个过滤器,过滤器通过实例化SqlFilter类实现,使用了/*让所有请求经过过滤器处理

2、过滤器的主要代码放在doFilter方法,看看这个过滤器doFilter方法做了什么,先将ServletRequest转为HttpServletRequest,再传入XssAndSqlHttpServletRequestWrapper类的构造器进行实例化,再通过checkXSSAndSql校验请求参数

3、如果直接就在过滤器里把Request对象的参数拿处理校验,那么后续的Controller将得到一个“失效”的Request对象,后面的业务逻辑就会全线垮掉……,所以需要将Request对象进行包装,这个XssAndSqlHttpServletRequestWrapper类继承了HttpServletRequestWrapper,通过HttpServletRequestWrapper先将Request对象包装,包装后在XssAndSqlHttpServletRequestWrapper中对参数进行读写校验,而不是直接操作Request对象的参数

4、checkXSSAndSql方法用于校验请求参数是否包含xss和SQL注入的有关字符串,可见过滤器中配置了xss和sql注入的校验防护,但这种黑名单的方案说不好就会被绕过

漏洞1-上传功能

5、现在来看Controller层的代码,经过过滤器处理,请求放行后就来到Controller层的具体业务逻辑中,看看上传文件的Controller

6、进入Service层看下upload方法具体怎么处理文件,发现并没有校验文件合法性

7、尝试去上传一个html去执行xss

漏洞2-不安全的直接对象引用

8、查看删除应用的Controller,接口没有使用AOP,也没有对用户身份做校验,属于不安全的直接对象引用,任何用户都有权限删除

8、直接根据路径变量中传入的appTemplateId去删除应用

9、使用普通用户登录,调用/delete/{appTemplateId}传入应用id删除应用

10、使用管理员登录,发现管理员之前创建的应用已被普通用户删除

漏洞3-越权

1、查看更新应用信息的Controller(系统的应用只有管理员可以编辑,普通用户看不到这个功能,如果任意用户都可以编辑,系统就会变得没有规范,乱套了),接口配置了AOP注解,通过AOP来鉴权

2、AOP (Aspect Orient Programming):面向切面编程,AOP的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离

3、这里使用的是注解方式,即在需要使用切面管理的方法上配置AOP注解(这里就是@DePermissions注解),AOP注解方法的定义需要声明@Retention、@Target,表示这是一个AOP注解

4、然后在切面类上方使用@Aspect注解声明这个是切面类,用于切面编程,为系统中添加了@DePermission注解的业务方法添加通用功能

5、在切面类中就可以定义切面方法,最常用的就是环绕通知,用于在目标方法(添加了AOP注解的方法)执行前、执行过程中、执行后为其添加增强方法。@Around声明这个方法是环绕通知,他的value属性的值就是上方声明的AOP注解的全限定名

6、来看看这个aop方法做了什么,首先调用AuthUtils.getUser().getIsAdmin()判断当前用户是不是管理员,进入看看是怎么判断的

7、从USER_INFO这个常量中用get方法获取CurrentUserDto对象,

8、CurrentUserDto继承SysUserEntity,是用户实体类,包含了用户的完整身份信息

9、查看这个USER_INFO常量,ThreadLocal,他是一个线程局部变量的值,当用户发送一个请求过来,这个请求就是一个线程,线程局部变量中存在了CurrentUserDto对象,就可以从请求中拿到CurrentUserDto对象,再通过CurrentUserDto对象的getIsAdmin方法返回一个Boolean值判断是不是管理员

10、如果是管理员,说明权限是够的,就会调用point.proceed()执行原始目标方法

10、如果不是管理员,就要继续鉴权,这里我把代码意思写在注释上,方便看

11、判断AOP注解的属性logical的值是不是AND,指一个逻辑条件,只有当两个AOP注解都满足条件才能访问原始目标方法

12、我们回到原始目标方法的AOP注解,他的AOP注解的属性logical是AND,也就是这里 @DePermissions 中的两个 @DePermission 都要满足条件才能访问update方法

13、回去看AOP的切面代码,会调用access方法依次处理这两个 @DePermission


13、最后会根据access方法的返回结果来判断有没有权限调用原始目标方法(update方法)

14、access方法的返回类型是Boolean,他接收了三个参数:被AOP注解的方法的参数列表、AOP注解、0 。access方法中,先获取了AOP注解的type、value、requireLevel这几个属性的值

15、然后调用AuthUtils.permissionByType(type),这个方法中,返回了当前用户可以操作的type对应的数据

15、再用stream流去过滤返回的数据中,level大于等于@DePermission上配置的所要求的level属性值的数据

16、回到原始目标方法update上配置的注解,可以看到@DePermissions中的第一个@DePermission注解没有声明level属性值,

17、如果没有声明,level的默认值是1

18、普通用户的level值是满足这个level的,但是这里要同时校验两个DePermission,另一个DePermission配置的level属性值是5,普通用户的level小于这个level,也就是说,普通用户是没有权限调用这个update方法的

19、再看access方法的代码,如果传入的Object为空,则直接返回true

20、于是使用level为1的普通用户去调用更新的接口,只传入id指定应用,不传入pid,pid对应的注解就会为空,即可绕过权限校验去更新应用

漏洞4-权限绕过

1、来看一个查看用户信息的接口,同样是配置了AOP来鉴权,要求的level级别是3

2、用普通用户去调用会提示权限不够

3、看看他的AOP切面代码中是如何做的鉴权,AuthUtils.permissionByType(type)方法前的代码逻辑在第三个漏洞中已经分析过,在permissionByType方法中,获取这个用户对应的level级别可以查看的用户信息

4、普通用户的level级别为1,调用不了level 3 的接口,所以调用api/user/userGrid/1提示未授权

5、但是在最后返回的时候自动增加了一个level为3,值为0的数据集

6、这个值为0的数据集level为3,可以调用到这个原始目标方法

7、此时requireLevel是3,会提取出level大于等于3的数据集,判断请求过来的/userGrid/{datasetId} 的datasetId在不在level大于等于3的数据集中,而值为0这个datasetId刚好能满足条件

8、于是通过/api/user/userGrid/0可以实现权限绕过获取到所有用户信息

9、后边研发也反馈要增加一个level为3值为0的数据集是有其他的业务意义,这个接口之后也被删除

漏洞5-sql注入

1、由于使用的是mybatis框架来支持curd,可以直接搜索xml文件中的${,${}拼接的sql,如果sql参数可控,可能就存在注入风险,

2、但这样做可能有遗漏,因为有的sql语句使用的是sql注解直接把sql语句写在了代理接口方法上方,审计的时候两种情况都看看

3、找到order by的字段sort是拼接

4、往上找到代码接口searchOne被调用的地方

5、找到servie层中实现了searchOne接口后被调用的位置

6、找到Controller中对Service方法getWithPermission的调用

7、发现只有一个路径变量参数id,sort不可控,只能换一个了

8、按照上述方式找其他的调用链中sort可控的Controller,找到一个PanelTemplateRequest入参的Controller

9、其中sort参数可控

10、确定sort可控后,尝试注入,因为一开始分析过滤器代码时,发现有对sql关键字的黑名单检验

11、尝试找到黑名单意外的函数去执行,比如这个GTID_SUBSET就没在黑名单中


文章来源: https://xz.aliyun.com/t/12681
如有侵权请联系:admin#unsafe.sh