通过iptables的recent模块实现服务器的动态访问控制

2014-09-29 • 技术文章

模块介绍

recent模块可以看作iptables里面维护了一个地址列表,这个地址列表可以通过“–set”(将地址和时间戳添加进列表)、“–update”(刷新时间戳)、“–rcheck”(检查地址是否在列表)、“–remove”(删除)四种方法来修改列表,每次使用时只能选用一种。还可附带“–name”参数来指定列表的名字(默认为DEFAULT),“–rsource”、“–rdest”指示当前方法应用到数据包的源地址还是目的地址(默认是前者)。

recent语句都带有布尔型返回值,每次执行若结果为真,则会执行后续的语句,比如“-j ACCEPT”之类的。

利用ping命令实现SSH动态开放

1
2
3
4
5
6
7
8
9
10
11
12
# 当包走完INPUT链而没被拿走时就会丢弃掉
iptables -P INPUT DROP
# 接受本地
iptables -A INPUT -s 127.0.0.1/32 -j ACCEPT
# 表明已经建立成功的连接和与主机发送出去的包相关的数据包都接受,保证后续可以连接
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
# icmp类型8是ping包;指定包大小为128字节;recent用的列表名称为SSHOPEN,列表记录源地址。符合上述条件的数据包都接收。如果ping包内容为100字节,则加上IP头、ICMP头的28字节,总共128字节。
iptables -A INPUT -p icmp --icmp-type 8 -m length --length 128 -m recent --set --name SSHOPEN --rsource -j ACCEPT
# 接受一般的ping包
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
# 对连接ssh 22端口的连接进行处理,来源于SSHOPEN源地址列表并且在列表时间小于等于15秒的放行
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --rcheck --seconds 15 --name SSHOPEN --rsource -j ACCEPT

实现连接限制

1
2
3
# 1小时内只允许连接5次
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --rcheck --seconds 3600 --hitcount 5 -j DROP
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name SSHPOOL --set -j ACCEPT

参考资料

http://www.cszhi.com/20120510/iptables-modules-recent.html

TCP旁路干扰的特征与识别

2014-09-23 • 技术文章

一年前,某家ISP与学校合作,推出了10元半年的宽带。速度确实快一些,但该宽带运营商却通过TCP旁路干扰的方式嵌入广告甚至是攻击脚本来牟利。这种劫持方法在很多小ISP那里很常见,那么这种劫持是如何实现的又该如何检测呢?

原理

旁路干扰的原理,简而言之,就是抢先应答。当我们和某地址建立TCP连接时,发出[SYN]顺带一个seq=x(随机数),对方应答一个[SYN,ACK]顺带seq=y(也是随机)和ack=x+1,你再回复一个[ACK]顺带seq=x+1和ack=y+1,这时三次握手完成,连接被成功建立。但是假如在路由路径的中间某点有一台旁路设备,它先于真实的目标地址收到响应,然后伪造一个应答,那么这个应答会在真实目标地址的应答之前返回给你,这时你就收到了伪造的数据然后丢弃了真实的数据。

通过抓包,可以发现,TCP旁路干扰的方式按标志位特点来看一般有以下几种:RST干扰(比如就是不想让你访问某个地址,发现你访问了,抢先应答一个[RST]来重置连接)、SYN/ACK干扰破坏或欺骗握手、FIN干扰。第一种只是想中断你的连接,后两种却可以用来加广告搞攻击。

SYN/ACK干扰过程的假握手前奏上面已经说过了,当你GET请求时,返回一个HTTP 30x将请求引向另一个地址,标志位一般会是[FIN,PSH,ACK]。这种做法有可能出于好的目的,也可能是坏的目的。好就是小区缓存,当我们下载某些资源时尤其是大的文件时,有小区缓存会加速我们的下载。当然小区缓存也有盲目性,我曾经发现被劫持的某视频文件的地址和网站指向的CDN在同一IP段,可能就在一个机房里,白缓存了一遍;再有就是缓存有时候仅仅根据URL做判断有可能已经是脏数据。坏就是插广告,比如跳转到一个包含真正目的地址的iframe,父框架放广告,或者劫持了页面JS加广告。

FIN干扰就是替换掉你要访问的页面,主要是返回[FIN,ACK]标志位HTTP 200的一个iframe。

危害

