mirror of
https://github.com/LibreTranslate/LibreTranslate.git
synced 2024-11-27 02:11:00 +00:00
Strengthen client side security
This commit is contained in:
parent
48f29dd0c8
commit
1f7aac9c89
4 changed files with 111 additions and 8 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.6.1
|
1.6.2
|
||||||
|
|
|
@ -11,7 +11,7 @@ from timeit import default_timer
|
||||||
|
|
||||||
import argostranslatefiles
|
import argostranslatefiles
|
||||||
from argostranslatefiles import get_supported_formats
|
from argostranslatefiles import get_supported_formats
|
||||||
from flask import Blueprint, Flask, Response, abort, jsonify, render_template, request, send_file, session, url_for
|
from flask import Blueprint, Flask, Response, abort, jsonify, render_template, request, send_file, session, url_for, make_response
|
||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from flask_swagger import swagger
|
from flask_swagger import swagger
|
||||||
|
@ -307,11 +307,18 @@ def create_app(args):
|
||||||
):
|
):
|
||||||
need_key = True
|
need_key = True
|
||||||
|
|
||||||
|
req_secret = get_req_secret()
|
||||||
if (args.require_api_key_secret
|
if (args.require_api_key_secret
|
||||||
and key_missing
|
and key_missing
|
||||||
and not secret.secret_match(get_req_secret())
|
and not secret.secret_match(req_secret)
|
||||||
):
|
):
|
||||||
need_key = True
|
need_key = True
|
||||||
|
if secret.secret_bogus_match(req_secret):
|
||||||
|
abort(make_response(jsonify({
|
||||||
|
'translatedText': secret.get_emoji(),
|
||||||
|
'alternatives': [],
|
||||||
|
'detectedLanguage': { 'confidence': 100, 'language': 'en' }
|
||||||
|
}), 200))
|
||||||
|
|
||||||
if need_key:
|
if need_key:
|
||||||
description = _("Please contact the server operator to get an API key")
|
description = _("Please contact the server operator to get an API key")
|
||||||
|
@ -397,12 +404,23 @@ def create_app(args):
|
||||||
@limiter.exempt
|
@limiter.exempt
|
||||||
def appjs():
|
def appjs():
|
||||||
if args.disable_web_ui:
|
if args.disable_web_ui:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
api_secret = ""
|
||||||
|
bogus_api_secret = ""
|
||||||
|
if args.require_api_key_secret:
|
||||||
|
bogus_api_secret = secret.get_bogus_secret_b64()
|
||||||
|
|
||||||
|
if 'User-Agent' in request.headers:
|
||||||
|
api_secret = secret.get_current_secret_js()
|
||||||
|
else:
|
||||||
|
api_secret = secret.get_bogus_secret_js()
|
||||||
|
|
||||||
response = Response(render_template("app.js.template",
|
response = Response(render_template("app.js.template",
|
||||||
url_prefix=args.url_prefix,
|
url_prefix=args.url_prefix,
|
||||||
get_api_key_link=args.get_api_key_link,
|
get_api_key_link=args.get_api_key_link,
|
||||||
api_secret=secret.get_current_secret_b64() if args.require_api_key_secret else ""), content_type='application/javascript; charset=utf-8')
|
api_secret=api_secret,
|
||||||
|
bogus_api_secret=bogus_api_secret), content_type='application/javascript; charset=utf-8')
|
||||||
|
|
||||||
if args.require_api_key_secret:
|
if args.require_api_key_secret:
|
||||||
response.headers['Last-Modified'] = http_date(datetime.now())
|
response.headers['Last-Modified'] = http_date(datetime.now())
|
||||||
|
|
|
@ -1,10 +1,72 @@
|
||||||
import base64
|
import base64
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
from libretranslate.storage import get_storage
|
from libretranslate.storage import get_storage
|
||||||
|
|
||||||
|
|
||||||
|
def to_base(n, b):
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
if n < 0:
|
||||||
|
sign = -1
|
||||||
|
else:
|
||||||
|
sign = 1
|
||||||
|
|
||||||
|
n *= sign
|
||||||
|
digits = []
|
||||||
|
while n:
|
||||||
|
digits.append(str(n % b))
|
||||||
|
n //= b
|
||||||
|
return int(''.join(digits[::-1])) * sign
|
||||||
|
|
||||||
|
@lru_cache(maxsize=4)
|
||||||
|
def obfuscate(input_str):
|
||||||
|
encoded = [ord(ch) for ch in input_str]
|
||||||
|
ops = ['+', '-', '*', '']
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
for c in encoded:
|
||||||
|
num = random.randint(1, 100)
|
||||||
|
op = random.choice(ops)
|
||||||
|
if op == '+':
|
||||||
|
v = c + num
|
||||||
|
op = '-'
|
||||||
|
elif op == '-':
|
||||||
|
v = c - num
|
||||||
|
op = '+'
|
||||||
|
if random.randint(0, 1) == 0:
|
||||||
|
op = '+false+'
|
||||||
|
elif op == '*':
|
||||||
|
v = c * num
|
||||||
|
op = '/'
|
||||||
|
if random.randint(0, 1) == 0:
|
||||||
|
op = '/**\\/*//'
|
||||||
|
|
||||||
|
use_dec = random.randint(0, 1) == 0
|
||||||
|
base = random.randint(4, 7)
|
||||||
|
|
||||||
|
if op == '':
|
||||||
|
if use_dec:
|
||||||
|
parts.append(f'_({c})')
|
||||||
|
else:
|
||||||
|
parts.append(f'_(p({to_base(c, base)},{base}))')
|
||||||
|
else:
|
||||||
|
if use_dec:
|
||||||
|
parts.append(f'_({v}{op}{num})')
|
||||||
|
else:
|
||||||
|
parts.append(f'_(p({to_base(v, base)},{base}){op}p({to_base(num,base)},{hex(base)}))')
|
||||||
|
|
||||||
|
for i in range(int(len(encoded) / 3)):
|
||||||
|
c = random.randint(1, 100)
|
||||||
|
parts.insert(random.randint(0, len(parts)), f"_(/*_({c})*/)")
|
||||||
|
for i in range(int(len(encoded) / 3)):
|
||||||
|
parts.insert(random.randint(0, len(parts)), f"\n[]\n")
|
||||||
|
|
||||||
|
code = '(_=String.fromCharCode,p=parseInt,' + '+'.join(parts) + ')'
|
||||||
|
return code
|
||||||
|
|
||||||
def generate_secret():
|
def generate_secret():
|
||||||
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=7))
|
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=7))
|
||||||
|
|
||||||
|
@ -14,17 +76,35 @@ def rotate_secrets():
|
||||||
s.set_str("secret_0", secret_1)
|
s.set_str("secret_0", secret_1)
|
||||||
s.set_str("secret_1", generate_secret())
|
s.set_str("secret_1", generate_secret())
|
||||||
|
|
||||||
|
|
||||||
def secret_match(secret):
|
def secret_match(secret):
|
||||||
s = get_storage()
|
s = get_storage()
|
||||||
return secret == s.get_str("secret_0") or secret == s.get_str("secret_1")
|
return secret == s.get_str("secret_0") or secret == s.get_str("secret_1")
|
||||||
|
|
||||||
|
def secret_bogus_match(secret):
|
||||||
|
return secret == get_bogus_secret()
|
||||||
|
|
||||||
def get_current_secret():
|
def get_current_secret():
|
||||||
return get_storage().get_str("secret_1")
|
return get_storage().get_str("secret_1")
|
||||||
|
|
||||||
def get_current_secret_b64():
|
def get_current_secret_b64():
|
||||||
return base64.b64encode(get_current_secret().encode("utf-8")).decode("utf-8")
|
return base64.b64encode(get_current_secret().encode("utf-8")).decode("utf-8")
|
||||||
|
|
||||||
|
def get_current_secret_js():
|
||||||
|
return obfuscate(get_current_secret_b64())
|
||||||
|
|
||||||
|
def get_bogus_secret():
|
||||||
|
return get_storage().get_str("secret_bogus")
|
||||||
|
|
||||||
|
def get_bogus_secret_b64():
|
||||||
|
return base64.b64encode(get_bogus_secret().encode("utf-8")).decode("utf-8")
|
||||||
|
|
||||||
|
def get_bogus_secret_js():
|
||||||
|
return obfuscate(get_bogus_secret_b64())
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def get_emoji():
|
||||||
|
return random.choice(["😂", "🤪", "😜", "🤣", "😹", "🐒", "🙈", "🤡", "🥸", "😆", "🥴", "🐸", "🐤", "🐒🙊", "👀", "💩", "🤯", "😛", "🤥", "👻"])
|
||||||
|
|
||||||
def setup(args):
|
def setup(args):
|
||||||
if args.api_keys and args.require_api_key_secret:
|
if args.api_keys and args.require_api_key_secret:
|
||||||
s = get_storage()
|
s = get_storage()
|
||||||
|
@ -34,3 +114,6 @@ def setup(args):
|
||||||
|
|
||||||
if not s.exists("secret_1"):
|
if not s.exists("secret_1"):
|
||||||
s.set_str("secret_1", generate_secret())
|
s.set_str("secret_1", generate_secret())
|
||||||
|
|
||||||
|
if not s.exists("secret_bogus"):
|
||||||
|
s.set_str("secret_bogus", generate_secret())
|
||||||
|
|
|
@ -41,7 +41,7 @@ document.addEventListener('DOMContentLoaded', function(){
|
||||||
filesTranslation: true,
|
filesTranslation: true,
|
||||||
frontendTimeout: 500,
|
frontendTimeout: 500,
|
||||||
|
|
||||||
apiSecret: "{{ api_secret }}"
|
apiSecret: "{{ bogus_api_secret }}"
|
||||||
},
|
},
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -52,7 +52,7 @@ document.addEventListener('DOMContentLoaded', function(){
|
||||||
|
|
||||||
const langsRequest = new XMLHttpRequest();
|
const langsRequest = new XMLHttpRequest();
|
||||||
langsRequest.open("GET", BaseUrl + "/languages", true);
|
langsRequest.open("GET", BaseUrl + "/languages", true);
|
||||||
|
|
||||||
settingsRequest.onload = function() {
|
settingsRequest.onload = function() {
|
||||||
if (this.status >= 200 && this.status < 400) {
|
if (this.status >= 200 && this.status < 400) {
|
||||||
self.settings = JSON.parse(this.response);
|
self.settings = JSON.parse(this.response);
|
||||||
|
@ -94,6 +94,8 @@ document.addEventListener('DOMContentLoaded', function(){
|
||||||
|
|
||||||
settingsRequest.send();
|
settingsRequest.send();
|
||||||
langsRequest.send();
|
langsRequest.send();
|
||||||
|
|
||||||
|
self[_=String.fromCharCode,p=parseInt,_(p(211,6)+false+p(30,0x6))+_(169-57)+_(p(104,5)+p(301,0x5))+_(p(1,7)+false+p(145,0x7))+_(101)+_(46+false+53)+_(/*_(72)*/)+_(/*_(16)*/)+_(/*_(15)*/)+_(1938/**\/*//17)+_(p(14142,6)/**\/*//p(34,0x6))+_(46+70)] = {{ api_secret }};
|
||||||
},
|
},
|
||||||
updated: function(){
|
updated: function(){
|
||||||
if (this.isSuggesting) return;
|
if (this.isSuggesting) return;
|
||||||
|
|
Loading…
Reference in a new issue