web-dev-qa-db-ja.com

SpringでRequestRejectedExceptionをインターセプトする方法は?

TomcatログにRequestRejectedExceptionエントリがトンあります(以下に貼り付けたサンプル)。これらは、数か月前のマイナーバージョンアップグレード(Spring Security 4.2.4、IIRC)後にログファイルに表示されるようになったため、これは明らかにデフォルトで有効になっているSpringの新しいセキュリティ機能です。同様の問題は ここで報告 ですが、私の質問は特にコントローラーでこれらの例外をインターセプトする方法に関係しています。この問題について文書化されたSpring Securityのバグがあります( RequestRejectedExceptionを処理する方法を提供 )。ただし、Spring 5.1まではこの問題の修正を対象としていません。

これらの例外がスローされる理由 を理解しており、 このセキュリティ機能を無効にする はしたくない。

次のように、この機能をある程度制御したいと思います。

  1. 私は自分のサイトから正当なユーザーをブロックしていないことを知っています。
  2. これをトリガーしているリクエストを確認できます(SQLインジェクション攻撃ですか?)
  3. サーバーの応答を調整できます。 Spring Securityファイアウォールは、500 Internal Server Error(これは非常に間違っています。これは400 Bad Requestである必要があります)とともに、完全なスタックトレースをWebクライアントにダンプします(情報漏えい)。

要求されたURLをログに記録する方法を見つけたいが、有用な情報を提供せずにログファイルを汚染しているため、これらの例外のスタックトレースも抑制したい。最適なのは、これらの例外をインターセプトして、Tomcatログで報告するのではなく、アプリケーション層で処理することです。

たとえば、これは私のcatalina.outに毎日表示されるこれらのログエントリの1つです。

Aug 10, 2018 2:01:36 PM org.Apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [] threw exception
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
        at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.Java:265)
        at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.Java:245)
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.Java:193)
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.Java:177)
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.Java:347)
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.Java:263)
        at org.Apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.Java:193)
        at org.Apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.Java:166)
        at org.Apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.Java:198)
        at org.Apache.catalina.core.StandardContextValve.invoke(StandardContextValve.Java:96)
        at org.Apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.Java:496)
        at org.Apache.catalina.core.StandardHostValve.invoke(StandardHostValve.Java:140)
        at org.Apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.Java:81)
        at org.Apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.Java:87)
        at org.Apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.Java:342)
        at org.Apache.coyote.ajp.AjpProcessor.service(AjpProcessor.Java:486)
        at org.Apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.Java:66)
        at org.Apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.Java:790)
        at org.Apache.Tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.Java:1459)
        at org.Apache.Tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.Java:49)
        at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1149)
        at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:624)
        at org.Apache.Tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.Java:61)
        at Java.lang.Thread.run(Thread.Java:748)

2日間で3,200以上を見ていますが、catalina.outログファイルの最大の貢献者となり、他の正当な問題が見られなくなるまでになりました。本質的に、この新しいSpring Security機能は組み込みのサービス拒否の形式であり、4月から私の時間を無駄にしています。私はそれが重要な機能ではなく、単にデフォルトの実装が完全に失敗していると言っているのではなく、開発者としてもシステム管理者としても、それを制御する方法を見つけたいと思っています。

Springでは、他の多くの例外タイプ(IOExceptionを含む)をインターセプトするために、カスタムエラーコントローラーを使用します。ただし、RequestRejectedExceptionは何らかの理由で失敗しているようです。

これは私のErrorController.Javaの関連部分であり、私が達成しようとしていることのアイデアを提供します。

