[PATCH] More secure SYSRQ for xtables-addons

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello All,

This is a patch to the SYSRQ xtables-addon that is, I believe, secure enough to use in moderately untrustworthy environments. I'm relatively new to posting patches so please forgive me if I've messed this up.

Rationale:

I want to be able to use SYSRQ to reboot, crash or partially diagnose machines that become unresponsive for one reason or another. These machines, typically, are blades or rack mounted machines that do not have a PS/2 connection for a keyboard and the old method of wheeling round a "crash trolley" that has a monitor and a keyboard on it no longer works: USB keyboards rarely, if ever, work because by the time the machine is responding only to a ping, udev is incapable of setting up a new keyboard.

This patch extends the xt_SYSRQ module to avoid both disclosing the sysrq password and preventing replay. This is done by changing the request packet from the simple "<key><password>" to a slightly more complex "<key>,<seqno>,<salt>,<hash>". The hash is the sha1 checksum of "<key>,<seqno>,<salt>,<password>". A request can be constructed in a small shell script, for example:

   key="s"
   password="cookie"
   seqno=$(date +%s)
salt="$(dd bs=12 count=1 if=/dev/urandom 2>/dev/null | openssl enc -base64)"
   req="$key,$seqno,$salt"
   req="$req,$(echo -n "$req,$password" | sha1sum | cut -c 1-40)"

As before this can be sent to the victim machine using socat or netcat.

Verification of the hash in xt_SYSRQ follows much the same process. The sequence number, seqno, is initialised to the current time (in seconds) when the xt_SYSRQ module is loaded and is updated each time a valid request is received. A request with a sequence number less than the current sequence number or a wrong hash is silently ignored. (Using the time for the sequence number assumes (requires) that time doesn't go backwards on a reboot and that the requester and victim have reasonably synchronized clocks.)

The random salt is there to prevent pre-computed dictionary attacks difficult: dictionary attacks are still feasible if you capture a packet because the hash is computed quickly -- taking perhaps several milliseconds to compute a more complex hash in xt_SYSRQ when the machine is unresponsive is probably not the best thing you could do. However, cracking, say, a random 32 character password would take some time and is probably beyond what the people in the target untrustworthy environment are prepared to do or have the resources for. It almost goes without saying that no two victim machines should use the same password.

Ideally the iptables rules for SYSRQ would include a minimum time between requests to avoid the worst effects of sysrq-request bombing, although I haven't mentioned that in the updated man page (mostly because I'm not sure how to do it).

Finally, the module allocates all the resources it need at module initialisation time on the assumption that if things are going badly resource allocation is going to be troublesome.

jch


