返回博客首页
← 所有帖子

保护您的移动应用程序 - 第一集(保护代码)

2019 年 1 月 14 日 - 作者:Rob Lauer

无论您是开发传统的原生应用程序、从 Appcelerator 或 Xamarin 等平台跨编译的应用程序、使用 Ionic 的混合应用程序,还是使用 NativeScript 或 React Native 的 JavaScript 原生应用程序,应用程序安全都是贯穿其中的共同主题。

移动安全不再是轻视的事情。几乎每个人都随身携带敏感数据、访问公司机密和/或受保护的健康信息。

观看网络研讨会 "保护您的移动应用程序的最佳实践",获取有关 NativeScript 安全的提示和技巧!

一些历史

还记得 90 年代吗?我(大多)记得。我还记得在我的咨询生涯中遇到的问题,比如:

  • 将用户密码存储为明文;
  • 在查询字符串中发送包含社会安全号码的请求;
  • 在未启用 SSL 的情况下接受信用卡付款。

美好的时光!😬

传统上,用户依赖公共应用商店作为最终的应用程序守门人:充当病毒守卫者,防止恶意 API 使用。但现实情况是,我们开发者有责任在我们部署下一个伟大的应用程序之前实施额外的安全考虑因素。

在本系列文章的四部分中,我们将深入探讨一系列安全相关提示和技巧,供您应用到您的应用程序中。大多数这些技巧非常容易实施,因为繁重的工作已经由我们杰出的插件开发人员社区完成了。

查看来自 NativeScripting.com 的移动应用程序安全新课程,并使用代码 NSSECURE 获取 30% 的折扣。

源代码安全...?

我们大多数人来自 Web 开发背景。我们习惯于通过服务器将代码(实际上)发送到用户的浏览器。知识产权(代码复制)问题确实存在,但我们几乎无能为力阻止这些问题。另一方面,桌面和移动开发人员更多地习惯于将代码编译成大部分不可读的位 - 保护代码并降低检测漏洞的努力。

那么,使用 React Native 和 NativeScript 等技术构建的这种新一波“JavaScript 原生”应用程序如何处理这些问题呢?混合应用程序(使用 Ionic 构建)又如何呢?

securing source code

我很不喜欢打破大家的想法,但发送到客户端的源代码本质上是不安全的 - 因为它在技术上可以通过最终用户以某种方式读取。NativeScript、React Native 和 Cordova/Ionic - 这些都没有编译成原生字节码。JavaScript 在设备上被解释,类似于 Web 浏览器的工作方式。

所以您是传统的原生应用程序开发人员,认为自己没问题?再想想 - 有无数的工具可以反编译您的代码并读取您的机密。🤐

但并非一切皆失!让我们看看一些策略,用于保护您的源代码,防止窥探者窥视您的商品 - 保护您的知识产权并减轻对您的应用程序和后端系统的潜在攻击。

缩小和混淆

第一个也是最不强大的保护代码方法是缩小/混淆。这是一种久经考验的技术,可以使您的代码对人眼不可读。一个流行的混淆库,Uglify,可以将像这样的可读 JavaScript 代码

const app = require("tns-core-modules/application");
const HomeViewModel = require("./home-view-model");

function onNavigatingTo(args) {
    const page = args.object;
    page.bindingContext = new HomeViewModel();
}

function onDrawerButtonTap(args) {
    const sideDrawer = app.getRootView();
    sideDrawer.showDrawer();
}

exports.onNavigatingTo = onNavigatingTo;
exports.onDrawerButtonTap = onDrawerButtonTap;

...转换成像这样的稍微不那么可读的代码

const app=require("tns-core-modules/application"),HomeViewModel=require("./home-view-model");function onNavigatingTo(o){o.object.bindingContext=new HomeViewModel}function onDrawerButtonTap(o){app.getRootView().showDrawer()}exports.onNavigatingTo=onNavigatingTo,exports.onDrawerButtonTap=onDrawerButtonTap;

NativeScript CLI 允许您开箱即用地缩小您的应用程序,假设您已经在使用 Webpack(如果您没有,您应该使用它!)。只需发出以下命令来构建和缩小您的代码

tns build android|ios --bundle --env.uglify

