로컬 암호화 이슈
취약점 소개
해당 취약점은 중요 정보 암호화가 로컬에서 이루어지는데 해당 부분의 소스코드가 평문으로 표기되어 중요정보를 암호화, 복호화 할 수 있는 취약점이다.
InsecureBankv2는 Autofill Credentials라는 기능을 하는 버튼이 있는데 해당 버튼의 기능은 가장 최근에 로그인한 인증 정보를 불러와주는 기능이다.
로그인 시 해당 정보를 저장하기 때문에 DoLogin.class를 살펴보면 로그인 성공 시 saveCreds 함수에 로그인 정보를 담고 호출하는 것을 볼 수 있다.
if (DoLogin.this.result != null) { if (DoLogin.this.result.indexOf("Correct Credentials") != -1) { Log.d("Successful Login:", ", account=" + DoLogin.this.username + ":" + DoLogin.this.password); saveCreds(DoLogin.this.username, DoLogin.this.password); trackUserLogins(); Intent pL = new Intent(DoLogin.this.getApplicationContext(), PostLogin.class); pL.putExtra("uname", DoLogin.this.username); DoLogin.this.startActivity(pL); return; } Intent xi = new Intent(DoLogin.this.getApplicationContext(), WrongLogin.class); DoLogin.this.startActivity(xi); }
saveCreds의 메서드를 확인해 보면 SharedPreferences API를 사용하여 “mySharedPreferences”란 파일에 username은 base64로 인코딩하고, password는 CryptoClass의
aesEncryptedString
를 이용하여 암호화 한 뒤 저장하는 것을 볼 수 있다.private void saveCreds(String username, String password) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { SharedPreferences mySharedPreferences = DoLogin.this.getSharedPreferences("mySharedPreferences", 0); SharedPreferences.Editor editor = mySharedPreferences.edit(); DoLogin.this.rememberme_username = username; DoLogin.this.rememberme_password = password; String base64Username = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4)); CryptoClass crypt = new CryptoClass(); DoLogin.this.superSecurePassword = crypt.aesEncryptedString(DoLogin.this.rememberme_password); editor.putString("EncryptedUsername", base64Username); editor.putString("superSecurePassword", DoLogin.this.superSecurePassword); editor.commit(); }
SharedPreferences API를 사용하여 정보를 저장하게되면 /data/data/com.test.test/shared_prefs 위치에 저장이 된다. 따라서 ADB를 이용하여 해당 위치의 mySharedPreferences.xml를 확인해보면 암호화 된 usersename과 password가 있는 것을 볼 수 있다.
Encryptedusername 값을 Base64로 디코딩 하면 다음과 같이 값이 나온다.
디코딩 도구는 Dreamhack Tool을 이용하였다.
superSecurePassword 값은 CryptoClass의 aesEncryptedString로 암호화 되었기 때문에 CrptoClass.class를 분석해 보았다.
aesEncryptedString는 string을 입력 받아 aes256encrypt를 이용하여 키 "This is the super secret key 123" 와 함께 암호화 하는 것을 볼 수 있다.
public class CryptoClass { String base64Text; byte[] cipherData; String cipherText; String plainText; String key = "This is the super secret key 123"; byte[] ivBytes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; public static byte[] aes256encrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(1, newKey, ivSpec); return cipher.doFinal(textBytes); } public static byte[] aes256decrypt(byte[] ivBytes, byte[] keyBytes, byte[] textBytes) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes); SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(2, newKey, ivSpec); return cipher.doFinal(textBytes); } public String aesDeccryptedString(String theString) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { byte[] keyBytes = this.key.getBytes("UTF-8"); this.cipherData = aes256decrypt(this.ivBytes, keyBytes, Base64.decode(theString.getBytes("UTF-8"), 0)); this.plainText = new String(this.cipherData, "UTF-8"); return this.plainText; } public String aesEncryptedString(String theString) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { byte[] keyBytes = this.key.getBytes("UTF-8"); this.plainText = theString; this.cipherData = aes256encrypt(this.ivBytes, keyBytes, this.plainText.getBytes("UTF-8")); this.cipherText = Base64.encodeToString(this.cipherData, 0); return this.cipherText; } }
따라서 대칭키 암호화를 사용하고 소스코드가 난독화 되어있지 않아 키가 공개되어 있으므로 superSecurePassword 값을 간단히 복호화 시켜 얻을 수 있었다.
복호화 도구는 https://encode-decode.com/aes256-encrypt-online/를 사용하였다.
대응방안
username이 단순 base64로 인코딩 되어 저장되어 저장하기 때문에 암호화를 통해 저장한다.
소스코드가 난독화 되어있지 않아 중요 정보가 노출되니 소스코드 난독화를 진행하여야 한다.