diff -up xtables-addons-1.6/extensions/libxt_SYSRQ.man.sysrq xtables-addons-1.6/extensions/libxt_SYSRQ.man
--- xtables-addons-1.6/extensions/libxt_SYSRQ.man.sysrq	2008-11-18 17:16:34.000000000 +0000
+++ xtables-addons-1.6/extensions/libxt_SYSRQ.man	2008-11-27 11:02:12.000000000 +0000
@@ -5,13 +5,15 @@ stuck as a result -- if still possible, processes are stuck, interrupts are likely to be still processed, and as such,
sysrq can be triggered through incoming network packets.
.PP
-This xt_SYSRQ implementation does not use any encryption, so you should change
-the SYSRQ password after use unless you have made sure it was transmitted
-securely and no one sniffed the network, e.g. by use of an IPsec tunnel whose
-endpoint is at the machine where you want to trigger the sysrq. Also, you
-should limit as to who can issue commands using \fB-s\fP and/or \fB-m mac\fP,
-and also that the destination is correct using \fB-d\fP (to protect against
-potential broadcast packets), noting that it is still short of MAC/IP spoofing:
+The xt_SYSRQ implementation uses a salted SHA1 hash and a sequence number to
+prevent network sniffers from either guessing the password or replaying earlier
+requests.  The initial sequence number comes from the time of day so you will
+have a small window of vulnerability should time go backwards at a reboot.
+However, the file /sys/module/xt_SYSREQ/seqno can be used to both query and
+update the current sequence number.  Also, you should limit as to who can issue
+commands using \fB-s\fP and/or \fB-m mac\fP, and also that the destination is
+correct using \fB-d\fP (to protect against potential broadcast packets), noting
+that it is still short of MAC/IP spoofing:
.IP
-A INPUT -s 10.10.25.1 -m mac --mac-source aa:bb:cc:dd:ee:ff -d 10.10.25.7
-p udp --dport 9 -j SYSRQ
@@ -24,10 +26,9 @@ This extension does not take any options
required.
.PP
The SYSRQ password can be changed through
-/sys/module/xt_SYSRQ/parameters/password; note you need to use `echo -n` to
-not add a newline to the password, i.e.
+/sys/module/xt_SYSRQ/parameters/password, for example:
.IP
-echo -n "password" >/sys/.../password
+echo "password" >/sys/module/xt_SYSRQ/parameters/password
.PP
Alternatively, the password may be specified at modprobe time, but this is
insecure as people can possible see it through ps(1). You can use an option
@@ -36,12 +37,36 @@ by root.
.IP
options xt_SYSRQ password=cookies
.PP
-To trigger SYSRQ from a remote host, just use netcat or socat, specifying the
-action (only one) as first character, followed by the password:
-.IP
-echo -n "scookies" | socat stdin udp-sendto:10.10.25.7:9
-.IP
-echo -n "scookies" | netcat -u 10.10.25.7 9
-.PP
-See the Linux docs for possible sysrq keys. Important ones are:
-re(b)oot, power(o)ff, (s)ync filesystems, (u)mount and remount readonly.
+The xt_SYSRQ module is normally silent unless a successful request is received,
+but the \fIdebug\fP module parameter can be used to find exactly why a
+seemingly correct request is not being processed.
+.PP
+To trigger SYSRQ from a remote host, just use netcat or socat:
+.RS
+.nf
+
+sysrq_key="s"  # the SysRq key
+password="password"
+seqno="$(date +%s)"
+salt="$(dd bs=12 count=1 if=/dev/urandom 2>/dev/null |
+    openssl enc -base64)"
+req="$sysrq_key,$seqno,$salt"
+req="$req,$(echo -n "$req,$password" | sha1sum | cut -c1-40)"
+
+echo "$req" | socat stdin udp-sendto:10.10.25.7:9
+# or
+echo "$req" | netcat -uw1 10.10.25.7 9
+
+.fi
+.RE
+.PP
+See the Linux docs for possible sysrq keys. Important ones are: re(b)oot,
+power(o)ff, (s)ync filesystems, (u)mount and remount readonly.  More than one
+sysrq key can be used at once, but bear in mind that, for example, a sync may
+not complete before a subsequent reboot or poweroff.
+.PP
+The hashing scheme should be enough to prevent mis-use of SYSRQ in many
+environments, but it is not perfect: take reasonable precautions to protect
+your machines.  Most importantly ensure that each machine has a different
+password: there is scant protection for a SYSRQ packet being applied to a
+machine that happens to have the same password.
diff -up xtables-addons-1.6/extensions/xt_SYSRQ.c.sysrq xtables-addons-1.6/extensions/xt_SYSRQ.c
--- xtables-addons-1.6/extensions/xt_SYSRQ.c.sysrq	2008-11-18 17:16:34.000000000 +0000
+++ xtables-addons-1.6/extensions/xt_SYSRQ.c	2008-11-27 11:15:34.000000000 +0000
@@ -3,7 +3,6 @@
 *	Copyright © Jan Engelhardt <jengelh [at] medozas de>, 2008
 *
 *	Based upon the ipt_SYSRQ idea by Marek Zalem <marek [at] terminus sk>
- *	xt_SYSRQ does not use hashing or timestamps.
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
@@ -19,43 +18,137 @@
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter/x_tables.h>
+#include <linux/crypto.h>
+#include <linux/scatterlist.h>
#include <net/ip.h>
#include "compat_xtables.h"

