From 5eeea464d84f6582e62cc8b6b92a47204a1dee89 Mon Sep 17 00:00:00 2001 From: Georg Felber Date: Sat, 6 Apr 2024 09:23:52 +0000 Subject: [PATCH 01/10] Initial commit --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1b11cf --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# tulip + + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + +Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! + +## Add your files + +- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files +- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: + +``` +cd existing_repo +git remote add origin https://gitlab.w0y.at/h4ck/tulip.git +git branch -M main +git push -uf origin main +``` + +## Integrate with your tools + +- [ ] [Set up project integrations](https://gitlab.w0y.at/h4ck/tulip/-/settings/integrations) + +## Collaborate with your team + +- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) +- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) +- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) +- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) +- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) + +## Test and Deploy + +Use the built-in continuous integration in GitLab. + +- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) +- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) +- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) +- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) +- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) + +*** + +# Editing this README + +When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. + +## Suggestions for a good README + +Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. + +## Name +Choose a self-explaining name for your project. + +## Description +Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. + +## Badges +On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. + +## Visuals +Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. + +## Installation +Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. + +## Usage +Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. + +## Support +Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. + +## Roadmap +If you have ideas for releases in the future, it is a good idea to list them in the README. + +## Contributing +State if you are open to contributions and what your requirements are for accepting them. + +For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. + +You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. + +## Authors and acknowledgment +Show your appreciation to those who have contributed to the project. + +## License +For open source projects, say how it is licensed. + +## Project status +If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. From b96ec436777a02893249f342375552882e81b214 Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sat, 6 Apr 2024 11:35:50 +0200 Subject: [PATCH 02/10] improved favicon --- frontend/public/favicon.ico | Bin 114133 -> 16958 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index c1e0423bb0e66afd82b4ce09125385ceb65158a7..ca84cf2de94213bcdb3b09891ead3f280d185191 100644 GIT binary patch literal 16958 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 BF*X1I literal 114133 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 From fafeb74eab54513d3ccc710215d1e9433298c70b Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:54:56 +0200 Subject: [PATCH 03/10] added ssdeep hash calculation to assembler and now shown in frontend --- frontend/src/pages/FlowView.tsx | 52 +++++++++++++--------- frontend/src/types.ts | 1 + services/go-importer/cmd/assembler/tcp.go | 21 +++++++-- services/go-importer/go.mod | 3 +- services/go-importer/go.sum | 17 +++++++ services/go-importer/internal/pkg/db/db.go | 1 + 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/frontend/src/pages/FlowView.tsx b/frontend/src/pages/FlowView.tsx index 422adb1..48461ca 100644 --- a/frontend/src/pages/FlowView.tsx +++ b/frontend/src/pages/FlowView.tsx @@ -233,51 +233,59 @@ function FlowOverview({ flow }: { flow: FullFlow }) {
Meta
-
Source:
+
Source:
-
Tags:
+
Tags:
[{flow.tags.join(", ")}]
-
Flags:
+
Flags:
[{flow.flags.map((query, i) => ( - + {i > 0 ? ', ' : ''} - - ))}] + ))}]
-
Flagids:
+
+
Flagids:
[{flow.flagids.map((query, i) => ( {i > 0 ? ', ' : ''} - ))}] + ))}] +
+
+
ssdeep hash:
+
+ + {flow.ssdeep} +
-
Source - Target:
+
Source - Target:
{" "} @@ -302,14 +310,14 @@ export function FlowView() { const id = params.id; - const { data: flow } = useGetFlowQuery(id!, { skip: id === undefined }); + const {data: flow} = useGetFlowQuery(id!, {skip: id === undefined}); const [triggerPwnToolsQuery] = useLazyToPwnToolsQuery(); const [triggerFullPythonRequestQuery] = useLazyToFullPythonRequestQuery(); async function copyAsPwn() { if (flow?._id.$oid) { - const { data } = await triggerPwnToolsQuery(flow?._id.$oid); + const {data} = await triggerPwnToolsQuery(flow?._id.$oid); console.log(data); return data || ""; } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 5af9d5b..b6a1421 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -16,6 +16,7 @@ export interface Flow { flagids: string[]; suricata: number[]; filename: string; + ssdeep: string; } export interface TickInfo { diff --git a/services/go-importer/cmd/assembler/tcp.go b/services/go-importer/cmd/assembler/tcp.go index 36e6b5c..672806a 100644 --- a/services/go-importer/cmd/assembler/tcp.go +++ b/services/go-importer/cmd/assembler/tcp.go @@ -10,10 +10,12 @@ package main import ( "go-importer/internal/pkg/db" + "log" "sync" "time" + "github.com/glaslos/ssdeep" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/reassembly" @@ -37,7 +39,8 @@ type TcpStreamFactory struct { } func (factory *TcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream { - source := ac.GetCaptureInfo().AncillaryData[0].(string); + ssdeep.Force = true + source := ac.GetCaptureInfo().AncillaryData[0].(string) fsmOptions := reassembly.TCPSimpleFSMOptions{ SupportMissingEstablishment: *nonstrict, } @@ -193,6 +196,17 @@ func (t *TcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { time = t.FlowItems[0].Time duration = t.FlowItems[len(t.FlowItems)-1].Time - time + allData := make([]byte, 0) + for idx := 0; idx < len(t.FlowItems); idx++ { + flowItem := &t.FlowItems[idx] + allData = append(allData[:], []byte(flowItem.Data)[:]...) + } + + fuzzyHash, err := ssdeep.FuzzyBytes(allData) + if err != nil { + log.Println("Error: ", err) + } + entry := db.FlowEntry{ Src_port: int(t.src_port), Dst_port: int(t.dst_port), @@ -204,13 +218,14 @@ func (t *TcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { Parent_id: primitive.NilObjectID, Child_id: primitive.NilObjectID, Blocked: false, - Tags: []string { "tcp" }, + Tags: []string{"tcp"}, Suricata: make([]int, 0), Filename: t.source, Flow: t.FlowItems, Size: t.total_size, Flags: make([]string, 0), - Flagids: make([]string, 0), + Flagids: make([]string, 0), + Ssdeep: fuzzyHash, } t.reassemblyCallback(entry) diff --git a/services/go-importer/go.mod b/services/go-importer/go.mod index 38d358b..5d249bd 100644 --- a/services/go-importer/go.mod +++ b/services/go-importer/go.mod @@ -4,9 +4,10 @@ go 1.13 require ( github.com/andybalholm/brotli v1.0.4 + github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 github.com/fsnotify/fsnotify v1.5.4 + github.com/glaslos/ssdeep v0.3.3 github.com/google/gopacket v1.1.19 github.com/tidwall/gjson v1.14.1 go.mongodb.org/mongo-driver v1.9.1 - github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 ) diff --git a/services/go-importer/go.sum b/services/go-importer/go.sum index dd6a23f..3e11d1e 100644 --- a/services/go-importer/go.sum +++ b/services/go-importer/go.sum @@ -2,11 +2,14 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 h1:8yL+85JpbwrIc6m+7N1iYrjn/22z68jwrTIBOJHNe4k= github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184/go.mod h1:tGWUZLZp9ajsxUOnHmFFLnqnlKXsCn6GReG4jAD59H0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/glaslos/ssdeep v0.3.3 h1:6+/hcz3CcLKSZVOm/xbmcfCAuyVDhBAYFQxHHQR+ATA= +github.com/glaslos/ssdeep v0.3.3/go.mod h1:y1Kro2xRYIZdPkL9uQCf/aXtPEEf+TEHe91tlKU8Y0w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= @@ -19,17 +22,29 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -78,5 +93,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/services/go-importer/internal/pkg/db/db.go b/services/go-importer/internal/pkg/db/db.go index 9aac3d5..8a00d72 100644 --- a/services/go-importer/internal/pkg/db/db.go +++ b/services/go-importer/internal/pkg/db/db.go @@ -46,6 +46,7 @@ type FlowEntry struct { Size int Flags []string Flagids []string + Ssdeep string } type Database struct { From f940e1e150f7a4eb215ae8ea04508ddc46cc1b0b Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:09:43 +0200 Subject: [PATCH 04/10] removed unneeded filters --- frontend/src/components/Corrie.tsx | 4 ---- frontend/src/components/FlowList.tsx | 4 ---- frontend/src/store/filter.ts | 16 +--------------- frontend/src/types.ts | 2 -- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/frontend/src/components/Corrie.tsx b/frontend/src/components/Corrie.tsx index e943a26..51e2557 100644 --- a/frontend/src/components/Corrie.tsx +++ b/frontend/src/components/Corrie.tsx @@ -29,8 +29,6 @@ export const Corrie = () => { const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); const filterTags = useAppSelector((state) => state.filter.filterTags); - const filterFlags = useAppSelector((state) => state.filter.filterFlags); - const filterFlagids = useAppSelector((state) => state.filter.filterFlagids); const [searchParams, setSearchParams] = useSearchParams(); @@ -54,8 +52,6 @@ export const Corrie = () => { includeTags: includeTags, excludeTags: excludeTags, tags: filterTags, - flags: filterFlags, - flagids: filterFlagids, }, { refetchOnMountOrArgChange: true, diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index 6e8e85e..bb0b37d 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -40,8 +40,6 @@ export function FlowList() { const { data: services } = useGetServicesQuery(); const filterTags = useAppSelector((state) => state.filter.filterTags); - const filterFlags = useAppSelector((state) => state.filter.filterFlags); - const filterFlagids = useAppSelector((state) => state.filter.filterFlagids); const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); @@ -72,8 +70,6 @@ export function FlowList() { to_time: to_filter, service: "", // FIXME tags: filterTags, - flags: filterFlags, - flagids: filterFlagids, includeTags: includeTags, excludeTags: excludeTags }, diff --git a/frontend/src/store/filter.ts b/frontend/src/store/filter.ts index 75770f8..15dca6f 100644 --- a/frontend/src/store/filter.ts +++ b/frontend/src/store/filter.ts @@ -2,8 +2,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; export interface TulipFilterState { filterTags: string[]; - filterFlags: string[]; - filterFlagids: string[]; includeTags: string[]; excludeTags: string[]; // startTick?: number; @@ -16,8 +14,6 @@ const initialState: TulipFilterState = { includeTags: [], excludeTags: [], filterTags: [], - filterFlags: [], - filterFlagids: [], }; export const filterSlice = createSlice({ @@ -53,17 +49,7 @@ export const filterSlice = createSlice({ } } } - }, - toggleFilterFlags: (state, action: PayloadAction) => { - state.filterFlags = state.filterFlags.includes(action.payload) - ? state.filterFlags.filter((t) => t !== action.payload) - : [...state.filterFlags, action.payload]; - }, - toggleFilterFlagids: (state, action: PayloadAction) => { - state.filterFlagids = state.filterFlagids.includes(action.payload) - ? state.filterFlagids.filter((t) => t !== action.payload) - : [...state.filterFlagids, action.payload]; - }, + } }, }); diff --git a/frontend/src/types.ts b/frontend/src/types.ts index b6a1421..4298fe0 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -60,8 +60,6 @@ export interface FlowsQuery { includeTags: string[]; excludeTags: string[]; tags: string[]; - flags: string[]; - flagids: string[]; } export type Service = { From d1d614259b54c17825da9a3bdce37249a0ffb65c Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:12:57 +0200 Subject: [PATCH 05/10] implemented similarity matching using ssdeep hashes --- frontend/src/api.ts | 2 + frontend/src/components/Corrie.tsx | 8 ++- frontend/src/components/FlowList.tsx | 55 ++++++++++++++----- frontend/src/components/Header.tsx | 82 +++++++++++++++++++++------- frontend/src/const.ts | 1 + frontend/src/pages/FlowView.tsx | 65 ++++++++++++++-------- frontend/src/store/filter.ts | 37 ++++++++++++- frontend/src/types.ts | 3 + services/api/Dockerfile-api | 2 +- services/api/requirements.txt | 1 + services/api/webservice.py | 13 ++++- 11 files changed, 204 insertions(+), 65 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index a71cdfc..4ca0d8e 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -33,6 +33,8 @@ export const tulipApi = createApi({ ...query, includeTags: query.includeTags.length > 0 ? query.includeTags : undefined, excludeTags: query.excludeTags.length > 0 ? query.excludeTags : undefined, + includeSsdeep: query.includeSsdeep.length > 0 ? query.includeSsdeep : undefined, + excludeSsdeep: query.excludeSsdeep.length > 0 ? query.excludeSsdeep : undefined, }), }), }), diff --git a/frontend/src/components/Corrie.tsx b/frontend/src/components/Corrie.tsx index 51e2557..b9d8d36 100644 --- a/frontend/src/components/Corrie.tsx +++ b/frontend/src/components/Corrie.tsx @@ -7,7 +7,7 @@ import { START_FILTER_KEY, END_FILTER_KEY, CORRELATION_MODE_KEY, - FLOW_LIST_REFETCH_INTERVAL_MS, + FLOW_LIST_REFETCH_INTERVAL_MS, SIMILARITY_FILTER_KEY, } from "../const"; import useDebounce from "../hooks/useDebounce"; @@ -29,6 +29,8 @@ export const Corrie = () => { const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); const filterTags = useAppSelector((state) => state.filter.filterTags); + const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); + const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); const [searchParams, setSearchParams] = useSearchParams(); @@ -36,6 +38,7 @@ export const Corrie = () => { const service = services && services.find((s) => s.name == service_name); const text_filter = searchParams.get(TEXT_FILTER_KEY) ?? undefined; + const similarity = searchParams.get(SIMILARITY_FILTER_KEY) ?? undefined; const from_filter = searchParams.get(START_FILTER_KEY) ?? undefined; const to_filter = searchParams.get(END_FILTER_KEY) ?? undefined; @@ -52,6 +55,9 @@ export const Corrie = () => { includeTags: includeTags, excludeTags: excludeTags, tags: filterTags, + similarity: similarity, + includeSsdeep: includeSsdeep, + excludeSsdeep: excludeSsdeep }, { refetchOnMountOrArgChange: true, diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index bb0b37d..03e2281 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -11,10 +11,10 @@ import { TEXT_FILTER_KEY, START_FILTER_KEY, END_FILTER_KEY, - FLOW_LIST_REFETCH_INTERVAL_MS, + FLOW_LIST_REFETCH_INTERVAL_MS, SIMILARITY_FILTER_KEY, } from "../const"; import { useAppSelector, useAppDispatch } from "../store"; -import { toggleFilterTag } from "../store/filter"; +import {toggleFilterSsdeep, toggleFilterTag} from "../store/filter"; import { HeartIcon, FilterIcon, LinkIcon } from "@heroicons/react/solid"; import { HeartIcon as EmptyHeartIcon } from "@heroicons/react/outline"; @@ -42,6 +42,9 @@ export function FlowList() { const filterTags = useAppSelector((state) => state.filter.filterTags); const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); + const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); + const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); + const ssdeeps = useAppSelector((state) => state.filter.ssdeeps); const dispatch = useAppDispatch(); @@ -56,6 +59,7 @@ export function FlowList() { const service = services?.find((s) => s.name == service_name); const text_filter = searchParams.get(TEXT_FILTER_KEY) ?? undefined; + const similarity = searchParams.get(SIMILARITY_FILTER_KEY) ?? undefined; const from_filter = searchParams.get(START_FILTER_KEY) ?? undefined; const to_filter = searchParams.get(END_FILTER_KEY) ?? undefined; @@ -71,7 +75,10 @@ export function FlowList() { service: "", // FIXME tags: filterTags, includeTags: includeTags, - excludeTags: excludeTags + excludeTags: excludeTags, + similarity: similarity, + includeSsdeep: includeSsdeep, + excludeSsdeep: excludeSsdeep }, { refetchOnMountOrArgChange: true, @@ -130,21 +137,39 @@ export function FlowList() {
)} + {showFilters && ( +
+

+ Similarity filter +

+
+ {(ssdeeps ?? []).map((ssdeep) => ( + dispatch(toggleFilterSsdeep(ssdeep))} + > + ))} +
+
+ )}
( - ( + { + setValue(event.target.value); + searchParams.set(SIMILARITY_FILTER_KEY, event.target.value) + setSearchParams(searchParams); + }; + + return ( +
+
+ + Similarity: + + +
+ +
+ ); +} + export function Header() { - let [searchParams] = useSearchParams(); - const { setToLastnTicks, currentTick } = useMessyTimeStuff(); + let [searchParams] = useSearchParams(); + const {setToLastnTicks, currentTick} = useMessyTimeStuff(); - return ( - <> - -
🌷
- -
- -
-
- - + return ( + <> + +
🌷
+ +
+ +
+
+ +
@@ -309,6 +348,7 @@ export function Header() { Graph view
+
diff --git a/frontend/src/const.ts b/frontend/src/const.ts index dd3f847..5bfc9b6 100644 --- a/frontend/src/const.ts +++ b/frontend/src/const.ts @@ -2,6 +2,7 @@ export const API_BASE_PATH = "/api"; export const TEXT_FILTER_KEY = "text"; export const SERVICE_FILTER_KEY = "service"; +export const SIMILARITY_FILTER_KEY = "similarity"; export const START_FILTER_KEY = "start"; export const END_FILTER_KEY = "end"; export const FIRST_DIFF_KEY = "first"; diff --git a/frontend/src/pages/FlowView.tsx b/frontend/src/pages/FlowView.tsx index 48461ca..ec0d498 100644 --- a/frontend/src/pages/FlowView.tsx +++ b/frontend/src/pages/FlowView.tsx @@ -21,6 +21,8 @@ import { API_BASE_PATH, TEXT_FILTER_KEY } from "../const"; +import {toggleFilterSsdeep, toggleFilterTag} from "../store/filter"; +import {useAppDispatch, useAppSelector} from "../store"; const SECONDARY_NAVBAR_HEIGHT = 50; @@ -195,6 +197,9 @@ function formatIP(ip: string) { function FlowOverview({ flow }: { flow: FullFlow }) { const FILTER_KEY = TEXT_FILTER_KEY; let [searchParams, setSearchParams] = useSearchParams(); + const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); + const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); + const dispatch = useAppDispatch(); return (
{flow.signatures?.length > 0 ? ( @@ -241,9 +246,20 @@ function FlowOverview({ flow }: { flow: FullFlow }) {
Tags:
-
[{flow.tags.join(", ")}]
-
Flags:
+ [{flow.tags.map((tag, i) => ( + + {i > 0 ? ', ' : ''} + + + ))}] +
+
+
Flags:
+
[{flow.flags.map((query, i) => ( {i > 0 ? ', ' : ''} @@ -275,33 +291,34 @@ function FlowOverview({ flow }: { flow: FullFlow }) { {query} - ))}] -
-
-
ssdeep hash:
-
- - {flow.ssdeep} - + ))}] +
+
+
ssdeep hash:
+
+ +
+
+
Source - Target:
+
+
+ {" "} + {formatIP(flow.src_ip)}: + {flow.src_port}
-
-
Source - Target:
-
-
- {" "} - {formatIP(flow.src_ip)}: - {flow.src_port} -
-
-
-
- {formatIP(flow.dst_ip)}: - {flow.dst_port} -
+
-
+
+ {formatIP(flow.dst_ip)}: + {flow.dst_port}
- ); +
+); } export function FlowView() { diff --git a/frontend/src/store/filter.ts b/frontend/src/store/filter.ts index 15dca6f..1e0c2e5 100644 --- a/frontend/src/store/filter.ts +++ b/frontend/src/store/filter.ts @@ -4,6 +4,9 @@ export interface TulipFilterState { filterTags: string[]; includeTags: string[]; excludeTags: string[]; + ssdeeps: string[]; + includeSsdeep: string[]; + excludeSsdeep: string[]; // startTick?: number; // endTick?: number; // service?: string; @@ -14,6 +17,9 @@ const initialState: TulipFilterState = { includeTags: [], excludeTags: [], filterTags: [], + ssdeeps: [], + includeSsdeep: [], + excludeSsdeep: [], }; export const filterSlice = createSlice({ @@ -49,10 +55,39 @@ export const filterSlice = createSlice({ } } } + }, + toggleFilterSsdeep: (state, action: PayloadAction) => { + var included = state.includeSsdeep.includes(action.payload) + var excluded = state.excludeSsdeep.includes(action.payload) + + // if ssdeep hash is new cache it + if(!state.ssdeeps.includes(action.payload)) + state.ssdeeps = [...state.ssdeeps, action.payload] + + // If a user clicks a 'included' ssdeep hash, the hash should be 'excluded' instead. + if (included) { + // Remove from included + state.includeSsdeep = state.includeSsdeep.filter((t) => t !== action.payload); + + // Add to excluded + state.excludeSsdeep = [...state.excludeSsdeep, action.payload] + } else { + // If the user clicks on an 'excluded' ssdeep hash, the hash should be 'unset' from both include / exclude tags + if (excluded) { + // Remove from excluded + state.excludeSsdeep = state.excludeSsdeep.filter((t) => t !== action.payload); + } else { + if (!included && !excluded) { + // The tag was disabled, so it should be added to included now + state.includeSsdeep = [...state.includeSsdeep, action.payload] + } + } + } } }, }); -export const { toggleFilterTag } = filterSlice.actions; + +export const { toggleFilterTag, toggleFilterSsdeep } = filterSlice.actions; export default filterSlice.reducer; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4298fe0..1b5c306 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -60,6 +60,9 @@ export interface FlowsQuery { includeTags: string[]; excludeTags: string[]; tags: string[]; + similarity?: string; + includeSsdeep: string[]; + excludeSsdeep: string[]; } export type Service = { diff --git a/services/api/Dockerfile-api b/services/api/Dockerfile-api index 5ef5300..a556b17 100644 --- a/services/api/Dockerfile-api +++ b/services/api/Dockerfile-api @@ -4,7 +4,7 @@ COPY ./requirements.txt /app/requirements.txt WORKDIR /app -RUN pip install -r requirements.txt +RUN BUILD_LIB=1 pip install -r requirements.txt COPY . /app diff --git a/services/api/requirements.txt b/services/api/requirements.txt index 3f28156..43d172c 100644 --- a/services/api/requirements.txt +++ b/services/api/requirements.txt @@ -2,3 +2,4 @@ Flask_Cors pymongo Flask requests +ssdeep \ No newline at end of file diff --git a/services/api/webservice.py b/services/api/webservice.py index 9d284ba..b49852a 100755 --- a/services/api/webservice.py +++ b/services/api/webservice.py @@ -33,6 +33,7 @@ from bson import json_util from flask_cors import CORS from flask import request +import ssdeep from flow2pwn import flow2pwn @@ -62,8 +63,16 @@ def getTickInfo(): @application.route('/query', methods=['POST']) def query(): - json = request.get_json() - result = db.getFlowList(json) + filter = request.get_json() + result = db.getFlowList(filter) + similarity = int(filter["similarity"]) if "similarity" in filter else 75 + + if "includeSsdeep" in filter: + result = [x for x in result if all(ssdeep.compare(hash, x["ssdeep"]) >= similarity for hash in filter["includeSsdeep"])] + + if "excludeSsdeep" in filter: + result = [x for x in result if all(ssdeep.compare(hash, x["ssdeep"]) < similarity for hash in filter["excludeSsdeep"])] + return return_json_response(result) @application.route('/tags') From 452acfb92ef71360dfc5587e464c1db18c114a2a Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:10:05 +0200 Subject: [PATCH 06/10] + similarity list now now uses flow ids instead of hashes + similarity state now get's restore in ui. + moved ssdeep generation to main.go for udp and tcp compatibility --- frontend/src/components/FlowList.tsx | 9 +++---- frontend/src/components/Header.tsx | 2 +- frontend/src/pages/FlowView.tsx | 2 +- frontend/src/store/filter.ts | 28 ++++++++++++++-------- services/go-importer/cmd/assembler/main.go | 18 ++++++++++++++ services/go-importer/cmd/assembler/tcp.go | 21 +++------------- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index 03e2281..aa07608 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -45,6 +45,7 @@ export function FlowList() { const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); const ssdeeps = useAppSelector((state) => state.filter.ssdeeps); + const ssdeep_ids = useAppSelector((state) => state.filter.ssdeep_ids); const dispatch = useAppDispatch(); @@ -143,13 +144,13 @@ export function FlowList() { Similarity filter

- {(ssdeeps ?? []).map((ssdeep) => ( + {(ssdeeps ?? []).map((ssdeep, i) => ( dispatch(toggleFilterSsdeep(ssdeep))} + onClick={() => dispatch(toggleFilterSsdeep([ssdeep, ssdeep_ids[i]]))} > ))}
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 079066f..769dab8 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -276,7 +276,7 @@ function Diff() { function SimilaritySlider() { let [searchParams, setSearchParams] = useSearchParams(); // Initialize state to keep track of the value - const [value, setValue] = useState(75); // You can set the initial value to whatever you prefer + const [value, setValue] = useState(searchParams.get(SIMILARITY_FILTER_KEY) ?? 75); // You can set the initial value to whatever you prefer // Function to update the state based on input changes const handleChange = (event:any) => { diff --git a/frontend/src/pages/FlowView.tsx b/frontend/src/pages/FlowView.tsx index ec0d498..d7bcdeb 100644 --- a/frontend/src/pages/FlowView.tsx +++ b/frontend/src/pages/FlowView.tsx @@ -297,7 +297,7 @@ function FlowOverview({ flow }: { flow: FullFlow }) {
ssdeep hash:
diff --git a/frontend/src/store/filter.ts b/frontend/src/store/filter.ts index 1e0c2e5..282105c 100644 --- a/frontend/src/store/filter.ts +++ b/frontend/src/store/filter.ts @@ -1,10 +1,13 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +// Note: all off these states are immutable and can only be changed through overwrite export interface TulipFilterState { filterTags: string[]; includeTags: string[]; excludeTags: string[]; + // can't use Map because immutable bs ssdeeps: string[]; + ssdeep_ids: string[]; includeSsdeep: string[]; excludeSsdeep: string[]; // startTick?: number; @@ -18,6 +21,7 @@ const initialState: TulipFilterState = { excludeTags: [], filterTags: [], ssdeeps: [], + ssdeep_ids: [], includeSsdeep: [], excludeSsdeep: [], }; @@ -56,30 +60,34 @@ export const filterSlice = createSlice({ } } }, - toggleFilterSsdeep: (state, action: PayloadAction) => { - var included = state.includeSsdeep.includes(action.payload) - var excluded = state.excludeSsdeep.includes(action.payload) + toggleFilterSsdeep: (state, action: PayloadAction) => { + var ssdeep = action.payload[0] + var id = action.payload[1] + var included = state.includeSsdeep.includes(ssdeep) + var excluded = state.excludeSsdeep.includes(ssdeep) - // if ssdeep hash is new cache it - if(!state.ssdeeps.includes(action.payload)) - state.ssdeeps = [...state.ssdeeps, action.payload] + // If the ssdeep hash is new cache it + if(!state.ssdeeps.includes(ssdeep)) { + state.ssdeeps = [...state.ssdeeps, ssdeep] + state.ssdeep_ids = [...state.ssdeep_ids, id] + } // If a user clicks a 'included' ssdeep hash, the hash should be 'excluded' instead. if (included) { // Remove from included - state.includeSsdeep = state.includeSsdeep.filter((t) => t !== action.payload); + state.includeSsdeep = state.includeSsdeep.filter((t) => t !== ssdeep); // Add to excluded - state.excludeSsdeep = [...state.excludeSsdeep, action.payload] + state.excludeSsdeep = [...state.excludeSsdeep, ssdeep] } else { // If the user clicks on an 'excluded' ssdeep hash, the hash should be 'unset' from both include / exclude tags if (excluded) { // Remove from excluded - state.excludeSsdeep = state.excludeSsdeep.filter((t) => t !== action.payload); + state.excludeSsdeep = state.excludeSsdeep.filter((t) => t !== ssdeep); } else { if (!included && !excluded) { // The tag was disabled, so it should be added to included now - state.includeSsdeep = [...state.includeSsdeep, action.payload] + state.includeSsdeep = [...state.includeSsdeep, ssdeep] } } } diff --git a/services/go-importer/cmd/assembler/main.go b/services/go-importer/cmd/assembler/main.go index 8962d8c..b2d6d96 100644 --- a/services/go-importer/cmd/assembler/main.go +++ b/services/go-importer/cmd/assembler/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/glaslos/ssdeep" "go-importer/internal/pkg/db" "flag" @@ -70,15 +71,32 @@ var g_db db.Database var flagids []db.Flagid var flagidUpdate int64 = 0 +func init() { + // force ssdeep creation even for small data sizes + ssdeep.Force = true +} + // TODO; FIXME; RDJ; this is kinda gross, but this is PoC level code func reassemblyCallback(entry db.FlowEntry) { // Parsing HTTP will decode encodings to a plaintext format ParseHttpFlow(&entry) + allData := make([]byte, 0) + for idx := 0; idx < len(entry.Flow); idx++ { + flowItem := entry.Flow[idx] + allData = append(allData[:], []byte(flowItem.Data)[:]...) + } + + fuzzyHash, err := ssdeep.FuzzyBytes(allData) + if err != nil { + log.Println("Error: ", err) + } + // Apply flag in / flagout if *flag_regex != "" { ApplyFlagTags(&entry, flag_regex) } + entry.Ssdeep = fuzzyHash //Apply flagid in / out if *flagid { diff --git a/services/go-importer/cmd/assembler/tcp.go b/services/go-importer/cmd/assembler/tcp.go index 672806a..36e6b5c 100644 --- a/services/go-importer/cmd/assembler/tcp.go +++ b/services/go-importer/cmd/assembler/tcp.go @@ -10,12 +10,10 @@ package main import ( "go-importer/internal/pkg/db" - "log" "sync" "time" - "github.com/glaslos/ssdeep" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/reassembly" @@ -39,8 +37,7 @@ type TcpStreamFactory struct { } func (factory *TcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream { - ssdeep.Force = true - source := ac.GetCaptureInfo().AncillaryData[0].(string) + source := ac.GetCaptureInfo().AncillaryData[0].(string); fsmOptions := reassembly.TCPSimpleFSMOptions{ SupportMissingEstablishment: *nonstrict, } @@ -196,17 +193,6 @@ func (t *TcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { time = t.FlowItems[0].Time duration = t.FlowItems[len(t.FlowItems)-1].Time - time - allData := make([]byte, 0) - for idx := 0; idx < len(t.FlowItems); idx++ { - flowItem := &t.FlowItems[idx] - allData = append(allData[:], []byte(flowItem.Data)[:]...) - } - - fuzzyHash, err := ssdeep.FuzzyBytes(allData) - if err != nil { - log.Println("Error: ", err) - } - entry := db.FlowEntry{ Src_port: int(t.src_port), Dst_port: int(t.dst_port), @@ -218,14 +204,13 @@ func (t *TcpStream) ReassemblyComplete(ac reassembly.AssemblerContext) bool { Parent_id: primitive.NilObjectID, Child_id: primitive.NilObjectID, Blocked: false, - Tags: []string{"tcp"}, + Tags: []string { "tcp" }, Suricata: make([]int, 0), Filename: t.source, Flow: t.FlowItems, Size: t.total_size, Flags: make([]string, 0), - Flagids: make([]string, 0), - Ssdeep: fuzzyHash, + Flagids: make([]string, 0), } t.reassemblyCallback(entry) From 5d40456a28ede091e6c782a0cf442a3c508fad44 Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Sat, 6 Apr 2024 12:54:56 +0200 Subject: [PATCH 07/10] changed fuzzy hash to nilsimsa hash --- frontend/src/api.ts | 4 +- frontend/src/components/Corrie.tsx | 8 ++-- frontend/src/components/FlowList.tsx | 26 ++++++------ frontend/src/components/Header.tsx | 10 ++--- frontend/src/pages/FlowView.tsx | 26 ++++++------ frontend/src/store/filter.ts | 46 +++++++++++----------- frontend/src/types.ts | 6 +-- services/api/requirements.txt | 2 +- services/api/webservice.py | 13 +++--- services/go-importer/cmd/assembler/main.go | 14 ++----- services/go-importer/go.mod | 2 +- services/go-importer/go.sum | 4 +- services/go-importer/internal/pkg/db/db.go | 2 +- 13 files changed, 77 insertions(+), 86 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 4ca0d8e..3118032 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -33,8 +33,8 @@ export const tulipApi = createApi({ ...query, includeTags: query.includeTags.length > 0 ? query.includeTags : undefined, excludeTags: query.excludeTags.length > 0 ? query.excludeTags : undefined, - includeSsdeep: query.includeSsdeep.length > 0 ? query.includeSsdeep : undefined, - excludeSsdeep: query.excludeSsdeep.length > 0 ? query.excludeSsdeep : undefined, + includeFuzzyHashes: query.includeFuzzyHashes.length > 0 ? query.includeFuzzyHashes : undefined, + excludeFuzzyHashes: query.excludeFuzzyHashes.length > 0 ? query.excludeFuzzyHashes : undefined, }), }), }), diff --git a/frontend/src/components/Corrie.tsx b/frontend/src/components/Corrie.tsx index b9d8d36..e5a15df 100644 --- a/frontend/src/components/Corrie.tsx +++ b/frontend/src/components/Corrie.tsx @@ -29,8 +29,8 @@ export const Corrie = () => { const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); const filterTags = useAppSelector((state) => state.filter.filterTags); - const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); - const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); + const includeFuzzyHashes = useAppSelector((state) => state.filter.includeFuzzyHashes); + const excludeFuzzyHashes = useAppSelector((state) => state.filter.excludeFuzzyHashes); const [searchParams, setSearchParams] = useSearchParams(); @@ -56,8 +56,8 @@ export const Corrie = () => { excludeTags: excludeTags, tags: filterTags, similarity: similarity, - includeSsdeep: includeSsdeep, - excludeSsdeep: excludeSsdeep + includeFuzzyHashes: includeFuzzyHashes, + excludeFuzzyHashes: excludeFuzzyHashes }, { refetchOnMountOrArgChange: true, diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index aa07608..d540914 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -14,7 +14,7 @@ import { FLOW_LIST_REFETCH_INTERVAL_MS, SIMILARITY_FILTER_KEY, } from "../const"; import { useAppSelector, useAppDispatch } from "../store"; -import {toggleFilterSsdeep, toggleFilterTag} from "../store/filter"; +import {toggleFilterFuzzyHashes, toggleFilterTag} from "../store/filter"; import { HeartIcon, FilterIcon, LinkIcon } from "@heroicons/react/solid"; import { HeartIcon as EmptyHeartIcon } from "@heroicons/react/outline"; @@ -42,10 +42,10 @@ export function FlowList() { const filterTags = useAppSelector((state) => state.filter.filterTags); const includeTags = useAppSelector((state) => state.filter.includeTags); const excludeTags = useAppSelector((state) => state.filter.excludeTags); - const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); - const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); - const ssdeeps = useAppSelector((state) => state.filter.ssdeeps); - const ssdeep_ids = useAppSelector((state) => state.filter.ssdeep_ids); + const includeFuzzyHashes = useAppSelector((state) => state.filter.includeFuzzyHashes); + const excludeFuzzyHashes = useAppSelector((state) => state.filter.excludeFuzzyHashes); + const fuzzyHashes = useAppSelector((state) => state.filter.fuzzyHashes); + const fuzzyHashIds = useAppSelector((state) => state.filter.fuzzyHashIds); const dispatch = useAppDispatch(); @@ -78,8 +78,8 @@ export function FlowList() { includeTags: includeTags, excludeTags: excludeTags, similarity: similarity, - includeSsdeep: includeSsdeep, - excludeSsdeep: excludeSsdeep + includeFuzzyHashes: includeFuzzyHashes, + excludeFuzzyHashes: excludeFuzzyHashes }, { refetchOnMountOrArgChange: true, @@ -144,13 +144,13 @@ export function FlowList() { Similarity filter

- {(ssdeeps ?? []).map((ssdeep, i) => ( + {(fuzzyHashes ?? []).map((fuzzyHash, i) => ( dispatch(toggleFilterSsdeep([ssdeep, ssdeep_ids[i]]))} + key={fuzzyHashIds[i]} + tag={fuzzyHashIds[i]} + disabled={!includeFuzzyHashes.includes(fuzzyHash)} + excluded={excludeFuzzyHashes.includes(fuzzyHash)} + onClick={() => dispatch(toggleFilterFuzzyHashes([fuzzyHash, fuzzyHashIds[i]]))} > ))}
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 769dab8..fd70ccc 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -276,7 +276,7 @@ function Diff() { function SimilaritySlider() { let [searchParams, setSearchParams] = useSearchParams(); // Initialize state to keep track of the value - const [value, setValue] = useState(searchParams.get(SIMILARITY_FILTER_KEY) ?? 75); // You can set the initial value to whatever you prefer + const [value, setValue] = useState(searchParams.get(SIMILARITY_FILTER_KEY) ?? 112); // You can set the initial value to whatever you prefer // Function to update the state based on input changes const handleChange = (event:any) => { @@ -294,8 +294,8 @@ function SimilaritySlider() { @@ -303,8 +303,8 @@ function SimilaritySlider() { diff --git a/frontend/src/pages/FlowView.tsx b/frontend/src/pages/FlowView.tsx index d7bcdeb..8b47f33 100644 --- a/frontend/src/pages/FlowView.tsx +++ b/frontend/src/pages/FlowView.tsx @@ -21,7 +21,7 @@ import { API_BASE_PATH, TEXT_FILTER_KEY } from "../const"; -import {toggleFilterSsdeep, toggleFilterTag} from "../store/filter"; +import {toggleFilterFuzzyHashes, toggleFilterTag} from "../store/filter"; import {useAppDispatch, useAppSelector} from "../store"; const SECONDARY_NAVBAR_HEIGHT = 50; @@ -197,8 +197,6 @@ function formatIP(ip: string) { function FlowOverview({ flow }: { flow: FullFlow }) { const FILTER_KEY = TEXT_FILTER_KEY; let [searchParams, setSearchParams] = useSearchParams(); - const includeSsdeep = useAppSelector((state) => state.filter.includeSsdeep); - const excludeSsdeep = useAppSelector((state) => state.filter.excludeSsdeep); const dispatch = useAppDispatch(); return (
@@ -250,10 +248,10 @@ function FlowOverview({ flow }: { flow: FullFlow }) { [{flow.tags.map((tag, i) => ( {i > 0 ? ', ' : ''} - + ))}]
@@ -263,7 +261,7 @@ function FlowOverview({ flow }: { flow: FullFlow }) { [{flow.flags.map((query, i) => ( {i > 0 ? ', ' : ''} - + ))}]
@@ -281,7 +279,7 @@ function FlowOverview({ flow }: { flow: FullFlow }) { [{flow.flagids.map((query, i) => ( {i > 0 ? ', ' : ''} - + ))}]
-
ssdeep hash:
+
Nilsimsa hash:
- + dispatch(toggleFilterFuzzyHashes([flow.fuzzy_hash, flow._id.$oid]))}> + {flow.fuzzy_hash} +
Source - Target:
diff --git a/frontend/src/store/filter.ts b/frontend/src/store/filter.ts index 282105c..852595d 100644 --- a/frontend/src/store/filter.ts +++ b/frontend/src/store/filter.ts @@ -6,10 +6,10 @@ export interface TulipFilterState { includeTags: string[]; excludeTags: string[]; // can't use Map because immutable bs - ssdeeps: string[]; - ssdeep_ids: string[]; - includeSsdeep: string[]; - excludeSsdeep: string[]; + fuzzyHashes: string[]; + fuzzyHashIds: string[]; + includeFuzzyHashes: string[]; + excludeFuzzyHashes: string[]; // startTick?: number; // endTick?: number; // service?: string; @@ -20,10 +20,10 @@ const initialState: TulipFilterState = { includeTags: [], excludeTags: [], filterTags: [], - ssdeeps: [], - ssdeep_ids: [], - includeSsdeep: [], - excludeSsdeep: [], + fuzzyHashes: [], + fuzzyHashIds: [], + includeFuzzyHashes: [], + excludeFuzzyHashes: [], }; export const filterSlice = createSlice({ @@ -60,34 +60,34 @@ export const filterSlice = createSlice({ } } }, - toggleFilterSsdeep: (state, action: PayloadAction) => { - var ssdeep = action.payload[0] + toggleFilterFuzzyHashes: (state, action: PayloadAction) => { + var fuzzyHashes = action.payload[0] var id = action.payload[1] - var included = state.includeSsdeep.includes(ssdeep) - var excluded = state.excludeSsdeep.includes(ssdeep) + var included = state.includeFuzzyHashes.includes(fuzzyHashes) + var excluded = state.excludeFuzzyHashes.includes(fuzzyHashes) - // If the ssdeep hash is new cache it - if(!state.ssdeeps.includes(ssdeep)) { - state.ssdeeps = [...state.ssdeeps, ssdeep] - state.ssdeep_ids = [...state.ssdeep_ids, id] + // If the fuzzyHashes hash is new cache it + if(!state.fuzzyHashes.includes(fuzzyHashes)) { + state.fuzzyHashes = [...state.fuzzyHashes, fuzzyHashes] + state.fuzzyHashIds = [...state.fuzzyHashIds, id] } - // If a user clicks a 'included' ssdeep hash, the hash should be 'excluded' instead. + // If a user clicks a 'included' fuzzyHashes hash, the hash should be 'excluded' instead. if (included) { // Remove from included - state.includeSsdeep = state.includeSsdeep.filter((t) => t !== ssdeep); + state.includeFuzzyHashes = state.includeFuzzyHashes.filter((t) => t !== fuzzyHashes); // Add to excluded - state.excludeSsdeep = [...state.excludeSsdeep, ssdeep] + state.excludeFuzzyHashes = [...state.excludeFuzzyHashes, fuzzyHashes] } else { - // If the user clicks on an 'excluded' ssdeep hash, the hash should be 'unset' from both include / exclude tags + // If the user clicks on an 'excluded' fuzzyHashes hash, the hash should be 'unset' from both include / exclude tags if (excluded) { // Remove from excluded - state.excludeSsdeep = state.excludeSsdeep.filter((t) => t !== ssdeep); + state.excludeFuzzyHashes = state.excludeFuzzyHashes.filter((t) => t !== fuzzyHashes); } else { if (!included && !excluded) { // The tag was disabled, so it should be added to included now - state.includeSsdeep = [...state.includeSsdeep, ssdeep] + state.includeFuzzyHashes = [...state.includeFuzzyHashes, fuzzyHashes] } } } @@ -96,6 +96,6 @@ export const filterSlice = createSlice({ }); -export const { toggleFilterTag, toggleFilterSsdeep } = filterSlice.actions; +export const { toggleFilterTag, toggleFilterFuzzyHashes } = filterSlice.actions; export default filterSlice.reducer; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 1b5c306..d8ee21c 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -16,7 +16,7 @@ export interface Flow { flagids: string[]; suricata: number[]; filename: string; - ssdeep: string; + fuzzy_hash: string; } export interface TickInfo { @@ -61,8 +61,8 @@ export interface FlowsQuery { excludeTags: string[]; tags: string[]; similarity?: string; - includeSsdeep: string[]; - excludeSsdeep: string[]; + includeFuzzyHashes: string[]; + excludeFuzzyHashes: string[]; } export type Service = { diff --git a/services/api/requirements.txt b/services/api/requirements.txt index 43d172c..a059968 100644 --- a/services/api/requirements.txt +++ b/services/api/requirements.txt @@ -2,4 +2,4 @@ Flask_Cors pymongo Flask requests -ssdeep \ No newline at end of file +nilsimsa \ No newline at end of file diff --git a/services/api/webservice.py b/services/api/webservice.py index b49852a..4103eee 100755 --- a/services/api/webservice.py +++ b/services/api/webservice.py @@ -23,6 +23,8 @@ # along with Flower. If not, see . import traceback + +import nilsimsa from flask import Flask, Response, send_file from configurations import services, traffic_dir, start_date, tick_length @@ -33,7 +35,6 @@ from bson import json_util from flask_cors import CORS from flask import request -import ssdeep from flow2pwn import flow2pwn @@ -65,13 +66,13 @@ def getTickInfo(): def query(): filter = request.get_json() result = db.getFlowList(filter) - similarity = int(filter["similarity"]) if "similarity" in filter else 75 + similarity = int(filter["similarity"]) if "similarity" in filter else 112 - if "includeSsdeep" in filter: - result = [x for x in result if all(ssdeep.compare(hash, x["ssdeep"]) >= similarity for hash in filter["includeSsdeep"])] + if "includeFuzzyHashes" in filter: + result = [x for x in result if all(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) >= similarity for hash in filter["includeFuzzyHashes"])] - if "excludeSsdeep" in filter: - result = [x for x in result if all(ssdeep.compare(hash, x["ssdeep"]) < similarity for hash in filter["excludeSsdeep"])] + if "excludeFuzzyHashes" in filter: + result = [x for x in result if all(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) < similarity for hash in filter["excludeFuzzyHashes"])] return return_json_response(result) diff --git a/services/go-importer/cmd/assembler/main.go b/services/go-importer/cmd/assembler/main.go index b2d6d96..b81bba2 100644 --- a/services/go-importer/cmd/assembler/main.go +++ b/services/go-importer/cmd/assembler/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/glaslos/ssdeep" + "github.com/chikamim/nilsimsa" "go-importer/internal/pkg/db" "flag" @@ -71,11 +71,6 @@ var g_db db.Database var flagids []db.Flagid var flagidUpdate int64 = 0 -func init() { - // force ssdeep creation even for small data sizes - ssdeep.Force = true -} - // TODO; FIXME; RDJ; this is kinda gross, but this is PoC level code func reassemblyCallback(entry db.FlowEntry) { // Parsing HTTP will decode encodings to a plaintext format @@ -87,16 +82,13 @@ func reassemblyCallback(entry db.FlowEntry) { allData = append(allData[:], []byte(flowItem.Data)[:]...) } - fuzzyHash, err := ssdeep.FuzzyBytes(allData) - if err != nil { - log.Println("Error: ", err) - } + fuzzyHash := nilsimsa.HexSum(allData) // Apply flag in / flagout if *flag_regex != "" { ApplyFlagTags(&entry, flag_regex) } - entry.Ssdeep = fuzzyHash + entry.Fuzzy_hash = fuzzyHash //Apply flagid in / out if *flagid { diff --git a/services/go-importer/go.mod b/services/go-importer/go.mod index 5d249bd..3d593af 100644 --- a/services/go-importer/go.mod +++ b/services/go-importer/go.mod @@ -4,9 +4,9 @@ go 1.13 require ( github.com/andybalholm/brotli v1.0.4 + github.com/chikamim/nilsimsa v0.0.0-20200618014539-84540bbf06dd // indirect github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 github.com/fsnotify/fsnotify v1.5.4 - github.com/glaslos/ssdeep v0.3.3 github.com/google/gopacket v1.1.19 github.com/tidwall/gjson v1.14.1 go.mongodb.org/mongo-driver v1.9.1 diff --git a/services/go-importer/go.sum b/services/go-importer/go.sum index 3e11d1e..0d62ad0 100644 --- a/services/go-importer/go.sum +++ b/services/go-importer/go.sum @@ -1,5 +1,7 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/chikamim/nilsimsa v0.0.0-20200618014539-84540bbf06dd h1:7NiFDBnypHw4sI70R6+jpaExOEj9bA8wDY2GqTQcwvo= +github.com/chikamim/nilsimsa v0.0.0-20200618014539-84540bbf06dd/go.mod h1:Cc/DVLWhhAlZSuVPhfGw/2mUYLAPtGGNG0d6ha1H2pc= github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184 h1:8yL+85JpbwrIc6m+7N1iYrjn/22z68jwrTIBOJHNe4k= github.com/cloudflare/ahocorasick v0.0.0-20210425175752-730270c3e184/go.mod h1:tGWUZLZp9ajsxUOnHmFFLnqnlKXsCn6GReG4jAD59H0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -8,8 +10,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/glaslos/ssdeep v0.3.3 h1:6+/hcz3CcLKSZVOm/xbmcfCAuyVDhBAYFQxHHQR+ATA= -github.com/glaslos/ssdeep v0.3.3/go.mod h1:y1Kro2xRYIZdPkL9uQCf/aXtPEEf+TEHe91tlKU8Y0w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= diff --git a/services/go-importer/internal/pkg/db/db.go b/services/go-importer/internal/pkg/db/db.go index 8a00d72..641a972 100644 --- a/services/go-importer/internal/pkg/db/db.go +++ b/services/go-importer/internal/pkg/db/db.go @@ -46,7 +46,7 @@ type FlowEntry struct { Size int Flags []string Flagids []string - Ssdeep string + Fuzzy_hash string } type Database struct { From 83413663fef107d059792f590e695cb5330ae4ce Mon Sep 17 00:00:00 2001 From: gfelber <@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:06:16 +0200 Subject: [PATCH 08/10] added similarity gradient --- frontend/src/components/FlowList.tsx | 15 ++++++++++++++- frontend/src/components/Header.tsx | 10 +++++----- frontend/src/types.ts | 1 + 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index d540914..19f2605 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -206,11 +206,24 @@ function FlowListEntry({ flow, isActive, onHeartClick }: FlowListEntryProps) { ) : (
{flow.duration}ms
); - return ( + + + const DEFAULT = [243, 244, 246] + const GREEN = [134, 239, 172] + const RED = [252, 165, 165] + + var color: number[] + if (flow.similarity != undefined) + color = GREEN.map((g, i) => ((g*flow.similarity) + (RED[i]*(1-flow.similarity)))); + else + color = DEFAULT; + + return (
  • { @@ -294,8 +294,8 @@ function SimilaritySlider() { @@ -303,8 +303,8 @@ function SimilaritySlider() { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d8ee21c..8a3413b 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -17,6 +17,7 @@ export interface Flow { suricata: number[]; filename: string; fuzzy_hash: string; + similarity: number } export interface TickInfo { From 8cb20b7284f7fe07ca90f652f07023bca8bdc8e8 Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sun, 12 May 2024 10:09:59 +0200 Subject: [PATCH 09/10] fixed webservice --- services/api/webservice.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/services/api/webservice.py b/services/api/webservice.py index 4103eee..9366542 100755 --- a/services/api/webservice.py +++ b/services/api/webservice.py @@ -66,13 +66,22 @@ def getTickInfo(): def query(): filter = request.get_json() result = db.getFlowList(filter) - similarity = int(filter["similarity"]) if "similarity" in filter else 112 + similarity = int(filter["similarity"]) if "similarity" in filter else 90 + + # adapt similarity to nilsimsa compare value + similarity = (similarity*256/100) - 128 if "includeFuzzyHashes" in filter: result = [x for x in result if all(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) >= similarity for hash in filter["includeFuzzyHashes"])] + for x in result: + x['similarity'] = ((sum(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) for hash in filter["includeFuzzyHashes"]) / len(filter["includeFuzzyHashes"]))+128)/256 if "excludeFuzzyHashes" in filter: result = [x for x in result if all(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) < similarity for hash in filter["excludeFuzzyHashes"])] + for x in result: + similarity = ((-sum(nilsimsa.compare_digests(hash, x["fuzzy_hash"]) for hash in filter["excludeFuzzyHashes"]) / len(filter["excludeFuzzyHashes"]))+128)/256 + x['similarity'] = (similarity + x['similarity'])/2 if 'similarity' in x else similarity + return return_json_response(result) From d48c3f0116fe8866d3105ab52b8316bf86c2ec10 Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Mon, 13 May 2024 12:10:31 +0200 Subject: [PATCH 10/10] fixed merge conflicts and reset old favicon --- frontend/public/favicon.ico | Bin 16958 -> 114133 bytes frontend/src/components/FlowList.tsx | 11 +- frontend/src/components/Header.tsx | 2 - frontend/src/pages/FlowView.tsx | 200 +++++++++++---------------- 4 files changed, 82 insertions(+), 131 deletions(-) diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index ca84cf2de94213bcdb3b09891ead3f280d185191..c1e0423bb0e66afd82b4ce09125385ceb65158a7 100644 GIT binary patch literal 114133 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 literal 16958 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 BF*X1I diff --git a/frontend/src/components/FlowList.tsx b/frontend/src/components/FlowList.tsx index cb08200..6351181 100644 --- a/frontend/src/components/FlowList.tsx +++ b/frontend/src/components/FlowList.tsx @@ -240,16 +240,7 @@ export function FlowList() { [classes.list_container]: true, "sidebar-loading": isLoading, })} - data={transformedFlowData} - ref={virtuoso} - initialTopMostItemIndex={flowIndex} - itemContent={(index, flow) => ( - + > diff --git a/frontend/src/pages/FlowView.tsx b/frontend/src/pages/FlowView.tsx index 1a1979a..aa0b659 100644 --- a/frontend/src/pages/FlowView.tsx +++ b/frontend/src/pages/FlowView.tsx @@ -27,10 +27,6 @@ import { useToSinglePythonRequestQuery, useGetFlagRegexQuery, } from "../api"; -import { - API_BASE_PATH, - TEXT_FILTER_KEY -} from "../const"; import {toggleFilterFuzzyHashes, toggleFilterTag} from "../store/filter"; import {useAppDispatch, useAppSelector} from "../store"; import escapeStringRegexp from 'escape-string-regexp'; @@ -309,113 +305,108 @@ function FlowOverview({ flow }: { flow: FullFlow }) { let [searchParams, setSearchParams] = useSearchParams(); const dispatch = useAppDispatch(); return ( -
    - {flow.signatures?.length > 0 ? ( -
    -
    Suricata
    +
    + {flow.signatures?.length > 0 ? ( +
    +
    Suricata
    +
    + {flow.signatures.map((sig) => { + return ( +
    +
    +
    Message:
    +
    {sig.msg}
    +
    +
    +
    Rule ID:
    +
    {sig.id}
    +
    +
    +
    Action taken:
    +
    + {sig.action} +
    +
    +
    + ); + })} +
    +
    + ) : undefined} +
    +
    Meta
    - {flow.signatures.map((sig) => { - return ( -
    -
    -
    Message:
    -
    {sig.msg}
    -
    -
    -
    Rule ID:
    -
    {sig.id}
    -
    -
    -
    Action taken:
    -
    - {sig.action} -
    -
    -
    - ); - })} -
    -
    - ) : undefined} -
    -
    Meta
    -
    -
    Source:
    - -
    -
    Tags:
    -
    - [{flow.tags.map((tag, i) => ( +
    Source:
    + +
    +
    Tags:
    +
    + [{flow.tags.map((tag, i) => ( {i > 0 ? ', ' : ''} dispatch(toggleFilterTag(tag))}> + onClick={() => dispatch(toggleFilterTag(tag))}> {tag} - ))}] + ))}]
    Flags:
    - [{flow.flags.map((query, i) => ( - + [{flow.flags.map((query, i) => ( + {i > 0 ? ', ' : ''} -
    -
    -
    Flagids:
    -
    - [{flow.flagids.map((query, i) => ( - + ))}] +
    +
    +
    Flagids:
    +
    +
    Nilsimsa hash:
    + +
    Source - Target (Duration):
    @@ -436,34 +427,6 @@ function FlowOverview({ flow }: { flow: FullFlow }) {
    ); - ))}] -
    -
    -
    Nilsimsa hash:
    - -
    -
    Source - Target:
    -
    -
    - {" "} - {formatIP(flow.src_ip)}: - {flow.src_port} -
    -
    -
    -
    - {formatIP(flow.dst_ip)}: - {flow.dst_port} -
    -
    -
    -
    -
    -); } export function FlowView() { @@ -473,7 +436,6 @@ export function FlowView() { const id = params.id; - const {data: flow} = useGetFlowQuery(id!, {skip: id === undefined}); const { data: flow, isError, isLoading } = useGetFlowQuery(id!, { skip: id === undefined }); const [triggerPwnToolsQuery] = useLazyToPwnToolsQuery(); @@ -566,7 +528,7 @@ export function FlowView() { onMouseDown={(e) => { if( e.button === 1 ) { // handle opening in new tab window.open(`/flow/${flow.parent_id.$oid}?${searchParams}`, '_blank') - } else if (e.button === 0) { + } else if (e.button === 0) { navigate(`/flow/${flow.parent_id.$oid}?${searchParams}`) } }}