【问题标题】:How to leave client waiting for Java JAX-RS service to prevent DOS如何让客户端等待 Java JAX-RS 服务以防止 DOS
【发布时间】:2015-12-28 17:22:43
【问题描述】:

我遇到了一个 Web 服务问题,用户试图通过循环随机 ID 来猜测应用程序 ID。

错误请求来自随机 IP,所以我不能只禁止他们的 IP(除非我动态地这样做,但我还没有调查)。

目前,当我检测到一个客户端尝试了 10 次错误的应用 ID 尝试时,我会将它们放在我的应用中的阻止列表中,并在当天拒绝来自该 IP 的进一步请求。

我想尽量减少我的服务器需要做的工作量,因为坏客户端即使被拒绝也会继续发送 1000 多个请求。我知道有动态防火墙解决方案,但现在想在我的应用程序中轻松实现。目前我正在睡觉 5 秒以减少呼叫,但我想做的只是不向客户端发送响应,所以它必须超时。

有人知道如何在 Java 中,在 JAX-RS 中做到这一点吗?

我的服务是这样的,

@Path("/api")
public class MyServer {

@GET
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
@Path("/my-request")
public String myRequest(String type,
    @Context HttpServletRequest requestContext,
    @Context HttpServletResponse response) {
...
}

见: How to stop hack/DOS attack on web API

【问题讨论】:

  • 攻击者可能不在乎请求是否超时。听起来它来自机器人农场,这意味着永远打开连接是无关紧要的。我不了解 JAX RS,但在 Java 中,我会查看异步 servlet(或延续)以不占用休眠 5 秒的服务器线程。抱歉,我不知道如何在 JAXRS 中做到这一点!
  • 添加睡眠会减少调用,所以让它超时应该会进一步减少调用,我认为它来自 Androd 应用程序,而不是农场
  • 嘿@James,您是否尝试过将客户端重定向到非常慢的地方或其他地方的大文件?如果客户端接受重定向,它将被占用一段时间,您不再需要休眠线程。
  • @konqi 我认为攻击者很快就会提出一个解决方案来避免跟随重定向:) 此外,攻击者没有注意到他在被阻止时会等待 5 秒,但是当他这样做时,他肯定会停止等待响应,即他将尝试发出并行请求。所以,James,你的解决方案可能不会有很长时间,除非请求确实来自移动设备,在这种情况下,你可以希望移动设备的所有者会注意到最终的大流量并卸载应用程序。
  • 你能改变/修改你的 REST API 吗?

标签: java web-services rest jax-rs ddos


【解决方案1】:

有很多可能的解决方案,根据您给出的限制,我想到了 2 个可能的解决方案:

1) 使用已经支持限制请求的转发代理。我个人使用过Nginx 并且可以推荐它,部分原因是它易于设置。相关限速配置:Limit Req Module

2) 使用Asynchronous JAX-RS 让您检测到的恶意请求超时。理智的请求可以直接处理。但要注意后果,无论哪种方式,这种方法都会消耗服务器上的资源!

