Bulletproof Android
Godfrey Nolan
Agenda
- How did we get here
- OWASP Top 10
- What’s New
- Recommendations
How did we get here
- Virtual Machines
- Static information
- Dynamic information
Decompilation 101
Decompilation 101
Decompilation 101
$ adb shell pm path com.united.mobile.android
package:/data/app/com.united.mobile.android-1/base.apk
$ adb pull /data/app/com.united.mobile.android-1/base.apk
4349 KB/s (51855610 bytes in 11.642s)
$ jadx-gui base.apk
$ adb backup com.united.mobile.android
Now unlock your device and confirm the backup operation.
$ java -jar abe.jar unpack backup.ab backup.tar
$ tar -xvf backup.tar
$ sqlite3 apps/com.united.mobile.android/db/united.db
Decompilation 101
Decompilation 101
Audit Reports
OWASP Top 10
- Weak Server Side Controls
- Insecure Data Storage
- Insufficient Transport Layer Protection
- Unintended Data Leakage
- Poor Authorization and Authentication
- Broken Cryptography
- Client Side Injection
- Security Decision via Untrusted Input
- Improper Session Handling
-
Lack of Binary Protections
OWASP Top 10
-
For each item
- Explain the problem
- Show an example
- Talk about the fix
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
GET http://herdfinancial.com/api/v1/balances/1234567899/
{"success":"true","checkingBalance":"0.0","savingsBalance":"0.0"}
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
GET http://herdfinancial.com/api/v1/balances/1234567890/
{"success":"true","checkingBalance":"947.3","savingsBalance":"0.0"}
Example
"actor": {“first_name": "Rita","last_name": "D.","title": "Rita D.","gender": "F",
"is_mvp": false,
"preferred_brand": 32,
"_links": {"self": [{"href": "\/v7.0\/user\/3273986\/","id": "3273986"}]},
"type": "user",
"friendship": null,
"id": 3273986
},"id": "1-3273986-9-1440092847",
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Use GUID that maps to ID
- REST verbs are easy to guess
- OWASP Web/Cloud top 10
- Don’t trust the client, verify
- Assume SSL is insecure
- Assume your app is debuggable
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="remember" value="true" />
<string name="password">goatdroid</string>
<string name="username">goatdroid</string>
</map>
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- No caching of passwords, SSNs etc.
- Minimum password length
- Validate email addresses
- Multi-factor authentication
- Client side and Server side access control
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
More Problems
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
private static String PUB_KEY = "30820122300d06092a864886f70d0101" +
"0105000382010f003082010a0282010100b35ea8adaf4cb6db86068a836f3c85" +
"5a545b1f0cc8afb19e38213bac4d55c3f2f19df6dee82ead67f70a990131b6bc" +
"ac1a9116acc883862f00593199df19ce027c8eaaae8e3121f7f329219464e657" +
"2cbf66e8e229eac2992dd795c4f23df0fe72b6ceef457eba0b9029619e0395b8" +
"609851849dd6214589a2ceba4f7a7dcceb7ab2a6b60c27c69317bd7ab2135f50" +
"c6317e5dbfb9d1e55936e4109b7b911450c746fe0d5d07165b6b23ada7700b00" +
"33238c858ad179a82459c4718019c111b4ef7be53e5972e06ca68a112406da38" +
"cf60d2f4fda4d1cd52f1da9fd6104d91a34455cd7b328b02525320a35253147b" +
"e0b7a5bc860966dc84f10d723ce7eed5430203010001";
// Pin it!
final boolean expected = PUB_KEY.equalsIgnoreCase(encoded);
if (!expected) {
throw new CertificateException("checkServerTrusted: Expected public key: "
+ PUB_KEY + ", got public key:" + encoded);
}
}
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Assume SSL is broken
- SSL pinning
- Do more on the server
- Scan with nogotofail
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Example
public ActivityLaunchAppLoad() {
this.WAY_WAY_TOO_LOW = 49;
this.A_LITTLE_LESS_WAY_TOO_LOW = 50;
this.LESSER_WAY_TOO_LOW = 51;
this.BIT_TOO_LOW = 52;
this.TOO_LOW = 53;
this.MORE = 54;
this.A_LITTLE_MORE = 55;
this.WAY_TOO_MORE = 97;
this.BIG_DADDY = 102;
this.orderOfTheThronesTrois = new int[]{this.BIG_DADDY, this.MORE, this.WAY_TOO_MORE, this.MORE};
this.orderOfTheThronesQuatre = new int[]{this.LESSER_WAY_TOO_LOW, this.MORE, this.LESSER_WAY_TOO_LOW, this.TOO_LOW};
this.orderOfTheThronesUn = new int[]{this.BIT_TOO_LOW, this.BIT_TOO_LOW, this.WAY_WAY_TOO_LOW, this.BIT_TOO_LOW};
this.orderOfTheThronesDeux = new int[]{this.MORE, this.A_LITTLE_MORE, this.A_LITTLE_LESS_WAY_TOO_LOW, this.BIT_TOO_LOW};
}
String createTheHalfBloodPrince() {
String strTemp = StringUtils.EMPTY;
int x = 0;
while (x < 4) {
int[] xyz = null;
if (x == 0) {
xyz = this.orderOfTheThronesTrois;
} else if (x == 1) {
xyz = this.orderOfTheThronesQuatre;
} else if (x == 2) {
xyz = this.orderOfTheThronesUn;
} else if (x == 3) {
xyz = this.orderOfTheThronesDeux;
}
int y = 3;
while (y >= 0) {
strTemp = new StringBuilder(String.valueOf(strTemp)).append(Character.toString((char) xyz[y])).toString();
y--;
}
x++;
}
return strTemp;
}
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Strip any logging code if possible
- Check any third party libraries
- Double check your webview caches
- Download and unzip your APK
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="TM_MEMBER_EMAIL">godfrey@riis.com</string>
<int name="TM_MEMBER_MARKET_ID" value="7" />
<string name="TM_MEMBER_TAP_ID">77ef62159ad9c32913dfdbee0e58aea3</string>
<string name="TM_MEMBER_LNAME"></string>
<string name="TM_MEMBER_LANGUAGE">en-us</string>
<int name="TM_BILLING_COUNTRY_CODE" value="-1" />
<string name="TM_MEMBER_POSTCODE">48070</string>
<string name="TM_LAST_BILLING_ID"></string>
<int name="TM_MEMBER_COUNTRY" value="840" />
<string name="TM_MEMBER_PASSWORD">2secret4me</string>
<string name="TM_MEMBER_FNAME">Godfrey</string>
</map>
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- No password caching
- Minimum Password Length
- Validate Email Address
- Multi Factor Authentication
- Encryption
- Public-Private Key exchange
- Public-Private Key exchange
- Tokens, tokens, tokens
- OAuth
- Use Server side nonce’s
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Example
public static String decrypt(String paramString)
throws Exception
{
if (paramString != null)
return new String(decrypt(getRawKey("3lIoM_d0idrn4|4TleD".getBytes()), toByte(paramString)));
return null;
}
private static byte[] decrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
throws Exception
{
SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramArrayOfByte1, "AES");
Cipher localCipher = Cipher.getInstance("AES");
localCipher.init(2, localSecretKeySpec);
return localCipher.doFinal(paramArrayOfByte2);
}
Example
// NDK code - still see the code in disassembler
jstring Java_com_riis_decompilingandroid_getPassword(JNIEnv* env, jobject thiz)
{
return (*env)->NewStringUTF(env, "xeHnwfiy4uzefrabruebeb");
}
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Use asymmetric encryption
- Encrypt databases
- Android Keystore is broken
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
select * from login where USERNAME = '' OR 1=1 --'
and PASSWORD = 'test'
Problem
public boolean checkLogin(String param1, String param2)
{
boolean bool = false;
Cursor cursor = db.rawQuery("select * from login where USERNAME = '" +
param1 + "' and PASSWORD = '" + param2 + "';", null);
if (cursor != null) {
if (cursor.moveToFirst())
bool = true;
cursor.close();
}
return bool;
}
select * from login where USERNAME = '' OR 1=1 --' and PASSWORD = 'test'
Fix
public boolean checkLogin(String param1, String param2)
{
boolean bool = false;
Cursor cursor = db.rawQuery("select * from login where " +
"USERNAME = ? and PASSWORD = ?", new String[]{param1, param2});
if (cursor != null) {
if (cursor.moveToFirst())
bool = true;
cursor.close();
}
return bool;
}
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
<script>alert("xss");</script>
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Use parameterized queries
- setJavaScriptEnabled(false)
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Problem
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.riis.login"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.riis.login.LoginActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.riis.login.IntentReceiverActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="com.riis.login.IntentReceiverActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
Problem
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.riis.hellointent"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.riis.hellointent.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.riis.login.IntentReceiverActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
Fix
// implicit
Intent intent = new Intent();
// explicit
Intent intent = new Intent(this, IntentReceiverActivity.class);
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Use explicit intents
- Scan app using Intent Sniffer
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
if (dao.isDevicePermanentlyAuthorized(deviceID)) {
String newAuthToken = Utils.generateAutToken();
doa.updateAuthrizedDeviceAuth(deviceID, newAuthToken);
login.setAuthToken(newAuthToken);
login.setUserName(dao.getUserName(newAuthToken));
login.setAccountNumber(dao.getAccountNumber(newAuthToken));
login.setSuccess(true);
}
Example
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- App should not be permanently authorized to access the backend server
- Try backup on another phone
- Careful using OAuth logins to FB etc.
Problem
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
/**
* Logs you into your SIP provider, registering this device as the location to
* send SIP calls to for your SIP address.
*/
public void initializeLocalProfile() {
if (manager == null) {
return;
}
if (me != null) {
closeLocalProfile();
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String username = prefs.getString("namePref", "");
String domain = prefs.getString("domainPref", "");
String password = prefs.getString("passPref", "");
Example
Fix
Weak Server Side Controls
Insecure Data Storage
Insufficient Transport Layer Protection
Unintended Data Leakage
Poor Authorization and Authentication
Broken Cryptography
Client Side Injection
Security Decision via Untrusted Input
Improper Session Handling
Lack of Binary Protections
- Obfuscation helps remove useful info
- not a silver bullet
- Anti Dexguard apps out there
- Hackers just move to Smali
- Move to NDK
- harder to read
- you can still disassemble C++
What's New
- Jadx
- android:debuggable(true)
- some Smali required
- SSL Pinning
- Bug Bounties
What's New
What's New
- Disassemble using apktool
- Find main class in AdroidManifest.xml
- Add debug wait to onCreate method
- Recompile using apktool
- Sign and install
java -jar apktool.jar d -d test.apk -o out
<activity android:label="@string/app_name" android:name="com.riis.helloworld.MainActivity">
a=0;// # virtual methods
a=0;// .method protected onCreate(Landroid/os/Bundle;)V
a=0;// invoke-static {}, Landroid/os/Debug;->waitForDebugger()V
a=0;//
a=0;// .locals 1
a=0;// .param p1, "savedInstanceState" # Landroid/os/Bundle;
java -jar apktool.jar b -d out -o debug.apk
What's New
- Security is too difficult to keep up with??
- Crowdsource it with Bug Bounties
- Alternative to security firms
- United Airlines offering substantial airmiles
- Lessons Learned
- Requires effort to keep up with submissions
- Update your app often to keep interest alive
Reasons to Ignore Security
- Security is too difficult to keep up with
- Requires physical access
- Avast report - 80k old phones on eBay
- allowBackup=false
- Proguard is too hard to use
- The code is already obfuscated
- You need to talk to the API team
- We don't have time
- (Credit Union audit)
Recommendations
- Understand debuggable=true, allowbackup=true
- Don’t trust, verify
- Rewrite SSL code, use asymmetric encryption
- Provide an email or security page for white hats
- Attacks are going to get more complex
- Start a Bug Bounty
- Store nothing important on the device
- Don't ignore Smali attacks
- Secure your server
Resources
http://www.decompilingandroid.com
http://www.owasp.org
https://github.com/nelenkov/android-backup-extractor
http://www.charlesproxy.com
http://www.programering.com/a/MjM5UTMwATg.html
http://www.cs.ru.nl/~joeri/papers/spsm14.pdf
https://www.mwrinfosecurity.com/products/drozer
https://github.com/skylot/jadx
http://keyczar.org
https://www.nccgroup.trust/us/about-us/resources/intent-sniffer/
http://www.saikoa.com
http://sqlitebrowser.org
http://bit.ly/1JlPoiY - How to hide your android API key
http://bit.ly/1hIeNNi - Where to store your password
https://github.com/google/nogotofail
https://codio.com/godfreynolan/AnDevCon-Bulletproof
Contact Details
godfrey@riis.com
@godfreynolan
slideshare.com/godfreynolan
Bulletproof Android
By godfreynolan
Bulletproof Android
- 983