summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--README60
-rw-r--r--forcessl.c253
-rw-r--r--main.c177
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 *~
diff --git a/README b/README
new file mode 100644
index 0000000..4e6a6c6
--- /dev/null
+++ b/README
@@ -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);
+ }
+ }
+ }
+ }
+}
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..89c23fc
--- /dev/null
+++ b/main.c
@@ -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);
+}
contact: Jan Huwald // Impressum