Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}