博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
安卓应用安全指南 4.6.3 处理文件 高级话题
阅读量:6157 次
发布时间:2019-06-21

本文共 11989 字,大约阅读时间需要 39 分钟。

安卓应用安全指南 4.6.3 处理文件 高级话题

原书:

译者:

协议:

4.6.3.1 通过文件描述符的文件共享

有一种方法可以通过文件描述符共享文件,而不是让其他应用访问公共文件。 此方法可用在内容供应器和服务中。 对方的应用可以通过文件描述符读取/写入文件,这些文件描述符通过在内容供应器或服务中,打开私人文件来获得。

其他应用直接访问文件的共享方式,与文件描述符的共享方式的比较如下表 4.6-2。 优点是访问权限的变化,以及允许访问的应用范围。 特别是从安全角度来看,这是一个很大的优点,可以详细控制允许访问的应用。

表 4.6-2 应用内文件共享方式的比较

文件共享方式 验证或者访问权限设置 允许访问的应用范围
允许其他应用直接访问的文件共享 读、写、读写 给予所有应用同等访问权限
通过文件描述符的文件共享 读、写、仅添加、读写、读+添加 可以控制是否将权限授予应用,它们尝试独立和暂时访问内容供应器和服务。

在上述两种文件共享方法中,这是很常见的,因为向其他应用提供文件写入权限时,文件内容的完整性很难得到保证。 当多个应用并行写入时,可能会破坏文件内容的数据结构,导致应用无法正常工作。 因此,在与其他应用共享文件时,只允许只读权限。

以下是通过内容供应器的文件共享的实现示例,及其示例代码。

要点:

1) 源应用是内部应用,因此可以保存敏感信息。

2) 即使是由内部的内容供应器产生的结果,也要验证结果数据的安全性。

InhouseProvider.java

