There is a saying in cybersecurity: “Obfuscation is not security”. In essence, this means that you cannot substitute confusing or misleading design for proper security measures. The following is an example detailing why this saying is true.
On a recent engagement I noticed some odd behavior. I was testing a web application, and every time I tried to replay a web call I was getting 401 Unauthorized errors:
I wasn’t modifying any data between the original call and the replay, so some check must’ve been failing server-side. I searched my call history and found successive calls to the same endpoint and sent them to Burp Suite’s ‘Comparer’ tab to have a look:
There were two headers which were different between calls. The app’s JWT (JSON Web Token), and an unknown header called ‘Bv-Tls-Vl’. JWTs updating between successive calls is common in web apps, and I was able to confirm that older JWTs were still valid by injecting them into intercepted calls in Burp Suite. That meant the ‘Bv-Tls-Vl’ header must be what was causing the 401 errors.
I got to work reverse engineering how this header was produced in the client-side JavaScript code, and found the following (simplified) code snippet:
function getHeaderValForVL() {
return "LV-SLT-VB".split("").reverse().join("");
}
function getHeaderValForPK() {
return "SLT-KR".split("").reverse().join("");
}
function getEncryptionKeyAfterLogin(e) {
if (e.headers.get(Yn.a.getHeaderValForPK())) {
this.current_bv_tls_vl = 1;
this.current_bv_tls_pk = e.headers.get(Yn.a.getHeaderValForPK()).split("").reverse().join("");
}
}
function setBvTlsVlHeaderForCall(e) {
this.current_bv_tls_vl++;
let t = "POST" === e.method ? e.serializeBody() : JSON.stringify(e.url);
let n = zn.SHA1(t).toString().toUpperCase() + "|" + this.current_bv_tls_vl;
let i = zn.enc.Base64.parse(this.current_bv_tls_pk);
let o = zn.enc.Base64.parse("Q29uc3RhbnRJbml0VmVjdG9y");
let s = zn.AES.encrypt(n, i, {iv: o, mode: zn.mode.CBC, padding: zn.pad.pkcs7}).toString();
let a = e.headers.set(Yn.a.getHeaderValForVL(), s.toString());
}
Multiple attempts at obfuscation were made by the developers throughout this process:
Reversing the hardcoded header name references in the code (‘SLT-KR’ instead of ‘RK-TLS’)
Reversing the AES encryption key returned from the server
Using unknown but seemingly innocuous header names
Base64 encoding the hardcoded initialization vector
Returning a 401 Unauthorized error when an invalid value is provided in the ‘Bv-Tls-Vl’ header instead of the more appropriate 400 Bad Request error, imitating proper authorization
Ultimately, these attempts at security through obscurity failed, and I was able to determine exactly how this checksum-like header was created. Furthermore, I was able to develop a custom Burp Suite extension to mimic this process and provide the server expected values for the ‘Bv-Tls-Vl’ header for any given call. Here’s the Java code to encrypt payloads from that extension:
private String encryptPayload(String payload) {
try {
current_bv_tls_vl++;
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hashBytes = md.digest(payload.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
String shaHash = sb.toString();
shaHash = shaHash.toUpperCase() + "|" + String.valueOf(current_bv_tls_vl);
byte[] decodedKeyBytes = Base64.getDecoder().decode(current_bv_tls_pk);
String iv = "ConstantInitVector";
SecretKeySpec keySpec = new SecretKeySpec(decodedKeyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encryptedBytes = cipher.doFinal(shaHash.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
catch(Exception e) {
callbacks.printError(e.getMessage());
return "";
}
}
Using this extension, I was able to find multiple high-level horizontal privilege escalation vulnerabilities within this app. It seemed the app team was confident in this pseudo ‘checksum’ to prevent unauthorized access, since they didn’t implement any logic stopping users from swapping user ID and form ID values within specific calls to read data they shouldn’t have access to:
Fortunately, after reporting these issues, a retest proved the app team had learned their lesson. Proper authorization checks had been implemented in this app, eliminating the horizontal privilege escalation vulnerabilities.
This serves as a practical example proving that obscurity does not equal security. If a mechanism for authorization or anti-tampering occurs client-side, then it will always be possible for an attacker to reverse engineer that logic and exploit your app. Also, basic obfuscation techniques like reversing strings or Base64 encoding to hide sensitive information within client-side code are not valid security controls. Malware developers deal with these kinds of techniques constantly and will sniff them out in no time. Best case, you will slow down a less experienced attacker for a few minutes. It’s better to spend precious development time on security controls that stop attackers in their tracks.