@ControllerAdvice
public final class ErrorController
{
    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName());

    /**
     * Generates an Error page by intercepting exceptions generated from HttpFirewall.
     *
     * @param ex A RequestRejectedException exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(RequestRejectedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleRequestRejectedException(final HttpServletRequest request, final RequestRejectedException ex)
    {
        if (LOGGER.isLoggable(Level.INFO))
        {
            LOGGER.log(Level.INFO, "Request Rejected", ex);
        }

        LOGGER.log(Level.WARNING, "Rejected request for [" + request.getRequestURL().toString() + "]. Reason: " + ex.getMessage());
        return "errorPage";
    }

    /**
     * Generates a Server Error page.
     *
     * @param ex An exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleException(final Exception ex)
    {
        if (LOGGER.isLoggable(Level.SEVERE))
        {
            LOGGER.log(Level.SEVERE, "Server Error", ex);
        }

        return "errorPage";
    }
}

このエラーコントローラーは、多くの例外に対して機能します。たとえば、次のIllegalStateExceptionを正常にインターセプトしました。

Aug 05, 2018 7:50:30 AM com.mycompany.spring.controller.ErrorController handleException
SEVERE: Server Error
Java.lang.IllegalStateException: Cannot create a session after the response has been committed
        at org.Apache.catalina.connector.Request.doGetSession(Request.Java:2999)
...

ただし、これはRequestRejectedExceptionをインターセプトしていません(上記の最初のログサンプルに「サーバーエラー」がないことからわかるように)。

エラーコントローラーでRequestRejectedExceptionをインターセプトするにはどうすればよいですか?

21
vallismortis

HttpFirewallStrictHttpFirewallにはいくつかの設計エラー(以下のコードに記載)が含まれていますが、Spring SecurityのOne True Firewallこれらのフラグ付きリクエストをreal(永続的)に渡すことができるHttpFirewallへのリクエスト属性を介してHandlerInterceptor情報をトンネル最初にそれらにフラグを立てた元のビジネスロジックを犠牲にすることなくファイアウォール。ここで文書化されたメソッドは、HttpFirewallインターフェースからの単純なコントラクトに準拠し、残りは単にSpring FrameworkのコアとJava Servlet API 。

これは基本的に、より複雑ですが、より完全な代替手段である 以前の回答 です。この回答では、StrictHttpFirewallの新しいサブクラスを実装し、拒否されたリクエストを特定のログレベルでインターセプトしてログを記録しますが、HTTPリクエストに属性を追加して、ダウンストリームフィルター(またはコントローラー)が処理するようにフラグを立てます。また、このAnnotatingHttpFirewallinspect()メソッドを提供し、サブクラスが要求をブロックするためのカスタムルールを追加できるようにします。

このソリューションは、(1)Spring Securityと(2)Spring Framework(Core)の2つの部分に分かれています。そもそもこの問題の原因となった格差、そしてこれはそれをどのように埋めるかを示しています。

参考のため、これはSpring 4.3.17およびSpring Security 4.2.6でテストされています。 Spring 5.1がリリースされると、大幅な変更が加えられる可能性があります。


パート1:Spring Security

これは、Spring Security内でロギングとフラグ付けを実行するソリューションの半分です。


AnnotatingHttpFirewall.Java

import Java.util.logging.Level;
import Java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.StrictHttpFirewall;

/**
 * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
 */
public class AnnotatingHttpFirewall extends StrictHttpFirewall
{
    /**
     * The name of the HTTP header representing a request that has been rejected by this firewall.
     */
    public static final String HTTP_HEADER_REQUEST_REJECTED_FLAG = "X-HttpFirewall-RequestRejectedFlag";

    /**
     * The name of the HTTP header representing the reason a request has been rejected by this firewall.
     */
    public static final String HTTP_HEADER_REQUEST_REJECTED_REASON = "X-HttpFirewall-RequestRejectedReason";

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(AnnotatingHttpFirewall.class.getName());

    /**
     * Default constructor.
     */
    public AnnotatingHttpFirewall()
    {
        super();
        return;
    }

