好文!学习了!
前几天我在代码审计知识星球里发表了一个介绍nmap利用interactive
模式提权的帖子:
# 进入nmap的交互模式
nmap --interactive
# 执行sh,提权成功
!sh
但具体实施的时候会遇到很多有趣的问题,我们来详细研究一下。
suid提权
说到这个话题,我们不得不先介绍一下两个东西:
- suid提权是什么
- nmap为什么可以使用suid提权
通常来说,Linux运行一个程序,是使用当前运行这个程序的用户权限,这当然是合理的。但是有一些程序比较特殊,比如我们常用的ping命令。
ping需要发送ICMP报文,而这个操作需要发送Raw Socket。在Linux 2.2引入CAPABILITIES前,使用Raw Socket是需要root权限的(当然不是说引入CAPABILITIES就不需要权限了,而是可以通过其他方法解决,这个后说),所以你如果在一些老的系统里ls -al $(which ping)
,可以发现其权限是-rwsr-xr-x
,其中有个s位,这就是suid:
root@linux:~# ls -al /bin/ping
-rwsr-xr-x 1 root root 44168 May 7 2014 /bin/ping
suid全称是Set owner User ID up on execution。这是Linux给可执行文件的一个属性,上述情况下,普通用户之所以也可以使用ping命令,原因就在我们给ping这个可执行文件设置了suid权限。
设置了s位的程序在运行时,其Effective UID将会设置为这个程序的所有者。比如,/bin/ping
这个程序的所有者是0(root),它设置了s位,那么普通用户在运行ping时其Effective UID就是0,等同于拥有了root权限。
这里引入了一个新的概念Effective UID。Linux进程在运行时有三个UID:
- Real UID 执行该进程的用户实际的UID
- Effective UID 程序实际操作时生效的UID(比如写入文件时,系统会检查这个UID是否有权限)
- Saved UID 在高权限用户降权后,保留的其原本UID(本文中不对这个UID进行深入探讨)
通常情况下Effective UID和Real UID相等,所以普通用户不能写入只有UID=0号才可写的/etc/passwd
;有suid的程序启动时,Effective UID就等于二进制文件的所有者,此时Real UID就可能和Effective UID不相等了。
有的同学说某某程序只要有suid权限,就可以提权,这个说法其实是不准确的。只有这个程序的所有者是0号或其他super user,同时拥有suid权限,才可以提权。
nmap为什么可以用suid提权
常用nmap的同学就知道,如果你要进行UDP或TCP SYN扫描,需要有root权限:
$ nmap -sU target
You requested a scan type which requires root privileges.
QUITTING!
$ nmap -sS 127.0.0.1
You requested a scan type which requires root privileges.
QUITTING!
原因就是这些操作会用到Raw Socket。
有时候你不得不使用sudo来执行nmap,但在脚本调用nmap时sudo又需要tty,有可能还要输入密码,这个限制在很多情况下会造成一些不必要的麻烦。
所以,有一些管理员会给nmap加上suid权限,这样普通用户就可以随便运行nmap了。
当然,增加了s位的nmap是不安全的,我们可以利用nmap提权。在nmap 5.20以前存在interactive交互模式,我们可以通过这个模式来提权:
星球里@A11risefor*师傅提到,nmap 5.20以后可以通过加载自定义script的方式来执行命令:
补充一个,--interactive应该是比较老版本的nmap提供的选项,最近的nmap上都没有这个选项了,不过可以写一个nse脚本,内容为
os.execute('/bin/sh')
,然后nmap --script=shell.nse
来提权
的确是一个非常及时的补充,因为现在大部分的nmap都是没有interactive交互模式了。
但经过测试我们发现,这个方法启动的shell似乎仍然是当前用户的,并没有我们想象中的提权。
Linux发行版与shell
我曾使用interactive模式提权成功,但是因为那个nmap版本过老,没有script支持,所以没法测试script的提权方法;同样,新的nmap支持script但又没有interactive模式,无法做直观对比,我只能先猜想提权失败的原因:
- nmap在高版本中限制了suid权限
- lua脚本中限制了suid权限
- 新版Linux系统对子进程的suid权限进行了限制
这些猜想中变量太多,所以我需要控制一下。首先我阅读了老版本nmap的源码,发现其实!sh
执行的就是很简单的system('sh')
,而且前面并没用丢弃Effective UID权限的操作:
} else if (*myargv[0] == '!') {