警告:这相当于我们中学时使用的廉价自行车锁!

cheap bike lock

它会阻止普通黑客进入我们的业务,但问题是那里有许多“美化”资源可以将缩小的代码变得更加可读。在上面混淆的代码上使用其中一项服务会产生以下结果

const app = require("tns-core-modules/application"),
    HomeViewModel = require("./home-view-model");

function onNavigatingTo(o) {
    o.object.bindingContext = new HomeViewModel
}

function onDrawerButtonTap(o) {
    app.getRootView().showDrawer()
}
exports.onNavigatingTo = onNavigatingTo, exports.onDrawerButtonTap = onDrawerButtonTap;

好吧,这是一个开始。但我认为我们可以做得更好。

许多人提到 ProGuard 也是一种选择。ProGuard 可以混淆Java代码,但对JavaScript无能为力。

Jscrambler(增强保护)

NativeScript 团队一直与 Jscrambler 的人员保持联系,这已经好几年了,可以追溯到我们进行混合应用程序开发的时代。Jscrambler 是一项服务,提供高级 JavaScript 混淆和保护,直到代码变得不可读,即使在美化之后也是如此。

jscrambler-javascript-protection

Jscrambler 通过将您的 JavaScript 转换为一种形式来防止您的代码被篡改,这种形式使用自动静态分析攻击受到反向工程的保护。Jscrambler 还可以添加“代码锁”,限制何时、何地以及由谁执行 JavaScript。

例如,我们可以获取 NativeScript 应用程序中的某些 JavaScript,将其通过 Jscrambler 运行,然后获得以下结果

jscrambler-output

👍👍

在经过验证的 NativeScript 兼容性的情况下,非常值得尝试一下 Jscrambler。立即开始您的免费试用 jscrambler.com.

查看此深入教程,了解如何在 NativeScript 中使用 Jscrambler。

到目前为止,我们已经采取了一些非常可靠的措施来保护和保护我们交付给最终用户的代码。那么,采取额外措施来减少可以安装我们的应用程序的潜在范围怎么样呢?

通过私有应用程序商店限制访问

公共应用程序商店几乎没有限制谁可以下载您的应用程序。无论目的或受众如何,澳大利亚的 14 岁青少年通常与亚利桑那州的 80 岁老人具有相同的访问权限。

当然,您可以按年龄和地理位置限制您的应用程序,使其在特定国家/地区可用,但这与应用程序安全无关。

如果您正在开发一个只需要交付给单个实体(即一组用户或单个公司/组织)的应用程序,私有应用程序商店可能是更好的选择

企业 MAM/MDM 选项

如果您是大型组织的一部分,那么您的公司很可能依赖移动应用程序管理 (MAM) 或移动设备管理 (MDM) 软件来帮助保护您的内部应用程序和/或设备。使用 MAM 提供商(如 MobileIronAirWatch),您将获得一个内部“企业”应用程序商店,因此您无需担心未经授权的第三方能够下载您的应用程序。

还有其他更便宜、侵入性更小的选择

Apple 企业开发者计划

Apple 企业开发者计划 允许您绕过公共 iOS App Store,并将您的应用程序直接交付给您组织的用户。虽然成本高于传统的开发者协议(每年 299 美元对每年 99 美元),但分发灵活性是无价的。

代码签名和配置文件生成的过程与标准方法完全相同。您只需获得一个额外的独立配置文件选项,用于内部/临时应用程序部署。

简单!但在某些方面,Android 甚至更简单

Android 私有分发

Google 在将应用程序部署到 Google Play 之外时,限制要少得多。您可以设置自己的私有应用程序市场(甚至创建自己的应用程序充当应用程序商店),而不会像在 Apple 那样引起麻烦。Google 甚至允许您通过电子邮件、自己的网站或甚至托管的 Google Play 商店分发应用程序。

注意:如果您不使用 Google Play,则无法使用应用内结算和许可服务。

唯一的技巧是您的最终用户必须选择加入安装未知应用程序

还有许多服务提供类似的功能,如果您不想自己开发,可以选择这些服务。Applivery 就是这样的服务示例。

将业务逻辑保留在云中