    /**
     * Provides the request object which will be passed through the filter chain.
     *
     * @param request The original HttpServletRequest.
     * @returns A FirewalledRequest (required by the HttpFirewall interface) which
     *          inconveniently breaks the general contract of ServletFilter because
     *          we can't upcast this to an HttpServletRequest. This prevents us
     *          from re-wrapping this using an HttpServletRequestWrapper.
     */
    @Override
    public FirewalledRequest getFirewalledRequest(final HttpServletRequest request)
    {
        try
        {
            this.inspect(request); // Perform any additional checks that the naive "StrictHttpFirewall" misses.
            return super.getFirewalledRequest(request);
        } catch (RequestRejectedException ex) {
            final String requestUrl = request.getRequestURL().toString();

            // Override some of the default behavior because some requests are
            // legitimate.
            if (requestUrl.contains(";jsessionid="))
            {
                // Do not block non-cookie serialized sessions. Google's crawler does this often.
            } else {
                // Log anything that is blocked so we can find these in the catalina.out log.
                // This will give us any information we need to make
                // adjustments to these special cases and see potentially
                // malicious activity.
                if (LOGGER.isLoggable(Level.WARNING))
                {
                    LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
                }

                // Mark this request as rejected.
                request.setAttribute(HTTP_HEADER_REQUEST_REJECTED, Boolean.TRUE);
                request.setAttribute(HTTP_HEADER_REQUEST_REJECTED_REASON, ex.getMessage());
            }

            // Suppress the RequestBlockedException and pass the request through
            // with the additional attribute.
            return new FirewalledRequest(request)
            {
                @Override
                public void reset()
                {
                    return;
                }
            };
        }
    }

    /**
     * Provides the response which will be passed through the filter chain.
     * This method isn't extensible because the request may already be committed.
     * Furthermore, this is only invoked for requests that were not blocked, so we can't
     * control the status or response for blocked requests here.
     *
     * @param response The original HttpServletResponse.
     * @return the original response or a replacement/wrapper.
     */
    @Override
    public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
    {
        // Note: The FirewalledResponse class is not accessible outside the package.
        return super.getFirewalledResponse(response);
    }

    /**
     * Perform any custom checks on the request.
     * This method may be overridden by a subclass in order to supplement or replace these tests.
     *
     * @param request The original HttpServletRequest.
     * @throws RequestRejectedException if the request should be rejected immediately.
     */
    public void inspect(final HttpServletRequest request) throws RequestRejectedException
    {
        final String requestUri = request.getRequestURI(); // path without parameters
//        final String requestUrl = request.getRequestURL().toString(); // full path with parameters

        if (requestUri.endsWith("/wp-login.php"))
        {
            throw new RequestRejectedException("The request was rejected because it is a vulnerability scan.");
        }

        if (requestUri.endsWith(".php"))
        {
            throw new RequestRejectedException("The request was rejected because it is a likely vulnerability scan.");
        }

        return; // The request passed all custom tests.
    }
}

WebSecurityConfig.Java

WebSecurityConfigで、HTTPファイアウォールをAnnotatingHttpFirewallに設定します。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * Default constructor.
     */
    public WebSecurityConfig()
    {
        super();
        return;
    }

    @Override
    public final void configure(final WebSecurity web) throws Exception
    {
        super.configure(web);
        web.httpFirewall(new AnnotatingHttpFirewall()); // Set the custom firewall.
        return;
    }
}

パート2:Spring Framework

このソリューションの2番目の部分は、おそらくServletFilterまたはHandlerInterceptorとして実装できます。 HandlerInterceptorのパスに移動します。これは、最も柔軟性が高く、Spring Framework内で直接機能するように見えるためです。


RequestBlockedException.Java

このカスタム例外は、エラーコントローラーによって処理できます。これは、アプリケーションのビジネスロジック(永続的なファイアウォールなど)に関連する可能性のある未加工の要求(完全な要求自体も含む)から利用可能な要求ヘッダー、パラメーター、またはプロパティを追加するために拡張できます。

/**
 * A custom exception for situations where a request is blocked or rejected.
 */
public class RequestBlockedException extends RuntimeException
{
    private static final long serialVersionUID = 1L;

