SQL注入题解与知识

题目一

1
select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;

题目解析

简单的 SQL 注入,Payload 如下:

1
' or 1=1 ; -- 

没有任何过滤就拼接 SQL 语句,导致漏洞产生。下面分析 Payload 的效果:

1
select username,password from user where username !='flag' and id = '"' or 1=1 ; -- "' limit 1;
  • 单引号 ' 与前面的单引号闭合,使后面的 or 1=1 ; -- 被视为 SQL 语句而非字符串。
  • or 1=1 与前面的表达式组合得到 username !='flag' and id = '"' or 1=1
  • where 一般情况下检查表中的每一行,并针对每行计算 where 后的整个表达式的结果,若真则返回该行,反之则跳过。
  • 由于 and优先级or 高,所以最终计算结果恒为 1where 1 会匹配并返回所有行。
  • 分号 ; 结束该句,防止语法错误。
  • 注释符 -- 注释掉后面的双引号、单引号与 limit 1 。防止语法错误,以及绕过原语句中返回结果只有一行的限制。(最后面一定要有空格,关于注释,参考另一篇简单讲解 SQL 注释符的笔记)

题目二

SQL 部分

1
select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;

PHP 片段

1
2
3
4
//检查结果是否有flag
if($row->username!=='flag'){
$ret['msg']='查询成功';
}

题目解析

先探测列数:

1
' order by 1 -- 
  • order by :在 SQL 里,ORDER BY 是用于对查询结果进行排序的子句。语法如:
1
ORDER BY column1 [ASC|DESC], column2 [ASC|DESC], ...;

column1 为列,可以是列名,也可以是索引(1,2,3…); [ASC|DESC] 为升降排序的可选项。

  • order by 1 则是测试列数,若无报错(这里应该是空结果,因为 select username,password from ctfshow_user2 where username !='flag' and id = '"' order by 1 -- ,不太可能有某个字段的值为 " )则递增数字,直到报错为止,那么列数即为未报错的最大数字

也可以使用联合查询来探测:

1
' union select 1,2,3,4--  --递增列数
  • union select :在 SQL 中,SELECT 之间用 UNION 连接起来,结果集合并成一个结果集。
    合并要求:

    • 列数必须相同:所有 SELECT 语句的列数必须一致。
    • 数据类型兼容:对应列的数据类型应该兼容(或可隐式转换)。
    • ORDER BY 只能放在最后:合并结果只有一张表,所以 ORDER BY 要放在最后给整张表排序。列名通常继承自第一张表,如果要用列名排序,需要用第一张表的列名。也可以用索引第一张表的别名排序。

    基本语法:

    1
    2
    3
    SELECT column1, column2 FROM table1
    UNION [ALL]
    SELECT column1, column2 FROM table2;

    UNION 默认去重,使用 UNION ALL 合并结果时不去重。

  • union select 1,2,3,4 测试列数。可以把 select 1,2,3,4 看作联合查询时自定义添加一行内容 1,2,3,4由于联合查询时列数必须相同,所以当无报错时,说明列数正确;如果报错,则修改列数再次测试,直到不报错。

确定列数后,利用联合查询,按照下列步骤获得 flag :

1
2
3
4
' union select 1,database()--  --返回数据库名
' union select 1,group_concat(table_name) from information_schema.tables where table_schema='<库名>' -- --获取数据库里的表名
' union select 1,group_concat(column_name) from information_schema.columns where table_name='<表名>' -- --获取表内字段名
' union select 1,group_concat(<字段名>) from 表名 -- --获取字段值(不要用username字段,会被后续的PHP脚本过滤掉)
  • database() :MySQL内置函数,返回当前连接的数据库名。
  • information_schema :虚拟的数据库,并不实际存储数据,由数据库系统根据元数据动态生成。包含数据库中所有数据库的信息、数据库中所有表的详细信息、数据库中所有列的信息与关于索引的元数据以及其他一些信息等等。
  • information_schema.tables :包含了数据库中所有表的信息,如表名、表类型、存储引擎、行数、创建时间、更新时间等。此处用于获取当前数据库中有哪些表
  • where table_schema='<库名>'table_schema 是表中的一个字段,表明该表位于哪个数据库。
  • information_schema.columns :大致同上,此处用于获取目标表中有哪些列
  • where table_name='<表名>' :大致同上,表明列在哪张表中。
  • group_concat() :将多行结果合并成逗号分隔的字符串,全部表示在一行中。

整个过程,就是利用联合查询与数据库的特性得到各种信息,最后得到 flag 。