gawk
是GUN组织实现的awk
,awk
最初是由Alfred V. Aho, Brian W. Kernighan, Peter J. Weinberger, Addison-Wesley这4个人于1988年在Unix系统上实现的,用于系统信息的格式化报告打印。
[root@LiuXianQiE ~]# ls -l `which awk` lrwxrwxrwx. 1 root root 4 May 11 2019 /usr/bin/awk -> gawk [root@LiuXianQiE ~]#
从上述输出可见,在Linux系统上,仍然保留了awk
这个命令,但是作为gawk
的软链接形式存在。
gawk
中,也支持条件和逻辑判断结构(if…else if…else,switch)、循环迭代结构(for, while, do-while)、内建函数和自定义函数,以及这里要用到的数组特性。
在此前的博客中,介绍过bash中支持的数组,分为两类:索引数组和关联数组,参见Linux随笔10-Ubuntu网络配置、非交互式远程主机登录以及shell中的数组应用(冒泡排序数组中的元素),而gawk
中使用的则是关联数组。
gawk
数组可以直接应用在BEGIN{}
语句体、END{}
语句体以及循环语句体{}
中,其基本使用形式为array_name[key]=arr_value
,比如person[name]=zhangsan; person[gender]=male
。如果arr_value是字符串,需要用引号将其括起来,否则gawk会将其解释为变量名。
上述创建了数组,那如果要迭代数组中的元素,gawk
中也提供了一个类似于bash中的for循环一样的迭代方式:for (i in array_name) {do-something}
,使用这种方式就可以迭代数组中的元素了。
如果要判断某个索引是否存在于数组中,那么可以执行idx in array_name
,如果索引idx在数组中,那么上述表达式会返回1,如果不在数组中,那么上述表达式将会返回0。
下面通过几个示例进行演示:
判断索引是否在数组中
[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; > person["gender"]="Male"; print "name" in person, "sex" in person}' 1 0 [root@LiuXianQiE ~]#上述创建了一个名为person的数组,其中包含了两个元素,分别为name和gender,在构建数组的时候,数组的key需要用引号括起来,且该引号需要与语句体的引号相区别,语句体使用单引号,语句体内部需要将字符串使用双引号括起来,不能使用双引号将语句体括起来。具体如下所示:
[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; print "name" in person, "sex" in person}' 1 0 [root@LiuXianQiE ~]# gawk "BEGIN{person['name']='zhangsan'; person['gender']='Male'}" gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'} gawk: cmd. line:1: ^ invalid char ''' in expression gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'} gawk: cmd. line:1: ^ syntax error [root@LiuXianQiE ~]# gawk "BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person}" gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person} gawk: cmd. line:1: ^ invalid char ''' in expression gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person} gawk: cmd. line:1: ^ syntax error [root@LiuXianQiE ~]#如果要引用数组中的元素,直接使用
array_name[idx]
的形式即可。具体如下所示:[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; print "name="person["name"], "Gender="person["gender"]}' name=zhangsan Gender=Male [root@LiuXianQiE ~]#通过for循环遍历上述数组的内容,此时效果如下所示:
[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; for(i in person) {print person[i]}}' zhangsan Male [root@LiuXianQiE ~]#上述就是遍历数组的过程。
有了上述的基础,下面通过gawk
的数组功能,实现访问httpd服务的IP地址统计,并将超过100的IP地址封掉。
为此,需要准备两台虚拟机,一台虚拟机作为httpd服务器,一台作为客户端,并发发起连接请求。
在服务器上,安装httpd服务,此处使用Ubuntu20.04.2 LTS作为服务器,在其上安装httpd服务,执行命令
apt install -y apache2
即可。安装完成之后,执行命令systemctl enable --now apache2.service
即可将httpd服务设置为开机自动运行,并且立即启动该服务。
然后将准备主页内容:root@ubuntu20u04:~# echo 'This is a apache2 web service on Ubuntu20.04.2 LTS server' > /var/www/html/index.html root@ubuntu20u04:~# cat /var/www/html/index.html This is a apache2 web service on Ubuntu20.04.2 LTS server root@ubuntu20u04:~# curl localhost This is a apache2 web service on Ubuntu20.04.2 LTS server root@ubuntu20u04:~#至此,服务器端准备完成。
客户端采用CentOS 7.6系统,由于要在客户端发情并发连接,所以需要在客户端上执行
ab
命令,而该命令是由httpd-tools这个软件包提供的,所以需要在客户端上安装该软件包。安装的命令为yum install -y httpd-tools
即可。
查看安装后的结果如下:[root@c7u6-ha1 ~]# rpm -qf `which ab` httpd-tools-2.4.6-88.el7.centos.x86_64 [root@c7u6-ha1 ~]#从上述输出可见,
ab
命令是由httpd-tools这个软件包提供的。
服务器端和客户端都准备完成了,接下来在客户端通过ab
命令发起并发连接测试。具体如下所示:
[root@c7u6-ha1 ~]# ab -b 5120 -n 80000 -c 8000 http://ubuntu20u04/index.html This is ApacheBench, Version 2.3 <$Revision: 1430300 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking ubuntu20u04 (be patient) Completed 8000 requests Completed 16000 requests Completed 24000 requests Completed 32000 requests Completed 40000 requests Completed 48000 requests Completed 56000 requests Completed 64000 requests Completed 72000 requests Completed 80000 requests Finished 80000 requests Server Software: Apache/2.4.41 Server Hostname: ubuntu20u04 Server Port: 80 Document Path: /index.html Document Length: 10918 bytes Concurrency Level: 8000 Time taken for tests: 29.736 seconds Complete requests: 80000 Failed requests: 225 (Connect: 0, Receive: 0, Length: 225, Exceptions: 0) Write errors: 0 Total transferred: 892847592 bytes HTML transferred: 870988694 bytes Requests per second: 2690.38 [#/sec] (mean) Time per request: 2973.561 [ms] (mean) Time per request: 0.372 [ms] (mean, across all concurrent requests) Transfer rate: 29322.47 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 198 588.0 0 3015 Processing: 10 1191 5111.2 42 26819 Waiting: 0 1129 4992.9 41 26818 Total: 30 1389 5312.8 42 29329 Percentage of the requests served within a certain time (ms) 50% 42 66% 45 75% 53 80% 79 90% 1253 95% 6266 98% 27186 99% 27460 100% 29329 (longest request)
上述命令中,-c 8000
表示并发发起8000个连接;-n 80000
表示session中执行的请求数。
上述即为客户端发起的连接测试。接下来查看服务器端的访问日志,具体如下所示:
root@ubuntu20u04:~# cat /var/log/apache2/access.log | head -n10 127.0.0.1 - - [13/Jun/2021:09:34:35 +0800] "GET / HTTP/1.1" 200 11173 "-" "curl/7.68.0" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" 192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3" root@ubuntu20u04:~#
要统计服务器上各个IP地址的访问次数,可以由几种方式,具体如下所示:
root@ubuntu20u04:~# cat /var/log/apache2/access.log | cut -d' ' -f1 | sort -n | uniq -c 3 127.0.0.1 241206 192.168.122.16 root@ubuntu20u04:~#这种方式是最基本的形式,借助
cat
命令将文件内容打印到标准输出流中, cut
命令按照指定的分隔符提取出指定的字段, sort
命令对提取出的字段内容进行排序, 最后通过uniq
命令对排序的结果进行统计,进而得出各个IP地址的访问次数。gawk
命令实现root@ubuntu20u04:~# gawk '{print $1}' /var/log/apache2/access.log | sort -n | uniq -c 3 127.0.0.1 241206 192.168.122.16 root@ubuntu20u04:~#这种形式跟上面的形式差不多,只不过此处不再通过
cat
结合cut
命令提取指定的字段信息,而是通过gawk
命令提取指定的字段信息。然后仍然是借助sort
命令对提取的字段信息进行排序,最后通过uniq
命令对排序结果进行统计。gawk
命令实现统计root@ubuntu20u04:~# gawk '{ip[$1]++} END{for(i in ip) {print i, ip[i]}}' /var/log/apache2/access.log 127.0.0.1 3 192.168.122.16 241206 root@ubuntu20u04:~#这个操作只用到
gawk
命令,但是用到了gawk
内部提供的数组和for循环迭代数组的特性,ip[$1]
表示以IP地址作为数组的key,而ip[$1]++
的含义就是将相同的IP地址出现的次数加在一起,效果等同于ip[$1]+=1
,然后将得到的结果作为value存放在ip[$1]中,最终结果就是ip[127.0.0.1]=3
,即本地回环地址访问了3次。有了IP地址的访问次数统计,那么就可以通过ipables
命令将对应的IP地址封禁了。要封禁对应的IP地址,可以在iptables
的filter表的INPUT链中添加规则:源地址为待封禁IP,目标地址为本机IP,且从eth0网卡接口访问本机80端口的连接,全部丢弃。规则如下所示:
root@ubuntu20u04:~# iptables -t filter -L INPUT Chain INPUT (policy ACCEPT) target prot opt source destination root@ubuntu20u04:~# root@ubuntu20u04:~# iptables -t filter -A INPUT -s 192.168.122.16 -d 192.168.122.30 -i eth0 -p tcp --dport 80 -j DROP root@ubuntu20u04:~# iptables -t filter -L INPUT Chain INPUT (policy ACCEPT) target prot opt source destination DROP tcp -- 192.168.122.16 192.168.122.30 tcp dpt:http root@ubuntu20u04:~#
上述就给filter表的INPUT链添加了一条规则,在指定的端口的时候,需要指定数据包类型是TCP还是UDP。此时,就无法在1921.68.122.16这台虚拟机上访问192.168.122.30这台虚拟机的httpd服务了。具体如下所示:
[root@c7u6-ha1 ~]# curl ubuntu20u04 ^C [root@c7u6-ha1 ~]#
上述在执行的时候,会一致处于等待状态,没有信息返回,这是由于在iptables
规则中指定了目标动作为DROP,这个操作会直接将数据包丢弃,但是不给客户端返回任何信息。
如果想看到返回信息,可以将iptables
规则中的目标动做从DROP修改为REJECT即可。具体如下所示:
root@ubuntu20u04:~# iptables -t filter -L INPUT --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 DROP tcp -- 192.168.122.16 192.168.122.30 tcp dpt:http root@ubuntu20u04:~# iptables -t filter -R INPUT 1 -s 192.168.122.16 -d 192.168.122.30 -i eth0 -p tcp --dport 80 -j REJECT root@ubuntu20u04:~# iptables -t filter -L INPUT --line-numbers Chain INPUT (policy ACCEPT) num target prot opt source destination 1 REJECT tcp -- 192.168.122.16 192.168.122.30 tcp dpt:http reject-with icmp-port-unreachable root@ubuntu20u04:~#
此时在客户端访问如下所示:
[root@c7u6-ha1 ~]# curl ubuntu20u04 curl: (7) Failed connect to ubuntu20u04:80; Connection refused [root@c7u6-ha1 ~]#
但是在宿主机仍然可以访问,具体如下所示:
[root@LiuXianQiE ~]# curl ubuntu20u04 This is a apache2 web service on Ubuntu20.04.2 LTS server [root@LiuXianQiE ~]#
上述过程,如果写成脚本,然后放在计划任务中,如果每10分钟的连接数超过100,就将这个IP地址封禁。通过实时监控系统的连接数,可以在一定程度上抵御DOS攻击。
脚本内容如下所示:
#!/bin/bash #********************************************** # FileName: deny_ddos.sh # Author: 六弦企鹅 # Created: 2021.06.13 # Modified: 2021.06.13 # Version: 0.1 # Description: 检测http连接数,超过指定数量的IP封禁 # Usage: bash deny_ddos.sh #********************************************** # root@ubuntu20u04:~# ss -tnap | gawk -F' +|:' '/:22/ && /[0-9]+.{3}[0-9]+/ && ! /0.0.0.0/ && ! /127.0.0.1/ {ip[$6]++} END{for(i in ip) print i, ip[i]}' # 192.168.122.16 1 # 192.168.122.1 1 # root@ubuntu20u04:~# local_ip=192.168.122.30 ss_res=`ss -tnap | gawk -F' +|:' '/:80/ && /[0-9]+.{3}[0-9]+/ && ! /0.0.0.0/ && ! /127.0.0.1/ {ip[$6]++} END{for(i in ip) print i, ip[i]}' | sed -re ':label; N; s/\n/@/; t label'` IFS_OLD=$IFS IFS=@ for line in ${ss_res} do #echo $line ipaddr=`echo $line | cut -d' ' -f1` ipnum=`echo $line | cut -d' ' -f2` if [ ${ipnum} -gt 100 ]; then iptables -t filter -A INPUT -s ${ipaddr} -d ${local_ip} -i eth0 -p tcp --dport 80 -j DROP echo ${ipaddr} - ${ipnum} fi done上述脚本可以将80端口的连接IP做统计,并将脚本执行时连接超过100个的IP封禁。
将上述脚本写入到计划任务里面,每隔10分钟检查一次,具体如下所示:root@ubuntu20u04:~# crontab -e crontab: installing new crontab root@ubuntu20u04:~# crontab -l # m h dom mon dow command */10 * * * * bash /root/deny_ddos.sh上述就是每隔10分钟执行一次这个脚本,检查一次主机的80端口连接情况。
密钥交换,自然涉及到加密算法,而常用的加密算法,按照加密与解密所用密钥是否一致,可分为:对称加密和非对称加密。
TLS(Transport Layer Security)主要用在web安全传输上,用于数据加密。TLS的两个主要目标就是:机密性(Confidnetiality)和身份验证(Authentication),这两点对于互联网通信来说,都是十分重要的。
Web站点的证数中就包含了其公钥,如果Web站点能够证明其控制着对应的私钥,那么就认为这个站点就是证书的所有者。而在浏览器这边,如果这个证书是被系统预先安装的根证书信任的证书机构(Certificate Authority,CA)颁发的证书,同时包含了这个Web站点的域名信息,那么就认为这个Web站点的证书是可信的。
而在Web上下问环境中,机密性和身份验证是通过建立共享密钥和证明证书所有关系的过程来完成的。这个过程会中,双发会收发一系列消息,被称为握手(HandShake)。
TLS有两种类型的握手:一种是基于RSA,一种是基于Diffie-Hellman。这两种加密算法都引领着现代加密技术。这两种握手方式的区别仅在于密钥建立和身份验证所实现的主体不同,具体如下表所示:
Key Establishment | Authentication | |
---|---|---|
RSA handshake | RSA | RSA |
DH handshake | DH | RSA/DSA |
RSA和DH握手过程都有其各自的优缺点,RSA握手过程只是用了一个RSA公钥加密算法操作;DH握手过程除了RSA握手过程中相同的RSA证书操作之外,还有一步额外的DH操作。如果给定的证书是RSA证书,那么RSA握手过程的计算速度更快。公钥加密算法,比如RSA、DH在加密过程中都会消耗很多CPU资源,也是整个TLS握手过程中最慢的部分。
DH握手过程中需要运行两个算法,这样的操作除了增加计算时常之外,也有其优势,那就是可以使密钥建立独立于服务器的私钥完成。这就能够在一定程度上确保连接的前向机密性(forward secrecy),也就是说,即便在私钥被暴露的情况下也可以保护会话不会被解密。另外,DH握手过程中,也可以不开启RSA证书来提高性能。使用ECDSA(Elliptic Curve DSA)证书以及Elliptic Curve Diffie-Hellman Key Agreement的DH握手过程也会比一步操作的RSA握手过程更快。
为了更好的理解握手过程,需要了解下面几个术语的含义:
Session key:这是TLS握手过程的最终结果,即共享密钥。它使对称加密算法的密钥,允许客户端和服务器把将要发送的消息使用这个密钥进行加密,然后发送加密后的信息。
Client random:客户端随机值,这是客户端创建的32字节序列值,对于每个不同的连接,这个值都是不一样的。其中包含4个字节的时间戳以及28个字节的随机值。最近Google Chrome浏览器为了避免形成客户端指纹信息,省略掉了4个字节的时间戳,改为32字节全部为随机值。
Server random:与***Clinet random***含义相同,只不过是由服务器端生成的。
Pre-main secret:48字节的数据块,它可以使用伪随机数函数(“PseudoRandom Function” ,PRF)将***Client random***和***Server random***组合在一起生成共享密钥***Session key***。
Cipher suite:构建TLS连接的组合算法唯一标识符,它为下面的每一步定义了一个算法:
比如"AES128-SHA"定义了一个会话使用如下算法:
除了上述之外,还有更复杂的实现套件:“ECDHE-ECDSA-AES256-GCM-SHA384”,这个套件定义了一个会话任务中使用如下算法:
有了上述理解,就可以深入了解TLS的两种握手过程了。
注意,在这个握手过程中,没有任何数据是被Session key加密的,因为此时还未生成双方都能识别的Session key。所以这个过程中数据都是明文传输的。具体过程如下图所示:
上图的通信过程的各个步骤如下:
Ephemeral Diffie-Hellman handshake是另一种TLS的握手过程,它使用了两种不同的机制:一个是用于建立可共享的Pre-main secret,另一个是服务器身份验证。这个密钥特性依赖于Diffie-Hellman密钥一致性算法(Diffie-Hellman key agreement algorithm)。
在Diffie-Hellman中,双方通过不同的密钥交换消息来获得相同的共享密钥,这个握手过程依赖于一个简单的数学原理:对一个整数g计算a次幂的结果,对该结果计算其b次幂,写成数学表达式为(ga)b=gab,这个最终结果与对整数g计算b次幂的结果,然后对该结果计算其a次幂,写成数学表达式为(gb)a=gab,这两个结果是相同的。而这也也是为什么双方可以使用不同的密钥交换消息来获得相同的共享密钥的原理。该算法的工作过程如下所示:
上述操作对于一般的数来说,经过gab计算之后会得到很大的数,而对于一个数的n次方根执行上述计算,则不会得到很大的数,也是更高效的方式。除此之外,还可以改变问题空间,同样奏效,这是通过将一个固定大小的数除以一个大素数然后取余数这种计算方式实现的(However, we can change the problem space and make it work. This is done by restricting the computation to numbers of a fixed size by always dividing the result of a computation by big prime number and taking the remainder.)。而这被称为算数取模,对算数取模的结果计算其n次方根,被称为离散对数问题。
Diffie-Hellman密钥一致性(Diffie-Hellman key agreement)算法的一个变种就是使用椭圆曲线(Elliptic Curves, ECDHE),更多关于椭圆曲线的信息,参见素数这篇文章的介绍。通过使用这种固定大小的Diffie-Hellman密钥一致性算法就可以派生出共享密钥 Session key了。
Diffie-Hellman握手的过程具体如下所示:
上图的具体通信过程,如下所示:
上述就是TLS的密钥交换过程。
所谓HTTPS (Hypertext Transfer Protocol Secure)通信在HTTP通信协议的基础上,增加了SSL/TLS用于加密传输的数据。HTTPS的初中是通过加密通信双方传输的数据实现对用户隐私的保护以及通信双方传输数据的完整性。另外,为了验证访问的目标服务器的身份,还需要有一个权威的证书机构(CA)来给服务器颁发证书,同时浏览器所在操作系统上会安装这些CA机构的受信根证书。CA颁发给服务器的证书中,包含了服务器的域名信息、服务器的公钥信息,与服务器公钥信息配对的私钥信息则保存在服务器上。有了证书、公私密钥等信息,就可以实现服务器与客户端之间的数据加密和身份验证了。
下面以客户端对https://www.domain.com这个域名的访问为例,说明HTTPS的通信过程,具体如下图所示:
上述即为HTTPS的通信过程。
[1]. Keyless SSL: The Nitty Gritty Technical Details
[2]. Asymmetric Encryption
[3]. AES
[4]. ECDSA: The digital signature algorithm of a better internet
[5]. A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography
[6]. Network: Briefly on encryption and decryption in the HTTPS workflow
转载请注明出处!