Merge pull request #4 from Pwnzer0tt1/dev

FIregex 2.1.0
This commit is contained in:
Domingo Dirutigliano
2022-08-12 20:53:33 +02:00
committed by GitHub
43 changed files with 1554 additions and 303 deletions

View File

@@ -38,6 +38,7 @@ RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_th
COPY ./backend/ /execute/ COPY ./backend/ /execute/
COPY --from=frontend /app/build/ ./frontend/ COPY --from=frontend /app/build/ ./frontend/
ENTRYPOINT ["/bin/sh", "/execute/docker-entrypoint.sh"]
CMD ["/bin/sh", "/execute/docker-entrypoint.sh"]

View File

@@ -60,7 +60,6 @@ Initiially the project was based only on regex filters, and also now the main fu
## Next points ## Next points
- Create hijacking port to proxy
- Explanation about tools in the dedicated pages making them more user-friendly - 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 - 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 - Adding new section with "general firewall rules" to manage "simple" TCP traffic rules graphically and through nftables

View File

@@ -2,8 +2,7 @@
chown nobody:nobody -R /execute/ chown nobody:nobody -R /execute/
capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \ exec capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
--keep=1 --user=nobody --addamb=cap_net_admin -- \ --keep=1 --user=nobody --addamb=cap_net_admin -- -c "python3 /execute/app.py DOCKER"
-c "python3 /execute/app.py DOCKER"

View File

@@ -1,10 +1,12 @@
from typing import Dict, List, Set from typing import Dict, List, Set
from utils.firegextables import FiregexFilter, FiregexTables from modules.nfregex.nftables import FiregexTables
from utils import ip_parse, ip_family, run_func from utils import ip_parse, run_func
from modules.nfregex.models import Service, Regex from modules.nfregex.models import Service, Regex
import re, os, asyncio import re, os, asyncio
import traceback import traceback
nft = FiregexTables()
class RegexFilter: class RegexFilter:
def __init__( def __init__(
self, regex, self, regex,
@@ -52,7 +54,7 @@ class RegexFilter:
class FiregexInterceptor: class FiregexInterceptor:
def __init__(self): def __init__(self):
self.filter:FiregexFilter self.srv:Service
self.filter_map_lock:asyncio.Lock self.filter_map_lock:asyncio.Lock
self.filter_map: Dict[str, RegexFilter] self.filter_map: Dict[str, RegexFilter]
self.regex_filters: Set[RegexFilter] self.regex_filters: Set[RegexFilter]
@@ -61,16 +63,14 @@ class FiregexInterceptor:
self.update_task: asyncio.Task self.update_task: asyncio.Task
@classmethod @classmethod
async def start(cls, filter: FiregexFilter): async def start(cls, srv: Service):
self = cls() self = cls()
self.filter = filter self.srv = srv
self.filter_map_lock = asyncio.Lock() self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock() self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary() input_range, output_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked()) self.update_task = asyncio.create_task(self.update_blocked())
if not filter in FiregexTables().get(): nft.add(self.srv, input_range, output_range)
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)
return self return self
async def _start_binary(self): async def _start_binary(self):
@@ -139,8 +139,3 @@ class FiregexInterceptor:
except Exception: pass except Exception: pass
return res 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"}}})

View File

