Skip to content

Commit

Permalink
add: exploits now are clickable and give some more calculated stats
Browse files Browse the repository at this point in the history
  • Loading branch information
domysh committed Aug 24, 2024
1 parent 91cebb5 commit 451cbab
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 46 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
1 change: 0 additions & 1 deletion backend/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 1 addition & 1 deletion backend/models/exploit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 5 additions & 1 deletion backend/routes/exploits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
1 change: 0 additions & 1 deletion docs/db.puml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ entity "**Exploit**" as exploit{
hash: string
cli: boolean
status: string [enum]
last_update: date
created_at: date
}

Expand Down
102 changes: 102 additions & 0 deletions frontend/src/components/ExploitDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -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 <Modal opened={props.opened} onClose={props.close} title={<Box className="center-flex">
<StatusPoint status={exploit?.status} />
<Space w="xs" />
<Title order={3}> Exploit '{exploit?.name}' 🚩</Title>
</Box>
} size="xl" centered>
{exploit?<Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><FaGear /><Space w="xs" />Service<Space w="xs" /></Box>
<b>{serviceSolver(exploit.id)}</b>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><FaUser /><Space w="xs" />Last execution<Space w="xs" /></Box>
<Box>by <b>{exploit.last_execution_by?clientSolver(exploit.last_execution_by):"unknown"}</b> at <b>{exploit.last_update?getDateFormatted(exploit.last_update):"unknown"}</b> </Box>
</Box>
<Space h="xs" />
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><flagStatusTable.ok.icon /><Space w="xs" />Valid Flags<Space w="xs" /></Box>
<b>{okFlags} / {totFlags} ({totFlags!=0?(okFlags*100/totFlags).toFixed(2):0}%)</b>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><flagStatusTable.invalid.icon /><Space w="xs" />Invalid Flags<Space w="xs" /></Box>
<b>{invalidFlags} / {totFlags} ({totFlags!=0?(invalidFlags*100/totFlags).toFixed(2):0}%)</b>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><flagStatusTable.timeout.icon /><Space w="xs" />Timeouted Flags<Space w="xs" /></Box>
<b>{timeoutFlags} / {totFlags} ({totFlags!=0?(timeoutFlags*100/totFlags).toFixed(2):0}%)</b>
</Box>
<Space h="xs" />
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><attackStatusTable.done.icon /><Space w="xs" />Done attacks<Space w="xs" /></Box>
<b>{doneAttacks} / {totAttacks} ({totAttacks!=0?(doneAttacks*100/totAttacks).toFixed(2):0}%)</b>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><attackStatusTable.crashed.icon /><Space w="xs" />Crashed attacks<Space w="xs" /></Box>
<b>{crashedAttacks} / {totAttacks} ({totAttacks!=0?(crashedAttacks*100/totAttacks).toFixed(2):0}%)</b>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><attackStatusTable.noflags.icon /><Space w="xs" />No flags attacks<Space w="xs" /></Box>
<b>{noFlagsAttacks} / {totAttacks} ({totAttacks!=0?(noFlagsAttacks*100/totAttacks).toFixed(2):0}%)</b>
</Box>
<Space h="xs" />
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><MdTimer /><Space w="xs" />Exploit created<Space w="xs" /></Box>
<Box>by <b>{clientSolver(exploit.created_by)}</b> at <b>{getDateFormatted(exploit.created_at)}</b></Box>
</Box>
<Box display="flex">
<Box display="flex" style={{alignItems: "center", width: boxWidth}}><FaKeyboard /><Space w="xs" />Language used<Space w="xs" /></Box>
<b>{exploit.language}</b>
</Box>
<Space h="sm" />
</Box>:null}

</Modal>
}
22 changes: 13 additions & 9 deletions frontend/src/components/ExploitsBar.tsx
Original file line number Diff line number Diff line change
@@ -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<string|null>(null)

return <Box style={{width:"100%", fontSize:"90%"}} >
<b>Launched Exploits</b>
Expand All @@ -22,13 +25,10 @@ export const ExploitBar = () => {
return 1
else
return b.name.localeCompare(a.name)
} ).map((expl) => <Box>
<Box style={{fontSize:"90%"}}>
} ).map((expl) => <Box style={{fontSize:"90%", cursor:"pointer"}} onClick={()=>setModalExploitId(expl.id)} className="transparency-on-hover">
<Paper shadow="md" radius="xl" withBorder px={20} py={5} bg="gray" mx="xs">
<Box className="center-flex">
<Tooltip label={expl.status=="active"?"Active":"Inactive"} color={expl.status=="active"?"green":"red"}>
<span className="center-flex"><GoDotFill color={expl.status=="active"?"green":"red"} size={30} /></span>
</Tooltip>
<StatusPoint status={expl.status} />
<Space w={3} />
<Box>
<Box style={{ fontSize: "90%"}}>
Expand All @@ -39,15 +39,19 @@ export const ExploitBar = () => {
</Box>
</Box>
</Box>
</Paper>

</Box>
</Paper>
<Space h="md" />

</Box>)}
</Flex>
</ScrollArea>
<ExploitDetailModal opened={modalExploitId != null} close={()=>setModalExploitId(null)} exploitId={modalExploitId??""} />

<Space h="md" />
</Box>
}

export const StatusPoint = (props:{ status:"active"|"inactive"|"disabled"|undefined|null }) => {
return <Tooltip label={props.status=="active"?"Active":"Inactive"} color={props.status=="active"?"green":"red"}>
<span className="center-flex"><GoDotFill color={props.status=="active"?"green":"red"} size={30} /></span>
</Tooltip>
}
4 changes: 2 additions & 2 deletions frontend/src/components/LineChartAttackView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/LineChartFlagView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 37 additions & 23 deletions frontend/src/components/StatusIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <FaFlag size={20} />, label: "Flag has been submitted successfully"}
break
case "invalid":
icon_info = { color: "red", icon: <ImCross size={20} />, label: "Flag has been submitted but is invalid"}
break
case "timeout":
icon_info = { color: "orange", icon: <MdTimerOff size={25} />, label: "Flag is too old"}
break
case "wait":
icon_info = { color: "DeepSkyBlue", icon: <FaUpload size={20} />, label: "Flag is pending for submission..." }
break
case "tot":
icon_info = { color: "white", icon: <FaAsterisk size={20} />, 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 <span
style={{color: icon_info.color, textAlign: "center"}}
className="center-flex"
>{icon_info.icon}</span>
>{<icon_info.icon size={20} />}</span>
else
return <Tooltip color={icon_info.color!="white"?icon_info.color:"gray"} label={icon_info.label}>
<span
style={{color: icon_info.color, textAlign: "center"}}
className="center-flex"
>{icon_info.icon}</span>
</Tooltip>
>{<icon_info.icon size={20} />}</span>
</Tooltip>
}

export const attackStatusTable = {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/backend_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export interface components {
* Last Upadte
* Format: date-time
*/
last_upadte: string;
last_update: string;
/**
* Created At
* Format: date-time
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit 451cbab

Please sign in to comment.