티스토리 뷰

반응형

취약점 소개

안드로이드 애플리케이션에서 단말기에 주요 정보를 저장하기 위해서는
공격자에게 정보가 노출되지 않도록 평문이 아닌 암호화된 데이터로 저장해야 한다.
이러한 과정에서 발생하는 취약점을 로컬 암호화 취약점이라 한다.
암호화 과정에서 대칭키 암호화 방식은 키 배송 문제가 있어
공개키 암호화를 이용하여 수신자의 공개키로 암호화하고,
수신자의 개인키로 복호화하여 정보의 기밀성을 보장할 수 있다.


(키 배송 문제 : 대칭 키를 보내야만 복호화가 가능한데,
이 키를 어떻게 사전에 안전하게 배송할 것인가의 문제이다.)




취약점 진단 수행

Autofill Credentials 기능 확인

다음 인시큐어뱅크 앱에서의 초기 로그인 화면을 보자.



그림 3-1 인시큐어뱅크 앱 초기 로그인 화면 - Autofill Credentials 기능



Autofill Credentials 기능을 사용하면
사용자가 입력한 아이디와 비밀번호 정보를 단말기 어딘가에 저장해두고,
다음부터는 입력하지 않아도 자동으로 채워지게 된다.
즉, 자동 로그인 기능과 유사한 것이다.



정보 저장 디렉터리 탐색

아이디와 비밀번호 정보가 어디에 저장되는지 우리는 탐색해 볼 필요가 있다.
먼저 nox_adb shell 명령어로 쉘에 진입하고
/data/data/com.android.insecurebankv2/ 디렉터리로 접근하여 어떤 데이터가 있는지 알아본다.
/data/data/는 안드로이드 애플리케이션에서 필요한 데이터를 저장해놓는 위치이다.



그림 3-2 /data/data/com.android.insecurebankv2 디렉터리의 내용



총 8개의 디렉터리가 확인된다.
디렉터리에 있는 여러 파일을 한 번에 확인하기 위해 PC 단말로 복사한 후에 확인해본다.
nox_adb pull /data/data/insecurebankv2 .\ 명령어를 사용한다. 총 53개의 파일이 복사되었다.



로컬 PC에 파일 복사


그림 3-3 로컬 PC 단말로 /data/data/com.android.insecurebankv2의 내용을 복사



mydb 데이터베이스 파일 확인

databases 디렉터리에 mydb라는 데이터베이스 파일이 있어
SQLite로 3개의 테이블을 확인해보았으나 중요 정보는 존재하지 않는다.



그림 3-4 mydb 파일의 테이블 내용



shared_prefs 디렉터리 확인

다음으로 shared_prefs 디렉터리를 탐색한다.
아래와 같이 두 개의 파일이 있는데, 편집기를 통해 확인한다.
이 파일들은 초기 설정값, 자동 로그인 등의 환경 변수를 앱의 저장 공간 내에 파일 형태로 저장하는
안드로이드의 SharedPreferences 객체를 활용한 것이며, 삭제하지 않는 이상 값이 유지되는 특징이 있다.



그림 3-5 shared_prefs 디렉터리의 파일들




먼저 com.android.insecurebankv2_preferences.xml 파일을 열어보니,
아래와 같이 서버의 포트와 ip가 노출되고 있다.


그림 3-6 서버 포트와 ip의 노출



다음으로는 mySharedPreferences.xml 파일을 열어보니,
아래 그림처럼 사용자 아이디와 비밀번호로 추정되는 정보가 저장되어 있다.
EncryptedUsername은 암호화된 사용자 아이디, superSecurePassword는 중요 패스워드로 추정된다.


그림 3-7 사용자 아이디와 비밀번호 정보의 노출



그렇다면 위 xml 파일들은 언제 값이 저장되는 것인지 인시큐어뱅크 앱의 코드를 확인한다.
사용자 아이디와 비밀번호를 저장하는 것은 saveCreds(...) 함수이다.


private void saveCreds(String username, String password) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
   SharedPreferences mySharedPreferences;
   mySharedPreferences = getSharedPreferences(MYPREFS, Activity.MODE_PRIVATE);
   SharedPreferences.Editor editor = mySharedPreferences.edit();
   rememberme_username = username;
   rememberme_password = password;
   String base64Username = new String(Base64.encodeToString(rememberme_username.getBytes(), 4));
   CryptoClass crypt = new CryptoClass();;
   superSecurePassword = crypt.aesEncryptedString(rememberme_password);
   editor.putString("EncryptedUsername", base64Username);
   editor.putString("superSecurePassword", superSecurePassword);
   editor.commit();
}
표 3-1 DoLogin.java 파일 내의 SharedPreferences 객체 사용



위 코드를 보면, SharedPreferences 객체를 생성하고, getSharedPreferences 함수로 객체를 받아온다.
xml 파일은 MYPREFS라는 문자열 변수로 선언되어 있던 "mySharedPreferences.xml"의 이름으로 저장된다.


public class DoLogin extends Activity {
   ...   
   public static final String MYPREFS = "mySharedPreferences";
   ...
}
표 3-2 SharedPreferences의 xml 파일명을 지정하는 부분



이후, 사용자의 아이디를 Base64 방식으로 인코딩하고
비밀번호를 CryptoClass() 함수를 통해 AES 방식으로 암호화하여
SharedPreferences 객체의 값들을 편집기를 통해 수정한다.
(편집기를 통해야만 값이 수정될 수 있다.)
그렇다면 사용자 아이디를 Base64로 디코딩할 수 있는지 https://www.base64decode.org/에서 테스트해보면,
아래와 같이 jack 이라는 사용자 아이디를 확인할 수 있다.



그림 3-8 사용자 아이디 Base64 디코딩 시도 결과



이제 사용자 아이디를 확인했으니, 비밀번호까지 탈취할 수 있는지 알아본다.
CryptoClass() 함수로 암호화를 시도했으니, CryptoClass() 클래스로 이동한다.



암호화 코드 확인

String key = "This is the super secret key 123";

...

public String aesEncryptedString(String theString) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
   byte[] keyBytes = key.getBytes("UTF-8");
   plainText = theString;
   cipherData = CryptoClass.aes256encrypt(ivBytes, keyBytes, plainText.getBytes("UTF-8"));
   cipherText = Base64.encodeToString(cipherData, Base64.DEFAULT);
   return cipherText;
}
표 3-3 CryptoClass.java 파일의 비밀번호 저장 코드



위 코드는 다음과 같은 논리로 작성되었다.
AES 대칭키 암호화를 위해서는 키가 필요한데,
그 키를 "This is the super secret key 123"으로 설정하였다.


또한, 사용자가 입력한 비밀번호를 평문(theString: plainText)으로 받아와서,
aes256encrypt 함수를 이용하여 AES로 암호화하였고,
이를 Base64로 다시 인코딩하여 암호화된 비밀번호를 리턴하였다.
이 리턴한 값을 mySharedPreferences.xml 파일에 저장하며,
값은 "v/sJpihDCo2ckDmLW5Uwiw=="이고,
이는 앞서 언급한 "This is the super secret key 123"이라는 키로 AES 암호화된 값이다.


다음 그림을 보자.



그림 3-9 사용자 비밀번호 AES 암호화의 해독



위 그림은 https://www.devglan.com/online-tools/aes-encryption-decryption 에서
AES 암호화 값을 해독 시도한 결과이다.
AES 암호화 값을 Base64 출력 값으로 만든 뒤, 평문으로 해독한 결과는 Jack@123$으로,
jack 계정의 비밀번호인 것이다.


이렇듯, 아이디는 Base64로 쉽게 디코딩이 가능하여 알아낼 수 있었고,
대칭키 암호화 방식을 채택한 비밀번호를 암호화한 키가 노출되면서 계정 정보의 습득이 가능하였다.
만약 이중적인 보안 인증 방식이 존재하지 않는다면
본 계정은 이러한 공격에 의해 쉽게 무력화되는 것이다.




대응 방안

아이디가 Base64인코딩을 통해서만 저장되었던 점이 문제이다.
그러므로 비밀번호와 같이 AES 암호화를 적용해야 하며,
대칭키가 있으면 복호화가 가능하므로 키 관리를 따로 해야한다.
먼저 DoLogin 파일을 수정한다.


DoLogin.java 수정

