www.hackjason.com
前段时间看XDCTF的一道web题,发现了一种很奇特的构造webshell的方法。
Base64一句话木马
题目的大概意思就是允许包含,但限制了使用的字符,仅允许使用'acgtACGT'这8个字符。
emmm,就像我第一次看到一样,感觉这根本不能构造webshell嘛,这要能弄出来,我直播吃……冰激凌。
不废话了,原理如下:
先大致讲一下,任何由 {A-Z|a-z|0-9|+|/} 组合的字符串(如果不够4的倍数可以用'='补全),如果长度为4的倍数,则都可以作为base64解码的材料,而在base64decode的时候,会产生原字符串包含字符集以外的字符,举个例子:
字符串aaaa进行base64解码:
结果为i��,有一部分为乱码,不过不要紧,因为至少产生了一个额外的、可以被利用的字符i
如果是md5那样的哈希编码,多一位字母,编码后的整个字符串就完全不一样了,但是base64不一样。
base64编码是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节可表示4个可打印字符。也就是说3个字节进行base64编码之后是4个字节。四个字节解码后为三个字节。
因此base64有一个特性,就是以四位为一个单位,多个单位组合起来,进行多次解密,得到的结果和组合的顺序相同。再举一个例子:
abcABC123的编码结果为YWJjQUJDMTIz,我们把加密后的字符串四个为一组拆开YWJj(abc)、QUJD(ABC)、MTIz(123),组合为MTIzYWJjQUJD:
123和abcABC的顺序反过来了。
base64还有一个特性,就是会自动抛弃不符合要求的字符,如果要进行解密的base64字符串包括有不合法的字符,也就是不在集合 {A-Z|a-z|0-9|+|/} 里,同时也不是末尾的等号的字符。会被自动抛弃,又一个例子:
PS:注意py版本为2.7
aaaa的解密结果为i��
iiii的解密结果为�(�
如果我们把aaaa的解密结果重复四遍,再进行解密
结果和iiii的解密结果是一样的
从以上两个例子能Get到什么猥琐的技巧呢?
三个背景知识:
① 编码和解码不是唯一对应,就是说字母a可能通过不同的,其它字符的组合进行base64解码解出来。(组合种类远多于base64的合法字符种类)
② 被解码的字符,以四位为一个单位,多个单位组合起来,进行多次解密,得到的结果和组合的顺序相同。
③ 我们的一句话,可以通过解密另一个字符串,我们假设为字符串一号获得,而字符串一号可以通过解密字符串二号获得,并且这种序列不是唯一的。我们有可能找到仅仅由acgtACGT这8个字符组合起来的一串字符,这串字符在经过n次解密后的结果为我们的一句话木马。当然,在这个过程中,要保证四位一组,否则会乱序。
然后附上王一航大佬的Python脚本:
https://gist.github.com/WangYihang/a49c663237e68822dd4816e99534ca72)
我加入了很多的注解,然后我们来一步一步地,从主函数开始分析:
首先输出了base64_chars,这是在之前的base64_chars = string.letters + string.digits + "+/"中定义好的。base64除了‘=’以外可能会用到的字符串。然后tables = enmu_tables(set(chars))将可以使用的8个字符带入了enmu_tables()函数。
我们跟进enmu_tables()函数,它将我们可以使用的8个字符带入enmu_table()函数,四位为一组进行组合,然后进行base64解码,生成了一个list,这个list的key值为所有acgtACGT组合能够生成的合法数字(再带两个注定要被遗弃的非法数字),value值为生成这个合法数字的‘acgtACGT’四位字符组合。
还记得之前提到的一个栗子吗?
aaaa解码生成了i��,那么在第一次生成时,list的key值为i,value值为‘aaaa’
经过所有的组合之后
我们拿到了26个字符,而这二十六个字符能重新组成的四位字符串为四的26次方~
循环上一步步骤,我们拿到了57个
再循环一次
我们拿到了64个,已经是全部的base64合法字符了
这时,我们手中有三个表,分别是一层一层地一位key(伴随着两个注定要被扔掉的垃圾字符)对应四位value。这时,我们可以把一句话密码中的字符分隔开,挨个去最后一个表(64个key)中寻找由第二次循环生成的57个字符组成的4位字符串。找到之后,再去第二个表中,将目前的这些字符,用第一次循环得到的26位字符串的4位组合替换掉,然后再去第一个表中,找到用最初始的8位字符组成的四位字符串替换;
总共替换了3次,又因为在把一句话进行输入的时候额外进行了一次base64encode,所以最后的payload为:
include(php://filter/convert.base64-decode/resource=php://filter/convert.base64-decode/resource=php://filter/convert.base64-decode/resource=php://filter/convert.base64-decode/resource= 【我们的acgtACGT组合】);
生成的payload,储存在了名字为'acgtACGT'的文件中,长得是这个样子:
特别长,我就不贴出来了
那个脚本中还要注意的一点为:
其中,如果长度不是4的倍数,会根据base64编码原理默认用等号补齐,凑够长度为4的倍数。
后记
还有P师傅一些奇葩webshell的思路,以后有时间再写吧…