测试APP权限
overview
每个Android app在一个进程沙箱中活动,app必须明确申请沙箱外的资源和数据。访问请求通过声明他们需要的使用系统数据和特征的权限。基于数据和特征的敏感关键程度,系统会自动授权或者询问用户批准请求。
基于提供的保护程度,权限分为四个种类:
- Normal:对其他应用、用户、系统风险小,在安装应用时授权。是默认的权限。
- Dangrous:这类权限往往掌控用户数据和以影响用户的方式控制设备。这类权限可能不会在安装时被授权,app是否能获得权限会遵循用户的决定。
- Signature:只有在请求app与申明这项权限的app签名相同时,才能被授予。如果签名匹配,自动授予权限。
- SystemOrSignature:只授权给嵌入系统镜像中的应用程序,或者与声明权限相同的应用程序签名一致。
自定义权限
Android允许app向其他app暴露组件,定制权限在app访问暴露组件时需要。可以在manifest.xml文件中使用两个强制属性创建标签:
android:name
android:protectionLevel
依据最小权限原则,创建自定义权限是非常关键的:权限应该按照目的,用有意义和精确的标签和描述显式定义。
下面是一个自定义权限START_MAIN_ACTIVITY
的例子,在启动TEST_ACTIVITY
活动时需要。
<permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"
android:label="Start Activity in myapp"
android:description="Allow the app to launch the activity of myapp app, any app you grant this permission will be able to launch main activity by myapp app."
android:protectionLevel="normal" />
<activity android:name="TEST_ACTIVITY"
android:permission="com.example.myapp.permission.START_MAIN_ACTIVITY">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
任何声明了START_MAIN_ACTIVITY
权限的app都可以启动这个活动,通过user-permission
标签请求:
<uses-permission android:name="com.example.myapp.permission.START_MAIN_ACTIVITY"/>
静态分析
Android 权限
检查是否需要这些权限,去除不必要的权限。
仔细检查权限,与开发者劝人每一个权限的目的,去除不必要的权限。
除了分析AndroidManifest.xml
文件外,可以使用工具aapt:
$ aapt d permissions com.owasp.mstg.myapp
uses-permission: android.permission.WRITE_CONTACTS
uses-permission: android.permission.CHANGE_CONFIGURATION
uses-permission: android.permission.SYSTEM_ALERT_WINDOW
uses-permission: android.permission.INTERNAL_SYSTEM_WINDOW
定制权限
除了通过应用程序清单文件强制执行定制权限之外,还可以通过编程方式检查权限。但是,这是不推荐的,因为它更容易出错,并且可以更容易地绕过它,例如,运行时插装。当看到像下面的代码片段这样的代码时,请确保在manifest文件中强制执行相同的权限。
int canProcess = checkCallingOrSelfPermission("com.example.perm.READ_INCOMING_MSG");
if (canProcess != PERMISSION_GRANTED)
throw new SecurityException();
动态分析
已经安装的应用的权限可以由drozer获得
dz> run app.package.info -a com.android.mms.service
Package: com.android.mms.service
Application Label: MmsService
Process Name: com.android.phone
Version: 6.0.1
Data Directory: /data/user/0/com.android.mms.service
APK Path: /system/priv-app/MmsService/MmsService.apk
UID: 1001
GID: [2001, 3002, 3003, 3001]
Shared Libraries: null
Shared User ID: android.uid.phone
Uses Permissions:
- android.permission.RECEIVE_BOOT_COMPLETED
- android.permission.READ_SMS
- android.permission.WRITE_SMS
- android.permission.BROADCAST_WAP_PUSH
- android.permission.BIND_CARRIER_SERVICES
- android.permission.BIND_CARRIER_MESSAGING_SERVICE
- android.permission.INTERACT_ACROSS_USERS
Defines Permissions:
- None
测试自定义url
Android允许app之间通过自定义URL方案交流,这些自定义的URL允许其他应用在提供自定义URL方案的应用中执行特定的行为。自定义的URI能从任何方案的前缀开始,他们一般定义动作在应用和参数中执行。
考虑如下设计的例子:sms://compose/to=your.boss@company.com&message=I%20QUIT!&sendImmediately=true
,当一个受害者在手机上点击这个链接时,有漏洞的SMS应用会发送包含恶意内容的短信。这可能造成:
- 钱财丢失
- 手机号码暴露
一旦一个URL方案被定义,多个app能注册所有可能的方案。对于每个应用,每个这种自定义URL方案必须遍历,执行的行为必须测试。
URL方案可以用于深度连接,这是一种通过连接启动原生移动应用的广泛而便捷的方法,他本身是没有风险。
然而,通过URL方案的数据必须验证。
静态分析
检查自定义URL方案是否被定义。在androidmanifest.xml
文件中的intentfilter
元素中。
<activity android:name=".MyUriActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="path" />
</intent-filter>
</activity>
这个例子中方案叫做myapp://
。这个category
允许URI用浏览器打开。
数据能通过新的方案传播,比如,下列URI:myapp://path/to/what/i/want?keyOne=valueOne&keyTwo=valueTwo
,通过以下代码恢复数据:
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String valueOne = uri.getQueryParameter("keyOne");
String valueTwo = uri.getQueryParameter("keyTwo");
}
动态分析
通过Drozer模块scanner.activity.browsable
遍历app中的URL方案:
dz> run scanner.activity.browsable -a com.google.android.apps.messaging
Package: com.google.android.apps.messaging
Invocable URIs:
sms://
mms://
Classes:
com.google.android.apps.messaging.ui.conversation.LaunchConversationActivity
可以使用Drozer模块中app.activity.start
调用自定义URL模型
dz> run app.activity.start --action android.intent.action.VIEW --data-uri "sms://0123456789"
调用自定义方案(myapp://someaction/?var0=string&var1=string
)模块可能被用来发送数据给app:
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String valueOne = uri.getQueryParameter("var0");
String valueTwo = uri.getQueryParameter("var1");
}
测试通过IPC暴露
在实现移动应用程序的过程中,开发人员可以应用传统的IPC技术(例如使用共享文件或网络套接字)。应该使用移动应用程序平台提供的IPC系统功能,因为它比传统技术要成熟得多。使用没有安全性的IPC机制可能会导致应用程序泄漏或暴露敏感数据。
下面是一个可能公开敏感数据的Android IPC机制列表:
静态分析
所有的组件必须在AndroidManifest.xml
中声明,只有Broadcast receivers可以动态创建。
暴露的组件可以被其他应用访问,暴露组件有两种方式:
- 导出标签设置
android:exported="ture"
- 定义在组件中定义
<intent-filter>
就会默认导出标签为true。
如果要限制其他应用的访问,在组件中声明了android:permission
,设置合适的android:protectionLever
。如果在service中声明了android:permission
,其他app想要访问必须在manifest文件中声明一个相对应的<user-permission>
。
一旦识别了一个IPC列表,查看源代码,检查当机制使用时,敏感数据是否泄漏。
下面是两个例子:
Activity
检查AndroidManifest
检查源代码
通过检查PWList.java
acitivty,可以看到它提供了列举关键字、添加、删除选项等。如果直接调用,就可以绕过LoginActivity。更多可以在动态分析中找到。
Services
检查Manifest文件
在"Sieve"app中,有两个导出的service:
<service android:exported="true" android:name=".AuthService" android:process=":remote"/>
<service android:exported="true" android:name=".CryptoService" android:process=":remote"/>
检查源代码
搜索源代码的字符串,比如sendBroadcast
,sendOrderedBroadcast
, 和 sendStickyBroadcast
. 确保应用没有发送任何敏感数据。
如果一个intent只在应用内部广播和接收,应该使用LocalBroadcastManager
限制其他应用接收广播的消息。这减少了信息泄露的风险。
为了更好的理解接受者要做什么,我们必须深入静态分析类 android.content.BroadcastReceiver
和 Context.registerReceiver
(动态创建接受者) 的使用。
下面提取目标的源代码,显示了广播接收者触发包括用户解密口令的SMS的转发。
public class MyBroadCastReceiver extends BroadcastReceiver {
String usernameBase64ByteString;
public static final String MYPREFS = "mySharedPreferences";
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String phn = intent.getStringExtra("phonenumber");
String newpass = intent.getStringExtra("newpass");
if (phn != null) {
try {
SharedPreferences settings = context.getSharedPreferences(MYPREFS, Context.MODE_WORLD_READABLE);
final String username = settings.getString("EncryptedUsername", null);
byte[] usernameBase64Byte = Base64.decode(username, Base64.DEFAULT);
usernameBase64ByteString = new String(usernameBase64Byte, "UTF-8");
final String password = settings.getString("superSecurePassword", null);
CryptoClass crypt = new CryptoClass();
String decryptedPassword = crypt.aesDeccryptedString(password);
String textPhoneno = phn.toString();
String textMessage = "Updated Password from: "+decryptedPassword+" to: "+newpass;
SmsManager smsManager = SmsManager.getDefault();
System.out.println("For the changepassword - phonenumber: "+textPhoneno+" password is: "+textMessage);
smsManager.sendTextMessage(textPhoneno, null, textMessage, null, null);
广播接受者应该使用android:permission
属性,否则,其他应用能调用他们。可以使用Context.sendBroadcast(intent, receiverPermission);
明确一个接收者必须拥有什么权限来获取广播。
也可以设置一个显式应用包名限制处理这个intent的组件。如果属性默认,所有应用的组件都可以访问。如果不空,intent可以匹配给定应用包的组件。
动态分析
用Drozer遍历组件,模块app.package.attacksurface
:
dz> run app.package.attacksurface com.mwr.example.sieve
Attack Surface:
3 activities exported
0 broadcast receivers exported
2 content providers exported
2 services exported
is debuggable
content providers
Sieve应用包含一个由漏洞的content provider,列举暴露的内容提供器:
dz> run app.provider.finduri com.mwr.example.sieve
Scanning com.mwr.example.sieve...
content://com.mwr.example.sieve.DBContentProvider/
content://com.mwr.example.sieve.FileBackupProvider/
content://com.mwr.example.sieve.DBContentProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords/
content://com.mwr.example.sieve.DBContentProvider/Keys/
content://com.mwr.example.sieve.FileBackupProvider
content://com.mwr.example.sieve.DBContentProvider/Passwords
content://com.mwr.example.sieve.DBContentProvider/Keys
"password"和"key"命名的内容提供器是信息泄露最可疑的。
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys
Permission Denial: reading com.mwr.example.sieve.DBContentProvider uri content://com.mwr.example.sieve.DBContentProvider/Keys from pid=4268, uid=10054 requires com.mwr.example.sieve.READ_KEYS, or grantUriPermission()
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password | pin |
| SuperPassword1234 | 1234 |
这个内容提供器不需要访问权限:
dz> run app.provider.update content://com.mwr.example.sieve.DBContentProvider/Keys/ --selection "pin=1234" --string Password "newpassword"
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Keys/
| Password | pin |
| newpassword | 1234 |
Actiity
用模块app.activity.info
列举暴露的activity。明确目标包用-a
或者忽略这个选项以设备上的所有app为目标
dz> run app.activity.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.FileSelectActivity
Permission: null
com.mwr.example.sieve.MainLoginActivity
Permission: null
com.mwr.example.sieve.PWList
Permission: null
遍历Sieve的所有活动,com.mwr.example.sieve.PWList
不需要任何权限导出。可以使用模块 app.activity.start
启动这个activity。
dz> run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList
因为这个活动可以直接调用,所以用登录保护的管理就会被绕过。
service
遍历模块app.service.info
:
dz> run app.service.info -a com.mwr.example.sieve
Package: com.mwr.example.sieve
com.mwr.example.sieve.AuthService
Permission: null
com.mwr.example.sieve.CryptoService
Permission: null
先使用静态分析来识别需要的输入,然后与service通信。
用app.service.send
与service通信,更改目标应用中存储的密码:
dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj
Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService:
what: 4
arg1: 42
arg2: 0
Empty
broadcast receivers
遍历模块 app.broadcast.info
,目标用-a
参数:
dz> run app.broadcast.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
com.android.insecurebankv2.MyBroadCastReceiver
Permission: null
用Drozer模块app.broadcast.send
,我们可以定制一个intent来触发广播并发送密码给手机号码
dz> run app.broadcast.send --action theBroadcast --extra string phonenumber 07123456789 --extra string newpass 12345
嗅探intent
如果广播intents没有设置需要的权限或者明确目的包,这个intent会被设备上的所有device监视。
注:AcitivityManageService,简称AMS的intent分发机制,只把intent发送给匹配的intent-filter
登记一个broadcast receiver嗅探itent,使用Drozer模块 app.broadcast.sniff
,用参数--action
表示监视的动作:
dz> run app.broadcast.sniff --action theBroadcast
[*] Broadcast receiver registered to sniff matching intents
[*] Output is updated once a second. Press Control+C to exit.
Action: theBroadcast
Raw: Intent { act=theBroadcast flg=0x10 (has extras) }
Extra: phonenumber=07123456789 (java.lang.String)
Extra: newpass=12345 (java.lang.String)
测试WebView中的JavaScript执行
overview
JavaScript可以通过反射、存储或者基于DOM 的xss注入到web应用中。移动应用在沙箱环境中执行,并且在本地实现时不会有这种漏洞。尽管如此,webview可能允许网页浏览作为原生应用的一部分。每个应有有自己的Webview缓存,它不会与原生浏览器或者其他应用共享。在Android上,webview使用webkit渲染引擎来显示网页,但是页面被精简到最小的功能。例如,页面没有地址栏。如果WebView的实现太过于宽松,并且允许使用JavaScript,那么他就可以用来攻击应用程序并访问它的数据。
静态分析
检查源代码中的WebView类使用和执行。创建和使用一个WebView,必须创建一个WebView实例。
WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("https://www.owasp.org/");
JavaScript必须显示地确认。查找方法 setJavaScriptEnabled
检查JavaScript的激活。
webview.getSettings().setJavaScriptEnabled(true);
这使得WebView解释JavaScript。还有在有必要的时候用它来减少app的攻击面。如果JavaScript是必须的,你应该确定:
- 断电的通信始终依赖于HTTPS(或者其他允许加密的协议)来保护HTML和JavaScript在传输过程中不被篡改。
- JavaScript和HTML在本地加载时,是来自app数据目录或者可信web服务器
当app关闭时,去除所有JavaScript资源代码和本地存储数据,用clearCache()
清除WebView的cache。
在Android 4.4以下版本的设备上运行时,会使用一个由一些安全问题的WebKit。作为一种变通方法,该应用必须确认,如果应用在这些设备上运行,WebView对象只显示可信内容。
动态检测
动态检测依赖于操作环节,这是一些向WebView中注入JavaScript的方法:
- 在端点有存储xxs漏洞,当用户导航到易受攻击的功能时,该漏洞将被发送到移动应用的WebView。
- 攻击者获得中间人位置,通过注入JavaScript篡改应答。
- 恶意应用篡改webView加载的本地文件
要处理这些中间向量,检查:
- 端点提供的所有功能应该避免存储xss
- 只有在数据目录的文件能被WebView呈现
- HTTP必须根据最佳实践实现HTTPS通信,以避免中间人攻击。这以为着:
- 所有通信通过TLS加密
- 正确检测所有证书(参加测试用例“测试端点识别验证”)
- 证书应该被固定(参见“测试定制证书存储和SSL固定”)
测试WebView协议处理
overview
一些默认URL方案 可用,他们可以在WebView中触发,通过以下例子:
- http(s)://
- file://
- tel://
WebView能从一个端点加载远程内容,但是他们也能从app数据目录和外部存储加载。如果本地内容加载,用户不应该影响文件名或者用来加载的路径,用户应该不能编辑加载文件。
静态分析
检查使用WebView的源代码,WebView控制资源访问设置如下:
-
setAllowContentAccess
:允许WebView从安装在系统中的content provider加载内容,默认启用。 -
setAllowFileAccess
:在WebView中启用或禁止文件访问。文件访问默认开启,注意这知识启用和禁止访问文件系统。asset和资源不熟应用,可以通过file:///android_asset
和file:///android_res
访问。 -
setAllowFileAccessFromFileURLs
:是否允许JavaScript在文件模式URL的上下文中运行,以访问文件模式URLs中的内容。API 15 及以下默认true,API16及以上 默认false。 -
setAllowUniversalAccessFromFileURLs
:是否允许JavaScript在文件模式URL的上下文中运行,以访问来自任何来源的内容。API 15 及以下默认true,API16及以上 默认false。
如果以上一个或多个方法是启用的,应该确定这些方法是否对正常的工作有必要。
如果一个WebView实例被识别,查出本地文件是否被方法loadURL()
加载。
WebView = new WebView(this);
webView.loadUrl("file:///android_asset/filename.html");
HTML文件加载的地址必须被验证。如果文件从外部存储中加载,比如,文件可以被所有人读和写。这是一种不好的做法,文件应该放在app的assets目录中。
webview.loadUrl("file:///" +
Environment.getExternalStorageDirectory().getPath() +
"filename.html");
在loadURL中指定的URL应该检查可以被操纵的动态参数;他们的操纵可能导致本地文件被包含。
如果适用的话,可以使用下面的代码片段和最佳实践来禁用协议处理程序:
// 如果攻击者能向WebView注入脚本,他们可以访问本地资源。这可以通过禁用本地文件系统访问限制这种攻击,默认是启用的。
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setAllowFileAccessFromFileURLs(false);
webView.getSettings().setAllowUniversalAccessFromFileURLs(false);
webView.getSettings().setAllowContentAccess(false);
动态检测
为了识别协议处理程序的用法,在使用该应用程序时,寻找触发电话调用的方法,以及从文件系统访问文件的方法。
检查java类是否通过WebView暴露
overview
Android为JavaScript提供一种方法addJavascriptInterface
. 可以在WebView中执行调用app原生功能。
这个方法允许向WebView暴露Java Objects。当你在app中使用这种方法时,可以调用app原生方法。
在Android4.2(API 17)之前,在执行addJavascriptInterface
:时发现一个漏洞 :当恶意JavaScript脚本注射到WebView中,导致远程代码执行。
这个漏洞在4.2版本中修复,访问Java类方法的授权改变。当使用addJavascriptInterface
,添加@JavascriptInterface
注释,java类方法只能被JavaScript访问。之前的版本,默认所有类方法都能被访问。
静态分析
检查方法是否被使用,怎么使用,攻击者能否注入恶意JavaScript
下面的例子展示addJavascriptInterface
如何用来连接java类和WebView中的JavaScript
WebView webview = new WebView(this);
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);
MSTG_ENV_008_JS_Interface jsInterface = new MSTG_ENV_008_JS_Interface(this);
myWebView.addJavascriptInterface(jsInterface, "Android");
myWebView.loadURL("http://example.com/file.html");
setContentView(myWebView);
API 17以上,使用注释JavascriptInterface
显示允许JavaScript访问Java方法。
public class MSTG_ENV_008_JS_Interface {
Context mContext;
/** Instantiate the interface and set the context */
MSTG_ENV_005_JS_Interface(Context c) {
mContext = c;
}
@JavascriptInterface
public String returnString () {
return "Secret String";
}
/** Show a toast from the web page */
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
如果注释 @JavascriptInterface
在方法中定义,方法就能被JavaScript调用。
调用returnString
方法获取返回值,返回值存储在参数 result
var result = window.Android.returnString();
通过访问JavaScript代码,存储型XXS或者中间人攻击,攻击者直接调用暴露的Java方法。
动态分析
写一个JavaScript payload ,注入到app文件中。注入可以通过MITM攻击或者直接修改外部存储中的文件。整个过程可以通过Drozer和weasel完成。
MWR的博客中 有完整的攻击描述。
测试Fragment注入
overview
SDK为开发者提供了一种展示Preference activity的方法,允许开发者继承和改编抽象类。
抽象类分析一个intent额外的数据域,特别是,PreferenceActivity.EXTRA_SHOW_FRAGMENT(:android:show_fragment)
和PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS(:android:show_fragment_arguments)
第一个域需要包含一个Fragment
类名,第二个需要包含传递给Fragment的输入的bundle。
因为PreferenceActivity
使用反射加载fragment,所以一个任意的类可能会被加载到包或Android SDK中。 加载的类在导出这个activity的应用的上下文中执行。
有了这个漏洞攻击者可以在目的应用中调用fragment,或者执行其他类的构造函数中的代码。任何在intent中传递的、不扩展Fragment的类会造成java.lang.CastException 。但是在异常抛出之前,空的构造函数会被执行,允许在类构造函数中出现的代码运行 。
为了限制这个漏洞,isValidFragment
被添加到Android 4.4 KitKat(API Level 19)。允许开发者重写这些方法并且定义可能会被用到的fragment
在Android 4.4 KitKat(API Level 19)默认执行返回true,抛出异常。
静态分析
步骤:
- 找到包
minSDKVersion
确定类行为 - 找到集成
PreferenceActivity
的暴露组件 -
isValidFragment
有没有被重写
继承的例子:
public class MyPreferences extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
重写isValidFragment
方法,只允许MyPreferenceFragment
的加载:
@Override
protected boolean isValidFragment(String fragmentName)
{
return "com.fullpackage.MyPreferenceFragment".equals(fragmentName);
}
有漏洞的app和利用
MainActivity.class
public class MainActivity extends PreferenceActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
MyFragment.class
public class MyFragment extends Fragment {
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragmentLayout, null);
WebView myWebView = (WebView) wv.findViewById(R.id.webview);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.loadUrl(this.getActivity().getIntent().getDataString());
return v;
}
}
利用这个漏洞,创建一个应用:
Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.setClassName("pt.claudio.insecurefragment","pt.claudio.insecurefragment.MainActivity");
i.putExtra(":android:show_fragment","pt.claudio.insecurefragment.MyFragment");
Intent intent = i.setData(Uri.parse("https://security.claudio.pt"));
startActivity(i);
Vulnerable App
和 Exploit PoC App
下载
测试类持久化
overview
在Android上持久化一个对象有几种方法:
对象序列化
对象及其数据可以表示为字节序列。这是通过对象串行化在Java中完成的。串行化并不是固有的安全。它只是在.ser文件中本地存储数据的二进制格式(或表示)。只要密钥是安全存储的,就可以对hmac-序列化数据进行加密和签名。反序列化一个对象需要一个与用来序列化对象的类相同的类。在类被更改之后,ObjectInputStream无法从较老的.ser文件中创建对象。下面的例子展示了如何通过实现Serializable接口来创建可序列化的类。
import java.io.Serializable;
public class Person implements Serializable {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
//..
//getters, setters, etc
//..
}
现在可以通过 ObjectInputStream
/ObjectOutputStream
在另一个类中读写了。
JSON
有几种方法可以将对象的内容序列化为JSON。Android附带了JSONObject和JSONArray类。也可以使用包括GSON或Jackson在内的各种各样的库。库之间的主要区别在于,它们是否使用反射来组合对象,是否支持注释,以及它们使用的内存数量。请注意,几乎所有的JSON表示都是基于字符串的,因此是不可变的。这意味着任何存储在JSON中的秘密都将更难从内存中删除。JSON本身可以存储在任何地方,例如,一个(NoSQL)数据库或一个文件。您只需要确保包含机密的任何JSON都得到了适当的保护(例如,加密/hmaced)。有关更多细节,请参阅数据存储章节。下面是一个简单的例子(来自GSON用户指南),使用GSON来编写和读取JSON。在这个例子中,bagof原语的一个实例的内容被序列化成JSON:
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
orm
有一些库提供了直接在数据库中存储对象内容的功能,然后用数据库内容实例化对象。这被称为对象关系映射(ORM)。使用SQLite数据库的库包括 :
- OrmLite,
- SugarORM,
- GreenDAO and
- ActiveAndroid.
Realm 另一方面,领域使用自己的数据库来存储类的内容。ORM所能提供的保护数量主要取决于数据库是否被加密。有关更多细节,请参阅数据存储章节。Realm网站包含了一个很好的 example of ORM Lite.
Parcelable
可分配的是一个类的接口,它的实例可以被写入并从包裹中恢复。 包裹通常被用来包装一个类作为一个包的一部分。 下面是一个实现可分配的Android开发者文档示例:
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
静态分析
如果对象持久性被用于在设备上存储敏感信息,首先要确保信息是加密的,并且是signe/hmaced。有关更多细节,请参阅数据存储和加密管理的章节。接下来,确保只有在用户经过身份验证之后才能获得解密和验证密钥。安全检查应该在正确的位置进行,就像在最佳实践中所定义的那样。
有一些通用的补救步骤,你可以一直这样做:
- 确保敏感数据已被加密,并在序列化/持久性之后签署。在使用数据之前评估签名或HMAC。有关密码学的章节,请参阅相关章节。
- 确保步骤1中使用的键不能很容易地提取。用户和/或应用程序实例应该被正确地验证/授权以获得密钥。有关更多细节,请参阅数据存储章节。
- 确保在被积极使用之前,要仔细验证反序列化对象中的数据(例如,不使用业务/应用程序逻辑)。
对于关注可用性的高风险应用程序,我们建议只有当序列化的类是稳定的时才使用Serializable。 其次,我们建议不要使用基于反射的持久性,因为:
- 攻击者可以通过基于字符串的参数找到方法的签名
- 攻击者可能能够操纵基于反射的步骤来执行业务逻辑。
Object Serialization
寻找字符串:
import java.io.Serializable
implements Serializable
JSON
如果您需要对抗内存转储,请确保非常敏感的信息不会以JSON格式存储,因为您不能保证使用标准库来防止反内存转储技术。 JSONObject
您可以在相应的库中检查下列关键字:
import org.json.JSONObject;
import org.json.JSONArray;
GSON
查找关键字:
import com.google.gson
import com.google.gson.annotations
import com.google.gson.reflect
import com.google.gson.stream
new Gson();
- Annotations such as
@Expose
,@JsonAdapter
,@SerializedName
,@Since
, and@Until
Jackson
查找关键字:
import com.fasterxml.jackson.core
-
import org.codehaus.jackson
for the older version.
ORM
当您使用ORM库时,请确保数据存储在一个加密的数据库中,并且在存储之前,类表示是单独加密的。 有关更多细节,请参阅数据存储和加密管理的章节。 OrmLite 可以在相应的库中检查下列关键字:
import com.j256.*
import com.j256.dao
import com.j256.db
import com.j256.stmt
import com.j256.table\
请确保日志是禁用的。
SugarORM
关键字:
import com.github.satyan
extends SugarRecord<Type>
- In the AndroidManifest, there will be
meta-data
entries with values such asDATABASE
,VERSION
,QUERY_LOG
andDOMAIN_PACKAGE_NAME
.
确保 QUERY_LOG
禁用
GreenDAO
关键字:
import org.greenrobot.greendao.annotation.Convert
import org.greenrobot.greendao.annotation.Entity
import org.greenrobot.greendao.annotation.Generated
import org.greenrobot.greendao.annotation.Id
import org.greenrobot.greendao.annotation.Index
import org.greenrobot.greendao.annotation.NotNull
import org.greenrobot.greendao.annotation.*
import org.greenrobot.greendao.database.Database
import org.greenrobot.greendao.query.Query
ActiveAndroid
关键字:
ActiveAndroid.initialize(<contextReference>);
import com.activeandroid.Configuration
import com.activeandroid.query.*
Realm
关键字:
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
Parcelable
当敏感信息通过包含一个可分配的包的包中存储时,请确保采取适当的安全措施。使用显式的intent,并在使用应用程序级IPC时验证适当的附加安全控制(例如,签名验证、int权限、加密)。
动态分析
有几种方法可以执行动态分析:
对于实际的持久性:使用数据存储章节中描述的技术。 对于基于反射的方法:使用xposed来hook反序列化方法,或者向序列化的对象添加不可处理的信息,以查看它们是如何处理的(例如,应用程序崩溃还是通过丰富对象可以提取额外的信息)。