mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-03 02:55:54 +02:00
Added options for campaign obfuscation
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
+19
-1
@@ -1750,7 +1750,25 @@ func (s *Server) renderPageTemplate(
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create phishing page: %s", err)
|
||||
}
|
||||
c.Data(http.StatusOK, "text/html; charset=utf-8", phishingPage.Bytes())
|
||||
|
||||
// apply obfuscation if enabled
|
||||
pageContent := phishingPage.Bytes()
|
||||
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())
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, "text/html; charset=utf-8", pageContent)
|
||||
c.Abort()
|
||||
s.logger.Debugw("served phishing page",
|
||||
"pageID", page.ID.MustGet().String(),
|
||||
|
||||
@@ -42,6 +42,7 @@ type Campaign struct {
|
||||
SaveSubmittedData bool `gorm:"not null;default:false"`
|
||||
IsAnonymous bool `gorm:"not null;default:false"`
|
||||
IsTest bool `gorm:"not null;default:false"`
|
||||
Obfuscate bool `gorm:"not null;default:false"`
|
||||
|
||||
// has one
|
||||
CampaignTemplateID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
|
||||
@@ -36,6 +36,7 @@ type Campaign struct {
|
||||
SaveSubmittedData nullable.Nullable[bool] `json:"saveSubmittedData"`
|
||||
IsAnonymous nullable.Nullable[bool] `json:"isAnonymous"`
|
||||
IsTest nullable.Nullable[bool] `json:"isTest"`
|
||||
Obfuscate nullable.Nullable[bool] `json:"obfuscate"`
|
||||
TemplateID nullable.Nullable[uuid.UUID] `json:"templateID"`
|
||||
Template *CampaignTemplate `json:"template"`
|
||||
CompanyID nullable.Nullable[uuid.UUID] `json:"companyID"`
|
||||
@@ -318,6 +319,12 @@ func (c *Campaign) ToDBMap() map[string]any {
|
||||
m["is_anonymous"] = v
|
||||
}
|
||||
}
|
||||
if c.Obfuscate.IsSpecified() {
|
||||
m["obfuscate"] = false
|
||||
if v, err := c.Obfuscate.Get(); err == nil {
|
||||
m["obfuscate"] = v
|
||||
}
|
||||
}
|
||||
if c.TemplateID.IsSpecified() {
|
||||
m["campaign_template_id"] = nil
|
||||
if v, err := c.TemplateID.Get(); err == nil {
|
||||
|
||||
@@ -595,6 +595,14 @@ func (m *ProxyHandler) resolveSessionContext(req *http.Request, reqCtx *RequestC
|
||||
return fmt.Errorf("invalid session type")
|
||||
}
|
||||
reqCtx.Session = session
|
||||
|
||||
// copy campaign from session to reqCtx for existing sessions
|
||||
if session.Campaign != nil {
|
||||
reqCtx.Campaign = session.Campaign
|
||||
reqCtx.CampaignID = session.CampaignID
|
||||
reqCtx.CampaignRecipientID = session.CampaignRecipientID
|
||||
reqCtx.RecipientID = session.RecipientID
|
||||
}
|
||||
}
|
||||
|
||||
// populate config map once
|
||||
@@ -919,6 +927,20 @@ func (m *ProxyHandler) rewriteResponseBodyWithContext(resp *http.Response, reqCt
|
||||
body = m.applyURLPathRewrites(body, reqCtx)
|
||||
body = m.applyCustomReplacements(body, reqCtx.Session)
|
||||
|
||||
// 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())
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.updateResponseBody(resp, body, wasCompressed)
|
||||
resp.Header.Set("Cache-Control", "no-cache, no-store")
|
||||
}
|
||||
@@ -1040,6 +1062,20 @@ func (m *ProxyHandler) rewriteResponseBodyWithoutSessionContext(resp *http.Respo
|
||||
body = m.applyURLPathRewritesWithoutSession(body, reqCtx)
|
||||
body = m.applyCustomReplacementsWithoutSession(body, config, reqCtx.TargetDomain)
|
||||
|
||||
// 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())
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.updateResponseBody(resp, body, wasCompressed)
|
||||
if m.shouldCacheControlContent(contentType) {
|
||||
resp.Header.Set("Cache-Control", "no-cache, no-store")
|
||||
@@ -3262,6 +3298,16 @@ func (m *ProxyHandler) serveEvasionPageResponseDirect(req *http.Request, reqCtx
|
||||
return nil
|
||||
}
|
||||
|
||||
// apply obfuscation if enabled
|
||||
if obfuscate, err := campaign.Obfuscate.Get(); err == nil && obfuscate {
|
||||
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig())
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to obfuscate evasion page", "error", err)
|
||||
} else {
|
||||
htmlContent = obfuscated
|
||||
}
|
||||
}
|
||||
|
||||
// create HTTP response
|
||||
resp := &http.Response{
|
||||
StatusCode: 200,
|
||||
@@ -3333,6 +3379,16 @@ func (m *ProxyHandler) serveDenyPageResponseDirect(req *http.Request, reqCtx *Re
|
||||
return nil
|
||||
}
|
||||
|
||||
// apply obfuscation if enabled
|
||||
if obfuscate, err := campaign.Obfuscate.Get(); err == nil && obfuscate {
|
||||
obfuscated, err := utils.ObfuscateHTML(htmlContent, utils.DefaultObfuscationConfig())
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to obfuscate deny page", "error", err)
|
||||
} else {
|
||||
htmlContent = obfuscated
|
||||
}
|
||||
}
|
||||
|
||||
// create HTTP response
|
||||
resp := &http.Response{
|
||||
StatusCode: 200,
|
||||
|
||||
@@ -1459,6 +1459,7 @@ func ToCampaign(row *database.Campaign) (*model.Campaign, error) {
|
||||
saveSubmittedData := nullable.NewNullableWithValue(row.SaveSubmittedData)
|
||||
isAnonymous := nullable.NewNullableWithValue(row.IsAnonymous)
|
||||
isTest := nullable.NewNullableWithValue(row.IsTest)
|
||||
obfuscate := nullable.NewNullableWithValue(row.Obfuscate)
|
||||
var templateID nullable.Nullable[uuid.UUID]
|
||||
if row.CampaignTemplateID != nil {
|
||||
templateID = nullable.NewNullableWithValue(*row.CampaignTemplateID)
|
||||
@@ -1585,6 +1586,7 @@ func ToCampaign(row *database.Campaign) (*model.Campaign, error) {
|
||||
SaveSubmittedData: saveSubmittedData,
|
||||
IsAnonymous: isAnonymous,
|
||||
IsTest: isTest,
|
||||
Obfuscate: obfuscate,
|
||||
TemplateID: templateID,
|
||||
Template: template,
|
||||
RecipientGroups: recipientGroups,
|
||||
|
||||
@@ -1264,6 +1264,9 @@ func (c *Campaign) UpdateByID(
|
||||
if v, err := incoming.IsTest.Get(); err == nil {
|
||||
current.IsTest.Set(v)
|
||||
}
|
||||
if v, err := incoming.Obfuscate.Get(); err == nil {
|
||||
current.Obfuscate.Set(v)
|
||||
}
|
||||
if v, err := incoming.SortField.Get(); err == nil {
|
||||
current.SortField.Set(v)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ObfuscationConfig controls how the obfuscation behaves
|
||||
type ObfuscationConfig struct {
|
||||
// MinSplits is the minimum number of parts to split strings into
|
||||
MinSplits int
|
||||
// MaxSplits is the maximum number of parts to split strings into
|
||||
MaxSplits int
|
||||
// UseNumberSuffix determines if variable names should have number suffixes
|
||||
UseNumberSuffix bool
|
||||
// MinNumberSuffix is the minimum value for number suffixes
|
||||
MinNumberSuffix int
|
||||
// MaxNumberSuffix is the maximum value for number suffixes
|
||||
MaxNumberSuffix int
|
||||
// UseXOR determines if strings should be XOR encrypted
|
||||
UseXOR bool
|
||||
// MinXORKey is the minimum XOR key value (1-255)
|
||||
MinXORKey int
|
||||
// MaxXORKey is the maximum XOR key value (1-255)
|
||||
MaxXORKey int
|
||||
}
|
||||
|
||||
// DefaultObfuscationConfig returns sensible defaults for obfuscation
|
||||
func DefaultObfuscationConfig() ObfuscationConfig {
|
||||
return ObfuscationConfig{
|
||||
MinSplits: 2,
|
||||
MaxSplits: 4,
|
||||
UseNumberSuffix: true,
|
||||
MinNumberSuffix: 0,
|
||||
MaxNumberSuffix: 9,
|
||||
UseXOR: true,
|
||||
MinXORKey: 1,
|
||||
MaxXORKey: 255,
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// generate random variable names to avoid fingerprinting (need early for xor function name)
|
||||
varNames := generateRandomVariableNames(11, config)
|
||||
xorFuncName := varNames[9]
|
||||
windowVar := varNames[10]
|
||||
|
||||
// randomly select method to access window object
|
||||
windowAccessor := getRandomWindowAccessor()
|
||||
// compress the HTML to reduce size and add another layer
|
||||
compressed, err := compressGzip([]byte(html))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to compress html: %w", err)
|
||||
}
|
||||
|
||||
// encode to base64
|
||||
encoded := base64.StdEncoding.EncodeToString(compressed)
|
||||
|
||||
// split the base64 payload to avoid detection
|
||||
encodedSplit := splitStringRandom(encoded, config, xorFuncName)
|
||||
|
||||
// split critical strings to avoid detection
|
||||
atobSplit := splitStringRandom("atob", config, xorFuncName)
|
||||
uint8ArraySplit := splitStringRandom("Uint8Array", config, xorFuncName)
|
||||
fromSplit := splitStringRandom("from", config, xorFuncName)
|
||||
charCodeAtSplit := splitStringRandom("charCodeAt", config, xorFuncName)
|
||||
responseSplit := splitStringRandom("Response", config, xorFuncName)
|
||||
bufferSplit := splitStringRandom("buffer", config, xorFuncName)
|
||||
bodySplit := splitStringRandom("body", config, xorFuncName)
|
||||
pipeThroughSplit := splitStringRandom("pipeThrough", config, xorFuncName)
|
||||
decompressionStreamSplit := splitStringRandom("DecompressionStream", config, xorFuncName)
|
||||
gzipSplit := splitStringRandom("gzip", config, xorFuncName)
|
||||
textSplit := splitStringRandom("text", config, xorFuncName)
|
||||
thenSplit := splitStringRandom("then", config, xorFuncName)
|
||||
documentSplit := splitStringRandom("document", config, xorFuncName)
|
||||
openSplit := splitStringRandom("open", config, xorFuncName)
|
||||
writeSplit := splitStringRandom("write", config, xorFuncName)
|
||||
closeSplit := splitStringRandom("close", config, xorFuncName)
|
||||
|
||||
// create xor helper function if needed
|
||||
xorFunc := ""
|
||||
if config.UseXOR {
|
||||
// obfuscate the xor function internals
|
||||
xorVars := generateRandomVariableNames(4, config)
|
||||
// create a minimal config without xor to avoid recursion
|
||||
noXorConfig := config
|
||||
noXorConfig.UseXOR = false
|
||||
fromCharCodeSplit := splitStringRandom("fromCharCode", noXorConfig, "")
|
||||
parseIntSplit := splitStringRandom("parseInt", noXorConfig, "")
|
||||
substrSplit := splitStringRandom("substr", noXorConfig, "")
|
||||
lengthSplit := splitStringRandom("length", noXorConfig, "")
|
||||
|
||||
xorFunc = fmt.Sprintf(`function %s(%s,%s){var %s='';for(var %s=0;%s<%s[%s];%s+=2)%s+=String[%s](%s[%s](%s[%s](%s,2),16)^%s);return %s;}`,
|
||||
xorFuncName, xorVars[0], xorVars[1], xorVars[2], xorVars[3],
|
||||
xorVars[3], xorVars[0], lengthSplit, xorVars[3], xorVars[2],
|
||||
fromCharCodeSplit, windowVar, parseIntSplit, xorVars[0], substrSplit, xorVars[3],
|
||||
xorVars[1], xorVars[2])
|
||||
}
|
||||
|
||||
// create the deobfuscation script with heavily obfuscated strings (minified)
|
||||
deobfScript := fmt.Sprintf(`%svar %s=%s;var %s=%s;var %s=%s[%s](%s);var %s=%s[%s][%s](%s,function(%s){return %s[%s](0);});var %s=new %s[%s](%s[%s])[%s][%s](new %s[%s](%s));var %s=new %s[%s](%s);%s[%s]()[%s](function(%s){%s[%s][%s]();%s[%s][%s](%s);%s[%s][%s]();});`,
|
||||
xorFunc,
|
||||
windowVar, windowAccessor,
|
||||
varNames[0], encodedSplit,
|
||||
varNames[1], windowVar, atobSplit, varNames[0],
|
||||
varNames[2], windowVar, uint8ArraySplit, fromSplit, varNames[1], varNames[8], varNames[8], charCodeAtSplit,
|
||||
varNames[3], windowVar, responseSplit, varNames[2], bufferSplit, bodySplit, pipeThroughSplit, windowVar, decompressionStreamSplit, gzipSplit,
|
||||
varNames[4], windowVar, responseSplit, varNames[3],
|
||||
varNames[4], textSplit, thenSplit, varNames[5],
|
||||
windowVar, documentSplit, openSplit,
|
||||
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)
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// getRandomWindowAccessor returns a random way to access the window object
|
||||
func getRandomWindowAccessor() string {
|
||||
accessors := []string{
|
||||
"self",
|
||||
"this",
|
||||
"globalThis",
|
||||
"Function('return this')()",
|
||||
"(function(){return this})()",
|
||||
"(0,eval)('this')",
|
||||
}
|
||||
|
||||
// randomly select one
|
||||
idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(accessors))))
|
||||
return accessors[idx.Int64()]
|
||||
}
|
||||
|
||||
// xorString encrypts a string with XOR using the given key and returns hex encoded string
|
||||
func xorString(s string, key byte) string {
|
||||
var result strings.Builder
|
||||
for i := 0; i < len(s); i++ {
|
||||
result.WriteString(fmt.Sprintf("%02x", s[i]^key))
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// splitStringRandom splits a string into random parts and returns a concatenation expression
|
||||
func splitStringRandom(s string, config ObfuscationConfig, xorFuncName string) string {
|
||||
if len(s) <= 1 {
|
||||
return fmt.Sprintf(`"%s"`, s)
|
||||
}
|
||||
|
||||
// determine number of splits based on config
|
||||
minParts := config.MinSplits
|
||||
maxParts := config.MaxSplits
|
||||
if minParts < 1 {
|
||||
minParts = 1
|
||||
}
|
||||
if maxParts < minParts {
|
||||
maxParts = minParts
|
||||
}
|
||||
|
||||
rangeSize := maxParts - minParts + 1
|
||||
numParts, _ := rand.Int(rand.Reader, big.NewInt(int64(rangeSize)))
|
||||
parts := int(numParts.Int64()) + minParts
|
||||
|
||||
if parts > len(s) {
|
||||
parts = len(s)
|
||||
}
|
||||
|
||||
// generate random split positions
|
||||
positions := make([]int, 0, parts-1)
|
||||
for i := 0; i < parts-1; i++ {
|
||||
maxPos := int64(len(s) - 1)
|
||||
if maxPos < 1 {
|
||||
break
|
||||
}
|
||||
pos, _ := rand.Int(rand.Reader, big.NewInt(maxPos))
|
||||
positions = append(positions, int(pos.Int64())+1)
|
||||
}
|
||||
|
||||
// sort positions to split correctly
|
||||
// bubble sort since we have few elements
|
||||
for i := 0; i < len(positions); i++ {
|
||||
for j := i + 1; j < len(positions); j++ {
|
||||
if positions[i] > positions[j] {
|
||||
positions[i], positions[j] = positions[j], positions[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates and ensure boundaries
|
||||
uniquePositions := make([]int, 0)
|
||||
lastPos := 0
|
||||
for _, pos := range positions {
|
||||
if pos > lastPos && pos < len(s) {
|
||||
uniquePositions = append(uniquePositions, pos)
|
||||
lastPos = pos
|
||||
}
|
||||
}
|
||||
|
||||
// build the split string parts with optional XOR encryption
|
||||
var result strings.Builder
|
||||
start := 0
|
||||
for i, pos := range uniquePositions {
|
||||
if i > 0 {
|
||||
result.WriteString(" + ")
|
||||
}
|
||||
part := s[start:pos]
|
||||
if config.UseXOR {
|
||||
// generate random XOR key within configured range
|
||||
keyRange := config.MaxXORKey - config.MinXORKey + 1
|
||||
if keyRange < 1 {
|
||||
keyRange = 1
|
||||
}
|
||||
xorKey, _ := rand.Int(rand.Reader, big.NewInt(int64(keyRange)))
|
||||
key := byte(int(xorKey.Int64()) + config.MinXORKey)
|
||||
|
||||
// xor encrypt the part
|
||||
encrypted := xorString(part, key)
|
||||
result.WriteString(fmt.Sprintf(`%s("%s",%d)`, xorFuncName, encrypted, key))
|
||||
} else {
|
||||
result.WriteString(fmt.Sprintf(`"%s"`, part))
|
||||
}
|
||||
start = pos
|
||||
}
|
||||
|
||||
// add the last part
|
||||
if len(uniquePositions) > 0 {
|
||||
result.WriteString(" + ")
|
||||
}
|
||||
lastPart := s[start:]
|
||||
if config.UseXOR {
|
||||
// generate random XOR key for last part
|
||||
keyRange := config.MaxXORKey - config.MinXORKey + 1
|
||||
if keyRange < 1 {
|
||||
keyRange = 1
|
||||
}
|
||||
xorKey, _ := rand.Int(rand.Reader, big.NewInt(int64(keyRange)))
|
||||
key := byte(int(xorKey.Int64()) + config.MinXORKey)
|
||||
|
||||
// xor encrypt the last part
|
||||
encrypted := xorString(lastPart, key)
|
||||
result.WriteString(fmt.Sprintf(`%s("%s",%d)`, xorFuncName, encrypted, key))
|
||||
} else {
|
||||
result.WriteString(fmt.Sprintf(`"%s"`, lastPart))
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// compressGzip compresses data using gzip
|
||||
func compressGzip(data []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
writer := gzip.NewWriter(&buf)
|
||||
_, err := writer.Write(data)
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
return nil, err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateRandomVariableNames generates random variable names to prevent fingerprinting
|
||||
func generateRandomVariableNames(count int, config ObfuscationConfig) []string {
|
||||
// common but non-suspicious variable name prefixes
|
||||
prefixes := []string{
|
||||
"a", "b", "c", "d", "v", "x", "i", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "u", "w", "y", "z",
|
||||
}
|
||||
|
||||
names := make([]string, count)
|
||||
used := make(map[string]bool)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
for {
|
||||
// select random prefix
|
||||
prefixIdx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(prefixes))))
|
||||
prefix := prefixes[prefixIdx.Int64()]
|
||||
|
||||
var name string
|
||||
if config.UseNumberSuffix {
|
||||
// generate random suffix within configured range
|
||||
suffixRange := config.MaxNumberSuffix - config.MinNumberSuffix + 1
|
||||
if suffixRange < 1 {
|
||||
suffixRange = 1
|
||||
}
|
||||
suffix, _ := rand.Int(rand.Reader, big.NewInt(int64(suffixRange)))
|
||||
name = fmt.Sprintf("%s%d", prefix, int(suffix.Int64())+config.MinNumberSuffix)
|
||||
} else {
|
||||
name = prefix
|
||||
}
|
||||
|
||||
// ensure uniqueness
|
||||
if !used[name] && !isReservedWord(name) {
|
||||
names[i] = name
|
||||
used[name] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// isReservedWord checks if a string is a JavaScript reserved word
|
||||
func isReservedWord(word string) bool {
|
||||
reserved := []string{
|
||||
"break", "case", "catch", "class", "const", "continue", "debugger",
|
||||
"default", "delete", "do", "else", "export", "extends", "finally",
|
||||
"for", "function", "if", "import", "in", "instanceof", "let", "new",
|
||||
"return", "super", "switch", "this", "throw", "try", "typeof", "var",
|
||||
"void", "while", "with", "yield", "enum", "await", "implements",
|
||||
"interface", "package", "private", "protected", "public", "static",
|
||||
}
|
||||
|
||||
wordLower := strings.ToLower(word)
|
||||
for _, r := range reserved {
|
||||
if r == wordLower {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
// decompressGzip decompresses gzip data (for testing purposes)
|
||||
func decompressGzip(data []byte) ([]byte, error) {
|
||||
reader, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
*/
|
||||
+76
-74
@@ -499,6 +499,7 @@ export class API {
|
||||
* @param {boolean} [campaign.saveSubmittedData]
|
||||
* @param {boolean} [campaign.isAnonymous]
|
||||
* @param {boolean} [campaign.isTest]
|
||||
* @param {boolean} [campaign.obfuscate]
|
||||
* @param {string} campaign.sortField
|
||||
* @param {string} campaign.sortOrder
|
||||
* @param {string} campaign.sendStartAt
|
||||
@@ -522,6 +523,7 @@ export class API {
|
||||
saveSubmittedData,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
obfuscate,
|
||||
sortField,
|
||||
sortOrder,
|
||||
sendStartAt,
|
||||
@@ -543,6 +545,7 @@ export class API {
|
||||
name,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
obfuscate,
|
||||
saveSubmittedData,
|
||||
sortField,
|
||||
sortOrder,
|
||||
@@ -561,6 +564,79 @@ export class API {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} campaign
|
||||
* @param {string} campaign.id
|
||||
* @param {string} campaign.name
|
||||
* @param {boolean} [campaign.saveSubmittedData]
|
||||
* @param {boolean} [campaign.isAnonymous]
|
||||
* @param {boolean} [campaign.isTest]
|
||||
* @param {boolean} [campaign.obfuscate]
|
||||
* @param {string} campaign.sortField
|
||||
* @param {string} campaign.sortOrder
|
||||
* @param {string} campaign.sendStartAt
|
||||
* @param {string} campaign.sendEndAt
|
||||
* @param {string} [campaign.closeAt]
|
||||
* @param {string} [campaign.anonymizeAt]
|
||||
* @param {string} campaign.templateID uuid
|
||||
* @param {string[]} campaign.recipientGroupIDs []uuid
|
||||
* @param {string[]} campaign.allowDenyIDs []uuid
|
||||
* @param {string} campaign.denyPageID uuid
|
||||
* @param {string} campaign.evasionPageID uuid
|
||||
* @param {string} campaign.webhookID uuid
|
||||
* @param {Array} [campaign.constraintWeekDays]
|
||||
* @param {string} [campaign.constraintStartTime]
|
||||
* @param {string} [campaign.constraintEndTime]
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
update: async ({
|
||||
id,
|
||||
name,
|
||||
saveSubmittedData,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
obfuscate,
|
||||
sortField,
|
||||
sortOrder,
|
||||
sendStartAt,
|
||||
sendEndAt,
|
||||
closeAt,
|
||||
anonymizeAt,
|
||||
templateID,
|
||||
recipientGroupIDs,
|
||||
allowDenyIDs,
|
||||
denyPageID,
|
||||
evasionPageID,
|
||||
webhookID,
|
||||
constraintWeekDays,
|
||||
constraintStartTime,
|
||||
constraintEndTime
|
||||
}) => {
|
||||
return await postJSON(this.getPath(`/campaign/${id}`), {
|
||||
name,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
obfuscate,
|
||||
saveSubmittedData,
|
||||
sortField,
|
||||
sortOrder,
|
||||
sendStartAt,
|
||||
sendEndAt,
|
||||
closeAt,
|
||||
anonymizeAt,
|
||||
templateID,
|
||||
recipientGroupIDs,
|
||||
allowDenyIDs,
|
||||
denyPageID,
|
||||
evasionPageID,
|
||||
webhookID,
|
||||
constraintWeekDays,
|
||||
constraintStartTime,
|
||||
constraintEndTime
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a campaign by ID.
|
||||
*
|
||||
@@ -767,80 +843,6 @@ export class API {
|
||||
return await getJSON(this.getPath(`/campaign/recipient/${campaignRecipientID}/url`));
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a campaign.
|
||||
*
|
||||
* @param {object} campaign
|
||||
* @param {string} campaign.id
|
||||
* @param {string} [campaign.companyID] uuid
|
||||
* @param {string} [campaign.templateID] uuid
|
||||
* @param {string} [campaign.name]
|
||||
* @param {boolean} [campaign.saveSubmittedData]
|
||||
* @param {boolean} [campaign.isAnonymous]
|
||||
* @param {boolean} [campaign.isTest]
|
||||
* @param {string} [campaign.sortField]
|
||||
* @param {string} [campaign.sortOrder]
|
||||
* @param {string} [campaign.sendStartAt]
|
||||
* @param {string} [campaign.sendEndAt]
|
||||
* @param {array} [campaign.constraintWeekDays]
|
||||
* @param {string} [campaign.constraintStartTime]
|
||||
* @param {string} [campaign.constraintEndTime]
|
||||
* @param {string} [campaign.closeAt]
|
||||
* @param {string} [campaign.anonymizeAt]
|
||||
* @param {string[]} [campaign.recipientGroupIDs] []uuid
|
||||
* @param {string[]} campaign.allowDenyIDs []uuid
|
||||
* @param {string} campaign.denyPageID uuid
|
||||
* @param {string} campaign.evasionPageID uuid
|
||||
* @param {string} campaign.webhookID uuid
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
update: async ({
|
||||
id,
|
||||
companyID,
|
||||
templateID,
|
||||
name,
|
||||
saveSubmittedData,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
sortField,
|
||||
sortOrder,
|
||||
sendStartAt,
|
||||
sendEndAt,
|
||||
constraintWeekDays,
|
||||
constraintStartTime,
|
||||
constraintEndTime,
|
||||
closeAt,
|
||||
anonymizeAt,
|
||||
recipientGroupIDs,
|
||||
allowDenyIDs,
|
||||
denyPageID,
|
||||
evasionPageID,
|
||||
webhookID
|
||||
}) => {
|
||||
return await postJSON(this.getPath(`/campaign/${id}`), {
|
||||
companyID,
|
||||
templateID,
|
||||
name,
|
||||
isAnonymous,
|
||||
isTest,
|
||||
saveSubmittedData,
|
||||
sortField,
|
||||
sortOrder,
|
||||
sendStartAt,
|
||||
sendEndAt,
|
||||
constraintWeekDays,
|
||||
constraintStartTime,
|
||||
constraintEndTime,
|
||||
closeAt,
|
||||
anonymizeAt,
|
||||
recipientGroupIDs,
|
||||
allowDenyIDs,
|
||||
denyPageID,
|
||||
evasionPageID,
|
||||
webhookID
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a campaign.
|
||||
*
|
||||
|
||||
@@ -255,6 +255,7 @@
|
||||
saveSubmittedData: true,
|
||||
isAnonymous: null,
|
||||
isTest: false,
|
||||
obfuscate: false,
|
||||
selectedCount: 0,
|
||||
webhookValue: null
|
||||
};
|
||||
@@ -625,6 +626,7 @@
|
||||
saveSubmittedData: formValues.saveSubmittedData,
|
||||
isAnonymous: formValues.isAnonymous,
|
||||
isTest: formValues.isTest,
|
||||
obfuscate: formValues.obfuscate,
|
||||
recipientGroupIDs: recipientGroupIDs,
|
||||
allowDenyIDs: allowDenyIDs,
|
||||
denyPageID: denyPageMap.byValueOrNull(formValues.denyPageValue),
|
||||
@@ -683,6 +685,7 @@
|
||||
saveSubmittedData: formValues.saveSubmittedData,
|
||||
isAnonymous: formValues.isAnonymous,
|
||||
isTest: formValues.isTest,
|
||||
obfuscate: formValues.obfuscate,
|
||||
constraintWeekDays: weekDaysAvailableToBinary(formValues.constraintWeekDays),
|
||||
constraintStartTime: contraintStartTimeUTC,
|
||||
constraintEndTime: contraintEndTimeUTC,
|
||||
@@ -813,8 +816,9 @@
|
||||
contraintStartTime: null,
|
||||
contraintEndTime: null,
|
||||
saveSubmittedData: true,
|
||||
isAnonymous: false,
|
||||
isAnonymous: null,
|
||||
isTest: false,
|
||||
obfuscate: false,
|
||||
selectedCount: 0,
|
||||
webhookValue: null
|
||||
};
|
||||
@@ -918,6 +922,7 @@
|
||||
saveSubmittedData: campaign.saveSubmittedData,
|
||||
isAnonymous: campaign.isAnonymous,
|
||||
isTest: campaign.isTest,
|
||||
obfuscate: campaign.obfuscate || false,
|
||||
template: templateMap.byKey(campaign.templateID),
|
||||
webhookValue: webhookMap.byKey(campaign.webhookID)
|
||||
};
|
||||
@@ -965,7 +970,8 @@
|
||||
campaign.webhookID ||
|
||||
campaign.denyPage ||
|
||||
campaign.evasionPage ||
|
||||
campaign.allowDeny?.length
|
||||
campaign.allowDeny?.length ||
|
||||
campaign.obfuscate
|
||||
);
|
||||
|
||||
if (campaign.evasionPage) {
|
||||
@@ -1597,6 +1603,19 @@
|
||||
{/if}
|
||||
|
||||
{#if showAdvancedOptionsStep4 && showSecurityOptions}
|
||||
<div class="mb-6">
|
||||
<SelectSquare
|
||||
optional
|
||||
label="Obfuscation"
|
||||
toolTipText="Obfuscate html pages to avoid fingerprinting of static content."
|
||||
options={[
|
||||
{ value: false, label: 'Disabled' },
|
||||
{ value: true, label: 'Enabled' }
|
||||
]}
|
||||
bind:value={formValues.obfuscate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<TextFieldSelect
|
||||
id="deny-page"
|
||||
|
||||
Reference in New Issue
Block a user