diff --git a/backend/app.py b/backend/app.py index 3b2129e..af9ce58 100644 --- a/backend/app.py +++ b/backend/app.py @@ -17,7 +17,7 @@ from fastapi.middleware.cors import CORSMiddleware import socketio from socketio.exceptions import ConnectionRefusedError import hashlib - +from ipaddress import ip_network, ip_address # DB init db = SQLite('db/firegex.db') sysctl = SysctlManager({ @@ -35,6 +35,51 @@ async def lifespan(app): yield await shutdown_main() +ALLOWED_NETWORKS = [ip_network(ip.strip(), strict=False) for ip in os.getenv("ALLOWED_IPS", "").split(",") if ip.strip()] +PROXY_IP_HEADER = os.getenv("PROXY_IP_HEADER", "") + +class IPFilterMiddleware: + def __init__(self, app): + self.app = app + + async def __call__(self, scope, receive, send): + if scope["type"] in ["http", "websocket"]: + if ALLOWED_NETWORKS: + client_ip = None + if PROXY_IP_HEADER: + headers = dict(scope.get("headers", [])) + header_val = headers.get(PROXY_IP_HEADER.lower().encode()) + if header_val: + client_ip = header_val.decode().split(",")[0].strip() + + if not client_ip and scope.get("client"): + client_ip = scope["client"][0] + + if client_ip: + try: + ip_obj = ip_address(client_ip) + if not any(ip_obj in net for net in ALLOWED_NETWORKS): + if scope["type"] == "http": + await send({ + "type": "http.response.start", + "status": 403, + "headers": [(b"content-type", b"text/plain")], + }) + await send({ + "type": "http.response.body", + "body": b"Forbidden", + }) + elif scope["type"] == "websocket": + await send({ + "type": "websocket.close", + "code": 1008 + }) + return + except ValueError: + pass + + await self.app(scope, receive, send) + app = FastAPI( debug=DEBUG, redoc_url=None, @@ -43,6 +88,7 @@ app = FastAPI( title="Firegex API", version=API_VERSION, ) +app.add_middleware(IPFilterMiddleware) if DEBUG: app.add_middleware( @@ -65,17 +111,23 @@ app.mount("/sock", sio_app) def APP_STATUS(): return "init" if db.get("password") is None else "run" def JWT_SECRET(): return db.get("secret") -def hash_psw(psw: str): +def _hash_psw_sync(psw: str) -> str: salt = secrets.token_hex(32) return hashlib.pbkdf2_hmac("sha256", psw.encode(), salt.encode(), 500_000).hex()+"-"+salt -def verify_psw(psw: str, hashed: str) -> bool: +async def hash_psw(psw: str) -> str: + return await asyncio.to_thread(_hash_psw_sync, psw) + +def _verify_psw_sync(psw: str, hashed: str) -> bool: psw_hash, salt = hashed.split("-") new_hashed = hashlib.pbkdf2_hmac("sha256", psw.encode(), salt.encode(), 500_000).hex() return new_hashed == psw_hash -def set_psw(psw: str): - db.put("password", hash_psw(psw)) +async def verify_psw(psw: str, hashed: str) -> bool: + return await asyncio.to_thread(_verify_psw_sync, psw, hashed) + +async def set_psw(psw: str): + db.put("password", await hash_psw(psw)) def create_access_token(data: dict): to_encode = data.copy() @@ -145,7 +197,7 @@ async def login_api(form: OAuth2PasswordRequestForm = Depends()): if form.password == "": return {"status":"Cannot insert an empty password!"} await asyncio.sleep(0.3) # No bruteforce :) - if verify_psw(form.password, db.get("password")): + if await verify_psw(form.password, db.get("password")): return {"access_token": create_access_token({"logged_in": True}), "token_type": "bearer"} raise HTTPException(406,"Wrong password!") @@ -157,7 +209,7 @@ async def set_password(form: PasswordForm): raise HTTPException(status_code=400) if form.password == "": return {"status":"Cannot insert an empty password!"} - set_psw(form.password) + await set_psw(form.password) await refresh_frontend() return {"status":"ok", "access_token": create_access_token({"logged_in": True})} @@ -173,7 +225,7 @@ async def change_password(form: PasswordChangeForm): db.put("secret", secrets.token_hex(32)) await disconnect_all() - set_psw(form.password) + await set_psw(form.password) await refresh_frontend() return {"status":"ok", "access_token": create_access_token({"logged_in": True})} diff --git a/backend/binsrc/classes/netfilter.cpp b/backend/binsrc/classes/netfilter.cpp index 5705c5a..3b7b278 100644 --- a/backend/binsrc/classes/netfilter.cpp +++ b/backend/binsrc/classes/netfilter.cpp @@ -27,7 +27,23 @@ public: PktRequest* pkt; for(;;) { queue.take(pkt); - static_cast(this)->handle_next_packet(pkt); + try { + static_cast(this)->handle_next_packet(pkt); + } catch (const std::exception& e) { + std::cerr << "[error] Exception handling packet: " << e.what() << std::endl; + if (pkt->get_action() == FilterAction::NOACTION) { + try { + pkt->accept(); + } catch (...) {} + } + } catch (...) { + std::cerr << "[error] Unknown exception handling packet" << std::endl; + if (pkt->get_action() == FilterAction::NOACTION) { + try { + pkt->accept(); + } catch (...) {} + } + } delete pkt; } } diff --git a/backend/binsrc/classes/nfqueue.cpp b/backend/binsrc/classes/nfqueue.cpp index 8bb0128..4db983c 100644 --- a/backend/binsrc/classes/nfqueue.cpp +++ b/backend/binsrc/classes/nfqueue.cpp @@ -531,13 +531,21 @@ class NfQueue { return MNL_CB_ERROR; } + struct nfqnl_msg_packet_hdr *ph = (nfqnl_msg_packet_hdr*) mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); + struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh); + + if (attr[NFQA_PAYLOAD] == nullptr) { + cerr << "[error] [NfQueue._real_queue_cb] payload not set" << endl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh_verdict = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, ntohs(nfg->res_id)); + nfq_nlmsg_verdict_put(nlh_verdict, ntohl(ph->packet_id), NF_ACCEPT); + mnl_socket_sendto(info->nl, nlh_verdict, nlh_verdict->nlmsg_len); + return MNL_CB_OK; + } + //Get Payload uint16_t plen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]); char *payload = (char *)mnl_attr_get_payload(attr[NFQA_PAYLOAD]); - - //Return result to the kernel - struct nfqnl_msg_packet_hdr *ph = (nfqnl_msg_packet_hdr*) mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); - struct nfgenmsg *nfg = (nfgenmsg *)mnl_nlmsg_get_payload(nlh); bool is_input = ntohl(mnl_attr_get_u32(attr[NFQA_MARK])) & 0x1; // == 0x1337 that is odd handle_func(new PktRequest( diff --git a/backend/routers/nfproxy.py b/backend/routers/nfproxy.py index 79c13c7..83763cc 100644 --- a/backend/routers/nfproxy.py +++ b/backend/routers/nfproxy.py @@ -3,7 +3,7 @@ import sqlite3 from fastapi import APIRouter, HTTPException from pydantic import BaseModel from modules.nfproxy.nftables import FiregexTables -from modules.nfproxy.firewall import STATUS, FirewallManager +from modules.nfproxy.firewall import STATUS, FirewallManager, ServiceNotFoundException from utils.sqlite import SQLite from utils import ip_parse, refactor_name, socketio_emit, PortType from modules.tls.service import activate_stream @@ -420,9 +420,13 @@ async def join_outstream(sid, data): """Client joins a room.""" srv = data.get("service") if srv: - room = f"nfproxy-outstream-{srv}" - await utils.socketio.enter_room(sid, room) - await utils.socketio.emit(room, firewall.get(srv).read_outstrem_buffer(), room=sid) + try: + srv_manager = firewall.get(srv) + room = f"nfproxy-outstream-{srv}" + await utils.socketio.enter_room(sid, room) + await utils.socketio.emit(room, srv_manager.read_outstrem_buffer(), room=sid) + except ServiceNotFoundException: + pass async def leave_outstream(sid, data): """Client leaves a room.""" @@ -434,9 +438,13 @@ async def join_exception(sid, data): """Client joins a room.""" srv = data.get("service") if srv: - room = f"nfproxy-exception-{srv}" - await utils.socketio.enter_room(sid, room) - await utils.socketio.emit(room, firewall.get(srv).last_exception_time, room=sid) + try: + srv_manager = firewall.get(srv) + room = f"nfproxy-exception-{srv}" + await utils.socketio.enter_room(sid, room) + await utils.socketio.emit(room, srv_manager.last_exception_time, room=sid) + except ServiceNotFoundException: + pass async def leave_exception(sid, data): """Client leaves a room.""" diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 105845e..b217d5c 100644 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -9,13 +9,14 @@ import { ImExit } from 'react-icons/im'; import ResetPasswordModal from './ResetPasswordModal'; import ResetModal from './ResetModal'; import { MenuDropDownWithButton } from '../MainLayout'; -import { useNavbarStore } from '../../js/store'; +import { useNavbarStore, useSystemStore } from '../../js/store'; function HeaderPage(props: any) { const navigator = useNavigate() const { navOpened, toggleNav } = useNavbarStore() + const { version } = useSystemStore() const logout_action = () => { logout().then(r => { @@ -48,7 +49,7 @@ function HeaderPage(props: any) { [Fi]*regex -

By Pwnzer0tt1

+

By Pwnzer0tt1 {version && {version}}

diff --git a/frontend/src/components/MainLayout.tsx b/frontend/src/components/MainLayout.tsx index 12b4811..dd592d8 100644 --- a/frontend/src/components/MainLayout.tsx +++ b/frontend/src/components/MainLayout.tsx @@ -5,16 +5,17 @@ import NavBar from './NavBar'; import HeaderPage from './Header'; import { getMainPath } from '../js/utils'; import { useLocation } from 'react-router'; -import { useNavbarStore } from '../js/store'; +import { useNavbarStore, useSessionStore } from '../js/store'; import { HiMenu } from "react-icons/hi"; function MainLayout({ children }:{ children:any }) { const { navOpened } = useNavbarStore() + const { setHomeSection } = useSessionStore() const location = useLocation() useEffect(()=>{ if (location.pathname !== "/"){ - sessionStorage.setItem('home_section', getMainPath()) + setHomeSection(getMainPath()) } },[location.pathname]) return