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

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

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;