一般而言是遇到广告有些心烦,不过有些ISP做事情没有底线,除了插广告还做攻击。我就曾遇到运营商劫持返回的JS里直接就是攻击脚本而不是什么普通的广告,直接和黑产对接了。iframe算是比较笨的方法了,劫持JS文件效果更好,一来这些静态文件具有稳定性可以将恶意脚本追加其后神不知鬼不觉,二来不用浏览器地址栏地址发生变化。例如很多用户喜欢浏览器记住密码,但是假如JS被劫持,劫持者在尾部加入JS使页面加载完成后将用户名和密码从input的value取出并作为参数发送给某个指定URL,那么用户的密码就失窃了,想想还是蛮可怕的。再有就是劫持插入JS去劫持路由器,之后便进入了流量劫持的范畴,想怎么搞就可以怎么搞了。

识别

腾讯安全应急响应中心有一篇博客说可以通过TTL值判别是否受到了劫持干扰,但这种方法是不太靠谱的。现在的网络架构很复杂,由于一些负载均衡、防火墙、加速设备的存在,有可能让TTL变得很怪,靠TTL来判断非常不合理。

但是如果我们根据其原理综合标志位、RTT、TTL等因素来判断,准确性会有很大提高。首先看标志位,共有6位:SYN(Synchronization)、ACK(Acknowledgment)、FIN(Finish)、RST(Reset)、PSH(Push)、URG(Urgent)。[SYN]、[SYN,ACK]、[ACK]通常用来三次握手建立连接,[FIN,ACK]、[ACK]通常用来四次握手结束连接,[RST,ACK]、[RST]通常用来立即结束连接。有一些组合通常被用来做坏事,[SYN,FIN]这个非常矛盾的家伙有时被用来做扫描,还有[SYN,FIN,RST]、[SYN,FIN,PSH]、[SYN,FIN,RST,PSH]基本上是用来攻击的,仅包含[FIN]一般用于端口扫描,还有什么标志位都没有的非法包。我们上面就遇到的几种标志位并不算特殊,很容易理解劫持者的意图,再有就是TTL差过大有可能是因为包不是来自同一机器,还是不同包的的RTT也可以发现一些端倪。我们可以将几次和某IP连接的一些信息存到LRU缓存中,下次连接时比较一下,就可以比较准确地判断是否是遭到了劫持了。

此外,还有更直截了当的办法就是判断收到的两次同seq的应答,前者一般就是劫持包,后者才是真正的包。

防护现状

据我所知TCP劫持的检测主要还是在商业的应用上,如很多IDS/IPS具备此类功能。感觉个人级安全上这块目前是空白,像什么360、瑞星等等安全软件都不会去管TCP劫持的问题。我认为个人级安全软件还是应该加入这块内容,毕竟弹广告还是小事,但用来作攻击未尝不可。

参考文献与扩展阅读

http://wenku.baidu.com/view/755b399cf121dd36a32d82a6.html

http://www.symantec.com/connect/articles/abnormal-ip-packets

在CentOS下安装并配置squid代理

2014-09-19 • 技术文章

起初,学校是通过带双绞线LAN口的瘦AP在室内同时提供免费的有线和无线网络。国际工商与管理学院的一个同学送了我一个ChinaUnicom的无线账号,从此上网比学校的5Mbps的带宽快了很多。但是最近,学校却把有线的提供形式改为了电信出资建设的拨号网络,虽然是光纤进了宿舍,但校内免费网络的实际使用速度却只有原来的十分之一左右,让人难以接受,网页难打开,视频更是看不了。于是动手改造,把EPON终端撤了,把双绞线插回了AP,速度又回到了5Mbps的水平,但是和之前用过的ChinaUnicom的账号比起来还是有点慢。

为了改变这种状况,计划搭建校内代理服务器。学校有部分区域的计算机是不限制带宽的,校内互访原则上也是不限速的,但宿舍区直接访问外网却是受限制的。所以,只要在不限制带宽的服务器上搭建代理,作为宿舍区访问网络的跳板就可以实现宿舍区的高速上网了。学院正好有一台性能非常强的CentOS机器暂由我负责管理,其CPU平均使用率都在0.5%以下,作为代理还是不会有什么压力。

