Java安全之Filter許可權繞過

nice_0e3發表於2021-05-23

Java安全之Filter許可權繞過

0x00 前言

在一些需要挖掘一些無條件RCE中,大部分類似於一些系統大部分地方都做了許可權控制的,而這時候想要利用許可權繞過就顯得格外重要。在此來學習一波許可權繞過的思路。

0x01 許可權控制實現

常見的實現方式,在不呼叫Spring Security、Shiro等許可權控制元件的情況下,會使用Filter獲取請求路徑,進行校驗。

編寫一個servlet

package com.nice0e3;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/helloServlet")
public class helloServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

            response.getWriter().write("hello!!!");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

定義一個Filter

package com.nice0e3.filter;

import com.nice0e3.User;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter("/*")
public class demoFilter implements Filter {
    public void destroy() {
    }


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;



        String uri = request.getRequestURI();
        StringBuffer requestURL = request.getRequestURL();
        System.out.println(requestURL);
        if(uri.startsWith("/system/login")) {  //登陸介面設定⽩白名單,即登入頁面
            System.out.println("login_page");
            resp.getWriter(). write("login_page");

            chain.doFilter(request, resp);
        }
        else if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前⽤戶是否登陸
            User user =(User) request.getSession().getAttribute("user");
            if(user == null) {
                resp.getWriter(). write("unauthorized access"); //未授權訪問
                System.out.println("unauthorized access");
                resp.getWriter(). write("go to login_page");//跳轉登入
                System.out.println("go to login_page");
            }
        }


    }

    public void init(FilterConfig config) throws ServletException {

    }
}

這裡使用 request.getRequestURI();獲取URI為 /system/login開頭 則直接放行。結尾,為.do.action的請求去做校驗,獲取session有沒有user的值,沒有的話即返回unauthorized access,如果不為.do.action的請求或session中存在user即放行。

訪問main頁面,顯示未授權訪問並且跳轉到登入的頁面

在Java中通常會使用request.getRequestURL()request.getRequestURI()這兩個方法獲取請求路徑,然後對請求路徑做校驗。

../繞過方式

這裡採用../的方式繞過

這裡就繞過了,許可權控制,直接能訪問到main,而不是顯示未授權訪問。在繞過時候可以找一些白名單的路徑,然後使用../去繞過。

payload:/system/login/../../login/main.do

繞過原理分析

上圖可以看到我們前面為system/login開頭

符合匹配的規則,而匹配上該規則後則是直接放行,讓系統認為訪問路徑是一個登入的路徑,但在後面加入2個../進行跳轉到根目錄,並且拼接上login/main.do,這時候實際訪問到的是http://127.0.0.1/login/main.do

但使用

 StringBuffer requestURL = request.getRequestURL();
if(requestURL.toString().startsWith("/system/login"))

request.getRequestURL();該方法獲取URL是攜帶http://127.xxx等資訊的。其實這裡比較廢話,因為驗證首部的字元路徑的話,使用 request.getRequestURI();來獲取請求路徑部分來校驗。

URL截斷繞過

基於前面Filter程式碼將../進行過濾

package com.nice0e3.filter;

import com.nice0e3.User;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter("/*")
public class demoFilter implements Filter {
    public void destroy() {
    }


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;



        String uri = request.getRequestURI();

        if(uri.contains("./")){
            resp.getWriter().write("error");
            return;
        }
        StringBuffer requestURL = request.getRequestURL();
        System.out.println(requestURL);
        if(uri.startsWith("/system/login")) {  //登陸介面設定⽩白名單,即登入頁面
            System.out.println("login_page");
            resp.getWriter(). write("login_page");

            chain.doFilter(request, resp);
        }
        else if(uri.endsWith(".do")||uri.endsWith(".action")) {
//檢測當前⽤戶是否登陸
            User user =(User) request.getSession().getAttribute("user");
            if(user == null) {
                resp.getWriter(). write("unauthorized access"); //未授權訪問
                System.out.println("unauthorized access");
                resp.getWriter(). write("go to login_page");//跳轉登入
                System.out.println("go to login_page");
            }
        }

    chain.doFilter(request,resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }
}

新增多了一個uri.contains("./")做過濾只要包含./字元直接報錯。

這時候會報錯,可見上圖。可進行繞過

payload:/login/main.do;123

繞過分析

URL中有一個保留字元分號;,主要為引數進行分割使用,有時候是請求中傳遞的引數太多了,所以使用分號;將引數對(key=value)連線起來作為一個請求引數進⾏傳遞。

再來看到程式碼,程式碼中識別.do.action的字尾的字元,而加入;加上隨便內容後,程式碼中就識別不到了。則會走到最下面的chain.doFilter(request,resp);,而在後面新增分號不會對地址的訪問有任何影響。

繞過

建立一個後臺介面,只允許admin使用者登入訪問

package com.nice0e3.Servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/system/UserInfoSearch.do")
public class UserInfoServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.getWriter().write("admin_login!!!");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
    }
}

而許可權控制這步肯定是在Filter裡面實現

     String uri = request.getRequestURI();

        if(uri.equals("/system/UserInfoSearch.do")){
            User user =(User) request.getSession().getAttribute("user");
            String role = user.getRole();
            if(role.equals("admin")) {
//當前⽤使用者為admin,允許訪問該接⼝
                chain.doFilter(request, resp);
            }
            else {
                resp.getWriter().write("Unauthorized");
                return;
            }
        }

這時候去對/system/UserInfoSearch.do做了校驗,獲取URI地址後匹配如果是這個/system/UserInfoSearch.do,則驗證使用者身份,加入不為admin,則顯示Unauthorized,越權訪問。

可直接訪問到admin使用者才可訪問的頁面下。

payload: //system/UserInfoSearch.do;123

繞過分析

看到程式碼中只是對比了URI是否為/system/UserInfoSearch.do,而多加一個/並不影響正常解析,而又能讓該規則匹配不到。

URL編碼繞過

還是用上面的程式碼演示,繞過手法則是換成url編碼繞過的方式。

payload:/system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f

繞過分析

當Filter處理完相關的流程後,中介軟體會對請求的URL進行一次URL解碼操作,然後請求解碼後的Servlet,而在request.getRequestURL()和request.getRequestURI()中並不會自動進行解碼,所以這時候直接接收過來進行規則匹配,則識別不出來。這時候導致了繞過。

Spring MVC中追加/繞過

在SpringMVC中假設以如下方法配置:

<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

特定情況下Spring匹配web路徑的時候會容錯後面的/

如,/admin/main.do/

修復

使用該程式碼接受URI

String uri1 = request.getServletPath() + (request.getPathInfo() != null ? request.getPathInfo() : "");

下面來嘗試前面的幾種繞過方式。

分號階段繞過 payload: /login/main.do;123

/繞過payload: //system/UserInfoSearch.do;123

URL編碼繞過payload:/system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f

../繞過payload:/system/login/../../login/main.do

均不可用,使用上面的方式接受URI後,接受過去的時候傳送特殊字元一律被剔除了。打斷點可見。

關注點

前面提到過request.getRequestURL()request.getRequestURI(),這些危險字元並不會自動剔除掉。可重點關注該方法。

參考

https://blog.csdn.net/qq_38154820/article/details/106799046

0x02 結尾

不只是Filter裡面可以做許可權繞過,在使用到一些Shiro框架的時候,也會有一些許可權繞過的方式。

相關文章