@@ -1,6 +1,7 @@
import asyncio import asyncio
from typing import Dict 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 modules.nfregex.models import Regex, Service
from utils.sqlite import SQLite from utils.sqlite import SQLite
@@ -8,38 +9,40 @@ class STATUS:
STOP = "stop" STOP = "stop"
ACTIVE = "active" ACTIVE = "active"
nft = FiregexTables()
class FirewallManager: class FirewallManager:
def __init__(self, db:SQLite): def __init__(self, db:SQLite):
self.db = db self.db = db
self.proxy_table: Dict[str, ServiceManager] = {} self.service_table: Dict[str, ServiceManager] = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
async def close(self): async def close(self):
for key in list(self.proxy_table.keys()): for key in list(self.service_table.keys()):
await self.remove(key) await self.remove(key)
async def remove(self,srv_id): async def remove(self,srv_id):
async with self.lock: async with self.lock:
if srv_id in self.proxy_table: if srv_id in self.service_table:
await self.proxy_table[srv_id].next(STATUS.STOP) await self.service_table[srv_id].next(STATUS.STOP)
del self.proxy_table[srv_id] del self.service_table[srv_id]
async def init(self): async def init(self):
FiregexTables().init() nft.init()
await self.reload() await self.reload()
async def reload(self): async def reload(self):
async with self.lock: async with self.lock:
for srv in self.db.query('SELECT * FROM services;'): for srv in self.db.query('SELECT * FROM services;'):
srv = Service.from_dict(srv) srv = Service.from_dict(srv)
if srv.id in self.proxy_table: if srv.id in self.service_table:
continue continue
self.proxy_table[srv.id] = ServiceManager(srv, self.db) self.service_table[srv.id] = ServiceManager(srv, self.db)
await self.proxy_table[srv.id].next(srv.status) await self.service_table[srv.id].next(srv.status)
def get(self,srv_id): def get(self,srv_id):
if srv_id in self.proxy_table: if srv_id in self.service_table:
return self.proxy_table[srv_id] return self.service_table[srv_id]
else: else:
raise ServiceNotFoundException() raise ServiceNotFoundException()
@@ -94,13 +97,13 @@ class ServiceManager:
async def start(self): async def start(self):
if not self.interceptor: if not self.interceptor:
delete_by_srv(self.srv) nft.delete(self.srv)
self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) self.interceptor = await FiregexInterceptor.start(self.srv)
await self._update_filters_from_db() await self._update_filters_from_db()
self._set_status(STATUS.ACTIVE) self._set_status(STATUS.ACTIVE)
async def stop(self): async def stop(self):
delete_by_srv(self.srv) nft.delete(self.srv)
if self.interceptor: if self.interceptor:
await self.interceptor.stop() await self.interceptor.stop()
self.interceptor = None self.interceptor = None

View File

@@ -11,7 +11,14 @@ class Service:
@classmethod @classmethod
def from_dict(cls, var: dict): 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: class Regex:
@@ -27,4 +34,13 @@ class Regex:
@classmethod @classmethod
def from_dict(cls, var: dict): 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"]
)

View 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
}}})

View File

View 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()

View 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"]
)

View 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
}}})

View File

@@ -5,7 +5,7 @@ import sqlite3
from typing import List, Union from typing import List, Union
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
from modules.nfregex.firegex import FiregexTables from modules.nfregex.nftables import FiregexTables
from modules.nfregex.firewall import STATUS, FirewallManager from modules.nfregex.firewall import STATUS, FirewallManager
from utils.sqlite import SQLite from utils.sqlite import SQLite
from utils import ip_parse, refactor_name, refresh_frontend 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) @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""" """Get info about a specific service using his id"""
res = db.query(""" res = db.query("""
SELECT SELECT
@@ -164,21 +164,21 @@ async def get_service_by_id(service_id: str, ):
return res[0] return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel) @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""" """Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP) await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel) @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""" """Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE) await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel) @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""" """Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id) db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes 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'} return {'status': 'ok'}
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel) @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""" """Request to change the name of a specific service"""
form.name = refactor_name(form.name) form.name = refactor_name(form.name)
if not form.name: return {'status': 'The name cannot be empty!'} 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'} return {'status': 'ok'}
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel]) @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""" """Get the list of the regexes of a service"""
return db.query(""" return db.query("""
SELECT SELECT
@@ -209,7 +209,7 @@ async def get_service_regexe_list(service_id: str, ):
""", service_id) """, service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel) @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""" """Get regex info using his id"""
res = db.query(""" res = db.query("""
SELECT SELECT
@@ -221,7 +221,7 @@ async def get_regex_by_id(regex_id: int, ):
return res[0] return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel) @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""" """Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -232,7 +232,7 @@ async def regex_delete(regex_id: int, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel) @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""" """Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -242,7 +242,7 @@ async def regex_enable(regex_id: int, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel) @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""" """Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -252,7 +252,7 @@ async def regex_disable(regex_id: int, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.post('/regexes/add', response_model=StatusMessageModel) @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""" """Add a new regex"""
try: try:
re.compile(b64decode(form.regex)) re.compile(b64decode(form.regex))
@@ -269,7 +269,7 @@ async def add_new_regex(form: RegexAddForm, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddResponse) @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""" """Add a new service"""
try: try:
form.ip_int = ip_parse(form.ip_int) form.ip_int = ip_parse(form.ip_int)

View 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}

View File