我们可以通过yum install squid来安装squid,安装完后其配置文件会生成在/etc/squid/下,该目录下的squid.conf中可以设定squid的默认端口、ACL规则等等。其中的访问控制对于安全而言非常重要,但是由于我们外部已经有了一层NAT,在内部再做基于IP的配置也是没用的,在这里不再详述,配置参数根据说明和名称一看就懂。

考虑到安全性的问题,我们最好为代理服务器设定密码。最简单的密码验证方式如下:

1.在配置文件中加一行auth_param basic program /usr/lib/squid/ncsa_auth /etc/squid/passwd来生成passwd文件,文件名自定。

2.自定义一条名为auth_user的ACL,ACL类型为proxy_auth通过外部程序进行用户认证方式,值为REQUIRED,说明接受所有合法用户的访问,然后使用http_access选项允许该列表。在没有开启用户名密码认证时,能成功的只允许某些IP访问代理服务器,并且屏蔽掉客户端的信息。当开启用户名密码认证时,只要验证成功,无论IP是什么,都能够使用代理服务器。

1
2
3
auth_param basic program /usr/lib/squid/ncsa_auth /etc/squid/passwd
acl auth_user proxy_auth REQUIRED
http_access allow auth_user

3.然后回到终端,执行/usr/bin/htpasswd -c /etc/squid/passwd testuser生成前面提到的存储密码用的passwd文件,这里的testuser就是用户名,可自定,然后根据提示输入密码即可。

4.执行chkconfig --list squid可以看到当前squid服务是否会在各level上随机启动,我们执行chkconfig squid on即可设定为随机启动,如果不须随机启动,可跳过本步骤。

5.如果要实现对转发IP地址的隐藏,也就是客户端匿名化,可以在squid.conf中添加指令forwarded_for off。

6.执行service squid start启动squid服务即可提供代理服务器的服务了。

接下来,我们在浏览器中设置好代理服务器的IP和端口,输入用户名和密码就可以使用代理服务器上网了。经实测,代理上网的速度可达到100Mbps,非常快!

算法:求一个集合的所有子集

2014-07-30 • 技术文章

输入有限个数字(可能有重),求其所有的子集。

对于这个问题,暴力穷举无论是伪代码还是实际代码并不好写。对于全集的子集而言,也就只有两个可能,在这个子集中,每个元素只有存在或是不存在两种可能,所以适合用位来解决它,代码好写,省空间还省运算量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
 
public class A {
 
    public static Set<Integer> rs = new HashSet<Integer>();
    public static Map<Integer, Integer> map;
    public static int tmp;
 
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        map = new HashMap<Integer, Integer>();
        Set<Integer> set = new HashSet<Integer>();
 
        while(sc.hasNextInt()) {
            tmp = sc.nextInt();
            if(!set.contains(tmp))
                set.add(tmp);
        }
 
        tmp = 0;
        for(Integer num : set) {
            map.put(tmp++, num);
        }
 
        System.out.println(rs);
        tmp = 1 << tmp;
        for(int i = 1; i < tmp; ++i)
            printSet(i);
    }
 
    public static void printSet(int i) {
        rs.clear();
        char[] bs = Integer.toBinaryString(i).toCharArray();
        for(int e = bs.length - 1; e >= 0; --e) {
            if(bs[e] == '1') {
                rs.add(map.get(bs.length - e - 1));
            }
        }
        System.out.println(rs);
    }
 
}

说说Java中的new String()和toString()

2014-07-23 • 技术文章

区别

一个对象toString()方法如果没有被重写,那么默认调用它的父类Object的toString()方法。Object类的toString()方法返回一个能够代表该对象的字符串,由类名(对象是该类的一个实例)、“@”和此对象哈希码的无符号十六进制表示组成,即

1
getClass().getName() + '@' + Integer.toHexString(hashCode())

而new String()的接受的参数(这里仅仅指首个参数)类型包括byte[]、char[]、StringBuffer、StringBuilder、String、int[](ASCII码),将这些类型的字符数组或字符串或相似类型转化为String类型。

警告

正是基于两者的区别,将byte[]或char[]转化为String时不能使用toString()方法,而应使用new String()。

边注

查询相关资料时,看到(String)转型和toString()区别的问题。前者是将对象直接转型为String,而后者是调用了toString()方法。所以对于可达到同样目的不影响前后使用时,应优先使用前者,因为其没有外部方法调用,效率更高。

参考资料

Oracle JavaSE Docs