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