首页
社区
课程
招聘
[原创]android app漏洞入门学习小记-自定义权限和content provider
发表于: 2021-7-1 18:46 16288

[原创]android app漏洞入门学习小记-自定义权限和content provider

2021-7-1 18:46
16288

我们称为服务程序

调用我们的成为第三方程序

android自定义权限是第三方程序,调用服务程序组件的时候,服务程序,赋予第三方程序的权限。

name : 权限名称,第三方,写明声明的这个权限

description : 权限描述

permissionGroup : 指定权限属于的权限组,别的程序申请的时候需要写的

protectionLevel : 权限保护级别

Normal

这是最低风险的权限,如果应用声明了此权限,也不会提示安装应用的用户授权(例如,如果声明了定位权限,则应用到定位功能时,会明确提示用户,是否授予定位权限,但是protectionLevel为normal的不会明确提示,直接默认授予),系统直接默认该应用有此权限;

dangerous

这种级别的权限风险更高,拥有此权限可能会访问用户私人数据或者控制设备,给用户带来负面影响,这种类型的权限一般不会默认授权。

signature

这种权限级别,只有当发请求的应用和接收此请求的应用使用同一签名文件,并且声明了该权限才会授权,并且是默认授权,不会提示用户授权

signatureOrSystem

这种权限级别是系统授权的系统应用或者相同签名的应用,一般避免使用该级别,因为 signature 已经能满足大部分需求。

android:permission:统一提供程序范围读取/写入权限。
android:readPermission:提供程序范围读取权限。
android:writePermission:提供程序范围写入权限。

而权限,只有四个值,或者不写权限

Normal 和不写权限

没有声明权限,第三方程序,也可以不写,但是如果服务程序写了权限,第三方程序,必须写,否则为报错,使用这个组件权限不足的错误。

dangerous

这个权限没试过,估计权限很高,很危险,但是,我估计,基本不会遇到这种权限的app

signature 和 signatureOrSystem

signature:只有签名相同的程序才能使用,估计除非签名文件被盗,否则,应该都是自家在用,跟不开放没啥区别,

signatureOrSystem:比签名多了一个使用者,就是拥有系统签名的app,可能可以靠某个系统app间接调用过去,我们去测试的app,估计都是第三发发行的应用,我估计应该没人用这个。

总结:

只要export = true,外部程序直接可以访问,寻找漏洞。否则无法攻击

自定义的权限Normal,攻击程序就能进行攻击,否则无法攻击。

这个,其实没那么重要,因为,我们可以分析目标app,进行目标app的权限申请,如果有签名验证的话,是无法申请的,只能通过别的方式调用,也没办法处理。这个可能写的不太细致。有兴趣的可以自己查一查。

内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它方式。内容提供者继承于ContentProvider 基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。

首先我们需要知道三个类

假如我们现在有个应用A 提供了数据 ,应用B要操作应用A的数据,那么

这就是通信的大致流程,在了解更加详细的流程之前,我们还需要知道几个概念

ContentProvider中的URI是有固定格式的,例如:
在这里插入图片描述
Authority:授权信息,以区别不同的ContentProvider
path:表名,以区分ContentProvider中不同的数据表
id:id号,用于区分表中的不同数据

ContentResolver通过Authority寻找ContentProvider,

UriMatch主要为了区配URI,比如应用A提供了数据,但是并不是说有的应用都可以操作这些数据,只有提供了满足应用A的URI,才能访问A的数据,而UriMatch就是做这个区配工作的,他有如下方法

开始笔记的时候,我才发现,虽然我了解ContentProvider的部分原理,但是真的没有完整的用过她,更没有在某一个app中,实现并使用过她,甚至demo都是看博客,说说干嘛,而且我唯一分析的大型关于ContentProvider的app竟然就只有virturalApp,但是他的使用。废话到此为止。

ContentProvider,网上的大致都是打开数据库,进行操作数据的接口,或者FileProvider文件共享,给我整懵逼了,我仔细总结了一下,并亲自写了demo,总结如下:

上面的函数会直接的调用对应URI的provider 的方法,

openInputStream,会调用openAssetFile
call 会调用call
还会把参数传递过去,至于实现什么功能,就是你自己的事情了

