├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── config.go ├── config.yaml ├── database.go ├── go.mod ├── go.sum ├── log.go ├── main.go ├── responders ├── email.sh ├── email.template ├── iptables_block.sh ├── slack.sh ├── sms.py ├── splunk_hec.sh ├── syslog.sh └── webhook-generic.sh ├── server.go ├── templates ├── owa.html └── phpMyAdmin.html ├── validate.go └── web ├── assets └── logo.png ├── css └── styles.css ├── manage.html └── scripts └── script.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: paypal.me/referref 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 James Brine 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Active Development](https://img.shields.io/badge/Maintenance%20Level-Actively%20Developed-brightgreen.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d) 2 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/esta/issues) 3 | 4 | # modpot v0.3.11 (Not for workgroups) 5 | ![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white)![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white)![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) 6 | 7 | ![modpot-wide](https://github.com/referefref/modpot/assets/56499429/409d59b0-9e14-45f7-abdc-abe721e0e678) 8 | ![image](https://github.com/referefref/modpot/assets/56499429/f13d4e39-becd-48da-aee1-29e21b921ce4) 9 | 10 | modpot is a modular web application honeypot framework written in Golang and making use of gin framework. 11 | It is the antithesis to [***honeydet***](https://github.com/referefref/honeydet) in many ways and allows the user to deploy simple html/js honeypots that mimic web applications in order to detect requests and form entries that are related to attacks. Responders offer a modular capacity for automation and logging pipelines and are not limited by programming language. 12 | modpot is best utilised alongside [***honeypage***](https://github.com/referefref/honeypage) a tool that creates flattened single html file versions of web applications, which makes them portable and easy to use with modpot. 13 | 14 | ## Responders 15 | ![image](https://github.com/referefref/modpot/assets/56499429/c0f09791-3ebc-4159-b47e-a1669485f29d) 16 | 17 | Responders allow for simple triggering of automation, logging, or connection to SOC platforms. 18 | The parameters that can be passed to responders are ID, Application, Datetime, IP Source, Log Event. 19 | 20 | Included are the following examples: 21 | * Email 22 | * iptables - time window blocking 23 | * SMS (Using twilio) 24 | * Slack - webhook 25 | * Syslog 26 | * Splunk - HEC endpoint 27 | * Webhook-generic 28 | 29 | ## Example config 30 | ```yaml 31 | honeypots: 32 | - id: 1 33 | name: "ExampleHoneypot1" 34 | cve: "CVE-2021-XXXX" 35 | application: "FakeWebApp1" 36 | port: 8081 37 | enabled: true 38 | template_html_file: "index1.html" 39 | detection_endpoint: "/fakeapp" 40 | request_regex: ".*attack.*" 41 | redirect_url: "https://jamesbrine.com.au/" 42 | date_created: "2022-01-01" 43 | date_updated: "2022-01-02" 44 | responders: 45 | - engine: "/usr/bin/bash" 46 | script: "email.sh" 47 | parameters: ["honeypots.id", "honeypots.application", "honeypot_logs.datetime", "honeypot_logs.ip_source", "honeypot_logs.log_event"] 48 | - engine: python3 49 | script: sms.py 50 | parameters: ["honeypots.id", "honeypots.application", "honeypot_logs.datetime", "honeypot_logs.ip_source", "honeypot_logs.log_event"] 51 | - engine: "/usr/bin/bash" 52 | script: "iptables_block.sh" 53 | parameters: ["honeypot_logs.ip_source"] 54 | 55 | - id: 2 56 | name: "ExampleHoneypot2" 57 | cve: "CVE-2022-YYYY" 58 | application: "FakeWebApp2" 59 | port: 8082 60 | enabled: true 61 | template_html_file: "index2.html" 62 | detection_endpoint: "/anotherapp" 63 | request_regex: "^/admin" 64 | redirect_url: "test.html" 65 | date_created: "2022-02-01" 66 | date_updated: "2022-02-02" 67 | ``` 68 | 69 | ## Note 70 | I am not responsible for your use/mis-use of this application. It was created for research purposes and is not intended nor do I or it's existence give implicit authority to use it as a phishing tool or for any other malicious purposes. Now, with the boring stuff out of the way. 71 | 72 | ## Todo 73 | * Tidy up web frontend and features (search, filter, paginate etc.) 74 | * Allow for multi-step configs for more complex honeypages 75 | * Set up reporting/alerting on match and allow configuration through web frontend 76 | * Build honeypage into modpot, allowing the page download process to take place through the "Add honeypot" button in the main interface 77 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/yaml.v2" 6 | "io/ioutil" 7 | ) 8 | 9 | type AppConfig struct { 10 | Honeypots []HoneypotConfig `yaml:"honeypots"` 11 | } 12 | 13 | func LoadConfigurations(configPath string) (*AppConfig, error) { 14 | logInfo("Loading configuration from YAML file: " + configPath) 15 | 16 | configFile, err := ioutil.ReadFile(configPath) 17 | if err != nil { 18 | logError("Failed to read configuration file: " + err.Error()) 19 | return nil, err 20 | } 21 | 22 | var appConfig AppConfig 23 | err = yaml.Unmarshal(configFile, &appConfig) 24 | if err != nil { 25 | logError("Failed to unmarshal YAML configuration: " + err.Error()) 26 | return nil, err 27 | } 28 | 29 | logInfo(fmt.Sprintf("Found %d configurations in YAML file.", len(appConfig.Honeypots))) 30 | 31 | for _, config := range appConfig.Honeypots { 32 | logInfo("Processing configuration for: " + config.Name) 33 | 34 | if err := validateHoneypotConfig(config); err != nil { 35 | logError("Validation failed for " + config.Name + ": " + err.Error()) 36 | continue 37 | } 38 | 39 | existingConfig, err := SelectHoneypotConfig(config.ID) 40 | if err != nil || existingConfig == nil { 41 | logInfo("Inserting new configuration for: " + config.Name) 42 | err = InsertHoneypotConfig(&config) 43 | if err != nil { 44 | logError("Failed to insert configuration for " + config.Name + ": " + err.Error()) 45 | continue 46 | } 47 | logSuccess("Successfully inserted configuration for: " + config.Name) 48 | } else { 49 | logInfo("Updating existing configuration for: " + config.Name) 50 | err = UpdateHoneypotConfig(&config) 51 | if err != nil { 52 | logError("Failed to update configuration for " + config.Name + ": " + err.Error()) 53 | continue 54 | } 55 | logSuccess("Successfully updated configuration for: " + config.Name) 56 | } 57 | } 58 | 59 | return &appConfig, nil 60 | } 61 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | honeypots: 2 | - id: 1 3 | name: "ExampleHoneypot1" 4 | cve: "CVE-2021-XXXX" 5 | application: "FakeWebApp1" 6 | port: 8081 7 | enabled: true 8 | template_html_file: "index1.html" 9 | detection_endpoint: "/fakeapp" 10 | request_regex: ".*attack.*" 11 | redirect_url: "https://jamesbrine.com.au/" 12 | date_created: "2022-01-01" 13 | date_updated: "2022-01-02" 14 | responders: 15 | - engine: "/usr/bin/bash" 16 | script: "email.sh" 17 | parameters: ["honeypots.id", "honeypots.application", "honeypot_logs.datetime", "honeypot_logs.ip_source", "honeypot_logs.log_event"] 18 | - engine: "/usr/bin/python3" 19 | script: "sms.py" 20 | parameters: ["honeypots.id", "honeypots.application", "honeypot_logs.datetime", "honeypot_logs.ip_source", "honeypot_logs.log_event"] 21 | - engine: "/usr/bin/bash" 22 | script: "iptables_block.sh" 23 | parameters: ["honeypot_logs.ip_source"] 24 | 25 | - id: 2 26 | name: "ExampleHoneypot2" 27 | cve: "CVE-2022-YYYY" 28 | application: "FakeWebApp2" 29 | port: 8082 30 | enabled: true 31 | template_html_file: "index2.html" 32 | detection_endpoint: "/anotherapp" 33 | request_regex: "^/admin" 34 | redirect_url: "test.html" 35 | date_created: "2022-02-01" 36 | date_updated: "2022-02-02" 37 | 38 | - id: 3 39 | name: "SMRS Portal" 40 | cve: "" 41 | application: "SMRS Portal" 42 | port: 8003 43 | template_html_file: "smrs.html" 44 | detection_endpoint: "Login" 45 | request_regex: "^.*1=1.*$" 46 | date_created: "2024-02-17" 47 | date_updated: "2024-02-17" 48 | redirect_url: "https://jamesbrine.com.au" 49 | enabled: true 50 | 51 | - id: 4 52 | name: "phpMyAdmin Login" 53 | cve: "" 54 | application: "phpMyAdmin" 55 | port: 8444 56 | template_html_file: "phpMyAdmin.html" 57 | detection_endpoint: "/phpMyAdmin/login" 58 | request_regex: "^username=.*&password=.*&$" 59 | date_created: "2024-03-12" 60 | date_updated: "2024-03-12" 61 | redirect_url: "302.php" 62 | enabled: true 63 | 64 | - id: 5 65 | name: "Outlook Web Application" 66 | cve: "" 67 | application: "OWA" 68 | port: 8445 69 | template_html_file: "owa.html" 70 | detection_endpoint: "logon.aspx" 71 | request_regex: "^username=.*&password=.*&$" 72 | date_created: "2024-03-12" 73 | date_updated: "2024-03-12" 74 | redirect_url: "302.php" 75 | enabled: true 76 | -------------------------------------------------------------------------------- /database.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | _ "github.com/mattn/go-sqlite3" 9 | "net/http" 10 | "strconv" 11 | ) 12 | 13 | var db *sql.DB 14 | 15 | type HoneypotConfig struct { 16 | ID int `yaml:"id"` 17 | Name string `yaml:"name"` 18 | CVE string `yaml:"cve"` 19 | Application string `yaml:"application"` 20 | Port int `yaml:"port"` 21 | TemplateHTMLFile string `yaml:"template_html_file"` 22 | DetectionEndpoint string `yaml:"detection_endpoint"` 23 | RequestRegex string `yaml:"request_regex"` 24 | Responders []ResponderConfig `yaml:"responders"` 25 | DateCreated string `yaml:"date_created"` 26 | DateUpdated string `yaml:"date_updated"` 27 | RedirectURL string `yaml:"redirect_url"` 28 | Enabled bool `yaml:"enabled"` 29 | } 30 | 31 | type ResponderConfig struct { 32 | Engine string `yaml:"engine"` 33 | Script string `yaml:"script"` 34 | Parameters []string `yaml:"parameters"` 35 | } 36 | 37 | func InitDB(filepath string) { 38 | var err error 39 | db, err = sql.Open("sqlite3", filepath) 40 | if err != nil { 41 | logError("Failed to open database: " + err.Error()) 42 | return 43 | } 44 | logInfo("Database opened successfully") 45 | 46 | createTable := ` 47 | CREATE TABLE IF NOT EXISTS honeypots ( 48 | id INTEGER PRIMARY KEY AUTOINCREMENT, 49 | name TEXT, 50 | cve TEXT, 51 | application TEXT, 52 | port INTEGER, 53 | template_html_file TEXT, 54 | detection_endpoint TEXT, 55 | request_regex TEXT, 56 | date_created TEXT, 57 | date_updated TEXT, 58 | redirect_url TEXT, 59 | responders TEXT, 60 | enabled BOOLEAN DEFAULT true 61 | );` 62 | 63 | _, err = db.Exec(createTable) 64 | if err != nil { 65 | logError("Failed to create honeypots table: " + err.Error()) 66 | return 67 | } 68 | logInfo("Honeypots table created or already exists") 69 | 70 | createLogsTable := ` 71 | CREATE TABLE IF NOT EXISTS honeypot_logs ( 72 | id INTEGER PRIMARY KEY AUTOINCREMENT, 73 | honeypotID INTEGER, 74 | port INTEGER, 75 | datetime TEXT, 76 | ip_source TEXT, 77 | ip_destination TEXT, 78 | log_event TEXT, 79 | regex_match TEXT, 80 | FOREIGN KEY(honeypotID) REFERENCES honeypots(id) 81 | );` 82 | 83 | _, err = db.Exec(createLogsTable) 84 | if err != nil { 85 | logError("Failed to create honeypot_logs table: " + err.Error()) 86 | return 87 | } 88 | logInfo("Honeypot_logs table created or already exists") 89 | } 90 | 91 | func SelectHoneypotConfig(id int) (*HoneypotConfig, error) { 92 | config := &HoneypotConfig{} 93 | var redirectURLPtr *string 94 | var respondersJSON string 95 | 96 | query := `SELECT id, name, cve, application, port, template_html_file, detection_endpoint, request_regex, date_created, date_updated, redirect_url, responders, enabled FROM honeypots WHERE id = ?` 97 | err := db.QueryRow(query, id).Scan( 98 | &config.ID, 99 | &config.Name, 100 | &config.CVE, 101 | &config.Application, 102 | &config.Port, 103 | &config.TemplateHTMLFile, 104 | &config.DetectionEndpoint, 105 | &config.RequestRegex, 106 | &config.DateCreated, 107 | &config.DateUpdated, 108 | &redirectURLPtr, 109 | &respondersJSON, 110 | &config.Enabled, 111 | ) 112 | 113 | if err != nil { 114 | if err == sql.ErrNoRows { 115 | logInfo(fmt.Sprintf("Honeypot configuration for ID %d not found in database, inserting.", id)) 116 | return nil, nil 117 | } else { 118 | logError(fmt.Sprintf("Failed to select config for ID %d: %s", id, err.Error())) 119 | return nil, err 120 | } 121 | } 122 | 123 | if redirectURLPtr != nil { 124 | config.RedirectURL = *redirectURLPtr 125 | logInfo(fmt.Sprintf("Redirect URL for config ID %d is '%s'", id, config.RedirectURL)) 126 | } else { 127 | config.RedirectURL = "" 128 | logInfo(fmt.Sprintf("Redirect URL for config ID %d is NULL or empty", id)) 129 | } 130 | 131 | if respondersJSON != "" { 132 | var responders []ResponderConfig 133 | if err := json.Unmarshal([]byte(respondersJSON), &responders); err != nil { 134 | logError("Failed to unmarshal responders: " + err.Error()) 135 | } else { 136 | config.Responders = responders 137 | } 138 | } 139 | 140 | logInfo(fmt.Sprintf("Successfully selected config: %s (ID %d)", config.Name, config.ID)) 141 | return config, nil 142 | } 143 | 144 | func InsertHoneypotConfig(config *HoneypotConfig) error { 145 | respondersJSON, err := json.Marshal(config.Responders) 146 | if err != nil { 147 | logError("Failed to serialize responders: " + err.Error()) 148 | return err 149 | } 150 | 151 | insertSQL := `INSERT INTO honeypots(name, cve, application, port, template_html_file, detection_endpoint, request_regex, date_created, date_updated, redirect_url, responders, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` 152 | 153 | _, err = db.Exec(insertSQL, config.Name, config.CVE, config.Application, config.Port, config.TemplateHTMLFile, config.DetectionEndpoint, config.RequestRegex, config.DateCreated, config.DateUpdated, config.RedirectURL, string(respondersJSON), config.Enabled) 154 | if err != nil { 155 | logError("Failed to insert config: " + err.Error()) 156 | return err 157 | } 158 | logInfo("Config inserted successfully") 159 | return nil 160 | } 161 | 162 | func UpdateHoneypotConfig(config *HoneypotConfig) error { 163 | respondersJSON, err := json.Marshal(config.Responders) 164 | if err != nil { 165 | logError("Failed to serialize responders: " + err.Error()) 166 | return err 167 | } 168 | 169 | updateSQL := `UPDATE honeypots SET 170 | name = ?, 171 | cve = ?, 172 | application = ?, 173 | port = ?, 174 | template_html_file = ?, 175 | detection_endpoint = ?, 176 | request_regex = ?, 177 | date_created = ?, 178 | date_updated = ?, 179 | redirect_url = ?, 180 | responders = ?, 181 | enabled = ? 182 | WHERE id = ?` 183 | 184 | statement, err := db.Prepare(updateSQL) 185 | if err != nil { 186 | logError("Failed to prepare config update: " + err.Error()) 187 | return err 188 | } 189 | 190 | _, err = statement.Exec( 191 | config.Name, 192 | config.CVE, 193 | config.Application, 194 | config.Port, 195 | config.TemplateHTMLFile, 196 | config.DetectionEndpoint, 197 | config.RequestRegex, 198 | config.DateCreated, 199 | config.DateUpdated, 200 | config.RedirectURL, 201 | string(respondersJSON), 202 | config.Enabled, 203 | config.ID, 204 | ) 205 | if err != nil { 206 | logError("Failed to update config: " + err.Error()) 207 | return err 208 | } 209 | 210 | logInfo(fmt.Sprintf("Config '%s' updated successfully", config.Name)) 211 | return nil 212 | } 213 | 214 | func DeleteHoneypotConfig(id int) error { 215 | deleteSQL := `DELETE FROM honeypots WHERE id = ?` 216 | statement, err := db.Prepare(deleteSQL) 217 | if err != nil { 218 | logError("Failed to prepare config deletion: " + err.Error()) 219 | return err 220 | } 221 | _, err = statement.Exec(id) 222 | if err != nil { 223 | logError("Failed to delete config: " + err.Error()) 224 | return err 225 | } 226 | logInfo(fmt.Sprintf("Config with ID %d deleted successfully", id)) 227 | return nil 228 | } 229 | 230 | type HoneypotLog struct { 231 | ID int 232 | HoneypotID int 233 | Port int 234 | Datetime string 235 | IPSource string 236 | IPDestination string 237 | LogEvent string 238 | RegexMatch string `json:"regex_match"` 239 | } 240 | 241 | func InsertHoneypotLog(log *HoneypotLog) (int64, error) { 242 | insertSQL := `INSERT INTO honeypot_logs(honeypotID, port, datetime, ip_source, ip_destination, log_event, regex_match) VALUES (?, ?, ?, ?, ?, ?, ?)` 243 | result, err := db.Exec(insertSQL, log.HoneypotID, log.Port, log.Datetime, log.IPSource, log.IPDestination, log.LogEvent, log.RegexMatch) 244 | if err != nil { 245 | logError("Failed to insert log: " + err.Error()) 246 | return 0, err 247 | } 248 | logID, err := result.LastInsertId() 249 | if err != nil { 250 | logError("Failed to retrieve last insert ID: " + err.Error()) 251 | return 0, err 252 | } 253 | logInfo(fmt.Sprintf("Log inserted successfully with ID: %d", logID)) 254 | return logID, nil 255 | } 256 | 257 | func SelectHoneypotLog(honeypotID int) ([]HoneypotLog, error) { 258 | var logs []HoneypotLog 259 | query := `SELECT id, honeypotID, port, datetime, ip_source, ip_destination, log_event, regex_match FROM honeypot_logs WHERE honeypotID = ?` 260 | rows, err := db.Query(query, honeypotID) 261 | if err != nil { 262 | logError("Failed to query logs: " + err.Error()) 263 | return nil, err 264 | } 265 | defer rows.Close() 266 | 267 | for rows.Next() { 268 | var log HoneypotLog 269 | if err := rows.Scan(&log.ID, &log.HoneypotID, &log.Port, &log.Datetime, &log.IPSource, &log.IPDestination, &log.LogEvent, &log.RegexMatch); err != nil { 270 | logError("Failed to scan log from database: " + err.Error()) 271 | continue 272 | } 273 | logs = append(logs, log) 274 | } 275 | 276 | if err = rows.Err(); err != nil { 277 | logError("Error iterating through logs: " + err.Error()) 278 | return nil, err 279 | } 280 | 281 | logInfo(fmt.Sprintf("Retrieved %d logs successfully", len(logs))) 282 | return logs, nil 283 | } 284 | 285 | func UpdateHoneypotLog(log *HoneypotLog) error { 286 | updateSQL := `UPDATE honeypot_logs SET port = ?, datetime = ?, ip_source = ?, ip_destination = ?, log_event = ?, regex_match = ? WHERE id = ?` 287 | statement, err := db.Prepare(updateSQL) 288 | if err != nil { 289 | logError("Failed to prepare log update: " + err.Error()) 290 | return err 291 | } 292 | _, err = statement.Exec(log.Port, log.Datetime, log.IPSource, log.IPDestination, log.LogEvent, log.RegexMatch, log.ID) 293 | if err != nil { 294 | logError("Failed to update log: " + err.Error()) 295 | return err 296 | } 297 | logInfo(fmt.Sprintf("Log with ID %d updated successfully", log.ID)) 298 | return nil 299 | } 300 | 301 | func DeleteHoneypotLog(id int) error { 302 | deleteSQL := `DELETE FROM honeypot_logs WHERE id = ?` 303 | statement, err := db.Prepare(deleteSQL) 304 | if err != nil { 305 | logError("Failed to prepare log deletion: " + err.Error()) 306 | return err 307 | } 308 | _, err = statement.Exec(id) 309 | if err != nil { 310 | logError("Failed to delete log: " + err.Error()) 311 | return err 312 | } 313 | logInfo(fmt.Sprintf("Log with ID %d deleted successfully", id)) 314 | return nil 315 | } 316 | 317 | func RegisterAPIRoutes(router *gin.Engine) { 318 | logInfo("Registering API routes") 319 | // Honeypot configurations routes 320 | router.GET("/api/configs", listAllHoneypotConfigs) 321 | router.POST("/api/configs", insertHoneypotConfig) 322 | router.GET("/api/configs/:id", selectHoneypotConfig) 323 | router.PUT("/api/configs/:id", updateHoneypotConfig) 324 | router.DELETE("/api/configs/:id", deleteHoneypotConfig) 325 | router.PUT("/api/configs/:id/enable-disable", EnableDisableHoneypot) 326 | 327 | // Honeypot logs routes 328 | router.GET("/api/logs", listAllHoneypotLogs) 329 | //router.POST("/api/logs", insertHoneypotLog) 330 | router.GET("/api/logs/:honeypotID", selectHoneypotLogs) 331 | router.PUT("/api/logs/:id", updateHoneypotLog) 332 | router.DELETE("/api/logs/:id", deleteHoneypotLog) 333 | } 334 | 335 | func insertHoneypotConfig(c *gin.Context) { 336 | var config HoneypotConfig 337 | if err := c.ShouldBindJSON(&config); err != nil { 338 | logError("Error binding JSON for config insert: " + err.Error()) 339 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 340 | return 341 | } 342 | 343 | if err := InsertHoneypotConfig(&config); err != nil { 344 | logError("Error inserting config: " + err.Error()) 345 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 346 | return 347 | } 348 | 349 | c.JSON(http.StatusOK, gin.H{"status": "OK", "message": "Configuration added successfully"}) 350 | } 351 | 352 | func selectHoneypotConfig(c *gin.Context) { 353 | id, err := strconv.Atoi(c.Param("id")) 354 | if err != nil { 355 | logError("Invalid ID for config selection: " + err.Error()) 356 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 357 | return 358 | } 359 | 360 | config, err := SelectHoneypotConfig(id) 361 | if err != nil { 362 | logError("Error selecting config: " + err.Error()) 363 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 364 | return 365 | } 366 | 367 | logInfo(fmt.Sprintf("Config selected successfully: %+v", config)) 368 | c.JSON(http.StatusOK, config) 369 | } 370 | 371 | func updateHoneypotConfig(c *gin.Context) { 372 | var config HoneypotConfig 373 | if err := c.ShouldBindJSON(&config); err != nil { 374 | logError("Error binding JSON for config update: " + err.Error()) 375 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 376 | return 377 | } 378 | 379 | id, err := strconv.Atoi(c.Param("id")) 380 | if err != nil { 381 | logError("Invalid ID for config update: " + err.Error()) 382 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 383 | return 384 | } 385 | 386 | config.ID = id 387 | if err := UpdateHoneypotConfig(&config); err != nil { 388 | logError("Error updating config: " + err.Error()) 389 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 390 | return 391 | } 392 | 393 | logInfo(fmt.Sprintf("Config updated successfully: %+v", config)) 394 | c.JSON(http.StatusOK, gin.H{"status": "OK"}) 395 | } 396 | 397 | func deleteHoneypotConfig(c *gin.Context) { 398 | id, err := strconv.Atoi(c.Param("id")) 399 | if err != nil { 400 | logError("Invalid ID for config deletion: " + err.Error()) 401 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 402 | return 403 | } 404 | 405 | if err := DeleteHoneypotConfig(id); err != nil { 406 | logError("Error deleting config: " + err.Error()) 407 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 408 | return 409 | } 410 | 411 | logInfo(fmt.Sprintf("Config with ID %d deleted successfully", id)) 412 | c.JSON(http.StatusOK, gin.H{"status": "OK"}) 413 | } 414 | 415 | func EnableDisableHoneypot(c *gin.Context) { 416 | id, err := strconv.Atoi(c.Param("id")) 417 | if err != nil { 418 | logError("Invalid ID: " + err.Error()) 419 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 420 | return 421 | } 422 | 423 | var requestBody struct { 424 | Enabled bool `json:"enabled"` 425 | } 426 | if err := c.ShouldBindJSON(&requestBody); err != nil { 427 | logError("Error binding JSON: " + err.Error()) 428 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 429 | return 430 | } 431 | 432 | _, err = db.Exec("UPDATE honeypots SET enabled = ? WHERE id = ?", requestBody.Enabled, id) 433 | if err != nil { 434 | logError("Failed to update enabled state: " + err.Error()) 435 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 436 | return 437 | } 438 | 439 | logInfo(fmt.Sprintf("Honeypot configuration with ID %d enabled/disabled successfully", id)) 440 | c.JSON(http.StatusOK, gin.H{"status": "OK", "id": id, "enabled": requestBody.Enabled}) 441 | } 442 | 443 | // Honeypot Log Handlers 444 | 445 | // Enhanced insertHoneypotLog with verbose logging 446 | //func insertHoneypotLog(c *gin.Context) { 447 | // var log HoneypotLog 448 | // if err := c.ShouldBindJSON(&log); err != nil { 449 | // logError("Error binding JSON for log insert: " + err.Error()) 450 | // c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 451 | // return 452 | // } 453 | // 454 | // if err := InsertHoneypotLog(&log); err != nil { 455 | // logError("Error inserting log: " + err.Error()) 456 | // c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 457 | // return 458 | // } 459 | // 460 | // logInfo(fmt.Sprintf("Log inserted successfully: %+v", log)) 461 | // c.JSON(http.StatusOK, gin.H{"status": "OK"}) 462 | //} 463 | 464 | func selectHoneypotLogs(c *gin.Context) { 465 | honeypotID, err := strconv.Atoi(c.Param("honeypotID")) 466 | if err != nil { 467 | logError("Invalid honeypot ID for log selection: " + err.Error()) 468 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid honeypot ID"}) 469 | return 470 | } 471 | 472 | logs, err := SelectHoneypotLog(honeypotID) 473 | if err != nil { 474 | logError("Error selecting logs: " + err.Error()) 475 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 476 | return 477 | } 478 | 479 | logInfo(fmt.Sprintf("Logs for honeypot ID %d selected successfully", honeypotID)) 480 | c.JSON(http.StatusOK, logs) 481 | } 482 | 483 | func updateHoneypotLog(c *gin.Context) { 484 | var log HoneypotLog 485 | if err := c.ShouldBindJSON(&log); err != nil { 486 | logError("Error binding JSON for log update: " + err.Error()) 487 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 488 | return 489 | } 490 | 491 | id, err := strconv.Atoi(c.Param("id")) 492 | if err != nil { 493 | logError("Invalid ID for log update: " + err.Error()) 494 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 495 | return 496 | } 497 | 498 | log.ID = id 499 | if err := UpdateHoneypotLog(&log); err != nil { 500 | logError("Error updating log: " + err.Error()) 501 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 502 | return 503 | } 504 | 505 | logInfo(fmt.Sprintf("Log with ID %d updated successfully", id)) 506 | c.JSON(http.StatusOK, gin.H{"status": "OK"}) 507 | } 508 | 509 | func deleteHoneypotLog(c *gin.Context) { 510 | id, err := strconv.Atoi(c.Param("id")) 511 | if err != nil { 512 | logError("Invalid ID for log deletion: " + err.Error()) 513 | c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 514 | return 515 | } 516 | 517 | if err := DeleteHoneypotLog(id); err != nil { 518 | logError("Error deleting log: " + err.Error()) 519 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 520 | return 521 | } 522 | 523 | logInfo(fmt.Sprintf("Log with ID %d deleted successfully", id)) 524 | c.JSON(http.StatusOK, gin.H{"status": "OK"}) 525 | } 526 | 527 | func SelectHoneypotLogByID(logID int64) (*HoneypotLog, error) { 528 | var log HoneypotLog 529 | err := db.QueryRow("SELECT id, honeypotID, port, datetime, ip_source, ip_destination, log_event, regex_match FROM honeypot_logs WHERE id = ?", logID).Scan( 530 | &log.ID, &log.HoneypotID, &log.Port, &log.Datetime, &log.IPSource, &log.IPDestination, &log.LogEvent, &log.RegexMatch, 531 | ) 532 | if err != nil { 533 | return nil, err 534 | } 535 | return &log, nil 536 | } 537 | 538 | func listAllHoneypotLogs(c *gin.Context) { 539 | var logs []HoneypotLog 540 | query := "SELECT id, honeypotID, port, datetime, ip_source, ip_destination, log_event, regex_match FROM honeypot_logs" 541 | rows, err := db.Query(query) 542 | if err != nil { 543 | logError("Failed to query database for logs: " + err.Error()) 544 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to query database for logs"}) 545 | return 546 | } 547 | defer rows.Close() 548 | 549 | for rows.Next() { 550 | var log HoneypotLog 551 | if err := rows.Scan(&log.ID, &log.HoneypotID, &log.Port, &log.Datetime, &log.IPSource, &log.IPDestination, &log.LogEvent, &log.RegexMatch); err != nil { 552 | logError("Failed to scan log from database: " + err.Error()) 553 | continue 554 | } 555 | logs = append(logs, log) 556 | } 557 | 558 | if err = rows.Err(); err != nil { 559 | logError("Error iterating through logs: " + err.Error()) 560 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Error iterating through logs"}) 561 | return 562 | } 563 | 564 | logInfo(fmt.Sprintf("Total %d logs listed successfully", len(logs))) 565 | c.JSON(http.StatusOK, logs) 566 | } 567 | 568 | func listAllHoneypotConfigs(c *gin.Context) { 569 | var configs []HoneypotConfig 570 | query := "SELECT id, name, cve, application, port, template_html_file, detection_endpoint, request_regex, date_created, date_updated, redirect_url, responders, enabled FROM honeypots" 571 | rows, err := db.Query(query) 572 | if err != nil { 573 | logError("Failed to query database for configs: " + err.Error()) 574 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to query database for configs"}) 575 | return 576 | } 577 | defer rows.Close() 578 | 579 | for rows.Next() { 580 | var config HoneypotConfig 581 | var respondersJSON string 582 | if err := rows.Scan(&config.ID, &config.Name, &config.CVE, &config.Application, &config.Port, &config.TemplateHTMLFile, &config.DetectionEndpoint, &config.RequestRegex, &config.DateCreated, &config.DateUpdated, &config.RedirectURL, &respondersJSON, &config.Enabled); err != nil { 583 | logError("Failed to scan config from database: " + err.Error()) 584 | continue 585 | } 586 | 587 | if err := json.Unmarshal([]byte(respondersJSON), &config.Responders); err != nil { 588 | logError(fmt.Sprintf("Error deserializing responders JSON: %s", err.Error())) 589 | continue 590 | } 591 | 592 | configs = append(configs, config) 593 | } 594 | 595 | if err = rows.Err(); err != nil { 596 | logError("Error iterating through configs: " + err.Error()) 597 | c.JSON(http.StatusInternalServerError, gin.H{"error": "Error iterating through configs"}) 598 | return 599 | } 600 | 601 | logInfo(fmt.Sprintf("Total %d configs listed successfully", len(configs))) 602 | c.JSON(http.StatusOK, configs) 603 | } 604 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module shadowpot 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/TwiN/go-color v1.4.1 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/mattn/go-sqlite3 v1.14.22 9 | github.com/sirupsen/logrus v1.9.3 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.9.1 // indirect 15 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 17 | github.com/gin-contrib/sse v0.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.14.0 // indirect 21 | github.com/goccy/go-json v0.10.2 // indirect 22 | github.com/json-iterator/go v1.1.12 // indirect 23 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 24 | github.com/leodido/go-urn v1.2.4 // indirect 25 | github.com/mattn/go-isatty v0.0.19 // indirect 26 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 27 | github.com/modern-go/reflect2 v1.0.2 // indirect 28 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 29 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 30 | github.com/ugorji/go/codec v1.2.11 // indirect 31 | golang.org/x/arch v0.3.0 // indirect 32 | golang.org/x/crypto v0.9.0 // indirect 33 | golang.org/x/net v0.10.0 // indirect 34 | golang.org/x/sys v0.8.0 // indirect 35 | golang.org/x/text v0.9.0 // indirect 36 | google.golang.org/protobuf v1.30.0 // indirect 37 | gopkg.in/yaml.v3 v3.0.1 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= 2 | github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= 3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 6 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 13 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 17 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 22 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 23 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 24 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 25 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 26 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 32 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 33 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 34 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 35 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 36 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 37 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 38 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 39 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 40 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 41 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 42 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 45 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 46 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 47 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 48 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 49 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 50 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 51 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 52 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 53 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 54 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 55 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 56 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 57 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 58 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 59 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 60 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 61 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 62 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 63 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 64 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 65 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 66 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 67 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 68 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 69 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 70 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 71 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 72 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 73 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 74 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 75 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 77 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 78 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 79 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 80 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 81 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 82 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 83 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 84 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 85 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 86 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 89 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 90 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 92 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 93 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 94 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 95 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/TwiN/go-color" 6 | "github.com/sirupsen/logrus" 7 | "net/http" 8 | "os" 9 | "time" 10 | ) 11 | 12 | var log = logrus.New() 13 | 14 | func InitializeLogger() { 15 | log.Out = os.Stdout 16 | log.SetFormatter(&logrus.JSONFormatter{}) 17 | } 18 | 19 | 20 | func LogRequest(request *http.Request) { 21 | fullPath := request.URL.Path 22 | if queryString := request.URL.RawQuery; queryString != "" { 23 | fullPath += "?" + queryString 24 | } 25 | 26 | log.WithFields(logrus.Fields{ 27 | "time": time.Now().Format(time.RFC3339), 28 | "source_ip": request.RemoteAddr, 29 | "method": request.Method, 30 | "uri": fullPath, 31 | "user_agent": request.UserAgent(), 32 | "x_forwarded_for": request.Header.Get("X-Forwarded-For"), 33 | }).Info("Honeypot interaction detected") 34 | } 35 | 36 | func LogEvent(level logrus.Level, message string, fields map[string]interface{}) { 37 | if fields != nil { 38 | log.WithFields(fields).Log(level, message) 39 | } else { 40 | log.Log(level, message) 41 | } 42 | } 43 | 44 | func LogError(err error) { 45 | log.WithFields(logrus.Fields{ 46 | "time": time.Now().Format(time.RFC3339), 47 | }).Error(err) 48 | } 49 | 50 | func logError(message string) { 51 | fmt.Println(color.Ize(color.Red, "Error: "+message)) 52 | } 53 | 54 | func logInfo(message string) { 55 | fmt.Println(color.Ize(color.Cyan, "INFO: "+message)) 56 | } 57 | 58 | func logSuccess(message string) { 59 | fmt.Println(color.Ize(color.Green, "SUCCESS: "+message)) 60 | } 61 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var ( 12 | configPath string 13 | configs *AppConfig 14 | dbPath string 15 | verbose bool 16 | refreshDB bool 17 | ) 18 | 19 | func init() { 20 | flag.StringVar(&configPath, "config", "config.yaml", "Path to the configuration file") 21 | flag.StringVar(&dbPath, "db", "honeypot.db", "Path to the SQLite database file") 22 | flag.BoolVar(&verbose, "verbose", false, "Enable verbose logging") 23 | flag.BoolVar(&refreshDB, "refresh-db", false, "Refresh the database (Warning: This will delete existing data!)") 24 | 25 | flag.Parse() 26 | 27 | InitializeLogger() 28 | 29 | if verbose { 30 | log.SetLevel(logrus.DebugLevel) 31 | } else { 32 | log.SetLevel(logrus.InfoLevel) 33 | } 34 | } 35 | 36 | func main() { 37 | if verbose { 38 | fmt.Println("Starting the honeypot server...") 39 | } 40 | 41 | if refreshDB { 42 | if verbose { 43 | fmt.Println("Refreshing database...") 44 | } 45 | os.Remove(dbPath) 46 | } 47 | 48 | InitDB(dbPath) 49 | 50 | configs, err := LoadConfigurations(configPath) 51 | if err != nil { 52 | logError("Failed to load configurations: " + err.Error()) 53 | return 54 | } 55 | server(configs) 56 | } 57 | -------------------------------------------------------------------------------- /responders/email.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Replace placeholders in the template with actual values 4 | template=$(cat email.template) 5 | template=${template//%ID%/$1} 6 | template=${template//%APPLICATION%/$2} 7 | template=${template//%DATETIME%/$3} 8 | template=${template//%IPSOURCE%/$4} 9 | template=${template//%LOGEVENT%/$5} 10 | 11 | # Email subject 12 | subject="Modpot Alert: $2" 13 | 14 | # Recipient email 15 | to="alerts@domain.com" 16 | 17 | # Sender email 18 | from="modpot@domain.com" 19 | 20 | # Temporary file for the email content 21 | tempfile=$(mktemp) 22 | 23 | # Prepare email content 24 | echo "To: $to" > $tempfile 25 | echo "From: $from" >> $tempfile 26 | echo "Subject: $subject" >> $tempfile 27 | echo "Content-Type: text/html" >> $tempfile 28 | echo "" >> $tempfile 29 | echo "$template" >> $tempfile 30 | 31 | # Send the email using msmtp 32 | msmtp -a default $to < $tempfile 33 | 34 | # Clean up 35 | cat $tempfile 36 | rm $tempfile 37 | -------------------------------------------------------------------------------- /responders/email.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 |
13 |
Modpot Alert
14 |
15 |

A honeypot event was generated by modpot:

16 | 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /responders/iptables_block.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: ./iptables_block.sh 4 | 5 | IP_TO_BLOCK=$1 6 | DURATION_DAYS=2 # Set the fixed duration of days for the IP block 7 | 8 | # Check if IP address is provided 9 | if [ -z "$IP_TO_BLOCK" ]; then 10 | echo "Error: No IP address provided." 11 | echo "Usage: ./iptables_block.sh " 12 | exit 1 13 | fi 14 | 15 | # Add iptables rule to block the IP 16 | sudo iptables -A INPUT -s "$IP_TO_BLOCK" -j DROP 17 | echo "Blocked IP $IP_TO_BLOCK" 18 | 19 | # Schedule rule removal after DURATION_DAYS 20 | echo "sudo iptables -D INPUT -s $IP_TO_BLOCK -j DROP" | sudo at now + ${DURATION_DAYS} days 21 | echo "Scheduled removal of IP block $IP_TO_BLOCK after $DURATION_DAYS days" 22 | -------------------------------------------------------------------------------- /responders/slack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | slack_webhook_url="YOUR_SLACK_WEBHOOK_URL" 5 | 6 | # Create a JSON payload with your message 7 | json_payload="{\"text\": \"*Alert from Modpot:*\n- Honeypot ID: $1\n- Application: $2\n- Datetime: $3\n- IP Source: $4\n- Log Event: $5\"}" 8 | 9 | # Use curl to send the data 10 | curl -X POST -H "Content-type: application/json" --data "$json_payload" $slack_webhook_url 11 | -------------------------------------------------------------------------------- /responders/sms.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from twilio.rest import Client 3 | 4 | # Twilio settings - replace these with your Twilio account details 5 | ACCOUNT_SID = 'YOUR_ACCOUNT_SID' 6 | AUTH_TOKEN = 'YOUR_AUTH_TOKEN' 7 | TWILIO_PHONE_NUMBER = 'YOUR_TWILIO_PHONE_NUMBER' 8 | DESTINATION_PHONE_NUMBER = 'DESTINATION_PHONE_NUMBER' 9 | 10 | def send_sms(honeypot_id, application, datetime, ip_source, log_event): 11 | # Initialize Twilio client 12 | client = Client(ACCOUNT_SID, AUTH_TOKEN) 13 | 14 | # Format the message 15 | message_body = f"*Alert from Modpot:*\n- Honeypot ID: {honeypot_id}\n- Application: {application}\n- Datetime: {datetime}\n- IP Source: {ip_source}\n- Log Event: {log_event}" 16 | 17 | # Send the message 18 | message = client.messages.create( 19 | body=message_body, 20 | from_=TWILIO_PHONE_NUMBER, 21 | to=DESTINATION_PHONE_NUMBER 22 | ) 23 | print(f"Message sent: {message.sid}") 24 | 25 | if __name__ == "__main__": 26 | if len(sys.argv) != 6: 27 | print("Usage: sms.py ") 28 | sys.exit(1) 29 | 30 | _, honeypot_id, application, datetime, ip_source, log_event = sys.argv 31 | send_sms(honeypot_id, application, datetime, ip_source, log_event) 32 | -------------------------------------------------------------------------------- /responders/splunk_hec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | splunk_hec_token="YOUR_SPLUNK_HEC_TOKEN" 5 | splunk_hec_endpoint="https://your-splunk-instance:8088/services/collector" 6 | 7 | # Create a JSON payload with your data 8 | json_payload="{\"event\": {\"honeypot_id\": \"$1\", \"application\": \"$2\", \"datetime\": \"$3\", \"ip_source\": \"$4\", \"log_event\": \"$5\"}}" 9 | 10 | # Use curl to send the data 11 | curl -k -H "Authorization: Splunk $splunk_hec_token" -H "Content-Type: application/json" -d "$json_payload" $splunk_hec_endpoint 12 | -------------------------------------------------------------------------------- /responders/syslog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | syslog_server="syslog_server_address" 5 | syslog_port="514" 6 | 7 | # Create a log message 8 | log_message="<14>$1 $2 $3 $4 $5" 9 | 10 | # Send the log message using nc (netcat) 11 | echo "$log_message" | nc -w1 -u $syslog_server $syslog_port 12 | -------------------------------------------------------------------------------- /responders/webhook-generic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables 4 | webhook_url="YOUR_GENERIC_WEBHOOK_URL" 5 | 6 | # Create a JSON payload with your data 7 | json_payload="{\"honeypot_id\": \"$1\", \"application\": \"$2\", \"datetime\": \"$3\", \"ip_source\": \"$4\", \"log_event\": \"$5\"}" 8 | 9 | # Use curl to send the data 10 | curl -X POST -H "Content-type: application/json" --data "$json_payload" $webhook_url 11 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/gin-gonic/gin" 17 | ) 18 | 19 | func server(configs *AppConfig) { 20 | var wg sync.WaitGroup 21 | 22 | gin.SetMode(gin.ReleaseMode) 23 | 24 | globalRouter := gin.New() 25 | globalRouter.Use(gin.Logger(), gin.Recovery()) 26 | 27 | globalRouter.Static("/css", "./web/css") 28 | globalRouter.Static("/scripts", "./web/scripts") 29 | globalRouter.StaticFile("/manage", "./web/manage.html") 30 | 31 | RegisterAPIRoutes(globalRouter) 32 | 33 | globalRouter.GET("/", func(c *gin.Context) { 34 | c.Redirect(http.StatusMovedPermanently, "/manage") 35 | }) 36 | 37 | go func() { 38 | fmt.Println("Serving management interface on http://localhost:1337/manage") 39 | if err := globalRouter.Run(":1337"); err != nil { 40 | logError(fmt.Sprintf("Failed to serve management interface: %v", err)) 41 | } 42 | }() 43 | 44 | for _, config := range configs.Honeypots { 45 | if !config.Enabled { 46 | continue 47 | } 48 | wg.Add(1) 49 | go func(config HoneypotConfig) { 50 | defer wg.Done() 51 | 52 | router := gin.New() 53 | router.Use(gin.Logger(), gin.Recovery()) 54 | 55 | router.Use(func(c *gin.Context) { 56 | var bodyBytes []byte 57 | if c.Request.Body != nil { 58 | bodyBytes, _ = ioutil.ReadAll(c.Request.Body) 59 | c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) 60 | } 61 | c.Next() 62 | 63 | requestData := c.Request.RequestURI + " " + string(bodyBytes) 64 | regexMatch := "no" 65 | if matched, _ := regexp.MatchString(config.RequestRegex, requestData); matched { 66 | regexMatch = "yes" 67 | } 68 | 69 | logEvent := fmt.Sprintf("Method: %s, Path: %s, Agent: %s, Body: %s", c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), string(bodyBytes)) 70 | datetime := time.Now().Format(time.RFC3339) 71 | logID, err := InsertHoneypotLog(&HoneypotLog{ 72 | HoneypotID: config.ID, 73 | Port: config.Port, 74 | Datetime: datetime, 75 | IPSource: c.ClientIP(), 76 | IPDestination: c.Request.Host, 77 | LogEvent: logEvent, 78 | RegexMatch: regexMatch, 79 | }) 80 | 81 | if err != nil { 82 | logError("Failed to insert log: " + err.Error()) 83 | } else if regexMatch == "yes" { 84 | logInfo(fmt.Sprintf("Calling triggerResponder with log ID: %d", logID)) 85 | go func() { 86 | if err := triggerResponder(logID); err != nil { 87 | fmt.Sprintf("Error: %s", err) 88 | } 89 | }() 90 | } 91 | }) 92 | 93 | filePath := filepath.Join("templates", config.TemplateHTMLFile) 94 | router.GET(config.DetectionEndpoint, func(c *gin.Context) { 95 | c.File(filePath) 96 | }) 97 | 98 | router.POST(config.DetectionEndpoint, func(c *gin.Context) { 99 | if config.RedirectURL != "" { 100 | c.Redirect(http.StatusFound, config.RedirectURL) 101 | } else { 102 | c.Redirect(http.StatusFound, config.DetectionEndpoint) 103 | } 104 | }) 105 | 106 | port := fmt.Sprintf(":%d", config.Port) 107 | fmt.Printf("Starting server for %s on port %s\n", config.Name, port) 108 | if err := router.Run(port); err != nil { 109 | logError(fmt.Sprintf("Failed to start server for %s on port %s: %v", config.Name, port, err)) 110 | } 111 | }(config) 112 | } 113 | 114 | wg.Wait() 115 | } 116 | 117 | func getParameterValue(placeholder string, config *HoneypotConfig, log *HoneypotLog) (string, error) { 118 | switch placeholder { 119 | case "honeypots.id": 120 | return strconv.Itoa(config.ID), nil 121 | case "honeypots.application": 122 | return config.Application, nil 123 | case "honeypot_logs.datetime": 124 | return log.Datetime, nil 125 | case "honeypot_logs.ip_source": 126 | return log.IPSource, nil 127 | case "honeypot_logs.log_event": 128 | return log.LogEvent, nil 129 | default: 130 | errMsg := fmt.Sprintf("Unknown parameter placeholder: %s", placeholder) 131 | logError(errMsg) 132 | return "", fmt.Errorf(errMsg) 133 | } 134 | } 135 | func triggerResponder(logID int64) error { 136 | logEntry, err := SelectHoneypotLogByID(logID) 137 | if err != nil { 138 | logError(fmt.Sprintf("Failed to fetch log entry: %v", err)) 139 | return fmt.Errorf("failed to fetch log entry: %w", err) 140 | } 141 | 142 | config, err := SelectHoneypotConfig(logEntry.HoneypotID) 143 | if err != nil { 144 | logError(fmt.Sprintf("Failed to select honeypot config: %v", err)) 145 | return fmt.Errorf("failed to select honeypot config: %w", err) 146 | } 147 | 148 | for _, responder := range config.Responders { 149 | var cmdArgs []string 150 | cmdArgs = append(cmdArgs, responder.Script) 151 | 152 | for _, param := range responder.Parameters { 153 | value, err := getParameterValue(param, config, logEntry) 154 | if err != nil { 155 | logError(fmt.Sprintf("Failed to get parameter value: %v", err)) 156 | return fmt.Errorf("failed to get parameter value for %s: %w", param, err) 157 | } 158 | cmdArgs = append(cmdArgs, value) 159 | } 160 | 161 | logInfo(fmt.Sprintf("Preparing to execute command: %s %s", responder.Engine, strings.Join(cmdArgs, " "))) 162 | 163 | cmd := exec.Command(responder.Engine, cmdArgs...) 164 | cmd.Dir = "./responders/" 165 | var out, stderr bytes.Buffer 166 | cmd.Stdout = &out 167 | cmd.Stderr = &stderr 168 | 169 | logInfo(fmt.Sprintf("Executing responder: %s %s", responder.Engine, strings.Join(cmdArgs, " "))) 170 | if err := cmd.Run(); err != nil { 171 | logError(fmt.Sprintf("Responder script execution failed: %v. Stderr: %s", err, stderr.String())) 172 | return fmt.Errorf("responder script execution failed: %w. Stderr: %s", err, stderr.String()) 173 | } 174 | logInfo(fmt.Sprintf("Responder executed successfully. Output:\n%s", out.String())) 175 | } 176 | 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /templates/owa.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Outlook 7 | 26 | 654 | 1201 | 1202 | 1203 | 1262 | 1263 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1322 | 1323 |
1324 | 1325 | 1326 | 1327 | 1328 | 1329 |
1330 | 1335 | 1341 |
1342 |
1343 | 1344 |
1345 | Outlook 1346 |
1347 | 1348 | 1349 |
1350 | 1351 |
1352 |
1353 |
1354 | 1355 | Show password 1356 |
1357 | 1358 | 1359 | 1362 |
1363 |
1364 | sign in 1365 |
1366 | 1367 |
1368 |
1369 |
1370 |
1371 | 1380 |
1381 |
1382 | 1387 | 1388 | 1389 | 1390 | -------------------------------------------------------------------------------- /templates/phpMyAdmin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phpMyAdmin 5 | 6 | 14 | 1110 | 1111 | 1112 | 1113 | 1114 |
1115 | 1116 |

1117 | Welcome to phpMyAdmin

1118 | 1119 |
1120 | 1121 | 1122 | 1123 |
Language 1124 | 1247 |
1248 | 1253 |
1254 |
1255 | 1256 | 1276 |
Cookies must be enabled past this point.
1277 |
1278 | 1294 | 1295 | 1296 | 1297 | -------------------------------------------------------------------------------- /validate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | ) 9 | 10 | func ValidateHoneypotConfigs(configs []HoneypotConfig) ([]HoneypotConfig, error) { 11 | var validConfigs []HoneypotConfig 12 | logInfo("Starting validation of honeypot configurations") 13 | 14 | for _, config := range configs { 15 | logInfo(fmt.Sprintf("Validating configuration: %s", config.Name)) 16 | if err := validateHoneypotConfig(config); err != nil { 17 | logError(fmt.Sprintf("Validation error for '%s': %v", config.Name, err)) 18 | continue 19 | } 20 | logSuccess(fmt.Sprintf("Configuration validated successfully: %s", config.Name)) 21 | validConfigs = append(validConfigs, config) 22 | } 23 | 24 | if len(validConfigs) == 0 { 25 | logError("No valid honeypot configurations found") 26 | return nil, fmt.Errorf("no valid honeypot configurations found") 27 | } 28 | 29 | logSuccess(fmt.Sprintf("Validated %d configurations successfully", len(validConfigs))) 30 | return validConfigs, nil 31 | } 32 | 33 | 34 | func validateHoneypotConfig(config HoneypotConfig) error { 35 | logInfo(fmt.Sprintf("Validating individual fields for configuration: %s", config.Name)) 36 | 37 | if config.ID == 0 || config.Name == "" || config.Port == 0 || config.TemplateHTMLFile == "" || config.DetectionEndpoint == "" || config.RequestRegex == "" { 38 | logError(fmt.Sprintf("Mandatory fields missing in configuration: %s", config.Name)) 39 | return fmt.Errorf("mandatory fields missing in honeypot configuration for '%s'", config.Name) 40 | } 41 | logInfo(fmt.Sprintf("All mandatory fields present for '%s'", config.Name)) 42 | 43 | if config.Port < 1 || config.Port > 65535 { 44 | logError(fmt.Sprintf("Port out of range for '%s': %d", config.Name, config.Port)) 45 | return fmt.Errorf("port must be within the range 1-65535 for '%s'", config.Name) 46 | } 47 | logInfo(fmt.Sprintf("Port within valid range for '%s'", config.Name)) 48 | 49 | templatePath := filepath.Join("templates", config.TemplateHTMLFile) 50 | if _, err := os.Stat(templatePath); os.IsNotExist(err) { 51 | logError(fmt.Sprintf("Template HTML file does not exist for '%s': %s", config.Name, templatePath)) 52 | return fmt.Errorf("template HTML file '%s' does not exist for '%s'", templatePath, config.Name) 53 | } 54 | logInfo(fmt.Sprintf("Template HTML file exists for '%s'", config.Name)) 55 | 56 | if _, err := regexp.Compile(config.RequestRegex); err != nil { 57 | logError(fmt.Sprintf("Invalid request regex for '%s': %s", config.Name, config.RequestRegex)) 58 | return fmt.Errorf("request regex '%s' is not valid for '%s'", config.RequestRegex, config.Name) 59 | } 60 | logInfo(fmt.Sprintf("Request regex is valid for '%s'", config.Name)) 61 | 62 | logInfo(fmt.Sprintf("RedirectURL for '%s': %s", config.Name, config.RedirectURL)) 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /web/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/referefref/modpot/3722ce126b74e7bf9897f49fdde610c96eb8103f/web/assets/logo.png -------------------------------------------------------------------------------- /web/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Roboto', sans-serif; 3 | background-color: #ffffff; /* Light background */ 4 | } 5 | 6 | @media (max-width: 768px) { 7 | .main-content-container { 8 | max-width: 100%; 9 | } 10 | } 11 | 12 | .main-content-container { 13 | max-width: 90%; 14 | margin: auto; 15 | } 16 | 17 | .dark-mode { 18 | background-color: #121212; /* Dark background */ 19 | } 20 | 21 | .header { 22 | background-color: #000; 23 | color: #fff; 24 | padding: 10px 0; 25 | font-size: 18px; 26 | display: flex; 27 | justify-content: space-between; 28 | align-items: center; 29 | } 30 | 31 | .header img { 32 | width: 50px; 33 | height: auto; 34 | } 35 | 36 | .header a, .fa-github, .fa-globe { 37 | color: #fff; 38 | } 39 | 40 | .dark-mode .header { 41 | background-color: #1a1a1a; /* Darker shade for header */ 42 | } 43 | 44 | .dark-mode .header a, .dark-mode .fa-github, .dark-mode .fa-globe { 45 | color: #ccc; /* Lighter text color for links */ 46 | } 47 | 48 | .fa-github, .fa-globe { 49 | font-size: 24px; 50 | } 51 | 52 | .table thead th { 53 | vertical-align: middle; 54 | background-color: #000; 55 | color: #fff; 56 | } 57 | 58 | .dark-mode .table thead th { 59 | background-color: #333; /* Darker background for thead */ 60 | color: #ddd; /* Lighter text color */ 61 | } 62 | 63 | .table tbody td { 64 | vertical-align: middle; 65 | background-color: #f2f2f2; 66 | color: #000; 67 | } 68 | 69 | .dark-mode .table tbody td { 70 | background-color: #242424; /* Dark background for tbody */ 71 | color: #eee; /* Lighter text color */ 72 | } 73 | 74 | .footer { 75 | background-color: #grey; 76 | color: #fff; 77 | text-align: center; 78 | padding: 5px 0; 79 | font-size: 10px; 80 | } 81 | 82 | .dark-mode .footer { 83 | background-color: #333; /* Darker footer background */ 84 | } 85 | 86 | .event-column { 87 | max-width: 350px; /* Adjust as necessary */ 88 | white-space: normal; 89 | word-wrap: break-word; 90 | } 91 | 92 | .card-body.table-responsive { 93 | max-width: 100%; 94 | overflow-x: auto; 95 | } 96 | 97 | .main-content-container .card { 98 | margin-bottom: 20px; /* Adds consistent spacing between cards */ 99 | } 100 | 101 | .btn-dark-mode-toggle { 102 | background-color: #333; /* Dark mode toggle button background */ 103 | color: #fff; /* Dark mode toggle button text color */ 104 | } 105 | 106 | /* Styles for dark mode toggle effect */ 107 | .dark-mode .btn-dark-mode-toggle { 108 | background-color: #eee; /* Light mode background */ 109 | color: #333; /* Light mode text color */ 110 | } 111 | 112 | /* Dark mode adjustments for cards and card headers */ 113 | .dark-mode .card { 114 | background-color: #242424; /* Darker background for the card */ 115 | border-color: #333333; /* Darker border for the card */ 116 | color: #ffffff; /* Light text color for content within the card */ 117 | } 118 | 119 | .dark-mode .card-header { 120 | background-color: #333333; /* Darker background for the card header */ 121 | color: #ffffff; /* Light text color for the card header */ 122 | } 123 | 124 | .dark-mode .card .card-title { 125 | color: #ffffff; /* Ensure the card title also adheres to the dark mode text color */ 126 | } 127 | 128 | /* Ensure links within the card-header are also styled appropriately for dark mode */ 129 | .dark-mode .card-header a { 130 | color: #ffffff; 131 | } 132 | 133 | .dark-mode .card-header a:hover { 134 | color: #dddddd; /* Slightly lighter color on hover for distinction */ 135 | } 136 | 137 | .dark-mode .modal-content { 138 | background-color: #333; /* Dark background for modal content */ 139 | color: #ddd; /* Light text color for modal content */ 140 | } 141 | 142 | .dark-mode .modal-header { 143 | border-bottom: 1px solid #444; /* Dark border for modal header */ 144 | } 145 | 146 | .dark-mode .modal-footer { 147 | border-top: 1px solid #444; /* Dark border for modal footer */ 148 | } 149 | 150 | .dark-mode .modal-body { 151 | background-color: #242424; /* Slightly lighter dark background for modal body */ 152 | } 153 | 154 | /* Apply dark mode styles to form inputs and buttons within the modal */ 155 | .dark-mode .form-control, .dark-mode .btn { 156 | background-color: #444; /* Dark background for form elements and buttons */ 157 | color: #fff; /* Light text color */ 158 | border: 1px solid #555; /* Slightly lighter border for visibility */ 159 | } 160 | 161 | .dark-mode .btn-secondary, .dark-mode .btn-primary { 162 | border-color: #666; /* Dark border for buttons to distinguish */ 163 | } 164 | 165 | /* Optional: Adjust link colors in dark mode for better visibility */ 166 | .dark-mode a { 167 | color: #9ecfff; /* Light blue color for links */ 168 | } 169 | -------------------------------------------------------------------------------- /web/manage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Modpot v0.3.11 Management Dashboard 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 |
17 | Modpot Logo 18 | Modpot v0.1 19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 |
31 |

Honeypot Management

32 | 33 |
34 |
35 |

Honeypot Configurations

36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
IDNameCVEApplicationPortTemplate FileEndpointRegexCreatedUpdatedRedirect URLEnabledActions
59 |
60 |

Honeypot Logs

61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
IDDate/TimeSource IPDestination IPEventRegex Match
77 |
78 |
79 |
80 |
81 |
82 |
83 | 84 | 165 | 166 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /web/scripts/script.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | initializeDarkMode(); 3 | loadConfigs(); 4 | loadLogs(); 5 | document.getElementById('darkModeToggle').addEventListener('click', function() { 6 | toggleDarkMode(); 7 | }); 8 | }); 9 | 10 | function loadConfigs() { 11 | fetch('/api/configs') 12 | .then(response => response.json()) 13 | .then(data => { 14 | const configsTableBody = document.getElementById('configsTableBody'); 15 | configsTableBody.innerHTML = ''; 16 | data.forEach(config => { 17 | let row = configsTableBody.insertRow(); 18 | row.insertCell(0).innerText = config.ID; 19 | row.insertCell(1).innerText = config.Name; 20 | row.insertCell(2).innerText = config.CVE; 21 | row.insertCell(3).innerText = config.Application; 22 | row.insertCell(4).innerText = config.Port; 23 | row.insertCell(5).innerText = config.TemplateHTMLFile; 24 | row.insertCell(6).innerText = config.DetectionEndpoint; 25 | row.insertCell(7).innerText = config.RequestRegex; 26 | row.insertCell(8).innerText = config.DateCreated; 27 | row.insertCell(9).innerText = config.DateUpdated; 28 | row.insertCell(10).innerText = config.RedirectURL; 29 | let enabledCell = row.insertCell(11); 30 | enabledCell.innerText = config.Enabled ? 'Yes' : 'No'; 31 | 32 | let actionsCell = row.insertCell(12); 33 | let editButton = document.createElement('button'); 34 | editButton.innerText = 'Edit'; 35 | editButton.className = 'btn btn-sm btn-secondary'; 36 | editButton.onclick = () => showConfigForm(config); 37 | actionsCell.appendChild(editButton); 38 | 39 | let toggleButton = document.createElement('button'); 40 | toggleButton.innerText = config.Enabled ? 'Disable' : 'Enable'; 41 | toggleButton.className = 'btn btn-sm btn-warning'; 42 | toggleButton.onclick = () => toggleConfig(config.ID, !config.Enabled); 43 | actionsCell.appendChild(toggleButton); 44 | 45 | let removeButton = document.createElement('button'); 46 | removeButton.classList.add('btn', 'btn-danger', 'btn-sm'); 47 | removeButton.textContent = 'Remove'; 48 | removeButton.onclick = () => removeConfig(config.ID); 49 | actionsCell.appendChild(removeButton); 50 | }); 51 | }) 52 | .catch(error => console.error('Unable to load configurations:', error)); 53 | } 54 | 55 | function initializeDarkMode() { 56 | if (localStorage.getItem('darkMode') === 'enabled') { 57 | document.body.classList.add('dark-mode'); 58 | } else { 59 | document.body.classList.remove('dark-mode'); 60 | } 61 | } 62 | 63 | function toggleDarkMode() { 64 | if (document.body.classList.contains('dark-mode')) { 65 | document.body.classList.remove('dark-mode'); 66 | localStorage.setItem('darkMode', 'disabled'); 67 | } else { 68 | document.body.classList.add('dark-mode'); 69 | localStorage.setItem('darkMode', 'enabled'); 70 | } 71 | } 72 | 73 | function loadLogs() { 74 | fetch('/api/logs') 75 | .then(response => response.json()) 76 | .then(data => { 77 | const logsTableBody = document.getElementById('logsTableBody'); 78 | logsTableBody.innerHTML = ''; 79 | data.forEach(log => { 80 | let row = logsTableBody.insertRow(); 81 | row.insertCell(0).innerText = log.ID; 82 | row.insertCell(1).innerText = log.Datetime; 83 | row.insertCell(2).innerText = log.IPSource; 84 | row.insertCell(3).innerText = log.IPDestination; 85 | let eventCell = row.insertCell(4); 86 | eventCell.innerText = log.LogEvent; 87 | eventCell.classList.add('event-column'); 88 | let regexMatchCell = row.insertCell(5); 89 | regexMatchCell.innerText = log.regex_match === 'no' ? 'No' : 'Yes'; 90 | }); 91 | adjustEventColumnStyling(); 92 | }) 93 | .catch(error => console.error('Unable to load logs:', error)); 94 | } 95 | 96 | function adjustEventColumnStyling() { 97 | document.querySelectorAll('#logsTable tbody tr').forEach(row => { 98 | const eventCell = row.cells[4]; 99 | if (eventCell) { 100 | eventCell.classList.add('event-column'); 101 | } 102 | }); 103 | } 104 | 105 | function showConfigForm(config = null) { 106 | const form = document.getElementById('configForm'); 107 | form.reset(); 108 | 109 | const respondersContainer = document.querySelector('#respondersContainer'); 110 | respondersContainer.innerHTML = ''; 111 | 112 | if (config) { 113 | document.getElementById('configName').value = config.Name; 114 | document.getElementById('configCVE').value = config.CVE; 115 | document.getElementById('configApplication').value = config.Application; 116 | document.getElementById('configPort').value = config.Port; 117 | document.getElementById('configTemplateHTMLFile').value = config.TemplateHTMLFile; 118 | document.getElementById('configDetectionEndpoint').value = config.DetectionEndpoint; 119 | document.getElementById('configRequestRegex').value = config.RequestRegex; 120 | document.getElementById('configDateCreated').value = config.DateCreated; 121 | document.getElementById('configDateUpdated').value = config.DateUpdated; 122 | document.getElementById('configRedirectURL').value = config.RedirectURL; 123 | document.getElementById('configEnabled').checked = config.Enabled; 124 | form.dataset.configId = config.ID; 125 | 126 | if (config.Responders && config.Responders.length > 0) { 127 | config.Responders.forEach(responder => { 128 | addResponder(responder); 129 | }); 130 | } 131 | } else { 132 | delete form.dataset.configId; 133 | } 134 | 135 | $('#configModal').modal('show'); 136 | } 137 | 138 | function submitConfigForm() { 139 | const form = document.getElementById('configForm'); 140 | const configId = form.dataset.configId; 141 | 142 | const formData = { 143 | Name: form.configName.value, 144 | CVE: form.configCVE.value, 145 | Application: form.configApplication.value, 146 | Port: parseInt(form.configPort.value, 10), 147 | TemplateHTMLFile: form.configTemplateHTMLFile.value, 148 | DetectionEndpoint: form.configDetectionEndpoint.value, 149 | RequestRegex: form.configRequestRegex.value, 150 | DateCreated: form.configDateCreated.value, 151 | DateUpdated: form.configDateUpdated.value, 152 | RedirectURL: form.configRedirectURL.value, 153 | Enabled: form.configEnabled.checked, 154 | Responders: [{ 155 | engine: document.getElementById('responderEngine').value, 156 | script: document.getElementById('responderScript').value, 157 | parameters: document.getElementById('responderParameters').value.split(',') 158 | }] 159 | }; 160 | 161 | let url = '/api/configs' + (configId ? `/${configId}` : ''); 162 | let method = configId ? 'PUT' : 'POST'; 163 | 164 | fetch(url, { 165 | method: method, 166 | headers: { 167 | 'Content-Type': 'application/json', 168 | }, 169 | body: JSON.stringify(formData), 170 | }) 171 | .then(response => { 172 | if (!response.ok) { 173 | throw new Error('Network response was not ok'); 174 | } 175 | return response.json(); 176 | }) 177 | .then(() => { 178 | $('#configModal').modal('hide'); 179 | loadConfigs(); 180 | }) 181 | .catch(error => { 182 | console.error('Error:', error); 183 | }); 184 | } 185 | 186 | function toggleConfig(configId, enabled) { 187 | enableDisableHoneypot(configId, enabled); 188 | } 189 | 190 | function enableDisableHoneypot(configId, enabled) { 191 | fetch(`/api/configs/${configId}/enable-disable`, { 192 | method: 'PUT', 193 | headers: { 194 | 'Content-Type': 'application/json', 195 | }, 196 | body: JSON.stringify({ enabled: enabled }), 197 | }) 198 | .then(response => { 199 | if (!response.ok) { 200 | throw new Error('Network response was not ok'); 201 | } 202 | return response.json(); 203 | }) 204 | .then(() => { 205 | console.log(`Config ${configId} enabled/disabled to ${enabled}`); 206 | loadConfigs(); 207 | }) 208 | .catch(error => { 209 | console.error('Error enabling/disabling configuration:', error); 210 | }); 211 | } 212 | 213 | function removeConfig(configId) { 214 | if(confirm(`Are you sure you want to remove the configuration with ID ${configId}?`)) { 215 | fetch(`/api/configs/${configId}`, { 216 | method: 'DELETE', 217 | }) 218 | .then(response => { 219 | if(response.ok) { 220 | loadConfigs(); 221 | alert('Configuration removed successfully.'); 222 | } else { 223 | alert('Failed to remove configuration.'); 224 | } 225 | }) 226 | .catch(error => console.error('Error removing configuration:', error)); 227 | } 228 | } 229 | 230 | function addResponder(responder = null) { 231 | let template = document.querySelector('#responderTemplate').content.cloneNode(true); 232 | let container = document.querySelector('#respondersContainer'); 233 | 234 | if (responder) { 235 | let inputs = template.querySelectorAll('input'); 236 | inputs[0].value = responder.Engine; 237 | inputs[1].value = responder.Script; 238 | inputs[2].value = responder.Parameters.join(', '); 239 | } 240 | container.appendChild(template); 241 | } 242 | 243 | function addEmptyResponder() { 244 | addResponder(); 245 | } 246 | 247 | function removeResponder(button) { 248 | button.closest('.responder-form').remove(); 249 | } 250 | --------------------------------------------------------------------------------