源码分析:Linux共享库安全风险剖析 之 运行时加载顺序风险
字数 1174 2025-08-15 21:30:31
Linux共享库运行时加载顺序安全风险分析
一、概述
在Linux开发中,共享库(so)的加载顺序是一个重要但容易被忽视的安全问题。与Windows不同,Linux的可执行程序不会优先从当前目录加载共享库,而是遵循特定的搜索顺序。这种加载顺序机制可能导致安全风险,特别是当攻击者能够控制某些优先搜索路径时,可能实现库劫持攻击。
二、Linux共享库运行时加载顺序
Linux共享库的运行时加载顺序如下:
- 环境变量LD_LIBRARY_PATH指定的路径
- 链接时-rpath指定的共享库查找路径
- ldconfig配置文件ld.so.conf指定的路径
- /lib目录
- /usr/lib目录
详细验证过程
1. 实验准备
创建6个不同版本的共享库,每个库输出不同的标识信息:
// a1.c - 验证LD_LIBRARY_PATH
#include <stdio.h>
void myprint() { printf("Hello a1[LD_LIBRARY_PATH]\n"); }
// a2.c - 验证rpath
#include <stdio.h>
void myprint() { printf("Hello a2[rpath]\n"); }
// a3.c - 验证ld.so.conf
#include <stdio.h>
void myprint() { printf("Hello a3[ld.so.conf]\n"); }
// a4.c - 验证/lib和/usr/lib
#include <stdio.h>
void myprint() { printf("Hello a4[/lib--/usr/lib]\n"); }
// a5.c - 验证PATH
#include <stdio.h>
void myprint() { printf("Hello a5 PATH\n"); }
// a6.c - 验证本地目录
#include <stdio.h>
void myprint() { printf("Hello a6 local\n"); }
主程序main.c:
#include <stdio.h>
extern void myprint();
int main() {
myprint();
return 0;
}
2. 编译和设置
# 创建测试目录
mkdir dir_ld_conf dir_path dir_ld_path dir_rpath
# 编译各版本共享库
gcc -shared -o ./dir_ld_path/liba.so a1.c # LD_LIBRARY_PATH测试
gcc -shared -o ./dir_rpath/liba.so a2.c # rpath测试
gcc -shared -o ./dir_ld_conf/liba.so a3.c # ld.so.conf测试
sudo gcc -shared -o /lib/liba.so a4.c # /lib测试
gcc -shared -o ./dir_path/liba.so a5.c # PATH测试
gcc -shared -o liba.so a6.c # 本地目录测试
# 设置环境变量
export LD_LIBRARY_PATH=/home/user/pro/dir_ld_path
export PATH=/home/user/pro/dir_path:$PATH
# 配置ld.so.conf
sudo vim /etc/ld.so.conf # 添加/home/user/pro/dir_ld_conf
sudo ldconfig
# 编译主程序(带rpath)
gcc -o main main.c -L. -la -Wl,-rpath,dir_rpath
3. 验证结果
-
初始运行:
./main # 输出: Hello a1[LD_LIBRARY_PATH] -
删除dir_ld_path后:
./main # 输出: Hello a2[rpath] -
删除dir_rpath后:
./main # 输出: Hello a3[ld.so.conf] -
删除dir_ld_conf后:
./main # 输出: Hello a4[/lib--/usr/lib] -
删除/lib/liba.so后:
./main # 输出错误: liba.so: cannot open shared object file
结论:PATH环境变量和本地目录不会影响共享库的加载顺序。
三、源码分析
Glibc中的_dl_map_object函数(位于dl-load.c)实现了共享库的加载逻辑:
struct link_map * _dl_map_object (struct link_map *loader,
const char *name,
int type,
int trace_mode,
int mode,
Lmid_t nsid)
关键逻辑:
-
处理RPATH与RUNPATH:
- RPATH是旧式编译器使用的方式
- RUNPATH是新式编译器支持的方式
- 可通过
--enable-new-dtags/--disable-new-dtags控制
-
加载顺序决策:
if(对象没有RUNPATH) { if (对象有RPATH) { 使用RPATH } else { 递归查找加载者(loader)的RPATH(或者有RUNPATH退出) } if(可执行程序没有RUNPATH) { 使用可执行程序的RPATH } } 查找LD_LIBRARY_PATH 查找正被加载对象的RUNPATH 查找ld.so.cache 查找默认路径
四、安全风险分析
-
LD_LIBRARY_PATH风险
- 影响范围全局
- 可能影响其他应用程序运行
- 常用于调试,但生产环境应避免
-
-rpath链接选项
- 程序生成时指定,运行时难以修改
- 风险较低
-
ld.so.conf配置文件风险
- 与LD_LIBRARY_PATH类似
- 全局影响,可能被恶意利用
-
/lib和/usr/lib风险
- 需要root权限才能修改
- 不同Linux系统库位置有差异,需精确定位防御
五、防御建议
- 避免过度使用LD_LIBRARY_PATH
- 严格控制ld.so.conf的修改权限
- 对系统库目录(/lib, /usr/lib)实施严格的权限控制
- 使用
--disable-new-dtags确保RPATH优先于RUNPATH(如需更严格的控制) - 定期检查系统库完整性
- 使用工具监控共享库加载行为
六、补充说明
- 使用
readelf -d可查看程序的RPATH或RUNPATH设置 - 较新编译器默认使用RUNPATH
- 旧式编译器或使用
--disable-new-dtags时,RPATH可能优先于LD_LIBRARY_PATH
通过理解Linux共享库的加载顺序机制,开发者和系统管理员可以更好地防范潜在的库劫持攻击,确保系统安全。