7

I'm trying to disable HTTP TRACE method in embedded Jetty. In Jetty doc's is info that HTTP trace is disabled by default, but for embedded it is still enabled. I was trying to disable trace as a security constraint as is done in jetty.xml.

    ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY);
    servletHandler.setClassLoader(Server.class.getClassLoader());
    servletHandler.setContextPath("/");
    servletHandler.addEventListener(new ContextLoaderListener());
    servletHandler.addServlet(new ServletHolder(new CXFServlet()), "/*");
    servletHandler.setInitParameter("contextClass", AnnotationConfigWebApplicationContext.class.getName());
    servletHandler.setInitParameter("contextConfigLocation", BeansConfig.class.getName());
    servletHandler.setInitParameter("javax.ws.rs.Application", DispatcherConfig.class.getName());

     /*
     * <security-constraint>
     * <web-resource-collection>
     * <web-resource-name>Disable TRACE</web-resource-name>
     * <url-pattern>/</url-pattern>
     * <http-method>TRACE</http-method>
     * </web-resource-collection>
     * <auth-constraint/>
     * </security-constraint>
     */
     Constraint constraint = new Constraint();
     constraint.setName("Disable TRACE");

     ConstraintMapping mapping = new ConstraintMapping();
     mapping.setConstraint(constraint);
     mapping.setMethod("TRACE");
     mapping.setPathSpec("/"); // this did not work same this mapping.setPathSpec("/*");

     ConstraintSecurityHandler securityHandler = (ConstraintSecurityHandler) servletHandler.getSecurityHandler();
     securityHandler.addConstraintMapping(mapping);

Example output from soapUI:

HTTP/1.1 200 OK
Content-Type: message/http
Content-Length: 143
Server: Jetty(9.0.6.v20130930)

TRACE / HTTP/1.1
Connection: keep-alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
Host: 192.168.33.115
Accept-Encoding: gzip,deflate
Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136
Michal N.
  • 73
  • 1
  • 1
  • 3

5 Answers5

8

Extending the Server class and overriding the handle() method worked best for me.

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;

public class MyServer extends Server {

    @Override
    public void handle(HttpChannel<?> connection) throws IOException, ServletException {
        Request request=connection.getRequest();
        Response response=connection.getResponse();

        if ("TRACE".equals(request.getMethod())){
            request.setHandled(true);
            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
        } else {
            super.handle(connection);
        }
    }
}
jparker
  • 81
  • 1
  • 5
  • An `HttpConfiguration.Customizer` would probably be more appropriate, easier, and more future proof. (the `Server.handle()` API has changed a few times over the years) – Joakim Erdfelt Jan 21 '22 at 17:06
4

On your Constraint object, you need to call setAuthenticate(true), and ensure that you don't call setRoles(String[]). This makes it the equivalent of a <security-constraint> with an empty <auth-constraint>, which forbids access.

The reason it works with the DefaultServlet and not the CXFServlet is because the DefaultServlet specifically denies access to the TRACE method.

Robert
  • 1,286
  • 1
  • 17
  • 37
Jan
  • 1,287
  • 7
  • 7
3

You can create a filter:

...
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        if ("TRACE".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
            return;
        }
        chain.doFilter(request, response);
    }
....
idmitriev
  • 4,619
  • 4
  • 28
  • 44
  • this is what I'd do, as its not tied to Jetty or anything else. Also, trivial to reason about. – eis Aug 25 '17 at 13:25
  • If you decide to go this route ... be aware that a Filter won't be applied to non-context requests, and Filter behavior is subject to DispatcherType rules (which is very often overlooked). – Joakim Erdfelt Jan 21 '22 at 17:04
2

Jan's answer works (of course). However, it will cause jetty to print a warning on startup.

WARNING ... SecurityHandler ... has uncovered http methods for path ...

To avoid this first add the constraint as in Jan's answer (disable TRACE) and then add another constraint (allow everything but TRACE). I invoke the following code for each ServletContextHandler:

