Android敏感数据泄露引发的思考

Android敏感数据泄露引发的思考,第1张

概述Android敏感数据泄露引发思考1.事件始末2.事件分析3.事件处理1.首先创建了加密工具类:2.生成相应的头文件:3.编写相应的cpp文件:4.事件就此结束?5.总结1.事件始末一个清凉的午后,看到一则新闻,关注接口正在被机械式调用,怀疑是有人在使用脚本刷接口(目的主要是从平台导流)

AndroID敏感数据泄露引发的思考1.事件始末2.事件分析3.事件处理1.首先创建了加密工具类:2.生成相应的头文件:3.编写相应的cpp文件:4.事件就此结束?5.总结

1.事件始末

一个清凉的午后,看到一则新闻,关注接口正在被机械式调用,怀疑是有人在使用脚本刷接口(目的主要是从平台导流)。

纳尼?不会吧,一般接口请求是做了加密处理的,除非知道加密的密钥和加密方式,不然是不会调用成功的,一定是感觉错了。当服务端同事把接口调用日志出来时,彻底否定了侥幸心理。

接口调用频率固定为1s 一次被关注者的ID每次调用依次加一(目前业务上用户ID的生成是按照注册时间依次递增的)加密的密钥始终使用固定的一个(正常的是在固定的几个密钥中每次会随机使用一个)

综合以上三点就可以断定,肯定是存在刷接口的行为了。

2.事件分析

既然上述刷接口的行为成立,也就意味着密钥和加密方式被对方知道了,原因无非是以下两点:

内部人员泄露apk被破解

经过确认基本排除了第一点,那就只剩下apk被破解了,可是apk发布出去的包是进行过加固和混淆处理的,难道对方脱壳了?不管三七二十一,自己先来反编译试试。

于是乎从最近发布的版本一个一个去反编译,最后在反编译到较早前的一个版本时发现,保存密钥和加密的工具类居然源码完全暴露了。


炸了锅了,排查了一下这个版本居然未加固过就发布出去了,而且这个加密工具类未被混淆。虽然还不太清楚对方是不是按照这种方式获取的密钥和加密算法,但无疑这是客户端存在的一个安全漏洞。

3.事件处理

既然已经发现了上述问题,那就要想办法解决。

首先不考虑加固,如何尽最大可能保证客户端中的敏感数据不泄露?另一方面即使对方想要破解,也要想办法设障,增大破解难度。

想到这里基本就大致确定了一个思路:使用NDK,将敏感数据和加密方式放到native层,因为C++代码编译后生成的so库是一个二进制文件,这无疑会增加破解的难度。利用这个特性,可以将客户端的敏感数据写在C++代码中,从而增强应用的安全性。 说干就干吧!!!

