금융권 하드코딩 취약점 실전 시나리오

MOB-SER-025: 앱 소스코드 내 운영정보 노출 | 위험도: 5 (매우 높음)

🔴 공격자 (해커)

APK 디컴파일 준비 중...

attacker@kali:~$
$ # Waiting for command...

공격 진행 단계

시나리오를 시작하세요...

실전 공격 시나리오

취약한 코드 (하드코딩)

문제 원인
// Java - Android App
public class DatabaseHelper {
    
    // ❌ DB 정보 하드코딩!
    private static final String DB_URL = 
        "jdbc:mysql://10.1.2.3:3306/bank_db";
    
    private static final String DB_USER = "admin";
    
    // ❌ 비밀번호 평문 노출!
    private static final String DB_PASSWORD = 
        "Bank@1234!";
    
    public Connection connect() {
        return DriverManager.getConnection(
            DB_URL, DB_USER, DB_PASSWORD
        );
    }
}

public class ApiClient {
    
    // ❌ API 키 하드코딩!
    private static final String API_KEY = 
        "sk_live_51HxJ9KLp2k3M4n5o6P7q8R9s0T";
    
    // ❌ Secret 키도 노출!
    private static final String API_SECRET = 
        "whsec_9a8b7c6d5e4f3g2h1i0j";
    
    public void sendRequest() {
        headers.put("Authorization", 
            "Bearer " + API_KEY);
    }
}

public class CloudConfig {
    
    // ❌ AWS 자격증명 하드코딩!
    public static final String AWS_ACCESS_KEY = 
        "AKIAIOSFODNN7EXAMPLE";
    
    public static final String AWS_SECRET_KEY = 
        "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
    
    public static final String S3_BUCKET = 
        "xxbank-customer-data";
}

🟢 디컴파일 결과

jadx로 APK 역분석

[JADX] Waiting...
디컴파일 상태: IDLE

안전한 코드

해결책
// Java - 안전한 설정 관리

// ✅ 방법 1: 환경변수 사용
public class SecureDatabaseHelper {
    
    private static final String DB_URL = 
        System.getenv("DATABASE_URL");
    
    private static final String DB_USER = 
        System.getenv("DATABASE_USER");
    
    // 환경변수에서 안전하게 로드
    private static final String DB_PASSWORD = 
        System.getenv("DATABASE_PASSWORD");
    
    public Connection connect() {
        if (DB_URL == null || DB_PASSWORD == null) {
            throw new IllegalStateException(
                "Database credentials not configured"
            );
        }
        
        return DriverManager.getConnection(
            DB_URL, DB_USER, DB_PASSWORD
        );
    }
}

// ✅ 방법 2: 암호화된 설정 파일
public class EncryptedConfigManager {
    
    private Properties config;
    private static final String CONFIG_FILE = 
        "encrypted.properties";
    
    public EncryptedConfigManager() {
        loadEncryptedConfig();
    }
    
    private void loadEncryptedConfig() {
        try {
            // 암호화된 파일 읽기
            InputStream is = getClass()
                .getResourceAsStream(CONFIG_FILE);
            
            // AES 복호화
            byte[] encryptedData = is.readAllBytes();
            byte[] decryptedData = decrypt(
                encryptedData, 
                getDeviceKey()
            );
            
            // Properties 로드
            config = new Properties();
            config.load(new ByteArrayInputStream(
                decryptedData
            ));
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Failed to load config", e
            );
        }
    }
    
    private byte[] decrypt(byte[] data, byte[] key) {
        try {
            Cipher cipher = Cipher.getInstance(
                "AES/GCM/NoPadding"
            );
            SecretKeySpec keySpec = 
                new SecretKeySpec(key, "AES");
            
            GCMParameterSpec gcmSpec = 
                new GCMParameterSpec(128, 
                    Arrays.copyOf(data, 12));
            
            cipher.init(Cipher.DECRYPT_MODE, 
                keySpec, gcmSpec);
            
            return cipher.doFinal(data, 12, 
                data.length - 12);
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Decryption failed", e
            );
        }
    }
    
    private byte[] getDeviceKey() {
        // Android KeyStore에서 키 가져오기
        try {
            KeyStore keyStore = KeyStore
                .getInstance("AndroidKeyStore");
            keyStore.load(null);
            
            KeyStore.SecretKeyEntry entry = 
                (KeyStore.SecretKeyEntry) keyStore
                    .getEntry("ConfigKey", null);
            
            return entry.getSecretKey()
                .getEncoded();
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Failed to get device key", e
            );
        }
    }
    
    public String getProperty(String key) {
        return config.getProperty(key);
    }
}

