WinRAR目录穿越漏洞复现及分析

漏洞背景

Extracting a 19 Year Old Code Execution from WinRAR

Check Point的安全研究团队检测发现WinRAR的四个安全漏洞,分别为

ACE文件验证逻辑绕过漏洞(CVE-2018-20250)
ACE文件名逻辑验证绕过漏洞(CVE-2018-20251)
ACE/RAR文件越界写入漏洞(CVE-2018-20252)
LHA/LZH文件越界写入漏洞(CVE-2018-20253)

该漏洞是由于 WinRAR 所使用的一个陈旧的动态链接库UNACEV2.dll所造成的,该动态链接库在 2006 年被编译,没有任何的基础保护机制(ASLR, DEP 等)。该动态链接库的作用是处理 ACE 格式文件。在对解压目标的相对路径进行解析时,CleanPath函数过滤不严导致目录穿越漏洞,允许解压过程写入文件至开机启动项,导致代码执行。

受影响的版本包括:

winrar < 5.70 Beta
BandZip <= 6.2.0.0
好压 <= 5.9.8.10907
360压缩 < 4.0.0.1170

漏洞复现

复现系统环境:
Windows7
WinRAR 5.50 beta 2
WinAce Archiver
acefile

生成ace文件


选择全路径保存

查看ace文件头信息

利用acefile.py查看文件头信息

没有0x开头的值均为10进制

再利用windex查看文件

由于是小端存储(低地址存储低位数据,数据以字节为单位),所以在winhex里看到的数据是逆置的

红色框:hdr_crc的值占两字节
蓝色框:hdr_size的值占两字节(文件头部的大小,从01 01数据块开始,即上图中所选中的数据块的大小)
绿色框:文件路径长度大小(即“Users\Administrator\Desktop\example.txt”长度)

修改信息

尝试将文件解压的绝对路径改为C:\example.txt
利用文本编辑器打开这个ace文件,并改变路径

再使用winhex打开此ace文件,对上面框内内容进行修改
选中数据块后,看右下角即可得到所选块的大小(16进制)

这里可以看到hdr_size头部大小为2D,即将0046->002D
类似的修改文件路径长度将0027->000E

先保存一下数据,然后再利用acefile.py来获取修改后文件的hdr_crc值

再将DE5B->CAC3
修改后保存,然后进行解压


可以看到并没由解压到C盘,而是正确的解压到了桌面

触发漏洞

将文件路径修改为C:\C:\a\example.txt(设置为C盘根目录下提示压缩文件损坏)
然后按上面步骤修改对应的数值

在桌面上解压文件到当前文件夹,发现文件并没有解压到桌面,而是解压到了C盘下的a文件夹
(如果文件夹不存在会自动创建)

利用

可以压缩多个文件,设置病毒文件路径为用户目录下的启动项中
这里以自带的计算器进行演示

有两个header信息,要让calc.exe自动解压到用户启动项
只需要对第一个信息做改动

这里将路径设置为C:\C:C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\calc.exe
可在不知道用户名的情况下,用户在桌面解压时能自动解压到用户目录下的启动项文件夹中

再将后面的路径修改一下

此时压缩文件里有两个已压缩的文件,分别是calc.exe和a.txt
进行解压

可以看到,桌面上只有一个a.txt,而calc.exe跑到了用户的启动项中了

漏洞分析

通过报告原文知道漏洞触发点的关键流程如下

如果调用的结果GetDevicePathLen等于0,则sprintf如下所示:
sprintf(final_file_path, "%s%s", destination_folder, file_relative_path);
若不为0则:
sprintf(final_file_path, "%s%s", "", file_relative_path);
即当结果不为0时,文件的相对路径会变为绝对路径,然后被sprintf创建,从而触发目录穿越。

分析GetDevicePathLen函数

函数伪代码如下

_BYTE *__usercall __spoils<ecx> GetDevicePathLen@<eax>(_BYTE *path@<eax>)
{
  _BYTE *path_ptr; // ecx
  _BYTE *slash_pos; // eax
  int v3; // ecx

  path_ptr = path;
  slash_pos = 0;
  if ( *path_ptr == '\\' )
  {
    if ( path_ptr[1] == '\\' )
    {
      slash_pos = strchr(path_ptr + 2, '\\');
      if ( slash_pos )
      {
        slash_pos = strchr(slash_pos + 1, '\\');
        if ( slash_pos )
          slash_pos = &slash_pos[-v3 + 1];  //注释A
      }
    }
    else
    {
      slash_pos = (_BYTE *)1;   //注释B
    }
  }
  else if ( path_ptr[1] == ':' )
  {
    slash_pos = (_BYTE *)2; //注释C
    if ( path_ptr[2] == '\\' )
      slash_pos = (_BYTE *)3; //注释D
  }
  return slash_pos;
}

