├── .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 | [](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d)
2 | [](https://github.com/dwyl/esta/issues)
3 |
4 | # modpot v0.3.11 (Not for workgroups)
5 | 
6 |
7 | 
8 | 
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 | 
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 |
14 |
15 |
A honeypot event was generated by modpot:
16 |
17 | - ID: %ID%
18 | - Application: %APPLICATION%
19 | - Datetime: %DATETIME%
20 | - IP Source: %IPSOURCE%
21 | - Log Event: %LOGEVENT%
22 |
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 |
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 |
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 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
Honeypot Configurations
36 |
37 |
38 |
39 |
40 | ID |
41 | Name |
42 | CVE |
43 | Application |
44 | Port |
45 | Template File |
46 | Endpoint |
47 | Regex |
48 | Created |
49 | Updated |
50 | Redirect URL |
51 | Enabled |
52 | Actions |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Honeypot Logs
61 |
62 |
63 |
64 |
65 | ID |
66 | Date/Time |
67 | Source IP |
68 | Destination IP |
69 | Event |
70 | Regex Match |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
93 |
146 |
147 |
157 |
158 |
162 |
163 |
164 |
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 |
--------------------------------------------------------------------------------