113 - Bypassing Box MFA & Bad AES Key Generation
Combination of a local file inclusion bug and a file write bug. Firstly, the user/loader.php
and /user/index.php
pages had some interesting code where it would take a scripts
GET parameter to construct an include path in PHP. They try to prevent the abuse of this vector by checking for ..
strings in the parameter using stristr()
(case insensitive strstr()
). The problem is, certain characters like %00
are ignored when processing the include, and will bypass the stristr()
check since the length is different. This allowed them to install an API key that grants them full API access.
The second bug was in the add_server
endpoint they could now access with their API key, which wrote a DHCP string into a local text file. PHP could be written here, and by chaining again with the LFI, it was possible to include arbitrary PHP and achieve RCE.
While the hostnames were being validated for this vulnerability, injecting a @
into the path argument was sufficent to mislead the final URL parser and actual code making the HTTP request to go to an unapproved domain by tricking it into thinking the path is actually the host and everything before the @
is just credentials.
A better approach would be to generate the entire URL and perform validation against that, similar to how one might test for directory traversal by getting the final canonical path and performing checks against that. This could still leave room for parser differentials however between the code doing the validation, and the code actually making the request.
The issue here is relatively simple despite the technical depth the authors go into on the crypto and how it’s used. AES-128 keys are used to encrypt challenge codes for the authentication flow between NFC tags and the alarm system, but the way these AES-128 keys are generated is naive and insecure. They use the system’s current unix timestamp to seed a non-cryptographically secure RNG. Because of this, the number of potential keys is significantly reduced and is practical to bruteforce.
The gist of this is that an attack can use their own Time-based One-Time-Password (TOTP) code on another user’s account.
This is in part because the TOTP process takes as arguments both the assertion
or the code itself and a factorId
. The factorId
uniquely identifies the secrets used to generate the OTP, so the server can check if the code. The problem here seems to me that the application backend shouldn’t need to rely on the client-side to provide the factorId
in the first place, but even if they do it should validate ownership of that id.
As Box also didn’t validatea the ownership of the factorId
an attacker could use their own OTP and Id to bypass the MFA need.
It is possible that this lack of validation was also caused due to an unexpected state. The writeup specifically uses the case where a user has SMS-based MFA enabled, and then intercepts the request to go to the TOTP endpoint instead, then proceeds to perform the above attack. A plausible scenario is that in this case where the user has no TOTP, then Box essentially fails-open and allows any factorId
but had their been a registered TOTP then it would perform appropriate validation.