From 3494d10032ed7a861fa2dfd6cc7ec82f2d28ebf2 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Wed, 5 Mar 2025 14:05:31 +0100 Subject: [PATCH] docs completed --- fgex-lib/firegex/nfproxy/models/http.py | 6 +- fgex-lib/firegex/nfproxy/proxysim/__init__.py | 32 +- frontend/src/components/DocsButton.tsx | 9 +- .../src/components/NFProxy/NFProxyDocs.tsx | 349 ++++++++++++++++-- frontend/src/components/NFProxy/utils.ts | 17 +- .../components/PortHijack/PortHijackDocs.tsx | 38 +- frontend/src/pages/Firewall/index.tsx | 23 +- frontend/src/pages/NFProxy/ServiceDetails.tsx | 9 +- frontend/src/pages/NFProxy/index.tsx | 22 +- frontend/src/pages/NFRegex/index.tsx | 20 +- frontend/src/pages/PortHijack/index.tsx | 21 +- 11 files changed, 452 insertions(+), 94 deletions(-) diff --git a/fgex-lib/firegex/nfproxy/models/http.py b/fgex-lib/firegex/nfproxy/models/http.py index 7ec3a3f..9cbc5f0 100644 --- a/fgex-lib/firegex/nfproxy/models/http.py +++ b/fgex-lib/firegex/nfproxy/models/http.py @@ -188,10 +188,6 @@ class InternalBasicHttpMetaClass: def content_length(self) -> int|None: return self._parser.content_length_parsed - @property - def method(self) -> str|None: - return self._parser.method_parsed - def get_header(self, header: str, default=None) -> str: return self._parser.lheaders.get(header.lower(), default) @@ -271,7 +267,7 @@ class HttpRequest(InternalBasicHttpMetaClass): @property def method(self) -> bytes: - return self._parser.method + return self._parser.method_parsed def _before_fetch_callable_checks(self, internal_data: DataStreamCtx): return internal_data.current_pkt.is_input diff --git a/fgex-lib/firegex/nfproxy/proxysim/__init__.py b/fgex-lib/firegex/nfproxy/proxysim/__init__.py index a82f755..d604cd1 100644 --- a/fgex-lib/firegex/nfproxy/proxysim/__init__.py +++ b/fgex-lib/firegex/nfproxy/proxysim/__init__.py @@ -12,6 +12,8 @@ from watchfiles import awatch, Change fake_ip_header = b"FAKE:IP:TCP:HEADERS:" fake_ip_header_len = len(fake_ip_header) +MANGLE_WARNING = True + class LogLevels: INFO = "INFO" WARNING = "WARNING" @@ -36,7 +38,7 @@ def load_level_str(level:str): def log_print(module:str, *args, level:str = LogLevels.INFO, **kwargs): return print(f"{load_level_str(level)}[deep_pink4 bold]\\[nfproxy][/][medium_orchid3 bold]\\[{escape(module)}][/]", *args, **kwargs) -async def watch_filter_file(filter_file: str, reload_action): +async def _watch_filter_file(filter_file: str, reload_action): abs_path = os.path.abspath(filter_file) directory = os.path.dirname(abs_path) # Immediately call the reload action on startup. @@ -57,7 +59,7 @@ async def watch_filter_file(filter_file: str, reload_action): except asyncio.CancelledError: log_print("observer", "Watcher cancelled, stopping.") -async def forward_and_filter(filter_ctx: dict, +async def _forward_and_filter(filter_ctx: dict, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, is_input: bool, @@ -66,6 +68,7 @@ async def forward_and_filter(filter_ctx: dict, has_to_filter: bool = True): """Asynchronously forward data from reader to writer applying filters.""" try: + has_to_drop = False while True: try: data = await reader.read(4096) @@ -73,7 +76,8 @@ async def forward_and_filter(filter_ctx: dict, break if not data: break - + if has_to_drop: + continue if has_to_filter: filter_ctx["__firegex_packet_info"] = { "data": data, @@ -128,8 +132,9 @@ async def forward_and_filter(filter_ctx: dict, continue if action == DROP.value: - log_print("drop-action", "Dropping packet can't be simulated, so the connection will be rejected", level=LogLevels.WARNING) - action = REJECT.value + log_print("drop-action", "Dropping connection caused by {escape(filter_name)} pyfilter") + has_to_drop = True + continue if action == REJECT.value: log_print("reject-action", f"Rejecting connection caused by {escape(filter_name)} pyfilter") @@ -146,9 +151,10 @@ async def forward_and_filter(filter_ctx: dict, await writer.drain() continue log_print("mangle", f"Mangling packet caused by {escape(filter_name)} pyfilter") - log_print("mangle", - "In the real execution mangling is not so stable as the simulation does, l4_data can be different by data", - level=LogLevels.WARNING) + if MANGLE_WARNING: + log_print("mangle", + "In the real execution mangling is not so stable as the simulation does, l4_data can be different by data", + level=LogLevels.WARNING) writer.write(mangled_packet[fake_ip_header_len:]) await writer.drain() continue @@ -170,7 +176,7 @@ async def forward_and_filter(filter_ctx: dict, except Exception: pass -async def handle_connection( +async def _handle_connection( reader: asyncio.StreamReader, writer: asyncio.StreamWriter, filter_code: str, target_ip: str, target_port: int, ipv6: bool): """Handle a new incoming connection and create a remote connection.""" @@ -198,8 +204,8 @@ async def handle_connection( traceback.print_exc() filter_ctx = {} # Create asynchronous tasks for bidirectional forwarding. - task1 = asyncio.create_task(forward_and_filter(filter_ctx, reader, remote_writer, True, ipv6, True, True)) - task2 = asyncio.create_task(forward_and_filter(filter_ctx, remote_reader, writer, False, ipv6, True, True)) + task1 = asyncio.create_task(_forward_and_filter(filter_ctx, reader, remote_writer, True, ipv6, True, True)) + task2 = asyncio.create_task(_forward_and_filter(filter_ctx, remote_reader, writer, False, ipv6, True, True)) try: await asyncio.gather(task1, task2) except (KeyboardInterrupt, asyncio.CancelledError): @@ -219,7 +225,7 @@ async def _execute_proxy( """Start the asyncio-based TCP proxy server.""" addr_family = socket.AF_INET6 if ipv6 else socket.AF_INET server = await asyncio.start_server( - lambda r, w: handle_connection(r, w, filter_code, target_ip, target_port, ipv6), + lambda r, w: _handle_connection(r, w, filter_code, target_ip, target_port, ipv6), local_ip, local_port, family=addr_family) log_print("listener", f"TCP proxy listening on {escape(local_ip)}:{local_port} and forwarding to -> {escape(target_ip)}:{target_port}") async with server: @@ -286,7 +292,7 @@ def run_proxy_simulation(filter_file:str, proto:str, target_ip:str, target_port: proxy_process.start() try: - asyncio.run(watch_filter_file(filter_file, reload_proxy_proc)) + asyncio.run(_watch_filter_file(filter_file, reload_proxy_proc)) except KeyboardInterrupt: pass finally: diff --git a/frontend/src/components/DocsButton.tsx b/frontend/src/components/DocsButton.tsx index 0d1ef5b..6be3537 100644 --- a/frontend/src/components/DocsButton.tsx +++ b/frontend/src/components/DocsButton.tsx @@ -1,4 +1,4 @@ -import { ActionIcon, Box, Modal, ScrollArea, Title, Tooltip } from "@mantine/core"; +import { ActionIcon, ActionIconProps, Box, Modal, ScrollArea, Title, Tooltip } from "@mantine/core"; import { useState } from "react"; import { FaBookBookmark } from "react-icons/fa6"; import { NFRegexDocs } from "./NFRegex/NFRegexDocs"; @@ -6,7 +6,6 @@ import { NFProxyDocs } from "./NFProxy/NFProxyDocs"; import { PortHijackDocs } from "./PortHijack/PortHijackDocs"; import { EnumToPrimitiveUnion } from "../js/utils"; - export enum DocType{ NFREGEX = "nfregex", NFPROXY = "nfproxy", @@ -14,12 +13,12 @@ export enum DocType{ } -export const DocsButton = ({ doc }: { doc: EnumToPrimitiveUnion }) => { +export const DocsButton = ({ doc, ...props }: { doc: EnumToPrimitiveUnion } & ActionIconProps) => { const [open, setOpen] = useState(false); return - - setOpen(true)} size="lg" radius="md" variant="filled"> + + setOpen(true)} size="lg" radius="md" variant="filled" {...props}> setOpen(false)} fullScreen title={ Firegex Docs ๐Ÿ“• diff --git a/frontend/src/components/NFProxy/NFProxyDocs.tsx b/frontend/src/components/NFProxy/NFProxyDocs.tsx index bbf0179..8f4a8a4 100644 --- a/frontend/src/components/NFProxy/NFProxyDocs.tsx +++ b/frontend/src/components/NFProxy/NFProxyDocs.tsx @@ -1,5 +1,7 @@ import { CodeHighlight } from "@mantine/code-highlight"; -import { Container, Title, Text, List, Code, Space } from "@mantine/core"; +import { Container, Title, Text, List, Code, Space, Badge, Box } from "@mantine/core"; +import { CgEditBlackPoint } from "react-icons/cg"; +import { EXAMPLE_PYFILTER } from "./utils"; const IMPORT_CODE_EXAMPLE = `from firegex.nfproxy import pyfilter, ACCEPT, REJECT` @@ -18,6 +20,46 @@ def none_filter(): # This is a filter that does nothing ` +const TYPING_ARGS_EXAMPLE = `from firegex.nfproxy import pyfilter, ACCEPT, REJECT +from firegex.nfproxy.models import HttpRequest + +@pyfilter +def filter_with_args(http_request: HttpRequest) -> int: + if http_request.body: + if b"ILLEGAL" in http_request.body: + return REJECT +` + +const IMPORT_FULL_ACTION_STREAM = `from firegex.nfproxy import FullStreamAction` + +export const HELP_NFPROXY_SIM = `โžค fgex nfproxy -h + + Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT + + Run an nfproxy simulation + +โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ * filter_file TEXT The path to the filter file [default: None] [required] โ”‚ +โ”‚ * address TEXT The address of the target to proxy [default: None] [required] โ”‚ +โ”‚ * port INTEGER The port of the target to proxy [default: None] [required] โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ --proto [tcp|http] The protocol to proxy [default: tcp] โ”‚ +โ”‚ --from-address TEXT The address of the local server [default: None] โ”‚ +โ”‚ --from-port INTEGER The port of the local server [default: 7474] โ”‚ +โ”‚ -6 Use IPv6 for the connection โ”‚ +โ”‚ --help -h Show this message and exit. โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ` + +const HttpBadge = () => { + return HTTP +} + +const TCPBadge = () => { + return TCP +} + + export const NFProxyDocs = () => { return ( @@ -30,13 +72,291 @@ export const NFProxyDocs = () => { providing users with the flexibility to upload custom filters. - โš™๏ธ How to Use Netfilter Proxy + โš™๏ธ How to use Netfilter Proxy To use Netfilter Proxy, simply create and upload a Python filter. The filter is passed to the C++ binary, which then processes packets using the provided logic. This allows you to tailor the filtering behavior to your needs. - + ๐Ÿ’ก How to write pyfilters? + + First of all install the firegex lib and update it running pip install -U fgex. + After that you can use firegex module. + + With this code we imported the pyfilter decorator and the ACCEPT and REJECT statements.
+ Let's create a first (useless) filter to see the syntax: + + You see that the filter must be decorated with the pyfilter decorator and must return a statement about how to manage that packet. +
+ You can save every data about the current flow in the global variables, the code you write will be executed only once for flow. The globals variables are isolated between flows. + For each packet the filter functions will be called with the required paramethers and using the same globals as before. +
+ Saving data in globals of other modules is not recommended, because that memory is shared by the flows managed by the same thread and lead to unexpected behaviors. +
+ Global variables that starts with '__firegex' are reserved for internal use, don't use them. +
+ You can manage when the function is called and also getting some data specifying some paramethers, using type decorators. + Default values of the paramethers will be ignored, also kvargs values will be ignored. +
+ Functions with no type decorator are considered invalid pyfilters! +
+ + In this code we are filtering all the http requests that contains the word "ILLEGAL" in the body. All the other packets will be accepted (default behavior). + The function will be called only if at least internally teh HTTP request header has been parsed, and also when the body will be parsed. +
+ If we have multiple paramether, the function will be called only if with the packet arrived is possible to build all the paramethers. +
+ ๐Ÿ”ง How can I test the filter? + + You can test your filter by using fgex command installed by firegex lib: This will run a local proxy to a remote destination with the filter you specified. +
+ This can be done by running for instance: fgex nfproxy test_http.py 127.0.0.1 8080 --proto http + + You don't need to restart the proxy every time you change the filter, the filter will be reloaded automatically. +
+ ๐Ÿ“ฆ Packet Statements + + Here there are all the statments you can return from a filter: + + ACCEPT: The packet will be accepted and forwarded to the destination. (default if None is returned) + REJECT: The connection will be closed and all the packets will be dropped. + DROP: This packet and all the following will be dropped. (This not simulate a connection closure) + UNSTABLE_MANGLE: The packet will be modified and forwarded. You can edit the packet only with RawPacket data handler. (This is an unstable statement, use it carefully) + + + โš™๏ธ Data Structures + + Here there are all the data structure you can use for your filters: + + + <CgEditBlackPoint style={{marginBottom: -3}}/> RawPacket + + This data is the raw packet processed by nfqueue. It contains: + + + + + data: The raw packet data assembled by libtins (read only). + + + is_input: It's true if the packet is incoming, false if it's outgoing. (read only) + + + is_ipv6: It's true if the packet is IPv6, false if it's IPv4. (read only) + + + is_tcp: It's true if the packet is TCP, false if it's UDP. (read only) + + + l4_size: The size of l4 payload (read only) + + + raw_packet_header_len: The size of the raw packet header (read only) + + + raw_packet: The raw packet data with ip and TCP header. You can edit all the packet content and it will be modified if you send + the UNSTABLE_MANGLE statement. Be careful, beacause the associated layer 4 data can be different from 'data' filed that instead arrives from libtins. + When you edit this field, l4_size and l4_data will be updated automatically. + + + l4_data: The l4 payload data, directly taken by the raw packet. You can edit all the packet content and it will be modified if you send + the UNSTABLE_MANGLE statement. Be careful, beacause the associated layer 4 data can be different from 'data' filed that instead arrives from libtins. When you edit this field, l4_size and raw_packet will be updated automatically. + + + + + <CgEditBlackPoint style={{marginBottom: -3}}/> TCPInputStream (alias: TCPClientStream) + + This data is the TCP input stream: this handler is called only on is_input=True packets. The filters that handles this data will be called only in this case. + + + + + data: The entire stream in input direction. (read only) + + + total_stream_size: The size of the entire stream in input direction. (read only) + + + is_ipv6: It's true if the stream is IPv6, false if it's IPv4. (read only) + + + + + <CgEditBlackPoint style={{marginBottom: -3}}/> TCPOutputStream (alias TCPServerStream) + + This data is the TCP output stream: this handler is called only on is_input=False packets. The filters that handles this data will be called only in this case. + + + + + data: The entire stream in output direction. (read only) + + + total_stream_size: The size of the entire stream in output direction. (read only) + + + is_ipv6: It's true if the stream is IPv6, false if it's IPv4. (read only) + + + + + <CgEditBlackPoint style={{marginBottom: -3}}/> HttpRequest + + This data is the Http request processed by nfqueue. This handler can be called twice per request: once when the http headers are complete, and once when the body is complete. + If the http data arrives in 1 single TCP packet, this handler will be called once + + + + + url: The url of the request (read only) + + + headers: The headers of the request (read only). The keys and values are exactly the same as the original request (case sensitive). + + + get_header(key:str, default = None): A function that returns the value of a header: it matches the key without case sensitivity. If the header is not found, it returns the default value. + + + user_agent: The user agent of the request (read only) + + + content_encoding: The content encoding of the request (read only) + + + content_length: The content length of the request (read only) + + + body: The body of the request (read only). It's None if the body has not arrived yet. + + + http_version: The http version of the request (read only) + + + keep_alive: It's true if the connection was marked for keep alive, false if it's not. (read only) + + + should_upgrade: It's true if the connection should be upgraded, false if it's not. (read only) + + + method: The method of the request (read only) + + + has_begun: It's true if the request has begun, false if it's not. (read only) + + + headers_complete: It's true if the headers are complete, false if they are not. (read only) + + + message_complete: It's true if the message is complete, false if it's not. (read only) + + + total_size: The size of the entire http request (read only) + + + stream: It's the buffer that contains the stream of the websocket traffic in input. This is used only if should_upgrade is True. (read only) + + + + + <CgEditBlackPoint style={{marginBottom: -3}}/> HttpRequestHeader + + Same as HttpRequest, but this handler is called only when the headers are complete and body is not buffered. Body will always be None + + <CgEditBlackPoint style={{marginBottom: -3}}/> HttpResponse + + This data is the Http response processed by nfqueue. This handler can be called twice per response: once when the http headers are complete, and once when the body is complete. + If the http data arrives in 1 single TCP packet, this handler will be called once + + + + + url: The url of the response (read only) + + + headers: The headers of the response (read only). The keys and values are exactly the same as the original response (case sensitive). + + + get_header(key:str, default = None): A function that returns the value of a header: it matches the key without case sensitivity. If the header is not found, it returns the default value. + + + user_agent: The user agent of the response (read only) + + + content_encoding: The content encoding of the response (read only) + + + content_length: The content length of the response (read only) + + + body: The body of the response (read only). It's None if the body has not arrived yet. + + + http_version: The http version of the response (read only) + + + keep_alive: It's true if the connection was marked for keep alive, false if it's not. (read only) + + + should_upgrade: It's true if the connection should be upgraded, false if it's not. (read only) + + + status_code: The status code of the response (read only) (int) + + + has_begun: It's true if the response has begun, false if it's not. (read only) + + + headers_complete: It's true if the headers are complete, false if they are not. (read only) + + + message_complete: It's true if the message is complete, false if it's not. (read only) + + + total_size: The size of the entire http response (read only) + + + stream: It's the buffer that contains the stream of the websocket traffic in output. This is used only if should_upgrade is True. (read only) + + + + + <CgEditBlackPoint style={{marginBottom: -3}}/> HttpResponseHeader + + Same as HttpResponse, but this handler is called only when the headers are complete and body is not buffered. Body will always be None + โš ๏ธ Stream Limiter + + What happen if in a specific TCP stream you have a lot of data? The stream limiter will be activated and some action will be taken. + You can configure the action performed by setting some option in the globals: +
+ First import the FullStreamAction enum: + + Then you can set in the globals these options: + + + FGEX_STREAM_MAX_SIZE: Sets the maximum size of the stream. If the stream exceeds this size, the FGEX_FULL_STREAM_ACTION will be performed. (this limit is applyed at the single stream related to the single data handler). + For example if TCPInputStream has reached the limit but HttpResponse has not, the action will be performed only on the TCPInputStream. The default is 1MB. + + + FGEX_FULL_STREAM_ACTION: Sets the action performed when the stream exceeds the FGEX_STREAM_MAX_SIZE. The default is FullStreamAction.FLUSH. + + + Heres will be explained every type of action you can set: + + + FLUSH: Flush the stream and continue to acquire new packets (default) + + + DROP: Drop the next stream packets - like a DROP action by filter + + + REJECT: Reject the stream and close the connection - like a REJECT action by filter + + + ACCEPT: Stops to call pyfilters and accept the traffic + + +
๐Ÿš€ How It Works The proxy is built on a multi-threaded architecture and integrates Python for dynamic filtering: @@ -71,7 +391,6 @@ export const NFProxyDocs = () => { allowing for dynamic and flexible packet handling. ๐Ÿ - HTTP Parsing: @@ -80,26 +399,12 @@ export const NFProxyDocs = () => { - - ๐Ÿ’ก How to write pyfilters? + + ๐Ÿ“š Additional Resources - First of all install the firegex lib and update it running pip install -U fgex. - After that you can use firegex module. - - With this code we imported the pyfilter decorator and the ACCEPT and REJECT constants.
- Let's create a first (useless) filter to see the syntax: - - You see that the filter must be decorated with the pyfilter decorator and must return a statement about how to manage that packet. -
- You can save every data about the current flow in the global variables, the code you write will be executed only once for flow. The globals are isolated between flows. - For each packet the filter functions will be called with the required paramethers and the same globals as before. -
- Saving data in globals of other modules is not recommended, because that memory is shared by the flows managed by the same thread and lead to unexpected behaviors. -
- Global variables that starts with __firegex_ are reserved for internal use, don't use them. - + Here's a pyfilter code commented example: +
-
); }; diff --git a/frontend/src/components/NFProxy/utils.ts b/frontend/src/components/NFProxy/utils.ts index 55ddec6..541f3c7 100644 --- a/frontend/src/components/NFProxy/utils.ts +++ b/frontend/src/components/NFProxy/utils.ts @@ -103,7 +103,7 @@ export const EXAMPLE_PYFILTER = `# This in an example of a filter file with http # From here we can import the DataTypes that we want to use: # The data type must be specified in the filter functions # And will also interally be used to decide when call some filters and how aggregate data -from firegex.nfproxy.params import RawPacket +from firegex.nfproxy.models import RawPacket # global context in this execution is dedicated to a single TCP stream # - This code will be executed once at the TCP stream start @@ -158,15 +158,18 @@ def http_filter(http:HTTPRequest): # If the stream is too big, you can specify what actions to take: # This can be done defining some variables in the global context # - FGEX_STREAM_MAX_SIZE: The maximum size of the stream in bytes (default 1MB) -# NOTE: the stream size is calculated by the sum of the dimension of the packets in the stream (both directions) +# NOTE: the stream size is calculated and managed indipendently by the data type handling system +# Only types required by at least 1 filter will be stored. # - FGEX_FULL_STREAM_ACTION: The action to do when the stream is full -# - FLUSH: Flush the stream and continue to acquire new packets (default) -# - DROP: Drop the next stream packets - like a DROP action by filter -# - REJECT: Reject the stream and close the connection - like a REJECT action by filter -# - ACCEPT: Stops to call pyfilters and accept the traffic +# - FullStreamAction.FLUSH: Flush the stream and continue to acquire new packets (default) +# - FullStreamAction.DROP: Drop the next stream packets - like a DROP action by filter +# - FullStreamAction.REJECT: Reject the stream and close the connection - like a REJECT action by filter +# - FullStreamAction.ACCEPT: Stops to call pyfilters and accept the traffic + +from firege.nfproxy import FullStreamAction # Example of a global context FGEX_STREAM_MAX_SIZE = 4096 -FGEX_FULL_STREAM_ACTION = REJECT +FGEX_FULL_STREAM_ACTION = FullStreamAction.REJECT # This could be an ideal configuration if we expect to normally have streams with a maximum size of 4KB of traffic ` diff --git a/frontend/src/components/PortHijack/PortHijackDocs.tsx b/frontend/src/components/PortHijack/PortHijackDocs.tsx index 8b62750..1ddd48b 100644 --- a/frontend/src/components/PortHijack/PortHijackDocs.tsx +++ b/frontend/src/components/PortHijack/PortHijackDocs.tsx @@ -1,9 +1,37 @@ -import { Box } from "@mantine/core" +import { CodeHighlight } from "@mantine/code-highlight" +import { Code, Container, Space, Text, Title } from "@mantine/core" +import { HELP_NFPROXY_SIM } from "../NFProxy/NFProxyDocs" export const PortHijackDocs = () => { - return - PORT - {/* TODO write me pls */} - + return + โšก๏ธ Hijack port to proxy + + ๐Ÿ“– Overview + + 'Hijack port to proxy' uses nftables to redirect the traffic from an external IP to a localhost server. + You are responsable to run and keep alive this server, that is your proxy. The original service will be accessible using loopback (127.0.0.1). + In this way you can run your custom proxy without touching the service configuration. + + + โš™๏ธ How to use Hijack port to proxy + + To use this feature, simply create your proxy, run it, than create a new service and set the proxy port and the external ip and port. + The traffic will be redirected to your proxy, that will still be able to contact the original service using loopback. + The responses of your proxy will be redirected to the original client, and teh proxy will see as the requests were made by the original client. +
+ You can use for instance the proxy simulator of nfproxy feature of firegex, and run it using nfproxy features. This will advantage you if for instance you need to mangle the traffic. + changing packets it's possible but not sure to do with nfproxy, but the simulator can change the packets normally (on PacketRaw data is always == l4_data in the simulator, check the nfproxy docs for more info) +
+ You will need to install firegex library with pip install -U fgex and than use the simulator command + + for instance: fgex nfproxy test_http.py 127.0.0.1 8080 --proto http --from-port 13377 +
+ ๐Ÿš€ How It Works + + This modules works in a simple way: this only thing done is to change the destination and source ip using nftables rules so that the kernel will see that the request was done to the proxy port, + but externaly the packets exists as connections to the original service. This mangle is done only for external packet arriving from the external ip indicated, localhost traffic won't be touched. + + +
} \ No newline at end of file diff --git a/frontend/src/pages/Firewall/index.tsx b/frontend/src/pages/Firewall/index.tsx index 3a26aa1..65a7fef 100644 --- a/frontend/src/pages/Firewall/index.tsx +++ b/frontend/src/pages/Firewall/index.tsx @@ -407,15 +407,20 @@ export const Firewall = () => { )} :<> - No rule found! Add one clicking the "+" buttons - - - - - - - -} + + + Firewall Rules allows you to use nftables but through a web interface + + Add new rules, sort it and enable the firewall: be carefull, wrong rules could also drops out firegex access + + + + + + + + + } - No filters found! Edit the proxy file, install the firegex client:<Space w="xs" /><Code mb={-4} >pip install fgex</Code> + No filters found! Create some proxy filters, install the firegex client:<Space w="xs" /><Code mb={-4} >pip install -U fgex</Code> + + Read the documentation for more information<Space w="sm" /><DocsButton doc='nfproxy'/> Then create a new filter file with the following syntax and upload it here (using the button above) - - Before upload the filter you can test it using the fgex command installed by the python lib - - :<>{filtersList.data?.map( (filterInfo) => )} } Netfilter proxy is a simulated proxy written using python with a c++ core - Filters are created using a simple python syntax, infact the first you need to do is to install the firegex lib:<Space w="xs" /><Code mb={-4} >pip install firegex</Code> + Filters are created using a simple python syntax, infact the first you need to do is to install the firegex lib:<Space w="xs" /><Code mb={-4} >pip install -U fgex</Code> Then you can create a new service and write custom filters for the service - - Below there is a description and example about how a pyfilter has to be composed (this example is replicated in every empty service) - - - Add your first service - - - setOpen(true)} size="xl" radius="md" variant="filled"> - - - - + + + setOpen(true)} size="xl" radius="md" variant="filled"> + + + + +
} } diff --git a/frontend/src/pages/NFRegex/index.tsx b/frontend/src/pages/NFRegex/index.tsx index 16c5e7c..f51ed03 100644 --- a/frontend/src/pages/NFRegex/index.tsx +++ b/frontend/src/pages/NFRegex/index.tsx @@ -70,11 +70,21 @@ function NFRegex({ children }: { children: any }) { {(services.data && services.data?.length > 0)?services.data.map( srv => { navigator("/nfregex/"+srv.service_id) - }} />):<> No services found! Add one clicking the "+" buttons - - - setOpen(true)} size="xl" radius="md" variant="filled"> - + }} />):<> + + + Netfilter Regex allows you to filter traffic using regexes + + Start a service, add your regexes and it's already done! + + + + setOpen(true)} size="xl" radius="md" variant="filled"> + + + + + } } diff --git a/frontend/src/pages/PortHijack/index.tsx b/frontend/src/pages/PortHijack/index.tsx index a0c1072..288fa63 100644 --- a/frontend/src/pages/PortHijack/index.tsx +++ b/frontend/src/pages/PortHijack/index.tsx @@ -51,11 +51,22 @@ function PortHijack() { {(services.data && services.data.length > 0) ?services.data.map( srv => ):<> - No services found! Add one clicking the "+" buttons - - - setOpen(true)} size="xl" radius="md" variant="filled"> - + + + Hjiack Port to Proxy is a feature that allows you to run your custom proxy without touch the service config + + It hijack the traffic to a secondary port, where you can run your proxy, that will still be able to contact the original service using loopback + + Start using port hijacking creating a new service and routing the traffic to your proxy not changing the original service configs + + + + setOpen(true)} size="xl" radius="md" variant="filled"> + + + + + }