Files
honeydany/app/webservice_handler.go
T
2025-09-28 21:28:39 +01:00

242 lines
6.7 KiB
Go

package app
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
)
// WebServiceHandler handles custom web honeypot services
type WebServiceHandler struct {
config WebServiceConfig
templateManager *WebTemplateManager
logFunc func(Record)
}
// NewWebServiceHandler creates a new web service handler
func NewWebServiceHandler(config WebServiceConfig, templateManager *WebTemplateManager, logFunc func(Record)) *WebServiceHandler {
return &WebServiceHandler{
config: config,
templateManager: templateManager,
logFunc: logFunc,
}
}
// ServeHTTP implements the http.Handler interface
func (wsh *WebServiceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Log all requests
wsh.logRequest(r)
// Check if request is for the configured path
if r.URL.Path == wsh.config.Path {
wsh.handleLoginPage(w, r)
return
}
// For all other paths, return 403 Forbidden
wsh.logEvent(r, "forbidden_access", map[string]string{
"requested_path": r.URL.Path,
"configured_path": wsh.config.Path,
})
http.Error(w, "Forbidden", http.StatusForbidden)
}
// handleLoginPage serves the login page and processes login attempts
func (wsh *WebServiceHandler) handleLoginPage(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
wsh.serveLoginForm(w, r)
case http.MethodPost:
wsh.handleLoginAttempt(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// serveLoginForm renders and serves the login form
func (wsh *WebServiceHandler) serveLoginForm(w http.ResponseWriter, r *http.Request) {
// Load template
tmpl, err := wsh.templateManager.LoadTemplate(wsh.config.TemplateName)
if err != nil {
log.Printf("Failed to load template %s: %v", wsh.config.TemplateName, err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Prepare template data
data := map[string]interface{}{
"ServiceName": wsh.config.Name,
"LoginPath": wsh.config.Path,
"UseHTTPS": wsh.config.UseHTTPS,
}
// Set content type
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// Execute template
if err := tmpl.Execute(w, data); err != nil {
log.Printf("Failed to execute template: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Log page view
wsh.logEvent(r, "login_page_view", map[string]string{
"template": wsh.config.TemplateName,
})
}
// handleLoginAttempt processes login form submissions
func (wsh *WebServiceHandler) handleLoginAttempt(w http.ResponseWriter, r *http.Request) {
// Parse form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
username := strings.TrimSpace(r.FormValue("username"))
password := r.FormValue("password")
// Log the login attempt
wsh.logEvent(r, "login_attempt", map[string]string{
"username": username,
"password": password,
"template": wsh.config.TemplateName,
})
// Always reject login attempts (this is a honeypot)
// Redirect back to login page with error parameter
redirectURL := fmt.Sprintf("%s?error=invalid", wsh.config.Path)
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
}
// logRequest logs basic request information
func (wsh *WebServiceHandler) logRequest(r *http.Request) {
details := map[string]string{
"method": r.Method,
"path": r.URL.Path,
"query": r.URL.RawQuery,
"user_agent": r.Header.Get("User-Agent"),
"referer": r.Header.Get("Referer"),
}
// Add additional headers that might be interesting
if auth := r.Header.Get("Authorization"); auth != "" {
details["authorization"] = auth
}
if cookie := r.Header.Get("Cookie"); cookie != "" {
details["cookie"] = cookie
}
wsh.logEvent(r, "http_request", details)
}
// logEvent logs an event with the web service context
func (wsh *WebServiceHandler) logEvent(r *http.Request, eventType string, details map[string]string) {
if details == nil {
details = make(map[string]string)
}
// Add service context
details["event"] = eventType
details["service_name"] = wsh.config.Name
details["service_port"] = strconv.Itoa(wsh.config.Port)
details["service_path"] = wsh.config.Path
// Create log record
record := Record{
Timestamp: time.Now().UTC(),
RemoteAddr: remoteIP(r.RemoteAddr),
RemotePort: remotePort(r.RemoteAddr),
Service: fmt.Sprintf("web-%s", wsh.config.Name),
Details: details,
RawPayload: fmt.Sprintf("%s %s", r.Method, r.URL.String()),
}
if wsh.logFunc != nil {
wsh.logFunc(record)
}
}
// WebServiceManager manages multiple web service instances
type WebServiceManager struct {
templateManager *WebTemplateManager
logFunc func(Record)
services map[string]*WebServiceHandler
}
// NewWebServiceManager creates a new web service manager
func NewWebServiceManager(templateManager *WebTemplateManager, logFunc func(Record)) *WebServiceManager {
return &WebServiceManager{
templateManager: templateManager,
logFunc: logFunc,
services: make(map[string]*WebServiceHandler),
}
}
// StartWebService starts a web service on the specified configuration
func (wsm *WebServiceManager) StartWebService(config WebServiceConfig) error {
if !config.Enabled {
return nil
}
// Create handler
handler := NewWebServiceHandler(config, wsm.templateManager, wsm.logFunc)
// Create HTTP server
mux := http.NewServeMux()
mux.Handle("/", handler)
addr := fmt.Sprintf(":%d", config.Port)
server := &http.Server{
Addr: addr,
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Store service
serviceKey := fmt.Sprintf("%s:%d", config.Name, config.Port)
wsm.services[serviceKey] = handler
log.Printf("Starting web service '%s' on port %d at path %s", config.Name, config.Port, config.Path)
// Start server
go func() {
var err error
if config.UseHTTPS {
// For HTTPS, we would need to load certificates; using HTTP placeholder
log.Printf("HTTPS requested for %s but using HTTP for now. Configure TLS certificates for production.", config.Name)
err = server.ListenAndServe()
} else {
err = server.ListenAndServe()
}
if err != nil && err != http.ErrServerClosed {
log.Printf("Web service %s error: %v", config.Name, err)
}
}()
return nil
}
// StopWebService stops a web service
func (wsm *WebServiceManager) StopWebService(name string, port int) {
serviceKey := fmt.Sprintf("%s:%d", name, port)
delete(wsm.services, serviceKey)
// Note: In a production system, you'd want to properly shutdown the HTTP server
}
// GetActiveServices returns a list of active web services
func (wsm *WebServiceManager) GetActiveServices() []string {
var services []string
for key := range wsm.services {
services = append(services, key)
}
return services
}