reddit is dying now, but the response there was actually quite interesting, so I'm keeping an archive link to it around. Change-Id: I419276428ce141fc526124d9746e3ab13df4bbc8 Reviewed-on: https://cl.tvl.fyi/c/depot/+/8756 Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
		
			
				
	
	
		
			158 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			158 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| TIP: WatchGuard has
 | |
| [responded](https://web.archive.org/web/20230326041952/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 clients 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 curled 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:
 | |
| 
 | |
| ```xml
 | |
| <?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.
 |