Skip to content

Commit

Permalink
Merge pull request #40 from HisAtri/dev
Browse files Browse the repository at this point in the history
Feature: simple waf and new api
  • Loading branch information
HisAtri authored Mar 31, 2024
2 parents b845e50 + e3869da commit df4e0c8
Show file tree
Hide file tree
Showing 18 changed files with 373 additions and 58 deletions.
3 changes: 2 additions & 1 deletion api/__import__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import cover, login, lyrics, source, tag, time
from . import cover, login, lyrics, source, tag, time, file
from . import waf

"""
引入所有子模块
Expand Down
16 changes: 15 additions & 1 deletion api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import shutil
import logging
import sys
import os

from flask import Flask, Blueprint, request
from flask_caching import Cache
Expand Down Expand Up @@ -31,5 +33,17 @@ def make_cache_key(*args, **kwargs):
args = str(hash(frozenset(request.args.items())))
return path + args

def get_base_path():
"""
获取程序运行路径
如果是打包后的exe文件,则返回打包资源路径
"""
if getattr(sys, 'frozen', False):
return sys._MEIPASS
else:
return os.getcwd()

__all__ = ['app', 'v1_bp', 'cache', 'make_cache_key', 'logger']

src_path = os.path.join(get_base_path(), 'src') # 静态资源路径

__all__ = ['app', 'v1_bp', 'cache', 'make_cache_key', 'logger', 'src_path']
17 changes: 6 additions & 11 deletions api/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,13 @@ def cover_api():
req_args = {key: request.args.get(key) for key in request.args}
# 构建目标URL
target_url = 'http://api.lrc.cx/cover?' + '&'.join([f"{key}={req_args[key]}" for key in req_args])
"""
# 跟踪重定向并获取最终URL
final_url = follow_redirects(target_url)
# 获取最终URL的内容或响应
response = requests.get(final_url)
if response.status_code == 200:
content_type = response.headers.get('Content-Type', 'application/octet-stream')
return Response(response.content, content_type=content_type)
else:
result = requests.get(target_url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"})
if result.status_code == 200:
return result.content, 200, {"Content-Type": result.headers['Content-Type']}
elif result.status_code == 404:
abort(404)
"""
return redirect(target_url, 302)
else:
abort(500)


@v1_bp.route('/cover/<path:s_type>', methods=['GET'])
Expand Down
11 changes: 9 additions & 2 deletions api/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import requests
from urllib.parse import urlparse
from flask import request, render_template_string
from flask import request, render_template_string, send_from_directory
from werkzeug.utils import secure_filename

from mod.tools import calculate_md5
Expand Down Expand Up @@ -65,7 +65,7 @@ def file_api_download():
return {"error": str(e), "code": 500}, 500


@app.route('/file/upload', methods=['POST'])
@v1_bp.route('/file/upload', methods=['POST'])
def upload_file():
match require_auth(request=request, permission="rwd"):
case -1:
Expand Down Expand Up @@ -150,3 +150,10 @@ def list_file():
"files": data["FILES"][row * (page - 1):row * page]
}
}


"""
@app.route('/file', methods=['GET'])
def file():
return send_from_directory(src_path, 'file.html')
"""
21 changes: 8 additions & 13 deletions api/lyrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from urllib.parse import unquote_plus

from mod import search, lrc
from mod import searchx
from mod import tools
from mod import tag
from mod.auth import webui
Expand Down Expand Up @@ -54,11 +55,10 @@ def lyrics():
title = unquote_plus(request.args.get('title'))
artist = unquote_plus(request.args.get('artist', ''))
album = unquote_plus(request.args.get('album', ''))
executor = concurrent.futures.ThreadPoolExecutor()
# 提交任务到线程池,并设置超时时间
future = executor.submit(search.main, title, artist, album)
lyrics_text = future.result(timeout=30)
return lrc.standard(lyrics_text)
result: list = searchx.search_all(title=title, artist=artist, album=album, timeout=30)
if not result[0].get('lyrics'):
return "Lyrics not found.", 404
return result[0].get('lyrics')
except:
return "Lyrics not found.", 404

Expand Down Expand Up @@ -92,18 +92,13 @@ def lrc_json():
"lyrics": file_content
})

lyrics_list = search.allin(title, artist, album)
lyrics_list = searchx.search_all(title, artist, album)
if lyrics_list:
for i in lyrics_list:
if not i:
continue
i = lrc.standard(i)
response.append({
"id": tools.calculate_md5(i),
"title": title,
"artist": artist,
"lyrics": i
})
i['lyrics'] = lrc.standard(i['lyrics'])
response.append(i)
_response = jsonify(response)
_response.headers['Content-Type'] = 'application/json; charset=utf-8'
return jsonify(response)
24 changes: 4 additions & 20 deletions api/source.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import sys

from . import *

import os
Expand All @@ -10,20 +8,6 @@
from mod.auth.authentication import require_auth


def get_base_path():
"""
获取程序运行路径
如果是打包后的exe文件,则返回打包资源路径
"""
if getattr(sys, 'frozen', False):
return sys._MEIPASS
else:
return os.getcwd()


path = os.path.join(get_base_path(), 'src') # 静态资源路径


@app.route('/')
def redirect_to_welcome():
"""
Expand All @@ -39,7 +23,7 @@ def favicon():
favicon位置,返回图片
:return:
"""
return send_from_directory(path, 'img/Logo_Design.svg')
return send_from_directory(src_path, 'img/Logo_Design.svg')


@app.route('/src')
Expand All @@ -48,7 +32,7 @@ def return_index():
显示主页
:return: index page
"""
return send_from_directory(path, 'index.html')
return send_from_directory(src_path, 'index.html')


@app.route('/src/<path:filename>')
Expand All @@ -60,15 +44,15 @@ def serve_file(filename):
:return:
"""
FORBIDDEN_EXTENSIONS = ('.exe', '.bat', '.dll', '.sh', '.so', '.php', '.sql', '.db', '.mdb', '.gz', '.tar', '.bak',
'.tmp', '.key', '.pem', '.crt', '.csr', '.log')
'.tmp', '.key', '.pem', '.crt', '.csr', '.log', '.html', '.htm', '.xml', '.json', '.yml',)
_paths = filename.split('/')
for _path in _paths:
if _path.startswith('.'):
abort(404)
if filename.lower().endswith(FORBIDDEN_EXTENSIONS):
abort(404)
try:
return send_from_directory(path, filename)
return send_from_directory(src_path, filename)
except FileNotFoundError:
abort(404)

Expand Down
98 changes: 98 additions & 0 deletions api/waf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
WAF基本防火墙,承担基本的防火墙功能
防注入/恶意文件读取
"""
from api import *

import re
from flask import request, abort


@app.before_request
def check():
"""
检查请求是否合法
:return:
"""
# 获取请求的URL的路径+参数部分,不包括域名
path = request.path
if waf(path):
logger.warning(f"检测到恶意请求: {path}")
abort(403)


def waf(req: str):
"""
:param req:
:return:
"""
NN_RULES = r"""\.\./
\:\$
\$\{
[\\/]proc[\\/]self[\\/](environ|cmdline|maps)
(?i)select.+(from|limit)
(?i)d(?:elete|rop|ump).+table
(?:(union(.*?)select))
having|rongjitest
sleep\((\s*)(\d*)(\s*)\)
benchmark\((.*)\,(.*)\)
base64_decode\(
(?:from\W+information_schema\W)
(?:(?:current_)user|database|schema|connection_id)\s*\(
(?:etc\/\W*passwd)
into(\s+)+(?:dump|out)file\s*
group\s+by.+\(
xwork.MethodAccessor
xwork\.MethodAccessor
(gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/
java\.lang
\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[
\<(iframe|script|body|img|layer|div|meta|style|base|object|input)
(onmouseover|onerror|onload)\=
\.\./\.\./
/\*
\:\$
\$\{
(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|char|chr|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\(
\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[
\s+(or|xor|and)\s+.*(=|<|>|'|")
(?i)select.+(from|limit)
(?:(union(.*?)select))
sleep\((\s*)(\d*)(\s*)\)
benchmark\((.*)\,(.*)\)
(?:from\W+information_schema\W)
(?:(?:current_)user|database|schema|connection_id)\s*\(
into(\s+)+(?:dump|out)file\s*
group\s+by.+\(
\<(iframe|script|body|img|layer|div|meta|style|base|object|input)
@eval.*GET(.*])"""
for re_str in NN_RULES.split("\n"):
if re.search(re_str, req):
# 匹配到恶意请求
logger.warning(f"匹配规则: {re_str}")
return True
# 测试集均为恶意请求,返回False意味着存在漏报
return False


def test():
DATAS = [
"/../../", # 目录穿越
"/proc/self/maps", # 读取系统信息
"/etc/passwd", # 读取密码文件
"/etc/shadow", # 读取密码文件
"php://input", # PHP流协议
"SELECT * FROM", # SQL注入
"DROP TABLE", # SQL注入
"SeleCt * fRoM", # SQL注入,大小写混合
"sleep(3)", # SQL注入
"@@version", # SQL注入
"S%e%l%e%c%t * F%rom", # SQL注入,百分号编码
]
for data in DATAS:
if not waf(data):
print(f"有恶意请求未被拦截: {data}")


if __name__ == "__main__":
test()
Empty file added db/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion mod/args/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(self):
self.port = first(env_args.port, kw_args.port, config_args.port, default.port)
self.ip = first(config_args.ip, default.ip)
self.debug = kw_args.debug
self.version = "1.5.2"
self.version = "1.5.3"

def valid(self, key):
"""
Expand Down
4 changes: 0 additions & 4 deletions mod/music_tag/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,3 @@ def load_file(file_spec, err='raise'):
'AudioFile',
'load_file',
]

##
## EOF
##
12 changes: 8 additions & 4 deletions mod/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ async def kugou(title, artist, album):
async with aiohttp.ClientSession() as session:
# 第一层Json,要求获得Hash值
async with session.get(
f'http://mobilecdn.kugou.com/api/v3/search/song?format=json&keyword={title} {artist} {album}&page=1&pagesize=2&showtype=1',
f'http://mobilecdn.kugou.com/api/v3/search/song'
f'?format=json&keyword={title} {artist} {album}&page=1&pagesize=2&showtype=1',
headers=headers,
timeout=10) as response:
if response.status == 200:
Expand All @@ -38,15 +39,17 @@ async def kugou(title, artist, album):
ratio_max = max(ratio, ratio_max)
if ratio >= ratio_max:
async with session.get(
f"https://krcs.kugou.com/search?ver=1&man=yes&client=mobi&keyword=&duration=&hash={song_hash}&album_audio_id=",
f"https://krcs.kugou.com/search"
f"?ver=1&man=yes&client=mobi&keyword=&duration=&hash={song_hash}&album_audio_id=",
headers=headers,
timeout=10) as response2:
lyrics_info = await response2.json()
lyrics_id = lyrics_info["candidates"][0]["id"]
lyrics_key = lyrics_info["candidates"][0]["accesskey"]
# 第三层Json,要求获得并解码Base64
async with session.get(
f"http://lyrics.kugou.com/download?ver=1&client=pc&id={lyrics_id}&accesskey={lyrics_key}&fmt=lrc&charset=utf8",
f"http://lyrics.kugou.com/download"
f"?ver=1&client=pc&id={lyrics_id}&accesskey={lyrics_key}&fmt=lrc&charset=utf8",
headers=headers,
timeout=10) as response3:
lyrics_data = await response3.json()
Expand All @@ -60,7 +63,8 @@ async def kugou(title, artist, album):

async def api_2(title, artist, album):
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 LrcAPI/1.1',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/92.0.4515.131 LrcAPI/1.1',
}
url = f"http://api.lrc.cx/lyrics?title={title}&artist={artist}&album={album}&path=None&limit=1&api=lrcapi"

Expand Down
33 changes: 33 additions & 0 deletions mod/searchx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from concurrent import futures

from mod.searchx import api, kugou


def search_all(title, artist, album, timeout=30):
funcs = [api, kugou]
results = []

def request(task):
res: list = task.search(title, artist, album)
if isinstance(res, list):
results.extend(res)

with futures.ThreadPoolExecutor() as executor:
_futures = []
for func in funcs:
_futures.append(executor.submit(request, func))

# 等待所有任务完成,或回收超时任务,处理TimeoutError
for future in futures.as_completed(_futures, timeout=timeout):
future.result()
# 回收超时任务
for future in _futures:
if future.done() and future.exception():
future.result()
else:
future.cancel()
return results


if __name__ == "__main__":
print(search_all("大地", "Beyond", ""))
Loading

0 comments on commit df4e0c8

Please sign in to comment.