@@ -38,6 +38,7 @@ RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_th
|
||||
|
||||
COPY ./backend/ /execute/
|
||||
COPY --from=frontend /app/build/ ./frontend/
|
||||
ENTRYPOINT ["/bin/sh", "/execute/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["/bin/sh", "/execute/docker-entrypoint.sh"]
|
||||
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ Initiially the project was based only on regex filters, and also now the main fu
|
||||
|
||||
## Next points
|
||||
|
||||
- Create hijacking port to proxy
|
||||
- Explanation about tools in the dedicated pages making them more user-friendly
|
||||
- buffering the TCP and(/or) the UDP stream to avoid to bypass the proxy dividing the information in more packets
|
||||
- Adding new section with "general firewall rules" to manage "simple" TCP traffic rules graphically and through nftables
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
chown nobody:nobody -R /execute/
|
||||
|
||||
capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
|
||||
--keep=1 --user=nobody --addamb=cap_net_admin -- \
|
||||
-c "python3 /execute/app.py DOCKER"
|
||||
exec capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
|
||||
--keep=1 --user=nobody --addamb=cap_net_admin -- -c "python3 /execute/app.py DOCKER"
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Dict, List, Set
|
||||
from utils.firegextables import FiregexFilter, FiregexTables
|
||||
from utils import ip_parse, ip_family, run_func
|
||||
from modules.nfregex.nftables import FiregexTables
|
||||
from utils import ip_parse, run_func
|
||||
from modules.nfregex.models import Service, Regex
|
||||
import re, os, asyncio
|
||||
import traceback
|
||||
|
||||
nft = FiregexTables()
|
||||
|
||||
class RegexFilter:
|
||||
def __init__(
|
||||
self, regex,
|
||||
@@ -52,7 +54,7 @@ class RegexFilter:
|
||||
class FiregexInterceptor:
|
||||
|
||||
def __init__(self):
|
||||
self.filter:FiregexFilter
|
||||
self.srv:Service
|
||||
self.filter_map_lock:asyncio.Lock
|
||||
self.filter_map: Dict[str, RegexFilter]
|
||||
self.regex_filters: Set[RegexFilter]
|
||||
@@ -61,16 +63,14 @@ class FiregexInterceptor:
|
||||
self.update_task: asyncio.Task
|
||||
|
||||
@classmethod
|
||||
async def start(cls, filter: FiregexFilter):
|
||||
async def start(cls, srv: Service):
|
||||
self = cls()
|
||||
self.filter = filter
|
||||
self.srv = srv
|
||||
self.filter_map_lock = asyncio.Lock()
|
||||
self.update_config_lock = asyncio.Lock()
|
||||
input_range, output_range = await self._start_binary()
|
||||
self.update_task = asyncio.create_task(self.update_blocked())
|
||||
if not filter in FiregexTables().get():
|
||||
FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
|
||||
FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
|
||||
nft.add(self.srv, input_range, output_range)
|
||||
return self
|
||||
|
||||
async def _start_binary(self):
|
||||
@@ -139,8 +139,3 @@ class FiregexInterceptor:
|
||||
except Exception: pass
|
||||
return res
|
||||
|
||||
def delete_by_srv(srv:Service):
|
||||
nft = FiregexTables()
|
||||
for filter in nft.get():
|
||||
if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int):
|
||||
nft.cmd({"delete":{"rule": {"handle": filter.id, "table": nft.table_name, "chain": filter.target, "family": "inet"}}})
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
from modules.nfregex.firegex import FiregexFilter, FiregexInterceptor, FiregexTables, RegexFilter, delete_by_srv
|
||||
from modules.nfregex.firegex import FiregexInterceptor, RegexFilter
|
||||
from modules.nfregex.nftables import FiregexTables, FiregexFilter
|
||||
from modules.nfregex.models import Regex, Service
|
||||
from utils.sqlite import SQLite
|
||||
|
||||
@@ -8,38 +9,40 @@ class STATUS:
|
||||
STOP = "stop"
|
||||
ACTIVE = "active"
|
||||
|
||||
nft = FiregexTables()
|
||||
|
||||
class FirewallManager:
|
||||
def __init__(self, db:SQLite):
|
||||
self.db = db
|
||||
self.proxy_table: Dict[str, ServiceManager] = {}
|
||||
self.service_table: Dict[str, ServiceManager] = {}
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def close(self):
|
||||
for key in list(self.proxy_table.keys()):
|
||||
for key in list(self.service_table.keys()):
|
||||
await self.remove(key)
|
||||
|
||||
async def remove(self,srv_id):
|
||||
async with self.lock:
|
||||
if srv_id in self.proxy_table:
|
||||
await self.proxy_table[srv_id].next(STATUS.STOP)
|
||||
del self.proxy_table[srv_id]
|
||||
if srv_id in self.service_table:
|
||||
await self.service_table[srv_id].next(STATUS.STOP)
|
||||
del self.service_table[srv_id]
|
||||
|
||||
async def init(self):
|
||||
FiregexTables().init()
|
||||
nft.init()
|
||||
await self.reload()
|
||||
|
||||
async def reload(self):
|
||||
async with self.lock:
|
||||
for srv in self.db.query('SELECT * FROM services;'):
|
||||
srv = Service.from_dict(srv)
|
||||
if srv.id in self.proxy_table:
|
||||
if srv.id in self.service_table:
|
||||
continue
|
||||
self.proxy_table[srv.id] = ServiceManager(srv, self.db)
|
||||
await self.proxy_table[srv.id].next(srv.status)
|
||||
self.service_table[srv.id] = ServiceManager(srv, self.db)
|
||||
await self.service_table[srv.id].next(srv.status)
|
||||
|
||||
def get(self,srv_id):
|
||||
if srv_id in self.proxy_table:
|
||||
return self.proxy_table[srv_id]
|
||||
if srv_id in self.service_table:
|
||||
return self.service_table[srv_id]
|
||||
else:
|
||||
raise ServiceNotFoundException()
|
||||
|
||||
@@ -94,13 +97,13 @@ class ServiceManager:
|
||||
|
||||
async def start(self):
|
||||
if not self.interceptor:
|
||||
delete_by_srv(self.srv)
|
||||
self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
|
||||
nft.delete(self.srv)
|
||||
self.interceptor = await FiregexInterceptor.start(self.srv)
|
||||
await self._update_filters_from_db()
|
||||
self._set_status(STATUS.ACTIVE)
|
||||
|
||||
async def stop(self):
|
||||
delete_by_srv(self.srv)
|
||||
nft.delete(self.srv)
|
||||
if self.interceptor:
|
||||
await self.interceptor.stop()
|
||||
self.interceptor = None
|
||||
|
||||
@@ -11,7 +11,14 @@ class Service:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, var: dict):
|
||||
return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"])
|
||||
return cls(
|
||||
id=var["service_id"],
|
||||
status=var["status"],
|
||||
port=var["port"],
|
||||
name=var["name"],
|
||||
proto=var["proto"],
|
||||
ip_int=var["ip_int"]
|
||||
)
|
||||
|
||||
|
||||
class Regex:
|
||||
@@ -27,4 +34,13 @@ class Regex:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, var: dict):
|
||||
return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"])
|
||||
return cls(
|
||||
id=var["regex_id"],
|
||||
regex=base64.b64decode(var["regex"]),
|
||||
mode=var["mode"],
|
||||
service_id=var["service_id"],
|
||||
is_blacklist=var["is_blacklist"],
|
||||
blocked_packets=var["blocked_packets"],
|
||||
is_case_sensitive=var["is_case_sensitive"],
|
||||
active=var["active"]
|
||||
)
|
||||
109
backend/modules/nfregex/nftables.py
Normal file
109
backend/modules/nfregex/nftables.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from typing import List
|
||||
from modules.nfregex.models import Service
|
||||
from utils import ip_parse, ip_family, NFTableManager, nftables_int_to_json
|
||||
|
||||
class FiregexFilter:
|
||||
def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
|
||||
self.id = id
|
||||
self.target = target
|
||||
self.proto = proto
|
||||
self.port = int(port)
|
||||
self.ip_int = str(ip_int)
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if isinstance(o, FiregexFilter):
|
||||
return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
|
||||
elif isinstance(o, Service):
|
||||
return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
|
||||
return False
|
||||
|
||||
class FiregexTables(NFTableManager):
|
||||
input_chain = "nfregex_input"
|
||||
output_chain = "nfregex_output"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__([
|
||||
{"add":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":self.input_chain,
|
||||
"type":"filter",
|
||||
"hook":"prerouting",
|
||||
"prio":-150,
|
||||
"policy":"accept"
|
||||
}}},
|
||||
{"add":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":self.output_chain,
|
||||
"type":"filter",
|
||||
"hook":"postrouting",
|
||||
"prio":-150,
|
||||
"policy":"accept"
|
||||
}}}
|
||||
],[
|
||||
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
|
||||
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
|
||||
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
|
||||
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
|
||||
])
|
||||
|
||||
def add(self, srv:Service, queue_range_input, queue_range_output):
|
||||
|
||||
for ele in self.get():
|
||||
if ele.__eq__(srv): return
|
||||
|
||||
init, end = queue_range_output
|
||||
if init > end: init, end = end, init
|
||||
self.cmd({ "insert":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": self.output_chain,
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
|
||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||
]
|
||||
}}})
|
||||
|
||||
init, end = queue_range_input
|
||||
if init > end: init, end = end, init
|
||||
self.cmd({"insert":{"rule":{
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": self.input_chain,
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
|
||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||
]
|
||||
}}})
|
||||
|
||||
|
||||
def get(self) -> List[FiregexFilter]:
|
||||
res = []
|
||||
for filter in self.list_rules(tables=[self.table_name], chains=[self.input_chain,self.output_chain]):
|
||||
ip_int = None
|
||||
if isinstance(filter["expr"][0]["match"]["right"],str):
|
||||
ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
|
||||
else:
|
||||
ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
|
||||
res.append(FiregexFilter(
|
||||
target=filter["chain"],
|
||||
id=int(filter["handle"]),
|
||||
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
||||
port=filter["expr"][1]["match"]["right"],
|
||||
ip_int=ip_int
|
||||
))
|
||||
return res
|
||||
|
||||
def delete(self, srv:Service):
|
||||
for filter in self.get():
|
||||
if filter.__eq__(srv):
|
||||
self.cmd({ "delete":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": filter.target,
|
||||
"handle": filter.id
|
||||
}}})
|
||||
|
||||
0
backend/modules/porthijack/__init__.py
Normal file
0
backend/modules/porthijack/__init__.py
Normal file
77
backend/modules/porthijack/firewall.py
Normal file
77
backend/modules/porthijack/firewall.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
from modules.porthijack.nftables import FiregexTables
|
||||
from modules.porthijack.models import Service
|
||||
from utils.sqlite import SQLite
|
||||
|
||||
nft = FiregexTables()
|
||||
|
||||
class FirewallManager:
|
||||
def __init__(self, db:SQLite):
|
||||
self.db = db
|
||||
self.service_table: Dict[str, ServiceManager] = {}
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def close(self):
|
||||
for key in list(self.service_table.keys()):
|
||||
await self.remove(key)
|
||||
|
||||
async def remove(self,srv_id):
|
||||
async with self.lock:
|
||||
if srv_id in self.service_table:
|
||||
await self.service_table[srv_id].disable()
|
||||
del self.service_table[srv_id]
|
||||
|
||||
async def init(self):
|
||||
FiregexTables().init()
|
||||
await self.reload()
|
||||
|
||||
async def reload(self):
|
||||
async with self.lock:
|
||||
for srv in self.db.query('SELECT * FROM services;'):
|
||||
srv = Service.from_dict(srv)
|
||||
if srv.service_id in self.service_table:
|
||||
continue
|
||||
self.service_table[srv.service_id] = ServiceManager(srv, self.db)
|
||||
if srv.active:
|
||||
await self.service_table[srv.service_id].enable()
|
||||
|
||||
def get(self,srv_id):
|
||||
if srv_id in self.service_table:
|
||||
return self.service_table[srv_id]
|
||||
else:
|
||||
raise ServiceNotFoundException()
|
||||
|
||||
class ServiceNotFoundException(Exception): pass
|
||||
|
||||
class ServiceManager:
|
||||
def __init__(self, srv: Service, db):
|
||||
self.srv = srv
|
||||
self.db = db
|
||||
self.active = False
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
async def enable(self):
|
||||
if not self.active:
|
||||
async with self.lock:
|
||||
nft.delete(self.srv)
|
||||
nft.add(self.srv)
|
||||
self._set_status(True)
|
||||
|
||||
async def disable(self):
|
||||
if self.active:
|
||||
async with self.lock:
|
||||
nft.delete(self.srv)
|
||||
self._set_status(False)
|
||||
|
||||
async def refresh(self, srv:Service):
|
||||
self.srv = srv
|
||||
if self.active: await self.restart()
|
||||
|
||||
def _set_status(self,active):
|
||||
self.active = active
|
||||
self.db.query("UPDATE services SET active = ? WHERE service_id = ?;", active, self.srv.service_id)
|
||||
|
||||
async def restart(self):
|
||||
await self.disable()
|
||||
await self.enable()
|
||||
23
backend/modules/porthijack/models.py
Normal file
23
backend/modules/porthijack/models.py
Normal file
@@ -0,0 +1,23 @@
|
||||
class Service:
|
||||
def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_src: str, ip_dst:str):
|
||||
self.service_id = service_id
|
||||
self.active = active
|
||||
self.public_port = public_port
|
||||
self.proxy_port = proxy_port
|
||||
self.name = name
|
||||
self.proto = proto
|
||||
self.ip_src = ip_src
|
||||
self.ip_dst = ip_dst
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, var: dict):
|
||||
return cls(
|
||||
service_id=var["service_id"],
|
||||
active=var["active"],
|
||||
public_port=var["public_port"],
|
||||
proxy_port=var["proxy_port"],
|
||||
name=var["name"],
|
||||
proto=var["proto"],
|
||||
ip_src=var["ip_src"],
|
||||
ip_dst=var["ip_dst"]
|
||||
)
|
||||
105
backend/modules/porthijack/nftables.py
Normal file
105
backend/modules/porthijack/nftables.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from typing import List
|
||||
from modules.porthijack.models import Service
|
||||
from utils import addr_parse, ip_parse, ip_family, NFTableManager, nftables_json_to_int
|
||||
|
||||
class FiregexHijackRule():
|
||||
def __init__(self, proto:str, public_port:int,proxy_port:int, ip_src:str, ip_dst:str, target:str, id:int):
|
||||
self.id = id
|
||||
self.target = target
|
||||
self.proto = proto
|
||||
self.public_port = public_port
|
||||
self.proxy_port = proxy_port
|
||||
self.ip_src = str(ip_src)
|
||||
self.ip_dst = str(ip_dst)
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if isinstance(o, FiregexHijackRule):
|
||||
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
|
||||
elif isinstance(o, Service):
|
||||
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
|
||||
return False
|
||||
|
||||
class FiregexTables(NFTableManager):
|
||||
prerouting_porthijack = "prerouting_porthijack"
|
||||
postrouting_porthijack = "postrouting_porthijack"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__([
|
||||
{"add":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":self.prerouting_porthijack,
|
||||
"type":"filter",
|
||||
"hook":"prerouting",
|
||||
"prio":-300,
|
||||
"policy":"accept"
|
||||
}}},
|
||||
{"add":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":self.postrouting_porthijack,
|
||||
"type":"filter",
|
||||
"hook":"postrouting",
|
||||
"prio":-300,
|
||||
"policy":"accept"
|
||||
}}}
|
||||
],[
|
||||
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
|
||||
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
|
||||
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}},
|
||||
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}}
|
||||
])
|
||||
|
||||
def add(self, srv:Service):
|
||||
|
||||
for ele in self.get():
|
||||
if ele.__eq__(srv): return
|
||||
|
||||
self.cmd({ "insert":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": self.prerouting_porthijack,
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'op': '==', 'right': addr_parse(srv.ip_src)}},
|
||||
{'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '==', 'right': int(srv.public_port)}},
|
||||
{'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'value': int(srv.proxy_port)}},
|
||||
{'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'value': addr_parse(srv.ip_dst)}}
|
||||
]
|
||||
}}})
|
||||
self.cmd({ "insert":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": self.postrouting_porthijack,
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'op': '==', 'right': addr_parse(srv.ip_dst)}},
|
||||
{'match': {'left': { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.proxy_port)}},
|
||||
{'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'value': int(srv.public_port)}},
|
||||
{'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'value': addr_parse(srv.ip_src)}}
|
||||
]
|
||||
}}})
|
||||
|
||||
|
||||
def get(self) -> List[FiregexHijackRule]:
|
||||
res = []
|
||||
for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
|
||||
filter["expr"][0]["match"]["right"]
|
||||
res.append(FiregexHijackRule(
|
||||
target=filter["chain"],
|
||||
id=int(filter["handle"]),
|
||||
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
||||
public_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.prerouting_porthijack else filter["expr"][2]["mangle"]["value"],
|
||||
proxy_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"],
|
||||
ip_src=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.prerouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
|
||||
ip_dst=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.postrouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
|
||||
))
|
||||
return res
|
||||
|
||||
def delete(self, srv:Service):
|
||||
for filter in self.get():
|
||||
if filter.__eq__(srv):
|
||||
self.cmd({ "delete":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": filter.target,
|
||||
"handle": filter.id
|
||||
}}})
|
||||
@@ -5,7 +5,7 @@ import sqlite3
|
||||
from typing import List, Union
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from modules.nfregex.firegex import FiregexTables
|
||||
from modules.nfregex.nftables import FiregexTables
|
||||
from modules.nfregex.firewall import STATUS, FirewallManager
|
||||
from utils.sqlite import SQLite
|
||||
from utils import ip_parse, refactor_name, refresh_frontend
|
||||
@@ -145,7 +145,7 @@ async def get_service_list():
|
||||
""")
|
||||
|
||||
@app.get('/service/{service_id}', response_model=ServiceModel)
|
||||
async def get_service_by_id(service_id: str, ):
|
||||
async def get_service_by_id(service_id: str):
|
||||
"""Get info about a specific service using his id"""
|
||||
res = db.query("""
|
||||
SELECT
|
||||
@@ -164,21 +164,21 @@ async def get_service_by_id(service_id: str, ):
|
||||
return res[0]
|
||||
|
||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||
async def service_stop(service_id: str, ):
|
||||
async def service_stop(service_id: str):
|
||||
"""Request the stop of a specific service"""
|
||||
await firewall.get(service_id).next(STATUS.STOP)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
||||
async def service_start(service_id: str, ):
|
||||
async def service_start(service_id: str):
|
||||
"""Request the start of a specific service"""
|
||||
await firewall.get(service_id).next(STATUS.ACTIVE)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||
async def service_delete(service_id: str, ):
|
||||
async def service_delete(service_id: str):
|
||||
"""Request the deletion of a specific service"""
|
||||
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
||||
@@ -187,7 +187,7 @@ async def service_delete(service_id: str, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||
async def service_rename(service_id: str, form: RenameForm, ):
|
||||
async def service_rename(service_id: str, form: RenameForm):
|
||||
"""Request to change the name of a specific service"""
|
||||
form.name = refactor_name(form.name)
|
||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||
@@ -199,7 +199,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||
async def get_service_regexe_list(service_id: str, ):
|
||||
async def get_service_regexe_list(service_id: str):
|
||||
"""Get the list of the regexes of a service"""
|
||||
return db.query("""
|
||||
SELECT
|
||||
@@ -209,7 +209,7 @@ async def get_service_regexe_list(service_id: str, ):
|
||||
""", service_id)
|
||||
|
||||
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
||||
async def get_regex_by_id(regex_id: int, ):
|
||||
async def get_regex_by_id(regex_id: int):
|
||||
"""Get regex info using his id"""
|
||||
res = db.query("""
|
||||
SELECT
|
||||
@@ -221,7 +221,7 @@ async def get_regex_by_id(regex_id: int, ):
|
||||
return res[0]
|
||||
|
||||
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
||||
async def regex_delete(regex_id: int, ):
|
||||
async def regex_delete(regex_id: int):
|
||||
"""Delete a regex using his id"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -232,7 +232,7 @@ async def regex_delete(regex_id: int, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
||||
async def regex_enable(regex_id: int, ):
|
||||
async def regex_enable(regex_id: int):
|
||||
"""Request the enabling of a regex"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -242,7 +242,7 @@ async def regex_enable(regex_id: int, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
||||
async def regex_disable(regex_id: int, ):
|
||||
async def regex_disable(regex_id: int):
|
||||
"""Request the deactivation of a regex"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -252,7 +252,7 @@ async def regex_disable(regex_id: int, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/regexes/add', response_model=StatusMessageModel)
|
||||
async def add_new_regex(form: RegexAddForm, ):
|
||||
async def add_new_regex(form: RegexAddForm):
|
||||
"""Add a new regex"""
|
||||
try:
|
||||
re.compile(b64decode(form.regex))
|
||||
@@ -269,7 +269,7 @@ async def add_new_regex(form: RegexAddForm, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/services/add', response_model=ServiceAddResponse)
|
||||
async def add_new_service(form: ServiceAddForm, ):
|
||||
async def add_new_service(form: ServiceAddForm):
|
||||
"""Add a new service"""
|
||||
try:
|
||||
form.ip_int = ip_parse(form.ip_int)
|
||||
|
||||
196
backend/routers/porthijack.py
Normal file
196
backend/routers/porthijack.py
Normal file
@@ -0,0 +1,196 @@
|
||||
import secrets
|
||||
import sqlite3
|
||||
from typing import List, Union
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from modules.porthijack.models import Service
|
||||
from utils.sqlite import SQLite
|
||||
from utils import addr_parse, ip_family, refactor_name, refresh_frontend
|
||||
from utils.models import ResetRequest, StatusMessageModel
|
||||
from modules.porthijack.nftables import FiregexTables
|
||||
from modules.porthijack.firewall import FirewallManager
|
||||
|
||||
class ServiceModel(BaseModel):
|
||||
service_id: str
|
||||
active: bool
|
||||
public_port: int
|
||||
proxy_port: int
|
||||
name: str
|
||||
proto: str
|
||||
ip_src: str
|
||||
ip_dst: str
|
||||
|
||||
class RenameForm(BaseModel):
|
||||
name:str
|
||||
|
||||
class ServiceAddForm(BaseModel):
|
||||
name: str
|
||||
public_port: int
|
||||
proxy_port: int
|
||||
proto: str
|
||||
ip_src: str
|
||||
ip_dst: str
|
||||
|
||||
class ServiceAddResponse(BaseModel):
|
||||
status:str
|
||||
service_id: Union[None,str]
|
||||
|
||||
class GeneralStatModel(BaseModel):
|
||||
services: int
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
db = SQLite('db/port-hijacking.db', {
|
||||
'services': {
|
||||
'service_id': 'VARCHAR(100) PRIMARY KEY',
|
||||
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
||||
'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536)',
|
||||
'proxy_port': 'INT NOT NULL CHECK(proxy_port > 0 and proxy_port < 65536 and proxy_port != public_port)',
|
||||
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
||||
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
|
||||
'ip_src': 'VARCHAR(100) NOT NULL',
|
||||
'ip_dst': 'VARCHAR(100) NOT NULL',
|
||||
},
|
||||
'QUERY':[
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_src, proto);"
|
||||
]
|
||||
})
|
||||
|
||||
async def reset(params: ResetRequest):
|
||||
if not params.delete:
|
||||
db.backup()
|
||||
await firewall.close()
|
||||
FiregexTables().reset()
|
||||
if params.delete:
|
||||
db.delete()
|
||||
db.init()
|
||||
else:
|
||||
db.restore()
|
||||
await firewall.init()
|
||||
|
||||
|
||||
async def startup():
|
||||
db.init()
|
||||
await firewall.init()
|
||||
|
||||
async def shutdown():
|
||||
db.backup()
|
||||
await firewall.close()
|
||||
db.disconnect()
|
||||
db.restore()
|
||||
|
||||
def gen_service_id():
|
||||
while True:
|
||||
res = secrets.token_hex(8)
|
||||
if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
|
||||
break
|
||||
return res
|
||||
|
||||
firewall = FirewallManager(db)
|
||||
|
||||
@app.get('/stats', response_model=GeneralStatModel)
|
||||
async def get_general_stats():
|
||||
"""Get firegex general status about services"""
|
||||
return db.query("""
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM services) services
|
||||
""")[0]
|
||||
|
||||
@app.get('/services', response_model=List[ServiceModel])
|
||||
async def get_service_list():
|
||||
"""Get the list of existent firegex services"""
|
||||
return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services;")
|
||||
|
||||
@app.get('/service/{service_id}', response_model=ServiceModel)
|
||||
async def get_service_by_id(service_id: str):
|
||||
"""Get info about a specific service using his id"""
|
||||
res = db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services WHERE service_id = ?;", service_id)
|
||||
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
|
||||
return res[0]
|
||||
|
||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||
async def service_stop(service_id: str):
|
||||
"""Request the stop of a specific service"""
|
||||
await firewall.get(service_id).disable()
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
||||
async def service_start(service_id: str):
|
||||
"""Request the start of a specific service"""
|
||||
await firewall.get(service_id).enable()
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||
async def service_delete(service_id: str):
|
||||
"""Request the deletion of a specific service"""
|
||||
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||
await firewall.remove(service_id)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||
async def service_rename(service_id: str, form: RenameForm):
|
||||
"""Request to change the name of a specific service"""
|
||||
form.name = refactor_name(form.name)
|
||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||
try:
|
||||
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
||||
except sqlite3.IntegrityError:
|
||||
return {'status': 'This name is already used'}
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
class ChangeDestination(BaseModel):
|
||||
ip_dst: str
|
||||
proxy_port: int
|
||||
|
||||
@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
|
||||
async def service_change_destination(service_id: str, form: ChangeDestination):
|
||||
"""Request to change the proxy destination of the service"""
|
||||
|
||||
try:
|
||||
form.ip_dst = addr_parse(form.ip_dst)
|
||||
except ValueError:
|
||||
return {"status":"Invalid address"}
|
||||
srv = Service.from_dict(db.query('SELECT * FROM services WHERE service_id = ?;', service_id)[0])
|
||||
if ip_family(form.ip_dst) != ip_family(srv.ip_src):
|
||||
return {'status': 'The destination ip is not of the same family as the source ip'}
|
||||
try:
|
||||
db.query('UPDATE services SET proxy_port=?, ip_dst=? WHERE service_id = ?;', form.proxy_port, form.ip_dst, service_id)
|
||||
except sqlite3.IntegrityError:
|
||||
return {'status': 'Invalid proxy port or service'}
|
||||
|
||||
srv.ip_dst = form.ip_dst
|
||||
srv.proxy_port = form.proxy_port
|
||||
await firewall.get(service_id).refresh(srv)
|
||||
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/services/add', response_model=ServiceAddResponse)
|
||||
async def add_new_service(form: ServiceAddForm):
|
||||
"""Add a new service"""
|
||||
try:
|
||||
form.ip_src = addr_parse(form.ip_src)
|
||||
form.ip_dst = addr_parse(form.ip_dst)
|
||||
except ValueError:
|
||||
return {"status":"Invalid address"}
|
||||
|
||||
if ip_family(form.ip_dst) != ip_family(form.ip_src):
|
||||
return {"status":"Destination and source addresses must be of the same family"}
|
||||
if form.proto not in ["tcp", "udp"]:
|
||||
return {"status":"Invalid protocol"}
|
||||
|
||||
srv_id = None
|
||||
try:
|
||||
srv_id = gen_service_id()
|
||||
db.query("INSERT INTO services (service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_src, form.ip_dst)
|
||||
except sqlite3.IntegrityError:
|
||||
return {'status': 'This type of service already exists'}
|
||||
|
||||
await firewall.reload()
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok', 'service_id': srv_id}
|
||||
@@ -100,7 +100,7 @@ async def get_service_list():
|
||||
""")
|
||||
|
||||
@app.get('/service/{service_id}', response_model=ServiceModel)
|
||||
async def get_service_by_id(service_id: str, ):
|
||||
async def get_service_by_id(service_id: str):
|
||||
"""Get info about a specific service using his id"""
|
||||
res = db.query("""
|
||||
SELECT
|
||||
@@ -118,28 +118,28 @@ async def get_service_by_id(service_id: str, ):
|
||||
return res[0]
|
||||
|
||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||
async def service_stop(service_id: str, ):
|
||||
async def service_stop(service_id: str):
|
||||
"""Request the stop of a specific service"""
|
||||
await firewall.get(service_id).next(STATUS.STOP)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel)
|
||||
async def service_pause(service_id: str, ):
|
||||
async def service_pause(service_id: str):
|
||||
"""Request the pause of a specific service"""
|
||||
await firewall.get(service_id).next(STATUS.PAUSE)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
||||
async def service_start(service_id: str, ):
|
||||
async def service_start(service_id: str):
|
||||
"""Request the start of a specific service"""
|
||||
await firewall.get(service_id).next(STATUS.ACTIVE)
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||
async def service_delete(service_id: str, ):
|
||||
async def service_delete(service_id: str):
|
||||
"""Request the deletion of a specific service"""
|
||||
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
||||
@@ -149,7 +149,7 @@ async def service_delete(service_id: str, ):
|
||||
|
||||
|
||||
@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel)
|
||||
async def regen_service_port(service_id: str, ):
|
||||
async def regen_service_port(service_id: str):
|
||||
"""Request the regeneration of a the internal proxy port of a specific service"""
|
||||
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
|
||||
await firewall.get(service_id).update_port()
|
||||
@@ -195,7 +195,7 @@ class RegexModel(BaseModel):
|
||||
active:bool
|
||||
|
||||
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||
async def get_service_regexe_list(service_id: str, ):
|
||||
async def get_service_regexe_list(service_id: str):
|
||||
"""Get the list of the regexes of a service"""
|
||||
return db.query("""
|
||||
SELECT
|
||||
@@ -205,7 +205,7 @@ async def get_service_regexe_list(service_id: str, ):
|
||||
""", service_id)
|
||||
|
||||
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
||||
async def get_regex_by_id(regex_id: int, ):
|
||||
async def get_regex_by_id(regex_id: int):
|
||||
"""Get regex info using his id"""
|
||||
res = db.query("""
|
||||
SELECT
|
||||
@@ -217,7 +217,7 @@ async def get_regex_by_id(regex_id: int, ):
|
||||
return res[0]
|
||||
|
||||
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
||||
async def regex_delete(regex_id: int, ):
|
||||
async def regex_delete(regex_id: int):
|
||||
"""Delete a regex using his id"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -227,7 +227,7 @@ async def regex_delete(regex_id: int, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
||||
async def regex_enable(regex_id: int, ):
|
||||
async def regex_enable(regex_id: int):
|
||||
"""Request the enabling of a regex"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -237,7 +237,7 @@ async def regex_enable(regex_id: int, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
||||
async def regex_disable(regex_id: int, ):
|
||||
async def regex_disable(regex_id: int):
|
||||
"""Request the deactivation of a regex"""
|
||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||
if len(res) != 0:
|
||||
@@ -255,7 +255,7 @@ class RegexAddForm(BaseModel):
|
||||
is_case_sensitive: bool
|
||||
|
||||
@app.post('/regexes/add', response_model=StatusMessageModel)
|
||||
async def add_new_regex(form: RegexAddForm, ):
|
||||
async def add_new_regex(form: RegexAddForm):
|
||||
"""Add a new regex"""
|
||||
try:
|
||||
re.compile(b64decode(form.regex))
|
||||
@@ -283,7 +283,7 @@ class RenameForm(BaseModel):
|
||||
name:str
|
||||
|
||||
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||
async def service_rename(service_id: str, form: RenameForm, ):
|
||||
async def service_rename(service_id: str, form: RenameForm):
|
||||
"""Request to change the name of a specific service"""
|
||||
form.name = refactor_name(form.name)
|
||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||
@@ -295,7 +295,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
|
||||
return {'status': 'ok'}
|
||||
|
||||
@app.post('/services/add', response_model=ServiceAddStatus)
|
||||
async def add_new_service(form: ServiceAddForm, ):
|
||||
async def add_new_service(form: ServiceAddForm):
|
||||
"""Add a new service"""
|
||||
serv_id = gen_service_id(db)
|
||||
form.name = refactor_name(form.name)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
from ipaddress import ip_interface
|
||||
import os, socket, psutil
|
||||
import sys
|
||||
from ipaddress import ip_address, ip_interface
|
||||
import os, socket, psutil, sys, nftables
|
||||
from fastapi_socketio import SocketManager
|
||||
|
||||
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
|
||||
@@ -38,6 +37,9 @@ def list_files(mypath):
|
||||
def ip_parse(ip:str):
|
||||
return str(ip_interface(ip).network)
|
||||
|
||||
def addr_parse(ip:str):
|
||||
return str(ip_address(ip))
|
||||
|
||||
def ip_family(ip:str):
|
||||
return "ip6" if ip_interface(ip).version == 6 else "ip"
|
||||
|
||||
@@ -48,3 +50,59 @@ def get_interfaces():
|
||||
if interf.family in [socket.AF_INET, socket.AF_INET6]:
|
||||
yield {"name": int_name, "addr":interf.address}
|
||||
return list(_get_interfaces())
|
||||
|
||||
def nftables_int_to_json(ip_int):
|
||||
ip_int = ip_parse(ip_int)
|
||||
ip_addr = str(ip_int).split("/")[0]
|
||||
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
||||
return {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}
|
||||
|
||||
def nftables_json_to_int(ip_json_int):
|
||||
if isinstance(ip_json_int,str):
|
||||
return str(ip_parse(ip_json_int))
|
||||
else:
|
||||
return f'{ip_json_int["prefix"]["addr"]}/{ip_json_int["prefix"]["len"]}'
|
||||
|
||||
class Singleton(object):
|
||||
__instance = None
|
||||
def __new__(class_, *args, **kwargs):
|
||||
if not isinstance(class_.__instance, class_):
|
||||
class_.__instance = object.__new__(class_, *args, **kwargs)
|
||||
return class_.__instance
|
||||
|
||||
class NFTableManager(Singleton):
|
||||
|
||||
table_name = "firegex"
|
||||
|
||||
def __init__(self, init_cmd, reset_cmd):
|
||||
self.__init_cmds = init_cmd
|
||||
self.__reset_cmds = reset_cmd
|
||||
self.nft = nftables.Nftables()
|
||||
|
||||
def raw_cmd(self, *cmds):
|
||||
return self.nft.json_cmd({"nftables": list(cmds)})
|
||||
|
||||
def cmd(self, *cmds):
|
||||
code, out, err = self.raw_cmd(*cmds)
|
||||
|
||||
if code == 0: return out
|
||||
else: raise Exception(err)
|
||||
|
||||
def init(self):
|
||||
self.reset()
|
||||
self.raw_cmd({"add":{"table":{"name":self.table_name,"family":"inet"}}})
|
||||
self.cmd(*self.__init_cmds)
|
||||
|
||||
def reset(self):
|
||||
self.raw_cmd(*self.__reset_cmds)
|
||||
|
||||
def list_rules(self, tables = None, chains = None):
|
||||
for filter in [ele["rule"] for ele in self.raw_list() if "rule" in ele ]:
|
||||
if tables and filter["table"] not in tables: continue
|
||||
if chains and filter["chain"] not in chains: continue
|
||||
yield filter
|
||||
|
||||
def raw_list(self):
|
||||
return self.cmd({"list": {"ruleset": None}})["nftables"]
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
from typing import List
|
||||
import nftables
|
||||
from utils import ip_parse, ip_family
|
||||
|
||||
class FiregexFilter():
|
||||
def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None):
|
||||
self.nftables = nftables.Nftables()
|
||||
self.id = int(id) if id else None
|
||||
self.queue = queue
|
||||
self.target = target
|
||||
self.proto = proto
|
||||
self.port = int(port)
|
||||
self.ip_int = str(ip_int)
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if isinstance(o, FiregexFilter):
|
||||
return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
|
||||
return False
|
||||
|
||||
class FiregexTables:
|
||||
|
||||
def __init__(self):
|
||||
self.table_name = "firegex"
|
||||
self.nft = nftables.Nftables()
|
||||
|
||||
def raw_cmd(self, *cmds):
|
||||
return self.nft.json_cmd({"nftables": list(cmds)})
|
||||
|
||||
def cmd(self, *cmds):
|
||||
code, out, err = self.raw_cmd(*cmds)
|
||||
|
||||
if code == 0: return out
|
||||
else: raise Exception(err)
|
||||
|
||||
def init(self):
|
||||
self.reset()
|
||||
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
|
||||
if code == 0:
|
||||
self.cmd(
|
||||
{"create":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":"input",
|
||||
"type":"filter",
|
||||
"hook":"prerouting",
|
||||
"prio":-150,
|
||||
"policy":"accept"
|
||||
}}},
|
||||
{"create":{"chain":{
|
||||
"family":"inet",
|
||||
"table":self.table_name,
|
||||
"name":"output",
|
||||
"type":"filter",
|
||||
"hook":"postrouting",
|
||||
"prio":-150,
|
||||
"policy":"accept"
|
||||
}}}
|
||||
)
|
||||
|
||||
|
||||
def reset(self):
|
||||
self.raw_cmd(
|
||||
{"flush":{"table":{"name":"firegex","family":"inet"}}},
|
||||
{"delete":{"table":{"name":"firegex","family":"inet"}}},
|
||||
)
|
||||
|
||||
def list(self):
|
||||
return self.cmd({"list": {"ruleset": None}})["nftables"]
|
||||
|
||||
def add_output(self, queue_range, proto, port, ip_int):
|
||||
init, end = queue_range
|
||||
if init > end: init, end = end, init
|
||||
ip_int = ip_parse(ip_int)
|
||||
ip_addr = str(ip_int).split("/")[0]
|
||||
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
||||
self.cmd({ "insert":{ "rule": {
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": "output",
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
|
||||
{'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}},
|
||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||
]
|
||||
}}})
|
||||
|
||||
def add_input(self, queue_range, proto = None, port = None, ip_int = None):
|
||||
init, end = queue_range
|
||||
if init > end: init, end = end, init
|
||||
ip_int = ip_parse(ip_int)
|
||||
ip_addr = str(ip_int).split("/")[0]
|
||||
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
||||
self.cmd({"insert":{"rule":{
|
||||
"family": "inet",
|
||||
"table": self.table_name,
|
||||
"chain": "input",
|
||||
"expr": [
|
||||
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}},
|
||||
{'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}},
|
||||
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||
]
|
||||
}}})
|
||||
|
||||
def get(self) -> List[FiregexFilter]:
|
||||
res = []
|
||||
for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]:
|
||||
queue_str = filter["expr"][2]["queue"]["num"]
|
||||
queue = None
|
||||
if isinstance(queue_str,dict): queue = int(queue_str["range"][0]), int(queue_str["range"][1])
|
||||
else: queue = int(queue_str), int(queue_str)
|
||||
ip_int = None
|
||||
if isinstance(filter["expr"][0]["match"]["right"],str):
|
||||
ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
|
||||
else:
|
||||
ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
|
||||
res.append(FiregexFilter(
|
||||
target=filter["chain"],
|
||||
id=int(filter["handle"]),
|
||||
queue=queue,
|
||||
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
||||
port=filter["expr"][1]["match"]["right"],
|
||||
ip_int=ip_int
|
||||
))
|
||||
return res
|
||||
|
||||
@@ -90,7 +90,7 @@ def load_routers(app):
|
||||
resets, startups, shutdowns = [], [], []
|
||||
for router in get_router_modules():
|
||||
if router.router:
|
||||
app.include_router(router.router, prefix=f"/{router.name}")
|
||||
app.include_router(router.router, prefix=f"/{router.name}", tags=[router.name])
|
||||
if router.reset:
|
||||
resets.append(router.reset)
|
||||
if router.startup:
|
||||
|
||||
@@ -6,11 +6,12 @@ import { Outlet, Route, Routes } from 'react-router-dom';
|
||||
import MainLayout from './components/MainLayout';
|
||||
import { PasswordSend, ServerStatusResponse } from './js/models';
|
||||
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, login, setpassword } from './js/utils';
|
||||
import NFRegex from './pages/NFRegex.tsx';
|
||||
import NFRegex from './pages/NFRegex';
|
||||
import io from 'socket.io-client';
|
||||
import RegexProxy from './pages/RegexProxy';
|
||||
import ServiceDetailsNFRegex from './pages/NFRegex.tsx/ServiceDetails';
|
||||
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
||||
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
||||
import PortHijack from './pages/PortHijack';
|
||||
|
||||
const socket = io({transports: ["websocket", "polling"], path:"/sock" });
|
||||
|
||||
@@ -153,6 +154,7 @@ function App() {
|
||||
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
||||
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
||||
</Route>
|
||||
<Route path="porthijack" element={<PortHijack />} />
|
||||
<Route path="*" element={<HomeRedirector />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
$primary_color: #242a33;
|
||||
$second_color: #1A1B1E;
|
||||
$secondary_color: #1A1B1E;
|
||||
$third_color:#25262b;
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
|
||||
import style from "./index.module.scss";
|
||||
|
||||
|
||||
function FooterPage() {
|
||||
return <Footer id="footer" height={70} className={style.footer}>
|
||||
Made by Pwnzer0tt1
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Autocomplete, AutocompleteItem } from '@mantine/core';
|
||||
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { okNotify, regex_ipv4, regex_ipv6, getipinterfaces } from '../../js/utils';
|
||||
import React, { useState } from 'react';
|
||||
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { nfregex } from './utils';
|
||||
import PortAndInterface from '../PortAndInterface';
|
||||
|
||||
type ServiceAddForm = {
|
||||
name:string,
|
||||
@@ -13,16 +14,6 @@ type ServiceAddForm = {
|
||||
autostart: boolean,
|
||||
}
|
||||
|
||||
interface ItemProps extends AutocompleteItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
||||
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
|
||||
( <b>{label}</b> ) -{">"} <b>{value}</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
|
||||
|
||||
const form = useForm({
|
||||
@@ -41,14 +32,6 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
||||
}
|
||||
})
|
||||
|
||||
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
||||
|
||||
useEffect(()=>{
|
||||
getipinterfaces().then(data => {
|
||||
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
|
||||
})
|
||||
},[])
|
||||
|
||||
const close = () =>{
|
||||
onClose()
|
||||
form.reset()
|
||||
@@ -85,28 +68,9 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<Space h="md" />
|
||||
|
||||
<Autocomplete
|
||||
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
|
||||
placeholder="10.1.1.0/24"
|
||||
itemComponent={AutoCompleteItem}
|
||||
data={ipInterfaces}
|
||||
{...form.getInputProps('ip_int')}
|
||||
/>
|
||||
|
||||
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
|
||||
<Space h="md" />
|
||||
|
||||
<NumberInput
|
||||
placeholder="8080"
|
||||
min={1}
|
||||
max={65535}
|
||||
label="Public Service port"
|
||||
{...form.getInputProps('port')}
|
||||
/>
|
||||
|
||||
<Space h="md" />
|
||||
|
||||
|
||||
<div className='center-flex'>
|
||||
<Switch
|
||||
label="Auto-Start Service"
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function NavBar({ closeNav, opened }: {closeNav: () => void, open
|
||||
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
|
||||
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} />
|
||||
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="lime" icon={<MdTransform />} />
|
||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} disabled/>
|
||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} />
|
||||
</Navbar.Section>
|
||||
|
||||
</Navbar>
|
||||
|
||||
46
frontend/src/components/PortAndInterface.tsx
Normal file
46
frontend/src/components/PortAndInterface.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Autocomplete, AutocompleteItem, Space, Title } from "@mantine/core"
|
||||
import { UseForm } from "@mantine/hooks/lib/use-form/use-form";
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { getipinterfaces } from "../js/utils";
|
||||
import PortInput from "./PortInput";
|
||||
|
||||
|
||||
interface ItemProps extends AutocompleteItem {
|
||||
label: string;
|
||||
}
|
||||
|
||||
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
||||
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
|
||||
( <b>{label}</b> ) -{">"} <b>{value}</b>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
export default function PortAndInterface({ form, int_name, port_name, label }:{ form:UseForm<any>, int_name:string, port_name:string, label?:string }) {
|
||||
|
||||
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
||||
|
||||
useEffect(()=>{
|
||||
getipinterfaces().then(data => {
|
||||
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
|
||||
})
|
||||
},[])
|
||||
|
||||
return <>
|
||||
{label?<>
|
||||
<Title order={6}>{label}</Title>
|
||||
<Space h="xs" /></> :null}
|
||||
|
||||
<div className='center-flex' style={{width:"100%"}}>
|
||||
<Autocomplete
|
||||
placeholder="10.1.1.1"
|
||||
itemComponent={AutoCompleteItem}
|
||||
data={ipInterfaces}
|
||||
{...form.getInputProps(int_name)}
|
||||
style={{width:"100%"}}
|
||||
/>
|
||||
<Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" />
|
||||
<PortInput {...form.getInputProps(port_name)} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
113
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
113
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
@@ -0,0 +1,113 @@
|
||||
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useState } from 'react';
|
||||
import { okNotify, regex_ipv6_no_cidr, regex_ipv4_no_cidr } from '../../js/utils';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { porthijack } from './utils';
|
||||
import PortAndInterface from '../PortAndInterface';
|
||||
|
||||
type ServiceAddForm = {
|
||||
name:string,
|
||||
public_port:number,
|
||||
proxy_port:number,
|
||||
proto:string,
|
||||
ip_src:string,
|
||||
ip_dst:string,
|
||||
autostart: boolean,
|
||||
}
|
||||
|
||||
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
name:"",
|
||||
public_port:80,
|
||||
proxy_port:8080,
|
||||
proto:"tcp",
|
||||
ip_src:"",
|
||||
ip_dst:"127.0.0.1",
|
||||
autostart: false,
|
||||
},
|
||||
validationRules:{
|
||||
name: (value) => value !== ""?true:false,
|
||||
public_port: (value) => value>0 && value<65536,
|
||||
proxy_port: (value) => value>0 && value<65536,
|
||||
proto: (value) => ["tcp","udp"].includes(value),
|
||||
ip_src: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false,
|
||||
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false
|
||||
}
|
||||
})
|
||||
|
||||
const close = () =>{
|
||||
onClose()
|
||||
form.reset()
|
||||
setError(null)
|
||||
}
|
||||
|
||||
const [submitLoading, setSubmitLoading] = useState(false)
|
||||
const [error, setError] = useState<string|null>(null)
|
||||
|
||||
const submitRequest = ({ name, proxy_port, public_port, autostart, proto, ip_src, ip_dst }:ServiceAddForm) =>{
|
||||
setSubmitLoading(true)
|
||||
porthijack.servicesadd({name, proxy_port, public_port, proto, ip_src, ip_dst }).then( res => {
|
||||
if (res.status === "ok" && res.service_id){
|
||||
setSubmitLoading(false)
|
||||
close();
|
||||
if (autostart) porthijack.servicestart(res.service_id)
|
||||
okNotify(`Service ${name} has been added`, `Successfully added service from port ${public_port} to ${proxy_port}`)
|
||||
}else{
|
||||
setSubmitLoading(false)
|
||||
setError("Invalid request! [ "+res.status+" ]")
|
||||
}
|
||||
}).catch( err => {
|
||||
setSubmitLoading(false)
|
||||
setError("Request Failed! [ "+err+" ]")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||
<TextInput
|
||||
label="Service name"
|
||||
placeholder="Challenge 01"
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<Space h="md" />
|
||||
<PortAndInterface form={form} int_name="ip_src" port_name="public_port" label="Public IP Address and port (ipv4/ipv6)" />
|
||||
<Space h="md" />
|
||||
<PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" label="Proxy/Internal IP Address and port (ipv4/ipv6)" />
|
||||
<Space h="md" />
|
||||
|
||||
<div className='center-flex'>
|
||||
<Switch
|
||||
label="Auto-Start Service"
|
||||
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
||||
/>
|
||||
<div className="flex-spacer"></div>
|
||||
<SegmentedControl
|
||||
data={[
|
||||
{ label: 'TCP', value: 'tcp' },
|
||||
{ label: 'UDP', value: 'udp' },
|
||||
]}
|
||||
{...form.getInputProps('proto')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Group position="right" mt="md">
|
||||
<Button loading={submitLoading} type="submit">Add Service</Button>
|
||||
</Group>
|
||||
|
||||
<Space h="md" />
|
||||
|
||||
{error?<>
|
||||
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||
Error: {error}
|
||||
</Notification><Space h="md" /></>:null}
|
||||
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
}
|
||||
|
||||
export default AddNewService;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Button, Group, Space, Notification, Modal } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { okNotify, regex_ipv4_no_cidr, regex_ipv6_no_cidr } from '../../../js/utils';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { porthijack, Service } from '../utils';
|
||||
import PortAndInterface from '../../PortAndInterface';
|
||||
|
||||
function ChangeDestination({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
ip_dst:service.ip_dst,
|
||||
proxy_port:service.proxy_port
|
||||
},
|
||||
validationRules:{
|
||||
proxy_port: (value) => value>0 && value<65536,
|
||||
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false
|
||||
}
|
||||
})
|
||||
|
||||
const close = () =>{
|
||||
onClose()
|
||||
form.reset()
|
||||
setError(null)
|
||||
}
|
||||
|
||||
useEffect(() => form.setValues({ip_dst:service.ip_dst, proxy_port: service.proxy_port}),[opened])
|
||||
|
||||
const [submitLoading, setSubmitLoading] = useState(false)
|
||||
const [error, setError] = useState<string|null>(null)
|
||||
|
||||
const submitRequest = ({ ip_dst, proxy_port }:{ ip_dst:string, proxy_port:number }) => {
|
||||
setSubmitLoading(true)
|
||||
porthijack.changedestination(service.service_id, ip_dst, proxy_port).then( res => {
|
||||
if (res.status === "ok"){
|
||||
setSubmitLoading(false)
|
||||
close();
|
||||
okNotify(`Service ${service.name} has changed destination in ${ ip_dst }:${ proxy_port }`, `Successfully changed destination of service on port ${service.public_port}`)
|
||||
}else{
|
||||
setSubmitLoading(false)
|
||||
setError(res.status)
|
||||
}
|
||||
}).catch( err => {
|
||||
setSubmitLoading(false)
|
||||
setError("Request Failed! [ "+err+" ]")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
return <Modal size="xl" title={`Change destination of '${service.name}' [${service.ip_src}]:${service.public_port}`} opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||
|
||||
<PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" />
|
||||
<Group position="right" mt="md">
|
||||
<Button loading={submitLoading} type="submit">Change</Button>
|
||||
</Group>
|
||||
<Space h="md" />
|
||||
|
||||
{error?<>
|
||||
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||
Error: {error}
|
||||
</Notification><Space h="md" /></>:null}
|
||||
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
}
|
||||
|
||||
export default ChangeDestination;
|
||||
68
frontend/src/components/PortHijack/ServiceRow/RenameForm.tsx
Normal file
68
frontend/src/components/PortHijack/ServiceRow/RenameForm.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Button, Group, Space, TextInput, Notification, Modal } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { okNotify } from '../../../js/utils';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { porthijack, Service } from '../utils';
|
||||
|
||||
function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
|
||||
|
||||
const form = useForm({
|
||||
initialValues: { name:service.name },
|
||||
validationRules:{ name: (value) => value !== "" }
|
||||
})
|
||||
|
||||
const close = () =>{
|
||||
onClose()
|
||||
form.reset()
|
||||
setError(null)
|
||||
}
|
||||
|
||||
useEffect(()=> form.setFieldValue("name", service.name),[opened])
|
||||
|
||||
const [submitLoading, setSubmitLoading] = useState(false)
|
||||
const [error, setError] = useState<string|null>(null)
|
||||
|
||||
const submitRequest = ({ name }:{ name:string }) => {
|
||||
setSubmitLoading(true)
|
||||
porthijack.servicerename(service.service_id, name).then( res => {
|
||||
if (!res){
|
||||
setSubmitLoading(false)
|
||||
close();
|
||||
okNotify(`Service ${service.name} has been renamed in ${ name }`, `Successfully renamed service on port ${service.public_port}`)
|
||||
}else{
|
||||
setSubmitLoading(false)
|
||||
setError("Error: [ "+res+" ]")
|
||||
}
|
||||
}).catch( err => {
|
||||
setSubmitLoading(false)
|
||||
setError("Request Failed! [ "+err+" ]")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
return <Modal size="xl" title={`Rename '${service.name}' service on port ${service.public_port}`} opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||
<TextInput
|
||||
label="Service Name"
|
||||
placeholder="Awesome Service Name!"
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<Group position="right" mt="md">
|
||||
<Button loading={submitLoading} type="submit">Rename</Button>
|
||||
</Group>
|
||||
|
||||
<Space h="md" />
|
||||
|
||||
{error?<>
|
||||
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||
Error: {error}
|
||||
</Notification><Space h="md" /></>:null}
|
||||
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
}
|
||||
|
||||
export default RenameForm;
|
||||
26
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
26
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
@@ -0,0 +1,26 @@
|
||||
|
||||
@use "../../../index.scss" as *;
|
||||
|
||||
.row{
|
||||
width: 95%;
|
||||
padding: 15px 0px;
|
||||
border-radius: 20px;
|
||||
margin: 10px;
|
||||
@extend .center-flex;
|
||||
}
|
||||
|
||||
.name{
|
||||
font-size: 2.3em;
|
||||
font-weight: bolder;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 13px;
|
||||
color:#FFF;
|
||||
}
|
||||
|
||||
.portInput *{
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
margin-top: -1px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
187
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
187
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
@@ -0,0 +1,187 @@
|
||||
import { ActionIcon, Badge, Divider, Menu, Space, Title, Tooltip } from '@mantine/core';
|
||||
import React, { useState } from 'react';
|
||||
import { FaPlay, FaStop } from 'react-icons/fa';
|
||||
import { porthijack, Service } from '../utils';
|
||||
import style from "./index.module.scss";
|
||||
import YesNoModal from '../../YesNoModal';
|
||||
import { errorNotify, okNotify } from '../../../js/utils';
|
||||
import { BsArrowRepeat, BsTrashFill } from 'react-icons/bs';
|
||||
import { BiRename } from 'react-icons/bi'
|
||||
import RenameForm from './RenameForm';
|
||||
import ChangeDestination from './ChangeDestination';
|
||||
import PortInput from '../../PortInput';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
|
||||
function ServiceRow({ service }:{ service:Service }) {
|
||||
|
||||
let status_color = service.active ? "teal": "red"
|
||||
|
||||
const [buttonLoading, setButtonLoading] = useState(false)
|
||||
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false)
|
||||
const [renameModal, setRenameModal] = useState(false)
|
||||
const [changeDestModal, setChangeDestModal] = useState(false)
|
||||
const portInputRef = React.createRef<HTMLInputElement>()
|
||||
|
||||
const form = useForm({
|
||||
initialValues: { proxy_port:service.proxy_port },
|
||||
validationRules:{ proxy_port: (value) => value > 0 && value < 65536 }
|
||||
})
|
||||
|
||||
const onChangeProxyPort = ({proxy_port}:{proxy_port:number}) => {
|
||||
if (proxy_port === service.proxy_port) return
|
||||
if (proxy_port > 0 && proxy_port < 65536 && proxy_port !== service.public_port){
|
||||
porthijack.changedestination(service.service_id, service.ip_dst, proxy_port).then( res => {
|
||||
if (res.status === "ok"){
|
||||
okNotify(`Service ${service.name} destination port has changed in ${ proxy_port }`, `Successfully changed destination port`)
|
||||
}else{
|
||||
errorNotify(`Error while changing the destination port of ${service.name}`,`Error: ${res.status}`)
|
||||
}
|
||||
}).catch( err => {
|
||||
errorNotify("Request for changing port failed!",`Error: [ ${err} ]`)
|
||||
})
|
||||
}else{
|
||||
form.setFieldValue("proxy_port", service.proxy_port)
|
||||
errorNotify(`Error while changing the destination port of ${service.name}`,`Insert a valid port number`)
|
||||
}
|
||||
}
|
||||
|
||||
const stopService = async () => {
|
||||
setButtonLoading(true)
|
||||
|
||||
await porthijack.servicestop(service.service_id).then(res => {
|
||||
if(!res){
|
||||
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.public_port} has been stopped!`)
|
||||
}else{
|
||||
errorNotify(`An error as occurred during the stopping of the service ${service.public_port}`,`Error: ${res}`)
|
||||
}
|
||||
}).catch(err => {
|
||||
errorNotify(`An error as occurred during the stopping of the service ${service.public_port}`,`Error: ${err}`)
|
||||
})
|
||||
setButtonLoading(false);
|
||||
}
|
||||
|
||||
const startService = async () => {
|
||||
setButtonLoading(true)
|
||||
await porthijack.servicestart(service.service_id).then(res => {
|
||||
if(!res){
|
||||
okNotify(`Service ${service.name} started successfully!`,`The service on ${service.public_port} has been started!`)
|
||||
}else{
|
||||
errorNotify(`An error as occurred during the starting of the service ${service.public_port}`,`Error: ${res}`)
|
||||
}
|
||||
}).catch(err => {
|
||||
errorNotify(`An error as occurred during the starting of the service ${service.public_port}`,`Error: ${err}`)
|
||||
})
|
||||
setButtonLoading(false)
|
||||
}
|
||||
|
||||
const deleteService = () => {
|
||||
porthijack.servicedelete(service.service_id).then(res => {
|
||||
if (!res){
|
||||
okNotify("Service delete complete!",`The service ${service.name} has been deleted!`)
|
||||
}else
|
||||
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
|
||||
}).catch(err => {
|
||||
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className={style.row} style={{width:"100%"}}>
|
||||
<Space w="xl" /><Space w="xl" />
|
||||
<div>
|
||||
<div className="center-flex-row">
|
||||
<div className="center-flex"><Title order={4} className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></div>
|
||||
<div className="center-flex">
|
||||
<Badge color={status_color} radius="sm" size="md" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
|
||||
<Space w="sm" />
|
||||
<Badge color={service.proto === "tcp"?"cyan":"orange"} radius="sm" size="md" variant="filled">
|
||||
{service.proto}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-spacer' />
|
||||
<div className="center-flex-row">
|
||||
<Badge color="lime" radius="sm" size="md" variant="filled">
|
||||
FROM {service.ip_src} : {service.public_port}
|
||||
</Badge>
|
||||
<Space h="sm" />
|
||||
<Badge color="blue" radius="sm" size="md" variant="filled">
|
||||
<div className="center-flex">
|
||||
TO {service.ip_dst} :
|
||||
<form onSubmit={form.onSubmit((v)=>portInputRef.current?.blur())}>
|
||||
<PortInput
|
||||
defaultValue={service.proxy_port}
|
||||
size="xs"
|
||||
variant="unstyled"
|
||||
style={{
|
||||
width: (6+form.values.proxy_port.toString().length*6.2) +"px"
|
||||
}}
|
||||
className={style.portInput}
|
||||
onBlur={(e)=>{onChangeProxyPort({proxy_port:parseInt(e.target.value)})}}
|
||||
ref={portInputRef}
|
||||
{...form.getInputProps("proxy_port")}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<Space w="xl" /><Space w="xl" />
|
||||
<div className="center-flex">
|
||||
<Menu>
|
||||
<Menu.Label><b>Rename service</b></Menu.Label>
|
||||
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
|
||||
<Menu.Label><b>Change destination</b></Menu.Label>
|
||||
<Menu.Item icon={<BsArrowRepeat size={18} />} onClick={()=>setChangeDestModal(true)}>Change hijacking destination</Menu.Item>
|
||||
<Divider />
|
||||
<Menu.Label><b>Danger zone</b></Menu.Label>
|
||||
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
|
||||
</Menu>
|
||||
<Space w="md"/>
|
||||
<Tooltip label="Stop service" zIndex={0} transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="red" opened={tooltipStopOpened} tooltipId="tooltip-stop-id">
|
||||
<ActionIcon color="red" loading={buttonLoading}
|
||||
onClick={stopService} size="xl" radius="md" variant="filled"
|
||||
disabled={!service.active}
|
||||
aria-describedby="tooltip-stop-id"
|
||||
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
|
||||
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
|
||||
<FaStop size="20px" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Space w="md"/>
|
||||
<Tooltip label="Start service" transition="pop" zIndex={0} transitionDuration={200} transitionTimingFunction="ease" color="teal">
|
||||
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
|
||||
variant="filled" disabled={service.active}>
|
||||
<FaPlay size="20px" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Space w="xl" /><Space w="xl" />
|
||||
|
||||
</div>
|
||||
<hr style={{width:"100%"}}/>
|
||||
<YesNoModal
|
||||
title='Are you sure to delete this service?'
|
||||
description={`You are going to delete the service '${service.public_port}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service! ⚠️`}
|
||||
onClose={()=>setDeleteModal(false) }
|
||||
action={deleteService}
|
||||
opened={deleteModal}
|
||||
/>
|
||||
<RenameForm
|
||||
onClose={()=>setRenameModal(false)}
|
||||
opened={renameModal}
|
||||
service={service}
|
||||
/>
|
||||
<ChangeDestination
|
||||
onClose={()=>setChangeDestModal(false)}
|
||||
opened={changeDestModal}
|
||||
service={service}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
export default ServiceRow;
|
||||
65
frontend/src/components/PortHijack/utils.ts
Executable file
65
frontend/src/components/PortHijack/utils.ts
Executable file
@@ -0,0 +1,65 @@
|
||||
import { ServerResponse } from "../../js/models"
|
||||
import { getapi, postapi } from "../../js/utils"
|
||||
|
||||
export type GeneralStats = {
|
||||
services:number
|
||||
}
|
||||
|
||||
export type Service = {
|
||||
name:string,
|
||||
service_id:string,
|
||||
active:boolean,
|
||||
proto: string,
|
||||
ip_src: string,
|
||||
ip_dst: string,
|
||||
proxy_port: number,
|
||||
public_port: number,
|
||||
}
|
||||
|
||||
export type ServiceAddForm = {
|
||||
name:string,
|
||||
public_port:number,
|
||||
proxy_port:number,
|
||||
proto:string,
|
||||
ip_src: string,
|
||||
ip_dst: string,
|
||||
}
|
||||
|
||||
export type ServiceAddResponse = {
|
||||
status: string,
|
||||
service_id?: string,
|
||||
}
|
||||
|
||||
export const porthijack = {
|
||||
stats: async () => {
|
||||
return await getapi("porthijack/stats") as GeneralStats;
|
||||
},
|
||||
services: async () => {
|
||||
return await getapi("porthijack/services") as Service[];
|
||||
},
|
||||
serviceinfo: async (service_id:string) => {
|
||||
return await getapi(`porthijack/service/${service_id}`) as Service;
|
||||
},
|
||||
servicestart: async (service_id:string) => {
|
||||
const { status } = await getapi(`porthijack/service/${service_id}/start`) as ServerResponse;
|
||||
return status === "ok"?undefined:status
|
||||
},
|
||||
servicerename: async (service_id:string, name: string) => {
|
||||
const { status } = await postapi(`porthijack/service/${service_id}/rename`,{ name }) as ServerResponse;
|
||||
return status === "ok"?undefined:status
|
||||
},
|
||||
servicestop: async (service_id:string) => {
|
||||
const { status } = await getapi(`porthijack/service/${service_id}/stop`) as ServerResponse;
|
||||
return status === "ok"?undefined:status
|
||||
},
|
||||
servicesadd: async (data:ServiceAddForm) => {
|
||||
return await postapi("porthijack/services/add",data) as ServiceAddResponse;
|
||||
},
|
||||
servicedelete: async (service_id:string) => {
|
||||
const { status } = await getapi(`porthijack/service/${service_id}/delete`) as ServerResponse;
|
||||
return status === "ok"?undefined:status
|
||||
},
|
||||
changedestination: async (service_id:string, ip_dst:string, proxy_port:number) => {
|
||||
return await postapi(`porthijack/service/${service_id}/change-destination`, {proxy_port, ip_dst}) as ServerResponse;
|
||||
}
|
||||
}
|
||||
36
frontend/src/components/PortInput.tsx
Normal file
36
frontend/src/components/PortInput.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NumberInput, NumberInputProps } from "@mantine/core"
|
||||
import React, { useState } from "react"
|
||||
|
||||
interface PortInputProps extends NumberInputProps {
|
||||
fullWidth?: boolean
|
||||
}
|
||||
|
||||
const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, ref) => {
|
||||
|
||||
const [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
|
||||
return <NumberInput
|
||||
variant={props.variant?props.variant:"filled"}
|
||||
hideControls
|
||||
placeholder="80"
|
||||
min={props.min?props.min:1}
|
||||
max={props.max?props.min:65535}
|
||||
style={props.fullWidth?props.style:{ width: "75px", ...props.style }}
|
||||
onInput={(e) => {
|
||||
const value = parseInt((e.target as HTMLInputElement).value)
|
||||
if (value > 65535) {
|
||||
(e.target as HTMLInputElement).value = oldValue
|
||||
} else if (value < 1) {
|
||||
(e.target as HTMLInputElement).value = oldValue
|
||||
}else{
|
||||
(e.target as HTMLInputElement).value = value.toString()
|
||||
}
|
||||
setOldValue((e.target as HTMLInputElement).value)
|
||||
props.onInput?.(e)
|
||||
}}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
})
|
||||
|
||||
export default PortInput
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
|
||||
import { Button, Group, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useState } from 'react';
|
||||
import { okNotify } from '../../js/utils';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { regexproxy } from './utils';
|
||||
import PortInput from '../PortInput';
|
||||
|
||||
type ServiceAddForm = {
|
||||
name:string,
|
||||
@@ -67,20 +68,16 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
||||
/>
|
||||
<Space h="md" />
|
||||
|
||||
<NumberInput
|
||||
placeholder="8080"
|
||||
min={1}
|
||||
max={65535}
|
||||
<PortInput
|
||||
fullWidth
|
||||
label="Public Service port"
|
||||
{...form.getInputProps('port')}
|
||||
/>
|
||||
|
||||
{form.values.chosenInternalPort?<>
|
||||
<Space h="md" />
|
||||
<NumberInput
|
||||
placeholder="8080"
|
||||
min={1}
|
||||
max={65535}
|
||||
<PortInput
|
||||
fullWidth
|
||||
label="Internal Proxy Port"
|
||||
{...form.getInputProps('internalPort')}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Button, Group, NumberInput, Space, Notification, Modal, Center, Title } from '@mantine/core';
|
||||
import { Button, Group, Space, Notification, Modal, Center, Title } from '@mantine/core';
|
||||
import { useForm } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ImCross } from "react-icons/im"
|
||||
import { FaLongArrowAltDown } from 'react-icons/fa';
|
||||
import { regexproxy, Service } from '../utils';
|
||||
import { okNotify } from '../../../js/utils';
|
||||
import PortInput from '../../PortInput';
|
||||
|
||||
type InputForm = {
|
||||
internalPort:number,
|
||||
@@ -58,12 +59,8 @@ function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:
|
||||
return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||
|
||||
|
||||
|
||||
<NumberInput
|
||||
placeholder="30001"
|
||||
min={1}
|
||||
max={65535}
|
||||
<PortInput
|
||||
fullWidth
|
||||
label="Internal Proxy Port"
|
||||
{...form.getInputProps('internalPort')}
|
||||
/>
|
||||
@@ -71,10 +68,8 @@ function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:
|
||||
<Space h="xl" />
|
||||
<Center><FaLongArrowAltDown size={50}/></Center>
|
||||
|
||||
<NumberInput
|
||||
placeholder="8080"
|
||||
min={1}
|
||||
max={65535}
|
||||
<PortInput
|
||||
fullWidth
|
||||
label="Public Service Port"
|
||||
{...form.getInputProps('port')}
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,9 @@ var Buffer = require('buffer').Buffer
|
||||
export const eventUpdateName = "update-info"
|
||||
|
||||
export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
|
||||
export const regex_ipv4 = "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
||||
export const regex_ipv6_no_cidr = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*$";
|
||||
export const regex_ipv4 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(3[0-2]|[1-2][0-9]|[0-9]))?$"
|
||||
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
||||
|
||||
export async function getapi(path:string):Promise<any>{
|
||||
|
||||
|
||||
73
frontend/src/pages/PortHijack/index.tsx
Executable file
73
frontend/src/pages/PortHijack/index.tsx
Executable file
@@ -0,0 +1,73 @@
|
||||
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BsPlusLg } from "react-icons/bs";
|
||||
import ServiceRow from '../../components/PortHijack/ServiceRow';
|
||||
import { GeneralStats, porthijack, Service } from '../../components/PortHijack/utils';
|
||||
import { errorNotify, eventUpdateName, fireUpdateRequest } from '../../js/utils';
|
||||
import AddNewService from '../../components/PortHijack/AddNewService';
|
||||
import { useWindowEvent } from '@mantine/hooks';
|
||||
|
||||
|
||||
function PortHijack() {
|
||||
|
||||
const [services, setServices] = useState<Service[]>([]);
|
||||
const [loader, setLoader] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
|
||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||
|
||||
const [generalStats, setGeneralStats] = useState<GeneralStats>({services:0});
|
||||
const updateInfo = async () => {
|
||||
|
||||
await Promise.all([
|
||||
porthijack.stats().then(res => {
|
||||
setGeneralStats(res)
|
||||
}).catch(
|
||||
err => errorNotify("General Info Auto-Update failed!", err.toString())
|
||||
),
|
||||
porthijack.services().then(res => {
|
||||
setServices(res)
|
||||
}).catch(err => {
|
||||
errorNotify("Home Page Auto-Update failed!", err.toString())
|
||||
})
|
||||
])
|
||||
setLoader(false)
|
||||
}
|
||||
|
||||
useWindowEvent(eventUpdateName, updateInfo)
|
||||
useEffect(fireUpdateRequest,[])
|
||||
|
||||
const closeModal = () => {setOpen(false);}
|
||||
|
||||
return <>
|
||||
<Space h="sm" />
|
||||
<div className='center-flex'>
|
||||
<Title order={4}>Hijack port to proxy</Title>
|
||||
<div className='flex-spacer' />
|
||||
<Badge size="sm" color="yellow" variant="filled">Services: {generalStats.services}</Badge>
|
||||
<Space w="xs" />
|
||||
<Tooltip label="Add a new service" position='bottom' transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="blue" opened={tooltipAddOpened}>
|
||||
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
|
||||
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
||||
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div id="service-list" className="center-flex-row">
|
||||
<LoadingOverlay visible={loader} />
|
||||
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.service_id} />):<><Space h="xl"/> <Title className='center-flex' align='center' order={3}>No services found! Add one clicking the "+" buttons</Title>
|
||||
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
|
||||
<div className='center-flex'>
|
||||
<Tooltip label="Add a new service" transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="blue" opened={tooltipAddServOpened}>
|
||||
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
|
||||
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
|
||||
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</>}
|
||||
<AddNewService opened={open} onClose={closeModal} />
|
||||
</div>
|
||||
<AddNewService opened={open} onClose={closeModal} />
|
||||
</>
|
||||
}
|
||||
|
||||
export default PortHijack;
|
||||
15
start.py
15
start.py
@@ -27,12 +27,16 @@ parser.add_argument('--no-autostart', "-n", required=False, action="store_true",
|
||||
parser.add_argument('--keep','-k', required=False, action="store_true", help='Keep the docker-compose file generated', default=False)
|
||||
parser.add_argument('--build', "-b", required=False, action="store_true", help='Build the container locally', default=False)
|
||||
parser.add_argument('--stop', '-s', required=False, action="store_true", help='Stop firegex execution', default=False)
|
||||
parser.add_argument('--restart', '-r', required=False, action="store_true", help='Restart firegex', default=False)
|
||||
parser.add_argument('--psw-no-interactive',type=str, required=False, help='Password for no-interactive mode', default=None)
|
||||
parser.add_argument('--startup-psw', required=False, action="store_true", help='Insert password in the startup screen of firegex', default=False)
|
||||
parser.add_argument('--startup-psw','-P', required=False, action="store_true", help='Insert password in the startup screen of firegex', default=False)
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
start_operation = not (args.stop or args.restart)
|
||||
|
||||
if args.build and not os.path.isfile("./Dockerfile"):
|
||||
puts("This is not a clone of firegex, to build firegex the clone of the repository is needed!", color=colors.red)
|
||||
exit()
|
||||
@@ -40,14 +44,14 @@ if args.build and not os.path.isfile("./Dockerfile"):
|
||||
if args.threads < 1:
|
||||
args.threads = multiprocessing.cpu_count()
|
||||
|
||||
if not args.stop:
|
||||
if start_operation:
|
||||
sep()
|
||||
puts(f"Firegex", color=colors.yellow, end="")
|
||||
puts(" will start on port ", end="")
|
||||
puts(f"{args.port}", color=colors.cyan)
|
||||
|
||||
psw_set = None
|
||||
if not args.stop:
|
||||
if start_operation:
|
||||
if args.psw_no_interactive:
|
||||
psw_set = args.psw_no_interactive
|
||||
elif not args.startup_psw:
|
||||
@@ -100,7 +104,10 @@ services:
|
||||
sep()
|
||||
if not args.no_autostart:
|
||||
try:
|
||||
if args.stop:
|
||||
if args.restart:
|
||||
puts("Running 'docker-compose restart'\n", color=colors.green)
|
||||
os.system("docker-compose -p firegex restart")
|
||||
elif args.stop:
|
||||
puts("Running 'docker-compose down'\n", color=colors.green)
|
||||
os.system("docker-compose -p firegex down")
|
||||
else:
|
||||
|
||||
@@ -25,13 +25,13 @@ if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colo
|
||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
||||
|
||||
#Create server
|
||||
server = TcpServer(args.port,ipv6=args.ipv6) if args.proto == "tcp" else UdpServer(args.port,ipv6=args.ipv6)
|
||||
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.ipv6)
|
||||
|
||||
def exit_test(code):
|
||||
if service_id:
|
||||
server.stop()
|
||||
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't deleted serivce ✗", color=colors.red); exit_test(1)
|
||||
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
||||
exit(code)
|
||||
|
||||
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" )
|
||||
|
||||
99
tests/ph_test.py
Executable file
99
tests/ph_test.py
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
from utils.colors import *
|
||||
from utils.firegexapi import *
|
||||
from utils.tcpserver import TcpServer
|
||||
from utils.udpserver import UdpServer
|
||||
import argparse, secrets, base64,time
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
|
||||
parser.add_argument("--password", "-p", type=str, required=True, help='Firegex password')
|
||||
parser.add_argument("--service_name", "-n", type=str , required=False, help='Name of the test service', default="Test Service")
|
||||
parser.add_argument("--port", "-P", type=int , required=False, help='Port of the test service', default=1337)
|
||||
parser.add_argument("--ipv6", "-6" , action="store_true", help='Test Ipv6', default=False)
|
||||
parser.add_argument("--proto", "-m" , type=str, required=False, choices=["tcp","udp"], help='Select the protocol', default="tcp")
|
||||
|
||||
args = parser.parse_args()
|
||||
sep()
|
||||
puts(f"Testing will start on ", color=colors.cyan, end="")
|
||||
puts(f"{args.address}", color=colors.yellow)
|
||||
|
||||
firegex = FiregexAPI(args.address)
|
||||
|
||||
#Login
|
||||
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
||||
|
||||
#Create server
|
||||
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+1,ipv6=args.ipv6,proxy_port=args.port)
|
||||
|
||||
def exit_test(code):
|
||||
if service_id:
|
||||
server.stop()
|
||||
if(firegex.ph_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
||||
exit(code)
|
||||
|
||||
#Create and start serivce
|
||||
service_id = firegex.ph_add_service(args.service_name, args.port, args.port+1, args.proto , "::1" if args.ipv6 else "127.0.0.1", "::1" if args.ipv6 else "127.0.0.1")
|
||||
if service_id: puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
|
||||
|
||||
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Failed to start service ✗", color=colors.red); exit_test(1)
|
||||
|
||||
server.start()
|
||||
time.sleep(0.5)
|
||||
|
||||
#Check if it started
|
||||
def checkData(should_work):
|
||||
res = None
|
||||
try: res = server.sendCheckData(secrets.token_bytes(432))
|
||||
except ConnectionRefusedError: res = False
|
||||
if res:
|
||||
if should_work: puts(f"Successfully received data ✔", color=colors.green)
|
||||
else: puts("Test Failed: Connection wasn't blocked ✗", color=colors.red); exit_test(1)
|
||||
else:
|
||||
if should_work: puts(f"Test Failed: Data wans't received ✗", color=colors.red); exit_test(1)
|
||||
else: puts(f"Successfully blcoked connection ✔", color=colors.green)
|
||||
|
||||
checkData(True)
|
||||
|
||||
#Pause the proxy
|
||||
if(firegex.ph_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id} ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
|
||||
|
||||
checkData(False)
|
||||
|
||||
#Start firewall
|
||||
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service with id {service_id} ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
|
||||
|
||||
checkData(True)
|
||||
|
||||
#Change port
|
||||
if(firegex.ph_change_destination(service_id, "::1" if args.ipv6 else "127.0.0.1", args.port+2)):
|
||||
puts(f"Sucessfully changed port ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't change destination ✗", color=colors.red); exit_test(1)
|
||||
|
||||
checkData(False)
|
||||
|
||||
server.stop()
|
||||
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+2,ipv6=args.ipv6,proxy_port=args.port)
|
||||
server.start()
|
||||
time.sleep(0.5)
|
||||
|
||||
checkData(True)
|
||||
|
||||
#Rename service
|
||||
if(firegex.ph_rename_service(service_id,f"{args.service_name}2")): puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
|
||||
else: puts(f"Test Failed: Coulnd't rename service ✗", color=colors.red); exit_test(1)
|
||||
|
||||
#Check if service was renamed correctly
|
||||
for services in firegex.ph_get_services():
|
||||
if services["name"] == f"{args.service_name}2":
|
||||
puts(f"Checked that service was renamed correctly ✔", color=colors.green)
|
||||
exit_test(0)
|
||||
|
||||
puts(f"Test Failed: Service wasn't renamed correctly ✗", color=colors.red); exit_test(1)
|
||||
exit_test(1)
|
||||
@@ -82,6 +82,7 @@ def checkRegex(regex, should_work=True, upper=False):
|
||||
if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
|
||||
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
|
||||
n_blocked += 1
|
||||
time.sleep(0.5)
|
||||
if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked:
|
||||
puts(f"The packed was reported as blocked ✔", color=colors.green)
|
||||
else:
|
||||
|
||||
@@ -12,3 +12,11 @@ echo "Running Netfilter Regex UDP ipv6"
|
||||
python3 nf_test.py -p testpassword -m udp -6
|
||||
echo "Running Proxy Regex"
|
||||
python3 px_test.py -p testpassword
|
||||
echo "Running Port Hijack TCP ipv4"
|
||||
python3 ph_test.py -p testpassword -m tcp
|
||||
echo "Running Port Hijack TCP ipv6"
|
||||
python3 ph_test.py -p testpassword -m tcp -6
|
||||
echo "Running Port Hijack UDP ipv4"
|
||||
python3 ph_test.py -p testpassword -m udp
|
||||
echo "Running Port Hijack UDP ipv6"
|
||||
python3 ph_test.py -p testpassword -m udp -6
|
||||
@@ -1,3 +1,4 @@
|
||||
import string
|
||||
from requests import Session
|
||||
|
||||
def verify(req):
|
||||
@@ -36,7 +37,7 @@ class FiregexAPI:
|
||||
def status(self):
|
||||
return self.s.get(f"{self.address}api/status").json()
|
||||
|
||||
def login(self,password):
|
||||
def login(self,password: str):
|
||||
req = self.s.post(f"{self.address}api/login", data=f"username=login&password={password}")
|
||||
try :
|
||||
self.s.set_token(req.json()["access_token"])
|
||||
@@ -48,7 +49,7 @@ class FiregexAPI:
|
||||
self.s.unset_token()
|
||||
return True
|
||||
|
||||
def set_password(self,password):
|
||||
def set_password(self,password: str):
|
||||
req = self.s.post(f"{self.address}api/set-password", json={"password":password})
|
||||
if verify(req):
|
||||
self.s.set_token(req.json()["access_token"])
|
||||
@@ -56,7 +57,7 @@ class FiregexAPI:
|
||||
else:
|
||||
return False
|
||||
|
||||
def change_password(self,password,expire):
|
||||
def change_password(self, password: str, expire: bool):
|
||||
req = self.s.post(f"{self.address}api/change-password", json={"password":password, "expire":expire})
|
||||
if verify(req):
|
||||
self.s.set_token(req.json()["access_token"])
|
||||
@@ -68,11 +69,11 @@ class FiregexAPI:
|
||||
req = self.s.get(f"{self.address}api/interfaces")
|
||||
return req.json()
|
||||
|
||||
def reset(self, delete):
|
||||
def reset(self, delete: bool):
|
||||
req = self.s.post(f"{self.address}api/reset", json={"delete":delete})
|
||||
|
||||
#Netfilter regex
|
||||
def nf_get_stats():
|
||||
def nf_get_stats(self):
|
||||
req = self.s.get(f"{self.address}api/nfregex/stats")
|
||||
return req.json()
|
||||
|
||||
@@ -80,43 +81,43 @@ class FiregexAPI:
|
||||
req = self.s.get(f"{self.address}api/nfregex/services")
|
||||
return req.json()
|
||||
|
||||
def nf_get_service(self,service_id):
|
||||
def nf_get_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}")
|
||||
return req.json()
|
||||
|
||||
def nf_stop_service(self,service_id):
|
||||
def nf_stop_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/stop")
|
||||
return verify(req)
|
||||
|
||||
def nf_start_service(self,service_id):
|
||||
def nf_start_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/start")
|
||||
return verify(req)
|
||||
|
||||
def nf_delete_service(self,service_id):
|
||||
def nf_delete_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/delete")
|
||||
return verify(req)
|
||||
|
||||
def nf_rename_service(self,service_id,newname):
|
||||
def nf_rename_service(self,service_id: str, newname: str):
|
||||
req = self.s.post(f"{self.address}api/nfregex/service/{service_id}/rename" , json={"name":newname})
|
||||
return verify(req)
|
||||
|
||||
def nf_get_service_regexes(self,service_id):
|
||||
def nf_get_service_regexes(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/regexes")
|
||||
return req.json()
|
||||
|
||||
def nf_get_regex(self,regex_id):
|
||||
def nf_get_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}")
|
||||
return req.json()
|
||||
|
||||
def nf_delete_regex(self,regex_id):
|
||||
def nf_delete_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/delete")
|
||||
return verify(req)
|
||||
|
||||
def nf_enable_regex(self,regex_id):
|
||||
def nf_enable_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/enable")
|
||||
return verify(req)
|
||||
|
||||
def nf_disable_regex(self,regex_id):
|
||||
def nf_disable_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
|
||||
return verify(req)
|
||||
|
||||
@@ -131,7 +132,7 @@ class FiregexAPI:
|
||||
return req.json()["service_id"] if verify(req) else False
|
||||
|
||||
#Proxy regex
|
||||
def px_get_stats():
|
||||
def px_get_stats(self):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/stats")
|
||||
return req.json()
|
||||
|
||||
@@ -139,54 +140,54 @@ class FiregexAPI:
|
||||
req = self.s.get(f"{self.address}api/regexproxy/services")
|
||||
return req.json()
|
||||
|
||||
def px_get_service(self,service_id):
|
||||
def px_get_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}")
|
||||
return req.json()
|
||||
|
||||
def px_stop_service(self,service_id):
|
||||
def px_stop_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/stop")
|
||||
return verify(req)
|
||||
|
||||
def px_pause_service(self,service_id):
|
||||
def px_pause_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/pause")
|
||||
return verify(req)
|
||||
|
||||
def px_start_service(self,service_id):
|
||||
def px_start_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/start")
|
||||
return verify(req)
|
||||
|
||||
def px_delete_service(self,service_id):
|
||||
def px_delete_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/delete")
|
||||
return verify(req)
|
||||
|
||||
def px_regen_service_port(self,service_id):
|
||||
def px_regen_service_port(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regen-port")
|
||||
return verify(req)
|
||||
|
||||
def px_change_service_port(self,service_id, port=None, internalPort=None):
|
||||
def px_change_service_port(self,service_id: str, port:int =None, internalPort:int =None):
|
||||
payload = {}
|
||||
if port: payload["port"] = port
|
||||
if internalPort: payload["internalPort"] = internalPort
|
||||
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload)
|
||||
return req.json() if verify(req) else False
|
||||
|
||||
def px_get_service_regexes(self,service_id):
|
||||
def px_get_service_regexes(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regexes")
|
||||
return req.json()
|
||||
|
||||
def px_get_regex(self,regex_id):
|
||||
def px_get_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}")
|
||||
return req.json()
|
||||
|
||||
def px_delete_regex(self,regex_id):
|
||||
def px_delete_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/delete")
|
||||
return verify(req)
|
||||
|
||||
def px_enable_regex(self,regex_id):
|
||||
def px_enable_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/enable")
|
||||
return verify(req)
|
||||
|
||||
def px_disable_regex(self,regex_id):
|
||||
def px_disable_regex(self,regex_id: str):
|
||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/disable")
|
||||
return verify(req)
|
||||
|
||||
@@ -195,11 +196,11 @@ class FiregexAPI:
|
||||
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
|
||||
return verify(req)
|
||||
|
||||
def px_rename_service(self,service_id,newname):
|
||||
def px_rename_service(self,service_id: str, newname: str):
|
||||
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/rename" , json={"name":newname})
|
||||
return verify(req)
|
||||
|
||||
def px_add_service(self, name: str, port: int, internalPort = None):
|
||||
def px_add_service(self, name: str, port: int, internalPort:int = None):
|
||||
payload = {}
|
||||
payload["name"] = name
|
||||
payload["port"] = port
|
||||
@@ -207,3 +208,37 @@ class FiregexAPI:
|
||||
payload["internalPort"] = internalPort
|
||||
req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload)
|
||||
return req.json()["id"] if verify(req) else False
|
||||
|
||||
#PortHijack
|
||||
def ph_get_services(self):
|
||||
req = self.s.get(f"{self.address}api/porthijack/services")
|
||||
return req.json()
|
||||
|
||||
def ph_get_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}")
|
||||
return req.json()
|
||||
|
||||
def ph_stop_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/stop")
|
||||
return verify(req)
|
||||
|
||||
def ph_start_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/start")
|
||||
return verify(req)
|
||||
|
||||
def ph_delete_service(self,service_id: str):
|
||||
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/delete")
|
||||
return verify(req)
|
||||
|
||||
def ph_rename_service(self,service_id: str,newname: str):
|
||||
req = self.s.post(f"{self.address}api/porthijack/service/{service_id}/rename" , json={"name":newname})
|
||||
return verify(req)
|
||||
|
||||
def ph_change_destination(self,service_id: str, ip_dst:string , proxy_port: int):
|
||||
req = self.s.post(f"{self.address}api/porthijack/service/{service_id}/change-destination", json={"ip_dst": ip_dst, "proxy_port": proxy_port})
|
||||
return verify(req)
|
||||
|
||||
def ph_add_service(self, name: str, public_port: int, proxy_port: int, proto: str, ip_src: str, ip_dst: str):
|
||||
req = self.s.post(f"{self.address}api/porthijack/services/add" ,
|
||||
json={"name":name, "public_port": public_port, "proxy_port":proxy_port, "proto": proto, "ip_src": ip_src, "ip_dst": ip_dst})
|
||||
return req.json()["service_id"] if verify(req) else False
|
||||
|
||||
@@ -2,7 +2,7 @@ from multiprocessing import Process
|
||||
import socket
|
||||
|
||||
class UdpServer:
|
||||
def __init__(self,port,ipv6):
|
||||
def __init__(self,port,ipv6, proxy_port = None):
|
||||
def _startServer(port):
|
||||
sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
@@ -15,6 +15,7 @@ class UdpServer:
|
||||
|
||||
self.ipv6 = ipv6
|
||||
self.port = port
|
||||
self.proxy_port = proxy_port
|
||||
self.server = Process(target=_startServer,args=[port])
|
||||
|
||||
def start(self):
|
||||
@@ -26,7 +27,7 @@ class UdpServer:
|
||||
def sendCheckData(self,data):
|
||||
s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.settimeout(2)
|
||||
s.sendto(data, ('::1' if self.ipv6 else '127.0.0.1', self.port))
|
||||
s.sendto(data, ('::1' if self.ipv6 else '127.0.0.1', self.proxy_port if self.proxy_port else self.port))
|
||||
try:
|
||||
received_data = s.recvfrom(432)
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user