Port hijack changes and start writing frontend

This commit is contained in:
DomySh
2022-08-12 11:55:07 +00:00
parent 0193f2efc8
commit 25d2cfd562
18 changed files with 653 additions and 56 deletions

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

@@ -1,6 +1,6 @@
from typing import List from typing import List
from modules.nfregex.models import Service from modules.nfregex.models import Service
from utils import ip_parse, ip_family, NFTableManager from utils import ip_parse, ip_family, NFTableManager, nftables_int_to_json
class FiregexFilter: class FiregexFilter:
def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int): def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
@@ -52,10 +52,6 @@ class FiregexTables(NFTableManager):
for ele in self.get(): for ele in self.get():
if ele.__eq__(srv): return if ele.__eq__(srv): return
ip_int = ip_parse(srv.ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
init, end = queue_range_output init, end = queue_range_output
if init > end: init, end = end, init if init > end: init, end = end, init
@@ -64,7 +60,7 @@ class FiregexTables(NFTableManager):
"table": self.table_name, "table": self.table_name,
"chain": self.output_chain, "chain": self.output_chain,
"expr": [ "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': 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)}}, {'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"]}} {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
] ]
@@ -77,7 +73,7 @@ class FiregexTables(NFTableManager):
"table": self.table_name, "table": self.table_name,
"chain": self.input_chain, "chain": self.input_chain,
"expr": [ "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': 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)}}, {'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"]}} {"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
] ]

View File

@@ -64,8 +64,8 @@ class ServiceManager:
nft.delete(self.srv) nft.delete(self.srv)
self._set_status(False) self._set_status(False)
async def change_port(self, new_port): async def refresh(self, srv:Service):
self.srv.proxy_port = new_port self.srv = srv
if self.active: await self.restart() if self.active: await self.restart()
def _set_status(self,active): def _set_status(self,active):

View File

@@ -1,13 +1,23 @@
class Service: class Service:
def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_int: str): 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.service_id = service_id
self.active = active self.active = active
self.public_port = public_port self.public_port = public_port
self.proxy_port = proxy_port self.proxy_port = proxy_port
self.name = name self.name = name
self.proto = proto self.proto = proto
self.ip_int = ip_int self.ip_src = ip_src
self.ip_dst = ip_dst
@classmethod @classmethod
def from_dict(cls, var: dict): 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_int=var["ip_int"]) return cls(
service_id=var["service_id"],
active=var["active"],
public_port=var["public_port"],
proxy_port=var["proxy_port"],
name=var["name"],
proto=var["proto"],
ip_src=var["ip_src"],
ip_dst=var["ip_dst"]
)

View File

