SQL(结构化查询语言)注入是众所周知的软件弱点和安全漏洞,如果不是的话,也是最出名的漏洞之一。尽管享有盛名,但如何防止SQL注入仍然是主要漏洞之一,并且攻击持续增长。
查找SQL注入
根据OWASP Top 10,注入漏洞(其中SQL注入是其中一种)是Web应用程序安全性的头号问题。SQL注入在CWE Top 25中排名第六。其他类型的安全漏洞示例包括:
- 指令注入(CWE-77)
- 操作系统命令注入(CWE-78)
- 冬眠注射(CWE-564)
- 表达语言注入(CWE-917)
所有这些漏洞都有一个共同的属性。利用来自系统外部的数据,用户或文件输入或任何潜在危险功能来利用它们。
幸运的是,SQL注入可以通过工具静态和动态地检测到。但是,您永远无法确定是否全部抓住了它们。防止SQL注入也是减少这些漏洞的频率和影响的关键。结合了漏洞检测和预防功能的成熟DevSecOps流程很可能会捕获并阻止这些类型的漏洞进入已发布的产品。
什么是SQL?
SQL是一种特定于域的语言,旨在管理关系数据库。关系数据库将数据显示为行和列中的表的集合。每行都有一个提供与其他表的关系的键。这是表“user”的示例:
与CWE Top 25中常见的漏洞枚举有关的内存错误
SQL是用于管理,查询和处理关系数据库中数据的首选语言。它定义数据库创建中的表和关系。对于大多数日常使用,开发人员将SQL用于“CRUD”—创建、读取、更新和删除数据。
为什么SQL可利用?
通用编程语言不包括对SQL的支持。通过数据库供应商提供的API访问数据库命令。在许多情况下,SQL命令以字符串形式发送,API会解释该字符串并将其应用于数据库。以下是一些简单的SQL查询:
典型的SQL查询采用以下形式:
- Select (something) from (somewhere) (optional condition)
以上表为例,从姓氏为“Smith”的行中检索电子邮件,使用以下SQL语句:
- Select email from user where lastname = ‘Smith’
输出如下:
- Smith1234@mail.com
- John.smith@mail.netSmith1234@mail.com
使用Web表单(见下文)从用户那里获取输入是Web应用程序中的一种常见用例。 用户在“名称”字段中输入的数据,例如,用于根据收到的输入来形成SQL查询。 考虑以下简单的Web表单:
该软件处理表单并将值分配给变量,如下所示:
- String formName = request.getParameter(Name);
输入为“名称”的字符串用于使用该用户输入来组合查询:
- String myQuery = “select message from user where email = ‘” + formName +”’;”
使用此构造的查询:
- Select message from user where email= ‘Smith1234@mail.com’;
其输出(以上表为例)如下:
- Hello
- How are you
希望很容易看到这一切都会出错。假定直接在字符串中使用用户输入,那么了解SQL语法的人可以轻松地操纵它来生成SQL查询。考虑以下示例:
使用上面相同的表格,有人在电子邮件字段中输入“Smith1234@mail.com”或“1” =“1”。
相同的代码将组装以下SQL查询字符串:
- Select message from user where email = ‘Smith1234@mail.com’ or ‘1’=’1’;
添加看似无害的内容(例如“or 1=1”)会更改查询的逻辑,并可能通过返回表中称为“用户”的所有行来泄漏数据。在这种情况下,向您显示表中每个用户的消息。严重的隐私问题,在某些司法管辖区或环境中也可能存在法律问题,例如GDPR,HIPAA或CCPA。
上面的查询以以下意外输出结束:
- Hello
- Password 1234
- How are youDon’t tell anyone
- Wassup
SQL注入的工作方式
SQL注入(和其他类型的注入漏洞)的基本要点是在SQL查询字符串中使用来自应用程序外部的未经检查的数据,例如用户输入文本。CWE 89的描述:“SQL命令中使用的特殊元素的不适当中和(SQL注入)”更精确地定义了以下内容:
“在用户可控制的输入中没有充分删除或引用SQL语法的情况下,生成的SQL查询可能导致这些输入被解释为SQL而不是普通用户数据。这可用于更改查询逻辑以绕过安全性检查,或用于插入修改后端数据库的其他语句,可能包括执行系统命令。”
CWE数据库中的相同条目(CWE 89)提供了此攻击的另一个简单示例。假设应用程序代表用户“wiley”进行查询,并且用户以包含SQL指令的方式构造输入,例如:
- name'; DELETE FROM items; --
如果此应用程序不对此输入进行任何有效性检查,则会构造如下查询:
- SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name';
- DELETE FROM items;
- --'
如果此攻击成功,它将删除表项中的所有数据,从而对数据库造成破坏。任何有效的SQL命令都可能以这种方式执行。这是写/修改攻击的示例,其目的是破坏数据库或插入不需要的信息。前面的示例(“or 1=1”)是读取攻击,其目的是数据泄漏。
数据库服务器的许多实现都接受分号作为命令分隔符,这使得这种SQL注入非常危险。尾部的“–”表示文本的其余部分为注释,从而迫使SQL解释器忽略尾部的引号,否则将导致语法错误。欺骗组合查询字符串的方法有多种。有时以开发人员无法想象的方式。
防止SQL注入的缓解措施
开发人员应实施几种缓解措施。首先,安全立场应考虑所有来自不受信任的应用程序外部的数据。以下是典型的缓解策略:
- 将准备好的语句与参数化查询一起使用。
- 使用存储过程。
- 白名单输入验证。
- 转义所有提供的输入。
这些在OWASP速查表的SQL注入中有更详细的描述。
测试SQL注入
一种典型的安全性方法是,在集成软件运行时,作为常规质量检查操作的一部分,执行各种类型的安全性测试。不幸的是,功能测试不会尝试将漏洞利用插入用户输入字段中,因为大多数测试人员并不认为自己是坏演员。
除了传统上他们没有时间或方向的事实之外。手动测试注入类型漏洞也很困难,因为它需要尝试许多不同的输入组合。这是开始进行模糊测试或模糊测试的地方。它会创建随机,意外和无效的数据作为被测应用程序的输入。模糊测试是渗透测试的一部分,因为目标是通过公开的界面公开安全漏洞。
渗透测试
渗透性测试(以及扩展性的模糊测试)是有益的,因为它可以发现贯穿整个过程的安全性问题并揭示重要的安全性问题。但是,像所有动态测试一样,它完全取决于测试,代码和API覆盖的数量,以完全测试所有可能的排列和组合。渗透测试取决于功能测试的完整性,通常在用户界面级别进行。因此,请务必通过API测试和SAST支持渗透测试,以确保您的工作透彻。
API测试
API测试通过消除对脆弱且耗时的UI测试的依赖,有助于向左移动功能和安全性测试。API层是许多应用程序功能所驻留的地方,并且测试在此级别进行更改时更具弹性,并且更易于自动化和维护。
API级别的渗透测试
使用诸如Parasoft SOAtest之类的工具可以进行API级别的渗透测试以暴露SQL注入,在这些工具中,可以从现有功能测试中创建自动模糊测试,从而行使应用程序的业务逻辑。Parasoft SOAtest与著名的渗透测试工具Burp Suite集成在一起。
使用Parasoft SOAtest执行功能测试方案时,将捕获测试中定义的API调用以及请求和响应流量。每次测试中的Burp Suite分析工具都会将流量数据传递到Burp Suite应用程序的一个单独的运行实例,该实例将根据其在流量数据中观察到的API参数,使用自己的启发式方法对API进行渗透测试。
然后,Burp Suite分析工具将获取Burp Suite发现的任何错误,并将其报告为SOAtest中与访问API的测试相关的错误。Parasoft SOAtest结果将报告到Parasoft的报告和分析仪表板中。有关其他报告功能。
要了解有关此集成的更多信息,请参阅我们以前的渗透测试文章。有关Portswigger关于使用Burp进行SQL注入的更多信息,请查看其文章。以下是与Burp集成的工作方式的表示:
将这种类型的渗透测试集成到您的CI/CD流程中是防御SQL注入和其他类型漏洞的重要组成部分。
渗透和模糊测试无疑是DevSecOps中的重要过程,并且至关重要。但是,这提出了问题。
- 测试检测到安全漏洞时会发生什么?
- 当软件团队发现其大部分用户输入处理不安全时,会发生什么?
- 它当然需要修复,但是要付出什么代价?
在开发的后期发现严重的安全问题会导致严重的成本和延迟。预防和检测是将安全操作进一步转移到更便宜且更容易修复的地方的关键。
将SQL注入的检测和消除向左移动
在软件开发中采用DevSecOps方法意味着将安全性集成到DevOps管道的各个方面。正如团队尽早在SDLC中推进代码分析和单元测试等质量流程一样,安全性也是如此。
如果团队更广泛地采用这种方法,则SQL注入可能已成为过去。攻击的增加意味着它尚未发生。无论如何,让我们概述一种可以尽早防止SQL注入的方法。
与修补(和道歉!)已发布的应用程序相比,查找和修复潜在的SQL注入(以及其他注入漏洞)可节省大量时间。单个重大事件可能使公司损失20万美元或更多。小型企业发生许多事件。一次攻击会造成严重的财务压力,更不用说有关违规披露和保护个人身份信息的潜在监管问题了。
下面概述的“检测和阻止”方法基于将减轻SQL注入的风险转移到开发的最早阶段,并通过通过静态代码分析进行检测来增强此功能。
如何检测SQL注入
检测SQL注入依赖于静态分析来在源代码中找到这些类型的漏洞。检测发生在开发人员的桌面和构建系统中。它可以包括现有的、旧的和第三方代码。
连续检测安全问题可确保发现以下所有问题:
- 开发人员错过了IDE。
- 存在于比您的新的检测预防方法还早的代码中。
推荐的方法是信任但验证模型。安全性分析在IDE级别进行,开发人员在该级别上根据收到的报告做出实时决策。接下来,在构建级别进行验证。理想情况下,构建级别的目标不是找到漏洞。这是为了验证系统是否干净。
例如,以Parasoft的演示应用程序Parabank为例。com.parasoft.parabank.dao.jdbc.internal中的StockDataInserter.java文件中可能存在SQL注入:
- …
- final String sql = sb.toString();
- rows = (nextId - lastId) / JdbcSequenceDao.OFFSET;totalRows += rows;getJdbcTemplate().update(sql);…
Parasoft JTest在生成时生成的报告如下:
详细信息如下:
- Call to a dangerous method
- StockDataInserter.java (96): getJdbcTemplate().update(sql); *** Tainted data: SQL
追溯到先前发现源污染数据(来自应用程序外部的未经检查、未经验证的输入)的位置:
- Tainting point
- StockDataInserter.java (47): return getJdbcTemplate().query(SQL, new
- ResultSetExtractor<List<String>>() { *** Tainted data:
- getJdbcTemplate().query(SQL, new ResultSetExtractor<List<Str...return
- symbols; } })
在与SQL注入的持续斗争中,开发人员需要认真对待这些警告。在SQL查询中任何使用未经验证的数据都是严重的风险。即使当前的形式可能不是一个特定的警告问题,以后的重构也可能会暴露这些漏洞。检查查询字符串中使用的所有数据!
实际上,开发人员应验证来自应用程序外部的任何数据,以确保它们符合预期的格式和内容。转向“始终验证”的理念以及依靠安全编码而不是安全测试的过程会大大提高应用程序的安全性。开始加强代码,以防止SQL注入首先被封装。
何时以及如何防止SQL注入
防止SQL注入的理想时间和地点是开发人员在其IDE中编写代码时。采纳安全编码标准(例如C和C++的SEI CERT C和Java和.NET的OWASP Top 10或CWE Top 25)的团队都具有警告未验证SQL查询输入的准则。
在新创建的代码上运行静态分析既快速又简单,并且很容易集成到CI/CD流程中。在现阶段调查所有安全警告和不安全的编码做法是一个好习惯,以防止将此代码写入到内部版本中。
检测不良编码实践的同等重要的部分是报告的实用性。重要的是要能够理解静态分析违规的根本原因,以便快速、有效地解决它们。这就是Parasoft的C/C++test,dotTEST和Jtest等商业工具的发源地。
Parasoft的自动测试工具可对警告进行完整跟踪,在IDE中进行说明,并连续收集构建信息和其他信息。这些收集的数据以及测试结果和指标可提供对团队编码标准合规性的全面了解。它还显示了总体质量和安全状态。
这些报告包括风险模型,这些模型是OWASP,CERT和CWE提供的信息的一部分。这样,开发人员可以更好地了解该工具报告的潜在漏洞的影响以及应优先考虑哪些漏洞。在IDE级别生成的所有数据都与上面概述的下游活动相关。
总结
臭名昭著的SQL注入漏洞继续困扰着Web应用程序。尽管知道它是如何工作和可以被利用的,但它仍然很普遍。有关最新示例,请参见IoT Hall of Shame。
我们提出一种预防和检测方法来补充主动安全测试。这种方法可防止在写入代码之前尽早在SDLC中进行SQL注入。防止在IDE上进行SQL注入并在CI/CD管道中检测到它们是将其路由到软件之外的关键。最后,在使用渗透测试技术的测试过程中查找并修复这些错误。
与SQL注入(以及其他受污染数据的利用)的斗争仍在继续。精明的团队可以在他们现有的工作流程中以正确的流程、工具和自动化扭转局面。