Prepared by:
HALBORN
Last Updated 01/15/2025
Date of Engagement: September 12th, 2024 - September 25th, 2024
86% of all REPORTED Findings have been addressed
All findings
7
Critical
0
High
0
Medium
3
Low
3
Informational
1
Fuelet
engaged Halborn to conduct a security assessment of their Fuelet wallet Android mobile application, which began on September 12th, 2024 and ended on September 25th, 2024. The security assessment was scoped to the Fuelet wallet Android mobile application. The client team provided both the source code and the respective APK file to allow the security engineers to conduct testing using tools for scanning, detecting, and validating possible vulnerabilities, and to report the findings at the end of the engagement.
The team at Halborn was provided a timeline for the engagement and assigned two full-time security engineers to verify the security of the assets in scope. The security engineers are penetration testing experts with advanced knowledge in web, mobile (Android and iOS), reconnaissance, blockchain and infrastructure penetration testing.
The goals of our security assessments are to improve the quality of the systems we review and to target sufficient remediation to help protect users.
Of note was the ability to extract other wallets' information from memory when creating a new wallet, namely the private key and seed phrase. While the information was originally encrypted, by tracing calls to the Cipher class the encrypted information could be retrieved. Furthermore, the encryption algorithm was deemed insecure, as AES in CBC mode suffers from collision attacks. Instead, AES with CGM mode should be preferred, to further secure the information. Furthermore, the Fuelet wallet's fingerprint check could be bypassed, which would provide an attacker with access to the user's wallet. In conjunction with the lack of checks on when deleting wallets, an attacker could ultimately steal the devices, bypass the fingerprint check. In regard to the backend server handling GraphQL queries and mutations, it was misconfigured and affected by several Denial-of-Service (DoS) vulnerabilities, which may lead to wallets' not being able to retrieve information from the infrastructure.
Lastly, address the remaining issues in a timely manner to improve the overall security posture of users' wallets and the backend infrastructure.
For public release, this report was redacted per Fuelet request to exclude certain critical issues. It should be noted that all removed critical issues were fully addressed and resolved by the Fuelet team prior to the report’s publication
The security assessment was scoped to:
Fuelet wallet Android APK
Wallet HTTP and API calls made to the following hosts:
Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of the pentest. While manual testing is recommended to uncover flaws in logic, process and implementation; automated testing techniques assist enhance coverage of the infrastructure and can quickly identify flaws in it.
The following phases and associated tools were used throughout the term of the assessment:
Storing private keys and assets securely
Send/Receive tokens and assets securely to another wallet
Any attack that impacts funds, such as draining or manipulating of funds
Application logic flaws
Areas where insufficient validation allows for hostile input
Application of cryptography to protect secrets
Brute-force attempts
Input handling
Source code review
Fuzzing of all input parameters
Technology stack-specific vulnerabilities and code assessment
Known vulnerabilities in 3rd party/OSS dependencies
Critical
0
High
0
Medium
3
Low
3
Informational
1
Impact x Likelihood
HAL-01
HAL-02
HAL-03
HAL-05
HAL-07
HAL-04
HAL-06
Security analysis | Risk level | Remediation Date |
---|---|---|
Fingerprint Bypass | Medium | Solved - 12/05/2024 |
Insecure AES Encryption Mode | Medium | Solved - 01/09/2025 |
GraphQL Misconfiguration | Medium | Risk Accepted - 12/05/2024 |
Missing Root Detection Mechanism | Low | - |
Missing Checks on Delete Wallet Functionality | Low | Solved - 12/05/2024 |
Certificate Pinning Bypass | Low | Risk Accepted - 12/05/2024 |
Missing Authentication Check on Application Start | Informational | Acknowledged - 12/05/2024 |
//
The application had insufficient checks on biometric controls for the authentication process, which led to a bypass when in possession of the device.
The focus is on the onAuthenticationSucceeded callback, which is crucial in the authentication process. The fingerprint bypass could be conducted by setting NULL
as CryptoObject in onAuthenticationSucceeded(...). The Frida script forces an automatic bypass of the fingerprint authentication upon the method's invocation by hooking the callback and modifying the CryptoObject automatically.
The Frida script used to hook the interactions between the application and biometric android hardware/implementation in order to bypass it can be found here. The Frida script can be run with the following command:
frida -U Fuelet -l fingerprint-bypass.js
Use the second overload of the authenticate method, using a CryptoObject to decrypt some asymmetric or symmetric key stored on the device and use that key to allow access to the wallet:
The key to generating the CryptoObject
used for biometric authentication should be stored securely in the Android Keystore system. We also need to ensure that the key is set up in a way that user authentication is required on each use and is invalidated when a new biometric is enrolled.
Use .setUserAuthenticationRequired(true)
and .setInvalidatedByBiometricEnrollment(true)
as shown in the code example below to generate and retrieve the key for strong biometric authentication:
const val BIOMETRIC_KEY_STORE_ALIAS = "yourChoiceOfBiometricKeyStoreAlias"
const val ANDROID_KEYSTORE_NAME = "AndroidKeyStore"
private fun getBiometricKeyGenParameterSpec(): KeyGenParameterSpec {
val builder = KeyGenParameterSpec.Builder(
BIOMETRIC_KEY_STORE_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
builder.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
} else {
builder.setUserAuthenticationValidityDurationSeconds(-1)
}
return builder.build()
}
fun Context.generateStrongBiometricAuthenticationKey() {
if (isStrongBiometricAvailable()) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE_NAME)
keyGenerator.init(getBiometricKeyGenParameterSpec())
keyGenerator.generateKey()
}
}
fun getStrongBiometricAuthenticationKey(): SecretKey? {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE_NAME)
keyStore.load(null)
return keyStore.getKey(BIOMETRIC_KEY_STORE_ALIAS, null) as? SecretKey
}
fun getCipher(): Cipher {
return Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/" +
KeyProperties.BLOCK_MODE_CBC + "/" +
KeyProperties.ENCRYPTION_PADDING_PKCS7
)
}
override fun onUnlock() {
generateStrongBiometricAuthenticationKey()
}
Note
Calling setInvalidatedByBiometricEnrollment(true)
will invalidate the generated key stored in the Keystore when a new biometric is enrolled. This means that users would not be able to authenticate when a new biometric assigned because the Biometric Prompt will not create a new dialog. A solution would be to use the PIN and, if authentication is successful, regenerate the key for the biometric authentication. The key regeneration can be done in the onUnlock
method, which would be called immediately after the user successfully unlocks the device:
override fun onUnlock() {
generateStrongBiometricAuthenticationKey()
}
References
SOLVED: The biometric functionality was disabled/eliminated from the application provided.
//
During encryption and decryption routines performed when creating new wallets, AES in CBC mode is used, which is known to present several weaknesses.
Padding Oracle Attacks (on CBC):
CBC mode requires padding to make the last block of plaintext the same size as the AES block size (usually 128 bits). This introduces the risk of padding oracle attacks, where an attacker can exploit differences in how a system handles padding errors to learn information about the plaintext. This attack can effectively allow decryption without needing the key.
Lack of Authenticity/Integrity Verification
CBC mode does not provide authentication of the ciphertext. This means that an attacker could manipulate the encrypted data without being detected. For example, bit-flipping attacks can be used to modify the ciphertext and, consequently, alter the decrypted plaintext.
Initialization Vector (IV) Requirements and Reuse Vulnerabilities
In CBC mode, a unique Initialization Vector (IV) is required for each encryption operation to prevent the same plaintext from producing the same ciphertext. However, if the IV is reused (either accidentally or due to improper implementation), IV reuse attacks can occur, allowing attackers to detect relationships between ciphertexts and potentially decrypt data.
The Frida script to retrieve the seed phrase and the private key from the application memory can be found here.
Run the following command after starting the Fuelet application, but before creating a new wallet:
frida -U Fuelet -l tracer-cipher.js
The screenshots below detail the AES encryption/decryption routines performed first on the previous wallet, and afterwards only on the new wallet being created.
Consider using AES in GCM mode for the following reasons:
Built-in Authentication (AEAD):
GCM is an AEAD mode, meaning it provides both confidentiality and integrity in a single step. This removes the need for additional mechanisms (e.g., HMAC) to verify the integrity of the message.
Efficiency and Performance:
GCM can be parallelized and is optimized for hardware performance.
Avoids Padding Issues:
GCM mode does not require padding, which eliminates entire classes of attacks (e.g., padding oracle attacks) and simplifies the encryption process.
SOLVED: The same AES mode is still used due to the library’s limitations however the data it encrypts is protected using AES in GCM mode. Thus, the final encryption process is effectively: secret = AES(AES_gcm(data))
.
//
Several GraphQL configurations could allow users to perform Denial-of-Service (Dos) attacks at the application level.
The following GraphQL issues have been identified:
Alias Overloading
Field Duplication
Introspection-based Circular Query
Introspection Query
Alias overloading - Alias Overloading with 100+ aliases is allowed
GraphQL's alias feature allows a client to perform the same operation multiple times in the same HTTP request. Increasing the number of aliases in the query had an impact on the response length and time from the server, thus opened a possibility of Denial-of-Service condition.
Field duplication - Queries are allowed with 500 of the same repeated field
GraphQL allowed duplicate or repetitive fields in the query. An end-user could make the server process the same field again and again n
number of times. Increasing the number of same fields in the query had an impact on the response time from the server and thus opened a possibility of Denial-of-Service (Dos) condition.
Introspection-based Circular Query
The more the fragments are referencing each other in a query and the more the server response length and time increased. This opened a possibility of a Denial-of-Service (Dos) condition.
Introspection Query
The GraphQL introspection could be called by unauthenticated users.
1. Use a tool such as graphql-cop to identify GraphQL vulnerabilities. Run all the queries through a proxy using the flag --proxy
to identify the vulnerable queries.
2. Example of a request utilizing Alias Overloading.
3. Example of a request utilizing Field Duplication.
4. Example of a request utilizing Introspection-based Circular Query.
5. Example of an Introspection Query.
Resolve the Alias Overloading Vulnerability
The alias functionality cannot be directly disabled. Instead, it is advisable to implement a validation process to ensure that the query doesn’t contain more aliases than the chosen maximum.
The following open source project may help to resolve the issue: GraphQL No Alias Directive Validation
No alias directive for graphql mutation and query types. It can limit the amount of alias fields that can be used for queries and mutations, preventing batch attacks.
Resolve the Field Duplication Vulnerability
To prevent this attack, GraphQL servers should validate the incoming queries and reject any that contain duplicate fields. Additionally, servers could limit the complexity of queries by setting a limit on the response time, to prevent excessive resource consumption.
Resolve the Introspection-based Circular Query Vulnerability
Several possibilities exist to remediate this issue: either limit the maximum depth of the introspection query or limit the maximum elapsed time to execute a GraphQL query. Alternatively, disable introspection.
Resolve the Introspection Query Vulnerability
Disable the introspection query, or consider implementing authentication mechanisms to prevent unauthenticated users from retrieving it.
RISK ACCEPTED: The Fuelet team accepted the risk derived from this issue.
//
Anti-root detection mechanisms were not implemented in the Android application. These mechanisms could help to mitigate reverse engineering, application modification, and unauthorized versions of mobile applications to some extent, but few if any will be completely successful against a determined adversary. Those measures should nonetheless be implemented to deter attackers from attempting to reverse engineer the application, or at least increase the amount of effort required to do so.
Root an Android device using Magisk and observe that no check exists to determine whether the application runs a rooted phone.
Methods to detect rooted devices should be implemented in the application to prevent dynamic analysis. As a security best practice, implement a mechanism to check the rooted status of the mobile device. This can be done either manually by implementing a custom solution or using libraries already built for this purpose. Search for commonly known files and locations, check file permissions and attempt to find common rooting services such as SuperSU
, Magisk
or OpenSSH
, for example.
RISK ACCEPTED: The Fuelet team accepted the risk derived from this issue.
//
The Fuelet application did not ask for a PIN or fingerprint verification when deleting the wallet.
Access the application and attempt to delete the wallet. See that no PIN or fingerprint verification will prevent this action.
Secure the functionality with the PIN or fingerprint check to ensure that only the user himself can delete the wallet.
SOLVED: The Fuelet team solved this finding following the aforementioned recommendation.
//
Certificate pinning is a security measure that involves hardcoding the certificate or public key of a known server into the client application to prevent man-in-the-middle (MITM) attacks. This measure ensures that the client establishes connections only with the designated server, even in the presence of a seemingly valid certificate from a trusted Certificate Authority (CA).
Bypassing this security control can be achieved through various methods, such as exploiting vulnerabilities in the client-side implementation of pinning, manipulating the mobile device environment (e.g., root or jailbreak exploits), or leveraging control over a Root CA to issue deceptive certificates. Flaws in the implementation of pinning, such as inadequate validation of the certificate chain, reliance on intermediate CAs, or insufficient coverage across all communication channels, further exacerbate the risk. This bypass can lead to successful MitM attacks, allowing attackers to intercept, modify, or redirect data transmitted over supposedly secure connections.
With objection, the SSL pinning can be disable by running the command android sslpinning disable
.
Choose whether to pin the whole certificate or just its public key. Two options are possible when choosing to pin the public key:
● Pin the subjectPublicKeyInfo
.
● Pin one of the concrete types such as RSAPublicKey
or DSAPublicKey
.
Alternatively, consider whether to pin the root Certification Authority (CA), intermediate CA or leaf certificate:
● Pinning the root CA is generally not recommended since it highly increases the risk because it implies also trusting all its intermediate CAs.
● Pinning a specific intermediate CA reduces the risk, but the application will also be trusted to other certificates issued by this CA, not only the ones meant for your application.
● Pinning a leaf certificate is recommended but must include backup (e.g. intermediate CA). It provides 100% certainty that the app exclusively trusts the remote hosts it was designed to connect to.
Further information on certificate pinning may be found at OWASP's Certificate and Public Key Pinning resource.
RISK ACCEPTED: The Fuelet team accepted the risk derived from this issue.
//
When switching between applications, the Fuelet application did not lock the wallet to re-ask for the PIN or fingerprint when accessing it again after the application resumed from background.
Such a security measure would provide security in depth. In the case of a stolen phone with the application remaining opened in the background, the thief having access to the screen could view the list of applications opened, re-access the Magic Eden wallet and attempt to steal the funds.
Put the application in the background, open another application and switch back to the Fuelet application. No prompt will appear asking for a fingerprint check or PIN verification.
Implement a lockout mechanism when switching to another application.
Ask for a PIN or fingerprint check when accessing it again.
ACKNOWLEDGED: The Fuelet team acknowledged the risks associated with this issue.
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
// Download the full report
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed