1

I'm working on an application that sends some sensitive information as plain text via the URL query string. My goal is to customize my jetty requestlog so that any sensitive information is redacted or removed before being logged.

So far I've tried making a custom filter which did remove sensitive information from the query string but when I kept the native jetty logging enabled I was getting double logs (one with password and one without) and when I disabled the native logging I wasn't getting invalid traffic (requests that come to the correct service but have some other issue with the query string).

My question is, is there a way to either keep using my filter and not have jetty log a duplicate entry with the same information or is there a different method to clean up passwords (How can I implement a different logger, modify jetty requestlog jar?). I'm almost brand new to the world of development so I have no idea what's possible.

filter for reference

@Component
@Order(1)
public class RequestLogFilter implements Filter {

    private final static Logger log = LogManager.getLogger(RequestLogFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String uriPath = req.getRequestURI();
        String uri = req.getQueryString();

        uri = uri != null ? uri.replaceAll("password.+?&", "password=redacted&") : "";

        String method = req.getMethod();
        String ip = req.getRemoteAddr();
        String protocol = req.getProtocol();

        String logString = ip + " - - " + '"' + method + " " + uriPath + "?" + uri + " " + protocol + '"';
        log.info(logString);


        filterChain.doFilter(servletRequest, servletResponse);

    }

    @Override
    public void destroy() {
        log.warn("Destructing RequestLogFilter :{}");
    }
}

1 Answers1

1

Caution

Putting the password in the URL query string is a bad idea.

When that is sent via HTTP (doesn't matter the version, HTTP/1.0, HTTP/1.1, HTTP/2, and HTTP/3 all have the same behavior here) the path + query (of your URL/URI) is sent in the HTTP request-line.

So that means the URL/URI of say https://example.com:8080/api/user?id=bob&password=axolotl will send something like this (HTTP/1.1 example)

GET /api/user?id=bob&password=axolotl HTTP/1.1
Host: example.com:8080
Connection: close

That request-line is logged as-is in many places, but the headers are not. That request-line can also show up as logging during the parsing steps, or for reporting errors.

The entire request-line is also present in the Servlet Spec as part of RequestDispatcher based calls in the HttpServletRequest.getAttributes(), which can be logged too.

That query string is also used by the browser for sending Referer headers to other resources.

That query string is now part of your HTTP Client (browser, and various http libraries, including rest libs, and even phone software) will cache that request and query string. Browser history will show that query string too.

The query string is also subject to extra behaviors on the Servlet Spec.

If you use HttpServletRequest.getParameter("password"), that forces the entire request to be read to find a "parameter" in both the query string and request body content (if the request body content happens to be application/x-www-form-urlencoded or multipart/form-data then that parameter can also get it's value from the request body content.

If you (or any 3rd party library) uses the Servlet Spec include/exclude features, then the query string MUST be merged from the original request to the sub-requests to satisfy the include/exclude behaviors.

Some other suggestions for not using the query section for passwords.

Easiest and Safest Solution

If you can switch from GET and query string to POST and use either application/x-www-form-urlencoded or multipart/form-data request body content you will see many benefits, including the lack of logging everywhere in jetty for request body content.

Logging Specific Solution (A Terrible Hack)

If you still want to handle this at the logging side you basically have to do the following.

Capture all logging output events, from all logging libraries (java.util.logging, log4j, slf4j, jakarta-commons-logging, juli logging, etc) route them to a single logging implementation (you can do this with slf4j easily enough, just choose the right combination of slf4j jars) Then have a custom logging implementation that looks at ALL logging messages for the keyword you need to redact and change the logging message accordingly before it's appended to the output (be it console, or log file, etc)

log4j has some support for modifying the logging message starting in 1.2.15+

See: LOG4J: Modify logged message using custom appender

Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136
  • Thank you, I do also plan on getting passwords out of the query string eventually but that will come in a bit. I'm not too familiar with logging so I just wanted to make confirm something. If I'm running my application in a standalone jetty server that gets a war file will a custom appender stop jetty's native logs or will I need to turn them off manually? (I'll try this out today so I can probably figure this out by then). – Durhode Petifort Aug 18 '21 at 11:34
  • if you have `slf4j-api-.jar` present in the classloader, Jetty 9 will use it. (Jetty 10+ will only use slf4j-api) You can also configure Jetty 9 to use java.util.logging. In Jetty 9, requestlog is highly custom. In Jetty 10, RequestLog is super flexible and can use slf4j-api for output routing/filtering. – Joakim Erdfelt Aug 18 '21 at 11:36
  • If you have un-redacted logs that you want to send to a third party, you can use Sublime Text to retrospectively redact them - see https://codingrob.medium.com/how-to-redact-text-from-hundreds-of-text-files-in-less-than-60-seconds-511b4b91c2d8 – RobbiewOnline Mar 15 '22 at 12:12