深入探索MS SQL Server 2000網路連接的安全問題
建立時間:2001-11-13
文章屬性:原創
文章來源:refdom
文章提交:refdom (refdom_at_263.net)
下面我們要說的,並不是SQL Server存在的漏洞,而只是一些安全缺陷,存在一些問題,當然這些問題是SQL Server一產生的時候就存在的。
1、MS SQL Server的密碼明文傳輸缺陷
很倒黴,我沒有在微軟發布SQL Server的時候做下面的分析。當我吃驚地發現SQL Server竟然是使用明文進行密碼傳輸的時候,我就馬上去查閱是否有這些資料。可惜已經早早有人提出能夠用sniffer擷取SQL Server的密碼了。不過,既然微軟這麼大膽,我們還是去看看,分析分析這個缺陷。
當然,SQL Server的串連過程還是先進行TCP串連的三向交握,同伺服器建立串連過後,然後進行TDS(tabular data stream)協議的資料交流,可惜的是我一直沒有找到TDS協議的具體描述,只有一些片段,所以,都只能一點點地對著資料報分析。誰有TDS協議(SQL Sevrer)的具體描述一定送我一份哦。
直接到login包吧,你會發現,你的使用者名稱完全是明文的,而密碼還不是。當你改變密碼的時候,你可以看出,相同字元的編碼是一樣的,密碼字元之間用一個相同的字元作為分隔“a5”。呵呵。所以,最傻的辦法就是我做的這樣,一個字元一個字元地改變密碼,然後得到所有字元的對應編碼(我不會解密)。
我使用的是SQL Server 2000 。我這裡列舉一部分得到的對應編碼。大家可以很容易得到完整的字元編碼。
“a”——b3 “A”——b1
“b”——83 “B”——81
“c”——93 “C”——91
“d”——e3 “D”——e1
“e”——f3 “E”——f1
等等。
對了,在SQL Server中不支援用 '、" 等等符號作為密碼,如果你在用這些字元作為密碼的話,那就糟糕了,你永遠也登陸不上了,除非更改密碼。
在TDS資料報的頭中規定了一個位元組來表示資料報類型,我探測到的是0x10,而我得到的資料說是用0x02來表示Login的資料報,可能是MS SQL Server在協議上有一些改動,因為Sybase也是使用的TDS協議的,那麼Sybase可能也是使用的明文傳輸吧,我手裡沒有Sybase。可惜沒有TDS協議的完整描述,所以,我也偷懶沒有寫出專門獲得SQL Server帳號和密碼的sniffer程式,誰能給我TDS協議的詳細描述,請email: refdom@263.net。
不過MS對SQL Server的傳輸還是提供加密辦法的,你可以使用SSL來加密。於是我也試了試,可以在執行個體屬性的網路設定裡面選擇強制協議加密,然後要求你重新啟動SQL Server,呵呵,然後呢,你啟動起來了嗎?哈哈,我可是吃了虧的。因為沒有SSL認證,所以,你一啟動就錯誤,事件記錄中說明是沒有提供有效認證。重新安裝吧。該死的MS也不在我選擇的時候提醒一下。
嗅探得到資料庫帳號意味著什嗎?如果能夠得到SA帳號呢?哈哈,有興趣可以看看我前些天寫的《從IIS轉到SQL資料庫安全》。
2、明文的資料轉送缺陷
如果沒有使用加密的協議的話,那麼整個資料庫的網路資料都是沒有加密的,而且是明文傳輸。無論是從用戶端發送命令還是從伺服器端得到結果,都是明文傳輸的。看來,如果你用加密的協議,微軟是不會為你做任何安全防護的。我想,目前國內使用的SQL Server資料庫差不多都是沒有使用SSL來加密,看來在網路上能得到不少東西。
3、神秘的1434連接埠和伺服器資訊明文傳輸缺陷
對於SQL Server2000來說,開啟SQL Server用戶端準備串連,當拉開伺服器列表的時候,整個區域網路所有的SQL Server伺服器都被列出來了。於是我發現,從我自己的機器(192.168.0.1)上從1434連接埠廣播(192.168.0.255)了這個UDP包,然後,整個區域網路中的SQL Server伺服器都開始響應這個UDP資料包,這時,我的用戶端能夠得到所有伺服器資訊。
這就是用戶端進行串連的過程:當用戶端串連到伺服器時,應用程式請求串連遠端電腦,Dbnetlib.dll 將開啟到串連中所指定的電腦網路名稱上的 UDP 連接埠 1434 的串連。所有運行 SQL Server 2000 的電腦都監聽此連接埠。當一個用戶端 Dbnetlib.dll 串連到該連接埠時,伺服器將返回一個監聽伺服器上啟動並執行所有執行個體的資料包。對於每個執行個體,該資料包報告該執行個體正在監聽的伺服器 Net-Library 和網路地址。應用程式電腦上的 Dbnetlib.dll 收到該資料包後,選擇在應用程式電腦和 SQL Server 執行個體上都啟用的 Net-Library,然後串連為此資料包中的 Net-Library 列出的地址。
通過1434連接埠傳輸特定的UDP資料包,然後伺服器開始回應,所有這些都是明文傳輸的,我們可以很容易探測一個IP地址的1434連接埠,獲得該IP地址上啟動並執行SQL Server的相關資訊。這些資訊包括:主機名稱、執行個體名稱、版本、管道名稱以及使用的連接埠等。這個連接埠是微軟自己使用,而且不象預設的1433連接埠那樣可以改變,1434是不能改變的,呵呵,那麼我們為了安全,去改變這個1433連接埠能起什麼作用呢?
我們可以來捕獲這些資料報,可以發現,通過1434連接埠的資料非常簡單,用戶端僅僅簡單地發送了02一個位元組出去。不過多次捕獲,發現有時候發送的是 03。於是我就用下面程式一個一個測試,發送其他資料。不過最後只有02、03、04有回應。看來這三種位元組用來做SQL Server探測的。而且你可以發送 02 00 00,也可以發送 02 00 00 00 00等等都能夠得到SQL Server的回應,但是發送 02 03就不可以了。
下面是一個利用1434進行探測的程式,可以探測單個IP,也可以用來探測整個區域網路的資料庫伺服器。
////////////////////////////////////////////////////////////
//
// SQLPing by refdom
//
// Author: refdom. From Chip Andrews
// Email: refdom@263.net
//
////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <string.h>
#include <stdio.h>
#include <winsock2.h>
void decode_recv (char *buf, int size)
{
int index;
int counter = 0;
for (index = 3; index < size; index++)
{
if ((buf[index] == ';') && (buf[index+1] != ';'))
{
//Look for a semi-colon and check for end of record (;;)
if ((counter % 2) == 0)
{
printf(":");
counter++;
}
else
{
printf("/n");
counter++;
}
}
else
{
if (buf[index] != ';')
{
// If an end of record (;;), then double-space for next instance
printf("%c",buf[index]);
}
else
{
printf("/n");
}
}
}
printf("/n");
}
void listen (void* v)
{
static const unsigned int buffersize = 64000;
static char buffer [buffersize];
SOCKET s = (SOCKET)v;
for (;;)
{
struct sockaddr_in udpfrom;
int udpfromlen = sizeof(udpfrom);
int n = recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr *)&udpfrom, &udpfromlen);
int e = WSAGetLastError();
if (n > 0 && e == 0)
decode_recv(buffer, n);
}
}
void useage()
{
printf("******************************************/n");
printf("SQLPing/n");
printf("/t Written by Refdom/n");
printf("/t Email: refdom@263.net/n");
printf("Useage: sqlping.exe target_ip /n");
printf("*******************************************/n");
}
int main(int argc, char* argv[])
{
WSADATA WSAData;
SOCKET sock;
SOCKADDR_IN addr_in;
char buf[5]={'/x02'};
HANDLE listener;
useage();
if (argc<2)
{
return false;
}
if (WSAStartup(MAKEWORD(2,0),&WSAData)!=0)
{
printf("WSAStartup error.Error:%d/n",WSAGetLastError());
return false;
}
if ((sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))==INVALID_SOCKET)
{
printf("Socket failed.Error:%d/n",WSAGetLastError());
return false;
}
addr_in.sin_family=AF_INET;
addr_in.sin_port=htons(1434);
addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]);
const int SNDBUF = 0;
const int TCPNODELAY = true;
const int BROADCAST = true;
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&SNDBUF, sizeof(SNDBUF))==SOCKET_ERROR)
{
printf("Set SO_SNDBUF failed.Error:%d",WSAGetLastError());
return false;
}
if (setsockopt(sock, SOL_SOCKET, TCP_NODELAY, (const char*)&TCPNODELAY, sizeof(TCPNODELAY))==SOCKET_ERROR)
{
printf("Set TCP_NODELAY failed.Error:%d",WSAGetLastError());
return false;
}
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char*)&BROADCAST, sizeof(BROADCAST))==SOCKET_ERROR)
{
printf("Set SO_BROADCAST failed.Error:%d",WSAGetLastError());
return false;
}
listener = (HANDLE) _beginthread(listen, 0, (void*)sock);
// e = sendto(s, "/08", 1, 0,(sockaddr*) &hostaddr, sizeof(hostaddr));
if (sendto(sock, buf, sizeof(buf), 0,(sockaddr*) &addr_in, sizeof(addr_in))==SOCKET_ERROR)
{
printf("Send failed.Error:%d/n",WSAGetLastError());
return false;
}
printf("Listening..../n/n");
// wait a little while for listener thread
WaitForSingleObject(listener, 5000);
WSACleanup();
printf("SQLPing Complete./n");
return 0;
}
上面的程式只有探測作用,沒有破壞性。呵呵
感謝Hectic給我提供的協助。限於本人的水平,難免有錯誤之處,還請大家多多指正。如果誰有TDS協議(應用在SQL Server上的)的具體描述手冊,請EMAIL給我,謝過了。EMAIL:zj.wj@163.com