
Injection SQL
是时候看看 SQL 注入了。长期以来,它一直是OWASP前十名中无可争议的王者,我们说的是连续几年。尽管它已经过时了(大概已经超过20年了),尽管它比该榜单的头名略有下降,但它仍然是一个非常受欢迎和危险的漏洞。
作为网络安全漏洞,SQLi(SQLi)仍然是攻击者最常使用的 “黑客攻击” 技术之一,因为它允许他们操纵数据库并从中提取关键信息。更令人担忧的是,攻击者可以使自己成为数据库服务器的管理员,并做一些非常毁灭性的事情,例如破坏数据库、操纵事务、泄露数据以及使其容易受到更多问题的影响。
让我们快速来看看它是如何发生的
SQL(或结构化查询语言)是用于与关系数据库通信的语言;它是开发人员、数据库管理员和应用程序用来管理每天生成的大量数据的查询语言。
在应用程序中,存在两个上下文:一个用于数据,另一个用于代码。代码上下文告诉计算机要执行什么并将其与要处理的数据分开。当攻击者输入被 SQL 解释器错误地视为代码的数据时,就会发生 SQL 注入,从而使他们能够从应用程序中收集有价值的信息。
SQL 注入攻击的影响
SQL 注入可能对任何 Web 应用程序造成极大的危害,并且一直是许多备受瞩目的漏洞背后的首选技术,因为它为攻击者提供了对关键数据的未经授权的访问。他们可以看到很多信息,从用户名和密码到信用卡详细信息和个人识别码。
在获得这些数据的访问权限后,攻击者可以接管账户、重置密码、进行长时间的在线购物或实施其他(更糟糕的是)类型的欺诈。
但是,SQLi 最令人担忧的事情可能是,如果未被发现,攻击者可以长时间维护系统的后门。可以想象,无论后门打开多长时间,这都将导致重复的数据泄露。可怕的东西。
让我们来看几个例子,以更好地理解它的实际效果。
SQLi 示例
SQLi 包括各种可以应对不同情况的漏洞技术。以下只是一些最常见的 SQLi 示例:
SQLi 类型
好的,现在让我们来看看三种不同的 SQLi 类型。
带内 SQLi
这是最常见、最简单、最有效的 SQL 注入类型之一。在这种类型的攻击中,使用相同的通信渠道进行攻击和检索结果或结果。
以下是两种类型的带内 SQLi 攻击:
- 基于联盟的 SQLi -基于联合体的攻击利用联合运算符组合两个或多个 SQL 查询(例如 SELECT 语句)来获取所需信息并生成 HTTP GET 响应。
- 基于错误的 SQLi -攻击者利用数据库的错误消息来了解其结构。在此攻击中,攻击者可能会发送虚假请求或执行操作以使服务器显示错误消息,以便他们可以接收数据库信息。这就是为什么开发人员避免在实时环境中发送错误或日志消息很重要的原因;相反,它们应该以有限的访问权限进行存储。
推断式 SQLi
推断或盲目的 SQLi 攻击更为复杂,可能需要更多时间才能利用。最重要的是,攻击者实际上并没有立即得到攻击结果,这就是盲目攻击的原因。
攻击者通过 HTTP 请求将有效载荷发送到数据库服务器以重构用户的数据库,然后他们观察应用程序的响应和行为,以查看攻击是否成功。
以下是两种推断式 SQLi 攻击:
- 基于布尔值的 Blind SQLi -在此攻击中,向数据库发送查询以获得布尔值(真或假)结果,攻击者观察 HTTP 响应以预测布尔结果。
- 基于时间的盲人 SQLi -在本次攻击中,攻击者向数据库发送查询,使其等待几秒钟后再发送响应,攻击者根据 HTTP 请求的响应时间判断查询结果。
带外 SQLi
这是一种更为罕见的 SQLi 攻击,它取决于数据库服务器启用的功能。它发生在攻击者无法真正使用其他攻击类型的情况下。
例如,如果他们不能使用相同的通信渠道进行带内攻击,或者 HTTP 响应不够清晰,无法计算出查询结果。
此外,这种情况并不常见,因为它严重依赖数据库服务器发出 HTTP 或 DNS 请求向攻击者发送所需数据的能力。
如何防御 SQLi
值得庆幸的是,SQL 注入如此古老且如此普遍,其中的一线希望是有一些方法可以防止这种情况发生。使用这类预防技术不仅是一种良好的编码习惯,而且还能真正增强组织抵御SQLi的安全性。
有多种方法可以保护数据库服务器免受此类攻击,例如输入验证、使用 Web 应用程序防火墙 (WAF)、保护数据库、雇用第三方安全团队或系统以及编写万无一失的 SQL 查询。
让我们看一个通过使用上述安全措施之一来防止 Python 中的 SQL 注入的示例。
Python 示
在此示例中,攻击者将使用基于布尔值的盲 SQL 注入从系统中获取重要信息。
Python:脆弱
假设数据库中有一个名为 “sample_data” 的表。此表存储应用程序用户的用户名和密码。
现在允许用户通过以下命令从该数据库表中查找值:
导入 mysql 连接器
db = mysql.connector.conn
#Bad 练习。避免这种情况!这只是为了学习。
(host= “localhost”,user= “新用户”,passwd= “pass”,db= “样本”)
cur = db.cursor ()
name = raw_input ('输入名称:')
cur.execute(“从 sample_data 中选择 * 其中 Name = '%s';”% name)为 cur.fetchall () 中的行:打印(行)
db.close ()
Injection SQL
在这里,如果用户在搜索中输入一个名字,例如 Alicia,则输出不会有问题。
但是,如果用户输入像 Alicia'; DROP TABLE sample_data 这样的内容,它将对数据库产生重大影响。
Python:补救
应将 SQL 语句更改为以下语句以防止攻击发生:
cur.execute (“从样本数据中选择 * 其中 Name = %s;”, (name,))
现在,即使用户尝试向其中注入任何 SQL 查询,系统也会将用户输入视为字符串,并且仅将用户输入视为名称的值。
这种简单的更改可以防止未来查询中的恶意活动,并保护系统免受用户输入攻击。
Java 示例
在本示例中,我们还将使用一个名为 “sample_data” 的数据库表来存储应用程序的用户数据。
基本登录页面需要用户名和密码,而 java 文件(一个 servlet (LoginServlet))会根据数据库对其进行验证以允许登录操作。
Java:漏洞示例
使用数据库中的 “sample_data” 表,系统允许用户通过使用其凭据作为输入来执行登录操作。
LoginServlet 文件中有一个用于容纳登录操作的查询,即:
//不好的例子。不要使用字符串连接。
字符串查询 = “从 sample_data 中选择 * 其中 username='” + 用户名 + “'和密码 ='” + 密码 + “'”;
连接 conn = null;
语句 stmt = null;
试试 {
conn = driverManager.getConnection(“jdbc: mysql: //127.0.0. 1:3306 /用户”、“root”、“root”、“root”);
stmt = conn.createStatement ();
ResultSet rs = stmt.executeQuery(查询);
if (rs.next ()) {
//如果找到匹配项,则登录成功
成功 = 真实;
}
} catch(异常 e){
e. printStackTrace ();
} 最后 {
试试 {
stmt.close ();
conn.close ();
} catch(异常 e){}
}
如果(成功){
response.sendredirect (” home.html “);
} 其他 {
response.sendredirect (” login.html?错误 =1");
}
}
以下是用户登录查询:
从 sample_data 中选择 * 其中 username='username',密码 ='password'
Injection SQL
如果输入有效,系统将完美运行。例如,我们会再次说用户名是 Alicia,密码是机密的。
系统将使用这些凭据返回用户的数据。但是,攻击者可以使用 Postman 和 cURL 来操纵用户请求进行 SQL 注入。
例如,黑客可以发送虚拟用户名(Alicia)和密码 “或 '1'='1”。
在这种情况下,用户名和密码将不匹配,但条件 “1'='1” 将始终为真,因此登录操作将成功。
Java:预防
为了预防起见,我们需要修改登录验证代码并使用 PreparedStatement 代替语句来执行查询。此更改将防止在查询中串联用户名和密码,并将它们视为 setter 数据,以避免 SQL 注入。
以下是 LoginValidation 的修改代码:
字符串查询 = “从 sample_data 中选择 * 其中 username=?然后密码 =?”;
连接 conn = null;
PreparedStatement stmt = null;
试试 {
conn = driverManager.getConnection(“jdbc: mysql: //127.0.0. 1:3306 /用户”、“root”、“root”、“root”);
stmt = conn.PrepareStatement(查询);
stmt.setString (1,用户名);
stmt.setString (2,密码);
ResultSet rs = stmt.executeQuery ();
if (rs.next ()) {
成功 = 真实;
}
rs.close ();
} catch(异常 e){
e. printStackTrace ();
} 最后 {
试试 {
stmt.close ();
conn.close ();
} catch(异常 e){
}
}
在这种情况下,PreparedStatement、设置器和底层 JDBC API 将处理用户输入并防止 SQL 注入。

例子
现在,我们将再看几个不同语言的示例,以更好地理解实际情况。
C#-不安全
这个例子不安全,因为它使用了 fromRawSQL。此方法不会绑定参数,也不会尝试逃避它们。因此,应不惜一切代价避免使用这种方法。
var 博客 = 上下文帖子
.fromRawSQL(“从状态 = {0} 且作者 = {1} 的帖子中选择 *”,状态,作者)
.toList ();
C#-安全
这个示例是安全的,因为 fromSQLInterpolated,它采用插值并将其参数化。
尽管这通常是安全的,但它有与不安全的 “fromRawSQL” 非常相似的风险。
var 博客 = 上下文帖子
.fromSQLInterpolated($ “从状态 = {州} 且作者 = {作者} 的帖子中选择 *”)
.toList ();
Java-安全:Hibernate-命名查询 + 本机查询
Hibernate提供了两种通过其 “原生查询” 和 “命名查询” 以安全方式构造查询的方法。两者都允许为参数指定位置。
@NamedNativeQuery (
名称 = “按州和作者查找帖子”,
查询 =
“选择*” +
“来自帖子” +
“WHERE 状态 =: 状态” +
“和作者 =: 作者”,
结果类 = Post.class)
java
列表<Post>帖子 = session.createNativeQuery
“选择*” +
“来自帖子” +
“WHERE 状态 =: 状态” +
“和作者 =: 作者”)
.addEntity (Post.class)
.setParameter(“状态”,状态)
.setParameter(“作者”,作者)
.list ();
Java-安全:jplq
通过在 jplq 存储库接口上注释 `Query` 属性,它们可以采用多种形式,并且是参数化的。
@Query (“从帖子中选择 p 其中 u.state =?1 还有 u.author =?2 英寸)
帖子 findPostByStateandAuthor(字符串状态,int 作者);
@Query(“从帖子中选择 p,其中 u.state =: state 和 u.author =: author”)
用户按州和作者查找帖子 (@Param (“状态”) 字符串状态,@Param (“作者”) int author);
Javascript-安全:p
使用 `pg`库时,`query`方法允许通过其第二个参数提供参数值来进行参数化。
const {posts} = await db.query ('从帖子中选择 * 其中 state = $1 且作者 = $2 ',[州,作者])
Javascript-安全:续集
`sequelize`库提供了一种通过其第二个参数对查询进行参数化的方法,该参数采用查询的设置。这包括按名称或索引作为参数绑定到查询的值列表。