有“安全”的语言吗?

信息安全 威胁缓解 编程 安全编码
2021-09-02 10:47:28

是否有任何旨在抵御黑客攻击的编程语言?

换句话说,即使设计是完美的,应用程序也可能由于实施失败而被黑客入侵。我希望降低开发人员错误实施规范的风险。

例如

问题

  • 是否有一种语言可以解决许多或大部分这些问题?对于特定用例(例如 WebApp、桌面、移动或服务器使用)限定语言的范围是可以接受的。

编辑:很多人解决了缓冲区溢出问题,或者说程序员负责安全。我只是想知道是否存在主要目的是尽可能合理地提高安全性的语言。也就是说,某些语言是否具有使它们明显比大多数其他语言更(或更少)安全的特性?

4个回答

Ada 语言旨在尽可能防止常见的编程错误,并用于系统错误可能导致灾难性后果的关键系统

Ada 超越其他现代语言提供的典型内置安全性的几个例子:

  • 整数范围类型允许为整数指定允许的范围。超出此范围的任何值都将引发异常(在不支持范围类型的语言中,必须执行手动检查)。

  • :==用于相等检查的分配。这避免了=用于赋值和==相等性的语言在进行相等检查时意外分配的常见陷阱(在 Ada 中,意外分配不会编译)。

  • in以及out指定方法参数是否可以读取或写入的参数

  • 由于使用关键字,避免了语句组缩进级别的问题(例如最近的Apple SSL 错误)end

  • 合同(自 Ada 2012 起,之前在 SPARK 子集中)允许方法指定必须满足的前置条件和后置条件

安全手册(PDF)中提供了更多关于 Ada 是如何设计用于安全性的示例。

当然,许多这些问题可以通过适当的编码风格、代码审查、单元测试等来缓解,但是在语言级别完成它们意味着你可以免费获得它。

还值得补充的是,尽管为安全而设计的语言(如 Ada)消除了许多类别的错误,但仍然没有什么能阻止您引入该语言一无所知的业务逻辑错误。

实际上,大多数语言在缓冲区溢出方面都是“安全的”。一种语言在这方面“安全”所需要的是:严格类型、系统数组绑定检查和自动内存管理(“垃圾收集器”)。有关详细信息,请参阅此答案

从这个意义上说,一些旧语言并不“安全”,尤其是 C(和 C++),还有 Forth、Fortran……当然还有汇编。从技术上讲,可以编写一个“安全”的 C 实现,并且在形式上仍符合 C 标准,但代价高昂(例如,您必须进行free()无操作,因此分配了已分配的内存“永远”)。没有人这样做。

“安全”语言(关于缓冲区溢出)包括 Java、C#、OCaml、Python、Perl、Go,甚至 PHP。其中一些语言的效率足以实现 SSL/TLS(即使在嵌入式系统上——我是根据经验说话的)。虽然可以编写安全的 C 代码,但需要(很多)专注和技巧,而且经验反复表明这很难,即使是最好的开发人员也不能假装他们总是应用所需的注意力和能力水平。这是一次令人谦卑的经历。“不要使用 C,它很危险”的断言不受欢迎,不是因为它是错误的,而是恰恰相反,因为它是正确的:它迫使开发人员面对他们可能不是半神人的想法他们认为的编程,深深地隐藏在他们的灵魂深处。

但是请注意,这些“安全”语言并不能防止错误:缓冲区溢出仍然是不受欢迎的行为。但是它们包含了损害:缓冲区之外的内存实际上并没有被读取或写入;相反,有问题的线程会触发异常,并且(通常)被终止。在 heartbleed 的情况下,这可以避免漏洞成为漏洞,并且可能有助于防止我们在过去几天观察到的全面恐慌(没有人真正知道是什么让随机漏洞像 Youtube 一样传播开来)以韩国隐形马为特色的视频;但是,“从逻辑上讲”,如果它根本不是一个漏洞,那么这应该避免所有这些悲喜剧)。


编辑:由于在评论中讨论得很充分,我想到了C的安全内存管理问题,并且有一种仍然可以free()工作的解决方案,但是有一个作弊。

