我们脚踏实地,处理输入数据


今天的文章中的链接与通常的链接不同。 这不是一个对其源代码进行分析的项目,而是在几个不同项目中对相同诊断规则的一系列响应。 这里有什么兴趣? 事实是,某些被考虑的代码片段包含在使用应用程序时可重现的错误,而另一些则完全包含漏洞(CVE)。 另外,在本文的结尾,我们将讨论有关安全缺陷的话题。

简要序言


本文今天将要考虑的所有错误都有类似的模式:

  • 程序从标准输入流接收数据;
  • 检查是否成功读取数据;
  • 如果成功读取数据,则从行中删除进位字符。

但是,将要考虑的所有片段都包含错误,并且容易受到操纵输入的影响。 由于从用户那里接收到的数据可能违反应用程序执行的逻辑,因此人们很想尝试破坏某些内容。 我做到了

PVS-Studio静态分析器发现了以下所有问题,该分析器不仅在C,C ++和C#,Java中搜索代码中的错误。

当然,用静态分析仪发现问题是件好事,但是发现和再现是完全不同的乐趣。 :)

自由开关


fs_cli.exe模块的代码中找到了第一个可疑代码片段,该模块是FreeSWITCH分发工具包的一部分:

static const char *basic_gets(int *cnt) { .... int c = getchar(); if (c < 0) { if (fgets(command_buf, sizeof(command_buf) - 1, stdin) != command_buf) { break; } command_buf[strlen(command_buf)-1] = '\0'; /* remove endline */ break; } .... } 

PVS-Studio警告V1010 CWE-20索引:“ strlen(command_buf)”中使用了未经检查的污染数据。

分析器通过对command_buf数组的索引来警告可疑的调用。 由于未验证的外部数据被用作索引,因此被认为是可疑的。 外部的-因为它们是通过stdin流中的fgets函数获得的。 未验证-由于未在使用前进行验证。 表达式fgets(command_buf,....)!= Command_buf不计算在内,因为通过这种方式,我们仅检查接收数据的事实,而不检查其内容。

此代码的问题在于,在某些情况下,'\ 0'将被写入数组之外,这将导致未定义的行为。 为此,只需输入长度为零的字符串(从C语言的角度来看,即长度为零的字符串,即第一个字符为'\ 0'的字符串)。

让我们估计一下,如果将零长度的字符串传递给输入,将会发生什么:

  • fgets(command_buf,....) -> command_buf ;
  • fgets(....)!= command_buf- > false然后 if语句 分支将忽略);
  • strlen(command_buf) -> 0 ;
  • command_buf [strlen(command_buf)-1] -> command_buf [-1]

糟糕!

有趣的是,此分析仪警告可能“用手感到”。 为了重现该问题,您需要:

  • 使程序具有此功能;
  • 微调输入,以便getchar()调用返回负值;
  • fgets函数传递给在开头的终端为零的行,应该可以成功读取该行。

我在源代码中进行了一些混编,为重现该问题制定了一个特定的顺序:

  • 以批处理模式( fs_cli.exe -b )运行fs_cli.exe 。 我注意到要执行其他步骤,必须成功将fs_cli.exe连接到服务器。 为此,例如,以管理员身份在本地运行FreeSwitchConsole.exe就足够了。
  • 我们执行输入,以便getchar()调用返回负值。
  • 输入开头为零的行(例如,“ \ 0Oooops”)。
  • ....
  • 赢利!

以下是该问题的视频播放:


ftp


在NcFTP项目中发现了类似的问题,但已经在两个地方遇到了。 由于代码看起来相似,所以只考虑一个有问题的地方:

 static int NcFTPConfirmResumeDownloadProc(....) { .... if (fgets(newname, sizeof(newname) - 1, stdin) == NULL) newname[0] = '\0'; newname[strlen(newname) - 1] = '\0'; .... } 

PVS-Studio警告V1010 CWE-20索引:“ strlen(newname)”中使用了未经检查的污染数据。

