Servlet线程安全问题

Servlet线程安全问题

这个问题如果不深究原理的话,还是很简单理解的。但是安全从业者岂能不深究原理,加倍努力吧!

条件竞争漏洞

条件竞争漏洞(Race codition),官方的概念是”发生在多个线程同时访问一个文件,一块代码,一个变量等没有进行锁操作或者同步操作的场景中“。这个漏洞存在于操作系统,数据库,web等多个层面

  • 脏牛漏洞
  • php中的session.upload_progress选项
  • servlet线程安全问题

.......

Servlet实例化

我们在写一个servlet的时候写的是xxxservlet,这表示的是一个类。当第一个请求过来的时候会将xxxservlet类进行一次实例化,等到第二个或者更多的请求过来的时候用的依旧是xxxservlet初次实例化的对象,并不会在实例化新的xxxservlet对象。这样的话是不是就会存在条件竞争问题?

漏洞分析

看一看下面这一个butlerServelt,看似矛盾的处理,能不能尝试绕过。

package com;

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

public class butlerServlet extends HttpServlet {
    public boolean status;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String cmd = req.getParameter("cmd");
        status = true;
        if (cmd.equals("shell")) {
            status = false;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (!status) {
            //resp.getWriter().println("you are worng");
            return;
        }
        if(cmd.equals("shell")) {
            resp.getWriter().println("you are execute successful!");
        }
    }
}

使用python脚本请求来绕过,我们需要俩个python请求

a.py:

import requests
url = "http://localhost:8080/sec?cmd=shell"
while True:
    content = requests.get(url)
    if "successful" in content.text:
        print(content.text)

b.py:

import requests
url = "http://localhost:8080/sec?cmd=test"
while True:
    content = requests.get(url)
    if "successful" in content.text:
        print(content.text)

执行成功:

漏洞修复

漏洞原因:因为servlet只初始化一次,不同的请求对servlet中的成员变量同时进行操作,将可能出现非法的操作。

那么如何解决线程安全问题呢?

  • 实现singleThreadModel
  • 避免使用成员变量
  • 同步共享数据

1.实现SingleThreadModel接口

SingleThreadModel接口,以确保servlet一次只能处理一个请求。但是从Servlet API开始,该接口目前已经被弃用,因为它没有解决所有线程安全问题,例如静态变量和会话属性可以被多个线程同时访问,即使我们已经实现了 SingleThreadModel 接口。因此建议使用其他方式来解决这些线程安全问题,例如同步块等。

package com;

import javax.servlet.SingleThreadModel;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class butlerServlet extends HttpServlet implements SingleThreadModel {
    public boolean status;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String cmd = req.getParameter("cmd");
        status = true;
        if (cmd.equals("shell")) {
            status = false;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (!status) {
            //resp.getWriter().println("you are worng");
            return;
        }
        if(cmd.equals("shell")) {
            resp.getWriter().println("you are execute successful!");
        }
    }
}

最终效果:

2.避免使用成员变量

因为问题出现在多个线程操作成员变量,去掉该成员变量就OK了

package com;

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

public class butlerServlet extends HttpServlet {
    //public boolean status;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        boolean status;
        String cmd = req.getParameter("cmd");
        status = true;
        if (cmd.equals("shell")) {
            status = false;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (!status) {
            //resp.getWriter().println("you are worng");
            return;
        }
        if (cmd.equals("shell")) {
            resp.getWriter().println("you are execute successful!");
        }
    }
}

最终效果:

3.synchronized同步锁

Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。synchroized有三种用法:

  • 修饰实例方法

  • 修饰静态方法

  • 修饰代码块

    synchronized(this){ //这里的this指的是执行这段代码的对象
        //互斥代码
    }
    

处理方式:

package com;

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

public class butlerServlet extends HttpServlet {
    public boolean status;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String cmd = req.getParameter("cmd");
        synchronized (this) {
            status = true;
            if (cmd.equals("shell")) {
                status = false;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (!status) {
                //resp.getWriter().println("you are worng");
                return;
            }
            if (cmd.equals("shell")) {
                resp.getWriter().println("you are execute successful!");
            }
        }
    }
}

最终效果:

思考与分析

那么上面的漏洞修复方法就是最好的解决办法吗?

实现SingleThreadModel接口,为每一个请求都创建一个servlet实例,这样无疑加重了系统的负担。而synchronized修饰词取保了同一时间只有一个线程才可以访问这片代码块,那么在高并发的时候这有是一个无法解决的问题。

参考链接

Java的synchronized

y4tacker的文章:https://blog.csdn.net/solitudi/article/details/122781947?spm=1001.2014.3001.5501

posted @ 2022-07-14 17:38  B0T1eR  阅读(44)  评论(0编辑  收藏  举报