3

I’m working on a project where I want to disable TRACE method for an embedded jetty server. As it turned out, it is not disabled by default for embedded server.

I tried creating a Constraint and even a filter using the following source: Java embedded jetty is accepting HTTP TRACE method

It works, but when I try OPTIONS method, it still shows TRACE as Allow.

How can I make sure the OPTIONS method shows that trace is not allowed?

Note:

OPTIONS returns with the following header; Allow: GET, OPTIONS, TRACE What I want is: Allow: GET, OPTIONS

EDIT

ServletContextHandler myContext = new ServletContextHandler();
            
ServletHolder servletHolder = new ServletHolder(new MyServlet());
myContext.addServlet(servletHolder, "/");
myContext.addFilter(MyFilter.class,"/",EnumSet.of(DispatcherType.REQUEST));
contextHandlers.add(myContext);
jhthewow
  • 133
  • 1
  • 3
  • 12
  • How are you using Embedded Jetty (it matters)? please edit your question and include your code example which includes your WebAppContext, or ServletContextHandler, or ServletHandler, or just plain Handler setup. – Joakim Erdfelt May 12 '21 at 12:47
  • @JoakimErdfelt added my ServletContextHandler – jhthewow May 12 '21 at 13:00

1 Answers1

0

There's a few moving parts ... disable TRACE from working, and remove TRACE from the OPTIONS Allow response header field.

First, lets tackle the easy one, disabling TRACE from working.
That's done with a Constraint mapping at the ServletContext level.
That will impact all usage of TRACE across all Servlet endpoints.
See below example.

Next, removing TRACE from the OPTIONS Allow header field.
That's not as easy to do in a global way like the disabling of TRACE.
This is because every HttpServlet has a default implementation of doOptions(HttpServletRequest, HttpServletResponse) which will always add TRACE and OPTIONS to the Allow header, along with any other method that you have specifically enabled for that specific HttpServlet endpoint.

You can see the actual default implementation of HttpServlet.doOption here ...

https://github.com/eclipse-ee4j/servlet-api/blob/4.0.4-RELEASE/api/src/main/java/javax/servlet/http/HttpServlet.java#L361-L432

Why is TRACE and OPTIONS always returned on a OPTIONS method request? That's because the default implementation of those methods in HttpServlet always return a response, so they are always added to the Allow header.

That means OPTIONS is controlled on a per-servlet scope.
You can override in your specific servlet the doOptions method and make it return what you want, but you cannot have a top level Constraint or Filter control it, as the neither the Constraint nor the Filter know the Servlet endpoint details to properly determine the other parts of the OPTIONS Allow header (like GET, HEAD, POST, PUT, etc...)

Here's some sample code to show the proper Constraint mapping and optional doOption override.

package jetty;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.security.Constraint;

import static java.nio.charset.StandardCharsets.UTF_8;

public class NoTraceDemo
{
    public static void main(String[] args)
    {
        Server server = new Server(8888);

        ServletContextHandler servletContextHandler = new ServletContextHandler();
        servletContextHandler.addServlet(HelloServlet.class, "/");
        ConstraintSecurityHandler constraintSecurityHandler = new ConstraintSecurityHandler();
        servletContextHandler.setSecurityHandler(constraintSecurityHandler);

        Constraint constraintDisableTrace = new Constraint();
        constraintDisableTrace.setAuthenticate(true);
        ConstraintMapping mappingDisableTrace = new ConstraintMapping();
        mappingDisableTrace.setPathSpec("/");
        mappingDisableTrace.setMethod("TRACE");
        mappingDisableTrace.setConstraint(constraintDisableTrace);
        constraintSecurityHandler.addConstraintMapping(mappingDisableTrace);

        Constraint constraintEnabledEverythingButTrace = new Constraint();
        ConstraintMapping mappingEnableEverythingButTrace = new ConstraintMapping();
        mappingEnableEverythingButTrace.setPathSpec("/");
        mappingEnableEverythingButTrace.setMethodOmissions(new String[]{"TRACE"});
        mappingEnableEverythingButTrace.setConstraint(constraintEnabledEverythingButTrace);
        constraintSecurityHandler.addConstraintMapping(mappingEnableEverythingButTrace);

        server.setHandler(servletContextHandler);
        try
        {
            server.start();

            URL url = server.getURI().toURL();
            request("TRACE", url);
            request("OPTIONS", url);
            request("GET", url);
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
        finally
        {
            LifeCycle.stop(server);
        }
    }

    private static void request(String method, URL url)
    {
        try
        {
            HttpURLConnection http = (HttpURLConnection)url.openConnection();
            http.setDoOutput(true);
            http.setRequestMethod(method);

            System.out.println("---------");
            System.out.printf("%s %s HTTP/1.1%n", http.getRequestMethod(), http.getURL());
            System.out.println("----");
            System.out.printf("%s%n", http.getHeaderField(null));
            http.getHeaderFields().entrySet().stream()
                .filter(entry -> entry.getKey() != null)
                .forEach((entry) -> System.out.printf("%s: %s%n", entry.getKey(), http.getHeaderField(entry.getKey())));
            InputStream bodyStream = (http.getResponseCode() == 200) ? http.getInputStream() : http.getErrorStream();
            if (bodyStream != null)
            {
                String body = IO.toString(bodyStream, UTF_8);
                System.out.println(body);
            }
        }
        catch (Throwable t)
        {
            t.printStackTrace(System.out);
        }
    }

    public static class HelloServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setCharacterEncoding("utf-8");
            resp.setContentType("text/plain");
            resp.getWriter().printf("Hello from %s%n", this.getClass().getName());
        }

        /*
        @Override
        protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            // build allow here.
            // see HttpServlet.doOptions(HttpServletRequest, HttpServletResponse) for example
            String allow = "GET, PUT, OPTIONS";
            resp.setHeader("Allow", allow);
        }
         */
    }
}

You can always copy/paste the HttpServlet.doOptions method into your own servlet and just remove or alter the TRACE behavior default.

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136