在这里,与FreeSWITCH的示例不同,代码编写得更糟,更容易出现问题。 例如,无论使用fgets是否成功读取,都会写入'\ 0'。 也就是说,还有更多的可能性可以打破常规的执行逻辑。 让我们以行之有效的方式进行-通过零长度的行。

重现的问题比FreeSWITCH复杂。 步骤的顺序如下所述:

  • 启动并连接到可以从中下载文件的服务器。 例如,我使用了speedtest.tele2.net (最后,应用程序启动命令如下所示: ncftp.exe speedtest.tele2.net );
  • 从服务器下载文件。 在本地,具有相同名称但具有不同属性的文件应该已经存在。 例如,您可以从服务器下载文件,进行更改,然后尝试再次执行下载命令(例如, 获取512KB.zip );
  • 回答有关选择以'N'开头的一行的动作的问题(例如, 现在让我们玩得开心 );
  • 输入“ \ 0”(或更有趣的东西);
  • ....
  • 赢利!

问题的重现也记录在视频中:


Openldap


在OpenLDAP项目中(更确切地说,在其中一个附带的实用程序中),他们踩着与FreeSWITCH中相同的耙子。 仅当成功读取行后,才尝试删除换行符,但也没有针对零长度行的保护措施。

程式码片段:

 int main( int argc, char **argv ) { char buf[ 4096 ]; FILE *fp = NULL; .... if (....) { fp = stdin; } .... if ( fp == NULL ) { .... } else { while ((rc == 0 || contoper) && fgets(buf, sizeof(buf), fp) != NULL) { buf[ strlen( buf ) - 1 ] = '\0'; /* remove trailing newline */ if ( *buf != '\0' ) { rc = dodelete( ld, buf ); if ( rc != 0 ) retval = rc; } } } .... } 

PVS-Studio警告V1010 CWE-20索引:“ strlen(buf)”中使用了未经检查的污染数据。

我们排除了多余的部分,以便使问题的本质更加明显:

 while (.... && fgets(buf, sizeof(buf), fp) != NULL) { buf[ strlen( buf ) - 1 ] = '\0'; .... } 

此代码比NcFTP更好,但仍然容易受到攻击。 如果根据请求fget将零长度的字符串传递给输入:

  • fgets(buf,....) -> buf ;
  • fgets(....)!= NULL- > truewhile循环的主体开始执行);
  • strlen(buf)-1- > 0-1- > -1 ;
  • buf [-1] ='\ 0'

利比登


