From ee28cb94a63745e33bd423d999f7bd93a9be203e Mon Sep 17 00:00:00 2001 From: by2waysprojects Date: Wed, 27 Nov 2024 00:32:40 +0100 Subject: [PATCH] first commit --- .env | 4 + cmd/main.go | 36 +++++++ controllers/controller.go | 49 +++++++++ go.mod | 13 +++ go.sum | 8 ++ model/shell_code.go | 14 +++ routes/router.go | 19 ++++ services/data_loader_service.go | 41 ++++++++ services/exploit_db_service.go | 64 ++++++++++++ services/model/shell_code_exploit_db.go | 10 ++ services/neo4j_service.go | 127 ++++++++++++++++++++++++ 11 files changed, 385 insertions(+) create mode 100644 .env create mode 100644 cmd/main.go create mode 100644 controllers/controller.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 model/shell_code.go create mode 100644 routes/router.go create mode 100644 services/data_loader_service.go create mode 100644 services/exploit_db_service.go create mode 100644 services/model/shell_code_exploit_db.go create mode 100644 services/neo4j_service.go diff --git a/.env b/.env new file mode 100644 index 0000000..2c1f044 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +NEO4J_DB=neo4j://localhost:7687 +NEO4J_USER=neo4j +NEO4J_PASSWORD=password +SERVER_PORT=8080 \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..7a477fb --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "log" + "net/http" + "os" + "shellcode-db/controllers" + "shellcode-db/routes" + "shellcode-db/services" + + "github.com/joho/godotenv" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatalf("error loading file .env: %v", err) + } + + port := os.Getenv("SERVER_PORT") + databaseURL := os.Getenv("NEO4J_DB") + user := os.Getenv("NEO4J_USER") + password := os.Getenv("NEO4J_PASSWORD") + + neo4jService := services.NewNeo4jService(databaseURL, user, password) + defer neo4jService.Close() + + apiService := services.NewExploitDbService() + dataLoaderService := services.NewDataLoaderService(apiService, neo4jService) + shellCodeController := controllers.NewShellCodeController(neo4jService, dataLoaderService) + + router := routes.SetupRoutes(shellCodeController) + + log.Println("Server running on http://localhost:" + port) + log.Fatal(http.ListenAndServe(":"+port, router)) +} diff --git a/controllers/controller.go b/controllers/controller.go new file mode 100644 index 0000000..d9c4e3b --- /dev/null +++ b/controllers/controller.go @@ -0,0 +1,49 @@ +package controllers + +import ( + "encoding/json" + "net/http" + "shellcode-db/services" + + "github.com/gorilla/mux" +) + +type ShellCodeController struct { + DBService *services.Neo4jService + DataLoaderService *services.DataLoaderService +} + +func NewShellCodeController(dbService *services.Neo4jService, dataLoaderService *services.DataLoaderService) *ShellCodeController { + return &ShellCodeController{DBService: dbService, DataLoaderService: dataLoaderService} +} + +func (sc *ShellCodeController) GetAllArchitectures(w http.ResponseWriter, r *http.Request) { + archiectures, err := sc.DBService.GetAllArchitectures() + if err != nil { + http.Error(w, "Failed to fetch architectures", http.StatusInternalServerError) + return + } + json.NewEncoder(w).Encode(archiectures) +} + +func (sc *ShellCodeController) GetShellcodesByArchitectureID(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + architectureID := vars["id"] + + shellcodes, err := sc.DBService.GetShellcodesByArchitectureID(architectureID) + if err != nil { + http.Error(w, "Failed to fetch shellcodes", http.StatusInternalServerError) + return + } + json.NewEncoder(w).Encode(shellcodes) +} + +func (sc *ShellCodeController) LoadData(w http.ResponseWriter, r *http.Request) { + err := sc.DataLoaderService.LoadData() + if err != nil { + http.Error(w, "Failed to load data", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) + w.Write([]byte("Data successfully loaded")) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..03acd69 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module shellcode-db + +go 1.22.4 + +require ( + github.com/gorilla/mux v1.8.1 + github.com/neo4j/neo4j-go-driver/v5 v5.26.0 +) + +require ( + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 // indirect + github.com/joho/godotenv v1.5.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..075f329 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/neo4j/neo4j-go-driver/v5 v5.26.0 h1:GB3o4VtIGsvU+RmfgvF7L6nt1IpbPZaGtPMtPSOKmvc= +github.com/neo4j/neo4j-go-driver/v5 v5.26.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k= diff --git a/model/shell_code.go b/model/shell_code.go new file mode 100644 index 0000000..62fe93c --- /dev/null +++ b/model/shell_code.go @@ -0,0 +1,14 @@ +package model + +type Architecture struct { + ID string `json:"id"` + Name string `json:"name"` + Shellcodes []Shellcode `json:"shellcodes"` +} + +type Shellcode struct { + ID string `json:"id"` + Name string `json:"name"` + DatePublished string `json:"date_published"` + Data string `json:"data"` +} diff --git a/routes/router.go b/routes/router.go new file mode 100644 index 0000000..6b346ec --- /dev/null +++ b/routes/router.go @@ -0,0 +1,19 @@ +package routes + +import ( + "shellcode-db/controllers" + + "github.com/gorilla/mux" +) + +func SetupRoutes( + shellCodeController *controllers.ShellCodeController, +) *mux.Router { + router := mux.NewRouter() + + router.HandleFunc("/architectures", shellCodeController.GetAllArchitectures).Methods("GET") + router.HandleFunc("/architectures/{id}/shellcodes", shellCodeController.GetShellcodesByArchitectureID).Methods("GET") + router.HandleFunc("/load-data", shellCodeController.LoadData).Methods("POST") + + return router +} diff --git a/services/data_loader_service.go b/services/data_loader_service.go new file mode 100644 index 0000000..ee773b2 --- /dev/null +++ b/services/data_loader_service.go @@ -0,0 +1,41 @@ +package services + +import ( + "log" +) + +type DataLoaderService struct { + APIService *APIService + Neo4jService *Neo4jService +} + +func NewDataLoaderService(apiService *APIService, neo4jService *Neo4jService) *DataLoaderService { + return &DataLoaderService{ + APIService: apiService, + Neo4jService: neo4jService, + } +} + +func (dl *DataLoaderService) LoadData() error { + // Fetch Architectures from API + architectures, err := dl.APIService.FetchArchitectures() + if err != nil { + return err + } + + // Insert Architectures into Neo4j + for _, architecture := range architectures { + err := dl.Neo4jService.CreateArchitecture(architecture) + if err != nil { + log.Printf("Failed to insert architecture %s: %v", architecture.ID, err) + continue + } + + for _, shellcode := range architecture.Shellcodes { + if err := dl.Neo4jService.CreateShellcodeWithArchitecture(architecture.ID, shellcode); err != nil { + log.Printf("Failed to insert shellcode %s: %v", shellcode.ID, err) + } + } + } + return nil +} diff --git a/services/exploit_db_service.go b/services/exploit_db_service.go new file mode 100644 index 0000000..b757916 --- /dev/null +++ b/services/exploit_db_service.go @@ -0,0 +1,64 @@ +package services + +import ( + "fmt" + "io" + "net/http" + "shellcode-db/model" + services "shellcode-db/services/model" + + "github.com/gocarina/gocsv" +) + +type APIService struct{} + +func NewExploitDbService() *APIService { + return &APIService{} +} + +func (api *APIService) FetchArchitectures() ([]model.Architecture, error) { + url := "https://gitlab.com/exploit-database/exploitdb/-/raw/main/files_shellcodes.csv" + response, err := http.Get(url) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode) + } + + var shellcodesExploitDB []services.Shellcode + body, _ := io.ReadAll(response.Body) + + if err := gocsv.UnmarshalBytes(body, &shellcodesExploitDB); err != nil { + return nil, err + } + + var shellcodes []model.Architecture + mapArchitectureShell := map[string][]model.Shellcode{} + + for _, shell := range shellcodesExploitDB { + fileUrl := fmt.Sprintf("https://gitlab.com/exploit-database/exploitdb/-/raw/main/%s", shell.File) + response, err := http.Get(fileUrl) + if err != nil { + continue + } + + if response.StatusCode != http.StatusOK { + continue + } + + body, _ := io.ReadAll(response.Body) + response.Body.Close() + + mapArchitectureShell[shell.Platform] = append(mapArchitectureShell[shell.Platform], + model.Shellcode{ID: shell.Id, Name: shell.Description, DatePublished: shell.DatePublished, Data: string(body)}) + } + + for key, value := range mapArchitectureShell { + shellcodes = append(shellcodes, model.Architecture{ID: key, Name: key, Shellcodes: value}) + } + + return shellcodes, nil +} diff --git a/services/model/shell_code_exploit_db.go b/services/model/shell_code_exploit_db.go new file mode 100644 index 0000000..e7b704e --- /dev/null +++ b/services/model/shell_code_exploit_db.go @@ -0,0 +1,10 @@ +package services + +type Shellcode struct { + Id string `csv:"id"` + Platform string `csv:"platform"` + SourceURL string `csv:"source_url"` + DatePublished string `csv:"date_published"` + Description string `csv:"description"` + File string `csv:"file"` +} diff --git a/services/neo4j_service.go b/services/neo4j_service.go new file mode 100644 index 0000000..e179ded --- /dev/null +++ b/services/neo4j_service.go @@ -0,0 +1,127 @@ +package services + +import ( + "context" + "log" + "shellcode-db/model" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +type Neo4jService struct { + Driver neo4j.DriverWithContext +} + +func NewNeo4jService(uri, username, password string) *Neo4jService { + driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(username, password, "")) + if err != nil { + log.Fatalf("Failed to create Neo4j driver: %v", err) + } + return &Neo4jService{Driver: driver} +} + +func (s *Neo4jService) Close() { + s.Driver.Close(context.Background()) +} + +func (s *Neo4jService) GetAllArchitectures() ([]model.Architecture, error) { + ctx := context.Background() + session := s.Driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + architectures := []model.Architecture{} + _, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { + result, err := tx.Run(ctx, "MATCH (p:Architecture) RETURN p.id AS id, p.name AS name", nil) + if err != nil { + return nil, err + } + + for result.Next(ctx) { + record := result.Record() + architecture := model.Architecture{ + ID: record.Values[0].(string), + Name: record.Values[1].(string), + } + architectures = append(architectures, architecture) + } + return nil, result.Err() + }) + + return architectures, err +} + +func (s *Neo4jService) GetShellcodesByArchitectureID(architectureID string) ([]model.Shellcode, error) { + ctx := context.Background() + session := s.Driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeRead}) + defer session.Close(ctx) + + shellcodes := []model.Shellcode{} + _, err := session.ExecuteRead(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { + query := ` + MATCH (p:Architecture {id: $architectureID})-[:HAS_CHILD]->(c:Shellcode) + RETURN c.id AS id, c.name AS name, c.datePublished as datePublished, c.data as data + ` + params := map[string]interface{}{"architectureID": architectureID} + result, err := tx.Run(ctx, query, params) + if err != nil { + return nil, err + } + + for result.Next(ctx) { + record := result.Record() + shellcode := model.Shellcode{ + ID: record.Values[0].(string), + Name: record.Values[1].(string), + DatePublished: record.Values[2].(string), + Data: record.Values[3].(string), + } + shellcodes = append(shellcodes, shellcode) + } + return nil, result.Err() + }) + + return shellcodes, err +} + +func (s *Neo4jService) CreateArchitecture(architecture model.Architecture) error { + ctx := context.Background() + session := s.Driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + _, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { + query := ` + MERGE (p:Architecture {id: $id}) + SET p.name = $name + ` + params := map[string]interface{}{"id": architecture.ID, "name": architecture.Name} + _, err := tx.Run(ctx, query, params) + return nil, err + }) + return err +} + +func (s *Neo4jService) CreateShellcodeWithArchitecture(architectureID string, shellcode model.Shellcode) error { + ctx := context.Background() + session := s.Driver.NewSession(ctx, neo4j.SessionConfig{AccessMode: neo4j.AccessModeWrite}) + defer session.Close(ctx) + + _, err := session.ExecuteWrite(ctx, func(tx neo4j.ManagedTransaction) (interface{}, error) { + query := ` + MERGE (c:Shellcode {id: $shellcodeID}) + SET c.name = $shellcodeName, c.datePublished = $datePublished, c.data = $data + WITH c + MATCH (p:Architecture {id: $architectureID}) + MERGE (p)-[:HAS_CHILD]->(c) + ` + params := map[string]interface{}{ + "architectureID": architectureID, + "shellcodeID": shellcode.ID, + "shellcodeName": shellcode.Name, + "datePublished": shellcode.DatePublished, + "data": shellcode.Data, + } + _, err := tx.Run(ctx, query, params) + return nil, err + }) + return err +}