@@ -100,7 +100,7 @@ async def get_service_list():
""") """)
@app.get('/service/{service_id}', response_model=ServiceModel) @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""" """Get info about a specific service using his id"""
res = db.query(""" res = db.query("""
SELECT SELECT
@@ -118,28 +118,28 @@ async def get_service_by_id(service_id: str, ):
return res[0] return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel) @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""" """Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP) await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel) @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""" """Request the pause of a specific service"""
await firewall.get(service_id).next(STATUS.PAUSE) await firewall.get(service_id).next(STATUS.PAUSE)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel) @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""" """Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE) await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel) @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""" """Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id) db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM regexes 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) @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""" """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) db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
await firewall.get(service_id).update_port() await firewall.get(service_id).update_port()
@@ -161,7 +161,7 @@ class ChangePortForm(BaseModel):
internalPort: Union[int, None] internalPort: Union[int, None]
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel) @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""" """Choose and change the ports of the service"""
if change_port.port is None and change_port.internalPort is None: if change_port.port is None and change_port.internalPort is None:
return {'status': 'Invalid Request!'} return {'status': 'Invalid Request!'}
@@ -195,7 +195,7 @@ class RegexModel(BaseModel):
active:bool active:bool
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel]) @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""" """Get the list of the regexes of a service"""
return db.query(""" return db.query("""
SELECT SELECT
@@ -205,7 +205,7 @@ async def get_service_regexe_list(service_id: str, ):
""", service_id) """, service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel) @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""" """Get regex info using his id"""
res = db.query(""" res = db.query("""
SELECT SELECT
@@ -217,7 +217,7 @@ async def get_regex_by_id(regex_id: int, ):
return res[0] return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel) @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""" """Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -227,7 +227,7 @@ async def regex_delete(regex_id: int, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel) @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""" """Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -237,7 +237,7 @@ async def regex_enable(regex_id: int, ):
return {'status': 'ok'} return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel) @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""" """Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
if len(res) != 0: if len(res) != 0:
@@ -255,7 +255,7 @@ class RegexAddForm(BaseModel):
is_case_sensitive: bool is_case_sensitive: bool
@app.post('/regexes/add', response_model=StatusMessageModel) @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""" """Add a new regex"""
try: try:
re.compile(b64decode(form.regex)) re.compile(b64decode(form.regex))
@@ -283,7 +283,7 @@ class RenameForm(BaseModel):
name:str name:str
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel) @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""" """Request to change the name of a specific service"""
form.name = refactor_name(form.name) form.name = refactor_name(form.name)
if not form.name: return {'status': 'The name cannot be empty!'} 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'} return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddStatus) @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""" """Add a new service"""
serv_id = gen_service_id(db) serv_id = gen_service_id(db)
form.name = refactor_name(form.name) form.name = refactor_name(form.name)

View File

@@ -1,7 +1,6 @@
import asyncio import asyncio
from ipaddress import ip_interface from ipaddress import ip_address, ip_interface
import os, socket, psutil import os, socket, psutil, sys, nftables
import sys
from fastapi_socketio import SocketManager from fastapi_socketio import SocketManager
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) 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): def ip_parse(ip:str):
return str(ip_interface(ip).network) return str(ip_interface(ip).network)
def addr_parse(ip:str):
return str(ip_address(ip))
def ip_family(ip:str): def ip_family(ip:str):
return "ip6" if ip_interface(ip).version == 6 else "ip" return "ip6" if ip_interface(ip).version == 6 else "ip"
@@ -47,4 +49,60 @@ def get_interfaces():
for interf in interfs: for interf in interfs:
if interf.family in [socket.AF_INET, socket.AF_INET6]: if interf.family in [socket.AF_INET, socket.AF_INET6]:
yield {"name": int_name, "addr":interf.address} yield {"name": int_name, "addr":interf.address}
return list(_get_interfaces()) 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"]

View File

@@ -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

View File

@@ -90,7 +90,7 @@ def load_routers(app):
resets, startups, shutdowns = [], [], [] resets, startups, shutdowns = [], [], []
for router in get_router_modules(): for router in get_router_modules():
if router.router: 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: if router.reset:
resets.append(router.reset) resets.append(router.reset)
if router.startup: if router.startup:

View File

@@ -6,11 +6,12 @@ import { Outlet, Route, Routes } from 'react-router-dom';
import MainLayout from './components/MainLayout'; import MainLayout from './components/MainLayout';
import { PasswordSend, ServerStatusResponse } from './js/models'; import { PasswordSend, ServerStatusResponse } from './js/models';
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, login, setpassword } from './js/utils'; 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 io from 'socket.io-client';
import RegexProxy from './pages/RegexProxy'; 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 ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
import PortHijack from './pages/PortHijack';
const socket = io({transports: ["websocket", "polling"], path:"/sock" }); const socket = io({transports: ["websocket", "polling"], path:"/sock" });
@@ -153,6 +154,7 @@ function App() {
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} > <Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
<Route path=":srv" element={<ServiceDetailsProxyRegex />} /> <Route path=":srv" element={<ServiceDetailsProxyRegex />} />
</Route> </Route>
<Route path="porthijack" element={<PortHijack />} />
<Route path="*" element={<HomeRedirector />} /> <Route path="*" element={<HomeRedirector />} />
</Route> </Route>
</Routes> </Routes>

View File

@@ -1,4 +1,4 @@
$primary_color: #242a33; $primary_color: #242a33;
$second_color: #1A1B1E; $secondary_color: #1A1B1E;
$third_color:#25262b; $third_color:#25262b;

View File