尽管上面讨论的错误非常有趣(它们已被稳定地重现并且可以被“触摸”(除了我无法解决OpenLDAP问题),但不能将其称为漏洞,仅是因为问题未分配CVE标识符。

但是,某些实际漏洞具有相同的问题模式。 以下两个代码段均适用于libidn项目。

程式码片段:

 int main (int argc, char *argv[]) { .... else if (fgets (readbuf, BUFSIZ, stdin) == NULL) { if (feof (stdin)) break; error (EXIT_FAILURE, errno, _("input error")); } if (readbuf[strlen (readbuf) - 1] == '\n') readbuf[strlen (readbuf) - 1] = '\0'; .... } 

PVS-Studio警告V1010 CWE-20索引“ strlen(readbuf)”中使用了未经检查的污染数据。

情况类似,不同之处在于,与先前的示例不同,在此处以索引-1进行记录,然后在此处进行读取。 但是,这仍然是未定义的行为。 已为该错误分配了自己的CVE标识符( CVE-2015-8948 )。

检测到问题后,此代码如下更改:

 int main (int argc, char *argv[]) { .... else if (getline (&line, &linelen, stdin) == -1) { if (feof (stdin)) break; error (EXIT_FAILURE, errno, _("input error")); } if (line[strlen (line) - 1] == '\n') line[strlen (line) - 1] = '\0'; .... } 

有点惊讶? 它发生了。 新漏洞,对应的CVE标识符: CVE-2016-6262

PVS-Studio警告V1010 CWE-20索引:“ strlen(line)”中使用了未经检查的污染数据。

再次尝试,通过添加对输入字符串长度的检查来解决此问题:

 if (strlen (line) > 0) if (line[strlen (line) - 1] == '\n') line[strlen (line) - 1] = '\0'; 

让我们看一下日期。 提交``关闭''CVE-2015-8948-08/10/2015。 提交关闭CVE-2016-62-62-2016年1月14日。 也就是说,上述更正之间的差值为5个月 ! 在这里,您回想起了静态分析的优势,例如在编写代码的早期阶段检测错误...

静态分析与安全


不会再有其他代码示例;而是统计信息和推理。 在本节中,作者的观点可能与读者的观点不比本文以前的相符得多。

注意事项 我建议您阅读另一篇有关类似主题的文章-“ PVS-Studio如何帮助发现漏洞? ”。 有一些有趣的漏洞示例,看起来像简单的错误。 此外,在那篇文章中,我谈到了一些术语,以及如果您关心安全性主题,为什么必须进行静态分析。

让我们看一下过去十年来发现的漏洞数量的统计数据,以评估情况。 我从CVE Details网站获取了数据。

图片2



一个有趣的情况迫在眉睫。 直到2014年,记录的CVE数量均未超过6,000单位,并且从-开始没有下降。 但是,这里最有趣的当然是2017年的统计数据-绝对领先者(14,714个单位)。 至于当前-2018-年,它尚未结束,但已经打破纪录-15,310单位。

这是否意味着所有新软件都像筛子一样充满漏洞? 我不认为,这是为什么:

  • 对漏洞主题的兴趣增加。 当然,即使您不太熟悉安全性主题,您也会反复遇到有关安全性主题的文章,说明,报告和视频。 换句话说,创建了一种“炒作”。 不好吗 可能不是。 最后,归根结底是开发人员更加关注应用程序安全性这一事实,这是件好事。
  • 开发的应用程序数量增加。 更多代码-更有可能发生补充统计数据的漏洞。
  • 改进的漏洞搜索工具和代码质量保证。 更多需求->更多供应。 分析仪,模糊器和其他工具正在变得越来越先进,这使那些想要寻找漏洞的人(无论他们身处障碍的哪一侧)都可以使用它们。

因此,不能将这种新兴趋势称为完全消极的-出版商更加关注信息安全,发现问题的工具正在改进,而所有这些无疑都是积极的。

这是否意味着您可以放松而不是“沐浴”? 我认为不是。 如果您担心应用程序的安全性主题,则应采取尽可能多的安全性措施。 如果源代码位于公共领域,则尤其如此,因为:

  • 更容易嵌入外部漏洞;
  • 那些对您的应用程序漏洞感兴趣的“绅士”更倾向于“探索”,以利用它们。 尽管在这种情况下好心人士将能够为您提供更多帮助。

我不想说您不需要在开源下翻译项目。 只要记住适当的质量/安全控制。

静态分析是一种额外的措施吗? 是的 静态分析在检测将来可能会变成现实的潜在漏洞方面做得很好。

在我看来(我承认我错了),许多人认为漏洞是一种相当高级的现象。 是的,没有。 看起来像简单的编程错误的代码问题也可能是严重的漏洞。 同样,在前面提到的文章中提供了此类漏洞的一些示例。 不要小看“简单”的错误。

结论


不要忘记输入数据的长度可以为零,这也需要加以考虑。

关于所有带有漏洞的炒作仅仅是炒作还是存在问题的结论,请自己解决。

就我而言,除非我建议尝试您的项目PVS-Studio (如果尚未这样做)。

祝一切顺利!



如果您想与讲英语的读者分享这篇文章,请使用以下链接:Sergey Vasiliev。 处理输入数据时,请放手脚

Source: https://habr.com/ru/post/zh-CN433932/


All Articles