代码中的漏洞。您如何从微小错误中分辨出危险缺陷?

通常看起来像检查应用程序代码中的漏洞吗?安全专家启动了该过程,对代码进行了扫描,并在应用程序中发现了数千个漏洞。每个人-安全人员和开发人员-都感到震惊。开发人员的自然反应:“是的,肯定有一半是误报,另一半是非关键漏洞!”



至于误报,这里的一切都很简单:您可以直接看一下代码中那些怀疑为误报而发现漏洞的地方。确实,其中一些可能证明是误报(尽管显然不是总数的一半)。



但是关于关键和不关键,我想更实质性地谈。如果您了解了为什么不再可以使用SHA-1以及为什么对“;”进行了转义,也许本文不会为您带来新的东西。但是,如果所发现的漏洞引起的扫描结果令人眼花,乱,请切切欢迎-我们将告诉您在移动和Web应用程序中最经常发现哪些“漏洞”,它们如何工作,如何修复它们,最重要的是-如何了解您面前的漏洞-代码中存在危险孔或小错误。







实作



好吧,常见的漏洞类型。它们被嵌入到所有地方:SQL,LDAP,XML,XPath,XSLT,Xquery查询...所有这些注入都通过使用不受信任的数据来区分,从而使攻击者可以访问信息或更改应用程序的行为。例如,用户输入的验证不够充分。



根据OWASP的国际分类,使用“注入”方法的攻击在Web应用程序安全威胁的严重性级别中排名第一。让我们考虑最典型的实现类型。



SQL注入不受信任的数据进入对数据库的SQL查询。







如果对数据库的查询未实现对输入数据的正确身份验证,则攻击者可能破坏SQL查询:



  • 向其发送恶意代码;
  • 添加符号“-”或“;” 并终止正确的SQL命令:“-”之后的所有内容均被解释为注释,并且字符“;” 标记命令的结尾;
  • 通过顺序执行一系列SQL查询来猜测密码。


如何捍卫自己?以下是OWASP的一些建议



  • 使用提供参数化接口的API或对象关系映射(ORM)工具。
  • 为用户输入的数据实施验证机制。使用服务器端验证白名单。
  • 转义特殊字符(“;”,“-”,“ / *”,“ * /”,“'”;确切的列表取决于数据库)。
  • 将存储过程及其参数的过滤机制结合使用,以验证用户输入。


XML注入。应用程序使用XML来存储或交换数据,因此它们可以包含有价值的信息。







如果攻击者可以将数据写入XML文档,那么他可以更改其语义。在这种情况下,最无害的情况是允许您向文档中注入额外的标签,结果XML解析器将退出并出现错误。但是您可能会遇到更严重的事件:例如,替换客户群中的身份验证数据或商店产品数据库中的价格。 XML注入还可能导致跨站点脚本(XSS)-注入恶意代码,该恶意代码在打开页面时在用户浏览器中执行。



我们能提供什么建议?



  • 请勿创建其名称源自不受信任来源的数据(例如,由用户输入)的标记和属性。
  • 对用户输入的数据进行编码(XML实体编码),然后再将其写入XML文档。


XQuery注入是经典SQL注入的一种形式,但是在这种情况下,攻击将针对XML数据库,并且不受信任的数据将最终出现在XQuery表达式中。



在下面的示例中,应用程序基于参数usernamepassword通过HTTP请求(不受信任的源)创建并执行XQuery表达式



XQDataSource xqs = new XQDataSource();
XQConnection conn = xqs.getConnection();
String query = "for \$user in doc(users.xml)//user[username='" + request.getParameter("username") + "'and pass='" + request.getParameter("password") + "'] return \$user";
XQPreparedExpression xqpe = conn.prepareExpression(query);
XQResultSequence rs = xqpe.executeQuery();


如果数据正确,则请求将返回具有适当名称和密码的有关用户的信息:



for \$user in doc(users.xml)//user[username='test_user' and pass='pass123'] return \$user


如果攻击者将包含特殊字符(例如admin' or 1=1 or ''='的字符串指定为参数,则请求的语义将发生变化:



//user[username='admin']


收到的请求将返回有关所有用户的数据。



安全选项(使用prepared statements):



XQDataSource xqs = new XQDataSource();
XQConnection conn = xqs.getConnection();
String query = "declare variable $username as xs:string external; declare variable $password as xs:string external; for \$user in doc(users.xml)//user[username='$username' and pass='$password'] return \$user";
XQPreparedExpression xqpe = conn.prepareExpression(query);
xqpe.bindString(new QName("username"), request.getParameter("username"), null);
xqpe.bindString(new QName("password"), request.getParameter("password"), null);
XQResultSequence rs = xqpe.executeQuery();


如果应用程序在使用XSL时使用来自不受信任来源的数据,则可以嵌入XSLT(XML文档转换语言)。



应用程序使用XSL转换XML文档。XSL样式文件包含描述转换的功能,如果未正确实施,则可能包含漏洞。在这种情况下,攻击者存在以下风险:攻击者更改XSL样式文件的结构和内容,并因此更改相应的XML文件。我们在出口能得到什么?



首先,是XSS攻击:将恶意代码注入Web系统发布的页面并与攻击者的服务器进行交互。其次,黑客可以访问系统资源。第三,执行任意代码。对于甜点-XXE攻击(XML eXternal Entity-将外部实体注入XML)。



在命令中插入轻量目录访问协议(LDAP)可能会导致数据丢失或修改。在这种情况下,不受信任的数据将进入LDAP请求。



注入恶意解释器命令。不受信任的数据进入解释器命令。攻击者可以选择这样的输入,以使命令成功执行,并且应用程序中的其他权限可供他使用。



在下面的示例中,该应用程序运行脚本来创建数据库备份。该应用程序将备份类型作为参数,并以提升的特权运行脚本:



String btype = request.getParameter("backuptype");
String cmd = new String("cmd.exe /K
\"c:\\util\\rmanDB.bat "+btype+"&&c:\\utl\\cleanup.bat\"")
System.Runtime.getRuntime().exec(cmd);


这里的问题是该参数backuptype未通过验证。通常Runtime.exec()不执行多个命令,但在这种情况下,首先启动cmd.exe来通过调用执行多个命令Runtime.exec()启动命令行外壳程序后,它可以执行多个命令,以“ &&字符分隔如果攻击者&& del c:\\dbms\\*.*字符串“ ”指定为参数,则应用程序将删除指定的目录。



开发人员提示:



  • 不要让用户直接控制应用程序运行的命令。如果应用程序的行为必须取决于用户输入的数据,请为用户提供一系列允许命令的选择。
  • , . , . , .
  • , , . . .


文件上传不安全。在这种情况下,不仅单个数据来自不受信任的来源,而且整个文件也来自。因此,攻击者可以将恶意数据或代码上传到目标服务器。例如,如果允许公司网络上的用户将文件上传到可公开访问的目录,则黑客可以在公司服务器上远程运行恶意代码。



HTML中不安全地包含外部文件。当用户输入包含文件的路径时,将发生文件包含漏洞。事实是,现代脚本语言允许您动态链接第三方文件中的代码以重新使用它。此机制用于页面的统一外观或将代码划分为小模块。但是,攻击者可以通过更改路径并连接其文件来利用此包含项。



我们建议公司信息安全专业人员创建有效文件连接路径的“白名单”,以便员工只能通过此列表中的脚本添加文件。



书签



书签是有意引入到应用程序代码中的部分,在某些条件下,借助书签,您可以执行应用程序中未包含的操作。让我们考虑最常见的书签类型。



特别帐户。如果应用程序将密码或登录变量的值与未更改的值进行比较,请当心:此帐户可能是书签的一部分。让我们看看这是怎么发生的。







应用程序的开发人员在调试时使用一个特殊帐户(可能具有较高的特权),并将代码的相应部分保留在最终版本中,从而保留对应用程序的访问权限。攻击者可以还原应用程序的原始代码,提取特殊帐户的常量值并获得对应用程序的访问权限。

绝对不可能在应用程序源代码中存储登录名,密码,密钥。
隐藏功能(NDV)。当某个触发器触发时,将运行隐藏的功能代码。在Web应用程序中,触发器通常是“不可见”的查询参数。有时,有时还会执行带有触发器的请求来自哪个IP的检查,以便只有其作者才能激活书签。这种检查是可能的书签的信号。



未记录的网络活动。这种类型的活动包括:在后台连接到第三方资源,侦听未记录的端口,通过SMTP,HTTP,UDP,ICMP传输信息。

如果您在代码中发现可疑连接,但其地址不在已知安全地址列表中,则强烈建议您将其删除。

更改安全设置该应用程序包含用于更改存储身份验证成功的变量的值的代码。一个常见的错误是使用赋值(=)而不是比较(==)。在身份验证方法中,这特别危险,因为它可能是后门的一部分:



if (isAuthenticated = true)
{
    someDangerousAction();
}


时间触发(定时炸弹)。在特定时间点触发的书签。该应用程序将当前日期与特定的年,月和日进行比较:2021年1月1日,所有人都将惊喜连连:



Date now = java.util.Date();    // current time
if ((now.getYear() == 2021) && (now.getMonth() == 1) && (now.getDate() == 1))
{
    activateNewYearBackdoor();
}


也许不是。。。在实践中,当寻找临时触发因素时,经常会出现误报。例如,如果时间API也用于其预期目的:记录日志,计算执行时间,服务器对HTTP请求的响应的时间戳。



但!我们仍然建议不要对所有此类警报都保持警惕,因为我们知道此类漏洞的真实示例。



无效代码。注入的代码片段无济于事。无效代码本身并不危险,但是它可以成为已分散在多个文件中的书签的一部分。或者,书签触发器计划在以后实现。无论如何,无效代码应该是可疑的。



缺乏加密和使用弱加密算法



加密的主要问题是要么根本不使用加密,要么使用弱算法,并且密钥和加密太简单或存储不安全。所有这些漏洞的后果是相同的-窃取机密数据更容易。



该示例显示了使用传统DES算法的加密初始化:



Cipher cipher = Cipher.getInstance("DES");


易受攻击的加密算法示例:RC2,RC4,DES。安全选项:



Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");


根据OWASP国际分类,就Web应用程序安全威胁的严重性而言,诸如“机密数据泄漏”之类的漏洞排名第三。



我们对开发人员的建议:确保使用加密时要牢记安全性。



使用不安全的HTTP协议而不是HTTPS会给中间人带来很多麻烦。



安全的HTTPS协议基于HTTP,但也支持通过SSL / TLS加密协议进行加密。 HTTPS对通过其传输的所有数据进行加密,特别是登录和密码输入页面或用户银行卡数据,以防止未经授权的访问和更改。与HTTP不同,HTTP不保护传输的数据。结果,攻击者可以通过HTTP欺骗信息网站,并强迫用户在假页面上输入数据(网络钓鱼攻击)。



加密密钥在源代码中指定。结果,每个应用程序开发人员都可以使用这些密钥。此外,安装应用程序后,您只能使用更新从代码中删除密钥。



通常,使用源代码恢复程序(反编译器)可以轻松地从可执行文件中提取常量字符串。因此,攻击者无需访问源代码即可发现所使用密钥的值。在我们的实践中,我们经常遇到开发人员将null空字符串指定为键值的情况,这简直是不可接受的。



我们的建议:使用具有加密功能的强伪随机数生成器(PRNG)生成密钥,并使用特殊模块进行存储。



不安全的填充加密算法。如果使用没有OAEP填充的RSA加密算法,则加密的数据容易受到攻击



使用RSA之前,需要OAEP算法来处理消息。首先使用OAEP将消息填充到固定长度,然后使用RSA加密。这种加密方案称为RSA-OAEP,是当前标准的一部分



这是不填充而初始化RSA加密的示例:



rsa = javax.crypto.Cipher.getInstance("RSA/NONE/NoPadding");


安全选项:



rsa = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPWithMD5AndMGF1Padding");


加密密钥大小不足。如果使用短密钥,则此加密容易受到暴力攻击。



密码分析并没有停滞不前,新的攻击算法不断涌现,计算机获得了更大的功能。以前认为安全的加密设置已弃用,不再建议使用。因此,密钥长度为1024位的RSA在2010-2015年不再被认为是安全的。



弱哈希算法。由于上一段中所述的原因,哈希函数MD2,MD5,SHA1是不安全的。查找MD2和MD5功能的冲突不需要大量资源。



对于SHA1,有两个具有相同哈希值的不同文件的示例。黑客算法建议Google以及位于阿姆斯特丹的数学和计算机科学中心的员工。







如果将用户密码存储为哈希,但使用了不安全的哈希函数,则攻击者可以通过实施以下方案轻松获得对它们的访问权限。知道密码的哈希并利用哈希算法的漏洞,就可以计算出一个字符串,该字符串的哈希值与密码相同。攻击者使用计算出的字符串进行身份验证。



用于存储密码的哈希函数必须具有抗冲突能力,并且不能太快,因此无法实施暴力攻击。应该使用安全算法PBKDF2,bcrypt,scrypt。



一些有趣的数字:使用PBKDF2Intel Core2的键搜索速度降低到每秒70个,FPGA Virtex-4 FX60的键搜索速度降低到每秒1000个。为了进行比较,经典的LANMAN密码哈希函数的暴力破解速度约为每秒数亿个选项。



弱加密算法。与散列算法一样,加密算法的安全性由解密所需的时间和资源决定。 RC2,RC4,DES被认为是易受攻击的算法。后者由于密钥长度小(56位),可能会被蛮力破解。



弱伪随机数生成器(PRNG)生成可预测的序列。黑客可以绕过身份验证并劫持用户的会话。



让我们更深入地了解PRNG的性质。它们根据参数的初始值生成数字字符串seed。 PRNG有两种类型-统计和加密。



统计PRNG生成的可预测序列在统计上类似于随机序列。它们不能用于安全目的。



相反,如果seed从具有高熵的源获得参数值,则无法预测密码PRNG的运算结果。当前时间值的熵很小,而且质量也不安全seed。在Java中,从类的PRNGjava.util.Randomjava.lang.Math产生可预测的序列,不应该被用于信息安全的目的。



伪随机数生成器的弱种子。使用seed来自不受信任来源不安全的,因为它会生成可预测的序列。



许多密码算法的工作都基于使用抗密码分析的PRNG。一些算法可以将值作为附加参数,seed并为此参数的每个值生成可预测的序列。在这种情况下,系统的安全性基于这些值seed将不可预测的假设



盐在源代码中指定... 让我们记住盐是干什么的。为了通过蛮力方法破解密码,使用了具有流行密码哈希函数值的预编译表。Salt是一个任意字符串,与密码一起输入到哈希函数的输入中,从而使这种攻击更加困难。



如果盐存储在源代码中,则会出现与密码和密钥完全相同的问题。盐的价值可供开发人员使用,入侵者可以轻松获得,并且只有在应用程序的下一次更新时才能从应用程序的最终版本中删除盐。



操纵日志



日志中的各种错误充满了将恶意代码引入应用程序的麻烦。与日志记录相关的最常见漏洞是日志文件篡改和非结构化日志记录。当应用程序将不受信任的数据写入事件日志(日志)时,就会发生



日志文件篡改。黑客可以伪造日志条目或将恶意代码注入其中。



通常,应用程序记录事务历史记录以进行进一步处理,调试或统计信息收集。日志可以手动或自动解析。

如果将数据“按原样”写入日志,则攻击者可以将假记录注入日志中,通过使日志处理器发生故障来破坏文件结构,或注入利用处理器中已知漏洞的恶意代码。



在此示例中,Web应用程序尝试从请求参数中读取整数值。如果输入的值不能转换为整数,则应用程序将记录此值以及错误消息:



String val = request.getParameter("val");
try {
    int value = Integer.parseInt(val);
}
catch (NumberFormatException nfe) {
    log.info("Failed to parse val = " + val);
}


攻击者可以在日志中添加任意条目,例如,该行将twenty-one%0a%0aINFO:+User+logged+out%3dbadguy在日志中反映如下:



INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy


同样,可以在日志中嵌入任意记录。



安全选项(使用NumberFormatException):



public static final String NFE = "Failed to parse val. The input is required to be an integer value."

String val = request.getParameter("val");
try {
    int value = Integer.parseInt(val);
}
catch (NumberFormatException nfe) {
    log.info(NFE);
}


非结构化日志记录,即将错误消息输出到标准输出或错误流是不安全的方法。建议改用结构化日志记录。后者允许您生成具有级别,时间戳,标准格式的日志。如果程序实现了结构化的日志记录机制,但是错误消息被输出到标准流,则日志可能不包含关键信息。

仅在开发的早期阶段就可以将错误消息输出到标准流。



Cookie的处理不安全



与用户Cookie收集相关的漏洞非常多样。



Cookie的处理不安全。该应用程序在cookie中包含来自不受信任来源的数据,这可能导致缓存中毒,XSS(跨站点脚本)和响应拆分攻击。



如果将恶意代码(跨站点脚本)注入到应用程序中,则攻击者可以修改用户的cookie。



由于cookie是在HTTP响应标头中设置的,因此无法确认cookie中包含的数据可能会导致拆分响应攻击。 “ HTTP响应拆分”是一种攻击,黑客会发送HTTP请求,受害者将立即在两个HTTP响应(而不是正确的HTTP响应)中接受该响应。

如果攻击者指定author形式字符串作为参数Hacker \r\nHTTP/1.1 200 OK\r\n...,则答案将分为以下两个部分:



HTTP/1.1 200 OK
...
Set-Cookie: author=Hacker

HTTP/1.1 200 OK
...


第二个响应的内容完全在攻击者的控制之下,从而导致缓存中毒,XSS,恶意重定向和其他攻击。



没有HttpOnly的Cookies该应用程序创建没有标志的cookie httpOnly如果httpOnly包含在响应标头中http,攻击者将无法使用JavaScript代码获取cookie。并且,如果用户打开一个带有跨站点脚本(XSS)漏洞的页面,则浏览器将不会向第三方泄露Cookie。如果httpOnly未设置该标志,则可以使用脚本来窃取cookie(通常是会话cookie)。



创建不带标志的cookie的示例httpOnly



Cookie cookie = new Cookie("emailCookie", email);
response.addCookie(cookie);


httpOnly创建cookie时 设置标志。但是请记住,有一些方法可以绕过攻击,httpOnly因此您还应注意仔细验证输入。



注意:根据OWASP国际分类,在Web应用程序安全威胁的严重性级别中,“机密数据泄漏”漏洞排名第三。



Cookie过于笼统。如果Cookie域过于笼统(例如.example.com),则一个应用程序中的漏洞会将同一域中的其他应用程序暴露给漏洞。



在下面的示例中,安装在某个地址的安全Web应用程序http://secure.example.com将使用域值设置Cookie .example.com



Cookie cookie = new Cookie("sessionID", sessionID);
cookie.setDomain(".example.com");


如果在地址http://insecure.example.com处安装包含XSS的应用程序,则http://insecure.example.com可能会破坏访问该地址的安全应用程序的授权用户的cookie



攻击者还可以执行cookie中毒攻击:创建了公共域http://insecure.example.com的cookie将覆盖cookie http://secure.example.com



安全选项:



Cookie cookie = new Cookie("sessionID", sessionID);
cookie.setDomain("secure.example.com");


Cookie的参数太笼统path如果cookie中的路径不正确(例如/),则会出现与共享域相同的问题:一个应用程序中的漏洞会暴露同一域中的其他应用程序。



在下面的示例中,安装在URL上的应用程序http://pages.example.com/forum将cookie设置为路径/:



Cookie cookie = new Cookie("sessionID", sessionID);
cookie.setPath("/");


然后,在该地址安装的恶意应用程序http://pages.example.com/evil可能会破坏用户的Cookie。攻击者还可以执行cookie中毒攻击:创建了共享路径/evil的cookie将覆盖cookie /forum



安全选项:



Cookie cookie = new Cookie("sessionID", sessionID);
cookie.setPath("/forum");


Cookies不在SSL之上应用程序在不将标志设置为secure相等的情况下创建cookie true这些Cookie可以通过HTTP未经加密地传输。漏洞“使用不安全的HTTP协议”会立即被召回。



在以下示例中,应用程序创建不带标志的cookie secure



Cookie cookie = new Cookie("emailCookie", email);
response.addCookie(cookie);


如果应用程序同时使用HTTPS和HTTP,则在没有安全标志的情况下,作为HTTPS请求的一部分创建的cookie将在后续的HTTP请求中不加密地传输,这可能导致应用程序受到损害。如果cookie包含有价值的数据,尤其是会话ID,则尤其危险。



安全选项:



Cookie cookie = new Cookie("emailCookie", email);
cookie.setSecure(true);
response.addCookie(cookie);


有效期不限的Cookie如果您将有价值的Cookie存储太长时间,则攻击者可以访问它们。



默认情况下,使用非持久(会话)cookie,这些cookie不会保存到磁盘,并且在关闭浏览器后会被删除。但是,Web应用程序的开发人员可以指定cookie的保留时间-在这种情况下,它们将被写入磁盘并在浏览器重启和计算机重启之间保存。这使攻击者有很长的时间来制定攻击计划。



开发人员建议:确保该应用程序不会创建长期存在的Cookie:



Cookie cookie = new Cookie("longCookie", cookie);
cookie.setMaxAge(5*365*24*3600); // 5 !


按照OWASP准则提供合理的最大时间限制



信息泄漏



对于应用程序用户而言,这可能是最敏感的漏洞类型。



通过错误页面泄漏外部信息。该应用程序使用标准错误页面,其中可以包含有关系统配置的信息。



错误消息和调试信息将写入日志,显示到控制台或传输给用户。攻击者可以从错误消息中了解系统漏洞,这将使他的生活更轻松。例如,数据库错误可能表示对SQL注入不安全。有关操作系统版本,应用程序服务器和系统配置的信息,将使黑客更容易计划对应用程序的攻击。



有价值信息的外部泄漏...在这种情况下,我们正在谈论通过网络将有关应用程序的技术信息传输到另一台计算机上的信息。通常,外部泄漏比内部泄漏更危险。







内部有价值信息的泄漏。该操作机制类似于前面的两种类型的泄漏,但是在这种情况下,有关系统的信息将写入日志或显示在用户屏幕上。



机密数据泄漏。用户的宝贵个人数据从不同的来源进入应用程序:从用户本人,从各种数据库,从第三方存储。有时,这些数据没有被标记为机密,或者证明它本身不是有价值的,而是仅在特定上下文中才有价值。



当应用程序安全性和个人数据隐私相互矛盾时,就是这种情况。出于安全原因,建议在系统中记录有关活动的详细信息,以检测恶意活动。相反,从数据隐私的角度来看,在记录机密信息时,其泄露的风险更大。通常,确保应用程序用户个人数据的机密性是更高的优先级。



后记



本文考虑的漏洞类型涵盖了用不同编程语言编写的应用程序中的大多数“通用”漏洞。但是,某些语言有其自身的特定漏洞。但这已经是另一篇文章的主题。最后,请记住:在创建应用程序时,请不要忘记遵循上述建议,仔细阅读文档并使用专用软件检查应用程序中的漏洞。



作者:Solar appScreener分析部主管Elizaveta Kharlamova




All Articles