接上文:《深入考察解释型语言背后隐藏的攻击面,Part 1(上)》《深入考察解释型语言背后隐藏的攻击面,Part 1(下)》本系列第一篇关于解释型语言的底层攻击面,我们了解到,即使在Javascript、Python和Perl等解释型语言的核心实现中,内存安全也并非无懈可击。在本文中,我们将深入了解在基于C/C++的库通过外部函数接口(FFI)与解释型语言“粘合”期间如何出现安全漏洞。正如我们之前讨论的,FFI充当用两种不同语言编写的代码之间的接口。例如,使基于C的库可用于Javascript程序。FFI负责将编程语言A的对象翻译成编程语言B可用的对象,反之亦然。为了实现这种翻译,开发人员必须编写特定于语言的API代码以在两种语言之间来回转换。这通常也称为编写语言绑定。从攻击者的角度来看,外部语言绑定代表了一个可能的攻击面。在处理从内存安全语言转换为内存不安全语言(如C/C++)的FFI时,开发人员有可能引入内存安全漏洞。即使高级语言被认为是内存安全的并且目标外部代码已经过严格的安全审查,但在弥合两种语言之间差距的代码中仍可能潜伏着可利用的漏洞。在本文中,我们将仔细研究两个此类漏洞,并逐步研究攻击者如何评估您代码的可利用性。本文旨在提高读者对exploit开发过程的理解,不只是针对具体案例,而是从概念的角度。通过了解漏洞利用开发人员如何看待您的代码,它可以帮助您养成防御性编程习惯并编写更安全的代码。在我们的案例研究中,我们将检查两个看起来非常相似的错误,但只有一个是错误,另一个是安全漏洞。两者都存在于绑定Node.js包的C/C++代码中。node-sassNode-sass是一个将Node.js绑定到LibSass(Sass的C版本,一种流行的样式表预处理器)的库。node-sass虽然最近被弃用了,但它每周的下载量仍然超过500万,因此是一个非常有价值的审计对象。在阅读node-sass绑定时,我们注意到以下代码模式:intindent_len=Nan::ToNan::Get(options,Nan::New("indentWidth").ToLocalChecked()).ToLocalChecked()).FromJust();[1]ctx_w->indent=(char*)malloc(indent_len+1);strcpy(ctx_w->indent,std::string([2]indent_len,Nan::ToNan::Get(选项,Nan::New("indentType").ToLocalChecked()).ToLocalChecked()).FromJust()==1?'\t':''在[1]处,我们注意到一个由用户输入Integer值控制的32位值用于内存分配。如果此用户提供的整数为-1,则整数算术表达式indent_len+1的值变为0。在[2]处,原始负值用于创建制表符或空格字符串,其中indent_len值是负数,现在将变成一个相当大的正值,因为std::string构造函数需要一个类型为size_t的无符号长度参数。在JSAPI级别,我们注意到indentWidth的检索方法如下:/***Getindentwidth**@param{Object}options*@apiprivate*/functiongetIndentWidth(options){varwidth=parseInt(options.indentWidth)||2;returnwidth>10?2:width;}这里的目的是保证indentWidth>=2or<=10,但实际上这里只是检查了上限,parseInt允许我们提供负值,例如:varsass=require('node-sass')varresult=sass.renderSync({data:`h1{font-size:40px;}`,indentWidth:-1});这将触发整数溢出,导致分配的内存不足,并进一步导致后续的内存损坏。要解决此问题,node-sass应确保在将用户提供的indentWidth值传递给基础绑定之前检查下限和上限。彻底检查输入并将它们的值范围明确限制在对程序逻辑有意义的范围内,将很好地帮助您养成一般的防御性编程习惯。那么我们来总结一下。这里的错误模式是什么?整数溢出,导致堆分配不足,随后的内存填充会破坏相邻的堆内存。听起来像是一个值得分配的CVE,不是吗?然而,虽然这个整数溢出确实导致堆分配不足,但这个错误并不一定代表漏洞,因为样式表输入可能不受攻击者控制,并且在任何情况下都会抛出std::string异常,直到堆损坏发生。即使发生堆损坏,也只是非常有限的控制覆盖(借助具有非常大indent_len的制表符或空格字符),因此实际利用的可能性非常低。anticomputer@dc1:~$nodesass.jsterminate在抛出'std::length_error'what():basic_string::_S_createAborted(coredumped)实例后调用结论:只是一个错误。那么,在什么情况下攻击者会对这样的漏洞感兴趣呢?攻击者将能够影响触发错误的输入。在这种情况下,某人不太可能向node-sass绑定提供攻击者控制的输入。同时,内存破坏原语本身的控制能力也会非常有限。虽然在某些情况下,即使是非常有限的堆损坏也足以利用漏洞,但通常攻击者更愿意寻求一种情况,在这种情况下,他们可以控制用于损坏内存的内容,或者可以控制内存被覆盖。最好两者兼有。在这种情况下,即使std::string构造函数没有退出,攻击者也必须使用空格或制表符进行大量覆盖才能获得对进程的控制。虽然这并非完全不可能,但只要对周围的内存布局有足够的影响力和控制力,这种可能性仍然很小。在这种情况下,我们通常可以通过回答以下三个问题来进行简单的可利用性“嗅觉测试”:攻击者是如何触发漏洞的?攻击者控制了哪些数据,控制到什么程度??哪些算法受到攻击者的控制?除此之外,可利用性在很大程度上取决于攻击者的目标、经验和资源。我们可能对此一无所知。除非您花费大量时间实际编写漏洞利用程序,否则很难确定问题是否可利用。特别是当您的代码被其他软件使用时,即您正在编写库代码或更大系统中的组件。在孤立环境中看似错误的情况可能是更大规模的安全漏洞。虽然常识在确定可利用性方面大有帮助,但任何可由用户控制(或影响)输入、时间和资源允许的错误触发,都是潜在的安全漏洞,因此,应将安全漏洞视为非常明智的.png-img对于我们的第二个案例研究,我们将检查GHSL-2020-142。该错误存在于提供libpng绑定的node.jspng-img包中。当加载PNG图像进行处理时,png-img绑定将使用PNGIMG::InitStorage函数来分配用户提供的PNG数据所需的初始内存。voidPngImg::InitStorage_(){rowPtrs_.resize(info_.height,nullptr);[1]data_=newpng_byte[info_.height*info_.rowbytes];[2]for(size_ti=0;i
