修复漏洞

This commit is contained in:
晚风拂柳颜 2024-01-02 19:52:29 +08:00
parent bc34e592ce
commit 173a5d4be3
5 changed files with 168 additions and 80 deletions

View File

@ -19,7 +19,7 @@ from utils.encode import base64Encode, base64Decode, fetch, post, request, getCr
from utils.encode import verifyCode, setDetail, join, urljoin2, parseText, requireCache, forceOrder, base64ToImage, \ from utils.encode import verifyCode, setDetail, join, urljoin2, parseText, requireCache, forceOrder, base64ToImage, \
encodeStr, decodeStr encodeStr, decodeStr
from utils.encode import md5 as mmd5 from utils.encode import md5 as mmd5
from utils.safePython import safePython from utils.safePython import safePython, safe_eval
from utils.parser import runPy, runJScode, JsObjectWrapper, PyJsObject, PyJsString from utils.parser import runPy, runJScode, JsObjectWrapper, PyJsObject, PyJsString
from utils.htmlParser import jsoup from utils.htmlParser import jsoup
from urllib.parse import urljoin, quote, unquote from urllib.parse import urljoin, quote, unquote
@ -828,8 +828,9 @@ class CMS:
# print(url_rep) # print(url_rep)
# print(cnt_page) # print(cnt_page)
cnt_ctx = {} cnt_ctx = {}
exec(f'cnt_pg={cnt_page}', cnt_ctx) safe_eval(f'cnt_pg={cnt_page}', cnt_ctx)
cnt_pg = str(cnt_ctx['cnt_pg']) # 计算表达式的结果 # exec(f'cnt_pg={cnt_page}', cnt_ctx)
cnt_pg = str(cnt_ctx['cnt_pg']) if cnt_ctx.get('cnt_pg') else 1 # 计算表达式的结果
url = url.replace(url_rep, str(cnt_pg)).replace('(', '').replace(')', '') url = url.replace(url_rep, str(cnt_pg)).replace('(', '').replace(')', '')
# print(url) # print(url)
else: else:
@ -1351,8 +1352,9 @@ class CMS:
# print(url_rep) # print(url_rep)
# print(cnt_page) # print(cnt_page)
cnt_ctx = {} cnt_ctx = {}
exec(f'cnt_pg={cnt_page}', cnt_ctx) # exec(f'cnt_pg={cnt_page}', cnt_ctx)
cnt_pg = str(cnt_ctx['cnt_pg']) # 计算表达式的结果 safe_eval(f'cnt_pg={cnt_page}', cnt_ctx)
cnt_pg = str(cnt_ctx['cnt_pg']) if cnt_ctx.get('cnt_pg') else 1 # 计算表达式的结果
url = url.replace(url_rep, str(cnt_pg)).replace('(', '').replace(')', '') url = url.replace(url_rep, str(cnt_pg)).replace('(', '').replace(')', '')
# print(url) # print(url)
else: else:

View File

