Introduction

Part 1에 이어서 남은 취약점을 Part 2로 진행해보겠습니다.

Weak Cryptography

취약점 설명

Frida를 사용하여 앱 내의 암호화 메서드를 후킹함으로써 암호화 키, IV(초기화 벡터), 암호화 알고리즘 등 민감한 정보를 획득할 수 있는 취약점입니다.

코드 분석

Encrypt (AES/ECB 모드 사용)

public static String encrypt(String value) throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeyException {

        try {

            SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), "AES");
            // 취약점: ECB 모드는 동일한 평문 블록을 동일한 암호문으로 변환하여 패턴이 노출됨
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
            cipher.init(1, secretKeySpec);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return new String(encrypted);
        } catch (InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        }
    }
  1. MD5
    public static String md5Hash(String text) throws NoSuchAlgorithmException {
        StringBuilder stringBuilder = new StringBuilder();
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(text.getBytes());
            byte[] messageDigest = digest.digest();
            stringBuilder.append(String.format("%032X", new BigInteger(1, messageDigest)));
        } catch (Exception e) {
            Log.d("ALLSAFE", e.getLocalizedMessage());
        }
        return stringBuilder.toString();
    }
  1. randomNumber
    public static String randomNumber() {
        Random rnd = new Random();
        int n = rnd.nextInt(100000) + 1;
        return Integer.toString(n);
    }

PoC 과정

암호화 과정을 수동으로 분석하는 것도 좋지만, 여기서는 Codeshare에 공개된 Intercept Android APK Crypto Operations Frida 스크립트를 사용하여 암호화 작업을 모니터링합니다.

해당 Frida 스크립트와 함께 앱을 실행(Spawn)합니다.

다음으로, 텍스트 입력창에 임의의 문구를 입력하고 ENCRYPT 버튼을 누릅니다.

스크립트 로그를 통해 방금 수행된 암호화 프로세스의 알고리즘, 비밀 키(Secret Key), 그리고 평문 정보가 출력되는 것을 확인할 수 있습니다.

일반적인 위험성

  • 기밀성 상실: 암호화 키가 노출되면 암호화된 모든 사용자 데이터가 복호화될 수 있습니다.
  • 데이터 위변조: 공격자가 키를 알게 되면 데이터를 암호화하여 정상적인 데이터인 것처럼 서버나 앱에 주입할 수 있습니다.

대응 방안

  • KeyStore 사용: 암호화 키를 소스 코드나 리소스에 하드코딩하지 말고 Android Keystore System에 안전하게 저장해야 합니다.
  • 표준 알고리즘 준수: AES/ECB 대신 AES/GCM/NoPadding과 같은 안전한 모드를 사용하고, MD5 대신 SHA-256 이상을 사용합니다.
  • SecureRandom 사용: 난수 생성 시 java.security.SecureRandom을 사용합니다.

Insecure Service

취약점 설명

앱의 서비스 컴포넌트가 불필요하게 외부로 노출(exported=true)되어 있어, 권한이 없는 외부 앱이나 사용자가 해당 서비스를 강제로 실행시킬 수 있는 취약점입니다.

코드 분석

Service 시작 로직:

    public /* synthetic */ void lambda$onCreateView$0(View v) {
        if (ActivityCompat.checkSelfPermission(requireActivity(), "android.permission.RECORD_AUDIO") != 0 && ActivityCompat.checkSelfPermission(requireActivity(), "android.permission.READ_EXTERNAL_STORAGE") != 0 && ActivityCompat.checkSelfPermission(requireActivity(), "android.permission.WRITE_EXTERNAL_STORAGE") != 0) {
            ActivityCompat.requestPermissions(requireActivity(), new String[]{"android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE"}, 0);
        } else {
            requireActivity().startService(new Intent(requireActivity(), (Class<?>) RecorderService.class));
        }
    }

앱이 RECORD_AUDIO 권한을 가지고 있으며, 코드를 확인해보면 START SERVICE 버튼 클릭 시 infosecadventures.allsafe.challenges.RecorderService가 실행됩니다.

RecorderServiceAndroid Media Recorder를 설정하여 몇 초간 오디오를 녹음한 뒤 /sdcard/Download 경로에 mp3 파일로 저장합니다.

AndroidManifest.xml을 확인해보면 해당 서비스가 exported로 설정되어 있어 외부 호출이 가능함을 알 수 있습니다.

PoC 과정

ADB를 사용하여 앱을 열지 않고도 직접 녹음 서비스를 실행할 수 있습니다.

PS C:\Users\WIN11> adb shell am startservice infosecadventures.allsafe/.challenges.RecorderService
Starting service: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=infosecadventures.allsafe/.challenges.RecorderService }

명령어 실행 후 기기가 실제로 녹음을 시작하고, 예상된 경로에 오디오 파일을 저장하는 것을 확인할 수 있습니다.

일반적인 위험성

  • 사생활 침해: 공격자가 사용자 몰래 백그라운드에서 녹음, 위치 추적 등의 기능을 실행하여 민감한 정보를 수집할 수 있습니다.
  • 자원 소모: 불필요한 서비스 실행으로 배터리나 데이터를 소모시킬 수 있습니다.

