1 /* $Id$ */
2 /****************************************************************************
3 *
4 * Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
5 * Copyright (C) 2005-2013 Sourcefire, Inc.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License Version 2 as
9 * published by the Free Software Foundation. You may not use, modify or
10 * distribute this program under any other version of the GNU General
11 * Public License.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 *
22 ****************************************************************************/
23
24 // @file sp_react.c
25 // @author Russ Combs <rcombs@sourcefire.com>
26
27 /* The original Snort React Plugin was contributed by Maciej Szarpak, Warsaw
28 * University of Technology. The module has been entirely rewritten by
29 * Sourcefire as part of the effort to overhaul active response. Some of the
30 * changes include:
31 *
32 * - elimination of unworkable warn mode
33 * - elimination of proxy port (rule header has ports)
34 * - integration with unified active response mechanism
35 * - queuing of rule option responses so at most one is issued
36 * - allow override by rule action when action is drop
37 * - addition of http headers to default response
38 * - added custom page option
39 * - and other stuff
40 *
41 * This version will send a web page to the client and then reset both
42 * ends of the session. The web page may be configured or the default
43 * may be used. The web page can have the default warning message
44 * inserted or the message from the rule.
45 *
46 * If you wish to just reset the session, use the resp keyword instead.
47 */
48
49 #ifdef ENABLE_REACT
50
51 #ifdef HAVE_CONFIG_H
52 #include "config.h"
53 #endif
54
55 #include <sys/types.h>
56 #include <sys/stat.h>
57
58 #include <stdlib.h>
59 #include <string.h>
60 #include <ctype.h>
61
62 #include "sf_types.h"
63 #include "snort_debug.h"
64 #include "decode.h"
65 #include "encode.h"
66 #include "detection_options.h"
67 #include "parser.h"
68 #include "plugbase.h"
69 #include "plugin_enum.h"
70 #include "profiler.h"
71 #include "active.h"
72 #include "rules.h"
73 #include "sfhashfcn.h"
74 #include "sp_react.h"
75 #include "snort.h"
76
77 #ifdef PERF_PROFILING
78 static PreprocStats reactPerfStats;
79 extern PreprocStats ruleOTNEvalPerfStats;
80 #endif
81
82 static const char* MSG_KEY = "<>";
83 static const char* MSG_PERCENT = "%";
84
85 static const char* DEFAULT_HTTP =
86 "HTTP/1.1 403 Forbidden\r\n"
87 "Connection: close\r\n"
88 "Content-Type: text/html; charset=utf-8\r\n"
89 "Content-Length: %d\r\n"
90 "\r\n";
91
92 static const char* DEFAULT_HTML =
93 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\r\n"
94 " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\r\n"
95 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\r\n"
96 "<head>\r\n"
97 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\r\n"
98 "<title>Access Denied</title>\r\n"
99 "</head>\r\n"
100 "<body>\r\n"
101 "<h1>Access Denied</h1>\r\n"
102 "<p>%s</p>\r\n"
103 "</body>\r\n"
104 "</html>\r\n";
105
106 static const char* DEFAULT_MSG =
107 "You are attempting to access a forbidden site.<br />"
108 "Consult your system administrator for details.";
109
110 typedef struct _ReactData
111 {
112 uint32_t id;
113 int rule_msg; // 1=>use rule msg; 0=>use DEFAULT_MSG
114 ssize_t buf_len; // length of response
115 char* resp_buf; // response to send
116 const OptTreeNode* otn;
117
118 } ReactData;
119
120 static int s_init = 1;
121 static int s_deprecated = 0;
122 static char* s_page = NULL;
123
124 // When React_Init() is called the rule msg keyword may not have
125 // been processed. This necessitates two things:
126 //
127 // * A unique instance id is used in the hash in lieu of the
128 // message text. The id starts at 1 since 0 is reserved for
129 // the default msg. Assuming all rules have different msg
130 // strings, the id is a valid proxy.
131 //
132 // * React_Config() is installed to instantiate the page after
133 // rule parsing is complete (when for sure the msg is
134 // available).
135 //
136 // Ideally a separate rule configuration callback could be installed
137 // that would be called after all options are parsed and before the
138 // options are finalized.
139 static uint32_t s_id = 1;
140
141 // callback functions
142 static void React_Init(struct _SnortConfig *, char *, OptTreeNode *, int);
143 static void React_Cleanup(int signal, void *data);
144 static void React_Config (struct _SnortConfig *, void *data);
145
146 // core functions
147 static void React_GetPage(struct _SnortConfig *);
148 static void React_Parse(char *, OptTreeNode *, ReactData *);
149 static int React_Queue(Packet*, void*);
150 static void React_Send(Packet*, void*);
151
152 //--------------------------------------------------------------------
153 // public functions
154
ReactFree(void * d)155 void ReactFree(void *d)
156 {
157 ReactData *data = (ReactData *)d;
158 if (data->resp_buf)
159 free(data->resp_buf);
160 free(data);
161 }
162
ReactHash(void * d)163 uint32_t ReactHash(void *d)
164 {
165 uint32_t a,b,c,tmp;
166 unsigned int i,j,k,l;
167 ReactData *data = (ReactData *)d;
168
169 const char* s = s_page ? s_page : DEFAULT_HTML;
170 unsigned n = strlen(s);
171
172 a = data->rule_msg;
173 b = n;
174 c = (data->rule_msg ? data->id : 0);
175
176 mix(a,b,c);
177
178 for ( i=0,j=0; i<n; i+=4 )
179 {
180 tmp = 0;
181 k = n - i;
182 if (k > 4)
183 k=4;
184
185 for (l=0;l<k;l++)
186 {
187 tmp |= s[i + l] << l*8;
188 }
189
190 switch (j)
191 {
192 case 0:
193 a += tmp;
194 break;
195 case 1:
196 b += tmp;
197 break;
198 case 2:
199 c += tmp;
200 break;
201 }
202 j++;
203
204 if (j == 3)
205 {
206 mix(a,b,c);
207 j = 0;
208 }
209 }
210
211 if (j != 0)
212 {
213 mix(a,b,c);
214 }
215
216 a += RULE_OPTION_TYPE_REACT;
217
218 final(a,b,c);
219
220 return c;
221 }
222
ReactCompare(void * l,void * r)223 int ReactCompare(void *l, void *r)
224 {
225 ReactData *left = (ReactData *)l;
226 ReactData *right = (ReactData *)r;
227
228 if (!left || !right)
229 return DETECTION_OPTION_NOT_EQUAL;
230
231 if (left->buf_len != right->buf_len)
232 return DETECTION_OPTION_NOT_EQUAL;
233
234 if (memcmp(left->resp_buf, right->resp_buf, left->buf_len) != 0)
235 return DETECTION_OPTION_NOT_EQUAL;
236
237 if (left->rule_msg != right->rule_msg)
238 return DETECTION_OPTION_NOT_EQUAL;
239
240 return DETECTION_OPTION_EQUAL;
241 }
242
SetupReact(void)243 void SetupReact(void)
244 {
245 RegisterRuleOption("react", React_Init, NULL, OPT_TYPE_ACTION, NULL);
246 #ifdef PERF_PROFILING
247 RegisterPreprocessorProfile("react", &reactPerfStats, 3, &ruleOTNEvalPerfStats, NULL);
248 #endif
249 }
250
251 //--------------------------------------------------------------------
252 // callback functions
253
React_Init(struct _SnortConfig * sc,char * data,OptTreeNode * otn,int protocol)254 static void React_Init(struct _SnortConfig *sc, char *data, OptTreeNode *otn, int protocol)
255 {
256 ReactData* rd;
257 void *idx_dup;
258
259 if ( otn->ds_list[PLUGIN_RESPONSE] )
260 FatalError("%s(%d): Multiple response options in rule\n",
261 file_name, file_line);
262
263 if ( protocol != IPPROTO_TCP )
264 FatalError("%s(%d): React options on non-TCP rule\n",
265 file_name, file_line);
266
267 DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,"In React_Init()\n"););
268
269 if ( s_init )
270 {
271 AddFuncToCleanExitList(React_Cleanup, NULL);
272
273 React_GetPage(sc);
274
275 Active_SetEnabled(1);
276 s_init = 0;
277 }
278
279 /* parse the react keywords */
280 rd = SnortAlloc(sizeof(*rd));
281 React_Parse(data, otn, rd);
282 rd->otn = otn;
283
284 if (add_detection_option(sc, RULE_OPTION_TYPE_REACT, (void*)rd, &idx_dup)
285 == DETECTION_OPTION_EQUAL)
286 {
287 free(rd);
288 rd = idx_dup;
289 }
290 /* finally, attach the option's detection function to the rule's
291 detect function pointer list */
292 AddRspFuncToList(React_Queue, otn, (void*)rd);
293 AddFuncToPreprocPostConfigList(sc, React_Config, rd);
294
295 // this prevents multiple response options in rule
296 otn->ds_list[PLUGIN_RESPONSE] = rd;
297 }
298
React_Cleanup(int signal,void * data)299 static void React_Cleanup(int signal, void* data)
300 {
301 if ( s_page )
302 {
303 free(s_page);
304 s_page = NULL;
305 }
306 s_init = 1;
307 }
308
309 //--------------------------------------------------------------------
310 // core functions
311
React_GetPage(struct _SnortConfig * sc)312 static void React_GetPage (struct _SnortConfig *sc)
313 {
314 char* msg;
315 char* percent_s;
316 struct stat fs;
317 FILE* fd;
318 size_t n;
319
320 if ( !sc )
321 FatalError("react: %s(%d) Snort config for parsing is NULL.\n",
322 file_name, file_line);
323
324 if ( s_page || !sc->react_page ) return;
325
326 if ( stat(sc->react_page, &fs) )
327 FatalError("react: %s(%d) can't stat react page file '%s'.\n",
328 file_name, file_line, sc->react_page);
329
330 if ( fs.st_size < 2 )
331 FatalError("react: react page %s size is not adequate.\n",
332 sc->react_page);
333
334 s_page = SnortAlloc(fs.st_size+1);
335 fd = fopen(sc->react_page, "r");
336
337 if ( !fd )
338 FatalError("react: %s(%d) can't open react page file '%s'.\n",
339 file_name, file_line, sc->react_page);
340
341 n = fread(s_page, 1, fs.st_size, fd);
342 fclose(fd);
343
344 if ( n != (size_t)fs.st_size )
345 FatalError("react: %s(%d) can't load react page file '%s'.\n",
346 file_name, file_line, sc->react_page);
347
348 s_page[n] = '\0';
349
350 msg = strstr(s_page, MSG_KEY);
351 if ( msg ) strncpy(msg, "%s", 2);
352
353 // search for %
354 percent_s = strstr(s_page, MSG_PERCENT);
355 if (percent_s)
356 {
357 percent_s += strlen(MSG_PERCENT); // move past current
358 // search for % again
359 percent_s = strstr(percent_s, MSG_PERCENT);
360 if (percent_s)
361 {
362 FatalError("react: %s(%d) can't specify more than one %%s or other "
363 "printf style formatting characters in react page '%s'.\n",
364 file_name, file_line, sc->react_page);
365 }
366 }
367 }
368
369 //--------------------------------------------------------------------
370
React_Parse(char * data,OptTreeNode * otn,ReactData * rd)371 static void React_Parse(char* data, OptTreeNode* otn, ReactData* rd)
372 {
373 char* tok = NULL;
374
375 if ( data )
376 {
377 while(isspace((int)*data)) data++;
378
379 tok = strtok(data, ",");
380 }
381 while(tok)
382 {
383 /* parse the react option keywords */
384 if (
385 !strncasecmp(tok, "proxy", 5) ||
386 !strcasecmp(tok, "block") ||
387 !strcasecmp(tok, "warn") )
388 {
389 if ( !s_deprecated )
390 {
391 ParseWarning("proxy, block, and warn options are deprecated.\n");
392 s_deprecated = 1;
393 }
394 }
395 else if ( !strcasecmp(tok, "msg") )
396 {
397 rd->rule_msg = 1;
398 }
399 else
400 FatalError("%s(%d): invalid react option: %s\n",
401 file_name, file_line, tok);
402
403 tok = strtok(NULL, ",");
404
405 /* get rid of spaces */
406 while ( tok && isspace((int)*tok) ) tok++;
407 }
408 rd->resp_buf = NULL;
409 rd->buf_len = 0;
410 rd->id = s_id++;
411 }
412
413 //--------------------------------------------------------------------
414 // format response buffer
415
React_Config(struct _SnortConfig * sc,void * data)416 static void React_Config (struct _SnortConfig *sc, void *data)
417 {
418 ReactData *rd = (ReactData *)data;
419 size_t body_len, head_len, total_len;
420 char dummy;
421
422 const char* head = DEFAULT_HTTP;
423 const char* body = s_page ? s_page : DEFAULT_HTML;
424
425 const char* msg = rd->otn->sigInfo.message;
426 if ( !msg || !rd->rule_msg ) msg = DEFAULT_MSG;
427
428 body_len = snprintf(&dummy, 1, body, msg);
429 head_len = snprintf(&dummy, 1, head, body_len);
430 total_len = head_len + body_len + 1;
431
432 rd->resp_buf = (char*)SnortAlloc(total_len);
433
434 SnortSnprintf((char*)rd->resp_buf, head_len+1, head, body_len);
435 SnortSnprintf((char*)rd->resp_buf+head_len, body_len+1, body, msg);
436
437 // set actual length
438 rd->resp_buf[total_len-1] = '\0';
439 rd->buf_len = strlen(rd->resp_buf);
440 }
441
442 //--------------------------------------------------------------------
443
React_Queue(Packet * p,void * pv)444 static int React_Queue (Packet* p, void* pv)
445 {
446 ReactData* rd = (ReactData*)pv;
447 PROFILE_VARS;
448
449 PREPROC_PROFILE_START(reactPerfStats);
450
451 if ( Active_IsRSTCandidate(p) )
452 Active_QueueResponse(React_Send, rd);
453
454 Active_DropSession(p);
455 if (pkt_trace_enabled)
456 {
457 if (rd && rd->otn)
458 addPktTraceData(VERDICT_REASON_REACT, snprintf(trace_line, MAX_TRACE_LINE,
459 "Snort React: web page %s, gid %u, sid %u, %s\n", Active_IsRSTCandidate(p)? "is sent" : "isn't sent",
460 rd->otn->sigInfo.generator, rd->otn->sigInfo.id, getPktTraceActMsg()));
461 else addPktTraceData(VERDICT_REASON_REACT, snprintf(trace_line, MAX_TRACE_LINE,
462 "Snort React: web page %s, %s\n", Active_IsRSTCandidate(p)? "is sent" : "isn't sent", getPktTraceActMsg()));
463 }
464 else addPktTraceData(VERDICT_REASON_REACT, 0);
465
466 PREPROC_PROFILE_END(reactPerfStats);
467 return 0;
468 }
469
470 //--------------------------------------------------------------------
471
React_Send(Packet * p,void * pv)472 static void React_Send (Packet* p, void* pv)
473 {
474 ReactData* rd = (ReactData*)pv;
475 EncodeFlags df = (p->packet_flags & PKT_FROM_SERVER) ? ENC_FLAG_FWD : 0;
476 EncodeFlags sent = rd->buf_len;
477 EncodeFlags rf;
478 PROFILE_VARS;
479
480 PREPROC_PROFILE_START(reactPerfStats);
481 Active_IgnoreSession(p);
482
483 if (p->packet_flags & PKT_STREAM_EST)
484 {
485 Active_SendData(p, df, (uint8_t*)rd->resp_buf, rd->buf_len);
486 // Active_SendData sends a FIN, so need to bump seq by 1.
487 sent++;
488 }
489 rf = df ^ ENC_FLAG_FWD;
490 df |= ENC_FLAG_SEQ | (ENC_FLAG_VAL & sent);
491 Active_SendReset(p, df);
492 Active_SendReset(p, rf);
493
494 PREPROC_PROFILE_END(reactPerfStats);
495 }
496
497 #endif /* ENABLE_REACT */
498
499