openAssetFile ,调用openFile,我们可以直接重写openFile,也可以,重写openAssetFile(我开始的时候重写的openAssetFile,有点傻,后来发现要解决的问题有点多,不太会用,发现写openFile比较方便)

代码里的三个权限,设置,才是设置这个provider提供的文件读写功能,返回的文件对象,具有什么样的权限

grantUriPermissions = "true"表示,表示允许权限传递,就是拥有访问权限的组件,可以把访问权限传递给别的组件,为false,权限无法传递,只能自己使用。

被攻击的provider,无法通过外部访问

间接传递权限的activity

android:exported属性

从API Level17及以后,Content Provider默认是私有的,除非显式指定,android:exported="true"。

provider设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。如果为true,外部应用,就可以调用,访问使用内部提供的功能,根据实现的功能就行测试攻击,而不只是sqlite和文件访问这些功能而已。当然,可能效果没有那么好。(配合使用更佳,后面会介绍例子)

跨进程调用provider,如果进程死了,会唤醒目标进程,并且,在这个调用进程主动被杀以后,目标进程没有被杀。

作为第一手攻击点,启动进程,是可以的。不过,估计和广播哪个一样,没有后台弹出界面权限。助攻也可以

provider本进程启动的时候,会在Application oncreate方法调用前初始化完成,调用完oncreate

这里,加固的时候,如果在oncrete函数里调用provider,要在之前完成provider手工安装(这里记不太清了,但是provider在加固的时候有坑)可以去分析他的启动时机。

provider本身,没有权限控制,sql,文件读写功能,只提供的双端交互,后续以及产生的各种漏洞,都是开发人员在provider基础上进行功能扩展,代码检测不严格,出现的问题。

如果你看到这里觉得前面写的没有问题的话,那么。。。。。。。。。。

好吧,先说一下,出现的问题吧

grantUriPermissions = true 无法生效

先看官方描述