    /**
     * The requested URL.
     */
    private String requestUrl;

    /**
     * The remote address of the client making the request.
     */
    private String remoteAddress;

    /**
     * A message or reason for blocking the request.
     */
    private String reason;

    /**
     * The user agent supplied by the client the request.
     */
    private String userAgent;

    /**
     * Creates a new Request Blocked Exception.
     *
     * @param reqUrl The requested URL.
     * @param remoteAddr The remote address of the client making the request.
     * @param userAgent The user agent supplied by the client making the request.
     * @param message A message or reason for blocking the request.
     */
    public RequestBlockedException(final String reqUrl, final String remoteAddr, final String userAgent, final String message)
    {
        this.requestUrl = reqUrl;
        this.remoteAddress = remoteAddr;
        this.userAgent = userAgent;
        this.reason = message;
        return;
    }

    /**
     * Gets the requested URL.
     *
     * @return A URL.
     */
    public String getRequestUrl()
    {
        return this.requestUrl;
    }

    /**
     * Gets the remote address of the client making the request.
     *
     * @return A remote address.
     */
    public String getRemoteAddress()
    {
        return this.remoteAddress;
    }

    /**
     * Gets the user agent supplied by the client making the request.
     *
     * @return  A user agent string.
     */
    public String getUserAgent()
    {
        return this.userAgent;
    }

    /**
     * Gets the reason for blocking the request.
     *
     * @return  A message or reason for blocking the request.
     */
    public String getReason()
    {
        return this.reason;
    }
}

FirewallInterceptor.Java

このインターセプターは、Spring Securityフィルターが実行された後(つまり、AnnotatingHttpFirewallが拒否すべきリクエストにフラグを立てた後、呼び出されます。このインターセプターは、リクエストのフラグ(属性)を検出し、エラーコントローラーができるカスタム例外を発生させます)扱う。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * Intercepts requests that were flagged as rejected by the firewall.
 */
public final class FirewallInterceptor implements HandlerInterceptor
{
    /**
     * Default constructor.
     */
    public FirewallInterceptor()
    {
        return;
    }

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception
    {
        if (Boolean.TRUE.equals(request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED)))
        {
            // Throw a custom exception that can be handled by a custom error controller.
            final String reason = (String) request.getAttribute(AnnotatingHttpFirewall.HTTP_HEADER_REQUEST_REJECTED_REASON);
            throw new RequestRejectedByFirewallException(request.getRequestURL().toString(), request.getRemoteAddr(), request.getHeader(HttpHeaders.USER_AGENT), reason);
        }

        return true; // Allow the request to proceed normally.
    }

    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception
    {
        return;
    }

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception
    {
        return;
    }
}

WebConfig.Java

WebConfigで、FirewallInterceptorをレジストリに追加します。

@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{
    /**
     * Among your other methods in this class, make sure you register
     * your Interceptor.
     */
    @Override
    public void addInterceptors(final InterceptorRegistry registry)
    {
        // Register firewall interceptor for all URLs in webapp.
        registry.addInterceptor(new FirewallInterceptor()).addPathPatterns("/**");
        return;
    }
}

ErrorController.Java

これは特に上記のカスタム例外を処理し、すべての関連情報を記録し、カスタムアプリケーションファイアウォールの特別なビジネスロジックを呼び出しながら、クライアントのクリーンなエラーページを生成します。

import Java.io.IOException;
import Java.util.logging.Level;
import Java.util.logging.Logger;

import org.springframework.web.servlet.NoHandlerFoundException;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import RequestBlockedException;

@ControllerAdvice
public final class ErrorController
{
    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(ErrorController.class.getName());

