-
-
[原创]android app漏洞入门学习小记
-
2021-6-22 18:36 17672
-
前言
1 2 | 前面必须啰嗦两句,否则没有仪式感 还是得写两句,我写这篇的目的是,基础理论概念,以及android app漏洞挖掘相关知识点的记录 |
一、概述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Andorid app漏洞挖掘,从我学习android逆向的时候就已经有些了解了,网上也有不少的框架,但是当时却不知道怎么入门Android app漏洞挖掘,或者说没有这个概念。 我做很多事情,都需要知道为什么,漏洞那,关于漏洞有哪些为什么: 1. 出口入口,或者说,攻击点和可以造成的损失和危害 2. 有了攻击点,如何进行攻击,以及上一条的损失、危害的利用 网上有不少的android app漏洞挖掘,写了很多的四大组件安全问题,webview的安全问题,咋一看,让我感觉很简单,而且android主要是java代码,还容易反编译,很多都是api运用,权限控制这些,让我好像感觉不到,有多危险,甚至觉得,也许只有大公司,才回去做这个,有点吹毛求疵的感觉。 漏洞挖掘,有内核漏洞,web漏洞,应用程序漏洞,而android app漏洞挖掘,他是属于应用程序漏洞的,当然也有部分可以配合web漏洞来使用(我说的是app 漏洞,不是内核,也不是系统架构,binder那些)。 攻击点: 为什么网上一开口android 漏洞挖掘,直接就说四大组件,因为他就是攻击点,他就是入口,开始我也没想明白,为什么都是四大组件,万一再有的那,让我总觉得,把握不住,攻击一个程序,不管是什么程序,你想攻击她,都要寻找他的入口,我记得某个大神说过,大部分程序产生漏洞的原因是你没有办法对用户的输入进行有效的过滤。而四大组件,作为android上程序的首要入口点,担任app通讯的重要任务,作为攻击点,是最好的。(当然还有通过web界面调用apk的)。 危害: 1. 盗取用户信息 相比于电脑,手机可以说有更多我们的私人信息,都说这是个大数据的时代,数据哪里来的,这就仁者见仁,智者见智了。举几个例子,比如微信,抖音,我先通过分析他们的程序,找到他们的漏洞,然后,当你手机上安装了我的app的时候,我就可以攻击微信抖音,获取你的数据。当然,攻击银行软件这些,我就不想了,可能太难了,防护做的好,一条两条数据没什么,成千上万的手机如果都装了,那是多少数据,我不就拥有了和微信,抖音这些公司一样的数据分析体量了吗。 2. 木马病毒这些,保活,钓鱼。 这一类跟上面的没啥区别,可能就是作用不同,功能更加激进,强行锁屏,获取手机root,攻击手机这些,一般是分析的手机内部的rom框架的app的漏洞(可以参考一下android早年的锁屏漏洞,adb root,蓝牙,自动拨打电话发短信等等),如果手机安全等级高,基本不会有什么问题吧。保活了解,很多的找rom的空子钻,想办法不被杀死,在后台活着动不动弹广告之类的,如果检测到了你启动了某个要攻击的app,还可以钓鱼攻击。 攻击方式: 1 、攻击的目标是四大组件,基本方式肯定是Intent,通过直接或者间接的方式,调用你的组件,然后构造攻击参数,有意触发漏洞。 2 、简单的方式肯定是第三方apk的方式,创建Intent,攻击目标app,比如上传的某个应用商店,然后被你下载到手机上安装并运行,或者你在未知的网站上下载并运行的某个app 3 、webview 的方式,手机访问某个不安全的网站并且webview 检测不严格,也可以人为有意构造,启动activity 所以,你要学习android app漏洞,必须要从四大组件入手,要对四大组件非常的详细才行,不是能启动,发个数据,交互一下就可以的。 |
二、组件通讯--Intent
官方介绍
Intent 是一个消息传递对象,您可以用来从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件之间的通信,但其基本用例主要包括以下三个:
- 启动 Activity
Activity 表示应用中的一个屏幕。通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 用于描述要启动的 Activity,并携带任何必要的数据。
如果您希望在 Activity 完成后收到结果,请调用 startActivityForResult()。在 Activity 的 onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。如需了解详细信息,请参阅 Activity 指南。
- 启动服务
Service 是一个不使用用户界面而在后台执行操作的组件。使用 Android 5.0(API 级别 21)及更高版本,您可以启动包含 JobScheduler 的服务。如需了解有关 JobScheduler 的详细信息,请参阅其 API-reference documentation。
对于 Android 5.0(API 级别 21)之前的版本,您可以使用 Service 类的方法来启动服务。通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 用于描述要启动的服务,并携带任何必要的数据。
如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。如需了解详细信息,请参阅服务指南。
- 传递广播
广播是任何应用均可接收的消息。系统将针对系统事件(例如:系统启动或设备开始充电时)传递各种广播。通过将 Intent 传递给 sendBroadcast() 或 sendOrderedBroadcast(),您可以将广播传递给其他应用。
Intent 和 activity
Intent 启动方式:
显式 Intent:
通过提供目标应用的软件包名称或完全限定的组件类名来指定可处理 Intent 的应用。通常,您会在自己的应用中使用显式 Intent 来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,您可能会启动您应用内的新 Activity 以响应用户操作,或者启动服务以在后台下载文件。
```
直接创建Intent对象12Intent intent
=
new Intent (MainActivity.this,FirstActivity.
class
);
startActivity (intent);
使用setClass与setClassName方法
1234567891011Intent intent
=
new Intent ();
intent.setClass(this,FirstActivity.
class
);
startActivity (intent);
Intent intent
=
new Intent ();
intent.setClassName (this,
"com.itlong.mytwoactivtiy.FirstActivity"
);
startActivity (intent);
Intent intent
=
new Intent ();
intent.setClassName(
"com.itlong.mytwoactivtiy"
,
"com.itlong.mytwoactivtiy.FirstActivity"
);
startActivity (intent);
使用ComponentName类
由于该类的构造方法有多个,所以它又可以分为一下几种启动方式。1234567891011121314Intent intent
=
new Intent ();
ComponentName componentName
=
new ComponentName (MainActivity.this,
"com.itlong.mytwoactivtiy.FirstActivity"
);
intent.setComponent (componentName);
startActivity (intent);
Intent intent
=
new Intent ();
ComponentName componentName
=
new ComponentName (MainActivity.this,FirstActivity.
class
);
intent.setComponent (componentName);
startActivity (intent);
Intent intent
=
new Intent ();
ComponentName componentName
=
new ComponentName (
"com.itlong.mytwoactivtiy"
,
"com.itlong.mytwoactivtiy.FirstActivity"
);
intent.setComponent (componentName);
startActivity (intent);
Intent :
123setClass
setClassName
setClass
其实调用的也是ComponentName
public @NonNull Intent setClass(@NonNull Context packageContext, @NonNull Class<?> cls) {
12mComponent
=
new ComponentName(packageContext,
cls
);
return
this;
}
public @NonNull Intent setClassName(@NonNull Context packageContext,
123@NonNull
String className) {
mComponent
=
new ComponentName(packageContext, className);
return
this;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | public @NonNull Intent setClass(@NonNull Context packageContext, @NonNull Class<?> cls ) { mComponent = new ComponentName(packageContext, cls ); return this; } ``` + 隐式 Intent : 不会指定特定的组件,而是声明要执行的常规操作(动作 action),从而允许其他应用中的组件来处理。例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。 + 首先 在清单文件中申明action category data ``` <activity android:name = ".MainActivity" > <intent - filter > <action android:name = "android.intent.action.MAIN" / > <category android:name = "android.intent.category.LAUNCHER" / > < / intent - filter > <intent - filter > <action android:name = "com.myaction" / > <category android:name = "android.intent.category.DEFAULT" / > < / intent - filter > < / activity> <activity android:name = ".VulnActivity" > <! - - 第一种方式:自定义 - - > <intent - filter android:autoVerify = "true" > <action android:name = "android.intent.action.VIEW" / > <category android:name = "android.intent.category.DEFAULT" / > <category android:name = "android.intent.category.BROWSABLE" / > <data android:host = "AndroidHtml" android:scheme = "app" tools:ignore = "AppLinkUrlError" / > < / intent - filter > <! - - 第二种方式:以http协议 - - > <intent - filter android:autoVerify = "true" > <action android:name = "com.myaction" / > <category android:name = "android.intent.category.BROWSABLE" / > <data android:host = "test" android:path = "/detail" android:port = "8000" android:scheme = "openapp" / > < / intent - filter > < / activity> ``` + 然后,通过action category data 匹配进行调用(测试不那么具体,category貌似可以省略,data 如果存在,必须匹配,如果不匹配,是找不到,如果没有data,并且 action 匹配的情况下,通过category来区分具体的activity) ``` Intent intent = new Intent( "com.myaction" ); / / action 可以自定义 / / intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse( "openapp://test:8000/detail" )); / / 还有别的方式,这里不演示了 startActivity(intent); ``` |
Intent 构造分析:
显式 Intent :
通过 new Intent ,然后设置 ComponentName,来启动activity
```
Intent intent = new Intent();
intent.setClassName("com.example.webviewapplication", "com.example.webviewapplication.VulnActivity");
intent.setData(Uri.parse("openapp://test:8000/detail"));
startActivity(intent);log: START u0 {dat=openapp://test:8000/detail cmp=com.example.webviewapplication/.VulnActivity} from uid 10701
通过log打印出来
Log.i("ActivityTaskManager",intent.toString());
out = Intent { dat=openapp://test:8000/detail cmp=com.example.webviewapplication/.VulnActivity }Log.i("ActivityTaskManager",intent.toUri(Intent.URI_INTENT_SCHEME));
out = intent://test:8000/detail#Intent;scheme=openapp;component=com.example.webviewapplication/.VulnActivity;end然后我把源代码,替换成通过这个字符串,生成Intent
1234567Intent intent
=
null;
try
{
intent
=
Intent.parseUri(
"intent://test:8000/detail#Intent;scheme=openapp;component=com.example.webviewapplication/.VulnActivity;end"
,Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
e.printStackTrace();
}
startActivity(intent);
这种方式依然启动成功了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | ``` + 隐式 Intent : 通过 new Intent ,然后设置 action category 和data,来启动activity ``` Intent intent = new Intent( "com.myaction" , Uri.parse( "openapp://test:8000/detail" )); startActivity(intent); log: I / ActivityTaskManager: START u0 {act = com.myaction dat = openapp: / / test: 8000 / detail cmp = com.example.webviewapplication / .VulnActivity} from uid 10701 通过log打印出来 Log.i( "ActivityTaskManager" ,intent.toString()); out = Intent { act = com.myaction dat = openapp: / / test: 8000 / detail } Log.i( "ActivityTaskManager" ,intent.toUri(Intent.URI_INTENT_SCHEME)); out = intent: / / test: 8000 / detail #Intent;scheme=openapp;action=com.myaction;end 然后我把源代码,替换成通过这个字符串,生成Intent Intent intent = null; try { intent = Intent.parseUri( "intent://test:8000/detail#Intent;scheme=openapp;action=com.myaction;end" ,Intent.URI_INTENT_SCHEME); } catch (URISyntaxException e) { e.printStackTrace(); } startActivity(intent); 这种方式依然启动成功了 ``` + Intent 扩展 String参数 : ``` String str = "intent:#Intent;component=com.example.123456/.MainActivity;S.url=dwqdwqdwqfqf;end" ; Intent intent = null; try { intent = Intent.parseUri( str ,Intent.URI_INTENT_SCHEME); } catch (URISyntaxException e) { e.printStackTrace(); } Log.i( "ActivityTaskManager" ,intent.getStringExtra( "url" )); out : dwqdwqdwqfqf 可以看到Intent 后缀为 S.url = dwqdwqdwqfqf;end"; ;end估计是结束标志 S.可能是String的意思 然后就是url 和 dwqdwqdwqfqf ``` 通过这种方式我们了解了Intent 的构造,并且我们可以反向解析并打印的方式,显示出Intent的这种字符串组成结构 ``` Intent intent = new Intent( "com.myaction" , Uri.parse( "openapp://test:8000/detail" )); Log.i( "ActivityTaskManager" ,intent.toUri(Intent.URI_INTENT_SCHEME)); out: intent: / / test: 8000 / detail #Intent;scheme=openapp;action=com.myaction;end 输出的这个就是字符串形式的Intent ,我们可以把这个字符串通过上面的Intent.parseUri( str ,Intent.URI_INTENT_SCHEME);将他转变成一个Intent ``` |
activity 返回接收--startActivityForResult()
如何启动activity的时候,希望了解调用详情,接受来自被调用的activity返回的参数,需要使用startActivityForResult替换startActivity调动activity。当目标activity调用完成时,会调用当前activity的onActivityResult()。
目标activity返回调用结果,也必须通过Intent进行传递,当前activity在onActivityResult这个回调方法中,通过回传的Intent,获取调用结果。
Intent 启动 service
网上文档说,从Lollipop开始,service服务必须采用显示方式启动。
1 2 3 4 5 | / / 跨进程启动service Intent intent = new Intent(); intent.setClassName( "com.example.serviceapplication" , "com.example.serviceapplication.MyService" ); / / 包名,和server的类名 startService(intent); |
startService 这个,个人分析,不好作为攻击的第一入口点,因为,如果想要跨进程启动服务,必须建立在,目标进程已经启动的基础了(我们可以用activity攻击,先启动进程,然后攻击对应的服务)
注:service 写了个debug版本的demo ,默认是不导出的,要主动写 android:exported="true" ,才能被外部调用
Intent 和广播(BroadcastReceiver)
有序广播和无序广播(这一部分全抄的,我的代码,全部是无序的)
无序广播
无序广播即为我们平时经常使用的广播,其主要是通过public abstract void sendBroadcast (Intent intent)方法进行发送,并通过intent传递数据。代码示例如下:
123Intent nonOrderIntent
=
new Intent();
nonOrderIntent.setAction(ACTION);
sendBroadcast(nonOrderIntent);
无序广播会被注册了的相应的感兴趣(intent-filter匹配)接收,且顺序是无序的。如果发送广播时有相应的权限要求,BroadCastReceiver如果想要接收此广播,也需要有相应的权限。
无序广播的广播接收者不可以使用setResultData()方法和abortBroadcast()方法,如果使用了会报错。 但是可以使用getResultData()方法,虽然不报错,但是获取到的数据为null。但是在一种<u>特殊情况</u>下,getResultData()方法能取到无序广播传递的数据,下文会说明在什么情况下。
无序广播不可以被拦截,不可以被终止,不可以被修改,无序广播任何接收者只要匹配条件都可以接收到,无优先级问题。
如果想通过无序广播传递数据,则可以调用intent.putExtra方法传递, 接收者可通过intent.get...接收,不可通过getResultData接收。
有序广播
有序广播主要是通过
1public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int
initialCode, String initialData, Bundle initialExtras)
方法进行发送。代码示例如下:
123Intent intent
=
new Intent();
intent.setAction(ACTION);
sendOrderedBroadcast(intent, null, new Priority2BroadcastReceiver(), null, Activity.RESULT_OK,
"MainActivity发送了一个有序广播"
, null);
第一个intent:不多说指定intent,所有广播接收者的匹配规则
第二个receiverPermission:指定广播接收器的权限,一般自定义,不常用,可传null。
第三个resultReceiver:指定一个最终的广播接收器,相当于finally功能,不论优先级,最后都要接收一次广播,而这一次收到的广播为无序广播(可以在BroadcastReceiver中通过boolean orderedBroadcast = isOrderedBroadcast()方法验证),但是却可以通过getResultData等方法取得数据,这就是上面提到的特殊情况。
第四个scheduler:看英文没怎么看懂什么意思,一般传null。
第五个initialCode:指定一个code,一般传Activity.RESULT_OK。
第六个initialData:传一个字符串数据。对应的在BroadcastReceiver中通过String resultData = getResultData()取得数据;通过setResultData("优先级为3的setResultData的数据")修改数据,将数据传给下一个优先级较低的BroadcastReceiver;如果在优先级较高的BroadcastReceiver中没有使用setResultData修改数据,那么优先级较低的接收到的数据还是最原始的数据,即initialData的值。
第七个initialExtras:传一个Bundle对象,也就是可以传多种类型的数据。对应的在BroadcastReceiver中通过Bundle bundle = getResultExtras(false)取得Bundle对象,然后再通过bundle的各种get方法取得数据;通过setResultExtras()传入一个修改过的bundle,将该bundle对象传给下一个优先级较低的BroadcastReceiver;如果在优先级较高的BroadcastReceiver中没有使用setResultExtras修改数据,那么优先级较低的接收到的数据还是最原始的bundle对象,即initialExtras的值。
有序广播所对应的所有的receiver按照在intent-filter中设置的android:priority属性依次执行,android:priority表示优先级,值越大,其所对应的广播接收者,越先接收到广播。在android:priority相同的情况下,如果广播接收器是通过静态注册的,则接收到广播的顺序不确定,如果是动态注册的,先注册的将先收到广播。
有序广播可以被拦截,可以在较高优先级的接收器中通过abortBroadcast()拦截广播,这样就会导致较低优先级的接收器无法收到广播了,但是sendOrderedBroadcast第三个参数指定的BroadcastReceiver还是会收到广播的,而且能获得数据。
有序广播可以通过原始intent.putExtra这种方式传递数据给BroadcastReceiver,也能通过sendOrderedBroadcast方法的最后2个参数传递数据,但是通过第一种方式传递的数据无法中途修改,通过第二种方式传递的可以通过上面参数说明中的方式进行修改。
静态注册
12345678public
class
MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
/
/
if
(
"com.victim.messenger.IN_APP_MESSAGE"
.equals(intent.getAction())) {
/
/
Log.d(
"evil"
,
"From: "
+
intent.getStringExtra(
"from"
)
+
", text: "
+
intent.getStringExtra(
"text"
));
/
/
}
}
}
12345<receiver android:name
=
".BroadcastReceiver.MyBroadcastReceiver"
>
<intent
-
filter
>
<action android:name
=
"com.victim.messenger.IN_APP_MESSAGE"
/
>
<
/
intent
-
filter
>
<
/
receiver>
动态注册
```
// 注册接收端
private void initBroadcast() {123mBroadcastReceiver
=
new TestBroadcastReceiver();
IntentFilter
filter
=
new IntentFilter(action);
/
/
过滤
registerReceiver(mBroadcastReceiver,
filter
);
}
private class TestBroadcastReceiver extends BroadcastReceiver {
12345678/
/
接收广播
@Override
public void onReceive(Context context, Intent intent) {
Log.e(
"接受广播的状态-----"
,
"收到广播"
);
Log.e(
"收到的action-----"
, intent.getAction());
Log.e(
"收到的name-------"
, intent.getExtras().getString(
"name"
));
}
}
// 关闭广播
@Override
protected void onDestroy() {1234if
(mBroadcastReceiver !
=
null) {
unregisterReceiver(mBroadcastReceiver);
}
super
.onDestroy();
}
广播发送和Intent
广播发送也通过Intent,并且也有隐式和显示区别。
显示发送广播。
123456Intent intent
=
new Intent();
ComponentName componentName
=
new ComponentName(getApplicationContext(),
"com.example.vulnerableapplication.BroadcastReceiver.MyBroadcastReceiver"
);
intent.setComponent(componentName);
intent.putExtra(
"from"
,
"123456"
);
intent.putExtra(
"text"
,
"text"
);
sendBroadcast(intent);
通过制定类名直接发送。SDK 26,通过令静态注册的广播接收器失效 以限制后台过多应用启动,接受广播等情况。
解决方法:intent.addFlags(0x01000000 | 0x00400000);隐示发送广播
123456/
/
通过制定action 发送广播
Intent intent
=
new Intent();
intent.setAction(action);
intent.putExtra(
"name"
,
"zzw"
);
MainActivity.this.sendBroadcast(intent);
Log.e(
"发送广播的状态-----"
,
"发送成功"
);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | + 字符串生成Intent ``` / / Intent intent = new Intent(); / / intent.setAction(action); / / intent.putExtra( "name" , "zzw" ); / / Log.i( "ActivityTaskManager" ,intent.toUri(Intent.URI_INTENT_SCHEME)); / / intent: #Intent;action=com.zzw;S.name=zzw;end Intent intent = null; try { intent = Intent.parseUri( "intent:#Intent;action=com.zzw;S.name=zzw;end" ,Intent.URI_INTENT_SCHEME); } catch (URISyntaxException e) { e.printStackTrace(); } MainActivity.this.sendBroadcast(intent); Log.e( "发送广播的状态-----" , "发送成功" ); / / Intent intent = new Intent(); / / ComponentName componentName = new ComponentName(getApplicationContext(), "com.example.vulnerableapplication.BroadcastReceiver.MyBroadcastReceiver" ); / / intent.setComponent(componentName); / / intent.putExtra( "from" , "123456" ); / / intent.putExtra( "text" , "text" ); / / Log.i( "ActivityTaskManager" ,intent.toUri(Intent.URI_INTENT_SCHEME)); / / intent: #Intent;component=com.example.vulnerableapplication/.BroadcastReceiver.MyBroadcastReceiver;S.from=123456;S.text=text;end Intent intent = null; try { intent = Intent.parseUri( "intent:#Intent;component=com.example.vulnerableapplication/.BroadcastReceiver.MyBroadcastReceiver;S.from=123456;S.text=text;end" ,Intent.URI_INTENT_SCHEME); } catch (URISyntaxException e) { e.printStackTrace(); } sendBroadcast(intent); ``` 这两种方式也是可以的。 + 跨进程发送广播 显示发送广播,context参数,改成包名才行,隐式广播,上面的就可以 ``` Intent intent = new Intent(); ComponentName componentName = new ComponentName( "com.example.vulnerableapplication" , "com.example.vulnerableapplication.BroadcastReceiver.MyBroadcastReceiver" ); intent.setComponent(componentName); intent.putExtra( "from" , "123456" ); intent.putExtra( "text" , "text" ); Log.i( "ActivityTaskManager" ,intent.toUri(Intent.URI_INTENT_SCHEME)); ``` + 测试结果: 静态广播发送,可以在目标经常退出的情况下,发送广播,目标进程会发生回调,动态注册的广播,在进程退出的情况下,无法接受广播。 + 广播接收回调中启动activity ``` Intent intent1 = new Intent(context, TestActivity. class ); intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK ); context.startActivity(intent1); ``` 同进程的没有问题,跨进程调用,界面没调用起来。需要目标应用拥有后台弹出界面权限,没有权限,据说会被系统拦截 |
Intent 导致的安全问题
Intent,可以说android上大部分的应用安全问题,都是通过Intent直接或者间接导致的,作为攻击者(第三方应用攻击程序),在anroid系统上来说,唯一能主动跳出android 沙箱,攻击其他应用的方法,就是通过Intent(额,还想还有provider)。一台不联网的服务器,是没有web安全问题的,即使他的代码,有无数的web安全问题也没关系。在android上是一样的,如果没有人能跳出android沙箱,就没有android应用可以被第三方攻击导致的安全问题。
没有深入的分析过Intent的系统源码,不敢把话说的那么满,目前就我了解到的,Intent,除了上面介绍的,还能在传递中带参数,这就导致了一系列的,使用Intent参数的导致的安全问题。这些参数又会导致各种不同的安全问题。
照着网上抄几个吧
访问受保护的activity
一般我们作为外部应用,只能访问exported=”true” 的组件(某些没写,但是默认属性)。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
loginUtils
=
LoginUtils.getInstance(this);
Intent intent
=
getIntent();
Uri uri;
if
(intent !
=
null
&& Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri
=
intent.getData()) !
=
null) {
processDeeplink(uri);
}
finish();
}
private void processDeeplink(Uri uri) {
if
(
"oversecured"
.equals(uri.getScheme()) &&
"ovaa"
.equals(uri.getHost())) {
String path
=
uri.getPath();
Log.e(
"rzx"
,path);
if
(
"/logout"
.equals(path)) {
loginUtils.logout();
startActivity(new Intent(this, EntranceActivity.
class
));
}
else
if
(
"/login"
.equals(path)) {
String url
=
uri.getQueryParameter(
"url"
);
if
(url !
=
null) {
loginUtils.setLoginUrl(url);
}
startActivity(new Intent(this, EntranceActivity.
class
));
}
else
if
(
"/grant_uri_permissions"
.equals(path)) {
Intent i
=
new Intent(
"oversecured.ovaa.action.GRANT_PERMISSIONS"
);
if
(getPackageManager().resolveActivity(i,
0
) !
=
null) {
startActivityForResult(i, URI_GRANT_CODE);
}
}
else
if
(
"/webview"
.equals(path)) {
String url
=
uri.getQueryParameter(
"url"
);
if
(url !
=
null) {
String host
=
Uri.parse(url).getHost();
if
(host !
=
null && host.endsWith(
"example.com"
)) {
Intent i
=
new Intent(this, WebViewActivity.
class
);
i.putExtra(
"url"
, url);
startActivity(i);
}
}
}
}
else
{
Log.e(
"rzx"
,
"url == null"
);
}
}
上面这个activity就是导出的,我们可以通个这个activity,构造不同的Intent参数,来启东不同的activity
绕过登录activity
比如某些activity 登录界面,有着极强的验证手段,但是,在登录进去的用户界面,却没有很强的验证逻辑,我们可以考虑构造Intetn参数,直接启动这个界面,甚至他会有很多默认的东西,没有擦干净我们可以接着用,这又搞成了,身份伪造。
危害: 比如应用,账号密码界面,需要输密码才能进去,但是密码显示界面严格的调用来源检测,第三方应用,直接启动这个界面,造成用户数据泄露,截图往后台发照片啥的。
伪造身份,接收广播,或者发送会导致漏洞的广播
注册与目标应用相同的广播,就可以接受到目标应用发送过来的Intent。从而解析,泄露用户信息
也可以分析目标广播接收逻辑,Intent参数处理,构造特意的Intent参数,伪造身份,要确保目标应用广播没有来源验证,或者能绕过
伪造身份数据,协助攻击,主动给服务发消息
Intent类型的漏洞,用处很多,类型不少,但是其实主要还是作为攻击入手点的,感觉又有点单一,殊途同归的感觉,现在可能已经没有那么傻的,可以直接通过Intent,简单过一下,就能获得攻击回报的漏洞了。
三、webview漏洞利用和Andriod URL Scheme
什么是Andriod URL Scheme?
Android中的scheme是一种页面内跳转协议,好像也叫deeplink,简单点就是通过一个url的链接请求,可以直接打开一个对应的app页面
使用的场景有哪些
隐式启动的时候,通过action 和 data匹配,data填写对应的url,跳转到对应的activity界面
webview 界面,跳转到对应的链接地址的时候,可以跳转到一个这个app data填写的url,可以跳转到对应的activity界面
1<a href
=
"openapp://test:8000/detail"
>打开app页面(openapp:
/
/
test:
8000
/
detail)<
/
a>
- webview默认跳转的界面除了url匹配,必须满足(action,可以修改,后面会写):
如果没有这几行,我特意在android 9 试了一下debug demo ,跳不过去的,直接找不到123<action android:name
=
"android.intent.action.VIEW"
/
>
<category android:name
=
"android.intent.category.DEFAULT"
/
>
<category android:name
=
"android.intent.category.BROWSABLE"
/
>
- webview默认跳转的界面除了url匹配,必须满足(action,可以修改,后面会写):
使用示例以及测试结果
- app隐式调用,在前面演示过了
在webview中的漏洞利用
- activity使用的依然是前面的那个,url也相同
html 文件代码
12345678910111213141516171819202122232425262728293031323334353637383940<!DOCTYPE html>
<html>
<head>
<meta charset
=
"utf-8"
/
>
<title>这是标题啊<
/
title>
<style
type
=
"text/css"
>
dd {
margin
-
top:
30px
;
/
*
上外边距
30
像素
*
/
}
<
/
style>
<
/
head>
<body>
<div
id
=
"wrap"
>
<div
id
=
"header"
><h1>Webview简单使用<
/
h1>
<
/
div>
<div
id
=
"main"
>
<dl>
<dd><a href
=
"https://www.baidu.com"
>点击跳转到百度<
/
a><
/
dd>
<dd><a href
=
"http://www.google.com"
>点击跳转到google<
/
a><
/
dd>
<a href
=
"openapp://test:8000/detail"
>打开app页面(openapp:
/
/
test:
8000
/
detail)<
/
a>
<br><br><br>
<a href
=
"app://AndroidHtml"
>打开app页面(app:
/
/
AndroidHtml)<
/
a>
<br><br><br>
<dd>
<button
id
=
'callback_client'
onclick
=
"callBackClient()"
type
=
"button"
>用js调用客户端
<
/
button>
<
/
dd>
<
/
dl>
<
/
div>
<
/
body>
<script>
function callBackClient(){
alert(
"Js alert"
);
/
/
弹窗
javascript:android.getClient(
"传一个字符串给客户端"
);
/
/
调用客户端
}
<
/
script>
<
/
html>
- 跳转的代码
对应了了scheme data的格式1234<a href
=
"openapp://test:8000/detail"
>打开app页面(openapp:
/
/
test:
8000
/
detail)<
/
a>
<br><br><br>
<a href
=
"app://AndroidHtml"
>打开app页面(app:
/
/
AndroidHtml)<
/
a>
<br><br><br>
只要webview 访问了这个js,进行了这个url的跳转,就会直接跳转到这个activity界面。 setWebViewClient and shouldOverrideUrlLoading
如果webview 调用了setWebViewClient,设置了一个webviewclient对象,那么,就会无法触发URL Scheme,这个webviewclient,可以选择重写shouldOverrideUrlLoading 方法,这个方法,会在每一个链接跳转前进行拦截调用。很多重写这个方法的应用,会在这个位置,通过startActivity,做主动调用,网站过滤什么的,具体的要具体分析。
URL Scheme 攻击,是每一个,有web浏览器跳转android app界面功能的应用,都绕不过的攻击点。
造成的危害:
默认的webview URL Scheme,特定的activity访问
前面,写了,这个url访问activity的方式,是可以的,但是,访问的页面必须有以下标志,否则无法访问。
123<action android:name
=
"android.intent.action.VIEW"
/
>
<category android:name
=
"android.intent.category.DEFAULT"
/
>
<category android:name
=
"android.intent.category.BROWSABLE"
/
>
但是我在通过浏览器跳转到activity的时候发现了如下日志
12021
-
06
-
17
20
:
26
:
09.396
1573
-
4418
/
? I
/
ActivityManager: START u0 {act
=
android.intent.action.VIEW cat
=
[android.intent.category.BROWSABLE] dat
=
openapp:
/
/
test:
8000
/
detail
cmp
=
com.example.webviewapplication
/
.VulnActivity (has extras)}
from
uid
10200
可以看出,acttion 和 category他已经给我们写上了。我试着通过字符串修改了一下,希望能达到任意activity的访问,但是不行,不过action是可以修改的,category无法修改。
重写shouldOverrideUrlLoading 方法
有一些android app 内嵌webview,并且想要的更多。而且重写了 shouldOverrideUrlLoading 方法以后,必须要自己处理URL Scheme 问题,如果处理不当,检查不严格,最严重的可能就是任意activity访问,这个要具体分析,我们甚至可以具体分析他的参数,然后给他传参数,造成app csrf等