APP-SECURITY-404 组件导出漏洞复现
一.概述
组件就不多介绍了,安卓的四大组件:activity,service,broadcastReciver,ContentProvider
导出: 其他的应用或组件通过发送intent对象的方式调用其他组件。
intent是一种消息传递对象,intent的基本知识放个博客链接:
https://blog.csdn.net/salary/article/details/82865454
这个漏洞的主要原理还是在于对intent对象的处理没有添加异常事件所导致。
二.漏洞复现
二.1 简单类型
先写了两个activity,其中一个activity通过发送intent对象方式来启动另一个activity,并把数据存在了intent对象中,一起发送过去。
第一个activity
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent =new Intent(MainActivity.this,Activity1.class); //本质都是构造了compantName对象(封装了被调用组件的信息)
//传输数据给Activity
intent.putExtra("str1","i am str1");
startActivity(intent);
}
}
第二个activity
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast; public class Activity1 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
Intent intent=getIntent();
String str1=intent.getStringExtra("str1");
Toast.makeText(this,"str1="+str1,Toast.LENGTH_SHORT).show();
}
}
这里其实很简单,就是主活动通过发送包含数据的intent方式调用另一个活动组件,另一个活动通过getStringExtr方法来将数据取出来,再生成一个消息提示框,将取出来的字符串拼接好,放入对话框中
那么假设,我把第一个存数据的代码注释掉,会发生什么呢
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent =new Intent(MainActivity.this,Activity1.class); //本质都是构造了compantName对象(封装了被调用组件的信息)
//传输数据给Activity
//intent.putExtra("str1","i am str1");
startActivity(intent);
}
}
第二个活动的代码不变,这时候运行一下我们的app。
发现虽然没有这key-value存在,但是似乎自动生成了个key-value,不过value默认是null。这里没啥问题,但如果我的
第二个活动代码是这样写的话
package com.example.twoapplication; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast; public class Activity1 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_1);
Intent intent=getIntent();
String str1=intent.getStringExtra("str1");
if(str1.equals("i am str1"))
{
Toast.makeText(this,"str1="+str1,Toast.LENGTH_SHORT).show();
} }
}
这里有点意思是equals方法,这个方法是继承父类object类的,也就意味着只有对象才能引用这个对象
然后这里有可能出现str1为null的情况,String str1其实是引用,赋值给null是没毛病的,只是这里出问题
应该是程序员的忽视了,这里的修改意见应该是先判断是否为null,如果是null就没必要比较了,因为不可能
相等的,null和对象相等不存在好吧,如果不是null,那值得一比,不过第一个比较是用==,因为不是比字符串
的值了,没必要用equals,也不可能用。
这里运行一下,肯定会出现空指针异常,这里可以查下崩溃日志
点击android studio左下角的按钮就可以查看崩溃日志
这里报的是空指针异常,达到目的了,一是没有预防,二是没有去处理这个异常,添加try/catch是个不错的方式
二.2 复杂类型的组件导出
之前那种简单的组件导出基本上不会出现了,不过还有一种更复杂一些的,前面算是程序员粗心导致的,后面这种是算是逻辑错误
这里聚集在获取intent中数据的方法getStringExtra这里,这个地方要去看sdk的源码,因为我是mac,command+鼠标单击就会自动
跳转到这个对应方法的源码,不过一开始我sdk版本设置的过高,30的安卓10的版本直接裂开,根本没有源码,我就去改了build.gradle里面的
sdk版本号,然后同步了一下,没想到成功了,改成了29之后,本来下sdk时,就直接把源码也下载下来,这下就可以看源码了。
这里先看getStringExtra的源码
public @Nullable String getStringExtra(String name) {
return mExtras == null ? null : mExtras.getString(name);
}
private Bundle mExtras;
这个mExtras的类型是Bundle,这里的再跟着mExtras.getString的方法看看,发现是在BaseBundle类中找到的这个方法,应该是它父类的方法
@Nullable
public String getString(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
return (String) o;
} catch (ClassCastException e) {
typeWarning(key, o, "String", e);
return null;
}
}
unparcel是反序列化方法,跟进去一波。
@UnsupportedAppUsage
/* package */ void unparcel() {
synchronized (this) {
final Parcel source = mParcelledData;
if (source != null) {
initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
} else {
if (DEBUG) {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this))
+ ": no parcelled data");
}
}
}
} private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
boolean parcelledByNative) {
if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
+ "clobber all data inside!", new Throwable());
} if (isEmptyParcel(parcelledData)) {
if (DEBUG) {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this)) + ": empty");
}
if (mMap == null) {
mMap = new ArrayMap<>(1);
} else {
mMap.erase();
}
mParcelledData = null;
mParcelledByNative = false;
return;
} final int count = parcelledData.readInt();
if (DEBUG) {
Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ ": reading " + count + " maps");
}
if (count < 0) {
return;
}
ArrayMap<String, Object> map = mMap;
if (map == null) {
map = new ArrayMap<>(count);
} else {
map.erase();
map.ensureCapacity(count);
}
try {
if (parcelledByNative) {
// If it was parcelled by native code, then the array map keys aren't sorted
// by their hash codes, so use the safe (slow) one.
parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
} else {
// If parcelled by Java, we know the contents are sorted properly,
// so we can use ArrayMap.append().
parcelledData.readArrayMapInternal(map, count, mClassLoader);
}
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
map.erase();
} else {
throw e;
}
} finally {
mMap = map;
if (recycleParcel) {
recycleParcel(parcelledData);
}
mParcelledData = null;
mParcelledByNative = false;
}
if (DEBUG) {
Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}
这里不知道为啥我跟不进这个函数,裂开,parcel这个类,是一个共享内存,将序列化数据存进去,同时也可以通过parcel对象
将对象反序列化取出来,这里大概知道intent发送数据,是先将key -value的值序列化存进parcel,然后其他组件再通过parcel
对象通过类加载器和类名,解析反序列化出相对应的对象出来,存进map中。
---以下源码源自王师傅
/* package */ void readArrayMapInternal(ArrayMap outVal, int N,
ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
}
int startPos;
while (N > 0) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
String key = readString();
Object value = readValue(loader);
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
outVal.append(key, value);
N--;
}
outVal.validate();
}
再跟readValue方法
public final Object readValue(ClassLoader loader) {
int type = readInt(); switch (type) {
case VAL_NULL:
return null; case VAL_STRING:
return readString(); case VAL_INTEGER:
return readInt(); ...... case VAL_SERIALIZABLE:
return readSerializable(loader); ...... default:
int off = dataPosition() - 4;
throw new RuntimeException(
"Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
}
}
这个readSerializable方法有点意思,其实就是反序列化把对象取出来
再跟进去看看
private final Serializable readSerializable(final ClassLoader loader) {
String name = readString();
if (name == null) {
// For some reason we were unable to read the name of the Serializable (either there
// is nothing left in the Parcel to read, or the next value wasn't a String), so
// return null, which indicates that the name wasn't found in the parcel.
return null;
} byte[] serializedData = createByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
try {
ObjectInputStream ois = new ObjectInputStream(bais) {
@Override
protected Class<?> resolveClass(ObjectStreamClass osClass)
throws IOException, ClassNotFoundException {
// try the custom classloader if provided
if (loader != null) {
Class<?> c = Class.forName(osClass.getName(), false, loader);
if (c != null) {
return c;
}
}
return super.resolveClass(osClass);
}
};
return (Serializable) ois.readObject();
} catch (IOException ioe) {
throw new RuntimeException("Parcelable encountered " +
"IOException reading a Serializable object (name = " + name +
")", ioe);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException("Parcelable encountered " +
"ClassNotFoundException reading a Serializable object (name = "
+ name + ")", cnfe);
}
}
发现就是查找反序化类,然后反序列化对象出来,如果没有找到反序列化的类,就会抛出异常,这里就是突破口
假设传入的序列化的类找不到,而且并没有用try/catch来处理异常,那么就会崩溃。
poc 贴下王师傅的
public class MainActivity extends Activity {
private static final String TAG = "MainActivity"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName(target_package_name, target_component_name));
intent.putExtra("serializable_key", new DataSchema());
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
} class DataSchema implements Serializable {
private static final long serialVersionUID = -1L;
}
APP-SECURITY-404 组件导出漏洞复现的更多相关文章
- Weblogic WLS-WebServices组件反序列化漏洞复现
漏洞分析: 当weblogic使用WLS-WebServices组件时,该组件会调用XMLDecoder解析XML数据,由此就产生了该漏洞 影响版本: weblogic<10.3.6版本 复现过 ...
- uniapp解决测评有组件导出风险,解决APP反编译,回编译后app无法打开的问题
1.APP反编译 使用hbx云打包,打包出apk 拿到apk后,先下载反编译工具 https://pan.baidu.com/s/1A5D8x_pdSELlHYl-Wl6Xnw 提取码 6vzd 使用 ...
- struts2漏洞复现分析合集
struts2漏洞复现合集 环境准备 tomcat安装 漏洞代码取自vulhub,使用idea进行远程调试 struts2远程调试 catalina.bat jpda start 开启debug模式, ...
- 【S2-052】漏洞复现(CVE-2017-9805)
一.漏洞描述 Struts2 的REST插件,如果带有XStream组件,那么在进行反序列化XML请求时,存在未对数据内容进行有效验证的安全隐患,可能发生远程命令执行. 二.受影响版本 Struts2 ...
- struts2(s2-052)远程命令执行漏洞复现
漏洞描述: 2017年9月5日,Apache Struts发布最新安全公告,Apache Struts2的REST插件存在远程代码执行的高危漏洞,该漏洞由lgtm.com的安全研究员汇报,漏洞编号为C ...
- Weblogic 'wls-wsat' XMLDecoder 反序列化_CVE-2017-10271漏洞复现
Weblogic 'wls-wsat' XMLDecoder 反序列化_CVE-2017-10271漏洞复现 一.漏洞概述 WebLogic的 WLS Security组件对外提供webservic ...
- nginx解析漏洞复现
nginx解析漏洞复现 一.漏洞描述 该漏洞与nginx.php版本无关,属于用户配置不当造成的解析漏洞 二.漏洞原理 1. 由于nginx.conf的如下配置导致nginx把以’.php’结尾的文件 ...
- Weblogic-SSRF 漏洞复现
0x01 环境搭建 我这里使用的是vulhub,它几乎包含了所有的漏洞环境.(建议安装在ubuntu上) 有需要的小伙伴来企鹅群自取. 安装好vulhub之后需要cd 到weblogic ssrf 目 ...
- 【漏洞复现】S2-052 (CVE-2017-9805)
一.漏洞描述 Struts2 的REST插件,如果带有XStream组件,那么在进行反序列化XML请求时,存在未对数据内容进行有效验证的安全隐患,可能发生远程命令执行. 二.受影响版本 Struts2 ...
随机推荐
- python 中的nonlocal
python 中nonloal 关键字用来在函数或其他作用域中使用外层变量(非全局),也可使用global需要在函数外部
- Java Bean(Day_05)
我们一路奋战,不是为了改变世界,而是为了不让世界改变我们. 运行环境 JDK8 + IntelliJ IDEA 2018.3 本文中使用的jar包链接 https://files.cnblogs.co ...
- 数据流分析软件SQLFlow的工作原理
SQLFlow是一个可视化的在线处理SQL对象依赖关系的工具,只需要上传你的SQL脚本,它可以自动分析SQL里的数据对象,包括database.schema.table.view.column.pro ...
- unity inputfield 过滤emoji输入
unity版本:unity2017.1.5f1 复现步骤:InputField在安卓手机InputField连续输入两个emoji会报错 报错内容: 2020-01-08 19:56:38.366 2 ...
- fragment不适用binding的加载视图方法
abstract class BaseFragment :Fragment(){ override fun onCreateView( inflater: LayoutInflater, contai ...
- GO学习-(25) Go操作Redis实战
Go操作Redis实战 安装Redis客户端 Go语言中使用第三方库https://github.com/go-redis/redis连接Redis数据库并进行操作.使用以下命令下载并安装: go ...
- Go语言协程并发---管道信号量应用
package main import ( "fmt" "math" "strconv" "time" ) /* ·10 ...
- 如何查看Oracle SID即instance_name 和 dbname区别
SID 和 instance_name是一个实例名字db_name 是数据库名字搞清两个概念,数据库和实例 实例:实例是数据库启动时初始化的一组进程和内存结构 数据库:数据库则指的是用户存储数据的一 ...
- LR: GLU-Net: Global-Local Universal Network for Dense Flow and Correspondences
Abstract 在图像中简历稠密匹配是很重要的任务, 包括 几何匹配,光流,语义匹配. 但是这些应用有很大的挑战: 大的平移, 像素精度, 外观变化: 当前是用特定的网络架构来解决一个单一问题. 我 ...
- bind,call,apply的区别
function cat(){}cat.prototype={ food:"fish", say: function(){ alert("I love "+th ...