@@ -1,21 +1,22 @@
from typing import List from typing import List
from modules.porthijack.models import Service from modules.porthijack.models import Service
from utils import ip_parse, ip_family, NFTableManager from utils import addr_parse, ip_parse, ip_family, NFTableManager, nftables_json_to_int
class FiregexHijackRule(): class FiregexHijackRule():
def __init__(self, proto:str, public_port:int,proxy_port:int, ip_int:str, target:str, id:int): 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.id = id
self.target = target self.target = target
self.proto = proto self.proto = proto
self.public_port = public_port self.public_port = public_port
self.proxy_port = proxy_port self.proxy_port = proxy_port
self.ip_int = str(ip_int) self.ip_src = str(ip_src)
self.ip_dst = str(ip_dst)
def __eq__(self, o: object) -> bool: def __eq__(self, o: object) -> bool:
if isinstance(o, FiregexHijackRule): if isinstance(o, FiregexHijackRule):
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) 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): elif isinstance(o, Service):
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
return False return False
class FiregexTables(NFTableManager): class FiregexTables(NFTableManager):
@@ -54,17 +55,15 @@ class FiregexTables(NFTableManager):
for ele in self.get(): for ele in self.get():
if ele.__eq__(srv): return if ele.__eq__(srv): return
ip_int = ip_parse(srv.ip_int)
ip_addr = str(ip_int).split("/")[0]
ip_addr_cidr = int(str(ip_int).split("/")[1])
self.cmd({ "insert":{ "rule": { self.cmd({ "insert":{ "rule": {
"family": "inet", "family": "inet",
"table": self.table_name, "table": self.table_name,
"chain": self.prerouting_porthijack, "chain": self.prerouting_porthijack,
"expr": [ "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': 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)}}, {'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': 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": { self.cmd({ "insert":{ "rule": {
@@ -72,9 +71,10 @@ class FiregexTables(NFTableManager):
"table": self.table_name, "table": self.table_name,
"chain": self.postrouting_porthijack, "chain": self.postrouting_porthijack,
"expr": [ "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': 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)}}, {'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': 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)}}
] ]
}}}) }}})
@@ -82,18 +82,15 @@ class FiregexTables(NFTableManager):
def get(self) -> List[FiregexHijackRule]: def get(self) -> List[FiregexHijackRule]:
res = [] res = []
for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]): for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
ip_int = None filter["expr"][0]["match"]["right"]
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(FiregexHijackRule( res.append(FiregexHijackRule(
target=filter["chain"], target=filter["chain"],
id=int(filter["handle"]), id=int(filter["handle"]),
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"], 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"], 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"], proxy_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"],
ip_int=ip_int 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 return res

View File

@@ -3,8 +3,9 @@ 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.porthijack.models import Service
from utils.sqlite import SQLite from utils.sqlite import SQLite
from utils import ip_parse, refactor_name, refresh_frontend from utils import addr_parse, ip_family, refactor_name, refresh_frontend
from utils.models import ResetRequest, StatusMessageModel from utils.models import ResetRequest, StatusMessageModel
from modules.porthijack.nftables import FiregexTables from modules.porthijack.nftables import FiregexTables
from modules.porthijack.firewall import FirewallManager from modules.porthijack.firewall import FirewallManager
@@ -16,7 +17,8 @@ class ServiceModel(BaseModel):
proxy_port: int proxy_port: int
name: str name: str
proto: str proto: str
ip_int: str ip_src: str
ip_dst: str
class RenameForm(BaseModel): class RenameForm(BaseModel):
name:str name:str
@@ -26,7 +28,8 @@ class ServiceAddForm(BaseModel):
public_port: int public_port: int
proxy_port: int proxy_port: int
proto: str proto: str
ip_int: str ip_src: str
ip_dst: str
class ServiceAddResponse(BaseModel): class ServiceAddResponse(BaseModel):
status:str status:str
@@ -41,15 +44,15 @@ db = SQLite('db/port-hijacking.db', {
'services': { 'services': {
'service_id': 'VARCHAR(100) PRIMARY KEY', 'service_id': 'VARCHAR(100) PRIMARY KEY',
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))', 'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536) UNIQUE', '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)', 'proxy_port': 'INT NOT NULL CHECK(proxy_port > 0 and proxy_port < 65536 and proxy_port != public_port)',
'name': 'VARCHAR(100) NOT NULL UNIQUE', 'name': 'VARCHAR(100) NOT NULL UNIQUE',
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))', 'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
'ip_int': 'VARCHAR(100) NOT NULL', 'ip_src': 'VARCHAR(100) NOT NULL',
'ip_dst': 'VARCHAR(100) NOT NULL',
}, },
'QUERY':[ 'QUERY':[
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_int, proto);", "CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_src, proto);"
""
] ]
}) })
@@ -96,12 +99,12 @@ async def get_general_stats():
@app.get('/services', response_model=List[ServiceModel]) @app.get('/services', response_model=List[ServiceModel])
async def get_service_list(): async def get_service_list():
"""Get the list of existent firegex services""" """Get the list of existent firegex services"""
return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_int FROM 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) @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("SELECT service_id, active, public_port, proxy_port, name, proto, ip_int FROM services WHERE service_id = ?;", service_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!") if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0] return res[0]
@@ -139,17 +142,30 @@ async def service_rename(service_id: str, form: RenameForm):
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
class ChangePortRequest(BaseModel): class ChangeDestination(BaseModel):
ip_dst: str
proxy_port: int proxy_port: int
@app.post('/service/{service_id}/changeport', response_model=StatusMessageModel) @app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
async def service_changeport(service_id: str, form: ChangePortRequest): async def service_change_destination(service_id: str, form: ChangeDestination):
"""Request to change the proxy port of a specific service""" """Request to change the proxy destination of the service"""
try: try:
db.query('UPDATE services SET proxy_port=? WHERE service_id = ?;', form.proxy_port, service_id) 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: except sqlite3.IntegrityError:
return {'status': 'Invalid proxy port or service'} return {'status': 'Invalid proxy port or service'}
await firewall.get(service_id).change_port(form.proxy_port)
srv.ip_dst = form.ip_dst
srv.proxy_port = form.proxy_port
await firewall.get(service_id).refresh(srv)
await refresh_frontend() await refresh_frontend()
return {'status': 'ok'} return {'status': 'ok'}
@@ -157,18 +173,24 @@ async def service_changeport(service_id: str, form: ChangePortRequest):
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_src = addr_parse(form.ip_src)
form.ip_dst = addr_parse(form.ip_dst)
except ValueError: except ValueError:
return {"status":"Invalid address"} 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"]: if form.proto not in ["tcp", "udp"]:
return {"status":"Invalid protocol"} return {"status":"Invalid protocol"}
srv_id = None srv_id = None
try: try:
srv_id = gen_service_id() srv_id = gen_service_id()
db.query("INSERT INTO services (service_id, active, public_port, proxy_port, name, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?, ?)", 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_int) srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_src, form.ip_dst)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'This type of service already exists'} return {'status': 'This type of service already exists'}
await firewall.reload() await firewall.reload()
await refresh_frontend() await refresh_frontend()
return {'status': 'ok', 'service_id': srv_id} return {'status': 'ok', 'service_id': srv_id}

View File

@@ -1,5 +1,5 @@
import asyncio import asyncio
from ipaddress import ip_interface from ipaddress import ip_address, ip_interface
import os, socket, psutil, sys, nftables import os, socket, psutil, sys, nftables
from fastapi_socketio import SocketManager from fastapi_socketio import SocketManager
@@ -37,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"
@@ -48,6 +51,18 @@ def get_interfaces():
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): class Singleton(object):
__instance = None __instance = None
def __new__(class_, *args, **kwargs): def __new__(class_, *args, **kwargs):

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

