Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/src/detect-ftpbounce.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2007-2010 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 Pablo Rincon <pablo.rincon.crespo@gmail.com>
22
 *
23
 * ftpbounce keyword, part of the detection engine.
24
 */
25
26
#include "suricata-common.h"
27
#include "decode.h"
28
#include "detect.h"
29
#include "detect-parse.h"
30
#include "detect-engine.h"
31
#include "detect-engine-mpm.h"
32
#include "detect-engine-state.h"
33
#include "detect-content.h"
34
#include "detect-engine-build.h"
35
36
#include "app-layer.h"
37
#include "app-layer-parser.h"
38
#include "app-layer-ftp.h"
39
#include "util-unittest.h"
40
#include "util-unittest-helper.h"
41
#include "util-debug.h"
42
#include "flow.h"
43
#include "flow-var.h"
44
#include "flow-util.h"
45
#include "threads.h"
46
#include "detect-ftpbounce.h"
47
#include "stream-tcp.h"
48
#include "util-byte.h"
49
50
static int DetectFtpbounceALMatch(DetectEngineThreadCtx *,
51
        Flow *, uint8_t, void *, void *,
52
        const Signature *, const SigMatchCtx *);
53
54
static int DetectFtpbounceSetup(DetectEngineCtx *, Signature *, const char *);
55
static int g_ftp_request_list_id = 0;
56
57
/**
58
 * \brief Registration function for ftpbounce: keyword
59
 * \todo add support for no_stream and stream_only
60
 */
61
void DetectFtpbounceRegister(void)
62
73
{
63
73
    sigmatch_table[DETECT_FTPBOUNCE].name = "ftpbounce";
64
73
    sigmatch_table[DETECT_FTPBOUNCE].desc = "detect FTP bounce attacks";
65
73
    sigmatch_table[DETECT_FTPBOUNCE].Setup = DetectFtpbounceSetup;
66
73
    sigmatch_table[DETECT_FTPBOUNCE].AppLayerTxMatch = DetectFtpbounceALMatch;
67
73
    sigmatch_table[DETECT_FTPBOUNCE].url = "/rules/ftp-keywords.html#ftpbounce";
68
73
    sigmatch_table[DETECT_FTPBOUNCE].flags = SIGMATCH_NOOPT;
69
70
73
    g_ftp_request_list_id = DetectBufferTypeRegister("ftp_request");
71
72
73
    DetectAppLayerInspectEngineRegister2(
73
73
            "ftp_request", ALPROTO_FTP, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL);
74
73
}
75
76
/**
77
 * \brief This function is used to match ftpbounce attacks
78
 *
79
 * \param payload Payload of the PORT command
80
 * \param payload_len Length of the payload
81
 * \param ip_orig IP source to check the ftpbounce condition
82
 * \param offset offset to the arguments of the PORT command
83
 *
84
 * \retval 1 if ftpbounce detected, 0 if not
85
 */
86
static int DetectFtpbounceMatchArgs(
87
        uint8_t *payload, uint32_t payload_len, uint32_t ip_orig, uint32_t offset)