(https://developer.android.google.cn/guide/topics/manifest/provider-element 不知道这个文档是不是老了,或者有什么问题)

这里也说了,provider export = false ,grantUriPermissions= true,授权给别的应用,是可以访问的,能绕过 export = false 。

但是实际情况是,在android:grantUriPermissions=true ,export = false的情况下,我无法访问provider,并且报错,就是export=false

我也设置了权限传递

并且查了不少的资料,开发的一些demo,国内外的provider漏洞利用,都说没有错,但是我就是不好使,我开始觉得,可能他就是有问题的,但也可能这是个低版本问题,毕竟我手里,只有android 8 和 9。

grantUriPermissions = true 无法生效的解决方案

可能是对自己测试结果的不自信吧,于是到网上找了别人写好的demo试了一试,额,还真过去了。
```
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uriForFile);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 200);

grantUriPermissions = true 无法生效的解决方案(二)

在无意中,我还发现第二种方案,不过可能有些鸡肋。

总结

手机类型不全,不清楚android7是否会没有这样的问题,系统源码方面么有深入挖掘,第一种方法需要应用本身有相机权限,第二种方法,应用本身需要给目标应用主动授权。这两种方法都可以在应用层实现,不知道有没有办法,hook java方法什么的,达到这两种方法的目的。

 
 
<permission
   android:name="com.sl.permission.aidl"
   android:description="@string/service_permission"
   android:permissionGroup="com.sl.permissions"
   android:protectionLevel="signature" />
<permission
   android:name="com.sl.permission.aidl"
   android:description="@string/service_permission"
   android:permissionGroup="com.sl.permissions"
   android:protectionLevel="signature" />
<provider
    android:authorities="com.example.student"
    android:name=".provider.MyContentProvider"
    android:grantUriPermissions="true"
 
    android:permission="com.sl.permission.aidl"
 
    android:exported="true"/>
<provider
    android:authorities="com.example.student"
    android:name=".provider.MyContentProvider"
    android:grantUriPermissions="true"
 
    android:permission="com.sl.permission.aidl"
 
    android:exported="true"/>
<uses-permission android:name="com.sl.permission.aidl" />
<uses-permission android:name="com.sl.permission.aidl" />
 
 
 
 
 
//远程调用contentResolver对应的方法
        switch (v.getId()) {
            case R.id.zeng:
                contentResolver.insert(URI, contentValues);
                break;
            case R.id.shan:
                contentResolver.delete(URI, null, null);
                break;
            case R.id.gai:
                contentResolver.update(URI, contentValues, null, null);
                break;
            case R.id.cha:
                contentResolver.query(URI, null, null, null, null);
                break;
            case R.id.openfile:
                try {
                    contentResolver.openInputStream(URI);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.call:
                Bundle bundle = new Bundle();
                contentResolver.call(URI,"ABC","DEF",bundle);
                break;
        }
//远程调用contentResolver对应的方法
        switch (v.getId()) {
            case R.id.zeng:
                contentResolver.insert(URI, contentValues);
                break;
            case R.id.shan:
                contentResolver.delete(URI, null, null);
                break;
            case R.id.gai:
                contentResolver.update(URI, contentValues, null, null);
                break;
            case R.id.cha:
                contentResolver.query(URI, null, null, null, null);
                break;
            case R.id.openfile:
                try {
                    contentResolver.openInputStream(URI);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.call:
                Bundle bundle = new Bundle();
                contentResolver.call(URI,"ABC","DEF",bundle);
                break;
        }
 
public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
    ParcelFileDescriptor fd = openFile(uri, mode);
    return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}
public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
    ParcelFileDescriptor fd = openFile(uri, mode);
    return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
        throws FileNotFoundException {
    Log.e("Rzx","provider openfile");
    Log.e("Rzx",uri.toString()+uri.getPath());
    //由于是应用内的不同进程,故这里把文件放到data/data/包名 目录下
    File testFile =new File(getContext().getCacheDir(),"test.html");
 
    // ParcelFileDescriptor.MODE_READ_ONLY:只可读
    // ParcelFileDescriptor.MODE_WRITE_ONLY:只可写
    // ParcelFileDescriptor.MODE_READ_WRITE:可读可写
    return ParcelFileDescriptor.open(testFile,
            ParcelFileDescriptor.MODE_READ_WRITE);
 
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
        throws FileNotFoundException {
    Log.e("Rzx","provider openfile");
    Log.e("Rzx",uri.toString()+uri.getPath());
    //由于是应用内的不同进程,故这里把文件放到data/data/包名 目录下
    File testFile =new File(getContext().getCacheDir(),"test.html");
 
    // ParcelFileDescriptor.MODE_READ_ONLY:只可读
    // ParcelFileDescriptor.MODE_WRITE_ONLY:只可写
    // ParcelFileDescriptor.MODE_READ_WRITE:可读可写
    return ParcelFileDescriptor.open(testFile,
            ParcelFileDescriptor.MODE_READ_WRITE);
 
}
 
<provider
    android:authorities="com.example.student"
    android:name=".provider.MyContentProvider"
    android:grantUriPermissions="true"
    android:exported="false"/>
<provider
    android:authorities="com.example.student"
    android:name=".provider.MyContentProvider"
    android:grantUriPermissions="true"
    android:exported="false"/>
public class grantUriPermissionsActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        setResult(-1, getIntent());
        finish();
 
    }
}
public class grantUriPermissionsActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        setResult(-1, getIntent());
        finish();
 
    }
}
Intent intent = new Intent();
intent.setData(Uri.parse("content://" + "com.example.student"+"/input"));
intent.setClassName("com.example.vulnerableapplication", "com.example.vulnerableapplication.provider.grantUriPermissionsActivity");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent,0);
 
 
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.e("rzx","onActivityResult");
    super.onActivityResult(requestCode, resultCode, data);
 
    try {
        String str = "content://" + "com.example.student"+"/input";
        InputStream is = getContentResolver().openInputStream(Uri.parse(str));
        byte[] buffer = new byte[1024];
        int byteCount;
        while ((byteCount = is.read(buffer)) != -1) {
            Log.e("Rzx",new String(buffer));
        }
        is.close();
 
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Intent intent = new Intent();
intent.setData(Uri.parse("content://" + "com.example.student"+"/input"));

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 2925
活跃值: (5510)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
2

格式可能有点问题,代码和文档地址https://gitee.com/ChicWalking/android-vulnerablea-analysis

最后于 2021-7-1 18:49 被Thehepta编辑 ,原因:
2021-7-1 18:47
0
雪    币: 147
活跃值: (510)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
感谢分享!
2022-11-18 10:09
0
游客
登录 | 注册 方可回帖
返回
// // 统计代码