feat(web/blog): Check in blog posts that I want to keep
This commit is contained in:
parent
15b871806b
commit
cc2c130352
6 changed files with 564 additions and 0 deletions
158
web/blog/posts/reversing-watchguard-vpn.md
Normal file
158
web/blog/posts/reversing-watchguard-vpn.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
**Update**: WatchGuard has
|
||||
[responded](https://www.reddit.com/r/netsec/comments/5tg0f9/reverseengineering_watchguard_mobile_vpn/dds6knx/)
|
||||
to this post on Reddit. If you haven\'t read the post yet I\'d recommend
|
||||
doing that first before reading the response to have the proper context.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
One of my current client makes use of
|
||||
[WatchGuard](http://www.watchguard.com/help/docs/fireware/11/en-US/Content/en-US/mvpn/ssl/mvpn_ssl_client-install_c.html)
|
||||
Mobile VPN software to provide access to the internal network.
|
||||
|
||||
Currently WatchGuard only provides clients for OS X and Windows, neither
|
||||
of which I am very fond of. In addition an OpenVPN configuration file is
|
||||
provided, but it quickly turned out that this was only a piece of the
|
||||
puzzle.
|
||||
|
||||
The problem is that this VPN setup is secured using 2-factor
|
||||
authentication (good!), but it does not use OpenVPN\'s default
|
||||
[challenge/response](https://openvpn.net/index.php/open-source/documentation/miscellaneous/79-management-interface.html)
|
||||
functionality to negotiate the credentials.
|
||||
|
||||
Connecting with the OpenVPN config that the website supplied caused the
|
||||
VPN server to send me a token to my phone, but I simply couldn\'t figure
|
||||
out how to supply it back to the server. In a normal challenge/response
|
||||
setting the token would be supplied as the password on the second
|
||||
authentication round, but the VPN server kept rejecting that.
|
||||
|
||||
Other possibilities were various combinations of username&password
|
||||
(I\'ve seen a lot of those around) so I tried a whole bunch, for example
|
||||
`$password:$token` or even a `sha1(password, token)` - to no avail.
|
||||
|
||||
At this point it was time to crank out
|
||||
[Hopper](https://www.hopperapp.com/) and see what\'s actually going on
|
||||
in the official OS X client - which uses OpenVPN under the hood!
|
||||
|
||||
Diving into the client
|
||||
----------------------
|
||||
|
||||
The first surprise came up right after opening the executable: It had
|
||||
debug symbols in it - and was written in Objective-C!
|
||||
|
||||

|
||||
|
||||
A good first step when looking at an application binary is going through
|
||||
the strings that are included in it, and the WatchGuard client had a lot
|
||||
to offer. Among the most interesting were a bunch of URIs that looked
|
||||
important:
|
||||
|
||||

|
||||
|
||||
I started with the first one
|
||||
|
||||
`%@?action=sslvpn_download&filename=%@&fw_password=%@&fw_username=%@`
|
||||
|
||||
and just =curl=ed it on the VPN host, replacing the username and
|
||||
password fields with bogus data and the filename field with
|
||||
`client.wgssl` - another string in the executable that looked like a
|
||||
filename.
|
||||
|
||||
To my surprise this endpoint immediately responded with a GZIPed file
|
||||
containing the OpenVPN config, CA certificate, and the client
|
||||
*certificate and key*, which I previously thought was only accessible
|
||||
after logging in to the web UI - oh well.
|
||||
|
||||
The next endpoint I tried ended up being a bit more interesting still:
|
||||
|
||||
`/?action=sslvpn_logon&fw_username=%@&fw_password=%@&style=fw_logon_progress.xsl&fw_logon_type=logon&fw_domain=Firebox-DB`
|
||||
|
||||
Inserting the correct username and password into the query parameters
|
||||
actually triggered the process that sent a token to my phone. The
|
||||
response was a simple XML blob:
|
||||
|
||||
``` {.example}
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resp>
|
||||
<action>sslvpn_logon</action>
|
||||
<logon_status>4</logon_status>
|
||||
<auth-domain-list>
|
||||
<auth-domain>
|
||||
<name>RADIUS</name>
|
||||
</auth-domain>
|
||||
</auth-domain-list>
|
||||
<logon_id>441</logon_id>
|
||||
<chaStr>Enter Your 6 Digit Passcode </chaStr>
|
||||
</resp>
|
||||
```
|
||||
|
||||
Somewhat unsurprisingly that `chaStr` field is actually the challenge
|
||||
string displayed in the client when logging in.
|
||||
|
||||
This was obviously going in the right direction so I proceeded to the
|
||||
procedures making use of this string. The first step was a relatively
|
||||
uninteresting function called `-[VPNController sslvpnLogon]` which
|
||||
formatted the URL, opened it and checked whether the `logon_status` was
|
||||
`4` before proceeding with the `logon_id` and `chaStr` contained in the
|
||||
response.
|
||||
|
||||
*(Code snippets from here on are Hopper\'s pseudo-Objective-C)*
|
||||
|
||||

|
||||
|
||||
It proceeded to the function `-[VPNController processTokenPrompt]` which
|
||||
showed the dialog window into which the user enters the token, sent it
|
||||
off to the next URL and checked the `logon_status` again:
|
||||
|
||||
(`r12` is the reference to the `VPNController` instance, i.e. `self`).
|
||||
|
||||

|
||||
|
||||
If the `logon_status` was `1` (apparently \"success\" here) it proceeded
|
||||
to do something quite interesting:
|
||||
|
||||

|
||||
|
||||
The user\'s password was overwritten with the (verified) OTP token -
|
||||
before OpenVPN had even been started!
|
||||
|
||||
Reading a bit more of the code in the subsequent
|
||||
`-[VPNController doLogin]` method revealed that it shelled out to
|
||||
`openvpn` and enabled the management socket, which makes it possible to
|
||||
remotely control an `openvpn` process by sending it commands over TCP.
|
||||
|
||||
It then simply sent the username and the OTP token as the credentials
|
||||
after configuring OpenVPN with the correct config file:
|
||||
|
||||

|
||||
|
||||
... and the OpenVPN connection then succeeds.
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
Rather than using OpenVPN\'s built-in challenge/response mechanism, the
|
||||
WatchGuard client validates user credentials *outside* of the VPN
|
||||
connection protocol and then passes on the OTP token, which seems to be
|
||||
temporarily in a \'blessed\' state after verification, as the user\'s
|
||||
password.
|
||||
|
||||
I didn\'t check to see how much verification of this token is performed
|
||||
(does it check the source IP against the IP that performed the challenge
|
||||
validation?), but this certainly seems like a bit of a security issue -
|
||||
considering that an attacker on the same network would, if they time the
|
||||
attack right, only need your username and 6-digit OTP token to
|
||||
authenticate.
|
||||
|
||||
Don\'t roll your own security, folks!
|
||||
|
||||
Bonus
|
||||
-----
|
||||
|
||||
The whole reason why I set out to do this is so I could connect to this
|
||||
VPN from Linux, so this blog post wouldn\'t be complete without a
|
||||
solution for that.
|
||||
|
||||
To make this process really easy I\'ve written a [little
|
||||
tool](https://github.com/tazjin/watchblob) that performs the steps
|
||||
mentioned above from the CLI and lets users know when they can
|
||||
authenticate using their OTP token.
|
||||
Loading…
Add table
Add a link
Reference in a new issue