private void disableTraceMethodForHandler(final ServletContextHandler servletContextHandler) {
    SecurityHandler securityHandler = servletContextHandler.getSecurityHandler();
    if (securityHandler == null) {
        securityHandler = new ConstraintSecurityHandler();
        servletContextHandler.setSecurityHandler(securityHandler);
    }
    if (securityHandler instanceof ConstraintSecurityHandler) {
        ConstraintSecurityHandler constraintSecurityHandler = (ConstraintSecurityHandler) securityHandler;

        ConstraintMapping disableTraceMapping = new ConstraintMapping();
        Constraint disableTraceConstraint = new Constraint();
        disableTraceConstraint.setName("Disable TRACE");
        disableTraceConstraint.setAuthenticate(true);
        disableTraceMapping.setConstraint(disableTraceConstraint);
        disableTraceMapping.setPathSpec("/");
        disableTraceMapping.setMethod("TRACE");
        constraintSecurityHandler.addConstraintMapping(disableTraceMapping);

        ConstraintMapping enableEverythingButTraceMapping = new ConstraintMapping();
        Constraint enableEverythingButTraceConstraint = new Constraint();
        enableEverythingButTraceConstraint.setName("Enable everything but TRACE");
        enableEverythingButTraceMapping.setConstraint(enableEverythingButTraceConstraint);
        enableEverythingButTraceMapping.setMethodOmissions(new String[] {"TRACE"});
        enableEverythingButTraceMapping.setPathSpec("/");
        constraintSecurityHandler.addConstraintMapping(enableEverythingButTraceMapping);
    }
}

I figured out this solution after I found this issue in Google's appengine-java-vm-runtime and the fix, both by Jan. The code above should do the same as the XML config there.

harsel
  • 1,985
  • 1
  • 12
  • 10
0

Instead of using a Constraint, you can use an HttpConfiguration.Customizer.

Bonus is that this will apply to all request, even if they don't belong to a context, and works across all context specific web apps too.

package jetty;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.EnumSet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;

public class RejectHttpMethodsDemo
{
    public static class BanHttpMethods implements HttpConfiguration.Customizer
    {
        private final EnumSet<HttpMethod> bannedMethods;

        public BanHttpMethods(EnumSet<HttpMethod> bannedMethods)
        {
            this.bannedMethods = bannedMethods;
        }

        @Override
        public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
        {
            HttpMethod httpMethod = HttpMethod.fromString(request.getMethod());
            if (bannedMethods.contains(httpMethod))
            {
                request.setHandled(true);
                request.getResponse().setStatus(HttpStatus.METHOD_NOT_ALLOWED_405);
            }
        }
    }

    public static void main(String[] args) throws Exception
    {
        Server server = new Server();

        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.addCustomizer(new BanHttpMethods(EnumSet.of(HttpMethod.TRACE, HttpMethod.MOVE)));
        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
        connector.setPort(9090);
        server.addConnector(connector);

        HandlerList handlers = new HandlerList();
        handlers.addHandler(new AbstractHandler()
        {
            @Override
            public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
            {
                baseRequest.setHandled(true);
                response.setCharacterEncoding("utf-8");
                response.setContentType("text/plain");
                response.getWriter().printf("Hello, You asked to %s %s, that is all%n", baseRequest.getMethod(), baseRequest.getRequestURI());
            }
        });
        handlers.addHandler(new DefaultHandler());

        server.setHandler(handlers);
        server.start();

        try
        {
            HttpClient httpClient = HttpClient.newHttpClient();

            demoRequest(httpClient, server.getURI().resolve("/apple"), "GET");
            demoRequest(httpClient, server.getURI().resolve("/banana"), "TRACE");
            demoRequest(httpClient, server.getURI().resolve("/cherry"), "MOVE");
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
        finally
        {
            server.stop();
        }
    }

    private static void demoRequest(HttpClient httpClient, URI path, String method)
    {
        try
        {
            HttpRequest httpRequest = HttpRequest.newBuilder(path)
                .method(method, HttpRequest.BodyPublishers.noBody())
                .build();
            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
            System.out.printf("HTTP %s %s Response Status: %d%n", httpRequest.method(), httpRequest.uri(), httpResponse.statusCode());
            System.out.println(httpResponse.body());
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

With output ...

2022-01-21 11:23:13.718:INFO::main: Logging initialized @210ms to org.eclipse.jetty.util.log.StdErrLog
2022-01-21 11:23:13.775:INFO:oejs.Server:main: jetty-9.4.43.v20210629; built: 2021-06-30T11:07:22.254Z; git: 526006ecfa3af7f1a27ef3a288e2bef7ea9dd7e8; jvm 11.0.12+7
2022-01-21 11:23:13.809:INFO:oejs.AbstractConnector:main: Started ServerConnector@4fcd19b3{HTTP/1.1, (http/1.1)}{0.0.0.0:9090}
2022-01-21 11:23:13.812:INFO:oejs.Server:main: Started @308ms
HTTP GET http://127.0.1.1:9090/apple Response Status: 200
Hello, You asked to GET /apple, that is all

HTTP TRACE http://127.0.1.1:9090/banana Response Status: 405

HTTP MOVE http://127.0.1.1:9090/cherry Response Status: 405

2022-01-21 11:23:14.186:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@4fcd19b3{HTTP/1.1, (http/1.1)}{0.0.0.0:9090}
Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136