JAVA代码审计之鉴权逻辑错误审计小记

admin 2025年6月20日23:59:26评论34 views字数 6139阅读20分27秒阅读模式

文章前言

在之前对MyBlog的代码审计过程中我们可以发现整个博客系统的用户分为两个大类:一个是管理员——admin,它主要负责后台的文件内容的编辑和发布以及对整个系统了统一管理和配置,另外一个则是普通用户,它主要作为一个外部访客来访问博客系统对博客内容进行浏览、查阅、评论等内容,不过在审计的过程中我们头疼的一个点就是后端的SQL语句使用动态方式进行组建并且前端可控的部分入参已经在后端进行了预编译处理,而且部分入参已经做了限定必须为INT类型的入参,那么就更加不可能存在注入的问题了,随后我们转至其他维度

鉴权绕过

在我们查看Controller时我们发现了很多很多的delete操作,但是很奇怪的是这里的调用处也并未有类似@hasRole("admin");这种权限注解操作,那么新的问题就来了,这里的这个普通用户和管理用户调用接口是如何做的权限识别和划分的呢?

JAVA代码审计之鉴权逻辑错误审计小记

从上面我们可以看到这里的Controller层做了来一个简单的区分,admin目录下的为管理后台的Controller控制层,而外侧的部分则是公共用户可以进行调用的部分,例如:

JAVA代码审计之鉴权逻辑错误审计小记

那么这里既然没有走RBAC(基于角色鉴权)的方式来进行用户鉴权,那么是走的ABAC(基于属性的鉴权)的方式来进行的鉴权?不可能,绝对不可能,因为整个系统中就只要一个管理员,另外的都是未登录情况下的外部普通用户,那么还有什么方式来进行用户的鉴权呢?当然有,那就是Shiro,不过这个项目中并未映入Shiro组件,所以也直接排除,那么还有吗?当然有,最原始的拦截器部分,我们查看目录可以看到项目中存在一个interceptor目录,其下有两个文件——WebMvcConfig和BaseInterceptor拦截器,在这里我们重点看BaseInterceptor中的预处理——preHandle部分:

JAVA代码审计之鉴权逻辑错误审计小记

从上面可以看到这里首先使用request.getRequestURI();来获取URL请求路径信息,随后获取请求头中的USER_AGENT并获取用户的请求IP地址,这里呢,当然存在一个IP伪造的问题——XFF,具体如下,这里我们不展开说,我们继续来看上面的预处理的鉴权部分,随后这里会调用TaleUtils.getLoginUser(request)进行请求拦截处理

JAVA代码审计之鉴权逻辑错误审计小记

从下面可以看到这里其实就说获取请求中的session信息,如果session为空则直接返回——null,此类场景对应游客用户,如果不为空则直接返回对应的session信息

JAVA代码审计之鉴权逻辑错误审计小记

