Subverting Apache Tomcat's j_security_check with wget

Last updated: Sat, 23 Oct 2010 23:42:00 GMT

When all you have is a hammer, the saying goes, everything looks like a kitten. And when all of the business logic you want is buried inside a webapp, you start flailing your hammer menacingly at Tomcat.

I'm not a big fan of web GUIs for device admin. They have their place, but that place is mostly in small, relatively simple consumer level devices. Your wireless access point, for instance. In more industrial kit, I think that as often as not they detract from development of a useable CLI, placing an obstruction in my path the minute I need to start a-scriptin'.

But ours is not to reason why, and sometimes we need to just do. It's not the worst thing we need to do either; just a few weeks back I was using expect over telnet.

*shudder*

Some web GUIs are dumb enough that they use basic authentication, which is just fine for our purposes. Some are dumb enough that they'll accept authentication passed, GET-style, as part of the URL. Equally fine for our purposes. But some aren't that dumb, and some hold internal session state, and that's a pain.

Recently I had call to automate some diagnostics-gathering, and it transpired that all of the logic that collected all of the various parts togehether for the report was buried in just such an application, based on Apache Tomcat. Now, I could have written something that generated something that looked like those diagnostics, because I could see the end result. But... in a way, these UIs are contracts between the vendor and the user. A smart vendor doesn't make drastic changes to their UI without very good reason. Just like an API. The flipside of this is that a vendor may change the content or format of those diagnostics without notice, and without expecting the customer to care. Bang goes my carefully contructed simulacrum.

In short, the rule is always "go in through the front door, if you can". In this case, work out how to poke their app to think I asked it for diagnostics.

But, how to fool Tomcat into thinking a real human had logged in? Google found me various discussions, mostly from the point of view of the web app author. Clearly I'm not in a position to change any source, here. And neither can I start playing with exotic libraries, or other applications, because I'm not in the mood to start installing multiple dependencies on someone else's management server.

I have wget, and luckily that's enough.

It's a three stage process: request the initial diagnostics form and receive a response from j_security_check; submit authentication to j_security_check and receive the initial form; submit the fake form. Here are the three URLs corresponsing to those resources:

my $prepURL = "https://localhost/mgr/app/action/admin.DiagnosticsAction/eventsubmit_dopreparedownloaddiagnostics/ignored";
my $jSecurityCheckURL = "https://localhost/mgr/app/j_security_check";
my $diagURL = "https://localhost/mgr/app/action/admin.DiagnosticsAction?op&serverDiags=false&storageDiags=true&wwn=$WWNFormat";

So, first, use wget to ask for the form. Remember to --keep-session-cookies and of course --save-cookies to use later:

wget --no-check-certificate --keep-session-cookies --save-cookies=$wgetCookieFile \
    -o $wgetLogFile -O $wgetOutputFile "$prepURL"

This first step creates the session within the Tomcat app. Those cookies passed back are the magic ingredient. Next, using those magic cookies we saved, POST the authentication details:

wget --no-check-certificate --load-cookies=$wgetCookieFile --keep-session-cookies --save-cookies=$wgetCookieFile \
    --post-data="j_username=$username&j_password=$password" --referer="$prepURL" \
    -o $wgetLogFile -O $wgetOutputFile "$jSecurityCheckURL"

Finally, use those cookies, and the Tomcat session state they refer to, to finally poke the app into thinking that a real live authenticated human clicked submit and asked it to fire off some diags:

wget --no-check-certificate --load-cookies=$wgetCookieFile --referer="$prepURL" \
    -o $wgetLogFile -O $wgetOutputFile "$diagURL"

Done.

Almost inevitably, now that I know how this is done, it a) seems trivially easy; and b) seems like any half-sane primate with access to Google could work it out in ten seconds flat. But, before I had worked it out, I found only parts of the recipe, and it took a couple of hours to finally come together. So I thought I'd share.