如何让Node.js使用系统OpenSSL而非内置版本(无需源码编译)
问题描述
我需要通过OpenSSL的SSL_CTX_load_verify_locations函数注入自定义证书,覆盖TLS连接使用的证书。该方案在Python应用中通过LD_PRELOAD运行时覆盖OpenSSL函数已成功实现,但在Node.js中无效——原因是Node.js内置OpenSSL而非依赖动态链接库,导致LD_PRELOAD方法失效。
用于覆盖SSL_CTX_load_verify_locations的C代码
#define _GNU_SOURCE #include <dlfcn.h> #include <openssl/ssl.h> #include <stdio.h> // 保存原函数指针 int (*original_SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath) = NULL; // 自定义覆盖函数 int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath) { // 加载原函数 if (!original_SSL_CTX_load_verify_locations) { original_SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *)) dlsym(RTLD_NEXT, "SSL_CTX_load_verify_locations"); const char *error = dlerror(); if (error != NULL) { fprintf(stderr, "加载原SSL_CTX_load_verify_locations函数失败: %s\n", error); return -1; } } const char *my_CAfile = "/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/keploy.crt"; printf("\n\n替换CAfile为 %s\n\n", my_CAfile); // 调用原函数并传入自定义证书 return original_SSL_CTX_load_verify_locations(ctx, my_CAfile, CApath); }
编译命令
clang-14 -I/usr/include/openssl -L/usr/lib/aarch64-linux-gnu -fPIC -shared -o ssl_override.so ssl_override.c -lssl -lcrypto
Node.js测试代码
const express = require('express'); const axios = require('axios'); const fs = require('fs'); const https = require('https'); const app = express(); const CERTIFICATE_PATH = '/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/ninja.crt'; const httpsAgent = new https.Agent({ ca: fs.readFileSync(CERTIFICATE_PATH), keepAlive: false, }); app.get('/catFact', async (req, res) => { try { const response = await axios.get('https://catfact.ninja/fact', { httpsAgent }); res.status(200).json(response.data); } catch (error) { res.status(500).json({ error: 'SSL证书验证失败', details: error.message }); } }); const PORT = 8080; app.listen(PORT, () => { console.log(`服务器运行在 http://0.0.0.0:${PORT}`); });
Node.js运行命令
LD_PRELOAD=/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/tls-change-cert/ssl_override.so node index.js
已尝试的解决方法
- LD_PRELOAD加载多库:同时指定系统OpenSSL库和自定义覆盖库
export LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu:$LD_LIBRARY_PATH export LD_PRELOAD="/usr/lib/aarch64-linux-gnu/libssl.so.3 \ /usr/lib/aarch64-linux-gnu/libcrypto.so.3 \ /Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/tls-change-cert/ssl_override.so" node index.js - 用lsof验证库加载:确认自定义覆盖库和系统OpenSSL库已加载到Node.js进程
➜ lsof -p $(pidof node) | grep ssl node 507762 gouravkumar mem REG 0,40 70040 /path/to/ssl_override.so node 507762 gouravkumar mem REG 254,1 737192 /usr/lib/aarch64-linux-gnu/libssl.so.3 - 结果:虽然库已加载,但覆盖函数中的printf语句未触发,说明Node.js仍在使用内置OpenSSL实现。
求助问题
- 如何强制Node.js使用系统安装的OpenSSL共享库(如libssl.so.3和libcrypto.so.3)而非内置版本?
- 是否无需重新编译Node.js源码,就能让其动态加载系统OpenSSL?
- 是否有已知的workaround或配置(如设置特定环境变量)可实现该需求?
Node.js版本信息
➜ tls-support git:(main) ✗ node -p "process.versions" { node: '18.20.4', acorn: '8.11.3', ada: '2.7.8', ares: '1.28.1', base64: '0.5.2', brotli: '1.0.9', cjs_module_lexer: '1.2.2', cldr: '44.1', icu: '74.2', llhttp: '6.1.1', modules: '108', napi: '9', nghttp2: '1.61.0', nghttp3: '0.7.0', ngtcp2: '1.3.0', openssl: '3.0.13+quic', simdutf: '5.2.4', tz: '2024a', undici: '5.28.4', unicode: '15.1', uv: '1.44.2', uvwasi: '0.0.19', v8: '10.2.154.26-node.37', zlib: '1.3.0.1-motley' }
解决方案
核心结论
官方预编译的Node.js版本是静态链接内置OpenSSL的,无法通过LD_PRELOAD强制切换到系统OpenSSL,必须通过重新编译Node.js实现动态链接系统库。不过有两种替代方案可以绕开编译需求:
方案1:利用Node.js内置配置实现证书注入
不需要Hook OpenSSL函数,直接通过Node.js的原生机制覆盖CA证书:
- 全局生效:设置环境变量
NODE_EXTRA_CA_CERTS指向自定义证书文件,所有HTTPS请求会自动加载该证书进行验证:NODE_EXTRA_CA_CERTS=/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/keploy.crt node index.js - 细粒度控制:如果只需要针对特定请求生效,可在
https.Agent或axios配置中指定ca参数,就像你代码中已经实现的逻辑。
方案2:使用动态链接系统OpenSSL的Node.js版本
如果一定要用LD_PRELOAD的Hook方案,需要使用动态链接版本的Node.js:
- 通过包管理器安装:部分Linux发行版(如Debian、Ubuntu)的官方包仓库提供的Node.js是动态链接系统OpenSSL的,直接通过
sudo apt install nodejs安装即可。 - 用nvm编译安装指定版本:如果需要特定版本的Node.js,可使用nvm的
--shared-openssl参数从源码编译:
编译完成后,Node.js会动态链接系统的OpenSSL库,此时LD_PRELOAD的Hook就能正常拦截nvm install 18.20.4 --shared-opensslSSL_CTX_load_verify_locations调用。
为什么之前的LD_PRELOAD尝试失败?
官方预编译的Node.js将OpenSSL代码直接静态编译进了二进制文件,进程中加载的系统libssl.so.3只是被你的Hook库引用,Node.js本身不会调用动态库中的OpenSSL函数,因此覆盖函数的printf不会触发。只有当Node.js动态链接系统OpenSSL时,相关函数才会从动态库加载,LD_PRELOAD的Hook才能生效。
内容的提问来源于stack exchange,提问作者Gourav Kumar
相关产品推荐
相关产品推荐

