From b673d5df65a87a1e1d3847dd11d0854e91c64e7b Mon Sep 17 00:00:00 2001 From: DomySh Date: Wed, 10 Aug 2022 10:23:37 +0000 Subject: [PATCH 01/13] refactored nftable managment, and fixed stop of the container --- Dockerfile | 3 +- backend/docker-entrypoint.sh | 5 +- backend/modules/nfregex/firegex.py | 13 +-- backend/modules/nfregex/firewall.py | 3 +- .../nfregex/nftables.py} | 85 +++++++------------ backend/routers/nfregex.py | 2 +- backend/utils/__init__.py | 42 ++++++++- start.py | 15 +++- 8 files changed, 96 insertions(+), 72 deletions(-) rename backend/{utils/firegextables.py => modules/nfregex/nftables.py} (67%) diff --git a/Dockerfile b/Dockerfile index 7f7994d..85a320c 100755 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh index 6d7520a..a44a0b0 100644 --- a/backend/docker-entrypoint.sh +++ b/backend/docker-entrypoint.sh @@ -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" diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py index 15d5d40..6bfa39f 100644 --- a/backend/modules/nfregex/firegex.py +++ b/backend/modules/nfregex/firegex.py @@ -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 FiregexFilter, 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, @@ -68,9 +70,9 @@ class FiregexInterceptor: 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) + if not filter in nft.get(): + nft.add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) + nft.add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) return self async def _start_binary(self): @@ -140,7 +142,6 @@ class FiregexInterceptor: 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"}}}) \ No newline at end of file diff --git a/backend/modules/nfregex/firewall.py b/backend/modules/nfregex/firewall.py index 03ba62b..07f103b 100644 --- a/backend/modules/nfregex/firewall.py +++ b/backend/modules/nfregex/firewall.py @@ -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, delete_by_srv +from modules.nfregex.nftables import FiregexTables, FiregexFilter from modules.nfregex.models import Regex, Service from utils.sqlite import SQLite diff --git a/backend/utils/firegextables.py b/backend/modules/nfregex/nftables.py similarity index 67% rename from backend/utils/firegextables.py rename to backend/modules/nfregex/nftables.py index 8919625..2accc8a 100644 --- a/backend/utils/firegextables.py +++ b/backend/modules/nfregex/nftables.py @@ -1,10 +1,8 @@ from typing import List -import nftables -from utils import ip_parse, ip_family +from utils import ip_parse, ip_family, NFTableManager 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 @@ -17,55 +15,36 @@ class 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: - +class FiregexTables(NFTableManager): + input_chain = "nfregex_input" + output_chain = "nfregex_output" + 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"] + super().__init__([ + {"create":{"chain":{ + "family":"inet", + "table":self.table_name, + "name":self.input_chain, + "type":"filter", + "hook":"prerouting", + "prio":-150, + "policy":"accept" + }}}, + {"create":{"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_output(self, queue_range, proto, port, ip_int): init, end = queue_range @@ -76,7 +55,7 @@ class FiregexTables: self.cmd({ "insert":{ "rule": { "family": "inet", "table": self.table_name, - "chain": "output", + "chain": self.output_chain, "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)}}, @@ -93,7 +72,7 @@ class FiregexTables: self.cmd({"insert":{"rule":{ "family": "inet", "table": self.table_name, - "chain": "input", + "chain": self.input_chain, "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)}}, diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 48c6e5e..e5b7f8f 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -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 diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index ed23156..e501a7e 100755 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -1,7 +1,6 @@ import asyncio from ipaddress import ip_interface -import os, socket, psutil -import sys +import os, socket, psutil, sys, nftables from fastapi_socketio import SocketManager LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) @@ -47,4 +46,41 @@ def get_interfaces(): for interf in interfs: if interf.family in [socket.AF_INET, socket.AF_INET6]: yield {"name": int_name, "addr":interf.address} - return list(_get_interfaces()) \ No newline at end of file + return list(_get_interfaces()) + +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({"create":{"table":{"name":self.table_name,"family":"inet"}}}) + self.cmd(*self.__init_cmds) + + def reset(self): + print(self.raw_cmd(*self.__reset_cmds)) + + def list(self): + return self.cmd({"list": {"ruleset": None}})["nftables"] + \ No newline at end of file diff --git a/start.py b/start.py index ce15ad2..387226f 100755 --- a/start.py +++ b/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: From c10cf2b6ee83c3aec1697accfa72bd6e86468c7f Mon Sep 17 00:00:00 2001 From: nik012003 Date: Wed, 10 Aug 2022 21:35:30 +0200 Subject: [PATCH 02/13] Initial Testing of Porthijacking --- backend/modules/nfregex/nftables.py | 4 +- backend/routers/porthijack.py | 0 backend/test.py | 133 ++++++++++++++++++++++++++++ backend/utils/__init__.py | 4 +- 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 backend/routers/porthijack.py create mode 100644 backend/test.py diff --git a/backend/modules/nfregex/nftables.py b/backend/modules/nfregex/nftables.py index 2accc8a..c5b4e86 100644 --- a/backend/modules/nfregex/nftables.py +++ b/backend/modules/nfregex/nftables.py @@ -21,7 +21,7 @@ class FiregexTables(NFTableManager): def __init__(self): super().__init__([ - {"create":{"chain":{ + {"add":{"chain":{ "family":"inet", "table":self.table_name, "name":self.input_chain, @@ -30,7 +30,7 @@ class FiregexTables(NFTableManager): "prio":-150, "policy":"accept" }}}, - {"create":{"chain":{ + {"add":{"chain":{ "family":"inet", "table":self.table_name, "name":self.output_chain, diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/test.py b/backend/test.py new file mode 100644 index 0000000..e233309 --- /dev/null +++ b/backend/test.py @@ -0,0 +1,133 @@ + +from ipaddress import ip_interface +import nftables, traceback + +def ip_parse(ip:str): + return str(ip_interface(ip).network) + +def ip_family(ip:str): + return "ip6" if ip_interface(ip).version == 6 else "ip" + +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(self): + return self.cmd({"list": {"ruleset": None}})["nftables"] + + +class FiregexTables(NFTableManager): + prerouting_porthijack = "porthijack" + + def __init__(self): + super().__init__([ + {"add":{"chain":{ + "family":"inet", + "table":self.table_name, + "name":self.prerouting_porthijack, + "type":"nat", + "hook":"output", + "prio":-100, + "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}}} + ]) + + def add(self, ip_int, proto, public_port, proxy_port): + 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": self.prerouting_porthijack, + "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(public_port)}}, + {'redirect' : {'port' : int(proxy_port), 'flags' : []}} + ] + }}}) +""" + def add_output(self, queue_range, proto, port, ip_int): + + + 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": self.input_chain, + "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 +""" +try: + #print(FiregexTables().list()) + FiregexTables().init() + FiregexTables().add("127.0.0.1","tcp", 8080, 8081) + input() +except: + traceback.print_exc() + FiregexTables().reset() + +#https://www.mankier.com/5/libnftables-json \ No newline at end of file diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index e501a7e..5551442 100755 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -75,11 +75,11 @@ class NFTableManager(Singleton): def init(self): self.reset() - self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}}) + self.raw_cmd({"add":{"table":{"name":self.table_name,"family":"inet"}}}) self.cmd(*self.__init_cmds) def reset(self): - print(self.raw_cmd(*self.__reset_cmds)) + self.raw_cmd(*self.__reset_cmds) def list(self): return self.cmd({"list": {"ruleset": None}})["nftables"] From 1931536516edcc95fa5fe3a3b04d4cd661bbfd88 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 11 Aug 2022 13:12:12 +0000 Subject: [PATCH 03/13] End of test for port-hijacking --- backend/test.py | 77 +++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 54 deletions(-) diff --git a/backend/test.py b/backend/test.py index e233309..a0e9dc1 100644 --- a/backend/test.py +++ b/backend/test.py @@ -46,7 +46,8 @@ class NFTableManager(Singleton): class FiregexTables(NFTableManager): - prerouting_porthijack = "porthijack" + prerouting_porthijack = "prerouting_porthijack" + postrouting_porthijack = "postrouting_porthijack" def __init__(self): super().__init__([ @@ -54,14 +55,25 @@ class FiregexTables(NFTableManager): "family":"inet", "table":self.table_name, "name":self.prerouting_porthijack, - "type":"nat", - "hook":"output", - "prio":-100, + "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}}} + {"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, ip_int, proto, public_port, proxy_port): @@ -75,59 +87,16 @@ class FiregexTables(NFTableManager): "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(public_port)}}, - {'redirect' : {'port' : int(proxy_port), 'flags' : []}} + {'mangle': {'key': {'payload': {'protocol': str(proto), 'field': 'dport'}}, 'value': int(proxy_port)}} ] }}}) -""" - def add_output(self, queue_range, proto, port, ip_int): - - - 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":{ + self.cmd({ "insert":{ "rule": { "family": "inet", "table": self.table_name, - "chain": self.input_chain, + "chain": self.postrouting_porthijack, "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"]}} + {'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(proxy_port)}}, + {'mangle': {'key': {'payload': {'protocol': str(proto), 'field': 'sport'}}, 'value': int(public_port)}} ] }}}) - - 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 -""" -try: - #print(FiregexTables().list()) - FiregexTables().init() - FiregexTables().add("127.0.0.1","tcp", 8080, 8081) - input() -except: - traceback.print_exc() - FiregexTables().reset() - -#https://www.mankier.com/5/libnftables-json \ No newline at end of file From f4fe3d3ab597bcb45876dbf1cdfe4c1cd606c595 Mon Sep 17 00:00:00 2001 From: nik012003 Date: Thu, 11 Aug 2022 16:11:32 +0200 Subject: [PATCH 04/13] Refactoring code pt.1 --- backend/modules/nfregex/firegex.py | 4 - backend/modules/nfregex/firewall.py | 8 +- backend/modules/nfregex/nftables.py | 38 ++--- backend/modules/porthijack/__init__.py | 0 backend/modules/porthijack/firewall.py | 114 +++++++++++++ backend/modules/porthijack/models.py | 13 ++ .../porthijack/nftables.py} | 81 ++++----- backend/routers/porthijack.py | 160 ++++++++++++++++++ backend/utils/__init__.py | 9 +- 9 files changed, 354 insertions(+), 73 deletions(-) create mode 100644 backend/modules/porthijack/__init__.py create mode 100644 backend/modules/porthijack/firewall.py create mode 100644 backend/modules/porthijack/models.py rename backend/{test.py => modules/porthijack/nftables.py} (61%) diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py index 6bfa39f..c7ab825 100644 --- a/backend/modules/nfregex/firegex.py +++ b/backend/modules/nfregex/firegex.py @@ -141,7 +141,3 @@ class FiregexInterceptor: except Exception: pass return res -def delete_by_srv(srv:Service): - 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"}}}) \ No newline at end of file diff --git a/backend/modules/nfregex/firewall.py b/backend/modules/nfregex/firewall.py index 07f103b..56aa63e 100644 --- a/backend/modules/nfregex/firewall.py +++ b/backend/modules/nfregex/firewall.py @@ -1,6 +1,6 @@ import asyncio from typing import Dict -from modules.nfregex.firegex import FiregexInterceptor, 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 @@ -95,13 +95,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)) + FiregexTables().delete(self.srv) + self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv)) await self._update_filters_from_db() self._set_status(STATUS.ACTIVE) async def stop(self): - delete_by_srv(self.srv) + FiregexTables().delete(self.srv) if self.interceptor: await self.interceptor.stop() self.interceptor = None diff --git a/backend/modules/nfregex/nftables.py b/backend/modules/nfregex/nftables.py index c5b4e86..39ba765 100644 --- a/backend/modules/nfregex/nftables.py +++ b/backend/modules/nfregex/nftables.py @@ -1,10 +1,10 @@ from typing import List +from modules.nfregex.models import Service from utils import ip_parse, ip_family, NFTableManager class FiregexFilter(): - def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None): + def __init__(self, proto:str, port:int, ip_int:str, target:str=None, id=None): self.id = int(id) if id else None - self.queue = queue self.target = target self.proto = proto self.port = int(port) @@ -46,47 +46,41 @@ class FiregexTables(NFTableManager): {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}}, ]) - 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) + def add(self, srv:Service, queue_range_input, queue_range_output): + ip_int = ip_parse(srv.ip_int) ip_addr = str(ip_int).split("/")[0] ip_addr_cidr = int(str(ip_int).split("/")[1]) + + 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(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)}}, + {'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"]}} ] }}}) - - def add_input(self, queue_range, proto = None, port = None, ip_int = None): - init, end = queue_range + + init, end = queue_range_input 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": self.input_chain, "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)}}, + {'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 [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) + 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"])) @@ -95,10 +89,14 @@ class FiregexTables(NFTableManager): 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 + + def delete(self, srv:Service): + for filter in self.get(): + if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int): + self.cmd({"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}}) \ No newline at end of file diff --git a/backend/modules/porthijack/__init__.py b/backend/modules/porthijack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py new file mode 100644 index 0000000..13189eb --- /dev/null +++ b/backend/modules/porthijack/firewall.py @@ -0,0 +1,114 @@ +import asyncio +from typing import Dict +from modules.porthijack.nftables import FiregexTables, FiregexFilter +from modules.porthijack.models import Service +from utils.sqlite import SQLite + +class STATUS: + STOP = "stop" + ACTIVE = "active" + +class FirewallManager: + def __init__(self, db:SQLite): + self.db = db + self.proxy_table: Dict[str, ServiceManager] = {} + self.lock = asyncio.Lock() + + async def close(self): + for key in list(self.proxy_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] + + 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.id in self.proxy_table: + continue + self.proxy_table[srv.id] = ServiceManager(srv, self.db) + await self.proxy_table[srv.id].next(srv.status) + + def get(self,srv_id): + if srv_id in self.proxy_table: + return self.proxy_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.status = STATUS.STOP + self.filters: Dict[int, FiregexFilter] = {} + self.lock = asyncio.Lock() + self.interceptor = None + + async def _update_filters_from_db(self): + regexes = [ + Regex.from_dict(ele) for ele in + self.db.query("SELECT * FROM regexes WHERE service_id = ? AND active=1;", self.srv.id) + ] + #Filter check + old_filters = set(self.filters.keys()) + new_filters = set([f.id for f in regexes]) + #remove old filters + for f in old_filters: + if not f in new_filters: + del self.filters[f] + #add new filters + for f in new_filters: + if not f in old_filters: + filter = [ele for ele in regexes if ele.id == f][0] + self.filters[f] = RegexFilter.from_regex(filter, self._stats_updater) + if self.interceptor: await self.interceptor.reload(self.filters.values()) + + def __update_status_db(self, status): + self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv.id) + + async def next(self,to): + async with self.lock: + if (self.status, to) == (STATUS.ACTIVE, STATUS.STOP): + await self.stop() + self._set_status(to) + # PAUSE -> ACTIVE + elif (self.status, to) == (STATUS.STOP, STATUS.ACTIVE): + await self.restart() + + def _stats_updater(self,filter:RegexFilter): + self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", filter.blocked, filter.id) + + def _set_status(self,status): + self.status = status + self.__update_status_db(status) + + async def start(self): + if not self.interceptor: + FiregexTables().delete_by_srv(self.srv) + self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) + await self._update_filters_from_db() + self._set_status(STATUS.ACTIVE) + + async def stop(self): + FiregexTables().delete_by_srv(self.srv) + if self.interceptor: + await self.interceptor.stop() + self.interceptor = None + + async def restart(self): + await self.stop() + await self.start() + + async def update_filters(self): + async with self.lock: + await self._update_filters_from_db() \ No newline at end of file diff --git a/backend/modules/porthijack/models.py b/backend/modules/porthijack/models.py new file mode 100644 index 0000000..4e2a1d3 --- /dev/null +++ b/backend/modules/porthijack/models.py @@ -0,0 +1,13 @@ +class Service: + def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_int: 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_int = ip_int + + @classmethod + def from_dict(cls, var: dict): + return cls(id=var["service_id"], active=var["active"], public_port=var["public_port"], proxy_port=var["proxy_port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"]) diff --git a/backend/test.py b/backend/modules/porthijack/nftables.py similarity index 61% rename from backend/test.py rename to backend/modules/porthijack/nftables.py index a0e9dc1..3eb57cb 100644 --- a/backend/test.py +++ b/backend/modules/porthijack/nftables.py @@ -1,49 +1,19 @@ +from typing import List +from utils import ip_parse, ip_family, NFTableManager -from ipaddress import ip_interface -import nftables, traceback - -def ip_parse(ip:str): - return str(ip_interface(ip).network) - -def ip_family(ip:str): - return "ip6" if ip_interface(ip).version == 6 else "ip" - -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(self): - return self.cmd({"list": {"ruleset": None}})["nftables"] +class FiregexFilter(): + def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None): + 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(NFTableManager): prerouting_porthijack = "prerouting_porthijack" @@ -100,3 +70,26 @@ class FiregexTables(NFTableManager): {'mangle': {'key': {'payload': {'protocol': str(proto), 'field': 'sport'}}, 'value': int(public_port)}} ] }}}) + + def get(self) -> List[FiregexFilter]: + res = [] + for filter in self.list_rules(tables=[self.table_name], chains=[self.input_chain,self.output_chain]): + 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 + \ No newline at end of file diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index e69de29..3a812e4 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -0,0 +1,160 @@ +import secrets +import sqlite3 +from typing import List, Union +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from utils.sqlite import SQLite +from utils import ip_parse, refactor_name, refresh_frontend +from utils.models import ResetRequest, StatusMessageModel +from modules.porthijack.nftables import FiregexTables +from modules.porthijack.firewall import STATUS, FirewallManager + +class ServiceModel(BaseModel): + service_id: str + active: bool + public_port: int + proxy_port: int + name: str + proto: str + ip_int: str + +class RenameForm(BaseModel): + name:str + +class ServiceAddForm(BaseModel): + name: str + public_port: int + proxy_port: int + proto: str + ip_int: 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(port > 0 and port < 65536)', + 'proxy_port': 'INT NOT NULL CHECK(port > 0 and port < 65536)', + 'name': 'VARCHAR(100) NOT NULL UNIQUE', + 'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))', + 'ip_int': 'VARCHAR(100) NOT NULL', + }, + 'QUERY':[ + "CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_int, 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_int 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_int 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).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, ): + """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, ): + """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'} + + +@app.post('/services/add', response_model=ServiceAddResponse) +async def add_new_service(form: ServiceAddForm, ): + """Add a new service""" + try: + form.ip_int = ip_parse(form.ip_int) + except ValueError: + return {"status":"Invalid address"} + 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_int) VALUES (?, ?, ?, ?, ?, ?, ?)", + srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_int) + except sqlite3.IntegrityError: + return {'status': 'This type of service already exists'} + await firewall.reload() + await refresh_frontend() + return {'status': 'ok', 'service_id': srv_id} diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index 5551442..0a4c30d 100755 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -81,6 +81,13 @@ class NFTableManager(Singleton): def reset(self): self.raw_cmd(*self.__reset_cmds) - def list(self): + 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"] + \ No newline at end of file From e6b4ddd4a054bdcfa5caaa7221a9017b350c7695 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 11 Aug 2022 15:16:23 +0000 Subject: [PATCH 05/13] Code refactoring and adding port-hijacking backup commit --- backend/modules/nfregex/firegex.py | 12 ++-- backend/modules/nfregex/firewall.py | 30 ++++---- backend/modules/nfregex/nftables.py | 23 +++++-- backend/modules/porthijack/firewall.py | 95 +++++++++----------------- backend/modules/porthijack/nftables.py | 59 +++++++++------- backend/routers/porthijack.py | 6 +- 6 files changed, 108 insertions(+), 117 deletions(-) diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py index c7ab825..0753e0f 100644 --- a/backend/modules/nfregex/firegex.py +++ b/backend/modules/nfregex/firegex.py @@ -1,5 +1,5 @@ from typing import Dict, List, Set -from modules.nfregex.nftables import FiregexFilter, FiregexTables +from modules.nfregex.nftables import FiregexTables from utils import ip_parse, run_func from modules.nfregex.models import Service, Regex import re, os, asyncio @@ -54,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] @@ -63,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 nft.get(): - nft.add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) - nft.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): diff --git a/backend/modules/nfregex/firewall.py b/backend/modules/nfregex/firewall.py index 56aa63e..18544f6 100644 --- a/backend/modules/nfregex/firewall.py +++ b/backend/modules/nfregex/firewall.py @@ -9,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() @@ -95,13 +97,13 @@ class ServiceManager: async def start(self): if not self.interceptor: - FiregexTables().delete(self.srv) - self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv)) + 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): - FiregexTables().delete(self.srv) + nft.delete(self.srv) if self.interceptor: await self.interceptor.stop() self.interceptor = None diff --git a/backend/modules/nfregex/nftables.py b/backend/modules/nfregex/nftables.py index 39ba765..a0a31a0 100644 --- a/backend/modules/nfregex/nftables.py +++ b/backend/modules/nfregex/nftables.py @@ -2,9 +2,9 @@ from typing import List from modules.nfregex.models import Service from utils import ip_parse, ip_family, NFTableManager -class FiregexFilter(): - def __init__(self, proto:str, port:int, ip_int:str, target:str=None, id=None): - self.id = int(id) if id else None +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) @@ -13,6 +13,8 @@ class FiregexFilter(): 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): @@ -47,10 +49,14 @@ class FiregexTables(NFTableManager): ]) def add(self, srv:Service, queue_range_input, queue_range_output): + + for ele in self.get(): + if ele.__eq__(srv): return + ip_int = ip_parse(srv.ip_int) ip_addr = str(ip_int).split("/")[0] ip_addr_cidr = int(str(ip_int).split("/")[1]) - + init, end = queue_range_output if init > end: init, end = end, init self.cmd({ "insert":{ "rule": { @@ -97,6 +103,11 @@ class FiregexTables(NFTableManager): def delete(self, srv:Service): for filter in self.get(): - if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int): - self.cmd({"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}}) + if filter.__eq__(srv): + self.cmd({ "delete":{ "rule": { + "family": "inet", + "table": self.table_name, + "chain": filter.target, + "handle": filter.id + }}}) \ No newline at end of file diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py index 13189eb..9f253bc 100644 --- a/backend/modules/porthijack/firewall.py +++ b/backend/modules/porthijack/firewall.py @@ -1,28 +1,27 @@ +from ast import Delete import asyncio from typing import Dict from modules.porthijack.nftables import FiregexTables, FiregexFilter from modules.porthijack.models import Service from utils.sqlite import SQLite -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].disable() + del self.service_table[srv_id] async def init(self): FiregexTables().init() @@ -32,14 +31,15 @@ class FirewallManager: 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.service_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.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.proxy_table: - return self.proxy_table[srv_id] + if srv_id in self.service_table: + return self.service_table[srv_id] else: raise ServiceNotFoundException() @@ -49,66 +49,33 @@ class ServiceManager: def __init__(self, srv: Service, db): self.srv = srv self.db = db - self.status = STATUS.STOP - self.filters: Dict[int, FiregexFilter] = {} + self.active = False self.lock = asyncio.Lock() - self.interceptor = None - - async def _update_filters_from_db(self): - regexes = [ - Regex.from_dict(ele) for ele in - self.db.query("SELECT * FROM regexes WHERE service_id = ? AND active=1;", self.srv.id) - ] - #Filter check - old_filters = set(self.filters.keys()) - new_filters = set([f.id for f in regexes]) - #remove old filters - for f in old_filters: - if not f in new_filters: - del self.filters[f] - #add new filters - for f in new_filters: - if not f in old_filters: - filter = [ele for ele in regexes if ele.id == f][0] - self.filters[f] = RegexFilter.from_regex(filter, self._stats_updater) - if self.interceptor: await self.interceptor.reload(self.filters.values()) - - def __update_status_db(self, status): - self.db.query("UPDATE services SET status = ? WHERE service_id = ?;", status, self.srv.id) - async def next(self,to): - async with self.lock: - if (self.status, to) == (STATUS.ACTIVE, STATUS.STOP): + async def enable(self,to): + if (self.status != to): + async with self.lock: + await self.restart() + + async def disable(self,to): + if (self.status != to): + async with self.lock: await self.stop() self._set_status(to) - # PAUSE -> ACTIVE - elif (self.status, to) == (STATUS.STOP, STATUS.ACTIVE): - await self.restart() - def _stats_updater(self,filter:RegexFilter): - self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", filter.blocked, filter.id) - - def _set_status(self,status): - self.status = status - self.__update_status_db(status) + 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 start(self): - if not self.interceptor: - FiregexTables().delete_by_srv(self.srv) - self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) - await self._update_filters_from_db() - self._set_status(STATUS.ACTIVE) + if not self.active: + nft.delete(self.srv) + nft.add(self.srv) + self._set_status(True) async def stop(self): - FiregexTables().delete_by_srv(self.srv) - if self.interceptor: - await self.interceptor.stop() - self.interceptor = None + nft.delete(self.srv) async def restart(self): await self.stop() - await self.start() - - async def update_filters(self): - async with self.lock: - await self._update_filters_from_db() \ No newline at end of file + await self.start() \ No newline at end of file diff --git a/backend/modules/porthijack/nftables.py b/backend/modules/porthijack/nftables.py index 3eb57cb..13a2814 100644 --- a/backend/modules/porthijack/nftables.py +++ b/backend/modules/porthijack/nftables.py @@ -1,18 +1,21 @@ from typing import List +from modules.porthijack.models import Service from utils import ip_parse, ip_family, NFTableManager -class FiregexFilter(): - def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None): - self.id = int(id) if id else None - self.queue = queue +class FiregexHijackRule(): + def __init__(self, proto:str, public_port:int,proxy_port:int, ip_int:str, target:str, id:int): + self.id = id self.target = target self.proto = proto - self.port = int(port) + self.public_port = public_port + self.proxy_port = proxy_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) + if isinstance(o, FiregexHijackRule): + return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) + elif isinstance(o, Service): + return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) return False class FiregexTables(NFTableManager): @@ -46,8 +49,12 @@ class FiregexTables(NFTableManager): {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}} ]) - def add(self, ip_int, proto, public_port, proxy_port): - ip_int = ip_parse(ip_int) + def add(self, srv:Service): + + for ele in self.get(): + if ele.__eq__(srv): return + + ip_int = ip_parse(srv.ip_int) ip_addr = str(ip_int).split("/")[0] ip_addr_cidr = int(str(ip_int).split("/")[1]) self.cmd({ "insert":{ "rule": { @@ -56,8 +63,8 @@ class FiregexTables(NFTableManager): "chain": self.prerouting_porthijack, "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(public_port)}}, - {'mangle': {'key': {'payload': {'protocol': str(proto), 'field': 'dport'}}, 'value': int(proxy_port)}} + {'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)}} ] }}}) self.cmd({ "insert":{ "rule": { @@ -66,30 +73,36 @@ class FiregexTables(NFTableManager): "chain": self.postrouting_porthijack, "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(proxy_port)}}, - {'mangle': {'key': {'payload': {'protocol': str(proto), 'field': 'sport'}}, 'value': int(public_port)}} + {'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)}} ] }}}) - def get(self) -> List[FiregexFilter]: + + def get(self) -> List[FiregexHijackRule]: res = [] - for filter in self.list_rules(tables=[self.table_name], chains=[self.input_chain,self.output_chain]): - 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) + for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]): 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( + res.append(FiregexHijackRule( target=filter["chain"], id=int(filter["handle"]), - queue=queue, proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"], - port=filter["expr"][1]["match"]["right"], + public_port=filter["expr"][1]["match"]["right"] if filter["target"] == self.prerouting_porthijack else filter["expr"][2]["mangle"]["value"], + proxy_port=filter["expr"][1]["match"]["right"] if filter["target"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"], ip_int=ip_int )) return res - \ No newline at end of file + + 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 + }}}) \ No newline at end of file diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index 3a812e4..b5c7e19 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -7,7 +7,7 @@ from utils.sqlite import SQLite from utils import ip_parse, refactor_name, refresh_frontend from utils.models import ResetRequest, StatusMessageModel from modules.porthijack.nftables import FiregexTables -from modules.porthijack.firewall import STATUS, FirewallManager +from modules.porthijack.firewall import FirewallManager class ServiceModel(BaseModel): service_id: str @@ -107,14 +107,14 @@ async def get_service_by_id(service_id: str, ): @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).next(STATUS.STOP) + 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).next(STATUS.ACTIVE) + await firewall.get(service_id).enable() await refresh_frontend() return {'status': 'ok'} From 4076400ec43880aad127f12eb10a25f723e5c6c4 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 11 Aug 2022 15:38:17 +0000 Subject: [PATCH 06/13] Code refactoring + port-hijacking backend (need port-hijacking backend testing)+ --- backend/modules/porthijack/firewall.py | 3 +-- backend/modules/porthijack/nftables.py | 4 ++-- backend/routers/porthijack.py | 4 ++-- tests/px_test.py | 3 ++- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py index 9f253bc..bafa096 100644 --- a/backend/modules/porthijack/firewall.py +++ b/backend/modules/porthijack/firewall.py @@ -1,7 +1,6 @@ -from ast import Delete import asyncio from typing import Dict -from modules.porthijack.nftables import FiregexTables, FiregexFilter +from modules.porthijack.nftables import FiregexTables from modules.porthijack.models import Service from utils.sqlite import SQLite diff --git a/backend/modules/porthijack/nftables.py b/backend/modules/porthijack/nftables.py index 13a2814..17f0a5a 100644 --- a/backend/modules/porthijack/nftables.py +++ b/backend/modules/porthijack/nftables.py @@ -91,8 +91,8 @@ class FiregexTables(NFTableManager): target=filter["chain"], id=int(filter["handle"]), proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"], - public_port=filter["expr"][1]["match"]["right"] if filter["target"] == self.prerouting_porthijack else filter["expr"][2]["mangle"]["value"], - proxy_port=filter["expr"][1]["match"]["right"] if filter["target"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"], + 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_int=ip_int )) return res diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index b5c7e19..1394e0b 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -41,8 +41,8 @@ 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(port > 0 and port < 65536)', - 'proxy_port': 'INT NOT NULL CHECK(port > 0 and port < 65536)', + '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)', 'name': 'VARCHAR(100) NOT NULL UNIQUE', 'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))', 'ip_int': 'VARCHAR(100) NOT NULL', diff --git a/tests/px_test.py b/tests/px_test.py index 22ae91f..e90e34d 100755 --- a/tests/px_test.py +++ b/tests/px_test.py @@ -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: @@ -245,4 +246,4 @@ new_internal_port = firegex.px_get_service(service_id)["internal_port"] if (internal_port != new_internal_port): puts(f"Sucessfully got regenerated port {new_internal_port} ✔", color=colors.green) else: puts(f"Test Failed: Coundn't get internal port, or it was the same as previous ✗", color=colors.red); exit_test(1) -exit_test(0) \ No newline at end of file +exit_test(0) From 90538a89dd2283d37112dc1303d82c885eb32d00 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 11 Aug 2022 15:53:33 +0000 Subject: [PATCH 07/13] port-hijacking backend tested and fixed --- backend/modules/porthijack/firewall.py | 11 ++++++----- backend/modules/porthijack/models.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py index bafa096..f71f560 100644 --- a/backend/modules/porthijack/firewall.py +++ b/backend/modules/porthijack/firewall.py @@ -51,16 +51,17 @@ class ServiceManager: self.active = False self.lock = asyncio.Lock() - async def enable(self,to): - if (self.status != to): + async def enable(self): + if not self.active: async with self.lock: await self.restart() + self._set_status(True) - async def disable(self,to): - if (self.status != to): + async def disable(self): + if self.active: async with self.lock: await self.stop() - self._set_status(to) + self._set_status(False) def _set_status(self,active): self.active = active diff --git a/backend/modules/porthijack/models.py b/backend/modules/porthijack/models.py index 4e2a1d3..ff39882 100644 --- a/backend/modules/porthijack/models.py +++ b/backend/modules/porthijack/models.py @@ -10,4 +10,4 @@ class Service: @classmethod def from_dict(cls, var: dict): - return cls(id=var["service_id"], active=var["active"], public_port=var["public_port"], proxy_port=var["proxy_port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"]) + 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_int=var["ip_int"]) From a0fba5b2f23f391e3c44ca2592a03f5b000eac39 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 11 Aug 2022 18:01:07 +0000 Subject: [PATCH 08/13] Finished backend of hijack-port --- backend/modules/porthijack/firewall.py | 22 ++++++++----------- backend/routers/nfregex.py | 24 ++++++++++----------- backend/routers/porthijack.py | 30 +++++++++++++++++++------- backend/routers/regexproxy.py | 30 +++++++++++++------------- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/backend/modules/porthijack/firewall.py b/backend/modules/porthijack/firewall.py index f71f560..7325d32 100644 --- a/backend/modules/porthijack/firewall.py +++ b/backend/modules/porthijack/firewall.py @@ -54,28 +54,24 @@ class ServiceManager: async def enable(self): if not self.active: async with self.lock: - await self.restart() + nft.delete(self.srv) + nft.add(self.srv) self._set_status(True) async def disable(self): if self.active: async with self.lock: - await self.stop() + nft.delete(self.srv) self._set_status(False) + async def change_port(self, new_port): + self.srv.proxy_port = new_port + 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 start(self): - if not self.active: - nft.delete(self.srv) - nft.add(self.srv) - self._set_status(True) - - async def stop(self): - nft.delete(self.srv) async def restart(self): - await self.stop() - await self.start() \ No newline at end of file + await self.disable() + await self.enable() \ No newline at end of file diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index e5b7f8f..91131b1 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -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) diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index 1394e0b..7d66e8f 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -41,14 +41,15 @@ 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)', + 'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536) UNIQUE', + '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_int': 'VARCHAR(100) NOT NULL', }, 'QUERY':[ "CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_int, proto);", + "" ] }) @@ -98,28 +99,28 @@ async def get_service_list(): return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_int FROM services;") @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 service_id, active, public_port, proxy_port, name, proto, ip_int 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, ): +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, ): +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, ): +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) @@ -127,7 +128,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!'} @@ -138,9 +139,22 @@ async def service_rename(service_id: str, form: RenameForm, ): await refresh_frontend() return {'status': 'ok'} +class ChangePortRequest(BaseModel): + proxy_port: int + +@app.post('/service/{service_id}/changeport', response_model=StatusMessageModel) +async def service_changeport(service_id: str, form: ChangePortRequest): + """Request to change the proxy port of a specific service""" + try: + db.query('UPDATE services SET proxy_port=? WHERE service_id = ?;', form.proxy_port, service_id) + except sqlite3.IntegrityError: + return {'status': 'Invalid proxy port or service'} + await firewall.get(service_id).change_port(form.proxy_port) + await refresh_frontend() + 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) diff --git a/backend/routers/regexproxy.py b/backend/routers/regexproxy.py index dd49ec5..c7d565e 100644 --- a/backend/routers/regexproxy.py +++ b/backend/routers/regexproxy.py @@ -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() @@ -161,7 +161,7 @@ class ChangePortForm(BaseModel): internalPort: Union[int, None] @app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel) -async def change_service_ports(service_id: str, change_port:ChangePortForm ): +async def change_service_ports(service_id: str, change_port:ChangePortForm): """Choose and change the ports of the service""" if change_port.port is None and change_port.internalPort is None: return {'status': 'Invalid Request!'} @@ -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) From 0193f2efc8a03c35cde08ff2963c02dae15a812d Mon Sep 17 00:00:00 2001 From: DomySh Date: Fri, 12 Aug 2022 07:52:01 +0000 Subject: [PATCH 09/13] Starting InlineEdit component --- frontend/src/_vars.scss | 2 +- frontend/src/components/Footer/index.tsx | 1 - .../components/InlineEdit/index.module.scss | 20 ++++++++++++++++ frontend/src/components/InlineEdit/index.tsx | 24 +++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100755 frontend/src/components/InlineEdit/index.module.scss create mode 100755 frontend/src/components/InlineEdit/index.tsx diff --git a/frontend/src/_vars.scss b/frontend/src/_vars.scss index e65237e..6df635d 100755 --- a/frontend/src/_vars.scss +++ b/frontend/src/_vars.scss @@ -1,4 +1,4 @@ $primary_color: #242a33; -$second_color: #1A1B1E; +$secondary_color: #1A1B1E; $third_color:#25262b; diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx index 7f61c54..bcc4588 100755 --- a/frontend/src/components/Footer/index.tsx +++ b/frontend/src/components/Footer/index.tsx @@ -3,7 +3,6 @@ import React from 'react'; import style from "./index.module.scss"; - function FooterPage() { return