render page can use template variables. Added panic debug info

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-10-24 15:31:27 +02:00
parent dda9a66437
commit 9014fb6477
2 changed files with 232 additions and 24 deletions
+117 -17
View File
@@ -12,6 +12,7 @@ import (
"net/url"
"os"
"path/filepath"
"runtime/debug"
"strings"
textTmpl "text/template"
"time"
@@ -336,6 +337,10 @@ func (s *Server) Handler(c *gin.Context) {
"panic", r,
"host", c.Request.Host,
"url", c.Request.URL.String(),
"method", c.Request.Method,
"userAgent", c.Request.UserAgent(),
"remoteAddr", c.Request.RemoteAddr,
"stack", string(debug.Stack()),
)
c.Status(http.StatusInternalServerError)
c.Abort()
@@ -1450,31 +1455,126 @@ func (s *Server) renderDenyPage(
&repository.PageOption{},
)
if err != nil {
return fmt.Errorf("failed to get landing page: %s", err)
return fmt.Errorf("failed to get deny page: %s", err)
}
tmpl, err := textTmpl.New("page").
Funcs(service.TemplateFuncs()).
Parse(page.Content.MustGet().String())
// get campaign recipient - there MUST be one if we're rendering a deny page
campaignRecipient, _, err := server.GetCampaignRecipientFromURLParams(
ctx,
c.Request,
s.repositories.Identifier,
s.repositories.CampaignRecipient,
)
if err != nil {
return fmt.Errorf("failed to parse page template: %s", err)
return fmt.Errorf("failed to get campaign recipient for deny page: %s", err)
}
baseURL := "https://" + domain.Name
w := bytes.NewBuffer([]byte{})
err = tmpl.Execute(w,
map[string]string{
"BaseURL": baseURL,
})
if campaignRecipient == nil {
return fmt.Errorf("campaign recipient is nil")
}
// get recipient ID from campaign recipient with nil check
recipientID, err := campaignRecipient.RecipientID.Get()
if err != nil {
return fmt.Errorf("failed to execute page template: %s", err)
return fmt.Errorf("campaign recipient has no recipient ID: %s", err)
}
c.Data(http.StatusOK, "text/html; charset=utf-8", w.Bytes())
recipient, err := s.repositories.Recipient.GetByID(ctx, &recipientID, &repository.RecipientOption{})
if err != nil {
return fmt.Errorf("failed to get recipient: %s", err)
}
if recipient == nil {
return fmt.Errorf("recipient is nil")
}
// get campaign with nil check
campaignID, err := campaignRecipient.CampaignID.Get()
if err != nil {
return fmt.Errorf("campaign recipient has no campaign ID: %s", err)
}
campaign, err := s.repositories.Campaign.GetByID(ctx, &campaignID, &repository.CampaignOption{})
if err != nil {
return fmt.Errorf("failed to get campaign: %s", err)
}
if campaign == nil {
return fmt.Errorf("campaign is nil")
}
// get campaign template with email and nil check
templateID, err := campaign.TemplateID.Get()
if err != nil {
return fmt.Errorf("campaign has no template ID: %s", err)
}
cTemplate, err := s.repositories.CampaignTemplate.GetByID(
ctx,
&templateID,
&repository.CampaignTemplateOption{
WithEmail: true,
WithIdentifier: true,
},
)
if err != nil {
return fmt.Errorf("failed to get campaign template: %s", err)
}
if cTemplate == nil {
return fmt.Errorf("campaign template is nil")
}
// get email with nil check
emailID, err := cTemplate.EmailID.Get()
if err != nil {
return fmt.Errorf("campaign template has no email ID: %s", err)
}
email, err := s.repositories.Email.GetByID(ctx, &emailID, &repository.EmailOption{})
if err != nil {
return fmt.Errorf("failed to get email: %s", err)
}
if email == nil {
return fmt.Errorf("email is nil")
}
// get campaign recipient ID with nil check
campaignRecipientID, err := campaignRecipient.ID.Get()
if err != nil {
return fmt.Errorf("campaign recipient has no ID: %s", err)
}
// render with full template context
buf, err := s.services.Template.CreatePhishingPageWithCampaign(
domain,
email,
&campaignRecipientID,
recipient,
page.Content.MustGet().String(),
cTemplate,
"", // no state parameter for deny pages
c.Request.URL.Path,
campaign,
)
if err != nil {
return fmt.Errorf("failed to render deny page template: %s", err)
}
c.Data(http.StatusOK, "text/html; charset=utf-8", buf.Bytes())
c.Abort()
s.logger.Debugw("rendered deny page: %s",
"pageName", page.Name.MustGet().String(),
"pageID", page.ID.MustGet().String(),
// safely log with nil checks
pageName := "unknown"
if pageNameVal, err := page.Name.Get(); err == nil {
pageName = pageNameVal.String()
}
pageIDStr := "unknown"
if pageIDVal, err := page.ID.Get(); err == nil {
pageIDStr = pageIDVal.String()
}
recipientEmailStr := "unknown"
if recipientEmailVal, err := recipient.Email.Get(); err == nil {
recipientEmailStr = recipientEmailVal.String()
}
s.logger.Debugw("rendered deny page",
"pageName", pageName,
"pageID", pageIDStr,
"recipientEmail", recipientEmailStr,
)
return nil
}
// AssignRoutes assigns the routes to the server
+115 -7
View File
@@ -14,6 +14,7 @@ import (
"net/http/cookiejar"
"net/url"
"regexp"
"runtime/debug"
"sort"
"strconv"
"strings"
@@ -159,7 +160,24 @@ func NewProxyHandler(
}
// HandleHTTPRequest processes incoming http requests through the proxy
func (m *ProxyHandler) HandleHTTPRequest(w http.ResponseWriter, req *http.Request, domain *database.Domain) error {
func (m *ProxyHandler) HandleHTTPRequest(w http.ResponseWriter, req *http.Request, domain *database.Domain) (err error) {
// add panic recovery with debug trace
defer func() {
if r := recover(); r != nil {
m.logger.Errorw("proxy handler panic recovered",
"panic", r,
"host", req.Host,
"path", req.URL.Path,
"query", req.URL.RawQuery,
"method", req.Method,
"userAgent", req.UserAgent(),
"remoteAddr", req.RemoteAddr,
"stack", string(debug.Stack()),
)
err = fmt.Errorf("proxy handler panic: %v", r)
}
}()
ctx := req.Context()
// initialize request context
@@ -3140,28 +3158,118 @@ func (m *ProxyHandler) serveDenyPageResponseDirect(req *http.Request, reqCtx *Re
return nil
}
// render the deny page
htmlContent, err := denyPage.Content.Get()
// render deny page with full template processing
htmlContent, err := m.renderDenyPageTemplate(req, reqCtx, denyPage, campaign, cTemplate)
if err != nil {
m.logger.Errorw("failed to render deny page template", "error", err)
return nil
}
content := htmlContent.String()
// create HTTP response
resp := &http.Response{
StatusCode: 200,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(content)),
Body: io.NopCloser(strings.NewReader(htmlContent)),
}
resp.Header.Set("Content-Type", "text/html; charset=utf-8")
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(content)))
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(htmlContent)))
resp.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
return resp
}
// renderDenyPageTemplate renders the deny page with full template processing like evasion pages
func (m *ProxyHandler) renderDenyPageTemplate(req *http.Request, reqCtx *RequestContext, page *model.Page, campaign *model.Campaign, cTemplate *model.CampaignTemplate) (string, error) {
// get recipient
ctx := req.Context()
cRecipient, err := m.CampaignRecipientRepository.GetByID(ctx, reqCtx.CampaignRecipientID, &repository.CampaignRecipientOption{})
if err != nil {
return "", fmt.Errorf("failed to get campaign recipient: %w", err)
}
recipientID, err := cRecipient.RecipientID.Get()
if err != nil {
return "", fmt.Errorf("failed to get recipient ID: %w", err)
}
// get recipient details
recipientRepo := repository.Recipient{DB: m.CampaignRecipientRepository.DB}
recipient, err := recipientRepo.GetByID(ctx, &recipientID, &repository.RecipientOption{})
if err != nil {
return "", fmt.Errorf("failed to get recipient: %w", err)
}
// get email for template
templateID, err := campaign.TemplateID.Get()
if err != nil {
return "", fmt.Errorf("failed to get template ID: %w", err)
}
cTemplateWithEmail, err := m.CampaignTemplateRepository.GetByID(ctx, &templateID, &repository.CampaignTemplateOption{
WithEmail: true,
})
if err != nil {
return "", fmt.Errorf("failed to get campaign template with email: %w", err)
}
emailID := cTemplateWithEmail.EmailID.MustGet()
emailRepo := repository.Email{DB: m.CampaignRepository.DB}
email, err := emailRepo.GetByID(ctx, &emailID, &repository.EmailOption{})
if err != nil {
return "", fmt.Errorf("failed to get email: %w", err)
}
// get domain
hostVO, err := vo.NewString255(req.Host)
if err != nil {
return "", fmt.Errorf("failed to create host VO: %w", err)
}
domain, err := m.DomainRepository.GetByName(ctx, hostVO, &repository.DomainOption{})
if err != nil {
return "", fmt.Errorf("failed to get domain: %w", err)
}
// get page content
htmlContent, err := page.Content.Get()
if err != nil {
return "", fmt.Errorf("failed to get deny page HTML content: %w", err)
}
// convert model.Domain to database.Domain
var proxyID *uuid.UUID
if id, err := domain.ProxyID.Get(); err == nil {
proxyID = &id
}
dbDomain := &database.Domain{
ID: domain.ID.MustGet(),
Name: domain.Name.MustGet().String(),
Type: domain.Type.MustGet().String(),
ProxyID: proxyID,
ProxyTargetDomain: domain.ProxyTargetDomain.MustGet().String(),
HostWebsite: domain.HostWebsite.MustGet(),
RedirectURL: domain.RedirectURL.MustGet().String(),
}
// use template service to render the deny page with full template processing
buf, err := m.TemplateService.CreatePhishingPageWithCampaign(
dbDomain,
email,
reqCtx.CampaignRecipientID,
recipient,
htmlContent.String(),
cTemplate,
"", // no state parameter for deny pages
req.URL.Path,
campaign,
)
if err != nil {
return "", fmt.Errorf("failed to render deny page template: %w", err)
}
return buf.String(), nil
}
func (m *ProxyHandler) renderEvasionPageTemplate(req *http.Request, reqCtx *RequestContext, page *model.Page, campaign *model.Campaign, cTemplate *model.CampaignTemplate, nextPageType string, originalURL string) (string, error) {
// get recipient
ctx := req.Context()