开源计算机视觉库 OpenCV 被曝两个严重的任意代码执行漏洞(详情)
聚焦源代码安全,网罗国内外最新资讯!
开源计算机视觉库 OpenCV中修复了两个高危的缓冲区溢出漏洞,它们本可导致任意代码执行的后果。
OpenCV是一款开源库,它由Intel Research 于1999年开发而成,现在由非营利性组织机构OpenCV.org 负责维护。它包含超过2500种优化的计算机视觉和机器学习算法,旨在加速商业产品对机器感知的使用。
OpenCV广泛应用于谷歌、雅虎、微软、英特尔、IBM、索尼、本田、丰田等等厂商中,涵盖面部识别技术、机器人、运动跟踪和其它应用程序。OpenCV具有C++、Python、Java 和 MATLAB 接口,同时支持 Windows、Linux、安卓和 Mac OS 系统。
2019年12月末发布的 OpenCV4.2.0 推出多种改进和修复方案,包括修复了两个由思科Talos 团队发现的两个严重的缓冲区溢出漏洞。
CVE-2019-5063
第一个漏洞是 CVE-2019-5063(CVSS 评分8.8),它是存在于OpenCV 4.1.0 数据结构持久功能中的一个缓冲区溢出漏洞,可导致开发人员从磁盘文件写入并检索OpenCV 数据结构或将OpenCV 数据结构写入并检索到磁盘文件中。文件类型可以是XML、YAML或JSON。
思科 Talos 团队指出,一个特殊构造的 XML 文件可引发缓冲区溢出问题,导致多种堆损坏和潜在的代码执行后果。攻击者能够通过提供特殊构造文件的方式触发该漏洞。
在解析包含潜在的字符实体应用的 XML 文件过程中,遇到“&”号时,API 将继续提取字母数字字符,直到遇到分号为止。如果该字符串和switch 语句中的字符串之一不匹配,则该数据按原样被复制到缓冲区中。
在persistence_xml.cpp 中,我们可以看到将被溢出的缓冲区存在于堆上的一个FileStorageParser 类中。
char strbuf[CV_FS_MAX_LEN+16];
其中,persistence.hpp 将CV_FS_MAX_LEN定义为:
44
因此,我们的缓冲区大小是 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 else
601 {
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 else
619 {
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)
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)
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)
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 0x4242424242424242
0x91d3a0: 0x4242424242424242 0x4242424242424242
0x91d3b0: 0x4242424242424242 0x4242424242424242
0x91d3c0: 0x4242424242424242 0x4242424242424242
0x91d3d0: 0x4242424242424242 0x4242424242424242
0x91d3e0: 0x4242424242424242 0x4242424242424242
0x91d3f0: 0x4242424242424242 0x4242424242424242
0x91d400: 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:481
481 VMOVU %VEC(8), (%r11)
CVE-2019-5064
第二个漏洞 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
因此,我们的缓冲区大小是 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" );
605
606 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)
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)
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)
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 0x4242424242424242
0x91d790: 0x4242424242424242 0x4242424242424242
0x91d7a0: 0x4242424242424242 0x4242424242424242
0x91d7b0: 0x4242424242424242 0x4242424242424242
0x91d7c0: 0x4242424242424242 0x4242424242424242
0x91d7d0: 0x4242424242424242 0x4141414141414141
如果我们继续让它运行,该攻击的具体变体将触发任意的free():
Program received signal SIGSEGV, Segmentation fault.
__GI___libc_free (mem=0x4141414141414141) at malloc.c:3109
3109 p = mem2chunk (mem);
漏洞已修复
2019年7月22日,思科 Talos 团队将问题告知 OpenCV,漏洞已于2019年12月19日修复。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的产品线。
点个“在看”,bounty 多多~