@@ -3,7 +3,6 @@ import React from 'react';
import style from "./index.module.scss"; import style from "./index.module.scss";
function FooterPage() { function FooterPage() {
return <Footer id="footer" height={70} className={style.footer}> return <Footer id="footer" height={70} className={style.footer}>
Made by Pwnzer0tt1 Made by Pwnzer0tt1

View File

@@ -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 { useForm } from '@mantine/hooks';
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6, getipinterfaces } from '../../js/utils'; import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
import { nfregex } from './utils'; import { nfregex } from './utils';
import PortAndInterface from '../PortAndInterface';
type ServiceAddForm = { type ServiceAddForm = {
name:string, name:string,
@@ -13,16 +14,6 @@ type ServiceAddForm = {
autostart: boolean, 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 }) { function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
const form = useForm({ 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 = () =>{ const close = () =>{
onClose() onClose()
form.reset() form.reset()
@@ -85,28 +68,9 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
{...form.getInputProps('name')} {...form.getInputProps('name')}
/> />
<Space h="md" /> <Space h="md" />
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
<Autocomplete
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
placeholder="10.1.1.0/24"
itemComponent={AutoCompleteItem}
data={ipInterfaces}
{...form.getInputProps('ip_int')}
/>
<Space h="md" /> <Space h="md" />
<NumberInput
placeholder="8080"
min={1}
max={65535}
label="Public Service port"
{...form.getInputProps('port')}
/>
<Space h="md" />
<div className='center-flex'> <div className='center-flex'>
<Switch <Switch
label="Auto-Start Service" label="Auto-Start Service"

View File

@@ -42,7 +42,7 @@ export default function NavBar({ closeNav, opened }: {closeNav: () => void, open
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs"> <Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} /> <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="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.Section>
</Navbar> </Navbar>

View 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>
</>
}

View 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;

View File

@@ -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;

View 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;

View 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;
}

View 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;

View 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;
}
}

View 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

View File

@@ -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 { useForm } from '@mantine/hooks';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { okNotify } from '../../js/utils'; import { okNotify } from '../../js/utils';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
import { regexproxy } from './utils'; import { regexproxy } from './utils';
import PortInput from '../PortInput';
type ServiceAddForm = { type ServiceAddForm = {
name:string, name:string,
@@ -67,20 +68,16 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
/> />
<Space h="md" /> <Space h="md" />
<NumberInput <PortInput
placeholder="8080" fullWidth
min={1}
max={65535}
label="Public Service port" label="Public Service port"
{...form.getInputProps('port')} {...form.getInputProps('port')}
/> />
{form.values.chosenInternalPort?<> {form.values.chosenInternalPort?<>
<Space h="md" /> <Space h="md" />
<NumberInput <PortInput
placeholder="8080" fullWidth
min={1}
max={65535}
label="Internal Proxy Port" label="Internal Proxy Port"
{...form.getInputProps('internalPort')} {...form.getInputProps('internalPort')}
/> />

View File

@@ -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 { useForm } from '@mantine/hooks';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
import { FaLongArrowAltDown } from 'react-icons/fa'; import { FaLongArrowAltDown } from 'react-icons/fa';
import { regexproxy, Service } from '../utils'; import { regexproxy, Service } from '../utils';
import { okNotify } from '../../../js/utils'; import { okNotify } from '../../../js/utils';
import PortInput from '../../PortInput';
type InputForm = { type InputForm = {
internalPort:number, internalPort:number,
@@ -58,27 +59,21 @@ function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:
return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered> return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}> <form onSubmit={form.onSubmit(submitRequest)}>
<PortInput
fullWidth
<NumberInput
placeholder="30001"
min={1}
max={65535}
label="Internal Proxy Port" label="Internal Proxy Port"
{...form.getInputProps('internalPort')} {...form.getInputProps('internalPort')}
/> />
<Space h="xl" /> <Space h="xl" />
<Center><FaLongArrowAltDown size={50}/></Center> <Center><FaLongArrowAltDown size={50}/></Center>
<NumberInput <PortInput
placeholder="8080" fullWidth
min={1}
max={65535}
label="Public Service Port" label="Public Service Port"
{...form.getInputProps('port')} {...form.getInputProps('port')}
/> />
<Space h="xl" /> <Space h="xl" />
<Center><Title order={5}>The change of the ports will cause a temporarily shutdown of the service! </Title></Center> <Center><Title order={5}>The change of the ports will cause a temporarily shutdown of the service! </Title></Center>

View File

@@ -11,7 +11,9 @@ var Buffer = require('buffer').Buffer
export const eventUpdateName = "update-info" 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_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>{ export async function getapi(path:string):Promise<any>{

View 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;

View File

@@ -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('--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('--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('--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('--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() args = parser.parse_args()
os.chdir(os.path.dirname(os.path.realpath(__file__))) 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"): 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) puts("This is not a clone of firegex, to build firegex the clone of the repository is needed!", color=colors.red)
exit() exit()
@@ -40,14 +44,14 @@ if args.build and not os.path.isfile("./Dockerfile"):
if args.threads < 1: if args.threads < 1:
args.threads = multiprocessing.cpu_count() args.threads = multiprocessing.cpu_count()
if not args.stop: if start_operation:
sep() sep()
puts(f"Firegex", color=colors.yellow, end="") puts(f"Firegex", color=colors.yellow, end="")
puts(" will start on port ", end="") puts(" will start on port ", end="")
puts(f"{args.port}", color=colors.cyan) puts(f"{args.port}", color=colors.cyan)
psw_set = None psw_set = None
if not args.stop: if start_operation:
if args.psw_no_interactive: if args.psw_no_interactive:
psw_set = args.psw_no_interactive psw_set = args.psw_no_interactive
elif not args.startup_psw: elif not args.startup_psw:
@@ -100,7 +104,10 @@ services:
sep() sep()
if not args.no_autostart: if not args.no_autostart:
try: 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) puts("Running 'docker-compose down'\n", color=colors.green)
os.system("docker-compose -p firegex down") os.system("docker-compose -p firegex down")
else: else:

View File

@@ -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) else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
#Create server #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): def exit_test(code):
if service_id: if service_id:
server.stop() server.stop()
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green) 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) exit(code)
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" ) 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
View 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)

View File

@@ -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)): if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
puts(f"The malicious request was successfully blocked ✔", color=colors.green) puts(f"The malicious request was successfully blocked ✔", color=colors.green)
n_blocked += 1 n_blocked += 1
time.sleep(0.5)
if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked: if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked:
puts(f"The packed was reported as blocked ✔", color=colors.green) puts(f"The packed was reported as blocked ✔", color=colors.green)
else: 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) 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) 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) exit_test(0)

View File

@@ -11,4 +11,12 @@ python3 nf_test.py -p testpassword -m udp
echo "Running Netfilter Regex UDP ipv6" echo "Running Netfilter Regex UDP ipv6"
python3 nf_test.py -p testpassword -m udp -6 python3 nf_test.py -p testpassword -m udp -6
echo "Running Proxy Regex" echo "Running Proxy Regex"
python3 px_test.py -p testpassword 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

View File

@@ -1,3 +1,4 @@
import string
from requests import Session from requests import Session
def verify(req): def verify(req):
@@ -36,7 +37,7 @@ class FiregexAPI:
def status(self): def status(self):
return self.s.get(f"{self.address}api/status").json() 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}") req = self.s.post(f"{self.address}api/login", data=f"username=login&password={password}")
try : try :
self.s.set_token(req.json()["access_token"]) self.s.set_token(req.json()["access_token"])
@@ -48,7 +49,7 @@ class FiregexAPI:
self.s.unset_token() self.s.unset_token()
return True 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}) req = self.s.post(f"{self.address}api/set-password", json={"password":password})
if verify(req): if verify(req):
self.s.set_token(req.json()["access_token"]) self.s.set_token(req.json()["access_token"])
@@ -56,7 +57,7 @@ class FiregexAPI:
else: else:
return False 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}) req = self.s.post(f"{self.address}api/change-password", json={"password":password, "expire":expire})
if verify(req): if verify(req):
self.s.set_token(req.json()["access_token"]) self.s.set_token(req.json()["access_token"])
@@ -68,11 +69,11 @@ class FiregexAPI:
req = self.s.get(f"{self.address}api/interfaces") req = self.s.get(f"{self.address}api/interfaces")
return req.json() return req.json()
def reset(self, delete): def reset(self, delete: bool):
req = self.s.post(f"{self.address}api/reset", json={"delete":delete}) req = self.s.post(f"{self.address}api/reset", json={"delete":delete})
#Netfilter regex #Netfilter regex
def nf_get_stats(): def nf_get_stats(self):
req = self.s.get(f"{self.address}api/nfregex/stats") req = self.s.get(f"{self.address}api/nfregex/stats")
return req.json() return req.json()
@@ -80,43 +81,43 @@ class FiregexAPI:
req = self.s.get(f"{self.address}api/nfregex/services") req = self.s.get(f"{self.address}api/nfregex/services")
return req.json() 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}") req = self.s.get(f"{self.address}api/nfregex/service/{service_id}")
return req.json() 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") req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/stop")
return verify(req) 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") req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/start")
return verify(req) 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") req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/delete")
return verify(req) 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}) req = self.s.post(f"{self.address}api/nfregex/service/{service_id}/rename" , json={"name":newname})
return verify(req) 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") req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/regexes")
return req.json() 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}") req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}")
return req.json() 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") req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/delete")
return verify(req) 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") req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/enable")
return verify(req) 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") req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
return verify(req) return verify(req)
@@ -131,7 +132,7 @@ class FiregexAPI:
return req.json()["service_id"] if verify(req) else False return req.json()["service_id"] if verify(req) else False
#Proxy regex #Proxy regex
def px_get_stats(): def px_get_stats(self):
req = self.s.get(f"{self.address}api/regexproxy/stats") req = self.s.get(f"{self.address}api/regexproxy/stats")
return req.json() return req.json()
@@ -139,54 +140,54 @@ class FiregexAPI:
req = self.s.get(f"{self.address}api/regexproxy/services") req = self.s.get(f"{self.address}api/regexproxy/services")
return req.json() 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}") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}")
return req.json() 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/stop")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/pause")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/start")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/delete")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regen-port")
return verify(req) 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 = {} payload = {}
if port: payload["port"] = port if port: payload["port"] = port
if internalPort: payload["internalPort"] = internalPort if internalPort: payload["internalPort"] = internalPort
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload) req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload)
return req.json() if verify(req) else False 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") req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regexes")
return req.json() 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}") req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}")
return req.json() 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") req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/delete")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/enable")
return verify(req) 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") req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/disable")
return verify(req) return verify(req)
@@ -195,15 +196,49 @@ class FiregexAPI:
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive}) json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
return verify(req) 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}) req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/rename" , json={"name":newname})
return verify(req) 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 = {}
payload["name"] = name payload["name"] = name
payload["port"] = port payload["port"] = port
if internalPort: if internalPort:
payload["internalPort"] = internalPort payload["internalPort"] = internalPort
req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload) req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload)
return req.json()["id"] if verify(req) else False 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

View File

@@ -2,7 +2,7 @@ from multiprocessing import Process
import socket import socket
class UdpServer: class UdpServer:
def __init__(self,port,ipv6): def __init__(self,port,ipv6, proxy_port = None):
def _startServer(port): def _startServer(port):
sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -15,6 +15,7 @@ class UdpServer:
self.ipv6 = ipv6 self.ipv6 = ipv6
self.port = port self.port = port
self.proxy_port = proxy_port
self.server = Process(target=_startServer,args=[port]) self.server = Process(target=_startServer,args=[port])
def start(self): def start(self):
@@ -26,7 +27,7 @@ class UdpServer:
def sendCheckData(self,data): def sendCheckData(self,data):
s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2) 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: try:
received_data = s.recvfrom(432) received_data = s.recvfrom(432)
except Exception: except Exception: