TCP的syn-cookie以及SHA1摘要算法的实现

晨曦之光 发布于 2012/04/10 15:00
阅读 420
收藏 0

linux内核中实现了syn-cookie,可以有效阻止syn-flood攻击,syn-cookie理论上很简单,就是在服务器接收到客户端的syn包时并不分配任何内存空间,而是巧妙的选择服务器的isn值传给客户端,isn本地也并不保存(本地不保存任何东西),然后客户端发来synack的ack确认包后,从该确认包中取出确认号,然后利用确认号是上一次序列号加1的特性来得到原来的服务器发给客户端的synack包的序列号,然后用相同的源,目的地址/端口信息用相同的算法计算一个数和得到的原始序列号比较,如果一样就说明连接合法,否则不合法,在传输中使用超时机制。linux的实现大体上和理论的类似,只是使用sha1算法得到的isn中保存了mss值,这是个额外的收获,另外还限制了两次握手之间的最短时限:

__u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)

{

struct tcp_sock *tp = tcp_sk(sk);

int mssind;

const __u16 mss = *mssp;

tp->last_synq_overflow = jiffies;

for (mssind = 0; mss > msstab[mssind + 1]; mssind++);

*mssp = msstab[mssind] + 1;

NET_INC_STATS_BH(LINUX_MIB_SYNCOOKIESSENT); //以下调用函数计算摘要,最终作为服务器端序列号发送

return secure_tcp_syn_cookie(skb->nh.iph->saddr, skb->nh.iph->daddr, skb->h.th->source, skb->h.th->dest,

ntohl(skb->h.th->seq), jiffies / (HZ * 60), mssind);

}

static __u32 secure_tcp_syn_cookie(__u32 saddr, __u32 daddr, __u16 sport, __u16 dport, __u32 sseq, __u32 count, __u32 data)

{

//count << COOKIEBITS将当前分钟保存到了整个结果的高8位,后面的将一个由当前分钟计算而来的值保存到了结果的低24位,这样分钟值和后面计算的值就不打架了,更加清晰

return (cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq + (count << COOKIEBITS) +

((cookie_hash(saddr, daddr, sport, dport, count, 1) + data) & COOKIEMASK));

}

static inline int cookie_check(struct sk_buff *skb, __u32 cookie)

{

__u32 seq;

__u32 mssind;

seq = ntohl(skb->h.th->seq)-1;

mssind = check_tcp_syn_cookie(cookie,

skb->nh.iph->saddr, skb->nh.iph->daddr,

skb->h.th->source, skb->h.th->dest,

seq, jiffies / (HZ * 60), COUNTER_TRIES);

return mssind < NUM_MSS ? msstab[mssind] + 1 : 0;

}

static __u32 check_tcp_syn_cookie(__u32 cookie, __u32 saddr, __u32 daddr, __u16 sport, __u16 dport, __u32 sseq, __u32 count, __u32 axdiff)

{

__u32 diff;

cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;

//经过上面计算以后,cookie剩下了高8位的那时的分钟数和由地址,端口,时间算出来的一个sha摘要值以及一个附加的mss值,以下的diff计算得到当前时间和通过cookie得到的时间的差值,cookie >> COOKIEBITS得到cookie的高8位,最后的一个&操作屏掉了不相关的高位,但是有一个问题,见下面。

diff = (count - (cookie >> COOKIEBITS)) & ((__u32) - 1 >> COOKIEBITS);

if (diff >= maxdiff) //这个maxdiff就是COUNTER_TRIES,该值是通过tcp的超时机制计算出来的

return (__u32)-1;

//通过以下的减法就得到了当初的mss值,diff肯定小于maxdiff,也就是4,那么count减去这个差值就是原来的时间,以分钟计时。

return (cookie - cookie_hash(saddr, daddr, sport, dport, count - diff, 1)) & COOKIEMASK;

}

以上的算法并不是十全十美的,问题出在diff的计算上,如果当前的count值和过去产生cookie时的count值差很大,比如现在的count是11111111111111111111111100000011,原来的是00000000000000000000000000000001,这样通过diff的公式计算得到2,完全符合,然后计算下面的cookie_hash,此时如果sha又碰撞了,那么就会得到错误的结果,但是即使出错也没有什么大问题,出错的几率毕竟很小很小,因此syn-flood攻击还是无法成功,再者说了,之所以这里的diff检查不严格,一个后面还有一个sha计算,第二是tcp本身就是超时机制,相隔那么长的时间超时早就过了,因此根本就不会有那样的包回来。另外即使不碰撞,万一碰到由于count或者别的addr或者port等改变而引起了sha摘要的改变,但是改变的很小,被cookie减去之后仍然是合法的范围内的值,这样也会通过验证,不过大可不必担心这样的情况,出错率很低,摘要离得很近的概率也很低,因此这样的验证是可信的,设计系统时千万不要为了万无一失而损失效率。

