最近有人给我们提交了一个漏洞,涉及到泄露百万用户数据,然鹅我们APP利用AOP + Interceptor 完美避坑了...

最近有人给我们提交了一个漏洞,涉及到泄露百万用户数据,然鹅我们APP利用AOP + Interceptor 完美避坑了...

Lewis
2022-04-09 / 0 评论 / 36 阅读 / 正在检测是否收录...

l1r96d5v.png
l1r92kit.png

项目开发中,我们不可避免的需要对外暴露接口,尤其是APP。往往我们需要对APP进行最基础的权限认证,防止接口越权等问题。然而最近有用户给我们提交了一个BUG, 说利用此BUG可以获取 所有用户 信息,一听到这个差点吓死,还好不是我们开发的项目,不过今天我们还是来复盘一下。
  • 1.这个漏洞是怎么出现的呢?
    答:开放的接口中,直接上传用户id,然后后端代码根据上传的id查询用户信息并返回给前端,类似于这么一个接口/user/info?userId=1,前端登录后获取token,然后用postman直接调用接口,随便传任何用户id,都会返回用户id对应的用户信息。然鹅最要命的是这个用户id还是自增的,最要命的是这个接口返回的信息还是包含手机号、身份证信息等敏感信息的,最要命的是它返回的信息还是没有脱敏的。
  • 2.如何避免这个漏洞?
    答:通过上面的解答,大家肯定已经想到了解决思路,第一就是当前用户只能访问当前用户的信息,且用户id不是直接由客户端传给服务端;第二就是用户id最好不好用自增主键;第三返回给前端的数据要做脱敏,简单的说比如你要展示手机号,后端给返回131**5037这种。
  • 3.咱代码怎么写?
    答:我们不直接用前端上传的用户id,而是用用户登录信息中的id,然后比如查询订单的时候,用用户id+订单号查询,避免前面说到的系统漏洞。我也在下面写了一段示例代码,给大家参考。

1.定义一个RequireLogin 注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireLogin {

}

2.定义一个AuthorizationInterceptor类实现HandlerInterceptor

@Component
public class AuthorizationInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtils jwtUtils;
    public static final String USER_KEY = "userId";
    public static final String STORE_ID = "storeId";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        RequireLogin annotation;
        if (handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(RequireLogin.class);
        } else {
            return true;
        }
        if (annotation == null) {
            return true;
        }
        //获取用户凭证
        String token = request.getHeader(jwtUtils.getHeader());
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(jwtUtils.getHeader());
        }
        // 获取storeId
        String storeId = request.getHeader(STORE_ID);
        //凭证为空
        if (StringUtils.isBlank(token)) {
            throw new BaseException(jwtUtils.getHeader() + "不能为空", HttpStatus.UNAUTHORIZED.value());
        }
        if (StringUtils.isBlank(storeId)) {
            throw new BaseException(storeId + "不能为空。");
        }
        Claims claims = jwtUtils.getClaimByToken(token);
        if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) {
            throw new BaseException(jwtUtils.getHeader() + "失效,请重新登录", HttpStatus.UNAUTHORIZED.value());
        }
        //设置userId到request里,后续根据userId,获取用户信息
        request.setAttribute(USER_KEY, Integer.parseInt(claims.getSubject()));
        //设置storeId
        request.setAttribute(STORE_ID, Integer.parseInt(storeId));
        return true;
    }
}

3.通过WebMvcConfig注册我们添加的拦截器、方法参数解析器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private AuthorizationInterceptor authorizationInterceptor;
    @Autowired
    private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorizationInterceptor).addPathPatterns("/app/**");
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
    }
}

4.controler添加注解实现需要登陆后访问

    @ApiOperation("申请售后")
    @RequireLogin
    @PostMapping("apply")
    public Result apply(@RequestAttribute("userId") Integer userId, @RequestAttribute("storeId") Integer storeId, @RequestBody CommonForm<RefundApplyForm> form) {
        boolean apply = orderRefundService.apply(storeId, userId, form);
        return Result.ok();
    }

总结一下:系统的漏洞,往往都是前后端用户id这些传过来传过去,真正考虑安全的话,谢绝例如用户id直接从前端传。

1

评论 (0)

取消