@@ -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,155 @@
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Autocomplete, AutocompleteItem } from '@mantine/core';
import { useForm } from '@mantine/hooks';
import React, { useEffect, useState } from 'react';
import { okNotify, regex_ipv4, regex_ipv6, getipinterfaces } from '../../js/utils';
import { ImCross } from "react-icons/im"
import { porthijack } from './utils';
type ServiceAddForm = {
name:string,
public_port:number,
proxy_port:number,
proto:string,
ip_int:string,
autostart: boolean,
}
interface ItemProps extends AutocompleteItem {
label: string;
}
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
( <b>{label}</b> ) -{">"} <b>{value}</b>
</div>
);
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
const form = useForm({
initialValues: {
name:"",
public_port:80,
proxy_port:8080,
proto:"tcp",
ip_int:"",
autostart: true,
},
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_int: (value) => value.match(regex_ipv6)?true:false || value.match(regex_ipv4)?true:false
}
})
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
useEffect(()=>{
getipinterfaces().then(data => {
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
})
},[])
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_int }:ServiceAddForm) =>{
setSubmitLoading(true)
porthijack.servicesadd({name, proxy_port, public_port, proto, ip_int }).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" />
<div className='center-flex' style={{width:"100%"}}>
<Autocomplete
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
placeholder="10.1.1.0/24"
itemComponent={AutoCompleteItem}
data={ipInterfaces}
{...form.getInputProps('ip_int')}
/>
<Space w="sm" />:<Space w="sm" />
<NumberInput
placeholder="80"
min={1}
max={65535}
label="Public Service port"
{...form.getInputProps('public_port')}
/>
</div>
<Space h="md" />
<NumberInput
placeholder="8080"
min={1}
max={65535}
label="Redirect to port"
{...form.getInputProps('proxy_port')}
/>
<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,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.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.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,18 @@
@use "../../../index.scss" as *;
.row{
width: 95%;
padding: 30px 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;
}

View File

@@ -0,0 +1,156 @@
import { ActionIcon, Badge, Divider, Grid, MediaQuery, 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 { MdOutlineArrowForwardIos } from "react-icons/md"
import style from "./index.module.scss";
import YesNoModal from '../../YesNoModal';
import { errorNotify, okNotify, regex_ipv4 } from '../../../js/utils';
import { BsTrashFill } from 'react-icons/bs';
import { BiRename } from 'react-icons/bi'
import RenameForm from './RenameForm';
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
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 stopService = async () => {
setButtonLoading(true)
await porthijack.servicestop(service.service_id).then(res => {
if(!res){
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`)
}else{
errorNotify(`An error as occurred during the stopping of the service ${service.port}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the stopping of the service ${service.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.port} has been started!`)
}else{
errorNotify(`An error as occurred during the starting of the service ${service.port}`,`Error: ${res}`)
}
}).catch(err => {
errorNotify(`An error as occurred during the starting of the service ${service.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 <>
<Grid className={style.row} justify="flex-end" style={{width:"100%"}}>
<Grid.Col md={4} xs={12}>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}><div>
<div className="center-flex-row">
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.port}</Badge></div>
<Badge color={status_color} radius="sm" size="lg" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
</div>
</div></MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}><div>
<div className="center-flex">
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.port}</Badge></div>
<Badge style={{marginLeft:"20px"}} color={status_color} radius="sm" size="lg" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
<Space w="xl" />
</div>
</div></MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<Space h="xl" />
</MediaQuery>
</Grid.Col>
<Grid.Col className="center-flex" md={8} xs={12}>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' />
</MediaQuery>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
<div className="center-flex-row">
<Badge color={service.ip_int.match(regex_ipv4)?"cyan":"pink"} radius="sm" size="md" variant="filled">{service.ip_int} on {service.proto}</Badge>
</div>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<div className='flex-spacer' />
</MediaQuery>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
<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>
<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" />
{onClick?<div>
<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={45} />
<Space w="xl" />
</div>:null}
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<><Space w="xl" /><Space w="xl" /></>
</MediaQuery>
</Grid.Col>
</Grid>
<hr style={{width:"100%"}}/>
<YesNoModal
title='Are you sure to delete this service?'
description={`You are going to delete the service '${service.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}
/>
</>
}
export default ServiceRow;

View File

@@ -0,0 +1,64 @@
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,
port:number,
proto: string,
ip_int: string,
proxy_port: number,
public_port: number,
}
export type ServiceAddForm = {
name:string,
public_port:number,
proxy_port:number,
proto:string,
ip_int: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
},
changeport: async (service_id:string, proxy_port:number) => {
return await postapi(`porthijack/service/${service_id}/changeport`, {proxy_port}) as ServerResponse;
}
}

View File

@@ -0,0 +1,78 @@
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { BsPlusLg } from "react-icons/bs";
import { useNavigate } from 'react-router-dom';
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 navigator = useNavigate()
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} onClick={()=>{
navigator("/nfregex/"+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;