Fork project
This commit is contained in:
34
utils/routers/__init__.py
Normal file
34
utils/routers/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
from CTFd.utils import get_config
|
||||
|
||||
from .frp import FrpRouter
|
||||
from .trp import TrpRouter
|
||||
|
||||
_routers = {
|
||||
'frp': FrpRouter,
|
||||
'trp': TrpRouter,
|
||||
}
|
||||
|
||||
|
||||
def instanciate(cls):
|
||||
return cls()
|
||||
|
||||
|
||||
@instanciate
|
||||
class Router:
|
||||
_name = ''
|
||||
_router = None
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
router_conftype = get_config("whale:router_type", "frp")
|
||||
if Router._name != router_conftype:
|
||||
Router._router = _routers[router_conftype]()
|
||||
Router._name = router_conftype
|
||||
return getattr(Router._router, name)
|
||||
|
||||
@staticmethod
|
||||
def reset():
|
||||
Router._name = ''
|
||||
Router._router = None
|
||||
|
||||
|
||||
__all__ = ["Router"]
|
||||
25
utils/routers/base.py
Normal file
25
utils/routers/base.py
Normal file
@ -0,0 +1,25 @@
|
||||
import typing
|
||||
|
||||
from ...models import WhaleContainer
|
||||
|
||||
|
||||
class BaseRouter:
|
||||
name = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def access(self, container: WhaleContainer):
|
||||
pass
|
||||
|
||||
def register(self, container: WhaleContainer):
|
||||
pass
|
||||
|
||||
def unregister(self, container: WhaleContainer):
|
||||
pass
|
||||
|
||||
def reload(self):
|
||||
pass
|
||||
|
||||
def check_availability(self) -> typing.Tuple[bool, str]:
|
||||
pass
|
||||
132
utils/routers/frp.py
Normal file
132
utils/routers/frp.py
Normal file
@ -0,0 +1,132 @@
|
||||
import warnings
|
||||
|
||||
from flask import current_app
|
||||
from requests import session, RequestException
|
||||
|
||||
from CTFd.models import db
|
||||
from CTFd.utils import get_config, set_config, logging
|
||||
|
||||
from .base import BaseRouter
|
||||
from ..cache import CacheProvider
|
||||
from ..db import DBContainer
|
||||
from ..exceptions import WhaleError, WhaleWarning
|
||||
from ...models import WhaleContainer
|
||||
|
||||
|
||||
class FrpRouter(BaseRouter):
|
||||
name = "frp"
|
||||
types = {
|
||||
'direct': 'tcp',
|
||||
'http': 'http',
|
||||
}
|
||||
|
||||
class FrpRule:
|
||||
def __init__(self, name, config):
|
||||
self.name = name
|
||||
self.config = config
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'[{self.name}]\n' + '\n'.join(f'{k} = {v}' for k, v in self.config.items())
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ses = session()
|
||||
self.url = get_config("whale:frp_api_url").rstrip("/")
|
||||
self.common = ''
|
||||
try:
|
||||
CacheProvider(app=current_app).init_port_sets()
|
||||
except Exception:
|
||||
warnings.warn(
|
||||
"cache initialization failed",
|
||||
WhaleWarning
|
||||
)
|
||||
|
||||
def reload(self, exclude=None):
|
||||
rules = []
|
||||
for container in DBContainer.get_all_alive_container():
|
||||
if container.uuid == exclude:
|
||||
continue
|
||||
name = f'{container.challenge.redirect_type}_{container.user_id}_{container.uuid}'
|
||||
config = {
|
||||
'type': self.types[container.challenge.redirect_type],
|
||||
'local_ip': f'{container.user_id}-{container.uuid}',
|
||||
'local_port': container.challenge.redirect_port,
|
||||
'use_compression': 'true',
|
||||
}
|
||||
if config['type'] == 'http':
|
||||
config['subdomain'] = container.http_subdomain
|
||||
elif config['type'] == 'tcp':
|
||||
config['remote_port'] = container.port
|
||||
rules.append(self.FrpRule(name, config))
|
||||
|
||||
try:
|
||||
if not self.common:
|
||||
common = get_config("whale:frp_config_template", '')
|
||||
if '[common]' in common:
|
||||
self.common = common
|
||||
else:
|
||||
remote = self.ses.get(f'{self.url}/api/config')
|
||||
assert remote.status_code == 200
|
||||
set_config("whale:frp_config_template", remote.text)
|
||||
self.common = remote.text
|
||||
config = self.common + '\n' + '\n'.join(str(r) for r in rules)
|
||||
assert self.ses.put(
|
||||
f'{self.url}/api/config', config, timeout=5
|
||||
).status_code == 200
|
||||
assert self.ses.get(
|
||||
f'{self.url}/api/reload', timeout=5
|
||||
).status_code == 200
|
||||
except (RequestException, AssertionError) as e:
|
||||
raise WhaleError(
|
||||
'\nfrpc request failed\n' +
|
||||
(f'{e}\n' if str(e) else '') +
|
||||
'please check the frp related configs'
|
||||
) from None
|
||||
|
||||
def access(self, container: WhaleContainer):
|
||||
if container.challenge.redirect_type == 'direct':
|
||||
return f'nc {get_config("whale:frp_direct_ip_address", "127.0.0.1")} {container.port}'
|
||||
elif container.challenge.redirect_type == 'http':
|
||||
host = get_config("whale:frp_http_domain_suffix", "")
|
||||
port = get_config("whale:frp_http_port", "80")
|
||||
host += f':{port}' if port != 80 else ''
|
||||
return f'<a target="_blank" href="http://{container.http_subdomain}.{host}/">Link to the Challenge</a>'
|
||||
return ''
|
||||
|
||||
def register(self, container: WhaleContainer):
|
||||
if container.challenge.redirect_type == 'direct':
|
||||
if not container.port:
|
||||
port = CacheProvider(app=current_app).get_available_port()
|
||||
if not port:
|
||||
return False, 'No available ports. Please wait for a few minutes.'
|
||||
container.port = port
|
||||
db.session.commit()
|
||||
elif container.challenge.redirect_type == 'http':
|
||||
# config['subdomain'] = container.http_subdomain
|
||||
pass
|
||||
self.reload()
|
||||
return True, 'success'
|
||||
|
||||
def unregister(self, container: WhaleContainer):
|
||||
if container.challenge.redirect_type == 'direct':
|
||||
try:
|
||||
redis_util = CacheProvider(app=current_app)
|
||||
redis_util.add_available_port(container.port)
|
||||
except Exception as e:
|
||||
logging.log(
|
||||
'whale', 'Error deleting port from cache',
|
||||
name=container.user.name,
|
||||
challenge_id=container.challenge_id,
|
||||
)
|
||||
return False, 'Error deleting port from cache'
|
||||
self.reload(exclude=container.uuid)
|
||||
return True, 'success'
|
||||
|
||||
def check_availability(self):
|
||||
try:
|
||||
resp = self.ses.get(f'{self.url}/api/status', timeout=2.0)
|
||||
except RequestException as e:
|
||||
return False, 'Unable to access frpc admin api'
|
||||
if resp.status_code == 401:
|
||||
return False, 'frpc admin api unauthorized'
|
||||
return True, 'Available'
|
||||
69
utils/routers/trp.py
Normal file
69
utils/routers/trp.py
Normal file
@ -0,0 +1,69 @@
|
||||
import traceback
|
||||
from requests import session, RequestException, HTTPError
|
||||
|
||||
from CTFd.utils import get_config
|
||||
from .base import BaseRouter
|
||||
from ..db import DBContainer, WhaleContainer
|
||||
|
||||
|
||||
class TrpRouter(BaseRouter):
|
||||
name = "trp"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ses = session()
|
||||
self.url = get_config('whale:trp_api_url', '').rstrip("/")
|
||||
self.common = ''
|
||||
for container in DBContainer.get_all_alive_container():
|
||||
self.register(container)
|
||||
|
||||
@staticmethod
|
||||
def get_domain(container: WhaleContainer):
|
||||
domain = get_config('whale:trp_domain_suffix', '127.0.0.1.nip.io').lstrip('.')
|
||||
domain = f'{container.uuid}.{domain}'
|
||||
return domain
|
||||
|
||||
def access(self, container: WhaleContainer):
|
||||
ch_type = container.challenge.redirect_type
|
||||
domain = self.get_domain(container)
|
||||
port = get_config('whale:trp_listening_port', 1443)
|
||||
if ch_type == 'direct':
|
||||
return f'from pwn import *<br>remote("{domain}", {port}, ssl=True).interactive()'
|
||||
elif ch_type == 'http':
|
||||
return f'https://{domain}' + (f':{port}' if port != 443 else '')
|
||||
else:
|
||||
return f'[ssl] {domain} {port}'
|
||||
|
||||
def register(self, container: WhaleContainer):
|
||||
try:
|
||||
resp = self.ses.post(f'{self.url}/rule/{self.get_domain(container)}', json={
|
||||
'target': f'{container.user_id}-{container.uuid}:{container.challenge.redirect_port}',
|
||||
'source': None,
|
||||
})
|
||||
resp.raise_for_status()
|
||||
return True, 'success'
|
||||
except HTTPError as e:
|
||||
return False, e.response.text
|
||||
except RequestException as e:
|
||||
print(traceback.format_exc())
|
||||
return False, 'unable to access trp Api'
|
||||
|
||||
def unregister(self, container: WhaleContainer):
|
||||
try:
|
||||
resp = self.ses.delete(f'{self.url}/rule/{self.get_domain(container)}')
|
||||
resp.raise_for_status()
|
||||
return True, 'success'
|
||||
except HTTPError as e:
|
||||
return False, e.response.text
|
||||
except RequestException as e:
|
||||
print(traceback.format_exc())
|
||||
return False, 'unable to access trp Api'
|
||||
|
||||
def check_availability(self):
|
||||
try:
|
||||
resp = self.ses.get(f'{self.url}/rules').json()
|
||||
except RequestException as e:
|
||||
return False, 'Unable to access trp admin api'
|
||||
except Exception as e:
|
||||
return False, 'Unknown trp error'
|
||||
return True, 'Available'
|
||||
Reference in New Issue
Block a user