대응 방안

  • Exported False: 외부 앱에서 서비스를 실행할 필요가 없다면 android:exported="false"로 설정합니다.
  • 권한 검사: 외부 호출이 필요하다면 서비스 시작 시 checkCallingOrSelfPermission 등을 통해 호출자의 권한을 검증해야 합니다.

Object Serialization (객체 직렬화)

취약점 설명

안전하지 않은 데이터 저장 방식과 역직렬화(Deserialization) 취약점이 결합된 문제입니다. 공격자가 직렬화된 객체 파일을 변조하여 권한을 상승시키거나 의도하지 않은 코드를 실행할 수 있습니다.

코드 분석

User 클래스 (Serializable 구현):

    public static class User implements Serializable {
        String password;
        String role = "ROLE_AUTHOR";
        String username;

        public User() {
        }

        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }

        public String toString() {
            return "User{username='" + this.username + "', password='" + this.password + "', role='" + this.role + "'}";
        }
    }

코드 내부에는 사용자 이름(username), 비밀번호(password), 그리고 기본 역할(ROLE_AUTHOR)을 가진 User 객체가 정의되어 있습니다.

직렬화 (저장 로직):

    public /* synthetic */ void lambda$onCreateView$0(TextInputEditText username, TextInputEditText password, String path, View v) throws IOException {
        if (!((Editable) Objects.requireNonNull(username.getText())).toString().isEmpty() && !((Editable) Objects.requireNonNull(password.getText())).toString().isEmpty()) {
            User user = new User(username.getText().toString(), password.getText().toString());
            try {
                File file = new File(path);
                FileOutputStream fos = new FileOutputStream(file);
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(user);
                oos.close();
                fos.close();
            } catch (IOException e) {
                Log.d("ALLSAFE", e.getLocalizedMessage());
            }
            SnackUtil.INSTANCE.simpleMessage(requireActivity(), "User data successfully saved!");
            return;
        }
        SnackUtil.INSTANCE.simpleMessage(requireActivity(), "Fill out the fields!");
    }

사용자가 정보를 입력하고 SAVE를 클릭하면 ObjectOutputStream.writeObject를 사용하여 User 객체를 앱의 외부 파일 경로(/sdcard/Android/data/infosecadventures.allsafe/files)에 저장합니다.

역직렬화 (로드 로직):

    public /* synthetic */ void lambda$onCreateView$1(String path, View v) throws IOException {
        if (new File(path).exists()) {
            try {
                File file = new File(path);
                FileInputStream fis = new FileInputStream(file);
                ObjectInputStream ois = new ObjectInputStream(fis);
                User user = (User) ois.readObject();
                ois.close();
                fis.close();
                if (!user.role.equals("ROLE_EDITOR")) {
                    SnackUtil.INSTANCE.simpleMessage(requireActivity(), "Sorry, only editors have access!");
                } else {
                    SnackUtil.INSTANCE.simpleMessage(requireActivity(), "Good job!");
                    Toast.makeText(requireContext(), user.toString(), 0).show();
                }
                return;
            } catch (IOException | ClassNotFoundException e) {
                Log.d("ALLSAFE", e.getLocalizedMessage());
                return;
            }
        }
        SnackUtil.INSTANCE.simpleMessage(requireActivity(), "File not found!");
    }

LOAD를 클릭하면 저장된 객체를 역직렬화한 뒤, 역할(Role)이 ROLE_EDITOR인지 확인합니다. 이때 앱은 객체의 무결성을 검증하지 않습니다.

PoC 과정

앱은 직렬화된 객체의 무결성을 확인하지 않으므로, 저장된 파일을 수정하여 원본을 덮어쓰기만 하면 됩니다.

먼저 임의의 계정 정보 입력 후 'SAVE' 버튼을 클릭합니다.

starlteks:/sdcard/Android/data/infosecadventures.allsafe/files # ls -al
total 12
drwxrwx--x 2 u0_a265 sdcard_rw 4096 2026-01-21 23:57 .
drwxrwx--x 3 u0_a265 sdcard_rw 4096 2026-01-21 23:52 ..
-rw-rw---- 1 u0_a265 sdcard_rw  174 2026-01-21 23:57 user.dat

기기 내에 저장된 user.dat 파일을 확인합니다. 그런 다음, 해당 파일을 로컬로 가져옵니다.

Hex Editor를 사용하여 직렬화된 파일 내의 Role 값(ROLE_AUTHOR)을 ROLE_EDITOR로 변조합니다.

변조된 파일로 교체한 뒤 LOAD 기능을 실행하면 성공적으로 권한이 상승된 것을 확인할 수 있습니다.

일반적인 위험성

  • 권한 상승 (Privilege Escalation): 일반 사용자가 관리자 권한을 획득할 수 있습니다.
  • 원격 코드 실행 (RCE): 역직렬화 과정에서 악의적인 객체가 로드되어 임의의 코드가 실행될 수 있습니다.

대응 방안

  • 직렬화 지양: 가능하다면 Java 직렬화 대신 JSON이나 Protocol Buffers와 같은 안전한 데이터 포맷을 사용합니다.
  • 무결성 검증: 데이터 저장 시 HMAC 등을 사용하여 데이터가 변조되지 않았음을 검증해야 합니다.
  • 역직렬화 필터링: ObjectInputFilter 등을 사용하여 역직렬화가 허용되는 클래스를 엄격하게 제한합니다.