    /**
     * Generates an Error page by intercepting exceptions generated from AnnotatingHttpFirewall.
     *
     * @param request The original HTTP request.
     * @param ex A RequestBlockedException exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(RequestBlockedException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleRequestBlockedException(final RequestBlockedException ex)
    {
        if (LOGGER.isLoggable(Level.WARNING))
        {
            LOGGER.log(Level.WARNING, "Rejected request from " + ex.getRemoteAddress() + " for [" + ex.getRequestUrl() + "]. Reason: " + ex.getReason());
        }

        // Note: Perform any additional business logic or logging here.

        return "errorPage"; // Returns a Nice error page with the specified status code.
    }

    /**
     * Generates a Page Not Found page.
     *
     * @param ex A NoHandlerFound exception.
     * @return The tile definition name for the page.
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleException(final NoHandlerFoundException ex)
    {
        return "notFoundPage";
    }
}

FirewallController.Java

NoHandlerFoundExceptionをスローするデフォルトのマッピングを持つコントローラー。これは、 DispatcherServlet.noHandlerFound の鶏と卵の戦略を回避し、そのメソッドがalwaysを見つけて、FirewallInterceptor.preHandleが常に呼び出されます。これにより、RequestRejectedByFirewallExceptionよりもNoHandlerFoundExceptionが優先されます。

これが必要な理由:

here で述べたように、NoHandlerFoundExceptionDispatcherServletからスローされるとき(つまり、要求されたURLに対応するマッピングがないとき)、生成された例外を処理する方法はありません上記のファイアウォールから( preHandle() を呼び出す前にNoHandlerFoundExceptionがスローされるため、これらのリクエストは404ビューに到達します(私の場合は望ましい動作ではありません-あなたは多くの「URIを使用したHTTP要求のマッピングが見つかりません...」メッセージが表示されます)。これは、特別なヘッダーのチェックをnoHandlerFoundメソッドに移動することで修正できます。残念ながら、新しいDispatcher Servletをゼロから作成せずにこれを行う方法はないため、Spring Framework全体を破棄することもできます。 DispatcherServletを拡張することは、protected、private、およびfinalメソッドが混在しているため、そのプロパティにアクセスできない(ゲッターまたはセッターがない)ため、不可能です。実装できる共通インターフェースがないため、クラスをラップすることもできません。このクラスのデフォルトのマッピングは、すべてのロジックを回避するエレガントな方法を提供します。

重要な注意事項:以下のRequestMappingは、登録されているすべてのResourceHandlersよりも優先されるため、静的リソースの解決を妨げます。私はまだこれに対する回避策を探していますが、可能性の1つは、 this answer で提案されている静的リソースを処理する方法の1つを試すことです。

import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.NoHandlerFoundException;

@Controller
public final class FirewallController
{
    /**
     * The name of the model attribute (or request parameter for advertisement click tracking) that contains the request URL.
     */
    protected static final String REQUEST_URL = "requestUrl";

    /**
     * The name of the model attribute that contains the request method.
     */
    protected static final String REQUEST_METHOD = "requestMethod";

    /**
     * The name of the model attribute that contains all HTTP headers.
     */
    protected static final String REQUEST_HEADERS = "requestHeaders";

    /**
     * Default constructor.
     */
    public FirewallController()
    {
        return;
    }

    /**
     * Populates the request URL model attribute from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request URL.
     */
    @ModelAttribute(REQUEST_URL)
    public final String getRequestURL(final HttpServletRequest request)
    {
        return request.getRequestURL().toString();
    }

    /**
     * Populates the request method from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request method (GET, POST, HEAD, etc.).
     */
    @ModelAttribute(REQUEST_METHOD)
    public final String getRequestMethod(final HttpServletRequest request)
    {
        return request.getMethod();
    }

    /**
     * Gets all headers from the HTTP request.
     *
     * @param request The HTTP request.
     * @return The request headers.
     */
    @ModelAttribute(REQUEST_HEADERS)
    public final HttpHeaders getRequestHeaders(final HttpServletRequest request)
    {
        return FirewallController.headers(request);
    }

