大数跨境
0
0

开源文件管理库 E2fsprogs 被曝严重的远程代码执行漏洞

开源文件管理库 E2fsprogs 被曝严重的远程代码执行漏洞 代码卫士
2020-01-13
1
导读:漏洞已修复

聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士团队
E2fsprogs 是和 ext2ext3 以及 ext4 文件系统交互的行之有效的程序集,被认为是 linux unix 类操作系统的关键软件。因此在默认情况下,它配置于多数 linux 发行版本上。攻击者可利用该漏洞引发界外栈写入,从而在受害者机器上执行远程代码。
E2fsck进制位于 e2fsprogs中,用于修复已损坏的ext234文件系统。E2fsprogs E2fsck 1.45.4 的目录rehash 功能中存在一个代码执行漏洞。特别构造的ext4 目录可引发栈界外读取,从而导致代码执行后果。攻击者可通过损坏分区的方式触发该漏洞。
E2fsprogs1.43.3-1.45.3 版本均受该漏洞影响。该漏洞(CVE-2019-5188)CVSv3 评分为7.5分。20191218日,思科将问题告知厂商,后者于1220日将问题修复。

详情分析

ext234 的实现中存在很多用于优化磁盘文件大小的数据结构。因此,哈希树用于在目录中唯一映射所有文件。负责该哈希表格的fill_dir_struct 结构和哈希项目如下:
struct fill_dir_struct {      char *buf;      struct ext2_inode *inode;      ext2_ino_t ino;      errcode_t err;      e2fsck_t ctx;      struct hash_entry *harray;  // [1]     int max_array, num_array;   // [2]     unsigned int dir_size;      int compress;       ino_t parent;      ext2_ino_t dir;   };

其中最重要的是位于 [1] 处的 hash_entry 结构(包含哈希树所有的哈希条目)和位于[2] 处的num_array(包含哈希条目的数量)。为了维护哈希表格的完整性和假设,事实上所有的条目应当都是唯一的(例如位于目录中的两个单独文件无法共享相同的名称)。为了执行这个唯一性要求,当e2fsck检测到碰撞时,将会根据已存在的副本数量在第二个文件名称之后附加{~0, ~1, ..., ~n}用于该进程的结构如下:
struct hash_entry {          ext2_dirhash_t  hash;       ext2_dirhash_t  minor_hash;       ino_t       ino;       struct ext2_dir_entry   *dir;   };   
#define EXT2_NAME_LEN 255
struct ext2_dir_entry { __u32 inode; /* Inode number */ __u16 rec_len; /* Directory entry length */ __u16 name_len; /* Name length */ char name[EXT2_NAME_LEN]; /* File name */ };
对于活跃的重复数据删除进程,在fill_dir_struct 上调用duplicate_search_and_fix,包含如下哈希树:
static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs,                           ext2_ino_t ino,                           struct fill_dir_struct *fd  )   {          struct problem_context  pctx;          struct hash_entry   *ent, *prev;          int         i, j;          int         fixed = 0;          char            new_name[256];         // [1]       unsigned int        new_len;                int         hash_alg;          int hash_flags = fd->inode->i_flags & EXT4_CASEFOLD_FL;      
[...] for (i=1; i < fd->num_array; i++) { // [2] ent = fd->harray + i; prev = ent - 1;
[...]
new_len = ext2fs_dirent_name_len(ent->dir); // [3] memcpy(new_name, ent->dir->name, new_len); mutate_name(new_name, &new_len); // [4]
} } [...]

