1 /* 2 * Copyright (c) 2015 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@dragonflybsd.org> 6 * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 3. Neither the name of The DragonFly Project nor the names of its 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific, prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 /* 36 * Use: pipe syslog auth output to this program. 37 * 38 * Detects failed ssh login attempts and maps out the originating IP and 39 * issues, in case of a PF firewall, adds to a PF table <lockout> using 40 * 'pfctl -tlockout -Tadd' commands. 41 * 42 * /etc/syslog.conf line example: 43 * auth.info;authpriv.info |exec /usr/sbin/sshlockout -pf lockout 44 * 45 * Also suggest a cron entry to clean out the PF table at least once a day. 46 * 3 3 * * * pfctl -tlockout -Tflush 47 * 48 * Alternatively there is an ipfw(8) mode (-ipfw <rulenum>). 49 */ 50 51 #include <sys/types.h> 52 #include <sys/socket.h> 53 #include <sys/sysctl.h> 54 #include <sys/time.h> 55 56 #include <arpa/inet.h> 57 #include <netinet/in.h> 58 #include <net/if.h> 59 #include <net/ipfw/ip_fw.h> 60 61 #include <stdio.h> 62 #include <stdlib.h> 63 #include <unistd.h> 64 #include <string.h> 65 #include <stdarg.h> 66 #include <syslog.h> 67 #include <ctype.h> 68 #include <stdbool.h> 69 70 typedef struct iphist { 71 struct iphist *next; 72 struct iphist *hnext; 73 char *ips; 74 time_t t; 75 int hv; 76 } iphist_t; 77 78 struct args { 79 int fw_type; 80 char *arg1; 81 int arg2; 82 }; 83 84 #define FW_IS_PF 1 85 #define FW_IS_IPFW 2 86 #define FW_IS_IPFWTBL 3 87 88 #define HSIZE 1024 89 #define HMASK (HSIZE - 1) 90 #define MAXHIST 100 91 #define SSHLIMIT 5 /* per hour */ 92 #define MAX_TABLE_NAME 20 /* PF table name limit */ 93 94 static iphist_t *hist_base; 95 static iphist_t **hist_tail = &hist_base; 96 static iphist_t *hist_hash[HSIZE]; 97 static int hist_count = 0; 98 99 static int ipfw_sock = -1; 100 101 static struct args args; 102 103 static void init_iphist(void); 104 static void checkline(char *buf); 105 static int insert_iph(const char *ips, time_t t); 106 static void delete_iph(iphist_t *ip); 107 108 static void 109 block_ip(const char *ips) 110 { 111 struct ipfw_ioc_tblcont ent; 112 struct ipfw_ioc_tblent *te; 113 char buf[128]; 114 int r = 0; 115 116 switch (args.fw_type) { 117 case FW_IS_PF: 118 r = snprintf(buf, sizeof(buf), 119 "pfctl -t%s -Tadd %s", args.arg1, ips); 120 break; 121 122 case FW_IS_IPFW: 123 r = snprintf(buf, sizeof(buf), 124 "ipfw add %s deny tcp from %s to me 22", 125 args.arg1, ips); 126 break; 127 128 case FW_IS_IPFWTBL: 129 memset(&ent, 0, sizeof(ent)); 130 ent.tableid = args.arg2; 131 ent.entcnt = 1; 132 te = &ent.ent[0]; 133 134 r = inet_pton(AF_INET, ips, &te->key.sin_addr); 135 if (r <= 0) 136 break; 137 te->key.sin_family = AF_INET; 138 te->key.sin_len = sizeof(struct sockaddr_in); 139 140 if (setsockopt(ipfw_sock, IPPROTO_IP, IP_FW_TBL_ADD, 141 &ent, sizeof(ent)) < 0) { 142 r = -1; 143 break; 144 } 145 /* Done */ 146 return; 147 } 148 149 if (r > 0 && (int)strlen(buf) == r) { 150 system(buf); 151 } else { 152 syslog(LOG_ERR, "sshlockout: invalid command"); 153 } 154 } 155 156 /* 157 * Stupid simple string hash 158 */ 159 static __inline int 160 iphash(const char *str) 161 { 162 int hv = 0xA1B3569D; 163 164 while (*str) { 165 hv = (hv << 5) ^ *str ^ (hv >> 23); 166 ++str; 167 } 168 return hv; 169 } 170 171 172 static bool 173 parse_args(int ac, char **av) 174 { 175 if (ac >= 2) { 176 if (strcmp(av[1], "-pf") == 0 && ac == 3) { 177 /* -pf <tablename> */ 178 char *tablename = av[2]; 179 180 if (strlen(tablename) > 0 && 181 strlen(tablename) < MAX_TABLE_NAME) { 182 args.fw_type = FW_IS_PF; 183 args.arg1 = tablename; 184 return true; 185 } 186 } 187 if (strcmp(av[1], "-ipfw") == 0 && ac == 3) { 188 /* -ipfw <rule> */ 189 char *rule = av[2]; 190 191 if (strlen(rule) > 0 && strlen(rule) <= 5) { 192 for (char *s = rule; *s; ++s) { 193 if (!isdigit(*s)) 194 return false; 195 } 196 if (atoi(rule) < 1) 197 return false; 198 if (atoi(rule) > 65535) 199 return false; 200 args.fw_type = FW_IS_IPFW; 201 args.arg1 = rule; 202 return true; 203 } 204 } 205 206 if (strcmp(av[1], "-ipfwtbl") == 0 && ac == 3) { 207 /* -ipfwtbl <tableid> */ 208 int tableid; 209 char *eptr; 210 211 tableid = strtoul(av[2], &eptr, 0); 212 if (*eptr != '\0') 213 return false; 214 215 ipfw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); 216 if (ipfw_sock < 0) 217 return false; 218 219 args.fw_type = FW_IS_IPFWTBL; 220 args.arg2 = tableid; 221 return true; 222 } 223 } 224 225 return false; 226 } 227 228 int 229 main(int ac, char **av) 230 { 231 char buf[1024]; 232 233 args.fw_type = 0; 234 args.arg1 = NULL; 235 args.arg2 = 0; 236 237 if (!parse_args(ac, av)) { 238 syslog(LOG_ERR, "sshlockout: invalid argument"); 239 return(1); 240 } 241 242 init_iphist(); 243 244 openlog("sshlockout", LOG_PID|LOG_CONS, LOG_AUTH); 245 syslog(LOG_ERR, "sshlockout starting up"); 246 freopen("/dev/null", "w", stdout); 247 freopen("/dev/null", "w", stderr); 248 249 while (fgets(buf, sizeof(buf), stdin) != NULL) { 250 if (strstr(buf, "sshd") == NULL) 251 continue; 252 checkline(buf); 253 } 254 syslog(LOG_ERR, "sshlockout exiting"); 255 return(0); 256 } 257 258 static void 259 checkip(const char *str, const char *reason1, const char *reason2) 260 { 261 char ips[128]; 262 int n1; 263 int n2; 264 int n3; 265 int n4; 266 time_t t = time(NULL); 267 268 ips[0] = '\0'; 269 270 if (sscanf(str, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) { 271 snprintf(ips, sizeof(ips), "%d.%d.%d.%d", n1, n2, n3, n4); 272 } else { 273 /* 274 * Check for IPv6 address (primitive way) 275 */ 276 int cnt = 0; 277 while (str[cnt] == ':' || isxdigit(str[cnt])) { 278 ++cnt; 279 } 280 if (cnt > 0 && cnt < (int)sizeof(ips)) { 281 memcpy(ips, str, cnt); 282 ips[cnt] = '\0'; 283 } 284 } 285 286 /* 287 * We do not block localhost as is makes no sense. 288 */ 289 if (strcmp(ips, "127.0.0.1") == 0) 290 return; 291 if (strcmp(ips, "::1") == 0) 292 return; 293 294 if (strlen(ips) > 0) { 295 296 /* 297 * Check for DoS attack. When connections from too many 298 * IP addresses come in at the same time, our hash table 299 * would overflow, so we delete the oldest entries AND 300 * block it's IP when they are younger than 10 seconds. 301 * This prevents massive attacks from arbitrary IPs. 302 */ 303 if (hist_count > MAXHIST + 16) { 304 while (hist_count > MAXHIST) { 305 iphist_t *iph = hist_base; 306 int dt = (int)(t - iph->t); 307 if (dt < 10) { 308 syslog(LOG_ERR, 309 "Detected overflow attack, " 310 "locking out %s\n", 311 iph->ips); 312 block_ip(iph->ips); 313 } 314 delete_iph(iph); 315 } 316 } 317 318 if (insert_iph(ips, t)) { 319 syslog(LOG_ERR, 320 "Detected ssh %s attempt " 321 "for %s, locking out %s\n", 322 reason1, reason2, ips); 323 block_ip(ips); 324 } 325 } 326 } 327 328 static void 329 checkline(char *buf) 330 { 331 char *str; 332 333 /* 334 * ssh login attempt with password (only hit if ssh allows 335 * password entry). Root or admin. 336 */ 337 if ((str = strstr(buf, "Failed password for root from")) != NULL || 338 (str = strstr(buf, "Failed password for admin from")) != NULL) { 339 while (*str && (*str < '0' || *str > '9')) 340 ++str; 341 checkip(str, "password login", "root or admin"); 342 return; 343 } 344 345 /* 346 * ssh login attempt with password (only hit if ssh allows password 347 * entry). Non-existant user. 348 */ 349 if ((str = strstr(buf, "Failed password for invalid user")) != NULL) { 350 str += 32; 351 while (*str == ' ') 352 ++str; 353 while (*str && *str != ' ') 354 ++str; 355 if (strncmp(str, " from", 5) == 0) { 356 checkip(str + 5, "password login", "an invalid user"); 357 } 358 return; 359 } 360 361 /* 362 * ssh login attempt for non-existant user. 363 */ 364 if ((str = strstr(buf, "Invalid user")) != NULL) { 365 str += 12; 366 while (*str == ' ') 367 ++str; 368 while (*str && *str != ' ') 369 ++str; 370 if (strncmp(str, " from", 5) == 0) { 371 checkip(str + 5, "login", "an invalid user"); 372 } 373 return; 374 } 375 376 /* 377 * Premature disconnect in pre-authorization phase, typically an 378 * attack but require 5 attempts in an hour before cleaning it out. 379 */ 380 if ((str = strstr(buf, "Received disconnect from ")) != NULL && 381 strstr(buf, "[preauth]") != NULL) { 382 checkip(str + 25, "preauth", "an invalid user"); 383 return; 384 } 385 386 /* 387 * Maximum authentication attempts exceeded 388 */ 389 if ((str = strstr(buf, "maximum authentication " 390 "attempts exceeded for ")) != NULL && 391 strstr(buf, "[preauth]") != NULL) { 392 str += 45; 393 while (*str == ' ') 394 ++str; 395 while (*str && *str != ' ') 396 ++str; 397 if (strncmp(str, " from", 5) == 0) { 398 checkip(str + 5, "login", "many attempts"); 399 } 400 return; 401 } 402 } 403 404 /* 405 * Insert IP record 406 */ 407 static int 408 insert_iph(const char *ips, time_t t) 409 { 410 iphist_t *ip = malloc(sizeof(*ip)); 411 iphist_t *scan; 412 int found; 413 414 ip->hv = iphash(ips); 415 ip->ips = strdup(ips); 416 ip->t = t; 417 418 ip->hnext = hist_hash[ip->hv & HMASK]; 419 hist_hash[ip->hv & HMASK] = ip; 420 ip->next = NULL; 421 *hist_tail = ip; 422 hist_tail = &ip->next; 423 ++hist_count; 424 425 /* 426 * hysteresis 427 */ 428 if (hist_count > MAXHIST + 16) { 429 while (hist_count > MAXHIST) 430 delete_iph(hist_base); 431 } 432 433 /* 434 * Check limit 435 */ 436 found = 0; 437 for (scan = hist_hash[ip->hv & HMASK]; scan; scan = scan->hnext) { 438 if (scan->hv == ip->hv && strcmp(scan->ips, ip->ips) == 0) { 439 int dt = (int)(t - ip->t); 440 if (dt < 60 * 60) { 441 ++found; 442 if (found > SSHLIMIT) 443 break; 444 } 445 } 446 } 447 return (found > SSHLIMIT); 448 } 449 450 /* 451 * Delete an ip record. Note that we always delete from the head of the 452 * list, but we will still wind up scanning hash chains. 453 */ 454 static void 455 delete_iph(iphist_t *ip) 456 { 457 iphist_t **scanp; 458 iphist_t *scan; 459 460 scanp = &hist_base; 461 while ((scan = *scanp) != ip) { 462 scanp = &scan->next; 463 } 464 *scanp = ip->next; 465 if (hist_tail == &ip->next) 466 hist_tail = scanp; 467 468 scanp = &hist_hash[ip->hv & HMASK]; 469 while ((scan = *scanp) != ip) { 470 scanp = &scan->hnext; 471 } 472 *scanp = ip->hnext; 473 474 --hist_count; 475 free(ip); 476 } 477 478 static void 479 init_iphist(void) 480 { 481 hist_base = NULL; 482 hist_tail = &hist_base; 483 for (int i = 0; i < HSIZE; i++) { 484 hist_hash[i] = NULL; 485 } 486 hist_count = 0; 487 } 488