并不是只要设置了syn-cookie,每次有客户端连接都要进行验证,而是只有在一定情况下才开启,因为syn-cookie是很耗时并且违背tcp原则的,且看下面的代码段,这是在tcp服务端进行三次握手期间回复syn包时执行的代码段:

if (inet_csk_reqsk_queue_is_full(sk) && !isn) {

#ifdef CONFIG_SYN_COOKIES

if (sysctl_tcp_syncookies) {

want_cookie = 1;

} else

#endif

goto drop;

}

...

if (want_cookie) {

isn = cookie_v4_init_sequence(sk, skb, &req->mss);

}

只有在服务器的accept_queue满载时才会启用,可以看出懒惰启用对效率提升很有效,并且还可以有效防止syn-flood攻击,你可以随便攻击,这个linux不管,linux丝毫不会畏惧dos攻击,也不会提前采取任何行动,一切就像什么也没有发生一样,但是一旦攻击开始影响系统,这里就是接收队列已经爆满,linux就要应付了,启动应急预案,这里就是syn-cookie。

那么linux中是怎么实现syn-cookie的呢?其实是用sha1摘要算法计算的,接下来就看一下这个算法:

#define SHA_DIGEST_WORDS 5 //sha1的初始摘要一共5个32位空间

#define SHA_WORKSPACE_WORDS 80 //计算一个摘要需要80个32位的缓冲区空间

static __u32 syncookie_secret[2][16-3+SHA_DIGEST_WORDS]; //一共2*21*4字节的空间

static u32 cookie_hash(u32 saddr, u32 daddr, u32 sport, u32 dport, u32 count, int c)

{

__u32 tmp[16 + 5 + SHA_WORKSPACE_WORDS];

memcpy(tmp + 3, syncookie_secret[c], sizeof(syncookie_secret[c]));

tmp[0] = saddr;

tmp[1] = daddr;

tmp[2] = (sport << 16) + dport;

tmp[3] = count;

//从tmp的21偏移处开始填充sha的字空间,填充的内容是tmp的既有的前16个元素

//一共16+5+80个32位空间,前16个是既有数据空间,接着5个是sha输出缓冲区,注意这5个空间已经被填充了部分syncookie_secret,因为一个syncookie_secret元素的长度是18,并且从第三个tmp空间开始填充,故而一直延展到第21个空间,从第21个空间往后就成了sha计算缓冲区了。

sha_transform(tmp + 16, (__u8 *)tmp, tmp + 16 + 5);

return tmp[17]; //对于tcp的syn-cookie,只需要挑一个就可以了

}

sha算法核心,digest参数为摘要信息指针,其实就需要其前5个无符号32位数据,in是需要计算摘要的数据,最后的W是计算空间缓冲区,由参数digest和in以及动态计算被填充:

void sha_transform(__u32 *digest, const char *in, __u32 *W)

{

__u32 a, b, c, d, e, t, i;

for (i = 0; i < 16; i++) //W一共有80个32位空间,前16个空间就是数据数组的前16个元素

W[i] = be32_to_cpu(((const __be32 *)in)[i]);

for (i = 0; i < 64; i++) //从16个空间往后就需要用公式计算了,这就是W计算空间动态计算的部分

W[i+16] = rol32(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 1);

a = digest[0]; //这些操作是赋值操作,将数据缓冲区赋值给临时变量

b = digest[1]; //一共是5个32位缓冲区

c = digest[2]; //对于syn-cookie来讲,这个digest就是调用函数的tmp数组的从16下标开始的,已经被覆盖了syncookie_secret

d = digest[3];

e = digest[4]; //以下开始计算,分段计算

for (i = 0; i < 20; i++) {

t = f1(b, c, d) + K1 + rol32(a, 5) + e + W[i];

e = d; d = c; c = rol32(b, 30); b = a; a = t;

}

for (; i < 40; i ++) {

t = f2(b, c, d) + K2 + rol32(a, 5) + e + W[i];

e = d; d = c; c = rol32(b, 30); b = a; a = t;

}

for (; i < 60; i ++) {

t = f3(b, c, d) + K3 + rol32(a, 5) + e + W[i];

e = d; d = c; c = rol32(b, 30); b = a; a = t;

}

for (; i < 80; i ++) {

t = f2(b, c, d) + K4 + rol32(a, 5) + e + W[i];

e = d; d = c; c = rol32(b, 30); b = a; a = t;

}

digest[0] += a; //输出数据

digest[1] += b;

digest[2] += c;

digest[3] += d;

digest[4] += e;

}


原文链接:http://blog.csdn.net/dog250/article/details/5303708
加载中
返回顶部
顶部