    /**
     * A catch-all default mapping that throws a NoHandlerFoundException.
     * This will be intercepted by the ErrorController, which allows preHandle to work normally.
     *
     * @param requestMethod The request method.
     * @param requestUrl The request URL.
     * @param requestHeaders The request headers.
     * @throws NoHandlerFoundException every time this method is invoked.
     */
    @RequestMapping(value = "/**") // NOTE: This prevents resolution of static resources. Still looking for a workaround for this.
    public void getNotFoundPage(@ModelAttribute(REQUEST_METHOD) final String requestMethod, @ModelAttribute(REQUEST_URL) final String requestUrl, @ModelAttribute(REQUEST_HEADERS) final HttpHeaders requestHeaders) throws NoHandlerFoundException
    {
        throw new NoHandlerFoundException(requestMethod, requestUrl, requestHeaders);
    }

    /**
     * Gets all headers from a HTTP request.
     *
     * @param request The HTTP request.
     * @return The request headers.
     */
    public static HttpHeaders headers(final HttpServletRequest request)
    {
        final HttpHeaders headers = new HttpHeaders();

        for (Enumeration<?> names = request.getHeaderNames(); names.hasMoreElements();)
        {
            final String headerName = (String) names.nextElement();

            for (Enumeration<?> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
            {
                headers.add(headerName, (String) headerValues.nextElement());
            }
        }

        return headers;
    }
}

結果

この両方の部分が機能している場合、次の2つの警告が記録されます(最初の警告はSpring Securityにあり、2番目の警告はSpring Framework(Core)ErrorControllerです)。これで、ロギングを完全に制御でき、必要に応じて調整できる拡張可能なアプリケーションファイアウォールを使用できます。

Sep 12, 2018 10:24:37 AM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
WARNING: Intercepted org.springframework.security.web.firewall.RequestRejectedException: Remote Host: 0:0:0:0:0:0:0:1 User Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0 Request URL: http://localhost:8080/webapp-www-mycompany-com/login.php
Sep 12, 2018 10:24:37 AM com.mycompany.spring.controller.ErrorController handleException
WARNING: Rejected request from 0:0:0:0:0:0:0:1 for [http://localhost:8080/webapp-www-mycompany-com/login.php]. Reason: The request was rejected because it is a likely vulnerability scan.
4
vallismortis

また、単純なフィルターで処理することもできます。これにより、404エラー応答が発生します

@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class LogAndSuppressRequestRejectedExceptionFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        try {
            chain.doFilter(req, res);
        } catch (RequestRejectedException e) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;

            log
                .warn(
                        "request_rejected: remote={}, user_agent={}, request_url={}",
                        request.getRemoteHost(),  
                        request.getHeader(HttpHeaders.USER_AGENT),
                        request.getRequestURL(), 
                        e
                );

            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }
}
20
lanwen

要求情報をコンソールに記録し、スタックトレースを抑制した新しい例外をスローするStrictHttpFirewallのサブクラスを実装しました。これで私の問題は部分的に解決されます(少なくとも、今は悪いリクエストを見ることができます)。

スタックトレースなしで拒否されたリクエストを表示するだけの場合、これが探している答えです。

コントローラでこれらの例外を処理する場合は、完全な(ただし少し複雑な)ソリューションについて accepted answer を参照してください。


LoggingHttpFirewall.Java

このクラスは StrictHttpFirewall を拡張してRequestRejectedExceptionをキャッチし、リクエストからのメタデータと抑制されたスタックトレースを含む新しい例外をスローします。

import Java.util.logging.Level;
import Java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.StrictHttpFirewall;

/**
 * Overrides the StrictHttpFirewall to log some useful information about blocked requests.
 */
public final class LoggingHttpFirewall extends StrictHttpFirewall
{
    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(LoggingHttpFirewall.class.getName());

    /**
     * Default constructor.
     */
    public LoggingHttpFirewall()
    {
        super();
        return;
    }