package org.jssec.android.file.inhouseprovider;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import org.jssec.android.shared.SigPerm;import org.jssec.android.shared.Utils;import android.content.ContentProvider;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.net.Uri;import android.os.ParcelFileDescriptor;public class InhouseProvider extends ContentProvider {
private static final String FILENAME = "sensitive.txt"; // In-house signature permission private static final String MY_PERMISSION = "org.jssec.android.file.inhouseprovider.MY_PERMISSION"; // In-house certificate hash value private static String sMyCertHash = null; private static String myCertHash(Context context) { if (sMyCertHash == null) { if (Utils.isDebuggable(context)) { // Certificate hash value of debug.keystore "androiddebugkey" sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; } else { // Certificate hash value of keystore "my company key" sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA"; } } return sMyCertHash; } @Override public boolean onCreate() { File dir = getContext().getFilesDir(); FileOutputStream fos = null; try { fos = new FileOutputStream(new File(dir, FILENAME)); // *** POINT 1 *** The source application is In house application, so sensitive information can be saved. fos.write(new String("Sensitive information").getBytes()); } catch (IOException e) { android.util.Log.e("InhouseProvider", "failed to read file"); } finally { try { fos.close(); } catch (IOException e) { android.util.Log.e("InhouseProvider", "failed to close file"); } } return true; } @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { // Verify that in-house-defined signature permission is defined by in-house application. if (!SigPerm.test(getContext(), MY_PERMISSION, myCertHash(getContext()))) { throw new SecurityException( "In-house-defined signature permission is not defined by in-house application."); } File dir = getContext().getFilesDir(); File file = new File(dir, FILENAME); // Always return read-only, since this is sample int modeBits = ParcelFileDescriptor.MODE_READ_ONLY; return ParcelFileDescriptor.open(file, modeBits); } @Override public String getType(Uri uri) { return ""; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; }}

InhouseUserActivity.java

package org.jssec.android.file.inhouseprovideruser;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import org.jssec.android.shared.PkgCert;import org.jssec.android.shared.SigPerm;import org.jssec.android.shared.Utils;import android.app.Activity;import android.content.Context;import android.content.pm.PackageManager;import android.content.pm.ProviderInfo;import android.net.Uri;import android.os.Bundle;import android.os.ParcelFileDescriptor;import android.view.View;import android.widget.TextView;public class InhouseUserActivity extends Activity {
// Content Provider information of destination (requested provider) private static final String AUTHORITY = "org.jssec.android.file.inhouseprovider"; // In-house signature permission private static final String MY_PERMISSION = "org.jssec.android.file.inhouseprovider.MY_PERMISSION"; // In-house certificate hash value private static String sMyCertHash = null; private static String myCertHash(Context context) { if (sMyCertHash == null) { if (Utils.isDebuggable(context)) { // Certificate hash value of debug.keystore "androiddebugkey" sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; } else { // Certificate hash value of keystore "my company key" sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA"; } } return sMyCertHash; } // Get package name of destination (requested) content provider. private static String providerPkgname(Context context, String authority) { String pkgname = null; PackageManager pm = context.getPackageManager(); ProviderInfo pi = pm.resolveContentProvider(authority, 0); if (pi != null) pkgname = pi.packageName; return pkgname; } public void onReadFileClick(View view) { logLine("[ReadFile]"); // Verify that in-house-defined signature permission is defined by in-house application. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) { logLine(" In-house-defined signature permission is not defined by in-house application."); return; } // Verify that the certificate of destination (requested) content provider application is in-house certificate. String pkgname = providerPkgname(this, AUTHORITY); if (!PkgCert.test(this, pkgname, myCertHash(this))) { logLine(" Destination (Requested) Content Provider is not in-house application."); return; } // Only the information which can be disclosed to in-house only content provider application, can be included in a request. ParcelFileDescriptor pfd = null; try { pfd = getContentResolver().openFileDescriptor( Uri.parse("content://" + AUTHORITY), "r"); } catch (FileNotFoundException e) { android.util.Log.e("InhouseUserActivity", "no file"); } if (pfd != null) { FileInputStream fis = new FileInputStream(pfd.getFileDescriptor()); if (fis != null) { try { byte[] buf = new byte[(int) fis.getChannel().size()]; fis.read(buf); // *** POINT 2 *** Handle received result data carefully and securely, // even though the data came from in-house applications. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." logLine(new String(buf)); } catch (IOException e) { android.util.Log.e("InhouseUserActivity", "failed to read file"); } finally { try { fis.close(); } catch (IOException e) { android.util.Log.e("ExternalFileActivity", "failed to close file"); } } } try { pfd.close(); } catch (IOException e) { android.util.Log.e("ExternalFileActivity", "failed to close file descriptor"); } } else { logLine(" null file descriptor"); } } private TextView mLogView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mLogView = (TextView) findViewById(R.id.logview); } private void logLine(String line) { mLogView.append(line); mLogView.append("¥n"); }}

4.6.3.2 为目录设置访问权限

以上所解释的安全考虑,重点在于文件。 还需要考虑作为文件容器的目录的安全性。 以下说明了目录的访问权限设置的安全性考虑。

在 Android 中,有一些方法可以在应用目录中获取/创建子目录。 主要如表 4.6-3。

表 4.6-3 在应用目录中获取/创建子目录的方法

规定其它应用的访问权限 删除文件
Context#getFilesDir() 不可能(只有执行权限)
Context#getCacheDir() 不可能(只有执行权限)
`Context#getDir(String name,

int MODE)| 可以对MODE设置如下:MODE_PRIVATEMODE_WORLD_READABLEMODE_WORLD_WRITEABLE` | 设置=>应用=>选择目标应用=>清除数据 |

这里特别需要注意的是Context#getDir()的访问权限设置。 正如文件创建中所说明的,从安全设计的角度来看,目录基本上也应该设置为私有的。 当信息共享取决于访问权限设置时,可能会产生意想不到的副作用,所以应采取其他方法用于信息共享。

MODE_WORLD_READABLE

这是一个标志,为所有应用提供目录的只读权限。 所以所有应用都可以获取目录中的文件列表,和单个文件属性信息。 由于秘密文件可能不会被放置在这些目录中,所以通常不能使用该标志 [15]。

MODE_WORLD_WRITEABLE

该标志位其他应用提供目录的写入权限。 所有应用都可以创建/移动/重命名/删除目录中的文件。 这些操作与文件本身的访问权限设置(读/写/执行)没有关系,所以需要注意的是,仅仅使用目录的写入权限就能执行操作。 此标志允许其他应用随意删除或替换文件,因此一般不能使用。

[15] MODE_WORLD_READABLEMODE_WORLD_WRITEABLE在 API 17 和更高版本以及 API 24 和更高版本中弃用,使用它们将触发安全异常。

对于表 4.6-3 “用户删除”,请参考“4.6.2.4 应用应考虑文件范围而设计(必需)”。

4.6.3.3 共享首选项和数据库文件的访问权限设置

共享首选项和数据库也由文件组成。 对于访问权限设置,对文件解释的内容也会在这里解释。 因此,共享首选项和数据库都应该创建为私有文件,与文件相同,内容共享应该由 Android 的应用间联动系统来实现。

下面将展示共享首选项的使用示例。 通过MODE_PRIVATE,共享首选项被设置为私有文件。

import android.content.SharedPreferences;import android.content.SharedPreferences.Editor;// Ommision of a passage// Get Shared Preference . (If there's no Shared Preference, it's to be created.)// Point:Basically, specify MODE_PRIVATE mode.SharedPreferences preference = getSharedPreferences(    PREFERENCE_FILE_NAME, MODE_PRIVATE);// Example of writing preference which value is charcter stringEditor editor = preference.edit();editor.putString("prep_key", "prep_value");// key:"prep_key", value:"prep_value"editor.commit();

对于数据库,请参考“4.5 使用 SQLite”。

4.6.3.4 Android 4.4(API 级别 19)及更高版本中,外部存储访问的规范更改

自 Android 4.4(API Level 19)以来,外部存储访问的规范已更改为以下内容。

(1)如果应用需要读/写其外部存储器上的特定目录,则不需要使用<uses-permission>声明WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE权限。(已更改)

(2)如果应用需要读取除外部存储器上特定目录以外的目录中的文件,则需要使用<uses-permission>声明READ_EXTERNAL_STORAGE权限。 (已更改)

(3)如果应用需要写入主外部存储器上的特定目录以外的目录中的文件,则需要使用<uses-permission>声明WRITE_EXTERNAL_STORAGE权限。

(4)应用无法写入次要外部存储器上的特定目录以外的目录中的文件。

在该规范中,根据 Android OS 的版本确定是否需要权限请求。 因此,如果应用支持包括 Android 4.3 和 4.4 在内的版本,则可能会导致应用需要用户不必要的许可。 因此,建议使用对应(1)的应用,如下所示使用<uses-permission>maxSdkVersion属性。

AndroidManifest.xml

4.6.3.5 Android 7.0(API Level 24)中的规范已修改,以便访问外部存储介质上的特定目录

在运行 Android 7.0(API Level 24)或更高版本的设备上,引入了一种称为作用域目录访问 API的新 API。 作用域目录访问允许应用在未经许可的情况下,访问外部存储器上的特定目录。 在作用域目录访问中,将Environment类中定义的目录作为参数传递给StorageVolume#createAccessIntent方法,来创建一个意图。 通过startActivityForResult发送此意图,可以启动一个对话框,在终端屏幕上请求访问权限,并且 - 如果用户授予权限 - 每个存储卷上的指定目录都可以访问。

表 4.6-4 可以通过作用域目录访问来访问的目录

DIRECTORY_MUSIC 通用音乐文件的标准位置
DIRECTORY_PODCASTS 播客的标准目录
DIRECTORY_RINGTONES ringtone 的标准目录
DIRECTORY_ALARMS 闹铃的标准目录
DIRECTORY_NOTIFICATIONS 提醒的标准目录
DIRECTORY_PICTURES 图片的标准目录
DIRECTORY_MOVIES 电影的标准目录
DIRECTORY_DOWNLOADS 用户下载的文件的标准目录
DIRECTORY_DCIM 相机产生的图片/视频文件的标准目录
DIRECTORY_DOCUMENTS 用户创建的文档的标准目录

如果应用要访问的位置位于上述目录之一,并且该应用正在 Android 7.0 或更高版本的设备上运行,则建议使用作用域目录访问,原因如下。 对于必须继续支持 Android 7.0 以下的设备的应用,请参阅“4.6.3.4 Android 4.4(API级别19)及更高版本中的外部存储访问的规范更改”中,列出的AndroidManifest中的示例代码。

  • 授予访问外部存储的权限时,应用可以访问预期目标以外的目录。
  • 使用存储器访问框架来要求用户选择可访问的目录,会导致繁琐的过程,用户必须在每次访问时配置一个选择器。 另外,当访问外部存储器的根目录时,整个存储器变成可访问的。

转载地址:http://ylsfa.baihongyu.com/

你可能感兴趣的文章
《.NET应用架构设计:原则、模式与实践》新书博客--试读-1.1.2 架构师的职责
查看>>
SQL Server大负载的生产环境下的性能优化:初识元数据优化
查看>>
我的友情链接
查看>>
MySql,Sql Server分区技术浅析
查看>>
hibernate之缓存
查看>>
KVM虚拟化笔记(七)------kvm虚拟机VNC的配置
查看>>
我的友情链接
查看>>
修改freebsd的主机名
查看>>
Form表单基础知识和常用兼容方法笔记(二)
查看>>
asp.net mvc+httpclient+asp.net mvc api入门篇
查看>>
不错的东西: AutoMapper
查看>>
亲测能用的mysqli类,挺好用的
查看>>
一文读懂RPA与BPM的区别和联系
查看>>
LeetCode----67. Add Binary(java)
查看>>
一次加载多个包
查看>>
中信国健临床通讯 2011年3月期 目 录
查看>>
人生如戏
查看>>
Sicily 1346. 金明的预算方案
查看>>
SpringMVC (六)注解式开发
查看>>
使用VirtualEnvWrapper隔离python项目的库依赖
查看>>