注释A: 如果路径开头为\\且路径中仍还包含多的两个\则返回第四个斜杆与开头的差距。如\\LOCALHOST\some\some_folder\some_file.txt返回值为17。
注释B:如果路径以\开头,且不以\\开头,则返回1。如\some_folder\some_file.txt返回值为1。
注释C:如果路径以*:开头,且不以*:\开头,则返回2。如C:some_folder\some_file.txt返回值为2。
注释D:如果路径以*:\开头,则返回3。如如C:\some_folder\some_file.txt返回值为3。

在进入GetDevicePathLen前还有一个CleanPath函数对输入的路径进行处理。
只有CleanPath函数返回值为True时,才会调用GetDevicePathLen

分析CleanPath函数

伪代码如下

Bool CleanPath (PCHAR Path){
    char *PathTraversalPos NULL;
    if ( Path[1] == ':' && Path[2] == '\\')
        strcpy(Path, &Path[3]);
    if ( Path[1] == ':' && Path[2] != '\\')
        strcpy(Path, &Path[2]);
    PathTraversalPos = strstr(Path, "..\\");
    while (PathTraversalPos){
        if (PathTraversalPos == Path || *(PathTraversalPos - 1) == '\\'){
            strcpy(Path, &Path[3]);
            PathTraversalPos = strstr(Path, "..\\");
        }else{
            PathTraversalPos = strstr(Path + 1, "..\\");
        }
    }
    return Path
}

当路径以\\开头时,且路径中还包含两个\,会将中间部分忽略掉。这部分本意可能为处理smb文档的代码,如\\10.10.10.10\smb_folder_name\some_folder\some_file.txt路径会被处理为some_folder\some_file.txt
当路径以*:\开头时,忽略*:\。这部分本意可能为忽略盘符,如C:\some_folder\some_file.txt会被处理为some_folder\some_file.txt
当路径包含\..\时,忽略\..\。这部分本意可能为忽略回溯路径,以防止目录穿越。
当路径开头为*:*且路径不为*:\时,忽略*:。这部分本意不知道,感觉可能为某种文件路径格式。
由于未知原因C:\C: 可用。

漏洞利用总结

如何利用漏洞,首先要解决的是如何实现任意目录的解压。

具体来说可以使用C:\some_folder\some_file.txt文件路径使的GetDevicePathLen返回非0。
但是,由于函数一开始存在一个Clean_Path函数,如目录为C:\some_folder\some_file.txt,则会被处理为some_folder\some_file.txt

绕过该处理的方法为将目录更改为:C:C:\some_folder\some_file.txt,根据Clean_Path处理部分的第四条,该路径会被处理成C:\some_folder\some_file.txt,从而实现了目录穿越。

同时也可实现对smb共享文件夹的攻击,如目录
C:\\\10.10.10.10\smb_dir_name\some_dir\some_file.txt => \\10.10.10.10\smb_dir_name\some_dir\some_file.txt
根据根据Clean_Path处理部分的第二条,将会被处理成\\10.10.10.10\smb_dir_name\some_dir\some_file.txt,实现共享文件的目录穿越。

到这里目录穿越的原理已经解释清楚,下一个问题是如何利用。实际利用有一个局限性,就是需要知道相应解压目录的具体目录,不能使用回溯路径。利用的方法为主要有两个:

一个是将文件解压至开机自启动目录。
一个是实现dll劫持。

关于开机自启动目录,主要有两个:
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp
C:\Users$user_name\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
第一个需要管理员权限,路径是固定的,但是实施攻击的条件较高;第二个则不需要管理员权限,但是需要知道相应的用户名称,可能需要爆破。
poc中也提到了唯一一个可以不使用用户名的方式,那就是使用C:\C:C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe路径。
该路径的主要方式根据Clean_Path处理部分的第五条,得到C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe,这个目录是假设用户解压的路径一般为C:\Users\$user_name\Desktop或者C:\Users\$user_name\Downloads,此时的C:../便会回溯至C:\Users\$user_name\目录,所以可以顺利解压至C:\Users\$user_name\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup目录。
另一个利用方法是dll劫持,覆盖相应dll文件等。

posted @ 2022-09-12 11:32  略略略zjr  阅读(88)  评论(0)    收藏  举报