Java Tomcat 和 Spring中的 getRequestURI权限绕过

前言:在之前没有学习就听到过了Shiro的权限绕过,也就是通过特定字符来绕过Shiro框架自身的权限判断,现在自己也还没有能力去了解这个框架,但是对于权限绕过的原理,框架只是单纯的封装了,本质还是函数的逻辑漏洞,自己这里还没有去分析框架,所以这边就先去了解这些函数的权限绕过!

参考文章:https://www.cnblogs.com/nice0e3/p/14801884.html
参考文章:https://forum.butian.net/share/829

Tomcat的getRequestURI

../ 绕过方式

我们先把要代码放上来,web环境结构:

DemoFilter代码:

public class DemoFilter implements Filter {
    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        String uri = request.getRequestURI();//  /system/login
        StringBuffer requestURL = request.getRequestURL(); //  http://localhost:8080/system/login
        System.out.println("requestURL: " + requestURL);
        System.out.println("getRequestURI: " + uri);

        if(uri.startsWith("/system/login")) {
            //登陆接口设置⽩白名单,即登录页面
            System.out.println("this is login page...");
            resp.getWriter().write("this is login page...\n");
            chain.doFilter(req, resp);
        } else if(uri.endsWith(".do") || uri.endsWith(".action")) {
            //检测当前⽤户是否登陆
            User user = (User) request.getSession().getAttribute("user");
            if (user == null) {
                resp.getWriter().write("unauthorized access\n"); //未授权访问
                System.out.println("unauthorized access");
                return;
            }
        }
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

User pojo

public class User {
    public User(){

    }
}

AdminServlet

public class AdminServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("this is admin page...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

LoginServlet

public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("login!!!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

web.xml映射关系:


  <servlet>
    <servlet-name>login</servlet-name>
    <servlet-class>com.zpchcbd.servlet.LoginServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>login</servlet-name>
    <url-pattern>/system/login</url-pattern>
  </servlet-mapping>

  <servlet>
    <servlet-name>admin</servlet-name>
    <servlet-class>com.zpchcbd.servlet.AdminServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>admin</servlet-name>
    <url-pattern>/login/admin.do</url-pattern>
  </servlet-mapping>

  <filter>
    <filter-name>demo</filter-name>
    <filter-class>com.zpchcbd.filter.DemoFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>demo</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

web启动环境如下:

访问:http://localhost:8080/system/login

访问:http://localhost:8080/login/admin.do

到了这里,就先回到主题,关于../访问方式绕过问题,为什么可以进行绕过?原因肯定就是相关函数在对路径的处理时产生了逻辑问题

在Java中通常会使用request.getRequestURL()和request.getRequestURI()这两个方法获取请求路径,然后对请求路径做校验。

我们这里主要是对getRequestURI这个函数进行研究,权限绕过也是发生在这个地方。

在代码里面我们也有对请求的路径进行打印,如下图所示:

访问:http://localhost:8080/system/login

访问:http://localhost:8080/login/admin.do

当访问了/login/admin.do的时候显示的是未授权,但是这里的话我们可以通过../的方式来进行绕过!

我们这里访问:http://127.0.0.1:8080/system/login/../../login/admin.do,来看下这两个函数是如何获取的?

上面发现还是不行,那我们放在burp中来进行测试?

这里是有坑的,原因就是浏览器会自动进行解析路由的问题,它会直接帮你帮你把../先进行解析,最后再进行访问,所以这个点需要注意,也是自己需要注意的问题。

原因分析

这里就来调试看下它的流程是怎么走的

首先如下打上断点

最后获取的URI为/system/login/../../login/admin.do

最终导致了绕过

URL截断绕过

如果还是在面对../的情况下,但是发现./被过滤了,主要的逻辑代码如下:

        if(uri.contains("./")){
            resp.getWriter().write("error");
            return;
        }

那么当前的Filter实现为如下

public class DemoFilter implements Filter {
    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        String uri = request.getRequestURI();//  /system/login
        StringBuffer requestURL = request.getRequestURL(); //  http://localhost:8080/system/login
        System.out.println("requestURL: " + requestURL);
        System.out.println("getRequestURI: " + uri);

        if(uri.contains("./")){
            resp.getWriter().write("error");
            return;
        }

        if(uri.startsWith("/system/login")) {
            //登陆接口设置⽩白名单,即登录页面
            System.out.println("this is login page...");
            resp.getWriter().write("this is login page...\n");
            chain.doFilter(req, resp);
        } else if(uri.endsWith(".do") || uri.endsWith(".action")) {
            //检测当前⽤户是否登陆
            User user = (User) request.getSession().getAttribute("user");
            if (user == null) {
                resp.getWriter().write("unauthorized access\n"); //未授权访问
                System.out.println("unauthorized access");
                return;
            }
        }
        chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

这里我们再进行之前的访问看看还能不能进行权限绕过,如下图所示,发现直接回显了一个error,所以在面对这种情况的过滤,之前的绕过已经不行了

这里继续来进行绕过,可以进行访问:http://127.0.0.1:8080/login/admin.do;

可以发现,成功绕过了,那么这个原因又是什么呢?

原因分析

URL中有一个保留字符分号(;),主要作为参数分隔符进行使用,有时候是请求中传递的参数太多了,所以使用分号(;)将参数对(key=value)连接起来作为一个请求参数进⾏传递。

直接在URI中引入分隔符,正常来说是不会对实际接口的访问造成影响的。

这里就来调试看下它的流程是怎么走的,老样子如下图所示先打个断点

首先第一个判断,由于不存在./符号,所以这里可以绕过

第二个判断,由于结尾不是.do.action来结尾,所以这里也可以进行绕过

最后成功来到了要执行的地方

再来看看 /login;Bypass/admin.do;Bypass,发现同样也可以,那么这个又如何理解呢?

其实一样的,我们只要看是否存在./和结尾是否是.do.action,那么都不符合就直接绕过了

这里的话也只适合我们写的代码,真实环境不一定可以完美符合,就比如来验证了路径是否正确呢?那就不行了

URL编码绕过

还是上面的代码,访问地址 http://127.0.0.1:8080/login/%61%64%6d%69%6e%2e%64%6f

当filter处理完相关的流程后,中间件会对请求的URL进行一次URL解码操作,然后再找到对应的Servlet进行访问。也就是说尝试对我们访问的URL进行一次URL编码,中间件是可以正常解析并完成正常业务的

原因分析

tomcat中间件什么时候会做url编码的操作呢?

通过调试会发现在进行url解码的时候是在org.apache.catalina.connector.CoyoteAdapter#postParseRequest执行的,如下所示

在org.apache.tomcat.util.buf.UDecoder#convert方法中会对url编码的数据进行url解码的操作,如下图所示会先找到百分号的索引的位置

如果百分号后面的两个字符都是存在的,那么则会通过x2c方法进行转换,可以看到就是将十六进制的数据转换为十进制的数据然后返回

    private static int x2c(byte b1, byte b2) {
        int digit = b1 >= 65 ? (b1 & 223) - 65 + 10 : b1 - 48;
        digit *= 16;
        digit += b2 >= 65 ? (b2 & 223) - 65 + 10 : b2 - 48;
        return digit;
    }

接着下继续讲框架的权限问题,这里就先停下,到时候自己学到了会在这里继续补上

=2021-7-18更新=

shiro框架的权限绕过问题:https://www.cnblogs.com/zpchcbd/p/15023056.html

=2023-1-21更新=

补上spring中的权限绕过的常见点

Spring

URL编码绕过

参考文章:https://www.cnblogs.com/zpchcbd/p/17056667.html

分析原因

从这边开始跟所有的spring请求都会从org.springframework.web.servlet.DispatcherServlet#doDispatch经过

这边主要就是看如下三个处理函数

decodeAndCleanUriString:492, UrlPathHelper (org.springframework.web.util)
getRequestUri:381, UrlPathHelper (org.springframework.web.util)
getPathWithinApplication:297, UrlPathHelper (org.springframework.web.util)
getLookupPathForRequest:185, UrlPathHelper (org.springframework.web.util)
getHandlerInternal:124, AbstractUrlHandlerMapping (org.springframework.web.servlet.handler)
getHandler:396, AbstractHandlerMapping (org.springframework.web.servlet.handler)
getHandler:1237, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1019, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)

removeSemicolonContent 清除分号和斜杠之间的数据

decodeRequestString 路径url解码,权限绕过就出现在这边

getSanitizedPath 去除双斜杠转化为单斜杠

posted @ 2021-05-26 23:32  zpchcbd  阅读(2381)  评论(0编辑  收藏  举报