static bool sysrq_once;
static char sysrq_password[64];
+static long seqno;
+static int debug;
module_param_string(password, sysrq_password, sizeof(sysrq_password),
-	S_IRUSR | S_IWUSR);
+		S_IRUSR | S_IWUSR);
+module_param(seqno, long, S_IRUSR | S_IWUSR);
+module_param(debug, int, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(password, "password for remote sysrq");
+MODULE_PARM_DESC(seqno, "sequence number for remote sysrq");
+MODULE_PARM_DESC(debug, "debugging: 0=off, 1=on");

+
+static struct crypto_hash *tfm;
+static int digestsize;
+static unsigned char *digest_password;
+static unsigned char *sha1_digest;
+static char *sha1_hexdigest;
+
+/*
+ * The data is of the form "<requests>,<seqno>,<salt>,<hash>" where
+ * <requests> is a series of sysrq requests; <seqno> is a sequence number
+ * which must be greater than the last sequence number; <salt> is some
+ * random bytes; and <hash> is the sha1 hash of everything up to and
+ * including the preceding "/" together with the password.
+ *
+ * For example
+ *
+ *   salt=$RANDOM
+ *   req="s,$(date +%s),$salt"
+ *   echo "$req,$(echo -n $req,secret | sha1sum | cut -c1-40)"
+ *
+ * You probably want a better salt and password than that though :-)
+ */
static unsigned int sysrq_tg(const void *pdata, uint16_t len)
{
	const char *data = pdata;
-	char c;
+	int i, n;
+	struct scatterlist sg[2];
+	struct hash_desc desc;
+	int ret;
+	long new_seqno = 0;

	if (*sysrq_password == '\0') {
		if (!sysrq_once)
-			printk(KERN_INFO KBUILD_MODNAME "No password set\n");
+			printk(KERN_INFO KBUILD_MODNAME ": No password set\n");
		sysrq_once = true;
		return NF_DROP;
	}
-
	if (len == 0)
		return NF_DROP;

-	c = *data;
-	if (strncmp(&data[1], sysrq_password, len - 1) != 0) {
-		printk(KERN_INFO KBUILD_MODNAME "Failed attempt - "
-		       "password mismatch\n");
+	for (i = 0; sysrq_password[i]; i++)
+		if (sysrq_password[i] == '\n')
+			break;
+	sysrq_password[i] = '\0';
+
+	i = 0;
+	for (n = 0; n < len - 1; n++) {
+		if (i == 1 && '0' <= data[n] && data[n] <= '9')
+			new_seqno = 10L * new_seqno + data[n] - '0';
+		if (data[n] == ',' && ++i == 3)
+			break;
+	}
+	n++;
+	if (i != 3) {
+		if (debug)
+			printk(KERN_WARNING KBUILD_MODNAME
+				": badly formatted request\n");
+		return NF_DROP;
+	}
+	if (seqno >= new_seqno) {
+		if (debug)
+			printk(KERN_WARNING KBUILD_MODNAME
+				": old sequence number ignored\n");
+		return NF_DROP;
+	}
+
+	desc.tfm = tfm;
+	desc.flags = 0;
+	ret = crypto_hash_init(&desc);
+	if (ret)
+		goto hash_fail;
+	sg_init_table(sg, 2);
+	sg_set_buf(&sg[0], data, n);
+	strcpy(digest_password, sysrq_password);
+	i = strlen(digest_password);
+	sg_set_buf(&sg[1], digest_password, i);
+	ret = crypto_hash_digest(&desc, sg, n + i, sha1_digest);
+	if (ret)
+		goto hash_fail;
+
+	for (i = 0; i < digestsize; i++) {
+		sha1_hexdigest[2 * i] =
+			"0123456789abcdef"[(sha1_digest[i] >> 4) & 0xf];
+		sha1_hexdigest[2 * i + 1] =
+			"0123456789abcdef"[sha1_digest[i] & 0xf];
+	}
+	sha1_hexdigest[2 * digestsize] = '\0';
+	if (len - n < digestsize) {
+		if (debug)
+			printk(KERN_INFO KBUILD_MODNAME ": Short digest,"
+				" expected %s\n", sha1_hexdigest);
+		return NF_DROP;
+	}
+	if (strncmp(data + n, sha1_hexdigest, digestsize) != 0) {
+		if (debug)
+			printk(KERN_INFO KBUILD_MODNAME ": Bad digest,"
+				" expected %s\n", sha1_hexdigest);
		return NF_DROP;
	}

+	/* Now we trust the requester */
+	seqno = new_seqno;
+	for (i = 0; i < len && data[i] != ','; i++) {
+		printk(KERN_INFO KBUILD_MODNAME ": SysRq %c\n", data[i]);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
-	handle_sysrq(c, NULL);
+		handle_sysrq(data[i], NULL);
#else
-	handle_sysrq(c, NULL, NULL);
+		handle_sysrq(data[i], NULL, NULL);
#endif
+	}
	return NF_ACCEPT;
+hash_fail:
+	printk(KERN_WARNING KBUILD_MODNAME ": digest failure\n");
+	return NF_DROP;
}

static unsigned int
@@ -73,9 +166,11 @@ sysrq_tg4(struct sk_buff **pskb, const s
	udph = (void *)iph + ip_hdrlen(skb);
	len  = ntohs(udph->len) - sizeof(struct udphdr);

-	printk(KERN_INFO KBUILD_MODNAME ": " NIPQUAD_FMT ":%u -> :%u len=%u\n",
-	       NIPQUAD(iph->saddr), htons(udph->source), htons(udph->dest),
-	       len);
+	if (debug)
+		printk(KERN_INFO KBUILD_MODNAME
+			": " NIPQUAD_FMT ":%u -> :%u len=%u\n",
+			NIPQUAD(iph->saddr), htons(udph->source),
+			htons(udph->dest), len);
	return sysrq_tg((void *)udph + sizeof(struct udphdr), len);
}

@@ -94,9 +189,11 @@ sysrq_tg6(struct sk_buff **pskb, const s
	udph = udp_hdr(skb);
	len  = ntohs(udph->len) - sizeof(struct udphdr);

-	printk(KERN_INFO KBUILD_MODNAME ": " NIP6_FMT ":%hu -> :%hu len=%u\n",
-	       NIP6(iph->saddr), ntohs(udph->source),
-	       ntohs(udph->dest), len);
+	if (debug)
+		printk(KERN_INFO KBUILD_MODNAME
+			": " NIP6_FMT ":%hu -> :%hu len=%u\n",
+			NIP6(iph->saddr), ntohs(udph->source),
+			ntohs(udph->dest), len);
	return sysrq_tg(udph + sizeof(struct udphdr), len);
}

@@ -146,11 +243,54 @@ static struct xt_target sysrq_tg_reg[] _

static int __init sysrq_tg_init(void)
{
+	struct timeval now;
+	tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		printk(KERN_WARNING KBUILD_MODNAME
+			": Error: Could not find or load sha1 hash\n");
+		tfm = NULL;
+		goto fail;
+	}
+	digestsize = crypto_hash_digestsize(tfm);
+	sha1_digest = kmalloc(digestsize, GFP_KERNEL);
+	if (!sha1_digest) {
+		printk(KERN_WARNING KBUILD_MODNAME
+			": Cannot allocate sha1_digest\n");
+		goto fail;
+	}
+	sha1_hexdigest = kmalloc(2 * digestsize + 1, GFP_KERNEL);
+	if (!sha1_hexdigest) {
+		printk(KERN_WARNING KBUILD_MODNAME
+			": Cannot allocate sha1_hexdigest\n");
+		goto fail;
+	}
+	digest_password = kmalloc(sizeof(sysrq_password), GFP_KERNEL);
+	if (!digest_password) {
+		printk(KERN_WARNING KBUILD_MODNAME
+			": Cannot allocate password digest space\n");
+		goto fail;
+	}
+	do_gettimeofday(&now);
+	seqno = now.tv_sec;
	return xt_register_targets(sysrq_tg_reg, ARRAY_SIZE(sysrq_tg_reg));
+fail:
+	if (tfm)
+		crypto_free_hash(tfm);
+	if (sha1_digest)
+		kfree(sha1_digest);
+	if (sha1_hexdigest)
+		kfree(sha1_hexdigest);
+	if (digest_password)
+		kfree(digest_password);
+	return -EINVAL;
}

static void __exit sysrq_tg_exit(void)
{
+	crypto_free_hash(tfm);
+	kfree(sha1_digest);
+	kfree(sha1_hexdigest);
+	kfree(digest_password);
	return xt_unregister_targets(sysrq_tg_reg, ARRAY_SIZE(sysrq_tg_reg));
}



--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Netfitler Users]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux