diff options
author | stww <securethewebwith@priveasy.de> | 2011-10-31 17:23:41 (GMT) |
---|---|---|
committer | stww <securethewebwith@priveasy.de> | 2011-10-31 17:23:41 (GMT) |
commit | 635b2bb4009a6f9f66ffbed9b5d94173f6ac029b (patch) | |
tree | 61b07578ba272d32a7db298262480fa54ed924a9 |
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | README | 60 | ||||
-rw-r--r-- | forcessl.c | 253 | ||||
-rw-r--r-- | main.c | 177 |
4 files changed, 497 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3d01676 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +CFLAGS=-std=gnu99 -pedantic -Wall -Os + +all: forcessl + + +clean: + -rm forcessl *~ @@ -0,0 +1,60 @@ +Redirect a client from http://domain/uri to https://domain/uri without +transmitting a single byte of the clients request over the network +(except the HTTP method). URI, Cookies, User-Agent, Host, ... are not +transmitted, if a user accidentally visits your website over +unsecured HTTP. + +Approach: +The server's receive window is set to 4 byte during TCP handshake and +no data (beyond the handshake) is ever acknowledged. Before the client +tries to send its request the server already unconditionally pushes a +redirecting response. To implement this deviation in the handshake, +the server is implemented using packet sockets and SOCK_RAW. + +Redirection: +Three schemes are supported. 1 and 2 are used in combination. +1. If the client allows Javascript, replace ^http with https in + location.href +2. Use refresh-after meta tag to load https://newdomain/forcessl_nojs + and use referrer analysis (if possible) to detect from which URI + the user came +3. Use a HTTP 301 redirect to https://newdomain/forcessl_nojs + +In case 2 and 3 newdomain is either a command line specified domain or +the IP address of the server. + +Usage: +You have to prevent your machine to answer on port 80 using iptables +as sslforce operates outside the linux TCP/IP stack: +iptables -A INPUT -p tcp --dport 80 -j DROP + +Then you can start forcessl, e.g. +forcessl -i eth0 -h yourdomain.com + +Full command line spec: +forcessl -i interface [-3|-j] [-p port] [-h target-host] + -i interface to listen on + -3 use HTTP 301 reponses for redirection + -j use Javascript with Meta-Refresh as fallback for redirection (default) + -p port to listen (default: 80) + -h hostname of the redirection target; if unspecified the request destination IP is used + + +Caveats: +- only method 3 (redirection via 301) works with non-standard HTTP + clients (e.g. spiders) +- violates HTTP protocol by sending unconditional status codes + (especially when the user submitted something different than a plain + get) +- right now allows DoS multiplication due to a lack of randomness in + the initial sequence number (easily fixable) +- relies on the client to not check that all of its request have left + the client OS buffers (otherwise the client stalls) +- needs root-access even for non-privileged ports +- replies to all HTTP requests of the network, if the interface is in + promiscuous mode + +TODO: +- set up/remove iptable DROP rule on startup/termination +- implement example landing page +- prevent abuse of the server as DoS bandwidth multiplication diff --git a/forcessl.c b/forcessl.c new file mode 100644 index 0000000..3360397 --- /dev/null +++ b/forcessl.c @@ -0,0 +1,253 @@ +/* TODO: act proper if interface is promisc (only reply to packets + send to local host */ + +#include <arpa/inet.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <malloc.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <netpacket/packet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#define BLEN 512 + +const char *payloads[] = { + // redirect using javascript with Meta-Refresh as fallback (works + // only with browsers, sends dangerous HTTP 200 independent of the + // request): + "HTTP/1.0 200 OK\r\n" + "Refresh: 1; url=https://%s/forcessl_nojs\r\n" + "Content-type: text/html\r\n" + "\r\n" + "<html><body>" + "<script language=\"Javascript\">" + "location.href = location.href.replace(/^http/i, \"https\");" + "</script>" + "enabling crypto..." + "</body></html>", + + // redirect using 301 (closer to standart, friendly to non-browser + // HTTP clients): + + "HTTP/1.0 301 Not enough crypto to process request\r\n" + "Location: https://%s/forcessl_nojs\r\n" + "Content-type: text/html" + "\r\n" + "<html><body>" + "enabling crypto..." + "</body></html>" }; + +#define AP0(x) if (!(x)) { perror(NULL); exit(EXIT_FAILURE); } +#define APN(x) if ((x) < 0) { perror(NULL); exit(EXIT_FAILURE); } + +unsigned chksum(unsigned short *buf, int length) { + unsigned sum = 0; + for (; length > 1; length -= 2) + sum += *buf++; + return sum; +} + +unsigned short ipchksum(struct iphdr *ip) { + unsigned sum = chksum((unsigned short *) ip, sizeof(struct iphdr)); + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + return ~sum; +} + +unsigned short tcpchksum(struct iphdr *ip, struct tcphdr *tcp, int length) { + unsigned sum = + chksum((unsigned short*) &(ip->saddr), sizeof(ip->saddr)) + + chksum((unsigned short*) &(ip->daddr), sizeof(ip->daddr)) + + ((length + ip->protocol) << 8) + + chksum((unsigned short*) tcp, length); + if (length % 2) + sum += *(((char*) tcp) + length - 1); + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + return ~sum; +} + +void printopts(char **argv) { + printf("Usage: %s -i interface [-3|-j] [-p port] [-h target-host]\n" + "\t-i interface to listen on\n" + "\t-3 use HTTP 301 reponses for redirection\n" + "\t-j use Javascript with Meta-Refresh as fallback for redirection (default)\n" + "\t-p port to listen (default: 80)\n" + "\t-h hostname of the redirection target; if unspecified the request destination IP is used\n", + argv[0]); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) { + // parse cmd line args + int opt, + port = htons(80); + char *target_host = NULL, + *output_device = NULL; + const char *raw_payload = payloads[0]; + while ((opt = getopt(argc, argv, "3jp:h:i:")) != -1) { + switch (opt) { + case 'p': + port = htons(atoi(optarg)); + break; + case 'h': + AP0(target_host = strdup(optarg)); + break; + case 'i': + AP0(output_device = strdup(optarg)); + break; + case '3': + raw_payload = payloads[1]; + break; + case 'j': + raw_payload = payloads[0]; + break; + default: + printopts(argv); + } + } + if (!output_device) + printopts(argv); + + // prepare input buffer + char *ibuf = malloc(BLEN); + AP0(ibuf); + struct ethhdr *i_eth = (struct ethhdr*) ibuf; + struct iphdr *i_ip = (struct iphdr*) (i_eth + 1); + struct tcphdr *i_tcp = (struct tcphdr*) (i_ip + 1); + //char *i_pld = (char*) (i_tcp + 1); + + // prepare output buffer + const size_t os = + sizeof(struct ethhdr) + + sizeof(struct iphdr) + + sizeof(struct tcphdr) + + strlen(raw_payload) - 1 + + (target_host ? strlen(target_host) : 15); + char *obuf = malloc(os); + AP0(obuf); + struct ethhdr *o_eth = (struct ethhdr*) obuf; + struct iphdr *o_ip = (struct iphdr*) (o_eth + 1); + struct tcphdr *o_tcp = (struct tcphdr*) (o_ip + 1); + char *o_pld = (char*) (o_tcp + 1); + + if (os - sizeof(struct ethhdr) > 512) + fprintf(stderr, "warning: resulting packet size %ld exceeds MTU 512\n", + os - sizeof(struct ethhdr)); + + // setup sock + int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + APN(fd); + + // retrieve interface idx (for send()) + int ifidx; + { + struct ifreq req; + strcpy((char*) &(req.ifr_name), output_device); + APN(ioctl(fd, SIOCGIFINDEX, &req)); + ifidx = req.ifr_ifindex; + } + + // bind to interface + struct sockaddr_ll sall; + sall.sll_family = AF_PACKET; + sall.sll_ifindex = ifidx; + sall.sll_halen = 6; + memcpy(&(sall.sll_addr), &(o_eth->h_dest), sall.sll_halen); + sall.sll_protocol = sall.sll_hatype = sall.sll_pkttype = 0; + APN(bind(fd, (struct sockaddr*) &sall, sizeof(sall))); + + // prefill static output + o_eth->h_proto = htons(ETH_P_IP); + + o_ip->ihl = 5; + o_ip->version = 4; + o_ip->tos = 0; + o_ip->id = 0; + o_ip->frag_off = 0; + o_ip->ttl = 255; + o_ip->protocol = 6; + + o_tcp->source = port; + o_tcp->res1 = 0; + o_tcp->doff = 5; + o_tcp->ack = 1; + o_tcp->rst = 0; + o_tcp->psh = 1; + o_tcp->urg = 0; + o_tcp->ece = 0; + o_tcp->cwr = 0; + o_tcp->urg_ptr = 0; + o_tcp->window = htons(4); // first 4 byte are harmless (and we won't ack them) + + if (target_host) + snprintf(o_pld, strlen(raw_payload) + strlen(target_host) - 1, + raw_payload, target_host); + + + // read packets forever + while (1) { + int is = recv(fd, ibuf, BLEN, 0); + APN(is); + if ((is >= sizeof(struct ethhdr) + sizeof(struct iphdr) + + sizeof(struct tcphdr)) && // packet long enough? + (i_eth->h_proto == htons(ETH_P_IP)) && // ip protocol? + (i_ip->ihl == 5) && // no IP options + (i_ip->protocol == 6) && // tcp protocol? + (i_tcp->dest == port)) { // port 80? + int size = 0; + + // eth & IP are common for all responses + memcpy(&(o_eth->h_dest), &(i_eth->h_source), ETH_ALEN); + memcpy(&(o_eth->h_source), &(i_eth->h_dest), ETH_ALEN); + + o_ip->check = 0; + memcpy(&(o_ip->saddr), &(i_ip->daddr), sizeof(o_ip->saddr)); + memcpy(&(o_ip->daddr), &(i_ip->saddr), sizeof(o_ip->daddr)); + + o_tcp->dest = i_tcp->source; + o_tcp->check = 0; + + if (i_tcp->syn && !i_tcp->ack) { // first packet of a handshake? + size = os - strlen(o_pld); + o_tcp->seq = i_tcp->seq; + o_tcp->ack_seq = htonl(ntohl(i_tcp->seq) + 1); + o_tcp->fin = 0; + o_tcp->syn = 1; + }else if (i_tcp->ack) { + size = os; + o_tcp->seq = htonl(ntohl(i_tcp->ack_seq)); + o_tcp->ack_seq = htonl(ntohl(i_tcp->ack_seq)); + o_tcp->fin = 1; + o_tcp->syn = 0; + + if (!target_host) { + struct in_addr ba; + ba.s_addr = i_ip->daddr; + char *sa = inet_ntoa(ba); + snprintf(o_pld, os - (o_pld - (char*) o_eth), + raw_payload, sa); + size = size - 15 + strlen(sa); + } + } + + if (size > 0) { + o_ip->tot_len = htons(size - sizeof(struct ethhdr)); + o_ip->check = ipchksum(o_ip); + o_tcp->check = tcpchksum(o_ip, o_tcp, size - sizeof(struct ethhdr) - sizeof(struct iphdr)); + if (sendto(fd, o_eth, size, 0, (struct sockaddr*) &sall, sizeof(struct sockaddr_ll)) != size) { + perror(NULL); + exit(EXIT_FAILURE); + } + } + } + } +} @@ -0,0 +1,177 @@ +/* TODO: act proper if interface is promisc (only reply to packets + send to local host */ + +#include <assert.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <malloc.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <netpacket/packet.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/types.h> + +#define BLEN 16384 +#define PORT 80 + +const char * raw_payload = + "HTTP/1.0 301 Not enough crypto to process request\r\n" + "Location: %s\r\n" + "Connection: close\r\n" + "\r\n" + "<html><body>bye bye</body></html>"; + +unsigned chksum(unsigned short *buf, int length) { + unsigned sum = 0; + for (; length > 1; length -= 2) + sum += *buf++; + return sum; +} + +unsigned short ipchksum(struct iphdr *ip) { + unsigned sum = chksum((unsigned short *) ip, sizeof(struct iphdr)); + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + return ~sum; +} + +unsigned short tcpchksum(struct iphdr *ip, struct tcphdr *tcp, int length) { + unsigned sum = + chksum((unsigned short*) &(ip->saddr), sizeof(ip->saddr)) + + chksum((unsigned short*) &(ip->daddr), sizeof(ip->daddr)) + + ((length + ip->protocol) << 8) + + chksum((unsigned short*) tcp, length); + if (length % 2) + sum += *(((char*) tcp) + length - 1); + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + return ~sum; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Usage: %s target-url\n", argv[0]); + return 1; + } + + static unsigned ack_src; + /* prepare input buffer */ + char *ibuf = malloc(BLEN); + assert(ibuf != NULL); + struct ethhdr *i_eth = (struct ethhdr*) ibuf; + struct iphdr *i_ip = (struct iphdr*) (i_eth + 1); + struct tcphdr *i_tcp = (struct tcphdr*) (i_ip + 1); + char *i_pld = (char*) (i_tcp + 1); + + /* prepare output buffer */ + const size_t os = + sizeof(struct ethhdr) + + sizeof(struct iphdr) + + sizeof(struct tcphdr) + + strlen(raw_payload) + strlen(argv[1]) - 1; + char *obuf = malloc(os); + assert(obuf != NULL); + struct ethhdr *o_eth = (struct ethhdr*) obuf; + struct iphdr *o_ip = (struct iphdr*) (o_eth + 1); + struct tcphdr *o_tcp = (struct tcphdr*) (o_ip + 1); + char *o_pld = (char*) (o_tcp + 1); + + /* setup sock */ + int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + assert(fd >= 0); + + /* retrieve interface idx (for send()) */ + int ifidx; + { + struct ifreq req; + const char *if_name = "eth0"; + strcpy((char*) &(req.ifr_name), if_name); + assert(ioctl(fd, SIOCGIFINDEX, &req) >= 0); + ifidx = req.ifr_ifindex; + printf("device index of %s is %d\n", if_name, ifidx); + } + + /* prefill static output */ + o_eth->h_proto = htons(ETH_P_IP); + + o_ip->ihl = 5; + o_ip->version = 4; + o_ip->tos = 0; + o_ip->id = 0; + o_ip->frag_off = 0; + o_ip->ttl = 255; + o_ip->protocol = 6; + + o_tcp->source = htons(PORT); + o_tcp->res1 = 0; + o_tcp->doff = 5; + o_tcp->ack = 1; + o_tcp->rst = 0; + o_tcp->psh = 1; + o_tcp->urg = 0; + o_tcp->ece = 0; + o_tcp->cwr = 0; + o_tcp->urg_ptr = 0; + o_tcp->window = htons(4); /* first 4 byte are harmless (and we won't + ack them) */ + + snprintf(o_pld, strlen(raw_payload) + strlen(argv[1]) - 1, raw_payload, argv[1]); + + struct sockaddr_ll sall; + sall.sll_family = AF_PACKET; + sall.sll_ifindex = ifidx; + sall.sll_halen = 6; + memcpy(&(sall.sll_addr), &(o_eth->h_dest), sall.sll_halen); + sall.sll_protocol = sall.sll_hatype = sall.sll_pkttype = 0; + + /* read packets forever */ + while (1) { + int is = recv(fd, ibuf, BLEN, 0); + if ((is >= sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr)) && // packet long enough? + (ntohs(i_eth->h_proto) == ETH_P_IP) && // ip protocol? + (i_ip->ihl == 5) && // no IP options + (i_ip->protocol == 6) && // tcp protocol? + (ntohs(i_tcp->dest) == PORT)) { // port 80? + int size = 0; + + /* eth & IP are common for all responses */ + memcpy(&(o_eth->h_dest), &(i_eth->h_source), ETH_ALEN); + memcpy(&(o_eth->h_source), &(i_eth->h_dest), ETH_ALEN); + + o_ip->check = 0; + memcpy(&(o_ip->saddr), &(i_ip->daddr), sizeof(o_ip->saddr)); + memcpy(&(o_ip->daddr), &(i_ip->saddr), sizeof(o_ip->daddr)); + + o_tcp->dest = i_tcp->source; + o_tcp->check = 0; + + if (i_tcp->syn && !i_tcp->ack) { /* first packet of a handshake? */ + printf("syn\n"); + size = os - strlen(o_pld); + o_tcp->seq = i_tcp->seq; + o_tcp->ack_seq = htonl(ntohl(i_tcp->seq) + 1); + o_tcp->fin = 0; //1; + o_tcp->syn = 1; + }else if (i_tcp->ack) { + printf("ack\n"); + size = os; + o_tcp->seq = htonl(ntohl(i_tcp->ack_seq)); + o_tcp->ack_seq = htonl(ntohl(i_tcp->ack_seq)); + o_tcp->fin = 1; + o_tcp->syn = 0; + } + + if (size > 0) { + o_ip->tot_len = htons(size - sizeof(struct ethhdr)); + o_ip->check = ipchksum(o_ip); + o_tcp->check = tcpchksum(o_ip, o_tcp, size - sizeof(struct ethhdr) - sizeof(struct iphdr)); + assert(sendto(fd, o_eth, size, 0, (struct sockaddr*) &sall, sizeof(struct sockaddr_ll)) == size); + } + } + } + close(fd); +} |