// ✅ 방법 3: Android KeyStore 활용
public class SecureApiClient {
    
    private static final String KEY_ALIAS = 
        "api_key_alias";
    
    public SecureApiClient() {
        initializeKeyStore();
    }
    
    private void initializeKeyStore() {
        try {
            KeyStore keyStore = KeyStore
                .getInstance("AndroidKeyStore");
            keyStore.load(null);
            
            // KeyStore에 키가 없으면 생성
            if (!keyStore.containsAlias(KEY_ALIAS)) {
                KeyGenerator keyGenerator = 
                    KeyGenerator.getInstance(
                        KeyProperties.KEY_ALGORITHM_AES,
                        "AndroidKeyStore"
                    );
                
                keyGenerator.init(
                    new KeyGenParameterSpec
                        .Builder(KEY_ALIAS,
                            KeyProperties.PURPOSE_ENCRYPT |
                            KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(
                            KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(
                            KeyProperties
                                .ENCRYPTION_PADDING_NONE)
                        .setUserAuthenticationRequired(false)
                        .build()
                );
                
                keyGenerator.generateKey();
            }
            
        } catch (Exception e) {
            throw new RuntimeException(
                "KeyStore init failed", e
            );
        }
    }
    
    public String getApiKey() {
        // 서버에서 안전하게 가져오기
        try {
            // 1. 디바이스 인증
            String deviceToken = getDeviceToken();
            
            // 2. 서버에 API 키 요청
            Response response = apiService
                .getApiKey(deviceToken);
            
            // 3. KeyStore에 암호화 저장
            String apiKey = response.getApiKey();
            encryptAndStore(apiKey);
            
            return apiKey;
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Failed to get API key", e
            );
        }
    }
    
    private void encryptAndStore(String data) {
        try {
            KeyStore keyStore = KeyStore
                .getInstance("AndroidKeyStore");
            keyStore.load(null);
            
            SecretKey key = (SecretKey) keyStore
                .getKey(KEY_ALIAS, null);
            
            Cipher cipher = Cipher.getInstance(
                "AES/GCM/NoPadding"
            );
            cipher.init(Cipher.ENCRYPT_MODE, key);
            
            byte[] encrypted = cipher.doFinal(
                data.getBytes()
            );
            
            // SharedPreferences에 암호화된 데이터 저장
            SharedPreferences prefs = context
                .getSharedPreferences(
                    "secure_prefs", 
                    Context.MODE_PRIVATE
                );
            
            prefs.edit()
                .putString("encrypted_api_key", 
                    Base64.encodeToString(
                        encrypted, 
                        Base64.DEFAULT
                    ))
                .apply();
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Encryption failed", e
            );
        }
    }
}

// ✅ 방법 4: 백엔드에서 키 관리
public class SecureCloudConfig {
    
    // 하드코딩 대신 서버에서 가져오기
    public CloudCredentials getCloudCredentials() {
        try {
            // 1. 사용자 인증 토큰
            String authToken = getAuthToken();
            
            // 2. 서버에 임시 자격증명 요청
            Response response = cloudService
                .getTemporaryCredentials(authToken);
            
            // 3. STS (임시 자격증명) 반환
            return new CloudCredentials(
                response.getAccessKey(),
                response.getSecretKey(),
                response.getSessionToken(),
                response.getExpiration()
            );
            
        } catch (Exception e) {
            throw new RuntimeException(
                "Failed to get credentials", e
            );
        }
    }
}

// ProGuard 설정 (난독화)
# proguard-rules.pro

# 문자열 암호화
-keep class com.example.bank.** { *; }
-obfuscate
-repackageclasses 'o'

# 클래스/메서드 이름 난독화
-keepclassmembers class * {
    native ;
}

# 리플렉션 방지
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions

// build.gradle 설정
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
            
            // 환경변수 주입
            buildConfigField "String", "API_URL",
                System.getenv("API_URL")
        }
    }
}