1.首先创建了加密工具类:
public class httpKeyUtil {    static {        System.loadlibrary("jniSecret");    }    //根据随机值去获取密钥    public static native String gethttpSecretKey(int index);    //将待加密的数据传入,返回加密后的结果    public static native String getSecretValue(byte[] bytes);}
2.生成相应的头文件:

com_test_util_httpKeyUtil.h

#include <jni.h>#ifndef _Included_com_test_util_httpKeyUtil#define _Included_com_test_util_httpKeyUtil#ifdef __cplusplusextern "C" {#endifJNIEXPORT Jstring JNICALL Java_com_esky_common_component_util_httpKeyUtil_gethttpSecretKey        (jnienv *, jclass, jint);        JNIEXPORT Jstring JNICALL Java_com_test_util_httpKeyUtil_getSecretValue        (jnienv *, jclass, jbyteArray);#ifdef __cplusplus}#endif#endif
3.编写相应的cpp文件:

在相应的Module中创建jni目录,将com_test_util_httpKeyUtil.h拷贝进来,然后再创建com_test_util_httpKeyUtil.cpp文件

#include <jni.h>#include <cstring>#include <malloc.h>#include "com_test_util_httpKeyUtil.h"extern "C"const char *KEY1 = "密钥1";const char *KEY2 = "密钥2";const char *KEY3 = "密钥3";const char *UNKNowN = "unkNown";Jstring toMd5(jnienv *pEnv, jbyteArray pArray);extern "C" JNIEXPORT Jstring JNICALL Java_com_test_util_httpKeyUtil_gethttpSecretKey        (jnienv *env, jclass cls, jint index) {    if (随机数条件1) {        return env->NewStringUTF(KEY1);    } else if (随机数条件2) {        return env->NewStringUTF(KEY2);    } else if (随机数条件3) {        return env->NewStringUTF(KEY3);    } else {        return env->NewStringUTF(UNKNowN);    }}extern "C" JNIEXPORT Jstring JNICALLJava_com_test_util_httpKeyUtil_getSecretValue        (jnienv *env, jclass cls, jbyteArray jbyteArray1) {        //加密算法各有不同,这里我就用md5做个示范        return toMd5(env, jbyteArray1);}//md5Jstring toMd5(jnienv *env, jbyteArray source) {    // MessageDigest    jclass classMessageDigest = env->FindClass("java/security/MessageDigest");    // MessageDigest.getInstance()    jmethodID mIDGetInstance = env->GetStaticmethodID(classMessageDigest, "getInstance",                                                      "(Ljava/lang/String;)Ljava/security/MessageDigest;");    // MessageDigest object    jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, mIDGetInstance,                                                           env->NewStringUTF("md5"));    jmethodID mIDUpdate = env->getmethodID(classMessageDigest, "update", "([B)V");    env->CallVoIDMethod(objMessageDigest, mIDUpdate, source);    // Digest    jmethodID mIDDigest = env->getmethodID(classMessageDigest, "digest", "()[B");    jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod(objMessageDigest, mIDDigest);    Jsize intArrayLength = env->GetArrayLength(objArraySign);    jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NulL);    size_t length = (size_t) intArrayLength * 2 + 1;    char *char_result = (char *) malloc(length);    memset(char_result, 0, length);    toHexStr((const char *) byte_array_elements, char_result, intArrayLength);    // 在末尾补\0    *(char_result + intArrayLength * 2) = '\0';    Jstring stringResult = env->NewStringUTF(char_result);    // release    env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);    // 指针    free(char_result);    return stringResult;}//转换为16进制字符串voID toHexStr(const char *source, char *dest, int sourceLen) {    short i;    char highByte, lowByte;    for (i = 0; i < sourceLen; i++) {        highByte = source[i] >> 4;        lowByte = (char) (source[i] & 0x0f);        highByte += 0x30;        if (highByte > 0x39) {            dest[i * 2] = (char) (highByte + 0x07);        } else {            dest[i * 2] = highByte;        }        lowByte += 0x30;        if (lowByte > 0x39) {            dest[i * 2 + 1] = (char) (lowByte + 0x07);        } else {            dest[i * 2 + 1] = lowByte;        }    }}
4.事件就此结束?

到这里就此结束了?too yuang too simple!!!虽然将密钥和加密算法写在了c++中,貌似好像是比较安全了。

但是但是万一别人反编译后,拿到c++代码最终生成的so库,然后直接调用so库里的方法去获取密钥并调用加密方法怎么破?

看来我们还是要加一步身份校验才行:即在native层对应用的包名、签名进行鉴权校验,校验通过才返回正确结果。下面就是获取apk包名和签名校验的代码:

const char *PACKAGE_name = "你的ApplicationID";//(签名的md5值自己可以写方法获取,或者用签名工具直接获取,一般对接微信sdk的时候也会要应用签名的MD5值)const char *SIGN_MD5 = "你的应用签名的MD5值注意是大写";//获取Application实例jobject getApplication(jnienv *env) {    jobject application = NulL;    //这里是你的Application的类路径,混淆时注意不要混淆该类和该类获取实例的方法比如getInstance    jclass baseapplication_clz = env->FindClass("com/test/component/BaseApplication");    if (baseapplication_clz != NulL) {        jmethodID currentApplication = env->GetStaticmethodID(                baseapplication_clz, "getInstance",                "()Lcom/test/component/BaseApplication;");        if (currentApplication != NulL) {            application = env->CallStaticObjectMethod(baseapplication_clz, currentApplication);        }        env->DeleteLocalRef(baseapplication_clz);    }    return application;}bool isRight = false;//获取应用签名的MD5值并判断是否与本应用的一致jboolean getSignature(jnienv *env) {    LOGD("getSignature isRight: %d", isRight ? 1 : 0);    if (!isRight) {//避免每次都进行校验浪费资源,只要第一次校验通过后,后边就不在进行校验        jobject context = getApplication(env);        // 获得Context类        jclass cls = env->FindClass("androID/content/Context");        // 得到getPackageManager方法的ID        jmethodID mID = env->getmethodID(cls, "getPackageManager",                                         "()LandroID/content/pm/PackageManager;");        // 获得应用包的管理器        jobject pm = env->CallObjectMethod(context, mID);        // 得到getPackagename方法的ID        mID = env->getmethodID(cls, "getPackagename", "()Ljava/lang/String;");        // 获得当前应用包名        Jstring packagename = (Jstring) env->CallObjectMethod(context, mID);        const char *c_pack_name = env->GetStringUTFChars(packagename, NulL);        // 比较包名,若不一致,直接return包名        if (strcmp(c_pack_name, PACKAGE_name) != 0) {            return false;        }        // 获得PackageManager类        cls = env->GetobjectClass(pm);        // 得到getPackageInfo方法的ID        mID = env->getmethodID(cls, "getPackageInfo",                               "(Ljava/lang/String;I)LandroID/content/pm/PackageInfo;");        // 获得应用包的信息        jobject packageInfo = env->CallObjectMethod(pm, mID, packagename,                                                    0x40); //GET_SIGNATURES = 64;        // 获得PackageInfo 类        cls = env->GetobjectClass(packageInfo);        // 获得签名数组属性的ID        jfIEldID fID = env->GetFIEldID(cls, "signatures", "[LandroID/content/pm/Signature;");        // 得到签名数组        jobjectArray signatures = (jobjectArray) env->GetobjectFIEld(packageInfo, fID);        // 得到签名        jobject signature = env->GetobjectArrayElement(signatures, 0);        // 获得Signature类        cls = env->GetobjectClass(signature);        mID = env->getmethodID(cls, "toByteArray", "()[B");        // 当前应用签名信息        jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mID);        //转成Jstring        Jstring str = toMd5(env, signatureByteArray);        char *c_msg = (char *) env->GetStringUTFChars(str, 0);        LOGD("getSignature release sign md5: %s", c_msg);        isRight = strcmp(c_msg, SIGN_MD5) == 0;        return isRight;    }    return isRight;}//有了校验的方法,所以我们要对第3步中,获取密钥和加密方法的进行修改,添加校验的逻辑extern "C" JNIEXPORT Jstring JNICALL Java_com_test_util_httpKeyUtil_gethttpSecretKey        (jnienv *env, jclass cls, jint index) {    if (getSignature(env)){//校验通过      if (随机数条件1) {        return env->NewStringUTF(KEY1);      } else if (随机数条件2) {        return env->NewStringUTF(KEY2);      } else if (随机数条件3) {        return env->NewStringUTF(KEY3);      } else {        return env->NewStringUTF(UNKNowN);      }    }else {        return env->NewStringUTF(UNKNowN);    }}extern "C" JNIEXPORT Jstring JNICALLJava_com_test_util_httpKeyUtil_getSecretValue        (jnienv *env, jclass cls, jbyteArray jbyteArray1) {        //加密算法各有不同,这里我就用md5做个示范    if (getSignature(env)){//校验通过       return toMd5(env, jbyteArray1);    }else {        return env->NewStringUTF(UNKNowN);    }}
5.总结

以上就是此次事件native的相关代码,至于如何生成so库可以自行百度。从此次事件中需要反思的几点是:

安全性的认识,安全无小事发布出去的包必须走加固流程,为了防止疏漏

最后,感谢您的阅读。您的每个点赞、留言、分享都是对我们最大的鼓励,笔芯~

如有疑问,欢迎在评论区一起讨论!

总结

以上是内存溢出为你收集整理的Android敏感数据泄露引发的思考全部内容,希望文章能够帮你解决Android敏感数据泄露引发的思考所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1080694.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存