【讨论】:

    【解决方案2】:

    我还没有尝试过这个......只是在黑暗中拍摄,所以带着一粒盐。所以问题是,一旦你检测到一些可疑的东西并将 IP 置于块模式,你不想在这个请求上浪费另一部分资源,也不想让他们浪费时间超时。但是,如果您抛出异常,您的框架会做出响应。打断你当前的线程怎么样?您可以通过Thread.currentThread().interrupt(); 执行此操作。希望处理请求的 Java 容器会检查中断状态。它可能不会那样做。我知道我见过 IO 相关的类因为设置了中断标志而不处理请求。

    编辑:如果中断你的线程不起作用,你也可以尝试抛出一个 InterruptedException。它可能会达到预期的效果。

    【讨论】:

      【解决方案3】:

      据我所知,在 Servlet 规范中并没有这样的机制。由于 JAX-RS 实现使用 servlet(至少是 Jersey 和 Resteasy),因此在 Java 中没有标准的方法来实现。

      使用异步 JAX-RS 的想法比 Thread.sleep(5000) 更好,但它仍然会使用一些资源,并且只是稍后处理请求的一种方式,而不是永远忽略请求。

      【讨论】:

      【解决方案4】:

      您正在寻找 JAX-RS 支持的 asynchronous responsestutorial for Jersey 包含一些如何实现对请求的异步响应的示例。

      对于异步响应,负责响应请求的线程在请求​​完成之前就已经被释放以处理另一个请求。通过添加带有@Suspended 注释的参数来激活此功能。您还需要做的是注册一个专用的scheduler,该scheduler 负责在给定超时后唤醒您的请求,如下例所示:

      @Path("/api")
      public class MyServer {
      
        private ScheduledExecutorService scheduler = ...;
      
        @GET
        @Consumes(MediaType.APPLICATION_XML)
        @Produces(MediaType.APPLICATION_XML)
        @Path("/my-request")
        public String myRequest(String type,
                                @Context HttpServletRequest requestContext,
                                @Context HttpServletResponse response,
                                @Suspended AsyncResponse asyncResponse) {
          scheduler.schedule(new Runnable() {
            @Override
            public void run() {
              asyncResponse.resume(...)
            }
          }, 5, TimeUnit.SECOND);
        }
      }
      

      这样,在 5 秒的等待时间内没有线程被阻塞,这为同时处理其他请求提供了机会。

      JAX-RS 不提供在没有答案的情况下完全丢弃请求的方法。您需要保持连接打开以产生超时,如果您终止连接,则会通知用户终止。你能做的最好的就是永远不要回答异步请求,但这仍然会消耗一些资源。如果您想避免这种情况,则必须在 JAX-RS 之外解决问题,例如通过另一台服务器代理请求。

      一种方法是发送至set up mod_proxy,您可以在该处使用恶意请求的错误代码回答代理,并为此类请求设置非常大的重试限制。

      【讨论】:

      • 这肯定比Thread.sleep(5000) 好,但是资源还是会被消耗。据我了解这个问题,OP 要求始终忽略该请求,而不是在 5 秒后返回处理它。
      • 在这种情况下,可能永远不会恢复异步响应,但有必要保持连接打开。否则,将通知客户取消。但是,保持连接打开仍然会消耗一些资源。
      • 完全正确:从不恢复异步响应将消耗操作系统的资源,因为连接保持打开状态。因此,这不是一个好主意。 OP 希望静默地丢弃连接和所有相关资源,让客户端连接超时。即使您从不恢复异步响应,也不会获得所需的行为。
      • 这是一个非常好的解决方案。异步响应将消耗 非常 最少的资源(假设容器中正在使用 NIO)。保持连接打开并不意味着为其分配线程。我确信一个普通的服务器一次可以支持至少 10,000 个这样的请求,这是您可以通过此设置获得的最佳结果。只需确保您使用的调度程序是单线程池即可。
      • 如果你使用的是 Grizzly 连接器,很容易几十万。问题是,服务器需要处理多少个请求。
      【解决方案5】:

      我可能会建议将 IP 拒绝逻辑从 REST 移至普通 HTTP 过滤器:

      @WebFilter(urlPatterns = "/*", asyncSupported = true)
      @WebListener
      public class HttpFilter implements Filter {
      
          @Override
         public void init(FilterConfig filterConfig) throws ServletException {   }
      
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              if(denyIP(servletRequest.getRemoteAddr())) {
                  AsyncContext asyncContext = servletRequest.startAsync();
                  asyncContext.setTimeout(100000);
              }else{
                 filterChain.doFilter(servletRequest, servletResponse);
              }
          }
      
          @Override
          public void destroy() {   }
      
          private boolean denyIP(String address){
               //todo
               return true;
          }
      
      }
      

      应用服务器更便宜:无需 XML/JSON 反序列化,无需调用 REST 类。你也可能注意到我从不打电话给asyncContext.start。我检查 Wildfly 8.2 服务器。在这种情况下,Wildfly 不会为每个请求使用线程。我发送了很多请求,但线程数量是恒定的。

      PS

      尝试通过循环随机 ID 来猜测应用程序 ID

      这不是 DOS 攻击。这是蛮力攻击。

      【讨论】:

      • “在这种情况下,Wildfly 不会为每个请求使用线程。我发送了很多请求,但线程数量是恒定的。” 那是因为您从未调用过asyncContext.start()?
      • 是的。 Wildfly 将过期任务加入队列,有线程池用于处理队列,但是这个线程池分配的线程数量有限。
      【解决方案6】:

      您可以尝试 Tomcat 3.0 支持的 asyncContext。 此功能将 Web 请求处理程序和处理程序解耦。在您的情况下,接受请求的线程必须等待/睡眠超过配置的超时时间。让同一个线程休眠这么长时间会使它们饿死,并且会极大地影响服务器的性能。因此,异步处理是正确的方法。

      我已经使用 asyncContext 和 Java 单线程执行器 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html 它对我来说效果很好。我有类似的商业案例,我不得不模拟我的应用程序。

      参考这个实现http://peter-braun.org/2013/04/asynchronous-processing-from-servlet-3-0-to-jax-rs-2-0/

      单线程执行器不会占用资源,非常适合这种用例。

      【讨论】:

        【解决方案7】:

        我曾经通过创建一个 TCP/IP 隧道应用程序解决了类似的问题。

        这是一个侦听外部端口(例如 http 端口 80)的小型应用程序。在正常情况下,所有收到的调用都会被接受,从而创建专用的套接字对象。 这些单独的套接字然后调用真实的(隐藏的)网络服务器,该服务器在同一物理服务器或本地网络中的任何服务器上运行。

        隧道本身类似于调度程序,但也可以充当负载均衡器。它可以是多功能的。

        问题是此时您正在使用套接字进行低级工作。如果您将禁止的 IP 地址列表交给该应用程序,那么它可以在非常低的级别上关闭应用程序(甚至根本不接受它们的套接字调用)。

        您也可以将它集成到同一个 java 应用程序中。但我认为将其视为一个单独的应用程序更灵活。

        (如果您需要一些源代码,请告诉我,我可能有一些代码可以帮助您入门)

        【讨论】:

        • 我意识到这个答案有点牵强,我想有些人可能想要否决它。但是由于没有其他人提到这种方法,......我只是不想让你忽视这个选项。这只是一个开箱即用的想法。 :) 我希望你不会讨厌它。
        猜你喜欢
        • 2013-06-03
        • 1970-01-01
        • 2014-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多