随后我们回到上面,此时如果返回的user为null,那么会继续调用TaleUtils.getCookieUid来获取请求中的userid信息

        if (null == user) {            Integer uid = TaleUtils.getCookieUid(request);            if (null != uid) {                //这里还是有安全隐患,cookie是可以伪造的                user = userService.queryUserById(uid);                request.getSession().setAttribute(WebConst.LOGIN_SESSION_KEY, user);            }        }

TaleUtils.getCookieUid如下,可以看到如果Cookie为空,那么此时啥都不会有,直接返回一个null,随后在返回到上面的代码中可以看到这里的uid如果不为null,那么会根据uid来查询user信息并根据user来进行一次封装来生成一个Session,如果为null,那么会直接跳出这一个部分直接继续往下走

JAVA代码审计之鉴权逻辑错误审计小记

随后来到了下面的鉴权部分,可以看到如果请求路径以/admin开头且不以/admin/login开头且用户为null,那么则会直接跳转到/admin/login中去,这种场景对应于用户未登录直接去调用后台管理员的请求路径时触发的跳转,也就是我们上面的鉴权的关键实现部分,随后就是一套设置CSRF_token来防止CSRF攻击的防御套装

JAVA代码审计之鉴权逻辑错误审计小记

那么看到这里大家觉得有啥问题呢?没问题?当然有问题了,如果没有问题也就没有这个文章的标题了,这里的问题主要在于鉴权依赖于请求路径的匹配,而请求路径的获取使用request.getRequestURI();来获取路径,而在Tomcat中HTTPServletRequest常常通过下面几个常用的函数来处理用户发起的URL请求:

  • request.getRequestURL():返回全路径
  • request.getRequestURI():返回除去Host(域名或IP)部分的路径
  • request.getContextPath():返回工程名部分,如果工程映射为/,则返回为空
  • request.getServletPath():返回除去Host和工程名部分的路径
  • request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null

这里关于request.getRequestURI()的使用导致的差异性问题不再追溯,有兴趣的读者可以看一下@Mik7ea师傅之前在先知上发布的文章——Tomcat URL解析差异性导致的安全问题(https://xz.aliyun.com/news/7139),关键的问题引入到这篇文章的这个应用中来说就是Tomcat底层处理URL时normalize()函数经过parsePathParameters()函数过滤;xxx/的URL请求内容进而标准化处理,具体为将连续的多个/给删除掉只保留一个、将/./删除掉、将/../进行跨目录拼接处理,最后返回处理后的URL路径,所以Tomcat对/;xxx/以及/./的处理是包容的、对/../会进行跨目录拼接处理,那么反馈到这里的场景就是此时我们的/admin/login路径时一个可以对外访问的路径,而这里的/admin/article/delete则是需要过鉴权预处理之后才可以访问到的,否则会被直接重定向到/admin/login,不过如果我们这里使用一下底层的解析差异就不一样了,我们这里构造请求的URL如下:

/admin/login/../article/delete

那么此时传递到后端的时候预处理阶段因为开头为/admin/login,所以直接过鉴权,随后在后端的时候会解析/../进行跨目录处理,随后直接转为下面的路径(上一次这么有趣还是F5 BIG-IP的Tomcat和Nginx解析差异的导致的认证鉴权和RCE问题呢)

/admin/login/article/delete

下面我们直接来上手验证试试看,首先一个正常的请求如下:

JAVA代码审计之鉴权逻辑错误审计小记

随后我们移除掉JESSIONID并更改请求路径部分的内容,构造请求报文如下进行删除操作,虽然这里success回显了一个false,但是结果是正确的,确实被删除了,有兴趣大家可以自行试试看

POST /admin/login/../article/delete HTTP/1.1Host192.168.204.137:8081Content-Length5X-Requested-WithXMLHttpRequestUser-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36Acceptapplication/json, text/javascript, */*; q=0.01Content-Typeapplication/x-www-form-urlencoded; charset=UTF-8Originhttp://192.168.204.137:8081Refererhttp://192.168.204.137:8081/admin/articleAccept-Encodinggzip, deflateAccept-Languagezh-CN,zh;q=0.9CookieConnectionclosecid=4
JAVA代码审计之鉴权逻辑错误审计小记

完整的代码如下所示:

package com.my.blog.website.interceptor;import com.my.blog.website.modal.Vo.UserVo;import com.my.blog.website.service.IUserService;import com.my.blog.website.utils.*;import com.my.blog.website.constant.WebConst;import com.my.blog.website.dto.Types;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * 自定义拦截器 * Created by BlueT on 2017/3/9. */@Componentpublic class BaseInterceptor implements HandlerInterceptor {    private static final Logger LOGGE = LoggerFactory.getLogger(BaseInterceptor.class);    private static final String USER_AGENT = "user-agent";    @Resource    private IUserService userService;    private MapCache cache = MapCache.single();    @Resource    private Commons commons;    @Resource    private AdminCommons adminCommons;    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {        String uri = request.getRequestURI();        LOGGE.info("UserAgent: {}", request.getHeader(USER_AGENT));        LOGGE.info("用户访问地址: {}, 来路地址: {}", uri, IPKit.getIpAddrByRequest(request));        //请求拦截处理        UserVo user = TaleUtils.getLoginUser(request);        if (null == user) {            Integer uid = TaleUtils.getCookieUid(request);            if (null != uid) {                //这里还是有安全隐患,cookie是可以伪造的                user = userService.queryUserById(uid);                request.getSession().setAttribute(WebConst.LOGIN_SESSION_KEY, user);            }        }        if (uri.startsWith("/admin") && !uri.startsWith("/admin/login") && null == user) {            response.sendRedirect(request.getContextPath() + "/admin/login");            return false;        }        //设置get请求的token        if (request.getMethod().equals("GET")) {            String csrf_token = UUID.UU64();            // 默认存储30分钟            cache.hset(Types.CSRF_TOKEN.getType(), csrf_token, uri, 30 * 60);            request.setAttribute("_csrf_token", csrf_token);        }        return true;    }    @Override    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {        httpServletRequest.setAttribute("commons", commons);//一些工具类和公共方法        httpServletRequest.setAttribute("adminCommons", adminCommons);    }    @Override    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}

文末小结

本篇文章算是一个对博客系统的鉴权方式之一——通过请求路径匹配预处理的介绍和漏洞案例的分析,这个和我们之前的RBAC、ABAC、Shiro鉴权放到一起也勉勉强强算是一个较为常用的鉴权系列了,另外还有一个就是底层中间件容器对于URI地址的获取和解析的差异性带来的安全问题,这个也需要在具体的代码审计过程中多多留意和关注

原文始发于微信公众号(七芒星实验室):JAVA代码审计之鉴权逻辑错误审计小记

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月20日23:59:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA代码审计之鉴权逻辑错误审计小记https://cn-sec.com/archives/4183630.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息