private void saveCreds(String username, String password) throws ... {
   SharedPreferences mySharedPreferences;
   mySharedPreferences = getSharedPreferences(MYPREFS, Activity.MODE_PRIVATE);
   SharedPreferences.Editor editor = mySharedPreferences.edit();
   rememberme_username = username;
   rememberme_password = password;
   String base64Username = new String(Base64.encodeToString(rememberme_username.getBytes(), 4));
   CryptoClass crypt = new CryptoClass();;
   superSecurePassword = crypt.aesEncryptedString(rememberme_password);
   editor.putString("EncryptedUsername", base64Username);
   editor.putString("superSecurePassword", superSecurePassword);
   editor.commit();
}
표 4-1 로컬 암호화 이슈: DoLogin.java 파일 수정 전



private void saveCreds(String username, String password) throws ... {
   SharedPreferences mySharedPreferences;
   mySharedPreferences = getSharedPreferences(MYPREFS, Activity.MODE_PRIVATE);
   SharedPreferences.Editor editor = mySharedPreferences.edit();
   rememberme_username = username;
   rememberme_password = password;
   CryptoClass crypt = new CryptoClass();
   String encryptedUsername = crypt.aesEncryptedString(rememberme_username);
   superSecurePassword = crypt.aesEncryptedString(rememberme_password);
   editor.putString("EncryptedUsername", encryptedUsername);
   editor.putString("superSecurePassword", superSecurePassword);
   editor.commit();
}
표 4-2 로컬 암호화 이슈: DoLogin.java 파일 수정 후



위처럼, 로그인 시 사용자 아이디도 비밀번호와 같이
AES 암호화를 수행하여 xml파일에 저장할 수 있도록 하였다.


이제, 자동 로그인 기능에서도 Base64 디코딩을 통한 데이터 채움이 아닌
AES 해독을 통해 데이터를 채울 수 있도록 LoginActivity.java 파일의 코드를 수정한다.



LoginActivity.java 수정

Username_Text = (EditText) findViewById(R.id.loginscreen_username);
Password_Text = (EditText) findViewById(R.id.loginscreen_password);
Username_Text.setText(usernameBase64ByteString);
CryptoClass crypt = new CryptoClass();
String decryptedPassword = crypt.aesDeccryptedString(password);
Password_Text.setText(decryptedPassword);
표 4-3 로컬 암호화 이슈: LoginActivity.java - fillData() 함수 내용 수정 전



Username_Text = (EditText) findViewById(R.id.loginscreen_username);
Password_Text = (EditText) findViewById(R.id.loginscreen_password);
Username_Text.setText(usernameBase64ByteString);
CryptoClass crypt = new CryptoClass();
String decryptedId = crypt.aesDeccryptedString(username);
String decryptedPassword = crypt.aesDeccryptedString(password);
Username_Text.setText(decryptedId);
Password_Text.setText(decryptedPassword);
표 4-4 로컬 암호화 이슈: LoginActivity.java - fillData() 함수 내용 수정 후



수정이 끝났다. 다음 그림으로 결과를 확인하자.
취약점 진단 수행과 같은 과정을 거친다.



결과 확인


그림 4-1 로컬 암호화 이슈: 코드를 수정 후 다시 shared_prefs를 로컬 PC로 가져옴



mySharedPreferences.xml 파일을 다시 확인해보면, 이전과 다른 사용자 아이디 값이 보인다.
Base64 디코딩을 시도해본다.




그림 4-2 로컬 암호화 이슈: 수정 후 사용자 아이디가 암호화 됨




그림 4-3 로컬 암호화 이슈: Base64 디코딩 시도



Base64 디코딩을 시도했지만 해독되지 않았다.
코드를 수정한 것처럼 AES 암호화가 정상적으로 수행되었음을 확인할 수 있다.
결론❗은 다음과 같다.



결론

중요 정보를 로컬(단말기)에 저장할 때는 평문으로 저장해서는 안되며,
안전한 암호화 알고리즘을 채택해야 하며,
키의 길이 등을 충분히 안전한 권장되는 길이로 설정해야 한다.



반응형
댓글
반응형
Recent Post.
Recent Reply.
Thanks for comming.
오늘은
명이 방문했어요
어제는
명이 방문했어요