vlambda博客
学习文章列表

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


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

编译:奇安信代码卫士团队

开源计算机视觉库 OpenCV中修复了两个高危的缓冲区溢出漏洞,它们本可导致任意代码执行的后果。

OpenCV是一款开源库,它由Intel Research 1999年开发而成,现在由非营利性组织机构OpenCV.org 负责维护。它包含超过2500种优化的计算机视觉和机器学习算法,旨在加速商业产品对机器感知的使用。

OpenCV广泛应用于谷歌、雅虎、微软、英特尔、IBM、索尼、本田、丰田等等厂商中,涵盖面部识别技术、机器人、运动跟踪和其它应用程序。OpenCV具有C++PythonJava MATLAB 接口,同时支持 WindowsLinux、安卓和 Mac OS 系统。

201912月末发布的 OpenCV4.2.0 推出多种改进和修复方案,包括修复了两个由思科Talos 团队发现的两个严重的缓冲区溢出漏洞。

CVE-2019-5063

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

第一个漏洞是 CVE-2019-5063CVSS 评分8.8),它是存在于OpenCV 4.1.0 数据结构持久功能中的一个缓冲区溢出漏洞,可导致开发人员从磁盘文件写入并检索OpenCV 数据结构或将OpenCV 数据结构写入并检索到磁盘文件中。文件类型可以是XMLYAMLJSON

思科 Talos 团队指出,一个特殊构造的 XML 文件可引发缓冲区溢出问题,导致多种堆损坏和潜在的代码执行后果。攻击者能够通过提供特殊构造文件的方式触发该漏洞。

在解析包含潜在的字符实体应用的 XML 文件过程中,遇到&号时,API 将继续提取字母数字字符,直到遇到分号为止。如果该字符串和switch 语句中的字符串之一不匹配,则该数据按原样被复制到缓冲区中。

persistence_xml.cpp 中,我们可以看到将被溢出的缓冲区存在于堆上的一个FileStorageParser 类中。

char strbuf[CV_FS_MAX_LEN+16];


其中,persistence.hpp CV_FS_MAX_LEN定义为:

 

44 #define CV_FS_MAX_LEN 4096


因此,我们的缓冲区大小是 0x1010 (4112) 的长度。persistence_xml.cpp中的如下解析例程中会发生溢出:

583 else if( c == '&' )584 {585 if( *++ptr == '#' )586 {587 int val, base = 10;588 ptr++;589 if( *ptr == 'x' )590 {591 base = 16;592 ptr++;593 }594 val = (int)strtol( ptr, &endptr, base );595 if( (unsigned)val > (unsigned)255 ||596 !endptr || *endptr != ';' )597 CV_PARSE_ERROR_CPP( "Invalid numeric value in the string" );598 c = (char)val;599 }600 else601 {602 endptr = ptr;603 do c = *++endptr;604 while( cv_isalnum(c) );605 if( c != ';' )606 CV_PARSE_ERROR_CPP( "Invalid character in the symbol entity name" );607 len = (int)(endptr - ptr);608 if( len == 2 && memcmp( ptr, "lt", len ) == 0 )609 c = '<';610 else if( len == 2 && memcmp( ptr, "gt", len ) == 0 )611 c = '>';612 else if( len == 3 && memcmp( ptr, "amp", len ) == 0 )613 c = '&';614 else if( len == 4 && memcmp( ptr, "apos", len ) == 0 )615 c = '\'';616 else if( len == 4 && memcmp( ptr, "quot", len ) == 0 )617 c = '\"';618 else619 {620 memcpy( strbuf + i, ptr-1, len + 2 );621 i += len + 2;622 }623 }


溢出发生在第620行。溢出发生的原因在于缓冲区的大小是固定的,但memcpy 的大小被计算为整个XML实体值(第596行)的长度,而不会检查它是否超出了目标缓冲区。

我们可以看到易受攻击的 memcpy 操作发生在此处:

0x41f71f call memcpy@plt <0x406470> dest: 0x91c380 ◂— 0x676e69727400 src: 0x911e15 ◂— 0x4242424242422026 ('& BBBBBB') n: 0x2c83


如果缓冲区的大小仅为 0x1010(4112),则memcpy 的大小(实体引用字符串之一的整个值)即0x2c83(11395) 个字节,这样在后续的堆对象中会存在溢出,从而导致潜在的代码执行后果。

目标缓冲区位于FileStorageParser对象本身当中:

type = class cv::XMLParser : public cv::FileStorageParser { public: cv::FileStorage_API *fs; char strbuf[4112];
XMLParser(cv::FileStorage_API *); virtual ~XMLParser(void); char * skipSpaces(char *, int); virtual bool getBase64Row(char *, int, char *&, char *&); char * parseValue(char *, cv::FileNode &); char * parseTag(char *, std::__cxx11::string &, std::__cxx11::string &, int &); virtual bool parse(char *);} *


在这个具体案例中,该实例的堆对象位于0x91c350处,该缓冲区位于0x91c380处。

Heap chunk: 0x91c358 (malloc address)Heap chunk header: 0x91c350 Size: 0x1040 (4160) Size+Hdr: 0x1050 (4176) Status: in USE Prev size field: 0x0 (0) Raw Size: 0x1041 (4161) Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@.............. 000000000091c350: 00 00 00 00 00 00 00 00 41 10 00 00 00 00 00 00 ........A....... 000000000091c360: 48 3f 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 H?.............. 000000000091c370: 98 3f 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .?......`....... .... 000000000091d380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000000091d390: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......


堆中的下一个对象位于 0x91d390 处,距离恰好是 0x1040(4160) 个字节:

Heap chunk: 0x91d398 (malloc address)Heap chunk header: 0x91d390 Size: 0x20 (32) Size+Hdr: 0x30 (48) Status: in USE Prev size field: 0x0 (0) Raw Size: 0x21 (33) Flags: PREV_INUSE
000000000091d380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000000091d390: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!....... .... 000000000091d3a0: 00 73 74 72 69 6e 67 73 00 00 00 00 00 00 00 00 .strings........ 000000000091d3b0: 00 00 00 00 00 00 00 00 51 1c 00 00 00 00 00 00 ........Q.......


Memcpy()操作之后的对象如下:

Heap chunk: 0x91c358 (malloc address)Heap chunk header: 0x91c350 Size: 0x1040 (4160) Size+Hdr: 0x1050 (4176) Status: is FREE FD: 0x8e3f48 BK: 0x100000001 Prev size field: 0x0 (0) Raw Size: 0x1041 (4161) Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@.............. 000000000091c350: 00 00 00 00 00 00 00 00 41 10 00 00 00 00 00 00 ........A....... 000000000091c360: 48 3f 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 H?.............. 000000000091c370: 98 3f 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .?......`....... .... 000000000091d380: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB 000000000091d390: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB


0x91d390处的堆对象显然被损坏了:

0x91d390: 0x4242424242424242 0x42424242424242420x91d3a0: 0x4242424242424242 0x42424242424242420x91d3b0: 0x4242424242424242 0x42424242424242420x91d3c0: 0x4242424242424242 0x42424242424242420x91d3d0: 0x4242424242424242 0x42424242424242420x91d3e0: 0x4242424242424242 0x42424242424242420x91d3f0: 0x4242424242424242 0x42424242424242420x91d400: 0x4242424242424242 0x4242424242424242

堆分段从 0x8f9000 延伸到 0x91f000(本实例)。因为尝试将数据复制到对段之外,这种具体的攻击变体将触发访问冲突。

0x8f9000 0x91f000 rw-p 26000 0 [heap]

Program received signal SIGSEGV, Segmentation fault.__memmove_sse2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:481481 VMOVU %VEC(8), (%r11)


CVE-2019-5064

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

第二个漏洞 CVE-2019-5064(CVSS 评分8.8)也存在于该库的数据结构持久性功能中,可通过一个特殊构造的JSON 文件被触发。它可导致开发人员从磁盘文件写入并检索OpenCV 数据结构或将OpenCV 数据结构写入并检索到磁盘文件中。文件类型可以是XML、YAML或JSON。

问题在于,当解析一个JSON 文件并遇到一个null 字节时,OpenCV将那时的整个值复制到缓冲区。尽管如此,但并未执行检查以确定JSON 值是否会溢出目标缓冲区。

persistence_json.cpp中,我们可以看到我们可以看到将被溢出的缓冲区存在于堆上的一个FileStorageParser 类中。

847 char buf[CV_FS_MAX_LEN+1024];


其中,persistence.hpp CV_FS_MAX_LEN 定义为:

44 #define CV_FS_MAX_LEN 4096


因此,我们的缓冲区大小是 0x1400 (5120) 的长度。persistence_json.cpp中的如下解析例程中会发生溢出:

565 switch ( *ptr )566 {567 case '\\':568 {569 sz = (int)(ptr - beg);570 if( sz > 0 )571 {572 memcpy(buf + i, beg, sz);573 i += sz;574 }575 ptr++;576 switch ( *ptr )577 {578 case '\\':579 case '\"':580 case '\'': { buf[i++] = *ptr; break; }581 case 'n' : { buf[i++] = '\n'; break; }582 case 'r' : { buf[i++] = '\r'; break; }583 case 't' : { buf[i++] = '\t'; break; }584 case 'b' : { buf[i++] = '\b'; break; }585 case 'f' : { buf[i++] = '\f'; break; }586 case 'u' : { CV_PARSE_ERROR_CPP( "'\\uXXXX' currently not supported" ); break; }587 default : { CV_PARSE_ERROR_CPP( "Invalid escape character" ); }588 break;589 }590 ptr++;591 beg = ptr;592 break;593 }594 case '\0':595 {596 sz = (int)(ptr - beg);597 if( sz > 0 )598 {599 memcpy(buf + i, beg, sz); [0]600 i += sz;601 }602 ptr = fs->gets();603 if ( !ptr || !*ptr )604 CV_PARSE_ERROR_CPP( "'\"' - right-quote of string is missing" );605606 beg = ptr;607 break;608 }

溢出发生在第599行。溢出发生的原因在于缓冲区的大小是固定的,但memcpy 的大小被计算为整个 JSON实体值(第596行)的长度,而不会检查它是否超出了目标缓冲区。

我们可以看到易受攻击的 memcpy 操作发生在此处:

0x417999 call memcpy@plt <0x406470> dest: 0x91c380 ◂— 0x42 /* 'B' */ src: 0x911dee ◂— 0x4242424242424242 ('BBBBBBBB') n: 0x1460


如果缓冲区的大小仅为 0x1400 (5120),则 memcpy 的大小(json对之一的整个值)即0x1460 个字节,这样在后续的堆对象中会存在溢出,从而导致潜在的代码执行后果。

目标缓冲区位于FileStorageParser对象本身当中:

type = class cv::JSONParser : public cv::FileStorageParser { public: cv::FileStorage_API *fs; char buf[5120];
JSONParser(cv::FileStorage_API *); virtual ~JSONParser(void); char * skipSpaces(char *); char * parseKey(char *, cv::FileNode &, cv::FileNode &); virtual bool getBase64Row(char *, int, char *&, char *&); char * parseValue(char *, cv::FileNode &); char * parseSeq(char *, cv::FileNode &); char * parseMap(char *, cv::FileNode &); virtual bool parse(char *);} *


在这个具体案例中,该实例的堆对象位于0x91c350处,该缓冲区位于0x91c380处。

 Heap chunk: 0x91c358 (malloc address) Heap chunk header: 0x91c350 Size: 0x1430 (5168) Size+Hdr: 0x1440 (5184) Status: in USE Prev size field: 0x0 (0) Raw Size: 0x1431 (5169) Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@.............. 000000000091c350: 00 00 00 00 00 00 00 00 31 14 00 00 00 00 00 00 ........1....... 000000000091c360: c8 3d 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 .=.............. 000000000091c370: 18 3e 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .>......`....... .... 000000000091d770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000000091d780: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......


堆中的下一个对象位于 0x91d780 处,距离恰好是 0x1400 (5120) 个字节:

Heap chunk: 0x91d788 (malloc address)Heap chunk header: 0x91d780 Size: 0x20 (32) Size+Hdr: 0x30 (48) Status: in USE Prev size field: 0x0 (0) Raw Size: 0x21 (33) Flags: PREV_INUSE
000000000091d770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000000091d780: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!....... .... 000000000091d790: 00 00 00 00 00 00 00 00 10 e0 8f 00 00 00 00 00 ................ 000000000091d7a0: 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 ........!.......

Memcpy()操作之后的对象如下:

Heap chunk: 0x91c358 (malloc address) Heap chunk header: 0x91c350 Size: 0x1430 (5168) Size+Hdr: 0x1440 (5184) Status: is FREE FD: 0x8e3dc8 BK: 0x100000001 Prev size field: 0x0 (0) Raw Size: 0x1431 (5169) Flags: PREV_INUSE
000000000091c340: 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .@.............. 000000000091c350: 00 00 00 00 00 00 00 00 31 14 00 00 00 00 00 00 ........1....... 000000000091c360: c8 3d 8e 00 00 00 00 00 01 00 00 00 01 00 00 00 .=.............. 000000000091c370: 18 3e 8e 00 00 00 00 00 60 04 91 00 00 00 00 00 .>......`....... .... 000000000091d770: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB 000000000091d780: 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB


0x91d780处的堆对象显然被损坏了:

0x91d780: 0x4242424242424242 0x42424242424242420x91d790: 0x4242424242424242 0x42424242424242420x91d7a0: 0x4242424242424242 0x42424242424242420x91d7b0: 0x4242424242424242 0x42424242424242420x91d7c0: 0x4242424242424242 0x42424242424242420x91d7d0: 0x4242424242424242 0x4141414141414141


如果我们继续让它运行,该攻击的具体变体将触发任意的free()

Program received signal SIGSEGV, Segmentation fault.__GI___libc_free (mem=0x4141414141414141) at malloc.c:31093109 p = mem2chunk (mem);

漏洞已修复

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

2019722日,思科 Talos 团队将问题告知 OpenCV,漏洞已于20191219日修复。

 




推荐阅读






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


题图:Pixabay License



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



奇安信代码卫士 (codesafe)

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



 点个“在看”,bounty 多多~