88
0
{
89
0
    SCEnter();
90
0
    SCLogDebug("Checking ftpbounce condition");
91
0
    char *c = NULL;
92
0
    uint32_t i = 0;
93
0
    int octet = 0;
94
0
    int octet_ascii_len = 0;
95
0
    int noctet = 0;
96
0
    uint32_t ip = 0;
97
    /* PrintRawDataFp(stdout, payload, payload_len); */
98
99
0
    if (payload_len < 7) {
100
        /* we need at least a different ip address
101
         * in the format 1,2,3,4,x,y where x,y is the port
102
         * in two byte representation so let's look at
103
         * least for the IP octets in comma separated */
104
0
        return 0;
105
0
    }
106
107
0
    if (offset + 7 >= payload_len)
108
0
        return 0;
109
110
0
    c =(char*) payload;
111
0
    if (c == NULL) {
112
0
        SCLogDebug("No payload to check");
113
0
        return 0;
114
0
    }
115
116
0
    i = offset;
117
    /* Search for the first IP octect(Skips "PORT ") */
118
0
    while (i < payload_len && !isdigit((unsigned char)c[i])) i++;
119
120
0
    for (;i < payload_len && octet_ascii_len < 4 ;i++) {
121
0
        if (isdigit((unsigned char)c[i])) {
122
0
            octet =(c[i] - '0') + octet * 10;
123
0
            octet_ascii_len++;
124
0
        } else {
125
0
            if (octet > 256) {
126
0
                SCLogDebug("Octet not in ip format");
127
0
                return 0;
128
0
            }
129
130
0
            if (isspace((unsigned char)c[i]))
131
0
                while (i < payload_len && isspace((unsigned char)c[i]) ) i++;
132
133
0
            if (i < payload_len && c[i] == ',') { /* we have an octet */
134
0
                noctet++;
135
0
                octet_ascii_len = 0;
136
0
                ip =(ip << 8) + octet;
137
0
                octet = 0;
138
0
            } else {
139
0
                SCLogDebug("Unrecognized character '%c'", c[i]);
140
0
                return 0;
141
0
            }
142
0
            if (noctet == 4) {
143
                /* Different IP than src, ftp bounce scan */
144
0
                ip = SCNtohl(ip);
145
146
0
                if (ip != ip_orig) {
147
0
                    SCLogDebug("Different ip, so Matched ip:%d <-> ip_orig:%d",
148
0
                               ip, ip_orig);
149
0
                    return 1;
150
0
                }
151
0
                SCLogDebug("Same ip, so no match here");
152
0
                return 0;
153
0
            }
154
0
        }
155
0
    }
156
0
    SCLogDebug("No match");
157
0
    return 0;
158
0
}
159
160
/**
161
 * \brief This function is used to check matches from the FTP App Layer Parser
162
 *
163
 * \param t pointer to thread vars
164
 * \param det_ctx pointer to the pattern matcher thread
165
 * \param p pointer to the current packet
166
 * \param m pointer to the sigmatch but we don't use it since ftpbounce
167
 *          has no options
168
 * \retval 0 no match
169
 * \retval 1 match
170
 */
171
static int DetectFtpbounceALMatch(DetectEngineThreadCtx *det_ctx,
172
        Flow *f, uint8_t flags,
173
        void *state, void *txv,
174
        const Signature *s, const SigMatchCtx *m)
175
0
{
176
0
    SCEnter();
177
178
0
    FtpState *ftp_state = (FtpState *)state;
179
0
    if (ftp_state == NULL) {
180
0
        SCLogDebug("no ftp state, no match");
181
0
        SCReturnInt(0);
182
0
    }
183
184
0
    int ret = 0;
185
0
    if (ftp_state->command == FTP_COMMAND_PORT) {
186
0
        ret = DetectFtpbounceMatchArgs(ftp_state->port_line,
187
0
                  ftp_state->port_line_len, f->src.address.address_un_data32[0],
188
0
                  ftp_state->arg_offset);
189
0
    }
190
191
0
    SCReturnInt(ret);
192
0
}
193
194
/**
195
 * \brief this function is used to add the parsed ftpbounce
196
 *
197
 * \param de_ctx pointer to the Detection Engine Context
198
 * \param s pointer to the Current Signature
199
 * \param m pointer to the Current SigMatch
200
 * \param ftpbouncestr pointer to the user provided ftpbounce options
201
 *                     currently there are no options.
202
 *
203
 * \retval 0 on Success
204
 * \retval -1 on Failure
205
 */
206
int DetectFtpbounceSetup(DetectEngineCtx *de_ctx, Signature *s, const char *ftpbouncestr)
207
523
{
208
523
    SCEnter();
209
210
523
    SigMatch *sm = NULL;
211
212
523
    if (DetectSignatureSetAppProto(s, ALPROTO_FTP) != 0)
213
480
        return -1;
214
215
43
    sm = SigMatchAlloc();
216
43
    if (sm == NULL) {
217
0
        return -1;
218
0
    }
219
220
43
    sm->type = DETECT_FTPBOUNCE;
221
222
    /* We don't need to allocate any data for ftpbounce here.
223
     *
224
     * TODO: As a suggestion, maybe we can add a flag in the flow
225
     * to set the stream as "bounce detected" for fast Match.
226
     * When you do a ftp bounce attack you usually use the same
227
     * communication control stream to "setup" various destinations
228
     * without breaking the connection, so I guess we can make it a bit faster
229
     * with a flow flag set lookup in the Match function.
230
     */
231
43
    sm->ctx = NULL;
232
233
43
    SigMatchAppendSMToList(s, sm, g_ftp_request_list_id);
234
43
    SCReturnInt(0);
235
43
}