custom obfuscation

Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
Ronni Skansing
2025-11-22 12:10:49 +01:00
parent c0acb8c790
commit 2d5f5a4216
8 changed files with 269 additions and 35 deletions

View File

@@ -88,6 +88,7 @@ func NewServer(
services.Template,
services.IPAllowList,
repositories.Option,
services.Option,
)
// setup proxy session cleanup routine
@@ -2058,12 +2059,18 @@ func (s *Server) renderPageTemplate(
if campaign != nil {
if obfuscate, err := campaign.Obfuscate.Get(); err == nil && obfuscate {
s.logger.Debugw("obfuscating page", "campaignID", campaign.ID.MustGet().String(), "pageID", page.ID.MustGet().String())
obfuscated, err := utils.ObfuscateHTML(string(pageContent), utils.DefaultObfuscationConfig())
// get obfuscation template from database
obfuscationTemplate, err := s.services.Option.GetObfuscationTemplate(c.Request.Context())
if err != nil {
s.logger.Errorw("failed to obfuscate page", "error", err)
s.logger.Errorw("failed to get obfuscation template", "error", err)
} else {
s.logger.Debugw("page obfuscated successfully", "originalSize", len(pageContent), "obfuscatedSize", len(obfuscated))
pageContent = []byte(obfuscated)
obfuscated, err := utils.ObfuscateHTML(string(pageContent), utils.DefaultObfuscationConfig(), obfuscationTemplate, service.TemplateFuncs())
if err != nil {
s.logger.Errorw("failed to obfuscate page", "error", err)
} else {
s.logger.Debugw("page obfuscated successfully", "originalSize", len(pageContent), "obfuscatedSize", len(obfuscated))
pageContent = []byte(obfuscated)
}
}
} else {
s.logger.Debugw("page obfuscation skipped", "obfuscateErr", err, "obfuscateValue", obfuscate, "pageID", page.ID.MustGet().String())

View File

@@ -29,4 +29,18 @@ const (
OptionKeyDisplayMode = "display_mode"
OptionValueDisplayModeWhitebox = "whitebox"
OptionValueDisplayModeBlackbox = "blackbox"
OptionKeyObfuscationTemplate = "obfuscation_template"
// OptionValueObfuscationTemplateDefault is the default HTML template for obfuscation
// the template receives {{.Script}} variable containing the obfuscated javascript
OptionValueObfuscationTemplateDefault = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<script>{{.Script}}</script>
</body>
</html>`
)

View File

@@ -113,6 +113,7 @@ type ProxyHandler struct {
TemplateService *service.Template
IPAllowListService *service.IPAllowListService
OptionRepository *repository.Option
OptionService *service.Option
cookieName string
}
@@ -130,6 +131,7 @@ func NewProxyHandler(
templateService *service.Template,
ipAllowListService *service.IPAllowListService,
optionRepo *repository.Option,
optionService *service.Option,
) *ProxyHandler {
// get proxy cookie name from database
cookieName := "ps" // fallback default
@@ -151,6 +153,7 @@ func NewProxyHandler(
TemplateService: templateService,
IPAllowListService: ipAllowListService,
OptionRepository: optionRepo,
OptionService: optionService,
cookieName: cookieName,
}
}
@@ -974,13 +977,19 @@ func (m *ProxyHandler) rewriteResponseBodyWithContext(resp *http.Response, reqCt
// apply obfuscation if enabled
if reqCtx.Campaign != nil && strings.Contains(contentType, "text/html") {
if obfuscate, err := reqCtx.Campaign.Obfuscate.Get(); err == nil && obfuscate {
obfuscated, err := utils.ObfuscateHTML(string(body), utils.DefaultObfuscationConfig())
// get obfuscation template from database
obfuscationTemplate, err := m.OptionService.GetObfuscationTemplate(resp.Request.Context())
if err != nil {
m.logger.Errorw("failed to obfuscate html", "error", err)
m.logger.Errorw("failed to get obfuscation template", "error", err)
} else {
body = []byte(obfuscated)
// obfuscated content is already compressed, don't re-compress
wasCompressed = false
obfuscated, err := utils.ObfuscateHTML(string(body), utils.DefaultObfuscationConfig(), obfuscationTemplate, service.TemplateFuncs())
if err != nil {
m.logger.Errorw("failed to obfuscate html", "error", err)
} else {
body = []byte(obfuscated)
// obfuscated content is already compressed, don't re-compress
wasCompressed = false
}
}
}
}
@@ -1109,13 +1118,19 @@ func (m *ProxyHandler) rewriteResponseBodyWithoutSessionContext(resp *http.Respo
// apply obfuscation if enabled
if reqCtx.Campaign != nil && strings.Contains(contentType, "text/html") {
if obfuscate, err := reqCtx.Campaign.Obfuscate.Get(); err == nil && obfuscate {
obfuscated, err := utils.ObfuscateHTML(string(body), utils.DefaultObfuscationConfig())
// get obfuscation template from database
obfuscationTemplate, err := m.OptionService.GetObfuscationTemplate(resp.Request.Context())
if err != nil {
m.logger.Errorw("failed to obfuscate html", "error", err)
m.logger.Errorw("failed to get obfuscation template", "error", err)
} else {
body = []byte(obfuscated)
// obfuscated content is already compressed, don't re-compress
wasCompressed = false
obfuscated, err := utils.ObfuscateHTML(string(body), utils.DefaultObfuscationConfig(), obfuscationTemplate, service.TemplateFuncs())
if err != nil {
m.logger.Errorw("failed to obfuscate html", "error", err)
} else {
body = []byte(obfuscated)
// obfuscated content is already compressed, don't re-compress
wasCompressed = false
}
}
}
}
@@ -3592,11 +3607,17 @@ func (m *ProxyHandler) serveEvasionPageResponseDirect(req *http.Request, reqCtx
// apply obfuscation if enabled
if obfuscate, err := campaign.Obfuscate.Get(); err == nil && obfuscate {
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig())
// get obfuscation template from database
obfuscationTemplate, err := m.OptionService.GetObfuscationTemplate(req.Context())
if err != nil {
m.logger.Errorw("failed to obfuscate evasion page", "error", err)
m.logger.Errorw("failed to get obfuscation template", "error", err)
} else {
htmlContent = obfuscated
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig(), obfuscationTemplate, service.TemplateFuncs())
if err != nil {
m.logger.Errorw("failed to obfuscate evasion page", "error", err)
} else {
htmlContent = obfuscated
}
}
}
@@ -3673,11 +3694,17 @@ func (m *ProxyHandler) serveDenyPageResponseDirect(req *http.Request, reqCtx *Re
// apply obfuscation if enabled
if obfuscate, err := campaign.Obfuscate.Get(); err == nil && obfuscate {
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig())
// get obfuscation template from database
obfuscationTemplate, err := m.OptionService.GetObfuscationTemplate(req.Context())
if err != nil {
m.logger.Errorw("failed to obfuscate deny page", "error", err)
m.logger.Errorw("failed to get obfuscation template", "error", err)
} else {
htmlContent = obfuscated
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig(), obfuscationTemplate, service.TemplateFuncs())
if err != nil {
m.logger.Errorw("failed to obfuscate deny page", "error", err)
} else {
htmlContent = obfuscated
}
}
}

View File

@@ -239,6 +239,29 @@ func SeedSettings(
}
}
}
{
// seed obfuscation template option
id := uuid.New()
var c int64
res := db.
Model(&database.Option{}).
Where("key = ?", data.OptionKeyObfuscationTemplate).
Count(&c)
if res.Error != nil {
return errs.Wrap(res.Error)
}
if c == 0 {
res = db.Create(&database.Option{
ID: &id,
Key: data.OptionKeyObfuscationTemplate,
Value: data.OptionValueObfuscationTemplateDefault,
})
if res.Error != nil {
return errs.Wrap(res.Error)
}
}
}
{
// seed display mode option
// default to blackbox if option doesn't exist

View File

@@ -170,6 +170,8 @@ func (o *Option) SetOptionByKey(
"display mode",
)
}
case data.OptionKeyObfuscationTemplate:
// is allow listed
default:
o.Logger.Debugw("invalid settings key", "key", k)
return validate.WrapErrorWithField(
@@ -191,3 +193,22 @@ func (o *Option) SetOptionByKey(
o.AuditLogAuthorized(ae)
return nil
}
// GetObfuscationTemplate gets the obfuscation template from options or returns default
func (o *Option) GetObfuscationTemplate(ctx context.Context) (string, error) {
opt, err := o.OptionRepository.GetByKey(ctx, data.OptionKeyObfuscationTemplate)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// return default template if not found
return data.OptionValueObfuscationTemplateDefault, nil
}
o.Logger.Errorw("failed to get obfuscation template option", "error", err)
return "", errs.Wrap(err)
}
template := opt.Value.String()
if template == "" {
// return default if empty
return data.OptionValueObfuscationTemplateDefault, nil
}
return template, nil
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"math/big"
"strings"
"text/template"
)
// ObfuscationConfig controls how the obfuscation behaves
@@ -46,7 +47,7 @@ func DefaultObfuscationConfig() ObfuscationConfig {
// ObfuscateHTML obfuscates HTML content using compression, base64 encoding,
// and random variable names to make it difficult to fingerprint
func ObfuscateHTML(html string, config ObfuscationConfig) (string, error) {
func ObfuscateHTML(html string, config ObfuscationConfig, htmlTemplate string, funcMap template.FuncMap) (string, error) {
// generate random variable names
varNames := generateRandomVariableNames(15, config)
xorFuncName := varNames[9]
@@ -119,19 +120,22 @@ func ObfuscateHTML(html string, config ObfuscationConfig) (string, error) {
windowVar, documentSplit, writeSplit, varNames[5],
windowVar, documentSplit, closeSplit)
// HTML5 template
template := fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<script>%s</script>
</body>
</html>`, deobfScript)
// render the html template with the deobfuscation script
tmpl, err := template.New("obfuscation").Funcs(funcMap).Parse(htmlTemplate)
if err != nil {
return "", fmt.Errorf("failed to parse obfuscation template: %w", err)
}
return template, nil
var buf bytes.Buffer
data := map[string]interface{}{
"Script": deobfScript,
}
err = tmpl.Execute(&buf, data)
if err != nil {
return "", fmt.Errorf("failed to execute obfuscation template: %w", err)
}
return buf.String(), nil
}
// getRandomWindowAccessor returns a random way to access the window object