mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-07 21:07:56 +02:00
+11
-4
@@ -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())
|
||||
|
||||
@@ -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>`
|
||||
)
|
||||
|
||||
+43
-16
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+17
-13
@@ -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
|
||||
|
||||
@@ -2204,7 +2204,7 @@ export class API {
|
||||
/**
|
||||
* Get setting by key.
|
||||
*
|
||||
* @param {'is_installed'|'max_file_upload_size_mb'|'repeat_offender_months'|'sso_login'|'display_mode'} key
|
||||
* @param {'is_installed'|'max_file_upload_size_mb'|'repeat_offender_months'|'sso_login'|'display_mode'|'obfuscation_template'} key
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
get: async (key) => {
|
||||
@@ -2214,7 +2214,7 @@ export class API {
|
||||
/**
|
||||
* Set setting by key and value.
|
||||
*
|
||||
* @param {'max_file_upload_size_mb'|'repeat_offender_months'|'sso_login'|'display_mode'} key
|
||||
* @param {'max_file_upload_size_mb'|'repeat_offender_months'|'sso_login'|'display_mode'|'obfuscation_template'} key
|
||||
* @param {string} value
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
import PasswordField from '$lib/components/PasswordField.svelte';
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TextFieldSelect from '$lib/components/TextFieldSelect.svelte';
|
||||
import SimpleCodeEditor from '$lib/components/editor/SimpleCodeEditor.svelte';
|
||||
import { AppStateService } from '$lib/service/appState';
|
||||
import { hideIsLoading, showIsLoading } from '$lib/store/loading';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import { onMount } from 'svelte';
|
||||
import { onClickCopy } from '$lib/utils/common';
|
||||
import { displayMode, DISPLAY_MODE } from '$lib/store/displayMode';
|
||||
import ConditionalDisplay from '$lib/components/ConditionalDisplay.svelte';
|
||||
|
||||
const logLevels = ['debug', 'info', 'warn', 'error'];
|
||||
const dbLogLevels = ['silent', 'info', 'warn', 'error'];
|
||||
@@ -78,6 +80,12 @@
|
||||
let importForCompany = false;
|
||||
let contextCompanyID = null;
|
||||
|
||||
// obfuscation template editor
|
||||
let isObfuscationTemplateModalVisible = false;
|
||||
let obfuscationTemplate = '';
|
||||
let obfuscationTemplateError = '';
|
||||
let isObfuscationTemplateSubmitting = false;
|
||||
|
||||
$: {
|
||||
isCompanyContext = appState.isCompanyContext();
|
||||
importForCompany = isCompanyContext;
|
||||
@@ -465,6 +473,65 @@
|
||||
isImportSubmitting = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Open obfuscation template modal
|
||||
*/
|
||||
const openObfuscationTemplateModal = async () => {
|
||||
try {
|
||||
showIsLoading();
|
||||
const response = await api.option.get('obfuscation_template');
|
||||
if (response.success) {
|
||||
obfuscationTemplate = response.data.value || '';
|
||||
} else {
|
||||
obfuscationTemplateError = 'Failed to load template';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load obfuscation template:', error);
|
||||
obfuscationTemplateError = 'Failed to load template';
|
||||
} finally {
|
||||
hideIsLoading();
|
||||
isObfuscationTemplateModalVisible = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close obfuscation template modal
|
||||
*/
|
||||
const closeObfuscationTemplateModal = () => {
|
||||
isObfuscationTemplateModalVisible = false;
|
||||
obfuscationTemplateError = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit obfuscation template
|
||||
*/
|
||||
const onSubmitObfuscationTemplate = async (event) => {
|
||||
const saveOnly = event?.detail?.saveOnly || false;
|
||||
isObfuscationTemplateSubmitting = true;
|
||||
obfuscationTemplateError = '';
|
||||
|
||||
try {
|
||||
const response = await api.option.set('obfuscation_template', obfuscationTemplate);
|
||||
|
||||
if (response.success) {
|
||||
addToast(
|
||||
saveOnly ? 'Obfuscation template saved' : 'Obfuscation template updated',
|
||||
'Success'
|
||||
);
|
||||
if (!saveOnly) {
|
||||
isObfuscationTemplateModalVisible = false;
|
||||
}
|
||||
} else {
|
||||
obfuscationTemplateError = response.error || 'Failed to update template';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update obfuscation template:', error);
|
||||
obfuscationTemplateError = 'Failed to update template';
|
||||
} finally {
|
||||
isObfuscationTemplateSubmitting = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<HeadTitle title="Settings" />
|
||||
@@ -764,6 +831,40 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Obfuscation Template Section -->
|
||||
<ConditionalDisplay show="blackbox">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-sm dark:shadow-gray-900/50 border border-gray-100 dark:border-gray-700 h-[420px] flex flex-col transition-colors duration-200"
|
||||
>
|
||||
<h2
|
||||
class="text-xl font-semibold text-gray-700 dark:text-gray-200 mb-6 transition-colors duration-200"
|
||||
>
|
||||
Obfuscation Template
|
||||
</h2>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="space-y-4">
|
||||
<p class="text-gray-600 dark:text-gray-300 text-sm transition-colors duration-200">
|
||||
Customize the template used when obfuscation is enabled to.
|
||||
</p>
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-700 p-3 rounded-md transition-colors duration-200"
|
||||
>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
|
||||
<strong>Internal obfuscation variable:</strong>
|
||||
</p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 font-mono">
|
||||
{'{{.Script}}'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto pt-4">
|
||||
<Button size={'large'} on:click={openObfuscationTemplateModal}>Edit Template</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ConditionalDisplay>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1163,4 +1264,41 @@
|
||||
</FormGrid>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
{#if isObfuscationTemplateModalVisible}
|
||||
<Modal
|
||||
bind:visible={isObfuscationTemplateModalVisible}
|
||||
headerText="Edit Obfuscation Template"
|
||||
onClose={closeObfuscationTemplateModal}
|
||||
{isSubmitting}
|
||||
>
|
||||
<FormGrid
|
||||
on:submit={onSubmitObfuscationTemplate}
|
||||
isSubmitting={isObfuscationTemplateSubmitting}
|
||||
modalMode="update"
|
||||
>
|
||||
<div
|
||||
class="w-80vw col-start-1 col-end-4 row-start-1 py-8 px-6 flex flex-col bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<SimpleCodeEditor
|
||||
bind:value={obfuscationTemplate}
|
||||
language="html"
|
||||
height="large"
|
||||
showVimToggle={true}
|
||||
showExpandButton={false}
|
||||
/>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300 my-4">
|
||||
Example <code class="bg-gray-200 dark:bg-gray-700 p-1 rounded text-xs"
|
||||
>{"eval(atob('{{base64 .Script}}'))"}</code
|
||||
>
|
||||
</p>
|
||||
<FormError message={obfuscationTemplateError} />
|
||||
</div>
|
||||
<FormFooter
|
||||
isSubmitting={isObfuscationTemplateSubmitting}
|
||||
closeModal={closeObfuscationTemplateModal}
|
||||
/>
|
||||
</FormGrid>
|
||||
</Modal>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user