与其尝试保护设备上的私有业务逻辑,不如将其卸载到后端系统?与 Web 应用程序倾向于将复杂的业务逻辑保留在后端的方式类似,您也可以对移动应用程序执行相同的操作。

对于许多场景,您可能最好将敏感业务逻辑从您的应用程序转移到云中,无论是为了安全还是性能。

使用 NativeScript,一种简单的方法是使用 FlexServices - 由 Progress Kinvey 提供的轻量级 Node.js 微服务。

提示:Kinvey 为许多移动框架提供 SDK,例如 IonicXamarin原生 iOS原生 Android,当然还有 NativeScript

您可能偶尔会在您的应用程序中存储一些专有业务逻辑,这些逻辑最好保留在云中(无论是为了 IP 保护还是性能考虑,甚至在服务器上隐藏其他 API 密钥!)。因此,与其将此逻辑保留在您的应用程序中,不如使用 Kinvey 编写一个 FlexService。

kinvey flexservice

例如,以下 FlexService(由杰出的 TJ VanToll 提供)读取财务交易数据并根据专有算法对您的表现进行评分

const sdk = require('kinvey-flex-sdk');

function getTransactions(modules) {
  return new Promise((resolve, reject) => {
    const store = modules.dataStore({ useUserContext: false });
    const collection = store.collection('Transactions');
    const query = new modules.Query();

    collection.find(query, (err, result) => {
      if (err) {
        reject(err);
      } else {
        resolve(result);
      }
    });
  });
}

function determineScore(transactions) {
  var score = 100;
  transactions.forEach((transaction) => {
    if (transaction.amount < 0) {
      score -= 5;
    }
    if (transaction.amount > 5) {
      score += 10;
    }
    if (transaction.category === "restaurant") {
      score -= 5;
    }
  });
  return score.toString();
}

sdk.service((err, flex) => {
  function getBudgetScore(context, complete, modules) {
    getTransactions(modules).then((transactions) => {
      complete().setBody({
        score: determineScore(transactions)
      }).done();
    }).catch((err) => {
      complete().setBody(new Error(err)).runtimeError().done();
    });
  }

  flex.functions.register('getBudgetScore', getBudgetScore);
});

此 FlexService 通过 Kinvey 提供的端点在我们的应用程序中访问

return this.http.post(
    "https://baas.kinvey.com/rpc/kid_<ID>/custom/BudgetScore",
    {},
    {
        headers: new HttpHeaders({
            "Content-Type": "application/json",
            "Authorization": "Basic <YOUR AUTH KEY>"
        })
    }
);

使用这种方法,您的知识产权是安全的,您的业务逻辑不会以任何方式暴露给您的用户,并且您还可以获得完全可扩展的 Kinvey 实例的性能和可靠性优势。

要亲眼看看 Kinvey 如何为您提供移动应用程序开发需求,注册免费试用.

注意共享密钥!

好吧,这可能太基础了,但它发生的频率比您想象的要高得多:确保您没有共享私钥!

当我们在 GitHub 上使用公共仓库时,我们通常不会限制上传哪些文件。有一些机器人会定期扫描仓库以查找私有的 AWS 或 Firebase 密钥,然后利用这些密钥进行恶意目的,例如:

crypto mining

最简单的解决方法是使用 `.gitignore` 文件,并排除存储私钥的 .ts/.js 文件。以下是用于我的 NativeScript 项目的标准 `.gitignore` 文件(假设我使用 TypeScript,这也排除了 `app` 目录中的 JavaScript 文件):

.vscode/
.cloud/
platforms/
node_modules
app/**/*.js
app/**/*.map
npm-debug.log
app/keys.*
hooks/
app/**/google-services.json
app/**/GoogleService-Info.plist

这不仅排除了私钥,还阻止了 `platforms` 和 `node_modules` 目录被共享(如果您克隆应用程序,这些目录完全没有必要,更不用说包含数千个文件了!)。

进入第二集:静止数据安全!

今天我们学习了如何采取一些相对简单的步骤来保护我们的应用程序代码,减轻恶意用户试图查看我们代码的尝试,缩减我们的应用程序安装空间,并将敏感业务逻辑转移到云端。

在下一部分中,我们将探讨如何更好地保护存储在设备上的数据