From 451cbab43ccdeb1f0ab39413154ce132ec15829a Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Sun, 25 Aug 2024 00:41:15 +0200 Subject: [PATCH] add: exploits now are clickable and give some more calculated stats --- README.md | 3 +- backend/db.py | 1 - backend/models/exploit.py | 2 +- backend/routes/exploits.py | 6 +- docs/db.puml | 1 - .../src/components/ExploitDetailModal.tsx | 102 ++++++++++++++++++ frontend/src/components/ExploitsBar.tsx | 22 ++-- .../src/components/LineChartAttackView.tsx | 4 +- frontend/src/components/LineChartFlagView.tsx | 4 +- frontend/src/components/StatusIcon.tsx | 60 +++++++---- frontend/src/utils/backend_types.ts | 2 +- frontend/src/utils/types.ts | 6 +- 12 files changed, 167 insertions(+), 46 deletions(-) create mode 100644 frontend/src/components/ExploitDetailModal.tsx diff --git a/README.md b/README.md index fb96e24..e497a43 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@ FRONTEND: - General Settings (Table dimension ecc...) -- Submitter settings modal with upload and test - Edit button for [clients, service, exploit] -- Link from exploit status popup to exploit detail page +- Submitter settings modal with upload and test XFARM CLIENT: diff --git a/backend/db.py b/backend/db.py index 59d493b..978c0eb 100644 --- a/backend/db.py +++ b/backend/db.py @@ -89,7 +89,6 @@ class Exploit(ormar.Model): name: str = ormar.String(max_length=1024) language: str = ormar.String(max_length=1024, choices=list(Language), default=Language.other.value) status: str = ormar.String(max_length=1024, choices=list(ExploitStatus), default=ExploitStatus.disabled.value) - last_upadte: AwareDatetime = ormar.DateTime(timezone=True, default=datetime_now) created_at: AwareDatetime = ormar.DateTime(timezone=True, default=datetime_now) service: Service = ormar.ForeignKey(Service, related_name='exploits') created_by: Client = ormar.ForeignKey(Client, related_name='exploits_created') diff --git a/backend/models/exploit.py b/backend/models/exploit.py index 8ff81f9..371fbee 100644 --- a/backend/models/exploit.py +++ b/backend/models/exploit.py @@ -10,7 +10,7 @@ class ExploitDTO(BaseModel): name: str language: Language status: ExploitStatus|None = None - last_upadte: AwareDatetime + last_update: AwareDatetime created_at: AwareDatetime service: FkType[ServiceID] created_by: FkType[ClientID] diff --git a/backend/routes/exploits.py b/backend/routes/exploits.py index 4c9e6c1..3a60da6 100644 --- a/backend/routes/exploits.py +++ b/backend/routes/exploits.py @@ -25,7 +25,11 @@ async def elaborate_dto(ele: Exploit) -> ExploitDTO: case AttackMode.LOOP_DELAY: max_timeout += max(config.LOOP_ATTACK_DELAY, config.TICK_DURATION) ele.status = ExploitStatus.disabled if (datetime_now() - last_attack_execution.recieved_at).total_seconds() > max_timeout else ExploitStatus.active - return ExploitDTO(**ele.model_dump(mode="json"), last_execution_by = last_attack_execution.executed_by if last_attack_execution else None) + return ExploitDTO( + **ele.model_dump(mode="json"), + last_execution_by = last_attack_execution.executed_by if last_attack_execution else None, + last_update = last_attack_execution.recieved_at if last_attack_execution else ele.created_at + ) return await asyncio.gather(*[elaborate_dto(ele) for ele in await Exploit.objects.all()]) @router.post("", response_model=MessageResponse[ExploitDTO]) diff --git a/docs/db.puml b/docs/db.puml index d003c96..c4e9dcf 100644 --- a/docs/db.puml +++ b/docs/db.puml @@ -16,7 +16,6 @@ entity "**Exploit**" as exploit{ hash: string cli: boolean status: string [enum] - last_update: date created_at: date } diff --git a/frontend/src/components/ExploitDetailModal.tsx b/frontend/src/components/ExploitDetailModal.tsx new file mode 100644 index 0000000..4e6fd6c --- /dev/null +++ b/frontend/src/components/ExploitDetailModal.tsx @@ -0,0 +1,102 @@ +import { exploitsQuery, statsQuery, useClientSolver, useServiceSolverByExploitId } from "@/utils/queries"; +import { useGlobalStore } from "@/utils/stores"; +import { Box, Modal, Space, Title } from "@mantine/core" +import { showNotification } from "@mantine/notifications"; +import { useEffect } from "react"; +import { FaUser } from "react-icons/fa"; +import { MdTimer } from "react-icons/md"; +import { getDateFormatted } from "@/utils/time"; +import { FaKeyboard } from "react-icons/fa"; +import { FaGear } from "react-icons/fa6"; +import { StatusPoint } from "./ExploitsBar"; +import { attackStatusTable, flagStatusTable } from "./StatusIcon"; + +export const ExploitDetailModal = (props:{ opened:boolean, close:()=>void, exploitId:string }) => { + + if (!props.opened) return null + + const exploits = exploitsQuery() + const exploit = exploits.data?.find((exploit) => exploit.id == props.exploitId)??null + const stats = statsQuery() + const setLoading = useGlobalStore((store) => store.setLoader) + const clientSolver = useClientSolver() + const serviceSolver = useServiceSolverByExploitId() + + useEffect(() => { + if (exploits.isError){ + showNotification({ title: "Error", message: "Failed to fetch attack details", color: "red" }) + setLoading(false) + }else if (exploits.isSuccess && exploit != null){ + setLoading(false) + }else{ + setLoading(true) + } + }, [exploits.isLoading, exploit]) + + const boxWidth = 180 + + const okFlags = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.flags?.ok??0), 0)??0):0 + const invalidFlags = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.flags?.invalid??0), 0)??0):0 + const timeoutFlags = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.flags?.timeout??0), 0)??0):0 + const totFlags = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.flags?.tot??0), 0)??0):0 + + const doneAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.done??0), 0)??0):0 + const crashedAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.crashed??0), 0)??0):0 + const noFlagsAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.noflags??0), 0)??0):0 + const totAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.tot??0), 0)??0):0 + + return + + + Exploit '{exploit?.name}' 🚩 + + } size="xl" centered> + {exploit? + + Service + {serviceSolver(exploit.id)} + + + Last execution + by {exploit.last_execution_by?clientSolver(exploit.last_execution_by):"unknown"} at {exploit.last_update?getDateFormatted(exploit.last_update):"unknown"} + + + + Valid Flags + {okFlags} / {totFlags} ({totFlags!=0?(okFlags*100/totFlags).toFixed(2):0}%) + + + Invalid Flags + {invalidFlags} / {totFlags} ({totFlags!=0?(invalidFlags*100/totFlags).toFixed(2):0}%) + + + Timeouted Flags + {timeoutFlags} / {totFlags} ({totFlags!=0?(timeoutFlags*100/totFlags).toFixed(2):0}%) + + + + Done attacks + {doneAttacks} / {totAttacks} ({totAttacks!=0?(doneAttacks*100/totAttacks).toFixed(2):0}%) + + + Crashed attacks + {crashedAttacks} / {totAttacks} ({totAttacks!=0?(crashedAttacks*100/totAttacks).toFixed(2):0}%) + + + No flags attacks + {noFlagsAttacks} / {totAttacks} ({totAttacks!=0?(noFlagsAttacks*100/totAttacks).toFixed(2):0}%) + + + + Exploit created + by {clientSolver(exploit.created_by)} at {getDateFormatted(exploit.created_at)} + + + Language used + {exploit.language} + + + :null} + + +} \ No newline at end of file diff --git a/frontend/src/components/ExploitsBar.tsx b/frontend/src/components/ExploitsBar.tsx index 2396543..e506968 100644 --- a/frontend/src/components/ExploitsBar.tsx +++ b/frontend/src/components/ExploitsBar.tsx @@ -1,12 +1,15 @@ import { exploitsQuery, useClientSolver, useExtendedExploitSolver } from "@/utils/queries" import { Box, Divider, Flex, Paper, ScrollArea, Space, Tooltip } from "@mantine/core" +import { useState } from "react" import { GoDotFill } from "react-icons/go" +import { ExploitDetailModal } from "./ExploitDetailModal" export const ExploitBar = () => { const exploits = exploitsQuery() const extendedExploitSolver = useExtendedExploitSolver() const clientSolver = useClientSolver() + const [modalExploitId, setModalExploitId] = useState(null) return Launched Exploits @@ -22,13 +25,10 @@ export const ExploitBar = () => { return 1 else return b.name.localeCompare(a.name) - } ).map((expl) => - + } ).map((expl) => setModalExploitId(expl.id)} className="transparency-on-hover"> - - - + @@ -39,15 +39,19 @@ export const ExploitBar = () => { - - - + - )} + setModalExploitId(null)} exploitId={modalExploitId??""} /> +} + +export const StatusPoint = (props:{ status:"active"|"inactive"|"disabled"|undefined|null }) => { + return + + } \ No newline at end of file diff --git a/frontend/src/components/LineChartAttackView.tsx b/frontend/src/components/LineChartAttackView.tsx index f098ea2..492a5c4 100644 --- a/frontend/src/components/LineChartAttackView.tsx +++ b/frontend/src/components/LineChartAttackView.tsx @@ -66,11 +66,11 @@ export const LineChartAttackView = ({ seriesType, attackType, chartType, withCon const service_id = exploits.data?.find((exploit) => exploit.id == id)?.service if (service_id == null) return if (!result[service_id] || typeof result[service_id] == "string") result[service_id] = 0 - result[service_id] += tick.exploits[id].attacks[finalAttackStatus]??0 + result[service_id] += tick.exploits[id]?.attacks[finalAttackStatus]??0 }) } else { Object.keys(tick[finalSeries]).forEach((id) => { - result[id] = tick[finalSeries][id].attacks[finalAttackStatus]??0 + result[id] = tick[finalSeries][id]?.attacks[finalAttackStatus]??0 }) } return result diff --git a/frontend/src/components/LineChartFlagView.tsx b/frontend/src/components/LineChartFlagView.tsx index 46948d3..76749c3 100644 --- a/frontend/src/components/LineChartFlagView.tsx +++ b/frontend/src/components/LineChartFlagView.tsx @@ -66,11 +66,11 @@ export const LineChartFlagView = ({ seriesType, flagType, chartType, withControl const service_id = exploits.data?.find((exploit) => exploit.id == id)?.service if (service_id == null) return if (!result[service_id] || typeof result[service_id] == "string") result[service_id] = 0 - result[service_id] += tick.exploits[id].flags[finalFlagStatus]??0 + result[service_id] += tick.exploits[id]?.flags[finalFlagStatus]??0 }) }else{ Object.keys(tick[finalSeries]).forEach((id) => { - result[id] = tick[finalSeries][id].flags[finalFlagStatus]??0 + result[id] = tick[finalSeries][id]?.flags[finalFlagStatus]??0 }) } return result diff --git a/frontend/src/components/StatusIcon.tsx b/frontend/src/components/StatusIcon.tsx index c0b8f5a..0a33589 100644 --- a/frontend/src/components/StatusIcon.tsx +++ b/frontend/src/components/StatusIcon.tsx @@ -15,39 +15,53 @@ import { RiTimerFlashFill } from "react-icons/ri"; import { FaCheck } from "react-icons/fa"; import { RiEdit2Fill } from "react-icons/ri"; -export const FlagStatusIcon = (props: {status:FlagStatusType, disableTooltip?:boolean}) => { - - let icon_info: {color: MantineColor, icon: JSX.Element, label: string} = {color: "black", icon: <>, label: ""} - - switch (props.status) { - case "ok": - icon_info = {color: "LimeGreen", icon: , label: "Flag has been submitted successfully"} - break - case "invalid": - icon_info = { color: "red", icon: , label: "Flag has been submitted but is invalid"} - break - case "timeout": - icon_info = { color: "orange", icon: , label: "Flag is too old"} - break - case "wait": - icon_info = { color: "DeepSkyBlue", icon: , label: "Flag is pending for submission..." } - break - case "tot": - icon_info = { color: "white", icon: , label: "All flags"} - break +export const flagStatusTable = { + ok: { + name: "Ok", + color: "LimeGreen", + icon: FaFlag, + label: "Flag has been submitted successfully" + }, + invalid: { + name: "Invalid", + color: "red", + icon: ImCross, + label: "Flag has been submitted but is invalid" + }, + timeout: { + name: "Timeouted", + color: "orange", + icon: MdTimerOff, + label: "Flag is too old" + }, + wait: { + name: "Queued", + color: "DeepSkyBlue", + icon: FaUpload, + label: "Flag is pending for submission..." + }, + tot: { + name: "All", + color: "white", + icon: FaAsterisk, + label: "All attacks" } +} + +export const FlagStatusIcon = (props: {status:FlagStatusType, disableTooltip?:boolean}) => { + let icon_info = flagStatusTable[props.status] if (props.disableTooltip) return {icon_info.icon} + >{} else return {icon_info.icon} - + >{} + } export const attackStatusTable = { diff --git a/frontend/src/utils/backend_types.ts b/frontend/src/utils/backend_types.ts index eb9f7e1..48e3f45 100644 --- a/frontend/src/utils/backend_types.ts +++ b/frontend/src/utils/backend_types.ts @@ -305,7 +305,7 @@ export interface components { * Last Upadte * Format: date-time */ - last_upadte: string; + last_update: string; /** * Created At * Format: date-time diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index d693afa..d478346 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -34,9 +34,9 @@ export type TickStat = { start_time: string, end_time: string, globals: GlobalStat, - exploits: {[s:string]: GlobalStat}, - teams: {[s:string]: GlobalStat}, - clients: {[s:string]: GlobalStat} + exploits: {[s:string]: GlobalStat|undefined}, + teams: {[s:string]: GlobalStat|undefined}, + clients: {[s:string]: GlobalStat|undefined} } export type Stats = {