Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Актуальные вопросы безопасности Android-приложений

Актуальные вопросы безопасности Android-приложений

Доклад Дмитрия Терешина (Tinkoff) для PDUG-секции на форуме PHDays 9.

More Decks by Positive Development User Group

Other Decks in Programming

Transcript

  1. Заголовок 2 • Bad design • Need to support old

    Android versions • Incorrect use of Android API Reasons
  2. Заголовок 3 • Auth sharing • SSL pinning • Token

    encryption • Android wear • Push notifications Agenda
  3. Заголовок 5 • 2 Android applications (master, slave) • common

    API • API access token Objective: seamless transition between applications Objective
  4. Заголовок 7 • AppLinks – based on your website HTTP

    URL that has been verified to belong to your website Problem: need Android 6.0+ • Intent URL – add application id intent://main/#Intent;scheme=slave;package=com.example.slave.clie nt.android;end” Problem: application id forgering Problem 1: How to resolve
  5. Заголовок 11 Problem 4: Different Signatures String pkg = this.getCallingPackage();

    PackageInfo pkgInfo = pkgmgr.getPackageInfo(pkg, GET_SIGNATURES); Signatures[] signatures = pkgInfo.signatures; for (Signature sig: signatures) { if (sig.equals(TRUSTED_SIGNATURE)) { // trusted signature found, trust the application } }
  6. Заголовок 15 • Rogue CAs • Compromised certificates • Phished

    users Goal Note: added in Android 7.0+ (Network Security Configuration) Protection against certificate forgery:
  7. Заголовок 16 X509TrustManager and HostnameVerifier customtrustManager = new X509TrustManager() {

    @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) { } @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) {} @Override public X509Certificate[] getAcceptedIssuers() {} }; HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) {} });
  8. Заголовок 17 Mistake 1: custom trust manager private static class

    X509CertPinningTrustManager implements X509TrustManager { public void checkServerTrusted(X509Certificate[] chain, String authType) throwsCertificateException List pinsInfo = AppConfig.getPinningInfo(); boolean validCertFound = false; for (pinnedCert: pinsInfo) { byte[] serverCertFingerprint = digest(chain[pinnedCert.chainPosition]); if (java.util.Arrays.equals(serverCertFingerprint, pinnedCert.fingerprint)){ validCertFound = true; break; } } if (!validCertFound) { throw new CertificateException("Invalid X509Cert used");} }
  9. Заголовок 18 Mistake 1: custom trust manager CVE-2016-2402: OkHttp before

    2.7.4 and 3.x before 3.1.2 allows man-in- the-middle attackers to bypass certificate pinning
  10. Заголовок 20 Mistake 2: custom hostname verifier HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

    public boolean verify(String hostname, SSLSession session) { return hostname.equals("www.bank.com"); } }); Pin intermediate certificate &
  11. Заголовок 22 • How to support low levels API •

    What exactly pin • How to deploy • Rotate certificate or public key • Pin failures Decisions
  12. Заголовок 23 create pin-server Good implementation use internal CA pinning

    for pin-server request pins from pin-server establish connection
  13. Заголовок 25 • Send pin-code & RefreshToken to server •

    Local auth by pin-code, send RefreshToken to server • Encrypt RefreshToken with pin-code Mistakes
  14. Заголовок 27 Problem 1: padding 01 02 02 03 03

    03 04 04 04 04 05 05 05 05 05 06 06 06 06 06 06 … … | 61 62 66 65 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C | For our token:
  15. Заголовок 29 Token Format public String createRefreshToken() { byte[] refreshToken

    = new byte[64]; final SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(refreshToken); return Base64.getUrlEncoder() .withoutPadding().encodeToString(refreshToken); }
  16. Заголовок 30 Encryption Algorithm private static final String ALGORITHM =

    "AES"; private static final String CIPHER_SUITE = "AES/CBC/NoPadding"; private static final int AES_KEY_SIZE = 16; public byte[] encryptToken(String token, String pin, byte[] iv, byte[] salt) { byte[] decodedToken = decodeToken(token); byte[] rawPin = pin.getBytes(); byte[] key = kdf.deriveKey(rawPin, salt, AES_KEY_SIZE); Cipher cipher = Cipher.getInstance(CIPHER_SUITE); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, ALGORITHM), new IvParameterSpec(iv)); return cipher.doFinal(decodedToken); }
  17. Заголовок 32 Communication between watch and phone Wearable.MessageApi.sendMessage(mGoogleApiClient, nodeId, COMMAND_APP_LAUNCH,

    new byte[3] ); <service android:name="WearableListenerService" android:exported="true"> <intent-filter> <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED"/> <data android:scheme="wear" android:host="*" android:path="/COMMAND_APP_LAUNCH"/> </intent-filter> </service>
  18. Заголовок 33 Problem 1: Bluetooth HCI snoop log /etc/bluetooth/bt_stack.conf: all

    bluetooth data is written to /sdcard/btsnoop_hci.log (Android < 8)
  19. Заголовок 34 Problem 1: how to resolve try { Log.d("Bluetooth

    log", String.valueOf( Settings.Secure.getInt(getContentResolver(), "bluetooth_hci_log"))); } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); }
  20. Заголовок 35 Mistake 1: hijacking in WearableListenerService public class MyWearableListenerService

    extends WearableListenerService { private static final String COMMAND_APP_LAUNCH = "/COMMAND_APP_LAUNCH"; @Override public void onMessageReceived(MessageEvent messageEvent) { if (messageEvent.getPath().equals(COMMAND_APP_LAUNCH)) { Intent intent = WearSplashActivity.newIntent(this); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } } }
  21. Заголовок 36 Mistake 1: How to resolve public class MyWearableListenerService

    extends WearableListenerService { private static final String COMMAND_APP_LAUNCH = "/COMMAND_APP_LAUNCH"; @Override public void onMessageReceived(MessageEvent messageEvent) { if (messageEvent.getPath().equals(COMMAND_APP_LAUNCH)) { Intent intent = WearSplashActivity.newIntent(this); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } } }
  22. Заголовок 38 Message receiving public class MyFirebaseMessagingService extends FirebaseMessagingService {

    @Override public void onMessageReceived(RemoteMessage remoteMessage) { sendNotification(remoteMessage.getNotification().getBody()); } private void sendNotification(String messageBody) { NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.logo_22) .setColor(getResources().getColor(R.color.logo_green)) .setContentTitle(“Пополнение. Счет RUB…“) .setContentText(messageBody) .setAutoCancel(true) .setSound(defaultSoundUri); } }
  23. Заголовок 40 • move to iOS • move to Fuchsia

    • move to the dark side • use Android API safely Conclusions