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