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 去除双斜杠转化为单斜杠