1 //--------------------------------------------------------------------------
2 // Copyright (C) 2014-2021 Cisco and/or its affiliates. All rights reserved.
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License Version 2 as published
6 // by the Free Software Foundation.  You may not use, modify or distribute
7 // this program under any other version of the GNU General Public License.
8 //
9 // This program is distributed in the hope that it will be useful, but
10 // WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 // General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along
15 // with this program; if not, write to the Free Software Foundation, Inc.,
16 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 //--------------------------------------------------------------------------
18 // http_msg_status.cc author Tom Peters <thopeter@cisco.com>
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "http_msg_status.h"
25 
26 #include "http_api.h"
27 #include "http_common.h"
28 #include "http_enum.h"
29 #include "http_msg_header.h"
30 #include "stream/stream.h"
31 
32 using namespace HttpCommon;
33 using namespace HttpEnums;
34 using namespace snort;
35 
HttpMsgStatus(const uint8_t * buffer,const uint16_t buf_size,HttpFlowData * session_data_,SourceId source_id_,bool buf_owner,Flow * flow_,const HttpParaList * params_)36 HttpMsgStatus::HttpMsgStatus(const uint8_t* buffer, const uint16_t buf_size,
37     HttpFlowData* session_data_, SourceId source_id_, bool buf_owner, Flow* flow_,
38     const HttpParaList* params_) :
39     HttpMsgStart(buffer, buf_size, session_data_, source_id_, buf_owner, flow_, params_)
40 {
41     transaction->set_status(this);
42     get_related_sections();
43 }
44 
parse_start_line()45 void HttpMsgStatus::parse_start_line()
46 {
47     // Splitter guarantees line begins with "HTTP/"
48 
49     if ((start_line.length() < 12) || !is_sp_tab[start_line.start()[8]])
50     {
51         add_infraction(INF_BAD_STAT_LINE);
52         create_event(EVENT_MISFORMATTED_HTTP);
53         return;
54     }
55 
56     int32_t first_end; // last whitespace in first clump of whitespace
57     for (first_end = 9; (first_end < start_line.length())
58         && is_sp_tab[start_line.start()[first_end]]; first_end++);
59     first_end--;
60 
61     if (start_line.length() < first_end + 4)
62     {
63         add_infraction(INF_BAD_STAT_LINE);
64         create_event(EVENT_MISFORMATTED_HTTP);
65         return;
66     }
67 
68     if ((start_line.length() > first_end + 4) && !is_sp_tab[start_line.start()[first_end + 4]])
69     {
70         // FIXIT-M This should not be fatal. HI supports something like "HTTP/1.1 200\\OK\r\n" as
71         // seen in a status line test.
72         add_infraction(INF_BAD_STAT_LINE);
73         create_event(EVENT_MISFORMATTED_HTTP);
74         return;
75     }
76 
77     HttpModule::increment_peg_counts(PEG_RESPONSE);
78 
79     version.set(8, start_line.start());
80     derive_version_id();
81 
82     status_code.set(3, start_line.start() + first_end + 1);
83     derive_status_code_num();
84 
85     if (start_line.length() > first_end + 5)
86     {
87         reason_phrase.set(start_line.length() - first_end - 5, start_line.start() + first_end + 5);
88     }
89 }
90 
derive_status_code_num()91 void HttpMsgStatus::derive_status_code_num()
92 {
93     if ((status_code.start()[0] < '0') || (status_code.start()[0] > '9') ||
94         (status_code.start()[1] < '0') || (status_code.start()[1] > '9') ||
95         (status_code.start()[2] < '0') || (status_code.start()[2] > '9'))
96     {
97         add_infraction(INF_BAD_STAT_CODE);
98         create_event(EVENT_INVALID_STATCODE);
99         status_code_num = STAT_PROBLEMATIC;
100         return;
101     }
102     status_code_num = (status_code.start()[0] - '0') * 100 + (status_code.start()[1] - '0') * 10 +
103         (status_code.start()[2] - '0');
104     if ((status_code_num < 100) || (status_code_num > 599))
105     {
106         add_infraction(INF_BAD_STAT_CODE);
107         create_event(EVENT_INVALID_STATCODE);
108     }
109     if ((status_code_num >= 102) && (status_code_num <= 199))
110     {
111         add_infraction(INF_UNKNOWN_1XX_STATUS);
112         create_event(EVENT_UNKNOWN_1XX_STATUS);
113     }
114 }
115 
gen_events()116 void HttpMsgStatus::gen_events()
117 {
118     if (*transaction->get_infractions(source_id) & INF_BAD_STAT_LINE)
119         return;
120 
121     if (status_code.start() > start_line.start() + 9)
122     {
123         add_infraction(INF_STATUS_WS);
124         create_event(EVENT_IMPROPER_WS);
125     }
126 
127     for (int k = 8; k < status_code.start() - start_line.start(); k++)
128     {
129         if (start_line.start()[k] == '\t')
130         {
131             add_infraction(INF_STATUS_TAB);
132             create_event(EVENT_APACHE_WS);
133         }
134     }
135 
136     if (status_code.start() - start_line.start() + 3 < start_line.length())
137     {
138         if (status_code.start()[3] == '\t')
139         {
140             add_infraction(INF_STATUS_TAB);
141             create_event(EVENT_APACHE_WS);
142         }
143     }
144 
145     for (int k=0; k < reason_phrase.length(); k++)
146     {
147         if ((reason_phrase.start()[k] <= 31) || (reason_phrase.start()[k] >= 127))
148         {
149             // Illegal character in reason phrase
150             add_infraction(INF_BAD_PHRASE);
151             create_event(EVENT_CTRL_IN_REASON);
152             break;
153         }
154     }
155 
156     if (!request && (trans_num == 1))
157     {
158         if (flow->is_pdu_inorder(SSN_DIR_FROM_SERVER))
159         {
160             // HTTP response without a request. Possible ssh tunneling
161             add_infraction(INF_RESPONSE_WO_REQUEST);
162             create_event(EVENT_RESPONSE_WO_REQUEST);
163         }
164     }
165 
166     if (status_code_num == 206)
167     {
168         // Verify that 206 Partial Content is in response to a Range request. Unsolicited 206
169         // responses indicate content is being fragmented for no good reason.
170         if ((header[SRC_CLIENT] != nullptr) &&
171             (header[SRC_CLIENT]->get_header_count(HEAD_RANGE) == 0))
172         {
173             add_infraction(INF_206_WITHOUT_RANGE);
174             create_event(EVENT_206_WITHOUT_RANGE);
175         }
176     }
177 }
178 
update_flow()179 void HttpMsgStatus::update_flow()
180 {
181     if (*transaction->get_infractions(source_id) & INF_BAD_STAT_LINE)
182     {
183         session_data->half_reset(source_id);
184         session_data->type_expected[source_id] = SEC_ABORT;
185         return;
186     }
187     session_data->type_expected[source_id] = SEC_HEADER;
188     session_data->version_id[source_id] = version_id;
189     session_data->status_code_num = status_code_num;
190     // 100 response means the next response message will be added to this transaction instead
191     // of being part of another transaction. As implemented it is possible for multiple 100
192     // responses to all be included in the same transaction. It's not obvious whether that is
193     // the best way to handle what should be a highly abnormal situation.
194     if (status_code_num == 100)
195     {
196         // Were we "Expect"-ing this?
197         if ((header[SRC_CLIENT] != nullptr) &&
198             (header[SRC_CLIENT]->get_header_count(HEAD_EXPECT) == 0))
199         {
200             add_infraction(INF_UNEXPECTED_100_RESPONSE);
201             create_event(EVENT_UNEXPECTED_100_RESPONSE);
202         }
203         transaction->set_one_hundred_response();
204     }
205 }
206 
207 #ifdef REG_TEST
print_section(FILE * output)208 void HttpMsgStatus::print_section(FILE* output)
209 {
210     HttpMsgSection::print_section_title(output, "status line");
211     fprintf(output, "Version ID: %d\n", version_id);
212     fprintf(output, "Status Code Num: %d\n", status_code_num);
213     reason_phrase.print(output, "Reason Phrase");
214     get_classic_buffer(HTTP_BUFFER_STAT_CODE, 0, 0).print(output,
215         HttpApi::classic_buffer_names[HTTP_BUFFER_STAT_CODE-1]);
216     get_classic_buffer(HTTP_BUFFER_STAT_MSG, 0, 0).print(output,
217         HttpApi::classic_buffer_names[HTTP_BUFFER_STAT_MSG-1]);
218     get_classic_buffer(HTTP_BUFFER_VERSION, 0, 0).print(output,
219         HttpApi::classic_buffer_names[HTTP_BUFFER_VERSION-1]);
220     get_classic_buffer(HTTP_BUFFER_RAW_STATUS, 0, 0).print(output,
221         HttpApi::classic_buffer_names[HTTP_BUFFER_RAW_STATUS-1]);
222     HttpMsgSection::print_section_wrapup(output);
223 }
224 #endif
225 
226