/src/suricata7/src/app-layer-htp-xff.c
Line | Count | Source |
1 | | /* Copyright (C) 2014 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Ignacio Sanchez <sanchezmartin.ji@gmail.com> |
22 | | * \author Duarte Silva <duarte.silva@serializing.me> |
23 | | */ |
24 | | |
25 | | #include "suricata-common.h" |
26 | | #include "conf.h" |
27 | | |
28 | | #include "app-layer-parser.h" |
29 | | #include "app-layer-htp.h" |
30 | | #include "app-layer-htp-xff.h" |
31 | | |
32 | | #ifndef HAVE_MEMRCHR |
33 | | #include "util-memrchr.h" |
34 | | #endif |
35 | | |
36 | | #include "util-misc.h" |
37 | | #include "util-unittest.h" |
38 | | |
39 | | /** XFF header value minimal length */ |
40 | 1.29M | #define XFF_CHAIN_MINLEN 7 |
41 | | /** XFF header value maximum length */ |
42 | 325 | #define XFF_CHAIN_MAXLEN 256 |
43 | | /** Default XFF header name */ |
44 | 0 | #define XFF_DEFAULT "X-Forwarded-For" |
45 | | |
46 | | /** \internal |
47 | | * \brief parse XFF string |
48 | | * \param input input string, might be modified |
49 | | * \param output output buffer |
50 | | * \param output_size size of output buffer |
51 | | * \retval bool 1 ok, 0 fail |
52 | | */ |
53 | | static int ParseXFFString(char *input, char *output, int output_size) |
54 | 2.84k | { |
55 | 2.84k | size_t len = strlen(input); |
56 | 2.84k | if (len == 0) |
57 | 1.00k | return 0; |
58 | | |
59 | 1.84k | if (input[0] == '[') { |
60 | 0 | char *end = strchr(input, ']'); |
61 | 0 | if (end == NULL) // malformed, not closed |
62 | 0 | return 0; |
63 | | |
64 | 0 | if (end != input+(len - 1)) { |
65 | 0 | SCLogDebug("data after closing bracket"); |
66 | | // if we ever want to parse the port, we can do it here |
67 | 0 | } |
68 | | |
69 | | /* done, lets wrap up */ |
70 | 0 | input++; // skip past [ |
71 | 0 | *end = '\0'; // overwrite ], ignore anything after |
72 | |
|
73 | 1.84k | } else { |
74 | | /* lets see if the xff string ends in a port */ |
75 | 1.84k | int c = 0; |
76 | 1.84k | int d = 0; |
77 | 1.84k | char *p = input; |
78 | 20.7k | while (*p != '\0') { |
79 | 18.9k | if (*p == ':') |
80 | 242 | c++; |
81 | 18.9k | if (*p == '.') |
82 | 3.87k | d++; |
83 | 18.9k | p++; |
84 | 18.9k | } |
85 | | /* 3 dots: ipv4, one ':' port */ |
86 | 1.84k | if (d == 3 && c == 1) { |
87 | 1 | SCLogDebug("XFF w port %s", input); |
88 | 1 | char *x = strchr(input, ':'); |
89 | 1 | if (x) { |
90 | 1 | *x = '\0'; |
91 | 1 | SCLogDebug("XFF w/o port %s", input); |
92 | | // if we ever want to parse the port, we can do it here |
93 | 1 | } |
94 | 1 | } |
95 | 1.84k | } |
96 | | |
97 | 1.84k | SCLogDebug("XFF %s", input); |
98 | | |
99 | | /** Sanity check on extracted IP for IPv4 and IPv6 */ |
100 | 1.84k | uint32_t ip[4]; |
101 | 1.84k | if (inet_pton(AF_INET, input, ip) == 1 || |
102 | 586 | inet_pton(AF_INET6, input, ip) == 1) |
103 | 1.25k | { |
104 | 1.25k | strlcpy(output, input, output_size); |
105 | 1.25k | return 1; // OK |
106 | 1.25k | } |
107 | 586 | return 0; |
108 | 1.84k | } |
109 | | |
110 | | /** |
111 | | * \brief Function to return XFF IP if any in the selected transaction. The |
112 | | * caller needs to lock the flow. |
113 | | * \retval 1 if the IP has been found and returned in dstbuf |
114 | | * \retval 0 if the IP has not being found or error |
115 | | */ |
116 | | int HttpXFFGetIPFromTx(const Flow *f, uint64_t tx_id, HttpXFFCfg *xff_cfg, |
117 | | char *dstbuf, int dstbuflen) |
118 | 1.98M | { |
119 | 1.98M | uint8_t xff_chain[XFF_CHAIN_MAXLEN]; |
120 | 1.98M | HtpState *htp_state = NULL; |
121 | 1.98M | htp_tx_t *tx = NULL; |
122 | 1.98M | uint64_t total_txs = 0; |
123 | 1.98M | uint8_t *p_xff = NULL; |
124 | | |
125 | 1.98M | htp_state = (HtpState *)FlowGetAppState(f); |
126 | | |
127 | 1.98M | if (htp_state == NULL) { |
128 | 0 | SCLogDebug("no http state, XFF IP cannot be retrieved"); |
129 | 0 | return 0; |
130 | 0 | } |
131 | | |
132 | 1.98M | total_txs = AppLayerParserGetTxCnt(f, htp_state); |
133 | 1.98M | if (tx_id >= total_txs) |
134 | 298 | return 0; |
135 | | |
136 | 1.98M | tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP1, htp_state, tx_id); |
137 | 1.98M | if (tx == NULL) { |
138 | 682k | SCLogDebug("tx is NULL, XFF cannot be retrieved"); |
139 | 682k | return 0; |
140 | 682k | } |
141 | | |
142 | 1.29M | htp_header_t *h_xff = NULL; |
143 | 1.29M | if (tx->request_headers != NULL) { |
144 | 1.29M | h_xff = htp_table_get_c(tx->request_headers, xff_cfg->header); |
145 | 1.29M | } |
146 | | |
147 | 1.29M | if (h_xff != NULL && bstr_len(h_xff->value) >= XFF_CHAIN_MINLEN && |
148 | 325 | bstr_len(h_xff->value) < XFF_CHAIN_MAXLEN) { |
149 | | |
150 | 321 | memcpy(xff_chain, bstr_ptr(h_xff->value), bstr_len(h_xff->value)); |
151 | 321 | xff_chain[bstr_len(h_xff->value)]=0; |
152 | | |
153 | 321 | if (xff_cfg->flags & XFF_REVERSE) { |
154 | | /** Get the last IP address from the chain */ |
155 | 321 | p_xff = memrchr(xff_chain, ' ', bstr_len(h_xff->value)); |
156 | 321 | if (p_xff == NULL) { |
157 | 36 | p_xff = xff_chain; |
158 | 285 | } else { |
159 | 285 | p_xff++; |
160 | 285 | } |
161 | 321 | } |
162 | 0 | else { |
163 | | /** Get the first IP address from the chain */ |
164 | 0 | p_xff = memchr(xff_chain, ',', bstr_len(h_xff->value)); |
165 | 0 | if (p_xff != NULL) { |
166 | 0 | *p_xff = 0; |
167 | 0 | } |
168 | 0 | p_xff = xff_chain; |
169 | 0 | } |
170 | 321 | return ParseXFFString((char *)p_xff, dstbuf, dstbuflen); |
171 | 321 | } |
172 | 1.29M | return 0; |
173 | 1.29M | } |
174 | | |
175 | | /** |
176 | | * \brief Function to return XFF IP if any. The caller needs to lock the flow. |
177 | | * \retval 1 if the IP has been found and returned in dstbuf |
178 | | * \retval 0 if the IP has not being found or error |
179 | | */ |
180 | | int HttpXFFGetIP(const Flow *f, HttpXFFCfg *xff_cfg, char *dstbuf, int dstbuflen) |
181 | 64.5k | { |
182 | 64.5k | HtpState *htp_state = NULL; |
183 | 64.5k | uint64_t tx_id = 0; |
184 | 64.5k | uint64_t total_txs = 0; |
185 | | |
186 | 64.5k | htp_state = (HtpState *)FlowGetAppState(f); |
187 | 64.5k | if (htp_state == NULL) { |
188 | 0 | SCLogDebug("no http state, XFF IP cannot be retrieved"); |
189 | 0 | goto end; |
190 | 0 | } |
191 | | |
192 | 64.5k | total_txs = AppLayerParserGetTxCnt(f, htp_state); |
193 | 2.32M | for (; tx_id < total_txs; tx_id++) { |
194 | 2.26M | if (HttpXFFGetIPFromTx(f, tx_id, xff_cfg, dstbuf, dstbuflen) == 1) |
195 | 422 | return 1; |
196 | 2.26M | } |
197 | | |
198 | 64.1k | end: |
199 | 64.1k | return 0; // Not found |
200 | 64.5k | } |
201 | | |
202 | | /** |
203 | | * \brief Function to return XFF configuration from a configuration node. |
204 | | */ |
205 | | void HttpXFFGetCfg(ConfNode *conf, HttpXFFCfg *result) |
206 | 8 | { |
207 | 8 | BUG_ON(result == NULL); |
208 | | |
209 | 8 | ConfNode *xff_node = NULL; |
210 | | |
211 | 8 | if (conf != NULL) |
212 | 8 | xff_node = ConfNodeLookupChild(conf, "xff"); |
213 | | |
214 | 8 | if (xff_node != NULL && ConfNodeChildValueIsTrue(xff_node, "enabled")) { |
215 | 4 | const char *xff_mode = ConfNodeLookupChildValue(xff_node, "mode"); |
216 | | |
217 | 4 | if (xff_mode != NULL && strcasecmp(xff_mode, "overwrite") == 0) { |
218 | 0 | result->flags |= XFF_OVERWRITE; |
219 | 4 | } else { |
220 | 4 | if (xff_mode == NULL) { |
221 | 0 | SCLogWarning("The XFF mode hasn't been defined, falling back to extra-data mode"); |
222 | 0 | } |
223 | 4 | else if (strcasecmp(xff_mode, "extra-data") != 0) { |
224 | 0 | SCLogWarning( |
225 | 0 | "The XFF mode %s is invalid, falling back to extra-data mode", xff_mode); |
226 | 0 | } |
227 | 4 | result->flags |= XFF_EXTRADATA; |
228 | 4 | } |
229 | | |
230 | 4 | const char *xff_deployment = ConfNodeLookupChildValue(xff_node, "deployment"); |
231 | | |
232 | 4 | if (xff_deployment != NULL && strcasecmp(xff_deployment, "forward") == 0) { |
233 | 0 | result->flags |= XFF_FORWARD; |
234 | 4 | } else { |
235 | 4 | if (xff_deployment == NULL) { |
236 | 0 | SCLogWarning("The XFF deployment hasn't been defined, falling back to reverse " |
237 | 0 | "proxy deployment"); |
238 | 0 | } |
239 | 4 | else if (strcasecmp(xff_deployment, "reverse") != 0) { |
240 | 0 | SCLogWarning("The XFF mode %s is invalid, falling back to reverse proxy deployment", |
241 | 0 | xff_deployment); |
242 | 0 | } |
243 | 4 | result->flags |= XFF_REVERSE; |
244 | 4 | } |
245 | | |
246 | 4 | const char *xff_header = ConfNodeLookupChildValue(xff_node, "header"); |
247 | | |
248 | 4 | if (xff_header != NULL) { |
249 | 4 | result->header = (char *) xff_header; |
250 | 4 | } else { |
251 | 0 | SCLogWarning("The XFF header hasn't been defined, using the default %s", XFF_DEFAULT); |
252 | 0 | result->header = XFF_DEFAULT; |
253 | 0 | } |
254 | 4 | } |
255 | 4 | else { |
256 | 4 | result->flags = XFF_DISABLED; |
257 | 4 | } |
258 | 8 | } |
259 | | |
260 | | |
261 | | #ifdef UNITTESTS |
262 | | static int XFFTest01(void) { |
263 | | char input[] = "1.2.3.4:5678"; |
264 | | char output[16]; |
265 | | int r = ParseXFFString(input, output, sizeof(output)); |
266 | | FAIL_IF_NOT(r == 1 && strcmp(output, "1.2.3.4") == 0); |
267 | | PASS; |
268 | | } |
269 | | |
270 | | static int XFFTest02(void) { |
271 | | char input[] = "[12::34]:1234"; // thanks chort! |
272 | | char output[16]; |
273 | | int r = ParseXFFString(input, output, sizeof(output)); |
274 | | FAIL_IF_NOT(r == 1 && strcmp(output, "12::34") == 0); |
275 | | PASS; |
276 | | } |
277 | | |
278 | | static int XFFTest03(void) { |
279 | | char input[] = "[2a03:2880:1010:3f02:face:b00c:0:2]:80"; // thanks chort! |
280 | | char output[46]; |
281 | | int r = ParseXFFString(input, output, sizeof(output)); |
282 | | FAIL_IF_NOT(r == 1 && strcmp(output, "2a03:2880:1010:3f02:face:b00c:0:2") == 0); |
283 | | PASS; |
284 | | } |
285 | | |
286 | | static int XFFTest04(void) { |
287 | | char input[] = "[2a03:2880:1010:3f02:face:b00c:0:2]"; // thanks chort! |
288 | | char output[46]; |
289 | | int r = ParseXFFString(input, output, sizeof(output)); |
290 | | FAIL_IF_NOT(r == 1 && strcmp(output, "2a03:2880:1010:3f02:face:b00c:0:2") == 0); |
291 | | PASS; |
292 | | } |
293 | | |
294 | | static int XFFTest05(void) { |
295 | | char input[] = "[::ffff:1.2.3.4]:1234"; // thanks double-p |
296 | | char output[46]; |
297 | | int r = ParseXFFString(input, output, sizeof(output)); |
298 | | FAIL_IF_NOT(r == 1 && strcmp(output, "::ffff:1.2.3.4") == 0); |
299 | | PASS; |
300 | | } |
301 | | |
302 | | static int XFFTest06(void) { |
303 | | char input[] = "12::34"; |
304 | | char output[46]; |
305 | | int r = ParseXFFString(input, output, sizeof(output)); |
306 | | FAIL_IF_NOT(r == 1 && strcmp(output, "12::34") == 0); |
307 | | PASS; |
308 | | } |
309 | | |
310 | | static int XFFTest07(void) { |
311 | | char input[] = "1.2.3.4"; |
312 | | char output[46]; |
313 | | int r = ParseXFFString(input, output, sizeof(output)); |
314 | | FAIL_IF_NOT(r == 1 && strcmp(output, "1.2.3.4") == 0); |
315 | | PASS; |
316 | | } |
317 | | |
318 | | static int XFFTest08(void) { |
319 | | char input[] = "[1.2.3.4:1234"; |
320 | | char output[46]; |
321 | | int r = ParseXFFString(input, output, sizeof(output)); |
322 | | FAIL_IF_NOT(r == 0); |
323 | | PASS; |
324 | | } |
325 | | |
326 | | static int XFFTest09(void) { |
327 | | char input[] = "999.999.999.999:1234"; |
328 | | char output[46]; |
329 | | int r = ParseXFFString(input, output, sizeof(output)); |
330 | | FAIL_IF_NOT(r == 0); |
331 | | PASS; |
332 | | } |
333 | | |
334 | | #endif |
335 | | |
336 | | void HTPXFFParserRegisterTests(void) |
337 | 0 | { |
338 | | #ifdef UNITTESTS |
339 | | UtRegisterTest("XFFTest01", XFFTest01); |
340 | | UtRegisterTest("XFFTest02", XFFTest02); |
341 | | UtRegisterTest("XFFTest03", XFFTest03); |
342 | | UtRegisterTest("XFFTest04", XFFTest04); |
343 | | UtRegisterTest("XFFTest05", XFFTest05); |
344 | | UtRegisterTest("XFFTest06", XFFTest06); |
345 | | UtRegisterTest("XFFTest07", XFFTest07); |
346 | | UtRegisterTest("XFFTest08", XFFTest08); |
347 | | UtRegisterTest("XFFTest09", XFFTest09); |
348 | | #endif |
349 | 0 | } |