SIK-2016-009


Title:

Insecure Crypto-Communication ESET App

Report ID

SIK-2016-009

Summary:

The application contains an incorrect SSL certificate validation, which can be abused by an attacker for eavesdropping or manipulating the communication. Furthermore, an attacker can steal the ESET license information or the user and password for the license.

Details:

Incorrect SSL Certificate Validation:

NOTE: Due to the obfuscation of the application, we do not mention unobfuscated class or method names here. Therefore we refer to obfuscated class or method names only.

The class jl implements the X509TrustManager interface, which defines the checkServerTrusted method, handling the server certificate validation. In the current implementation this method is empty, which means it does not validate the correctness of the server certificate nor the whole certificate chain.

A Man-in-the-Middle attacker, e.g. at an open Wi-Fi Hotspot, can eavesdrop the SSL traffic because the application will trust all certificate. It is not necessary to install a compromised root certificate on the device. Each communication using the faulty Trustmanager can be compromised.

The following excerpt was derived from the registering process traffic (captured using a Man-in-theMiddle attack):

<?xml version="1.0" encoding="UTF-8"?>
<REGISTERING>
<SECTION ID="1000103">
<REGISTERINGREQUEST>
<NODE NAME="UidType" VALUE="8" TYPE="DWORD"/>
<NODE NAME="CountryCode" VALUE="US" TYPE="STRING"/>
<NODE NAME="EmailAddress" VALUE="" TYPE="STRING"/>
<NODE NAME="Note" VALUE="[LGE] [occam]" TYPE="STRING"/>
<NODE NAME="Platform" VALUE="Android 4.4.4" TYPE="STRING"/>
<NODE NAME="Product" VALUE="715" TYPE="STRING"/>
<NODE NAME="Version" VALUE="3.0.1318.0" TYPE="STRING"/>
<NODE NAME="LocaleCode" VALUE="de" TYPE="STRING"/>
<NODE NAME="EvCode" VALUE="eeXrRK5Ipltsamy8SshZ1di0Jvo=" TYPE="STRING"/>
<NODE NAME="CustomCode" VALUE="0" TYPE="DWORD"/>
<NODE NAME="ICode" VALUE="MDAwMDAwwCxwXphYj3JMoEasWcr+zmVQHjY=" TYPE="STRING"/>
<NODE NAME="LicenseUsername" VALUE="Fdax6a7wj/I+ZEet" TYPE="STRING"/>
<NODE NAME="LicensePassword" VALUE="Fdax6a7wj/I=" TYPE="STRING"/>
<NODE NAME="PreviousLicenseUsername"
VALUE="JNaV6Yvw1vJrZAStgsgqddgxE7x6+OkMc9013Q==" TYPE="STRING"/>
<NODE NAME="PreviousLicensePassword" VALUE="VNa26a/wzvIjZAetwsgtdY4xGLw=" TYPE="STRING"/>
</REGISTERINGREQUEST>
</SECTION>
</REGISTERING>

A Man-in-the-Middle attacker can steal the username and password for the AV license (see bold information in listing above). The given encryption/obfuscation, behind the BASE64 encoding, of the credential information does not provide enhanced protection in this attack scenario, because the attacker does not need to decrypt it to authenticate. He retransmits the sniffed (partial) XML information in his own context and gets authenticated.

An additional attack scenario would be to decrypt the credentials and abuse it for something else, like additional ESET services. This can be done because the implemented encryption scheme can be easily broken by chosen plain text attacks. Further details can be found in the next section.

Crypto Scheme, Chosen Cipher Text Attack and Decryption Algorithm:

In our analysis we did not completely reverse engineer the applied encryption scheme for the license information, but we used chosen cipher text attacks in order to break it.

The encryption is done by XOR the input value with a key generated by a random stream. In order to retrieve the key we utilized, for instance a username consisting of 25 times “a”. After that the application encrypted the username and the application with BASE64. We decoded the BASE64 String and XORed
the byte array (50 byte) with 25 times of “a” (each 2nd byte) and we got a key with the length of 25 bytes.

We attached a proof of concept script, which will decrypt the LicenseUsername and LicensePassword up to the length of 25 characters.
In the same way it would also be possible to generate longer keys but it depends of the length of username and password. Most people choose shorter ones.

Workaround

App users should avoid public unknown (unreliable) Wi-Fi Hotspots because Man-in-the-Middle attacks are possible (transparent proxy, ARP spoofing etc.). Data transfer over GPRS/UMTS is more secure because it requires higher effort to do a Man-in-the-Middle attack, but cannot be excluded. If public Wi-Fi connections are utilized, a VPN tunnel to a trusted counterpart should be established.

Suggested Mitigation

The SSL verification implementation has to be fixed. In general, it is not a good idea to overwrite the TrustManager or perform certificate verifications using own implementations. The operation system functions will handle SSL verification properly by default. For best practices, see the Android Developers references (http://developer.android.com/intl/es/training/articles/security-ssl.html).

For higher security demands, please refer to certificate pinning. But this approach must be evaluated if it is compliant to the backend infrastructure.

The encryption scheme should be avoided or removed. Self-implemented crypto solutions are usually insecure. For the context of authentication, the encryption of the license information does not bring any benefit. The Man-in-the-Middle attacker must only forward the stolen data, independent if it is encrypted or not. If the SSL validation is implemented correctly, the credentials are encrypted implicitly.

If you aim to protect the credentials additionally, because someone can abuse it for other services if he gets them in plaintext, we recommend to use a hash function instead of an encryption function. To protect the hash function against rainbow tables augment them with a salt value.

Appendix

Python Script for decrypting username or passwords. The POC contains a 25 byte long key for showing how simple the encryption scheme can be broken.

#!/usr/bin/python2.7
"""
Created on Mon Nov 30 16:39:39 2015
@author:
"""
key = [97, 212, 221, 251, 91, 53, 183, 25, 236, 43, 66, 217, 75, 7, 198, 8, 17, 81, 212, 184, 191, 169, 203, 45, 123]
def decrypt(inString, key):
 inString = bytearray( inString.decode("base64") ) # decode as base64
 inString = inString[::2] # take every second
char
 result = list()
 for i, c in enumerate( inString ):
 cipher = c ^ key[i]
 result.append( chr(cipher) )
 return result

print("Type 'q' to quit")
while True:
 inputString = raw_input("Enter base64 string to decrypt: ")
 if inputString == "q":
 exit()

 try:
 res = decrypt( inputString, key )
 print("Input: " + inputString)
 print("Encrypted: " + ''.join(res) + "\n")
 except:
 print("* Cannot decode base64 string!")

Timeline

  • 2015-10-13 Vulnerability Discovered
  • 2015-12-02 Vulnerability Reported
  • 2015-12-11 Vulnerability Fixed