mdefender-pro 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auto.js +62 -0
- package/bin/mdefender.js +85 -34
- package/block-page.html +56 -210
- package/client.mjs +345 -237
- package/index.js +30 -10
- package/package.json +3 -2
- package/vite.js +45 -2
package/client.mjs
CHANGED
|
@@ -1,22 +1,55 @@
|
|
|
1
|
-
// client.mjs
|
|
2
|
-
|
|
3
|
-
* Initialize MDefender Pro WAF client-side protection for SPAs.
|
|
4
|
-
* @param {Object} options - Client configuration
|
|
5
|
-
* @param {string} options.backendUrl - Base URL of your backend API server (e.g. 'http://localhost:5005')
|
|
6
|
-
*/
|
|
7
|
-
export function initWaf(options = {}) {
|
|
8
|
-
if (typeof window === 'undefined') return;
|
|
1
|
+
// client.mjs - MDefender Pro Client-Side & SPA Protection Engine
|
|
2
|
+
// High-performance 0ms synchronous execution with zero external network delays
|
|
9
3
|
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const ATTACK_PATTERNS = [
|
|
5
|
+
{ type: 'Cross-Site Scripting (XSS)', regex: /<\s*(?:script|iframe|object|embed|svg|img|math)\b/i },
|
|
6
|
+
{ type: 'Cross-Site Scripting (XSS)', regex: /\bon(?:error|load|click|mouseover|focus|submit)\s*=/i },
|
|
7
|
+
{ type: 'Cross-Site Scripting (XSS)', regex: /\b(?:javascript|data\s*:\s*text\/html)\s*:/i },
|
|
8
|
+
{ type: 'Cross-Site Scripting (XSS)', regex: /\b(?:alert|eval|confirm|prompt|document\.cookie)\s*\(/i },
|
|
9
|
+
{ type: 'SQL Injection', regex: /\bUNION\s+(?:ALL\s+)?SELECT\b/i },
|
|
10
|
+
{ type: 'SQL Injection', regex: /(?:'|\"|\b)\s*(?:OR|AND)\s+['\"`]?([a-zA-Z0-9_-]+)['\"`]?\s*=\s*['\"`]?\1/i },
|
|
11
|
+
{ type: 'SQL Injection', regex: /(?:--|#|\/\*).*?(?:DROP|ALTER|INSERT|DELETE|UPDATE|EXEC)/i },
|
|
12
|
+
{ type: 'Local File Inclusion (LFI)', regex: /(?:\.\.\/|\.\.\\|etc\/passwd|etc\/shadow|win\.ini|boot\.ini)/i },
|
|
13
|
+
{ type: 'Server-Side Request Forgery (SSRF)', regex: /(?:169\.254\.169\.254|metadata\.google\.internal|(?:gopher|dict|file):\/\/|=(?:https?:\/\/)?(?:127\.0\.0\.1|169\.254|localhost|0\.0\.0\.0))/i },
|
|
14
|
+
{ type: 'Remote Command Execution (RCE)', regex: /(?:;|\||\|\||&&|`|\$\()\s*(?:cat|ls|id|whoami|powershell|cmd|sh|bash|wget|curl)\b/i },
|
|
15
|
+
{ type: 'AI Prompt Injection', regex: /(?:ignore\s+all\s+(?:previous|prior)\s+instructions|system\s+override|DAN\s+mode)/i }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
function normalizeInput(str) {
|
|
19
|
+
if (!str) return '';
|
|
20
|
+
let curr = str;
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
try {
|
|
23
|
+
const decoded = decodeURIComponent(curr);
|
|
24
|
+
if (decoded === curr) break;
|
|
25
|
+
curr = decoded;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return curr.replace(/\/\*.*?\*\//g, ' ');
|
|
31
|
+
}
|
|
12
32
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
function generateRefId() {
|
|
34
|
+
return 'MDF-' + Math.random().toString(16).substring(2, 10).toUpperCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function renderOfficialBlockPage(attackType, refId, domain) {
|
|
38
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
39
|
+
if (window.__MDEFENDER_BLOCKED__) return;
|
|
40
|
+
window.__MDEFENDER_BLOCKED__ = true;
|
|
41
|
+
|
|
42
|
+
const referenceId = refId || generateRefId();
|
|
43
|
+
const host = window.location.hostname || domain || 'localhost';
|
|
17
44
|
const nowUtc = new Date().toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
18
45
|
const unixRef = Math.floor(Date.now() / 1000);
|
|
19
|
-
const
|
|
46
|
+
const classification = attackType || 'Cross-Site Scripting (XSS)';
|
|
47
|
+
|
|
48
|
+
let hash = 0;
|
|
49
|
+
for (let i = 0; i < classification.length; i++) {
|
|
50
|
+
hash = classification.charCodeAt(i) + ((hash << 5) - hash);
|
|
51
|
+
}
|
|
52
|
+
const ruleId = Math.abs(hash % 10000) + 90000;
|
|
20
53
|
|
|
21
54
|
const html = `<!DOCTYPE html>
|
|
22
55
|
<html lang="en">
|
|
@@ -34,179 +67,198 @@ export function initWaf(options = {}) {
|
|
|
34
67
|
--text-secondary: #475569;
|
|
35
68
|
--text-muted: #94a3b8;
|
|
36
69
|
--border-color: #cbd5e1;
|
|
37
|
-
--font-main: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
38
|
-
--font-mono:
|
|
70
|
+
--font-main: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
71
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
39
72
|
}
|
|
40
73
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
41
74
|
body {
|
|
42
75
|
font-family: var(--font-main);
|
|
43
76
|
background-color: var(--bg-color);
|
|
77
|
+
background-image:
|
|
78
|
+
radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.03) 0%, transparent 40%),
|
|
79
|
+
radial-gradient(circle at 90% 80%, rgba(59, 130, 246, 0.03) 0%, transparent 40%);
|
|
44
80
|
color: var(--text-primary);
|
|
45
81
|
min-height: 100vh;
|
|
46
82
|
display: flex;
|
|
47
83
|
align-items: center;
|
|
48
84
|
justify-content: center;
|
|
49
|
-
padding:
|
|
50
|
-
-webkit-font-smoothing: antialiased;
|
|
85
|
+
padding: 20px 14px;
|
|
51
86
|
}
|
|
52
87
|
.container {
|
|
53
88
|
width: 100%;
|
|
54
89
|
max-width: 660px;
|
|
55
90
|
background: var(--card-bg);
|
|
56
|
-
border: 1px solid
|
|
91
|
+
border: 1px solid rgba(226, 232, 240, 0.95);
|
|
57
92
|
border-radius: 12px;
|
|
58
|
-
box-shadow: 0
|
|
93
|
+
box-shadow: 0 8px 25px -4px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
|
|
59
94
|
padding: 24px 28px;
|
|
95
|
+
text-align: center;
|
|
60
96
|
}
|
|
61
|
-
.
|
|
97
|
+
.logo-wrap {
|
|
98
|
+
margin-bottom: 10px;
|
|
62
99
|
display: flex;
|
|
100
|
+
flex-direction: column;
|
|
63
101
|
align-items: center;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
justify-content: center;
|
|
103
|
+
}
|
|
104
|
+
.logo-svg {
|
|
105
|
+
width: 48px;
|
|
106
|
+
height: 48px;
|
|
107
|
+
}
|
|
108
|
+
.logo-text {
|
|
109
|
+
font-size: 13.5px;
|
|
110
|
+
font-weight: 800;
|
|
111
|
+
color: #1e293b;
|
|
112
|
+
letter-spacing: 0.5px;
|
|
113
|
+
margin-top: 4px;
|
|
69
114
|
text-transform: uppercase;
|
|
70
|
-
margin-bottom: 12px;
|
|
71
115
|
}
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
.status-badge {
|
|
75
|
-
display: inline-flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
gap: 7px;
|
|
78
|
-
background: #fff1f2;
|
|
79
|
-
border: 1px solid #fecdd3;
|
|
80
|
-
color: #e11d48;
|
|
81
|
-
padding: 4px 10px;
|
|
82
|
-
border-radius: 20px;
|
|
83
|
-
font-size: 11px;
|
|
116
|
+
.fw-badge {
|
|
117
|
+
font-size: 9.5px;
|
|
84
118
|
font-weight: 700;
|
|
85
|
-
|
|
119
|
+
color: #64748b;
|
|
120
|
+
text-transform: uppercase;
|
|
121
|
+
letter-spacing: 1.5px;
|
|
122
|
+
margin-bottom: 2px;
|
|
86
123
|
}
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
.fw-owner {
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
font-weight: 800;
|
|
127
|
+
color: #0f172a;
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
letter-spacing: 1px;
|
|
130
|
+
margin-bottom: 12px;
|
|
93
131
|
}
|
|
94
|
-
|
|
132
|
+
.main-title {
|
|
95
133
|
font-size: 19px;
|
|
96
134
|
font-weight: 800;
|
|
97
135
|
color: #0f172a;
|
|
98
|
-
letter-spacing: -0.
|
|
99
|
-
margin-bottom:
|
|
100
|
-
line-height: 1.25;
|
|
136
|
+
letter-spacing: -0.3px;
|
|
137
|
+
margin-bottom: 3px;
|
|
101
138
|
}
|
|
102
|
-
.
|
|
103
|
-
font-size:
|
|
104
|
-
|
|
105
|
-
|
|
139
|
+
.sub-title {
|
|
140
|
+
font-size: 14px;
|
|
141
|
+
font-weight: 700;
|
|
142
|
+
color: #0f172a;
|
|
143
|
+
margin-bottom: 3px;
|
|
106
144
|
}
|
|
107
|
-
.
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
.desc-text {
|
|
146
|
+
font-size: 11.5px;
|
|
147
|
+
color: var(--text-secondary);
|
|
148
|
+
margin-bottom: 12px;
|
|
110
149
|
}
|
|
111
|
-
.table-
|
|
150
|
+
.table-container {
|
|
112
151
|
border: 1px solid var(--border-color);
|
|
113
|
-
border-radius:
|
|
152
|
+
border-radius: 7px;
|
|
114
153
|
overflow: hidden;
|
|
115
|
-
margin-bottom: 18px;
|
|
116
154
|
background: #ffffff;
|
|
155
|
+
margin-bottom: 12px;
|
|
156
|
+
text-align: left;
|
|
117
157
|
}
|
|
118
|
-
table {
|
|
158
|
+
.details-table {
|
|
119
159
|
width: 100%;
|
|
120
160
|
border-collapse: collapse;
|
|
121
|
-
text-align: left;
|
|
122
|
-
font-size: 11.5px;
|
|
123
161
|
}
|
|
124
|
-
tr {
|
|
125
|
-
border-bottom: 1px solid
|
|
162
|
+
.details-table tr {
|
|
163
|
+
border-bottom: 1px solid var(--border-color);
|
|
126
164
|
}
|
|
127
|
-
tr:last-child {
|
|
165
|
+
.details-table tr:last-child {
|
|
128
166
|
border-bottom: none;
|
|
129
167
|
}
|
|
130
|
-
th {
|
|
131
|
-
|
|
132
|
-
background: #f8fafc;
|
|
133
|
-
padding: 8.5px 14px;
|
|
134
|
-
font-weight: 600;
|
|
168
|
+
.details-table th {
|
|
169
|
+
background-color: #f8fafc;
|
|
135
170
|
color: #475569;
|
|
136
|
-
|
|
171
|
+
font-weight: 700;
|
|
172
|
+
font-size: 11px;
|
|
137
173
|
text-transform: uppercase;
|
|
138
|
-
|
|
139
|
-
|
|
174
|
+
letter-spacing: 0.4px;
|
|
175
|
+
width: 190px;
|
|
176
|
+
padding: 7px 12px;
|
|
177
|
+
border-right: 1px solid var(--border-color);
|
|
178
|
+
vertical-align: middle;
|
|
140
179
|
}
|
|
141
|
-
td {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
font-family: var(--font-mono);
|
|
180
|
+
.details-table td {
|
|
181
|
+
color: #334155;
|
|
182
|
+
font-weight: 500;
|
|
145
183
|
font-size: 11.5px;
|
|
146
|
-
|
|
184
|
+
padding: 7px 12px;
|
|
185
|
+
vertical-align: middle;
|
|
147
186
|
}
|
|
148
|
-
.threat-
|
|
149
|
-
color: #
|
|
150
|
-
|
|
187
|
+
.threat-row td {
|
|
188
|
+
background-color: #fff5f5 !important;
|
|
189
|
+
color: #991b1b !important;
|
|
190
|
+
}
|
|
191
|
+
.threat-cell-content {
|
|
151
192
|
display: flex;
|
|
152
193
|
align-items: center;
|
|
153
194
|
justify-content: space-between;
|
|
195
|
+
width: 100%;
|
|
196
|
+
gap: 8px;
|
|
154
197
|
}
|
|
155
|
-
.
|
|
156
|
-
font-size: 9.5px;
|
|
198
|
+
.threat-text {
|
|
157
199
|
font-weight: 700;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
200
|
+
color: #dc2626;
|
|
201
|
+
word-break: break-word;
|
|
202
|
+
}
|
|
203
|
+
.info-tag {
|
|
204
|
+
background: #f1f5f9;
|
|
205
|
+
color: #475569;
|
|
206
|
+
border: 1px solid #cbd5e1;
|
|
207
|
+
font-size: 10px;
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
padding: 2px 7px;
|
|
161
210
|
border-radius: 4px;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
text-transform: uppercase;
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
white-space: nowrap;
|
|
165
213
|
}
|
|
166
214
|
.ip-wrap {
|
|
167
215
|
display: flex;
|
|
168
216
|
align-items: center;
|
|
169
|
-
gap:
|
|
217
|
+
gap: 8px;
|
|
170
218
|
}
|
|
171
|
-
.flag-
|
|
219
|
+
.flag-svg {
|
|
172
220
|
border-radius: 2px;
|
|
173
221
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
|
174
|
-
vertical-align: middle;
|
|
175
222
|
}
|
|
176
223
|
.why-header {
|
|
177
224
|
font-size: 12.5px;
|
|
178
225
|
font-weight: 700;
|
|
179
|
-
color:
|
|
226
|
+
color: #0f172a;
|
|
227
|
+
text-align: left;
|
|
180
228
|
margin-bottom: 4px;
|
|
181
229
|
}
|
|
182
230
|
.why-desc {
|
|
183
|
-
font-size:
|
|
231
|
+
font-size: 11px;
|
|
184
232
|
color: var(--text-secondary);
|
|
185
233
|
line-height: 1.45;
|
|
186
|
-
|
|
234
|
+
text-align: left;
|
|
235
|
+
margin-bottom: 12px;
|
|
187
236
|
}
|
|
188
237
|
.ref-box {
|
|
189
|
-
background: #
|
|
238
|
+
background: #f1f5f9;
|
|
190
239
|
border: 1px dashed var(--border-color);
|
|
191
|
-
|
|
192
|
-
|
|
240
|
+
border-radius: 5px;
|
|
241
|
+
padding: 7px;
|
|
193
242
|
font-family: var(--font-mono);
|
|
194
|
-
font-size:
|
|
243
|
+
font-size: 11px;
|
|
195
244
|
font-weight: 700;
|
|
196
|
-
color:
|
|
197
|
-
|
|
198
|
-
margin-bottom:
|
|
245
|
+
color: #334155;
|
|
246
|
+
letter-spacing: 0.6px;
|
|
247
|
+
margin-bottom: 14px;
|
|
199
248
|
cursor: pointer;
|
|
200
|
-
transition: all 0.
|
|
249
|
+
transition: all 0.2s;
|
|
201
250
|
}
|
|
202
251
|
.ref-box:hover {
|
|
203
|
-
background: #f1f5f9;
|
|
204
252
|
border-color: var(--brand-1);
|
|
253
|
+
color: var(--brand-1);
|
|
254
|
+
background: #eef2ff;
|
|
205
255
|
}
|
|
206
256
|
.actions {
|
|
207
257
|
display: flex;
|
|
208
258
|
align-items: center;
|
|
209
|
-
|
|
259
|
+
justify-content: center;
|
|
260
|
+
gap: 10px;
|
|
261
|
+
margin-bottom: 12px;
|
|
210
262
|
flex-wrap: wrap;
|
|
211
263
|
}
|
|
212
264
|
.btn {
|
|
@@ -214,68 +266,87 @@ export function initWaf(options = {}) {
|
|
|
214
266
|
align-items: center;
|
|
215
267
|
justify-content: center;
|
|
216
268
|
gap: 6px;
|
|
217
|
-
|
|
218
|
-
font-size:
|
|
269
|
+
font-family: var(--font-main);
|
|
270
|
+
font-size: 11px;
|
|
219
271
|
font-weight: 600;
|
|
220
|
-
|
|
272
|
+
padding: 7px 14px;
|
|
273
|
+
border-radius: 5px;
|
|
221
274
|
text-decoration: none;
|
|
222
|
-
cursor: pointer;
|
|
223
275
|
transition: all 0.15s ease;
|
|
224
|
-
|
|
276
|
+
cursor: pointer;
|
|
225
277
|
}
|
|
226
278
|
.btn-primary {
|
|
227
|
-
background:
|
|
279
|
+
background-color: #0f172a;
|
|
228
280
|
color: #ffffff;
|
|
229
|
-
border: 1px solid
|
|
230
|
-
box-shadow: 0 1px 2px rgba(79, 70, 229, 0.2);
|
|
281
|
+
border: 1px solid #0f172a;
|
|
231
282
|
}
|
|
232
283
|
.btn-primary:hover {
|
|
233
|
-
background: #
|
|
284
|
+
background-color: #1e293b;
|
|
285
|
+
border-color: #1e293b;
|
|
234
286
|
}
|
|
235
287
|
.btn-secondary {
|
|
236
|
-
background: #ffffff;
|
|
237
|
-
color:
|
|
288
|
+
background-color: #ffffff;
|
|
289
|
+
color: #334155;
|
|
238
290
|
border: 1px solid var(--border-color);
|
|
239
291
|
}
|
|
240
292
|
.btn-secondary:hover {
|
|
241
|
-
background: #f8fafc;
|
|
293
|
+
background-color: #f8fafc;
|
|
242
294
|
border-color: #94a3b8;
|
|
295
|
+
color: #0f172a;
|
|
243
296
|
}
|
|
244
297
|
.footer {
|
|
245
|
-
|
|
246
|
-
padding-top: 12px;
|
|
247
|
-
border-top: 1px solid #f1f5f9;
|
|
248
|
-
font-size: 10.5px;
|
|
298
|
+
font-size: 10px;
|
|
249
299
|
color: var(--text-muted);
|
|
250
|
-
|
|
300
|
+
border-top: 1px solid #f1f5f9;
|
|
301
|
+
padding-top: 8px;
|
|
251
302
|
}
|
|
252
303
|
</style>
|
|
253
304
|
</head>
|
|
254
305
|
<body>
|
|
255
306
|
<div class="container">
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
<!-- Shield Logo Header -->
|
|
308
|
+
<div class="logo-wrap">
|
|
309
|
+
<svg class="logo-svg" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
|
310
|
+
<defs>
|
|
311
|
+
<linearGradient id="shieldBg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
312
|
+
<stop offset="0%" stop-color="#f8fafc" />
|
|
313
|
+
<stop offset="30%" stop-color="#cbd5e1" />
|
|
314
|
+
<stop offset="70%" stop-color="#94a3b8" />
|
|
315
|
+
<stop offset="100%" stop-color="#475569" />
|
|
316
|
+
</linearGradient>
|
|
317
|
+
<linearGradient id="shieldBorder" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
318
|
+
<stop offset="0%" stop-color="#94a3b8" />
|
|
319
|
+
<stop offset="50%" stop-color="#f1f5f9" />
|
|
320
|
+
<stop offset="100%" stop-color="#475569" />
|
|
321
|
+
</linearGradient>
|
|
322
|
+
<linearGradient id="dragonColor" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
323
|
+
<stop offset="0%" stop-color="#1e293b" />
|
|
324
|
+
<stop offset="100%" stop-color="#0f172a" />
|
|
325
|
+
</linearGradient>
|
|
326
|
+
</defs>
|
|
327
|
+
<path d="M60 10 C85 22 98 25 98 48 C98 75 75 98 60 108 C45 98 22 75 22 48 C22 25 35 22 60 10 Z" fill="url(#shieldBg)" stroke="url(#shieldBorder)" stroke-width="3" />
|
|
328
|
+
<path d="M60 18 C80 28 90 30 90 48 C90 70 70 90 60 98 C50 90 30 70 30 48 C30 30 40 28 60 18 Z" fill="none" stroke="#ffffff" stroke-width="1.5" stroke-opacity="0.8" stroke-dasharray="3 2" />
|
|
329
|
+
<path d="M60 28 C58 35 52 38 48 38 C44 38 43 42 45 44 C47 46 51 44 51 48 C51 52 46 55 42 53 C38 51 36 54 38 58 C40 62 45 60 48 64 C50 66 48 70 44 72 C48 74 54 75 58 72 C58 68 55 65 57 62 C59 59 63 61 65 58 C67 55 66 52 62 50 C64 45 68 46 70 42 C72 38 67 36 65 32 C63 28 61 25 60 28 Z" fill="url(#dragonColor)" />
|
|
259
330
|
</svg>
|
|
260
|
-
|
|
331
|
+
<div class="logo-text">MDefender-Pro AI</div>
|
|
261
332
|
</div>
|
|
262
333
|
|
|
263
|
-
<div class="
|
|
264
|
-
|
|
265
|
-
403 — SECURITY ACTION REQUIRED • Access Denied by Corporate WAF
|
|
266
|
-
</div>
|
|
334
|
+
<div class="fw-badge">A.S.A.P. Security Firewall</div>
|
|
335
|
+
<div class="fw-owner">Mahabub</div>
|
|
267
336
|
|
|
268
|
-
<h1>403 —
|
|
269
|
-
<
|
|
337
|
+
<h1 class="main-title">403 — SECURITY ACTION REQUIRED</h1>
|
|
338
|
+
<h2 class="sub-title">Access Denied by Corporate WAF</h2>
|
|
270
339
|
|
|
271
|
-
<div class="
|
|
272
|
-
|
|
273
|
-
|
|
340
|
+
<div class="desc-text">Access to [${host}] restricted.</div>
|
|
341
|
+
|
|
342
|
+
<div class="table-container">
|
|
343
|
+
<table class="details-table">
|
|
344
|
+
<tr class="threat-row">
|
|
274
345
|
<th>Attack Classification</th>
|
|
275
346
|
<td>
|
|
276
|
-
<div class="threat-
|
|
277
|
-
<span>${
|
|
278
|
-
<span class="info-tag">
|
|
347
|
+
<div class="threat-cell-content">
|
|
348
|
+
<span class="threat-text">${classification}</span>
|
|
349
|
+
<span class="info-tag">Detailed info tag</span>
|
|
279
350
|
</div>
|
|
280
351
|
</td>
|
|
281
352
|
</tr>
|
|
@@ -283,8 +354,11 @@ export function initWaf(options = {}) {
|
|
|
283
354
|
<th>Origin Client IP</th>
|
|
284
355
|
<td>
|
|
285
356
|
<div class="ip-wrap">
|
|
286
|
-
<
|
|
287
|
-
|
|
357
|
+
<svg class="flag-svg" width="20" height="15" viewBox="0 0 20 15" xmlns="http://www.w3.org/2000/svg">
|
|
358
|
+
<rect width="20" height="15" fill="#006a4e"/>
|
|
359
|
+
<circle cx="9" cy="7.5" r="4" fill="#f42a41"/>
|
|
360
|
+
</svg>
|
|
361
|
+
<span><span id="client-ip-display" style="font-family: var(--font-mono); font-weight: 700;">103.151.30.111</span><span style="color: #64748b;"> (GeoIP: BD)</span></span>
|
|
288
362
|
</div>
|
|
289
363
|
</td>
|
|
290
364
|
</tr>
|
|
@@ -294,7 +368,7 @@ export function initWaf(options = {}) {
|
|
|
294
368
|
</tr>
|
|
295
369
|
<tr>
|
|
296
370
|
<th>Violation Reason</th>
|
|
297
|
-
<td>Request blocked by rule ID: <span>${ruleId}</span> (Ref: ${
|
|
371
|
+
<td>Request blocked by rule ID: <span>${ruleId}</span> (Ref: ${classification})</td>
|
|
298
372
|
</tr>
|
|
299
373
|
<tr>
|
|
300
374
|
<th>Protocol Details</th>
|
|
@@ -309,7 +383,7 @@ export function initWaf(options = {}) {
|
|
|
309
383
|
|
|
310
384
|
<h3 class="why-header">Why did this happen?</h3>
|
|
311
385
|
<p class="why-desc">
|
|
312
|
-
To maintain system integrity, suspicious requests are automatically analyzed and filtered. If you believe this is a valid action, please share the Reference ID below with your local IT/Security operations.
|
|
386
|
+
To maintain system integrity, suspicious requests are automatically analyzed and filtered. If you believe this is a valid corporate action, please share the Reference ID below with your local IT/Security operations. Regular users should clear cache or contact support.
|
|
313
387
|
</p>
|
|
314
388
|
|
|
315
389
|
<div class="ref-box" id="ref-id-box" title="Click to copy Reference ID" onclick="copyRef()">
|
|
@@ -317,8 +391,8 @@ export function initWaf(options = {}) {
|
|
|
317
391
|
</div>
|
|
318
392
|
|
|
319
393
|
<div class="actions">
|
|
320
|
-
<a
|
|
321
|
-
|
|
394
|
+
<a href="#" class="btn btn-primary" onclick="alert('Reference ID: ${referenceId}\\nContact your IT security team.'); return false;">
|
|
395
|
+
Contact Security Operations
|
|
322
396
|
</a>
|
|
323
397
|
<a href="/" class="btn btn-secondary">
|
|
324
398
|
Return to Homepage
|
|
@@ -338,109 +412,143 @@ export function initWaf(options = {}) {
|
|
|
338
412
|
function copyRef() {
|
|
339
413
|
navigator.clipboard.writeText(refId).then(() => {
|
|
340
414
|
const refBox = document.getElementById('ref-id-box');
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
415
|
+
if (refBox) {
|
|
416
|
+
const originalHtml = refBox.innerHTML;
|
|
417
|
+
refBox.innerHTML = 'COPIED TO CLIPBOARD! ✅';
|
|
418
|
+
refBox.style.color = '#10b981';
|
|
419
|
+
refBox.style.borderColor = '#10b981';
|
|
420
|
+
refBox.style.background = '#ecfdf5';
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
refBox.innerHTML = originalHtml;
|
|
423
|
+
refBox.style.color = '';
|
|
424
|
+
refBox.style.borderColor = '';
|
|
425
|
+
refBox.style.background = '';
|
|
426
|
+
}, 2000);
|
|
427
|
+
}
|
|
352
428
|
});
|
|
353
429
|
}
|
|
354
430
|
</script>
|
|
355
431
|
</body>
|
|
356
432
|
</html>`;
|
|
357
433
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
document.
|
|
361
|
-
};
|
|
434
|
+
// Instant DOM Replacement
|
|
435
|
+
try { window.stop(); } catch (e) { }
|
|
436
|
+
document.documentElement.innerHTML = html;
|
|
362
437
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
438
|
+
// Async Telemetry Beacon to MDefender Dashboard (recorded without blocking UI)
|
|
439
|
+
try {
|
|
440
|
+
const reportPayload = {
|
|
441
|
+
domain: domain || (typeof window !== 'undefined' ? window.location.hostname : 'localhost') || 'localhost',
|
|
442
|
+
request: {
|
|
443
|
+
method: 'GET',
|
|
444
|
+
url: typeof window !== 'undefined' ? (window.location.pathname + window.location.search) : '/',
|
|
445
|
+
query_string: typeof window !== 'undefined' ? window.location.search : '',
|
|
446
|
+
ip: '127.0.0.1',
|
|
447
|
+
headers: { 'User-Agent': typeof navigator !== 'undefined' ? navigator.userAgent : 'Frontend-WAF' },
|
|
448
|
+
body: '',
|
|
449
|
+
attack_type: classification,
|
|
450
|
+
reference_id: referenceId
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
const endpoint = 'http://localhost:8000/api/v1/waf/analyze';
|
|
454
|
+
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
455
|
+
navigator.sendBeacon(endpoint, new Blob([JSON.stringify(reportPayload)], { type: 'application/json' }));
|
|
456
|
+
} else if (typeof fetch !== 'undefined') {
|
|
457
|
+
fetch(endpoint, {
|
|
458
|
+
method: 'POST',
|
|
459
|
+
headers: { 'Content-Type': 'application/json' },
|
|
460
|
+
body: JSON.stringify(reportPayload)
|
|
461
|
+
}).catch(() => {});
|
|
462
|
+
}
|
|
463
|
+
} catch (e) {}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 0ms Instant URL Inspection on Script Load
|
|
467
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
468
|
+
const rawTarget = normalizeInput(window.location.search + ' ' + window.location.hash);
|
|
469
|
+
if (rawTarget.trim()) {
|
|
470
|
+
for (const pattern of ATTACK_PATTERNS) {
|
|
471
|
+
if (pattern.regex.test(rawTarget)) {
|
|
472
|
+
renderOfficialBlockPage(pattern.type);
|
|
473
|
+
throw new Error(`[MDefender WAF] Attack blocked on load (0ms): ${pattern.type}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
370
476
|
}
|
|
371
|
-
|
|
477
|
+
}
|
|
372
478
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Initialize MDefender Pro WAF client-side protection for SPAs & Frontend Websites.
|
|
481
|
+
* @param {Object} options - Client configuration
|
|
482
|
+
* @param {string} [options.backendUrl] - Base URL of your backend API server (e.g. 'http://localhost:5005' or 'http://localhost:8000')
|
|
483
|
+
* @param {string} [options.apiKey] - Your Website API key
|
|
484
|
+
* @param {string} [options.domain] - Your domain (e.g. 'localhost' or 'mysite.com')
|
|
485
|
+
* @param {boolean} [options.logBlocked] - Whether to log blocked attacks to console
|
|
486
|
+
*/
|
|
487
|
+
export function initWaf(options = {}) {
|
|
488
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
489
|
+
if (window.__MDEFENDER_WAF_INITIALIZED__) return;
|
|
490
|
+
window.__MDEFENDER_WAF_INITIALIZED__ = true;
|
|
379
491
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const origFetch = window.fetch;
|
|
383
|
-
const testUrl = backendUrl + '/api/books/' + window.location.search;
|
|
384
|
-
origFetch(testUrl)
|
|
385
|
-
.then(res => res.status === 403 ? res.text() : null)
|
|
386
|
-
.then(html => { if (html) handleWafBlock(html); })
|
|
387
|
-
.catch(() => {});
|
|
388
|
-
}
|
|
389
|
-
throw new Error("MDefender WAF Blocked Request");
|
|
390
|
-
}
|
|
391
|
-
}
|
|
492
|
+
const backendUrl = (options.backendUrl || 'http://localhost:8000').replace(/\/+$/, '');
|
|
493
|
+
const domain = options.domain || window.location.hostname || 'localhost';
|
|
392
494
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const separator = url.includes('?') ? '&' : '?';
|
|
403
|
-
url = url + separator + searchStr.substring(1);
|
|
404
|
-
}
|
|
405
|
-
} else if (url instanceof Request) {
|
|
406
|
-
if (url.url.startsWith('/') || url.url.includes(backendUrl) || url.url.includes('/api/')) {
|
|
407
|
-
const separator = url.url.includes('?') ? '&' : '?';
|
|
408
|
-
const newUrl = url.url + separator + searchStr.substring(1);
|
|
409
|
-
url = new Request(newUrl, url);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
495
|
+
// 1. Wrap window.fetch for outgoing requests
|
|
496
|
+
const originalFetch = window.fetch;
|
|
497
|
+
window.fetch = async (input, init, ...args) => {
|
|
498
|
+
let url = typeof input === 'string' ? input : (input && input.url ? input.url : '');
|
|
499
|
+
let targetParamStr = '';
|
|
500
|
+
try {
|
|
501
|
+
if (url.includes('?')) targetParamStr = url.substring(url.indexOf('?'));
|
|
502
|
+
else if (url.includes('#')) targetParamStr = url.substring(url.indexOf('#'));
|
|
503
|
+
} catch (e) {}
|
|
413
504
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
505
|
+
if (targetParamStr) {
|
|
506
|
+
const normParams = normalizeInput(targetParamStr);
|
|
507
|
+
for (const pattern of ATTACK_PATTERNS) {
|
|
508
|
+
if (pattern.regex.test(normParams)) {
|
|
509
|
+
renderOfficialBlockPage(pattern.type, null, domain);
|
|
510
|
+
throw new Error(`[MDefender WAF] Outgoing API attack blocked: ${pattern.type}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
424
514
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
515
|
+
try {
|
|
516
|
+
const response = await originalFetch(input, init, ...args);
|
|
517
|
+
if (response.status === 403) {
|
|
518
|
+
const clone = response.clone();
|
|
519
|
+
try {
|
|
520
|
+
const text = await clone.text();
|
|
521
|
+
if (text.includes('403') && (text.includes('MDefender') || text.includes('Access Denied'))) {
|
|
522
|
+
renderOfficialBlockPage('Cross-Site Scripting (XSS)', null, domain);
|
|
523
|
+
}
|
|
524
|
+
} catch (e) {}
|
|
525
|
+
}
|
|
526
|
+
return response;
|
|
527
|
+
} catch (err) {
|
|
528
|
+
throw err;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// 2. Wrap XMLHttpRequest
|
|
533
|
+
const originalOpen = XMLHttpRequest.prototype.open;
|
|
534
|
+
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
|
|
535
|
+
let targetParamStr = '';
|
|
536
|
+
try {
|
|
537
|
+
const urlStr = String(url);
|
|
538
|
+
if (urlStr.includes('?')) targetParamStr = urlStr.substring(urlStr.indexOf('?'));
|
|
539
|
+
} catch (e) {}
|
|
435
540
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
541
|
+
if (targetParamStr) {
|
|
542
|
+
const normParams = normalizeInput(targetParamStr);
|
|
543
|
+
for (const pattern of ATTACK_PATTERNS) {
|
|
544
|
+
if (pattern.regex.test(normParams)) {
|
|
545
|
+
renderOfficialBlockPage(pattern.type, null, domain);
|
|
546
|
+
throw new Error(`[MDefender WAF] XHR attack blocked: ${pattern.type}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return originalOpen.apply(this, [method, url, ...rest]);
|
|
551
|
+
};
|
|
446
552
|
}
|
|
553
|
+
|
|
554
|
+
export default { initWaf, renderOfficialBlockPage };
|