mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-05 03:47:53 +02:00
added proxy submit info event type
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
+83
-2
@@ -1668,7 +1668,11 @@ func (m *ProxyHandler) handlePathBasedCapture(capture service.ProxyServiceCaptur
|
||||
webhookData := map[string]interface{}{
|
||||
capture.Name: capturedData,
|
||||
}
|
||||
m.createCampaignSubmitEvent(session, webhookData, resp.Request, session.UserAgent)
|
||||
if capture.Event == "info" {
|
||||
m.createCampaignInfoEvent(session, webhookData, resp.Request, session.UserAgent)
|
||||
} else {
|
||||
m.createCampaignSubmitEvent(session, webhookData, resp.Request, session.UserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// check if cookie bundle should be submitted now that this capture is complete
|
||||
@@ -1850,7 +1854,11 @@ func (m *ProxyHandler) captureFromTextWithResponse(text string, capture service.
|
||||
webhookData := map[string]interface{}{
|
||||
capture.Name: capturedData,
|
||||
}
|
||||
m.createCampaignSubmitEvent(session, webhookData, req, session.UserAgent)
|
||||
if capture.Event == "info" {
|
||||
m.createCampaignInfoEvent(session, webhookData, req, session.UserAgent)
|
||||
} else {
|
||||
m.createCampaignSubmitEvent(session, webhookData, req, session.UserAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// check if we should submit cookie bundle (only when all captures complete)
|
||||
@@ -3565,6 +3573,79 @@ func (m *ProxyHandler) buildCampaignFlowRedirectURL(session *service.ProxySessio
|
||||
return targetURL
|
||||
}
|
||||
|
||||
// createCampaignInfoEvent saves captured data as a low-priority info event instead of a submit event.
|
||||
// The capture still participates in completion tracking and flow progression normally — only the
|
||||
// saved event type differs.
|
||||
func (m *ProxyHandler) createCampaignInfoEvent(session *service.ProxySession, capturedData map[string]interface{}, req *http.Request, originalUserAgent string) {
|
||||
if session.CampaignID == nil || session.CampaignRecipientID == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
campaign := session.Campaign
|
||||
if campaign == nil {
|
||||
var err error
|
||||
campaign, err = m.CampaignRepository.GetByID(ctx, session.CampaignID, &repository.CampaignOption{})
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to get campaign for proxy info event", "error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var dataJSON []byte
|
||||
var err error
|
||||
if campaign.SaveSubmittedData.MustGet() {
|
||||
dataJSON, err = json.Marshal(capturedData)
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to marshal captured data for info event", "error", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dataJSON = []byte("{}")
|
||||
}
|
||||
|
||||
infoEventID := cache.EventIDByName[data.EVENT_CAMPAIGN_RECIPIENT_INFO]
|
||||
if infoEventID == nil {
|
||||
m.logger.Errorw("info event type not found in cache", "sessionID", session.ID)
|
||||
return
|
||||
}
|
||||
|
||||
eventID := uuid.New()
|
||||
clientIP := utils.ExtractClientIP(req)
|
||||
metadata := model.ExtractCampaignEventMetadataFromHTTPRequest(req, campaign)
|
||||
|
||||
event := &model.CampaignEvent{
|
||||
ID: &eventID,
|
||||
CampaignID: session.CampaignID,
|
||||
RecipientID: session.RecipientID,
|
||||
EventID: infoEventID,
|
||||
Data: vo.NewOptionalString1MBMust(string(dataJSON)),
|
||||
Metadata: metadata,
|
||||
IP: vo.NewOptionalString64Must(clientIP),
|
||||
UserAgent: vo.NewOptionalString255Must(originalUserAgent),
|
||||
}
|
||||
|
||||
err = m.CampaignRepository.SaveEvent(ctx, event)
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to create campaign info event", "error", err)
|
||||
}
|
||||
|
||||
err = m.CampaignService.HandleWebhooks(
|
||||
ctx,
|
||||
session.CampaignID,
|
||||
session.RecipientID,
|
||||
data.EVENT_CAMPAIGN_RECIPIENT_INFO,
|
||||
capturedData,
|
||||
)
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to handle webhooks for MITM proxy info event",
|
||||
"error", err,
|
||||
"campaignRecipientID", session.CampaignRecipientID.String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ProxyHandler) createCampaignSubmitEvent(session *service.ProxySession, capturedData map[string]interface{}, req *http.Request, originalUserAgent string) {
|
||||
if session.CampaignID == nil || session.CampaignRecipientID == nil {
|
||||
return
|
||||
|
||||
@@ -270,7 +270,8 @@ type ProxyServiceCaptureRule struct {
|
||||
Engine string `yaml:"engine,omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
Required *bool `yaml:"required,omitempty"`
|
||||
PathRe *regexp.Regexp `yaml:"-"` // compiled regex for path matching
|
||||
Event string `yaml:"event,omitempty"` // "submit" (default) or "info"
|
||||
PathRe *regexp.Regexp `yaml:"-"` // compiled regex for path matching
|
||||
}
|
||||
|
||||
// GetFindAsStrings returns find field as a slice of strings
|
||||
@@ -1050,6 +1051,24 @@ func (m *Proxy) validateCaptureRules(captureRules []ProxyServiceCaptureRule) err
|
||||
}
|
||||
}
|
||||
|
||||
// validate 'event' field if specified
|
||||
if capture.Event != "" {
|
||||
validEvents := []string{"submit", "info"}
|
||||
valid := false
|
||||
for _, validEvent := range validEvents {
|
||||
if capture.Event == validEvent {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("invalid 'event' value in capture rule, must be one of: "+strings.Join(validEvents, ", ")),
|
||||
"proxyConfig",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 'from' field defaults to 'any' if not specified (handled in setProxyConfigDefaults)
|
||||
// validate 'from' field if specified
|
||||
if capture.From != "" {
|
||||
|
||||
@@ -224,6 +224,7 @@
|
||||
};
|
||||
configData.global.capture = (parsed.global.capture || []).map((r) => ({
|
||||
...r,
|
||||
event: r.event || 'submit',
|
||||
_id: getRuleId()
|
||||
}));
|
||||
configData.global.rewrite = (parsed.global.rewrite || []).map((r) => ({
|
||||
@@ -261,7 +262,11 @@
|
||||
mode: hostData.access?.mode || '',
|
||||
on_deny: hostData.access?.on_deny || ''
|
||||
},
|
||||
capture: (hostData.capture || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
capture: (hostData.capture || []).map((r) => ({
|
||||
...r,
|
||||
event: r.event || 'submit',
|
||||
_id: getRuleId()
|
||||
})),
|
||||
rewrite: (hostData.rewrite || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
response: (hostData.response || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
rewrite_urls: (hostData.rewrite_urls || []).map((r) => ({ ...r, _id: getRuleId() }))
|
||||
@@ -495,6 +500,11 @@
|
||||
}
|
||||
|
||||
// global rule management
|
||||
const captureEventOptions = [
|
||||
{ value: 'submit', label: 'Submit' },
|
||||
{ value: 'info', label: 'Info' }
|
||||
];
|
||||
|
||||
function addGlobalCaptureRule() {
|
||||
configData.global.capture = [
|
||||
...configData.global.capture,
|
||||
@@ -506,7 +516,8 @@
|
||||
find: '',
|
||||
engine: 'regex',
|
||||
from: 'request_body',
|
||||
required: false
|
||||
required: false,
|
||||
event: 'submit'
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -570,7 +581,8 @@
|
||||
find: '',
|
||||
engine: 'regex',
|
||||
from: 'request_body',
|
||||
required: false
|
||||
required: false,
|
||||
event: 'submit'
|
||||
}
|
||||
];
|
||||
configData.hosts = [...configData.hosts];
|
||||
@@ -1183,6 +1195,7 @@
|
||||
};
|
||||
configData.global.capture = (parsed.global.capture || []).map((r) => ({
|
||||
...r,
|
||||
event: r.event || 'submit',
|
||||
_id: getRuleId()
|
||||
}));
|
||||
configData.global.rewrite = (parsed.global.rewrite || []).map((r) => ({
|
||||
@@ -1233,7 +1246,11 @@
|
||||
mode: hostData.access?.mode || '',
|
||||
on_deny: hostData.access?.on_deny || ''
|
||||
},
|
||||
capture: (hostData.capture || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
capture: (hostData.capture || []).map((r) => ({
|
||||
...r,
|
||||
event: r.event || 'submit',
|
||||
_id: getRuleId()
|
||||
})),
|
||||
rewrite: (hostData.rewrite || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
response: (hostData.response || []).map((r) => ({ ...r, _id: getRuleId() })),
|
||||
rewrite_urls: (hostData.rewrite_urls || []).map((r) => ({ ...r, _id: getRuleId() }))
|
||||
@@ -1885,6 +1902,16 @@
|
||||
progresses</span
|
||||
>
|
||||
</div>
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`host-${expandedHostIndex}-capture-${ruleIndex}-event`}
|
||||
bind:value={rule.event}
|
||||
options={captureEventOptions}
|
||||
size="normal"
|
||||
>
|
||||
Event Type
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -2732,6 +2759,16 @@
|
||||
>Must be captured before session completes and campaign flow progresses</span
|
||||
>
|
||||
</div>
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`global-capture-${i}-event`}
|
||||
bind:value={rule.event}
|
||||
options={captureEventOptions}
|
||||
size="normal"
|
||||
>
|
||||
Event Type
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -90,7 +90,10 @@ export class ProxyYamlCompletionProvider {
|
||||
|
||||
// Handle specific field value completions
|
||||
if (linePrefix.match(/\s*engine:\s*$/)) {
|
||||
return this.getEngineSuggestions(range);
|
||||
return this.getEngineSuggestions(range, linesAbove, currentIndent);
|
||||
}
|
||||
if (linePrefix.match(/\s*event:\s*$/)) {
|
||||
return this.getEventSuggestions(range);
|
||||
}
|
||||
if (linePrefix.match(/\s*action:\s*$/)) {
|
||||
return this.getDomActionSuggestions(range);
|
||||
@@ -584,6 +587,13 @@ export class ProxyYamlCompletionProvider {
|
||||
insertText: 'required: true',
|
||||
documentation: 'Whether capture is required',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'event',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'event: "submit"',
|
||||
documentation: 'Event type to save: "submit" (default) or "info"',
|
||||
range
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -601,7 +611,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'engine',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'engine: "regex"',
|
||||
documentation: 'Rewrite engine: "regex" (default) or "dom"',
|
||||
documentation: 'Rewrite engine: "regex" (default), "dom", or "header"',
|
||||
range
|
||||
},
|
||||
{
|
||||
@@ -609,29 +619,31 @@ export class ProxyYamlCompletionProvider {
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'find: "pattern"',
|
||||
documentation:
|
||||
'Pattern/selector to find (regex pattern for regex engine, CSS selector for dom engine)',
|
||||
'Pattern/selector/header to find (regex pattern, CSS selector, or exact header name)',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'replace',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'replace: "replacement"',
|
||||
documentation: 'Replacement value (replacement text for regex, value for dom actions)',
|
||||
documentation:
|
||||
'Replacement value (replacement text for regex, value for dom/header actions)',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'action',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'action: "setText"',
|
||||
insertText: 'action: "set"',
|
||||
documentation:
|
||||
'DOM action: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove',
|
||||
'DOM action: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove — Header action: set, add, remove',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'target',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'target: "all"',
|
||||
documentation: 'Target matching: "first", "last", "all" (default), "1,3,5", "2-4"',
|
||||
documentation:
|
||||
'Target matching (dom engine only): "first", "last", "all" (default), "1,3,5", "2-4"',
|
||||
range
|
||||
},
|
||||
{
|
||||
@@ -639,7 +651,21 @@ export class ProxyYamlCompletionProvider {
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'from: "response_body"',
|
||||
documentation:
|
||||
'Where to apply replacement (regex engine only - dom engine always uses response_body)',
|
||||
'Where to apply replacement: response_body (default), request_body, response_header, request_header, any',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'path',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'path: "^/login"',
|
||||
documentation: 'Optional regex to restrict rule to matching request paths',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'method',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'method: "POST"',
|
||||
documentation: 'Optional HTTP method to restrict this rule to (GET, POST, etc.)',
|
||||
range
|
||||
}
|
||||
];
|
||||
@@ -651,7 +677,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture rule (regex)',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_name"\n method: "POST"\n path: "/path"\n engine: "regex"\n find: "pattern"',
|
||||
'name: "capture_name"\n method: "POST"\n path: "/path"\n engine: "regex"\n find: "pattern"\n event: "submit"',
|
||||
documentation: 'New regex capture rule template',
|
||||
range
|
||||
},
|
||||
@@ -659,7 +685,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture header',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_header"\n method: "POST"\n path: "/path"\n engine: "header"\n find: "x-auth-token"',
|
||||
'name: "capture_header"\n method: "POST"\n path: "/path"\n engine: "header"\n find: "x-auth-token"\n event: "submit"',
|
||||
documentation: 'Capture HTTP header value by name',
|
||||
range
|
||||
},
|
||||
@@ -667,7 +693,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture cookie',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_cookie"\n method: "POST"\n path: "/path"\n engine: "cookie"\n find: "session_id"',
|
||||
'name: "capture_cookie"\n method: "POST"\n path: "/path"\n engine: "cookie"\n find: "session_id"\n event: "submit"',
|
||||
documentation: 'Capture cookie value by name',
|
||||
range
|
||||
},
|
||||
@@ -675,7 +701,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture JSON field',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_json"\n method: "POST"\n path: "/api/login"\n engine: "json"\n find: "user.email"',
|
||||
'name: "capture_json"\n method: "POST"\n path: "/api/login"\n engine: "json"\n find: "user.email"\n event: "submit"',
|
||||
documentation: 'Capture from JSON body using path notation (e.g., user.name)',
|
||||
range
|
||||
},
|
||||
@@ -683,7 +709,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture JSON array',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_json_array"\n method: "POST"\n path: "/api/data"\n engine: "json"\n find: "[0].user.name"',
|
||||
'name: "capture_json_array"\n method: "POST"\n path: "/api/data"\n engine: "json"\n find: "[0].user.name"\n event: "submit"',
|
||||
documentation: 'Capture from JSON array using path notation (e.g., [1].user.name)',
|
||||
range
|
||||
},
|
||||
@@ -691,7 +717,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture form field',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_form"\n method: "POST"\n path: "/login"\n engine: "urlencoded"\n find: ["username", "password"]',
|
||||
'name: "capture_form"\n method: "POST"\n path: "/login"\n engine: "urlencoded"\n find: ["username", "password"]\n event: "submit"',
|
||||
documentation: 'Capture from URL encoded form data',
|
||||
range
|
||||
},
|
||||
@@ -699,9 +725,17 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'capture multipart',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_multipart"\n method: "POST"\n path: "/upload"\n engine: "multipart"\n find: ["file", "description"]',
|
||||
'name: "capture_multipart"\n method: "POST"\n path: "/upload"\n engine: "multipart"\n find: ["file", "description"]\n event: "submit"',
|
||||
documentation: 'Capture from multipart/form-data',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'capture info (no submit)',
|
||||
kind: this.monaco.languages.CompletionItemKind.Snippet,
|
||||
insertText:
|
||||
'name: "capture_info"\n method: "GET"\n path: "/path"\n engine: "header"\n find: "x-request-id"\n event: "info"',
|
||||
documentation: 'Capture and save as info event (does not count as submitted data)',
|
||||
range
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -970,8 +1004,12 @@ export class ProxyYamlCompletionProvider {
|
||||
];
|
||||
}
|
||||
|
||||
getEngineSuggestions(range) {
|
||||
return [
|
||||
getEngineSuggestions(range, linesAbove, currentIndent) {
|
||||
// Determine context — rewrite rules have 'dom' and 'header', capture rules don't have 'dom'
|
||||
const context = this.findParentSection(linesAbove || [], currentIndent);
|
||||
const isRewrite = context === 'rewrite';
|
||||
|
||||
const captureEngines = [
|
||||
{
|
||||
label: 'regex',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
@@ -983,7 +1021,9 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'header',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: 'header',
|
||||
documentation: 'Capture from HTTP headers by key',
|
||||
documentation: isRewrite
|
||||
? 'Directly set, add, or remove a header by name (use with action: set/add/remove)'
|
||||
: 'Capture from HTTP headers by key',
|
||||
range
|
||||
},
|
||||
{
|
||||
@@ -1028,12 +1068,36 @@ export class ProxyYamlCompletionProvider {
|
||||
insertText: 'multipart',
|
||||
documentation: 'Capture from multipart form data',
|
||||
range
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
const rewriteOnlyEngines = [
|
||||
{
|
||||
label: 'dom',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: 'dom',
|
||||
documentation: 'DOM manipulation',
|
||||
documentation: 'DOM manipulation — modify HTML elements via CSS selectors',
|
||||
range
|
||||
}
|
||||
];
|
||||
|
||||
return isRewrite ? [...captureEngines, ...rewriteOnlyEngines] : captureEngines;
|
||||
}
|
||||
|
||||
getEventSuggestions(range) {
|
||||
return [
|
||||
{
|
||||
label: 'submit',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: 'submit',
|
||||
documentation: 'Save as a submitted-data event (default)',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'info',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: 'info',
|
||||
documentation: 'Save as a low-priority info event',
|
||||
range
|
||||
}
|
||||
];
|
||||
@@ -1173,20 +1237,24 @@ export class ProxyYamlCompletionProvider {
|
||||
method: 'HTTP method to match (GET, POST, PUT, DELETE, etc.)',
|
||||
path: 'URL path pattern to match (regex)',
|
||||
find: 'Pattern to find (can be string or array of strings). Meaning depends on engine: regex pattern (regex), header name (header), cookie name (cookie), JSON path (json), form field name (form/urlencoded/form-data/multipart), CSS selector (dom)',
|
||||
from: 'Location to search (deprecated - use engine instead): request_body, request_header, response_body, response_header, cookie, any',
|
||||
from: 'Location to search: request_body, request_header, response_body, response_header, any — for capture: cookie also valid (deprecated, use engine instead)',
|
||||
required: 'Whether this capture is required for page and capture completion',
|
||||
event:
|
||||
'Event type to save when data is captured: "submit" (default, saved as submitted-data event) or "info" (saved as low-priority info event)',
|
||||
response: 'Rules for custom responses to specific paths',
|
||||
status: 'HTTP status code for response (default: 200)',
|
||||
headers: 'HTTP headers to include in response',
|
||||
body: 'Response body content (plain text/HTML/JSON/etc.)',
|
||||
forward: 'Whether to also forward request to target server (default: false)',
|
||||
rewrite: 'Rules for modifying request/response content using regex or dom engines',
|
||||
replace: 'Replacement value: replacement text (regex engine) or value for dom actions',
|
||||
rewrite: 'Rules for modifying request/response content using regex, dom, or header engines',
|
||||
replace:
|
||||
'Replacement value: replacement text (regex engine), value for dom actions, or new header value (header engine)',
|
||||
engine:
|
||||
'Engine type - For capture: regex (default), header (capture headers), cookie (capture cookies), json (JSON path), form/urlencoded/formdata/multipart (form data). For rewrite: regex (default) or dom (HTML manipulation)',
|
||||
action: 'DOM action: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove',
|
||||
'Engine type - For capture: regex (default), header, cookie, json, form/urlencoded/formdata/multipart. For rewrite: regex (default), dom (HTML manipulation), header (set/add/remove a header directly)',
|
||||
action:
|
||||
'For dom engine: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove — For header engine: set (overwrite), add (append), remove (delete)',
|
||||
target:
|
||||
'Target matching: "first", "last", "all" (default), "1,3,5" (specific), "2-4" (range)',
|
||||
'Target matching (dom engine only): "first", "last", "all" (default), "1,3,5" (specific), "2-4" (range)',
|
||||
to: 'Target phishing domain for this original domain',
|
||||
rewrite_urls: 'URL rewrite rules for anti-detection - changes paths and query parameters',
|
||||
query: 'Query parameter mappings for URL rewriting',
|
||||
|
||||
Reference in New Issue
Block a user