242 lines
6.7 KiB
Go
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
|
||
|
|
}
|