    /**
     * Provides the request object which will be passed through the filter chain.
     *
     * @returns A FirewalledRequest (required by the HttpFirewall interface) which
     *          inconveniently breaks the general contract of ServletFilter because
     *          we can't upcast this to an HttpServletRequest. This prevents us
     *          from re-wrapping this using an HttpServletRequestWrapper.
     * @throws RequestRejectedException if the request should be rejected immediately.
     */
    @Override
    public FirewalledRequest getFirewalledRequest(final HttpServletRequest request) throws RequestRejectedException
    {
        try
        {
            return super.getFirewalledRequest(request);
        } catch (RequestRejectedException ex) {
            if (LOGGER.isLoggable(Level.WARNING))
            {
                LOGGER.log(Level.WARNING, "Intercepted RequestBlockedException: Remote Host: " + request.getRemoteHost() + " User Agent: " + request.getHeader("User-Agent") + " Request URL: " + request.getRequestURL().toString());
            }

            // Wrap in a new RequestRejectedException with request metadata and a shallower stack trace.
            throw new RequestRejectedException(ex.getMessage() + ".\n Remote Host: " + request.getRemoteHost() + "\n User Agent: " + request.getHeader("User-Agent") + "\n Request URL: " + request.getRequestURL().toString())
            {
                private static final long serialVersionUID = 1L;

                @Override
                public synchronized Throwable fillInStackTrace()
                {
                    return this; // suppress the stack trace.
                }
            };
        }
    }

    /**
     * Provides the response which will be passed through the filter chain.
     * This method isn't extensible because the request may already be committed.
     * Furthermore, this is only invoked for requests that were not blocked, so we can't
     * control the status or response for blocked requests here.
     *
     * @param response The original HttpServletResponse.
     * @return the original response or a replacement/wrapper.
     */
    @Override
    public HttpServletResponse getFirewalledResponse(final HttpServletResponse response)
    {
        // Note: The FirewalledResponse class is not accessible outside the package.
        return super.getFirewalledResponse(response);
    }
}

WebSecurityConfig.Java

WebSecurityConfigで、HTTPファイアウォールをLoggingHttpFirewallに設定します。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * Default constructor.
     */
    public WebSecurityConfig()
    {
        super();
        return;
    }

    @Override
    public final void configure(final WebSecurity web) throws Exception
    {
        super.configure(web);
        web.httpFirewall(new LoggingHttpFirewall()); // Set the custom firewall.
        return;
    }
}

結果

このソリューションを実稼働環境に展開した後、StrictHttpFirewallのデフォルトの動作がGoogleのサイトのインデックス作成をブロックしていることがすぐにわかりました。

Aug 13, 2018 1:48:56 PM com.mycompany.spring.security.AnnotatingHttpFirewall getFirewalledRequest
WARNING: Intercepted RequestBlockedException: Remote Host: 66.249.64.223 User Agent: Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html) Request URL: https://www.mycompany.com/10.1601/tx.3784;jsessionid=692804549F9AB55F45DBD0AFE2A97FFD

これを発見するとすぐに、;jsessionid=を探してこれらの要求を許可する新しいバージョン( my other answer に含まれる)をすぐにデプロイしました。他のリクエストも通過する可能性がありますが、今ではこれらを検出する方法があります。

8
vallismortis

それを処理する別の方法は、 Spring AOP を使用することです。 FilterChainProxy.doFilter() メソッドに関するアドバイスを作成して、HttpFirewallによってスローされたRequestRejectedException(s)をキャッチし、400 BAD_REQUESTに変換することができます。

@Aspect
@Component
public class FilterChainProxyAdvice {

    @Around("execution(public void org.springframework.security.web.FilterChainProxy.doFilter(..))")
    public void handleRequestRejectedException (ProceedingJoinPoint pjp) throws Throwable {
        try {
            pjp.proceed();
        } catch (RequestRejectedException exception) {
            HttpServletResponse response = (HttpServletResponse) pjp.getArgs()[1]);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
        }
    }
}
0
Ahmed Sayed