@ -8,7 +8,8 @@ import ujson
import os import os
import re import re
from flask import Blueprint,abort,render_template,render_template_string,url_for,redirect,make_response,send_from_directory,request from flask import Blueprint, abort, render_template, render_template_string, url_for, redirect, make_response, \
send_from_directory, request
from controllers.service import storage_service, rules_service, parse_service from controllers.service import storage_service, rules_service, parse_service
from controllers.classes import getClasses, getClassInfo from controllers.classes import getClasses, getClassInfo
@ -28,13 +29,14 @@ from utils.web import getParmas,verfy_token
from utils.common_api import js_render from utils.common_api import js_render
import functools import functools
home = Blueprint("home", __name__, static_folder='/static') home = Blueprint("home", __name__, static_folder='/static')
@home.route('/') @home.route('/')
def forbidden(): # put application's code here def forbidden(): # put application's code here
abort(403) abort(403)
@home.route('/favicon.ico') # 设置icon @home.route('/favicon.ico') # 设置icon
def favicon(): def favicon():
# return home.send_static_file('img/favicon.svg') # return home.send_static_file('img/favicon.svg')
@ -42,6 +44,7 @@ def favicon():
# 对于当前文件所在路径,比如这里是static下的favicon.ico # 对于当前文件所在路径,比如这里是static下的favicon.ico
# return send_from_directory(os.path.join(app.root_path, 'static'), 'img/favicon.svg', mimetype='image/vnd.microsoft.icon') # return send_from_directory(os.path.join(app.root_path, 'static'), 'img/favicon.svg', mimetype='image/vnd.microsoft.icon')
@home.route('/index') @home.route('/index')
def index(): def index():
sup_port = cfg.get('SUP_PORT', 9001) sup_port = cfg.get('SUP_PORT', 9001)
@ -57,16 +60,26 @@ def index():
manager2 += f':{sup_port}' manager2 += f':{sup_port}'
# print(manager2) # print(manager2)
ver = getLocalVer() ver = getLocalVer()
return render_template('index.html',ver=ver,getHost=getHost,manager0=manager0,manager1=manager1,manager2=manager2,is_linux=is_linux()) return render_template('index.html', ver=ver, getHost=getHost, manager0=manager0, manager1=manager1,
manager2=manager2, is_linux=is_linux())
@home.route('/rules/clear') @home.route('/rules/clear')
def rules_to_clear(): def rules_to_clear():
if not verfy_token():
# return render_template('login.html')
return R.error('请登录后再试')
return render_template('rules_to_clear.html', rules=getRules(), classes=getClasses()) return render_template('rules_to_clear.html', rules=getRules(), classes=getClasses())
@home.route('/rules/view') @home.route('/rules/view')
def rules_to_view(): def rules_to_view():
if not verfy_token():
# return render_template('login.html')
return R.error('请登录后再试')
return render_template('rules_to_view.html', rules=getRules(), classes=getClasses()) return render_template('rules_to_view.html', rules=getRules(), classes=getClasses())
@home.route('/pics') @home.route('/pics')
def random_pics(): def random_pics():
id = getParmas('id') id = getParmas('id')
@ -92,6 +105,7 @@ def random_pics():
else: else:
return redirect(new_conf.WALL_PAPER) return redirect(new_conf.WALL_PAPER)
@home.route('/clear') @home.route('/clear')
def clear_rule(): def clear_rule():
rule = getParmas('rule') rule = getParmas('rule')
@ -103,6 +117,7 @@ def clear_rule():
os.remove(cache_path) os.remove(cache_path)
return R.success('成功删除文件:' + cache_path) return R.success('成功删除文件:' + cache_path)
@home.route("/plugin/<name>", methods=['GET']) @home.route("/plugin/<name>", methods=['GET'])
def plugin(name): def plugin(name):
# name=道长影视模板.js # name=道长影视模板.js
@ -113,6 +128,7 @@ def plugin(name):
except Exception as e: except Exception as e:
return R.failed(f'非法猥亵\n{e}') return R.failed(f'非法猥亵\n{e}')
@home.route('/files/<name>') @home.route('/files/<name>')
def get_files(name): def get_files(name):
base_path = 'base/files' base_path = 'base/files'
@ -129,18 +145,21 @@ def get_files(name):
response.headers['Content-Disposition'] = f'attachment;filename="{filename}"' response.headers['Content-Disposition'] = f'attachment;filename="{filename}"'
return response return response
@home.route('/txt/<path:filename>') @home.route('/txt/<path:filename>')
def custom_static_txt(filename): def custom_static_txt(filename):
# 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}} # 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}}
# print(filename) # print(filename)
return send_from_directory('txt', filename) return send_from_directory('txt', filename)
@home.route('/libs/<path:filename>') @home.route('/libs/<path:filename>')
def custom_static_libs(filename): def custom_static_libs(filename):
# 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}} # 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}}
# print(filename) # print(filename)
return send_from_directory('libs', filename) return send_from_directory('libs', filename)
# @home.route('/js/<path:filename>') # @home.route('/js/<path:filename>')
# def custom_static_js(filename): # def custom_static_js(filename):
# # 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}} # # 自定义静态目录 {{ url_for('custom_static',filename='help.txt')}}
@ -153,10 +172,12 @@ def custom_static_js(name):
# print(name) # print(name)
return js_render(name) return js_render(name)
@home.route('/raw/js/<path:filename>') @home.route('/raw/js/<path:filename>')
def custom_raw_js(filename): def custom_raw_js(filename):
return send_from_directory('js', filename) return send_from_directory('js', filename)
# @home.route('/txt/<name>') # @home.route('/txt/<name>')
# def get_txt_files(name): # def get_txt_files(name):
# base_path = 'txt' # base_path = 'txt'
@ -177,7 +198,8 @@ def get_lives():
# ?path=base/live.txt # ?path=base/live.txt
path = getParmas('path') path = getParmas('path')
live_path = path or 'base/直播.txt' live_path = path or 'base/直播.txt'
if not re.search('(txt|json|conf)$',live_path,re.M|re.S) or not re.search('^(txt|base)',live_path,re.M|re.S): if not re.search('(txt|json|conf)$', live_path, re.M | re.S) or not re.search('^(txt|base)', live_path,
re.M | re.S):
abort(403) abort(403)
if not os.path.exists(live_path): if not os.path.exists(live_path):
# with open(live_path,mode='w+',encoding='utf-8') as f: # with open(live_path,mode='w+',encoding='utf-8') as f:
@ -196,6 +218,7 @@ def get_lives():
response.headers['Content-Type'] = 'text/plain; charset=utf-8' response.headers['Content-Type'] = 'text/plain; charset=utf-8'
return response return response
@home.route('/liveslib') @home.route('/liveslib')
def get_liveslib(): def get_liveslib():
lsg = storage_service() lsg = storage_service()
@ -214,6 +237,7 @@ def get_liveslib():
response.headers['Content-Disposition'] = f'attachment;filename="{filename}"' response.headers['Content-Disposition'] = f'attachment;filename="{filename}"'
return response return response
@home.route('/hotsugg') @home.route('/hotsugg')
def get_hot_search(): def get_hot_search():
s_from = getParmas('from') s_from = getParmas('from')
@ -221,6 +245,7 @@ def get_hot_search():
data = getHotSuggest(s_from, size) data = getHotSuggest(s_from, size)
return R.success('获取成功', data) return R.success('获取成功', data)
def merged_hide(merged_config): def merged_hide(merged_config):
t1 = time() t1 = time()
store_rule = rules_service() store_rule = rules_service()
@ -237,7 +262,9 @@ def merged_hide(merged_config):
return name not in hide_rule_names return name not in hide_rule_names
merged_config['sites'] = list(filter(filter_show, merged_config['sites'])) merged_config['sites'] = list(filter(filter_show, merged_config['sites']))
logger.info(f'数据库筛选隐藏规则耗时{get_interval(t1)}毫秒,共计{all_cnt}条规则,隐藏后可渲染{len(merged_config["sites"])}条规则') logger.info(
f'数据库筛选隐藏规则耗时{get_interval(t1)}毫秒,共计{all_cnt}条规则,隐藏后可渲染{len(merged_config["sites"])}条规则')
@home.route('/config/<int:mode>') @home.route('/config/<int:mode>')
def config_render(mode): def config_render(mode):
@ -300,7 +327,9 @@ def config_render(mode):
else: else:
new_conf.EXT_FUNC = [] new_conf.EXT_FUNC = []
html = render_template('config.txt',js0_password=js0_password,UA=UA,xr_mode=xr_mode,ISTVB=ISTVB,pys=pys,rules=rules,host=host,mode=mode,js_mode=js_mode,jxs=jxs,alists=alists,alists_str=alists_str,live_url=live_url,config=new_conf) html = render_template('config.txt', js0_password=js0_password, UA=UA, xr_mode=xr_mode, ISTVB=ISTVB, pys=pys,
rules=rules, host=host, mode=mode, js_mode=js_mode, jxs=jxs, alists=alists,
alists_str=alists_str, live_url=live_url, config=new_conf)
merged_config = custom_merge(parseText(html), customConfig) merged_config = custom_merge(parseText(html), customConfig)
# print(merged_config['sites']) # print(merged_config['sites'])
merged_hide(merged_config) merged_hide(merged_config)
@ -333,6 +362,7 @@ def config_render(mode):
logger.info(f'自动生成动态配置共计耗时:{get_interval(tt)}毫秒') logger.info(f'自动生成动态配置共计耗时:{get_interval(tt)}毫秒')
return response return response
def special_rule(merged_config, lsg): def special_rule(merged_config, lsg):
# print(merged_config['sites']) # print(merged_config['sites'])
special = lsg.getItem('SPECIAL').strip() special = lsg.getItem('SPECIAL').strip()
@ -350,6 +380,7 @@ def special_rule(merged_config,lsg):
merged_config['sites'] = special_st merged_config['sites'] = special_st
merged_config['dr_count'] = len(special_st) merged_config['dr_count'] = len(special_st)
def comp(x, y): def comp(x, y):
if x['order'] > y['order']: if x['order'] > y['order']:
return 1 return 1
@ -363,6 +394,7 @@ def comp(x, y):
else: else:
return 0 return 0
def sort_sites_by_order(sites, js_mode=0): def sort_sites_by_order(sites, js_mode=0):
rules = rules_service() rules = rules_service()
rule_list = rules.query_all() rule_list = rules.query_all()
@ -399,6 +431,7 @@ def sort_sites_by_order(sites,js_mode=0):
del site['write_date'] del site['write_date']
return sites return sites
def sort_parses_by_order(parses, host): def sort_parses_by_order(parses, host):
t1 = time() t1 = time()
parse = parse_service() parse = parse_service()
@ -441,6 +474,7 @@ def sort_parses_by_order(parses,host):
logger.info(f'{len(new_parses)}/{len(parses)}条解析解析排序耗时:{get_interval(t1)}毫秒') logger.info(f'{len(new_parses)}/{len(parses)}条解析解析排序耗时:{get_interval(t1)}毫秒')
return new_parses return new_parses
@home.route('/configs') @home.route('/configs')
def config_gen(): def config_gen():
if not verfy_token(): if not verfy_token():
@ -471,15 +505,21 @@ def config_gen():
rules = get_multi_rules(rules) rules = get_multi_rules(rules)
host0 = getHost(0) host0 = getHost(0)
jxs = getJxs(host=host0) jxs = getJxs(host=host0)
set_local = render_template('config.txt',js0_password=js0_password,pys=pys,rules=rules,alists=alists,alists_str=alists_str,live_url=get_live_url(new_conf,0),mode=0,js_mode=js_mode,host=host0,jxs=jxs,config=new_conf) set_local = render_template('config.txt', js0_password=js0_password, pys=pys, rules=rules, alists=alists,
alists_str=alists_str, live_url=get_live_url(new_conf, 0), mode=0, js_mode=js_mode,
host=host0, jxs=jxs, config=new_conf)
# print(set_local) # print(set_local)
host1 = getHost(1) host1 = getHost(1)
jxs = getJxs(host=host1) jxs = getJxs(host=host1)
set_area = render_template('config.txt',js0_password=js0_password,pys=pys,rules=rules,alists=alists,alists_str=alists_str,live_url=get_live_url(new_conf,1),mode=1,js_mode=js_mode,host=host1,jxs=jxs,config=new_conf) set_area = render_template('config.txt', js0_password=js0_password, pys=pys, rules=rules, alists=alists,
alists_str=alists_str, live_url=get_live_url(new_conf, 1), mode=1, js_mode=js_mode,
host=host1, jxs=jxs, config=new_conf)
host2 = getHost(2) or host1 host2 = getHost(2) or host1
# print('远程地址:'+host2) # print('远程地址:'+host2)
jxs = getJxs(host=host2) jxs = getJxs(host=host2)
set_online = render_template('config.txt',js0_password=js0_password,pys=pys,rules=rules,alists=alists,alists_str=alists_str,live_url=get_live_url(new_conf,2),mode=1,js_mode=js_mode,host=host2,jxs=jxs,config=new_conf) set_online = render_template('config.txt', js0_password=js0_password, pys=pys, rules=rules, alists=alists,
alists_str=alists_str, live_url=get_live_url(new_conf, 2), mode=1, js_mode=js_mode,
host=host2, jxs=jxs, config=new_conf)
ali_token = new_conf.ALI_TOKEN ali_token = new_conf.ALI_TOKEN
# parses = [] # parses = []
with open('txt/pycms0.json', 'w+', encoding='utf-8') as f: with open('txt/pycms0.json', 'w+', encoding='utf-8') as f:
@ -517,7 +557,11 @@ def config_gen():
except Exception as e: except Exception as e:
return R.failed(f'配置文件生成错误:\n{e}') return R.failed(f'配置文件生成错误:\n{e}')
@home.route("/info", methods=['get']) @home.route("/info", methods=['get'])
def info_all(): def info_all():
if not verfy_token():
# return render_template('login.html')
return R.error('请登录后再试')
data = storage_service.query_all() data = storage_service.query_all()
return R.ok(data=data) return R.ok(data=data)

View File

@ -1,3 +1,6 @@
###### 2024/01/02
- [X] 3.9.49beta11 fixed issues#49
###### 2023/11/25 ###### 2023/11/25
- [X] 3.9.49beta8 增加直播转换小工具 - [X] 3.9.49beta8 增加直播转换小工具
@ -11,6 +14,7 @@
```shell ```shell
git config --global http.proxy http://127.0.0.1:10808 git config --global http.proxy http://127.0.0.1:10808
git config --global https.proxy http://127.0.0.1:10808 git config --global https.proxy http://127.0.0.1:10808
git config --global --unset http.proxy
# 我的v2vary在10808上系统代理 # 我的v2vary在10808上系统代理
``` ```

View File

@ -1 +1 @@
3.9.49beta10 3.9.49beta11

View File

@ -20,6 +20,8 @@ import base64
from utils.log import logger from utils.log import logger
time_out_sec = 8 # 安全执行python代码超时 time_out_sec = 8 # 安全执行python代码超时
class my_exception(Exception): class my_exception(Exception):
def __init__(self, message): def __init__(self, message):
self.message = message self.message = message
@ -28,10 +30,12 @@ class my_exception(Exception):
message = f'函数执行超时: "{self.message}"' message = f'函数执行超时: "{self.message}"'
return message return message
@func_set_timeout(time_out_sec) @func_set_timeout(time_out_sec)
def excute(*args): def excute(*args):
exec(*args) exec(*args)
def check_unsafe_attributes(string): def check_unsafe_attributes(string):
""" """
安全检测需要exec执行的python代码 安全检测需要exec执行的python代码
@ -48,6 +52,7 @@ def check_unsafe_attributes(string):
elif toktype == tokenize.OP: elif toktype == tokenize.OP:
pre_op = tokval pre_op = tokval
DEFAULT_PYTHON_CODE = """# 可用内置环境变量: DEFAULT_PYTHON_CODE = """# 可用内置环境变量:
# - log: log(message): 打印日志功能 # - log: log(message): 打印日志功能
# - error: 弹出用户错误的弹窗 # - error: 弹出用户错误的弹窗
@ -57,6 +62,39 @@ zyw_lists = env['hikerule.zyw.list'].with_context(active_test=True).sudo().searc
result = env['hikerule.zyw.list2data.wizard'].sudo().get_publish_value(zyw_lists) result = env['hikerule.zyw.list2data.wizard'].sudo().get_publish_value(zyw_lists)
""" """
def safe_eval(code: str = '', localdict: dict = None):
code = code.strip()
logger.info('code:' + code)
if not code:
return {}
if localdict is None:
localdict = {}
builtins = __builtins__
builtins = dict(builtins).copy()
for key in ['__import__', 'eval', 'exec', 'globals', 'dir', 'copyright', 'open', 'quit']:
del builtins[key] # 删除不安全的关键字
# print(builtins)
global_dict = {'__builtins__': builtins,
'requests': requests, 'urljoin': urljoin, 'quote': quote, 'unquote': unquote,
'log': logger.info, 'json': json, 'print': print,
're': re, 'etree': etree, 'time': time, 'datetime': datetime, 'base64': base64
} # 禁用内置函数,不允许导入包
try:
check_unsafe_attributes(code)
# 待解决windows下运行超时的问题
try:
# excute(to_run_code, global_dict, localdict)
excute(code, global_dict, localdict)
return localdict
except FunctionTimedOut:
raise my_exception(f'safe_eval运行时间超过{time_out_sec}秒,疑似死循环,已被系统切断')
except Exception as e:
ret = f'执行报错:{e}'
logger.info(ret)
return ret
class safePython: class safePython:
def __init__(self, name, code): def __init__(self, name, code):
self.name = name or '未定义' self.name = name or '未定义'