可以想象一个产生“胖指针”的 C 编译器。例如,在 32 位机器上,将指针设为 96 位值。每个分配的块都将被授予一个唯一的 64 位标识符(例如,一个计数器),并维护一个内部存储器结构(哈希表、平衡树...),它通过 ID 引用所有块。对于每个块,其长度也记录在结构中。然后,指针值是块 ID 和该块内的偏移量的串联。跟随指针时,通过ID定位块,将偏移量与块长度进行比较,然后才进行访问。此设置解决了双重释放和释放后使用问题。它还检测大多数缓冲区溢出(但不是全部:缓冲区可能是更大结构的一部分,并且malloc()/free() 管理只看到外部块)。

“作弊”是“唯一的 64 位计数器”。仅当您没有用完 64 位整数时,这才是正确的;除此之外,您必须重用旧值。64 位在实践中应该可以避免这个问题(“环绕”需要数年时间),但较小的计数器(例如 32 位)可能会成为问题。

此外,当然,内存访问的开销可能是不可忽略的(每次访问都会进行相当多的物理读取,尽管某些情况可能会被优化掉),对于指针丰富的结构,双倍指针大小也意味着更高的内存使用率. 我不知道有任何现有的 C 编译器应用这种策略;现在纯粹是理论上的。

当涉及到像 Heartbleed 这样的编程错误时,大多数比 C 更高级别的编程语言都更加安全。主要编译为机器码的示例包括 D、Rust 和 Ada。在我看来,仅仅谈论内存安全并不有趣。

以下是(我认为)使编写不安全代码变得更加困难的其他编程语言功能列表。前五个功能扩展了编译器推理代码的能力,所以,一个容易出错的人,不必*。此外,这些功能还应该使其他人(审计员)更容易推理您的代码。OpenSSL 的源代码通常被描述为一团糟,并且比 C 更严格的语言可能有助于使其更容易推理。最后两个特性是关于影响安全的上下文问题。

  • 严格的类型系统:更容易推理程序的正确性。消除某些输入攻击。
  • 默认情况下不可变:将不可变值作为主要数据容器意味着更容易推断程序的状态。
  • 禁用或受限的不安全性:不允许可怕的事情,例如指针算术(例如 Go),或者,至少只允许它包含在大的警告(Rust)中。请注意,完全缺乏指针运算的语言被排除在大量需要低级访问的应用程序中。
  • 编译时污点检查:扩展类型系统以允许识别受污染的值:基于输入以某种方式依赖的值。然后,编译器可以(有条件地)禁止带有污染值的操作,这些操作会将信息泄漏给外部观察者,例如在这样的值上进行分支。这可以防止或至少减轻某些类别的定时攻击。据我所知,这些仅在静态代码分析工具中可用,而不在编译器本身中?
  • 依赖类型:依赖类型是一种告诉编译器“这是一个值在 2 到 87 之间的 Int”或“这是一个最大长度为 12 的仅包含字母数字字符的字符串”的方法。未能满足这些要求会导致编译失败,而不是可能导致不安全结果的运行时失败。此功能在Idris和一些定理证明器语言中可用。
  • 没有垃圾收集:垃圾收集是语言安全的一个大问题——它会在你的程序中造成垃圾收集暂停。这些暂停会泄漏有关程序状态的信息,并允许发生计时攻击。然而,当垃圾收集器被调用时,作为开发人员是不可能(或者充其量是难以置信地难以预测),并且即使是最少量的代码更改也会发生巨大的变化。
  • 性能、可移植性和互操作性:如果您需要一个仅在 PowerPC 平台上运行的安全且缓慢的程序,但不要指望其他人将其用于跨平台 TLS 库,这可能会很好。OpenSSL 之所以受欢迎,正是因为它速度快,可以在任何地方运行,从基于 MIPS 的路由器到大规模并行的 SPARC 服务器,以及介于两者之间的一切。此外,世界上任何程序或运行时都可以作为库与 OpenSSL 交互,因为它使用 C 调用约定。

从我有限的语言知识来看,没有一种语言能做到所有这些。Rust是一种涵盖多种语言的示例 - 它是严格的、默认情况下不可变的、限制了不安全性、不需要垃圾收集并且非常高效且可移植。不幸的是,编译时污点检查和依赖类型目前似乎是需要额外静态代码分析工具或新语言的奇异特性。

* 参见:形式验证

按照您所要求的总体精神,我认为E 语言(“安全的分布式纯对象平台和 p2p 脚本语言”)非常有趣,因为它试图安全地提供通常不可用的功能/计算模型.