[1]处,我们通过临时的栈缓冲区来临时存储任何发生变化的名称,以便之后被写入磁盘,而在[2]处,我们可以在每个hash_entry 结构上看到该迭代。当文件名称之间存在碰撞时,当前文件名称的长度被在[3] 处抓取(entry->name_len& 0xff;)重要的是,注意ext2_dir_entry 结构的name_len 字段直接从磁盘读取,而非源自ext2_dir_entry->name之后,我们输入mutate_name [4],而变化正是发生在此处:
static void mutate_name(char *str, unsigned int *len) {       int i;       unsigned int l = *len;   
for (i = l-1; i > 0; i--) { // [1] if (!isdigit(str[i])) break; }
if ((i == (int)l - 1) || (str[i] != '~')) { // [2] if (((l-1) & 3) < 2) l += 2; else l = (l+3) & ~3; // [3] str[l-2] = '~'; // [4] str[l-1] = '0'; // [5] *len = l; return; }
for (i = l-1; i >= 0; i--) {
[..] }

[1][2] 处的首次检查用于查看该文件是否已发生变化,但处于我们自身目的的考虑,我们仅关心我们的文件名称中并未包含任何数字,因此当[1] 处的循环存在时,int I 被设为-1[2]处,假设是文件已经以~x 结尾 (其中 x == digit),但如果我们文件的长度是 0x0,由于 int 1 当前为-1,则 (i == (int)l - 1) 为真。之后我们到达第 [3] 处,由于 (0 + 3)& ~3 => 0,不会执行任何动作,而在第[4]处和第[5]处,l0由于l仍然是0而且也是一个非签名证书,因此用于数组索引时,该值下溢。
   0x00000000004422e4 <+180>:   mov    eax,DWORD PTR [rbp-0x18]      0x00000000004422e7 <+183>:   add    eax,0x3      0x00000000004422ea <+186>:   and    eax,0xfffffffc      0x00000000004422ed <+189>:   mov    DWORD PTR [rbp-0x18],eax      0x00000000004422f0 <+192>:   mov    rax,QWORD PTR [rbp-0x8]      0x00000000004422f4 <+196>:   mov    ecx,DWORD PTR [rbp-0x18]      0x00000000004422f7 <+199>:   sub    ecx,0x2                   // [1]    0x00000000004422fa <+202>:   mov    ecx,ecx      0x00000000004422fc <+204>:   mov    edx,ecx      0x00000000004422fe <+206>:   mov    BYTE PTR [rax+rdx*1],0x7e // [2]    0x0000000000442302 <+210>:   mov    rax,QWORD PTR [rbp-0x8]      0x0000000000442306 <+214>:   mov    ecx,DWORD PTR [rbp-0x18]      0x0000000000442309 <+217>:   sub    ecx,0x1                   // [3]    0x000000000044230c <+220>:   mov    ecx,ecx      0x000000000044230e <+222>:   mov    edx,ecx      0x0000000000442310 <+224>:   mov    BYTE PTR [rax+rdx*1],0x30 // [4] 

[1][2] 对应于str[l-2]= '~';,而[3][4]对应于str[l-1] = '0';,导致界外栈写入~0对于实际的利用,由于在64位系统上索引被预先截断为ecx,因此我们看到的行为和在32位系统上不同。在这两种情况下,最终结果都是new_name[0xFFFFFFFF]= '0'new_name[0xFFFFFFFE] = '~',但在64位系统上,利用是不可能的:
// sample 64-bit address space.0x00007f64b9062000 0x00007f64b9063000 0x0000000000000000 rw-0x00007ffc69ed5000 0x00007ffc69ef7000 0x0000000000000000 rw- [stack]   // [1]0x00007ffc69fba000 0x00007ffc69fbc000 0x0000000000000000 r-x [vdso]0x00007ffc69fbd000 0x00007ffc69fbe000 0x0000000000000000 r-x0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

在默认情况下,该栈的大小不足以使其运作,如[1]所示;有必要通过受用户控制的方法来将栈扩展到足够大使其不至于崩溃,同时在主机本身进行不常见的配置,使栈扩展发生 (ulimit-s unlimited)
对于32位系统,最终结果更依赖于二进制本身以及它是如何编译的。如下所示:
static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs,                       ext2_ino_t ino,                       struct fill_dir_struct *fd)   {       struct problem_context  pctx;       struct hash_entry   *ent, *prev;       int         i, j;       int         fixed = 0;       char            new_name[256];       unsigned int        new_len;       int         hash_alg;       int hash_flags = fd->inode->i_flags & EXT4_CASEFOLD_FL;  

如果我们下溢 new_name [256] 栈变量,则受影响的目标变量完全取决于编译器,因此可利用性取决于二进制。

崩溃信息

Program received signal SIGSEGV, Segmentation fault.   ─── registers ────   $rax   : 0x000010001ffff6b1  →  0x0000000000000000   $rbx   : 0x00007fffffffb6e0  →  0x000000000000000c   $rcx   : 0x00000000fffffffe  →  0x0000000000000000   $rdx   : 0x00000000fffffffe  →  0x0000000000000000   $rsp   : 0x00007fffffffb210  →  0x000000000072c930  →  <ext2fs_block_iterate3+0> push rbp   $rbp   : 0x00007fffffffb4d0  →  0x00007fffffffbc10  →  0x00007fffffffc1d0  →  0x00007fffffffc570  →  0x00007fffffffc990  →  0x00007fffffffcad0  →  0x00007fffffffe280  →  0x00000000009814c0   $rsi   : 0x8000ffffb58e   $rdi   : 0x8000ffffb58e   $rip   : 0x000000000069764b  →  <mutate_name+1435> mov cl, BYTE PTR [rax+0x7fff8000]   $r8    : 0xff   $r9    : 0x00007ffff6f8db01  →  <__memrchr_avx2+721> ret 0x8520   $r10   : 0x0   $r11   : 0x0   $r12   : 0x0   $r13   : 0x000010007fff7901  →  0x0000000000000000   $r14   : 0x1   $r15   : 0x1   $eflags: [zero CARRY PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]   $cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000   ──── stack ────   0x00007fffffffb210│+0x0000: 0x000000000072c930  →  <ext2fs_block_iterate3+0> push rbp    ← $rsp   0x00007fffffffb218│+0x0008: 0x00000000004c6389  →  <__asan::GetCurrentThreadStats()+9> test rax, rax   0x00007fffffffb220│+0x0010: 0x0000c00040404040 ("@@@@"?)   0x00007fffffffb228│+0x0018: 0x4040404040404040   0x00007fffffffb230│+0x0020: 0x5df2ae5f40404040   0x00007fffffffb238│+0x0028: 0x00000010000040400x0000000000000000   0x00007fffffffb240│+0x0030: 0x4040404000000000   0x00007fffffffb248│+0x0038: 0x0000000000000000   ─── code:x86:64 ────        0x69763b <mutate_name+1419> call   0x4f85a0 <__ubsan::__ubsan_handle_type_mismatch_v1(__ubsan::TypeMismatchData*,  __ubsan::ValueHandle)>        0x697640 <mutate_name+1424> mov    rax, QWORD PTR [rbp-0xd8]        0x697647 <mutate_name+1431> shr    rax, 0x3   =>   0x69764b <mutate_name+1435> mov    cl, BYTE PTR [rax+0x7fff8000]        0x697651 <mutate_name+1441> cmp    cl, 0x0        0x697654 <mutate_name+1444> mov    BYTE PTR [rbp-0xe1], cl        0x69765a <mutate_name+1450> je     0x697687 <mutate_name+1495>        0x697660 <mutate_name+1456> mov    rax, QWORD PTR [rbp-0xd8]        0x697667 <mutate_name+1463> and    rax, 0x7   ─── source:rehash.c+329 ────       324         if ((i == (int)l - 1) || (str[i] != '~')) {       325                 if (((l-1) & 3) < 2)       326                         l += 2;       327                 else       328                         l = (l+3) & ~3;              // str=0x00007fffffffb4b0  →  [...]  →  0x0000c00040404040 ("@@@@"?), l=0x0   329                 str[l-2] = '~';       330                 str[l-1] = '0';       331                 *len = l;       332                 return;       333         }       334         for (i = l-1; i >= 0; i--) {   ── threads ────   [#0] Id 1, Name: "asan_e2fsck_1_4", stopped 0x69764b in mutate_name (), reason: SIGSEGV   ── trace ────   [#0] 0x69764b → mutate_name(str=0x7fffffffb590 "H\n", len=0x7fffffffb6d0)   [#1] 0x68c0d3 → duplicate_search_and_fix(ctx=0x619000000080, fs=0x613000000040, ino=0x2d, fd=0x7fffffffbce0)   [#2] 0x68534f → e2fsck_rehash_dir(ctx=0x619000000080, ino=0x2d, pctx=0x7fffffffc200)   [#3] 0x6960e9 → e2fsck_rehash_directories(ctx=0x619000000080)   [#4] 0x5f36a5 → e2fsck_pass3(ctx=0x619000000080)   [#5] 0x52e91b → e2fsck_run(ctx=0x619000000080)   [#6] 0x506e8f → main(argc=0x3, argv=0x7fffffffe368)   --------------------------------------------   0x000000000069764b in mutate_name (str=0x7fffffffb590 "H\n", len=0x7fffffffb6d0) at rehash.c:329   329                     str[l-2] = '~';   

 

来不?一起玩耍!



推荐阅读

开源计算机视觉库 OpenCV 被曝两个严重的任意代码执行漏洞(详情)

2020年1月1日起,谷歌 Patch Rewards 计划将降低准入门槛,提升开源项目的安全性

突发:俄罗斯警方突袭开源 Web 服务器 NGINX 的莫斯科代表处,拘留两名联合创始人



原文链接
https://talosintelligence.com/vulnerability_reports/TALOS-2019-0973





题图:Pixabay License



本文由奇安信代码卫士编译,不代表奇安信观点,转载请注明“转自奇安信代码卫士 www.codesafe.cn”



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的产品线。



 点个“在看”,bounty 多多~                                          

                                                


【声明】内容源于网络
0
0
代码卫士
奇安信代码卫士是国内第一家专注于软件开发安全的产品线,产品涵盖代码安全缺陷检测、软件编码合规检测、开源组件溯源检测三大方向,分别解决软件开发过程中的安全缺陷和漏洞问题、编码合规性问题、开源组件安全管控问题。本订阅号提供国内外热点安全资讯。
内容 3434
粉丝 0
代码卫士 奇安信代码卫士是国内第一家专注于软件开发安全的产品线,产品涵盖代码安全缺陷检测、软件编码合规检测、开源组件溯源检测三大方向,分别解决软件开发过程中的安全缺陷和漏洞问题、编码合规性问题、开源组件安全管控问题。本订阅号提供国内外热点安全资讯。
总阅读766
粉丝0
内容3.4k