某RBAC管理系统审计

发布时间 2023-09-07 10:24:09作者: lockly

某RBAC管理系统审计

前言

这个管理系统的审计我去年就开始了但烂尾了,那时候太热闹了log4j2,cs的cve反制等等。这个都给忘了,所以本篇可能有些图有点老,现在就是旧图没一个个换遇到的新的就加上。现在Java也学了一些了,来搞审计理解就更深了。

环境配置

MySql

小皮面板开起MySQL。

image-20221216224737164

找到MySQL安装目录,加环境变量。小皮面板的在这个extensions目录下面。

image-20221216230024500

创建一个库。

image-20230904234317327

导入rbac.sql文件,全ok就没问题了。

image-20230904234606831

Maven

idea中内置了maven就直接用了,下面记录了一些遇到的问题。

依赖问题

加载依赖的时候还有可能会遇到这样的情况:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/context/index/CandidateComponentsIndexLoader

[这个异常的原因是Spring Boot在创建entityManagerFactory这个bean时,找不到CandidateComponentsIndexLoader这个类,解决办法就是在pom.xml中添加spring-context-indexer依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-indexer</artifactId>
    <optional>true</optional>
</dependency>

在idea中打开pom.xml,以项目方式打开,如果之前没用过maven就去设置里面勾选一下下面的选项,将IDE的构建/运行操作委托给Maven,就可以自动下载所需要的依赖了。

image-20230905000058252

更改数据库配置,修改号连接的用户名和密码。端口号有冲突也是自行修改。

image-20221217022004223

配置问题

有报错时,双击红色报错跳转到对应的标签下,可以发现org.springframework.boot没问题,但是的spring-boot-maven-plugin提示有问题。

image-20221217021828761

crtl+单击上面的org.springframework.boot会跳转到配置文件中,找到spring-boot-maven-plugin的版本信息,也就是,复制到标错的那行下边就行了。

image-20221217021854648

JDK问题

还有可能遇到的几种错误:

  1. java: -source 1.3 中不支持注释(请使用 -source 5 或更高版本以启用注释) 或者 java: Compilation failed: internal java compiler error

  2. class lombok.javac.apt.LombokProcessor (in unnamed module @0x50b78155) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x50b78155

    像这些情况基本都是JDK版本问题,就检查一下这三个地方是否已经更换成了相应版本的jdk。ctrl+alt+shift+s 打开项目结构。然后现在project这里更换

image-20221217104504612

再检查底下modules。

image-20221217104535513

最后再 File -> Settings里面注意两个地方的版本都要相同。

image-20221217104610612

最后能够跑起来有日志了就没问题了。

image-20221217104422905

访问 http://127.0.0.1:8088/login.html 即可看到以下界面。

image-20230907100839799

渗透测试

目录爆破

先用dirsearch扫一下目录。

image-20230905185416744

未授权访问

根据上面的结果可以看到有一些目录,首先先去看/actuator/env ,因为通过他可以看到 SpringBoot 载入了哪些 properties,以及 properties 的值(这个里面会自动用*替换 key、password、secret 等关键字的 properties 的值进行脱敏)

image-20230906000234404

其他的一些常见的:

路径 描述
/autoconfig 提供了一份自动配置报告,记录哪些自动配置条件通过了,哪些没通过
/beans 描述应用程序上下文里全部的Bean,以及它们的关系
/env 获取全部环境属性
/configprops 描述配置属性(包含默认值)如何注入Bean
/dump 获取线程活动的快照
/health 报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
/info 获取应用程序的定制信息,这些信息由info打头的属性提供
/mappings 描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
/metrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数
/shutdown 关闭应用程序,要求endpoints.shutdown.enabled设置为true
/trace 提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

我这里不知道是不是浏览器的原因,访问不到 /heapdump/actuator/heapdump(按道理actuator这个目录的所有内容都放行了应该能访问到的),拿不到堆转储文件后续就搞不了星号脱敏拿密码。项目用git托管了然后还原回去也还是下载不了?。

Swagger接口文档泄露

在前面目录扫描的结果里面有几个swagger路径,可以看到所有的接口都泄露了。

image-20230905110712984

登录验证码爆破

登录页面有个验证码,先去查看前端代码,点击验证码之后回去请求一个url。

image-20230905083319833

这个时候用到这个插件: captcha-killer。在bp中拦截请求发送给插件的send to captcha panel。

image-20230905183833185

接口的话可以选择去用百度免费的,登陆上控制台搜文字识别可以申请但是现在需,要实名认证才能领了。

image-20230905095637816

实名认证并领取,然后创建一个应用自定义名字,其他的我是全选的。

image-20230905172331592

然后还要利用这里的两个值去获得access_token,分别用上面的API KEY Secret Key去填充下面的两个参数,之后去浏览器中访问这个地址。或者按照官方的调用:

curl -i -k 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic?access_token=【调用鉴权接口获取的token】' --data 'image=【图片Base64编码,需UrlEncode】' -H 'Content-Type:application/x-www-form-urlencoded'

我这直接浏览器访问了:

https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=****&client_secret=****

image-20230905172835335

回到bp中选择百度的模板然后填上刚刚得到的token。

image-20230905173558347

但是真的是奇怪了,传的image参数明明是按照官方文档来的将图片转成base64,再url编码一下。

image-20230905183458828

但硬是乱识别一个都识别不出来,网上也说百度对gif的识别很差劲。先试试看验证码是不是只是前端验证。

image-20230905183328723

简单尝试可以发现验证码怎么修改都不会报类似验证码有误,验证码过期之类的错误提示。哪会不会是因为校验到账号密码不对就已经提前在函数中return了然后就只会显示用户名或账号密码错误了呢?

image-20230905185739534

但当去掉验证码的参数之后就提示了验证码不能为空,很显然不是这样他还是对验证码有校验的。

image-20230905105656050

那就有一种可能服务端根本就没做验证码值是否相等的验证,可以尝试一下,然后爆破出来得到admin:123456

image-20230905104657076

Druid登录爆破

用gobuster扫了一下显示有一个druid组件,是个登录页面可以进行爆破。

image-20230906170545967

在bp中给用户名和密码参数加上$,设置好字典挂着跑。根据响应长度得到admin:admin

image-20230905082242003

XSS

奉承见框就插的思想,对这些输入框都拿payload试试看有没有过滤校验。在角色管理->新增模块处存在xss。

image-20230905111510675

提交后刷新页面就会弹窗。

image-20230905111711183

在用户管理这里的查询也是,当插入payload时会引发全局异常,此时会被记录得到异常操作日志中。

image-20230905112843653

在系统管理->异常日志中加载错误记录时就会触发。

image-20230905112735383

同样的,这样的xss有很多很多,因为很多地方都直接将错误记录了,对一些输入框的校验过滤可以说根本没有。

image-20230905113234291

只要能触发全局异常,在加载异常日志的时候都会引发xss弹窗。

image-20230905113219365

SQL注入

现在很多项目中都有基本的安全意识不会去拼接sql语句,基本上都是用PrepareStatement去预编译了。就先挂插件在后台扫,自己试试看他有没有配置疏忽的地方,插件在bapp store里面有,环境要求有JPython,配置的话网上有一堆就不赘述。

image-20230906111842847

在开启扫描之后去看看其他的页面,把查询的地方都丢进去扫。不一会就有了,看payload是盲注。

image-20230906112645976

系统管理 -> 字典管理 的查询那里也有sql注入。

image-20230906114646538

插件里勾选了库和表信息但他没脱出来,这里就单独把报文拿出来用sqlmap跑一遍。

python sqlmap.py -r E:\sql1.txt --dbs --batch

image-20230906160909469

按src标准到这适可而止,做良好公民?。

python sqlmap.py -r E:\sql2.txt -D rbac --tables --batch

image-20230906162331049

越权漏洞

创建了用户110但是侧边栏什么功能都没有,没明白怎么个事?后续再看看。

image-20230907005156939

进数据库修改一下,可以看到很明显的是bcrypt,直接将admin或者110(这两个密码相同)的加密值给他就行,虽然是不同的加密字符串但提取出的盐值相同,bcrypt的验证可以通过。

image-20230907005605231

当然了破解也可以,这里将test6密码字段的加密字符串写到文件里面,john指定format即可。

john -w=/usr/share/wordlists/rockyou.txt hash --format=bcrypt

image-20230907075945713

以这个普通用户test1登录F12拿到他的cookie, 在插件中设置好cookie的值然后开启autorize,然后就到处点点页面的功能(不需要拦截)。

image-20230907010311968

红色表示不存在,绿色则是确定存在,黄色是不确定的需要手工验证。这里看到userId那里有可能就去尝试一下。

image-20230907010728728

试试删除功能,这里选择test用户进行删除,拦截报文看到他的userId是2,drop掉这个请求不将admin删除。

image-20230907000940916

然后退出,以刚刚创建的110:123456登录,这个用户下只能看到test6,抓包选择删除将userId改为2,下面乱码问题不大,状态码200就执行没问题。

image-20230907003807811

此时再登录上admin就可以看到test用户已经删除了。

image-20230907004040725

代码审计

未授权访问

产生原因就是SprinBoot的错误配置,这种大概率就是程序员为了方便调试而放行,但由于疏忽而忘记禁行。来到security/config/SpringSecurityConfig里面,可以看到放行了actuator和swagger-ui.html这种危险的目录。

image-20230907091317817

越权漏洞

逆向去分析越权漏洞产生的原因,根据之前发现漏洞的位置,在操作用户的地方/api/user。在控制层的UserController,通过关键字找到删除用户的相关函数:

image-20230907082431734

跟进这个函数,来到了事务层的UserService接口,直接去看他的实现接口。看到这里有个校验函数,后面就是在删除这个用户之前先删除与他相关的角色,相关的职位。

image-20230907082504177

进入到校验的checkUserAllowed,只判断了非空和是否是admin也就是说删除其他的用户都没有限制。

image-20230907082532226

一般dao都是直接操作数据库了,校验判断集中在事务层调度,这里也是一样。这一路下来并没有发现userId权限关联的判断校验,而userId也是直接从用户那边输入而不是从session中获取,也就是说他只是在根据userId进行删除用户。这就导致userId可控,这样任何人调用这个接口都可以进行删除操作。

验证码校验

看看后端的校验com/codermy/myspringsecurityplus/security/filter/VerifyCodeFilter.java(这下面的一段是本来就注释的不是我注的),看到他只有在验证码为空时才会置空销毁掉缓存中存储的验证码,这也就是说不为空的时候就算使用了错误的验证码也可以通过校验,这就是验证码可以重用的原因。

image-20230907083217376

SQL注入

这个项目中使用了 Mybatis 去操作数据库,在编辑xml时Mybatis支持两种参数符号。

  1. {value} 在预编译的时候就会用?代替参数部分将其当作字符串处理。

  2. ${} 这个表示拼接字符串,以这种方式拼接就会引起SQL注入。

根据前面发现的两处SQL注入一个个逆向去分析漏洞成因,先看字典管理那里的(一般直接审计代码的话就两下shift全局搜${)。在工程下的main/resources/mybatis-mappers, 看文件名就是DicMapper.xml

image-20230907082739211

很明显这里用了拼接,解决方法也就是直接用#替换掉$。这里一路跟进getFuzzyDictByPage这个函数的调用折,看到事务层的实现类DicServiceImpl调用了他。

image-20230907082851291

这里就不用去接着看谁调用他,回到这个实现方法的接口中看更直观(一般是action这个层去接收前端传来的内容,这里是controller)。已经很明显了,没有dictNname这个参数传入,那就是这是一个实体对象中的一个属性。

image-20230907083032769

他的传参是一个为MyDict的对象,进入这个bean看一下。找到了dictName这处为字典名称,这就是漏洞触发的地方。另一个地方同理。

image-20230907083103276

漏洞成因分析完了可能会有疑问使用拼接容易产生SQL注入,哪为什么不全部使用预编译?使用${}拼接是有一些有优点的,他可以实现一些动态的功能,比如指定表名,列名,排序字段等等。而在某一些场景下还真不能使用#{}去进行预编译。举个栗子?:

  1. 模糊查询 like

就是上面发现的地方,拿另外一个sql语句来看:

select * from users where name like '%#{name}%' 

这时候使用#号会直接报错,而经验不足的新手程序员就直接改成%号,这样如果没有对用户输入再有额外的过滤转义那势必产生SQL注入,而正确写法应该是这样的:

select * from users where name like contact('%', #{name}, '%')
  1. in之后有多个参数

in之后多个id查询时使用# 同样会报错,就比如:

select * from users where id in (#{ids})

正确用法应该是使用foreach标签,如下:

select * from users where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
  #{id}
</foreach>

  1. order by

使用#{}来动态拼接order by的字段或方向,可能会导致SQL注入,比如:

@Select("select * from user order by #{column} #{direction}")
List<User> selectUsers(@Param("column") String column, @Param("direction") String direction);

修改方案:使用${}。

@Select("select * from user order by ${column} ${direction}")
List<User> selectUsers(@Param("column") String column, @Param("direction") String direction);

或者也可以使用orderby方法来构建动态排序。

SelectStatementProvider selectStatement = select(user.allColumns())
    .from(user)
    .orderBy(sortColumn(sortSpecification(column, direction)))
    .build()
    .render(RenderingStrategies.MYBATIS3);

XSS

一般来说一个项目中都是由一个统一的工具类或者函数来过滤转义来自用户的传参。所以在idea中按两下shift搜索所有的文本内容,可以看到根本没有XSS相关过滤器(Filter)或拦截器(Interceptor)。这些关键词都出现在前端代码中:

image-20230907082625124

在前端代码中可以发现使用了Thymeleaf模板引擎和Layui框架,这里有个知识点就是它里面的th:text属性会将文本内容作为纯文本处理,也就是说,它会对HTML标签进行转义,从而避免XSS攻击,th:utext属性则会将文本内容作为未转义的HTML处理,也就是说,它会保留HTML标签,并将其渲染为HTML元素。因此全局搜索一下,发现并没有因为使用th:utext而产生的xss。

image-20230907090708883

但是还有一个属性——th:value,来到resources/templates/system/role/role-edit.html这里使用了th:value属性来设置输入框的值,而th:value属性不会对HTML标签进行转义,恶意输入也会被浏览器执行,导致产生XSS。

image-20230907090316451