my blog my blog

Category: Linux
通过nginx配置文件抵御CC攻击,防御99%的CC攻击

大家好,我们是OpenCDN团队的Twwy。这次我们来讲讲如何通过简单的配置文件来实现nginx防御攻击的效果。

其实很多时候,各种防攻击的思路我们都明白,比如限制IP啊,过滤攻击字符串啊,识别攻击指纹啦。可是要如何去实现它呢?用守护脚本吗?用PHP在外面包 一层过滤?还是直接加防火墙吗?这些都是防御手段。不过本文将要介绍的是直接通过nginx的普通模块和配置文件的组合来达到一定的防御效果。

0x01 验证浏览器行为

简易版

我们先来做个比喻。

社区在搞福利,在广场上给大家派发红包。而坏人派了一批人形的机器人(没有语言模块)来冒领红包,聪明工作人员需要想出办法来防止红包被冒领。

于是工作人员在发红包之前,会给领取者一张纸,上面写着“红包拿来”,如果那人能念出纸上的字,那么就是人,给红包,如果你不能念出来,那么请自觉。于是机器人便被识破,灰溜溜地回来了。

是的,在这个比喻中,人就是浏览器,机器人就是攻击器,我们可以通过鉴别cookie功能(念纸上的字)的方式来鉴别他们。下面就是nginx的配置文件写法。

if ($cookie_say != "hbnl"){
     add_header Set-Cookie "say=hbnl";
     rewrite .* "$scheme://$host$uri" redirect;
}

让我们看下这几行的意思,当cookie中say为空时,给一个设置cookie say为hbnl的302重定向包,如果访问者能够在第二个包中携带上cookie值,那么就能正常访问网站了,如果不能的话,那他永远活在了302中。你也可以测试一下,用CC攻击器或者webbench或者直接curl发包做测试,他们都活在了302世界中。

当然,这么简单就能防住了?当然没有那么简单。

增强版

仔细的你一定会发现配置文件这样写还是有缺陷。如果攻击者设置cookie为say=hbnl(CC攻击器上就可以这么设置),那么这个防御就形同虚设了。我们继续拿刚刚那个比喻来说明问题。

坏人发现这个规律后,给每个机器人安上了扬声器,一直重复着“红包拿来,红包拿来”,浩浩荡荡地又来领红包了。

这时,工作人员的对策是这样做的,要求领取者出示有自己名字的户口本,并且念出自己的名字,“我是xxx,红包拿来”。于是一群只会嗡嗡叫着“红包拿来”的机器人又被撵回去了。

当然,为了配合说明问题,每个机器人是有户口本的,被赶回去的原因是不会念自己的名字,虽然这个有点荒诞,唉。

然后,我们来看下这种方式的配置文件写法

if ($cookie_say != "hbnl$remote_addr"){
     add_header Set-Cookie "say=hbnl$remote_addr";
     rewrite .* "$scheme://$host$uri" redirect;
}

这样的写法和前面的区别是,不同IP的请求cookie值是不一样的,比如IP是1.2.3.4,那么需要设置的cookie是say=hbnl1.2.3.4。于是攻击者便无法通过设置一样的cookie(比如CC攻击器)来绕过这种限制。你可以继续用CC攻击器来测试下,你会发现CC攻击器打出的流量已经全部进入302世界中。

不过大家也能感觉到,这似乎也不是一个万全之计,因为攻击者如果研究了网站的机制之后,总有办法测出并预先伪造cookie值的设置方法。因为我们做差异化的数据源正是他们本身的一些信息(IP、user agent等)。攻击者花点时间也是可以做出专门针对网站的攻击脚本的。

完美版

那么要如何根据他们自身的信息得出他们又得出他们算不出的数值?

我想,聪明的你一定已经猜到了,用salt加散列。比如md5(“opencdn$remote_addr”),虽然攻击者知道可以自己IP,但是他无法得知如何用他的IP来计算出这个散列,因为他是逆不出这个散列的。当然,如果你不放心的话,怕cmd5.com万一能查出来的话,可以加一些特殊字符,然后多散几次。

很可惜,nginx默认是无法进行字符串散列的,于是我们借助nginx_lua模块来进行实现。

rewrite_by_lua '
     local say = ngx.md5("opencdn" .. ngx.var.remote_addr)
     if (ngx.var.cookie_say ~= say) then
         ngx.header["Set-Cookie"] = "say=" .. say
         return ngx.redirect(ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri)
     end
';

通过这样的配置,攻击者便无法事先计算这个cookie中的say值,于是攻击流量(代理型CC和低级发包型CC)便在302地狱无法自拔了。

大家可以看到,除了借用了md5这个函数外,其他的逻辑和上面的写法是一模一样的。因此如果可以的话,你完全可以安装一个nginx的计算散列的第三方模块来完成,可能效率会更高一些。

这段配置是可以被放在任意的location里面,如果你的网站有对外提供API功能的话,建议API一定不能加入这段,因为API的调用也是没有浏览器行为的,会被当做攻击流量处理。并且,有些弱一点爬虫也会陷在302之中,这个需要注意。

同时,如果你觉得set-cookie这个动作似乎攻击者也有可能通过解析字符串模拟出来的话,你可以把上述的通过header来设置cookie的操作,变成通过高端大气的js完成,发回一个含有doument.cookie=…的文本即可。

那么,攻击是不是完全被挡住了呢?只能说那些低级的攻击已经被挡住而来,如果攻击者必须花很大代价给每个攻击器加上webkit模块来解析js和执行set-cookie才行,那么他也是可以逃脱302地狱的,在nginx看来,确实攻击流量和普通浏览流量是一样的。那么如何防御呢?下节会告诉你答案。

0x02 请求频率限制

不得不说,很多防CC的措施是直接在请求频率上做限制来实现的,但是,很多都存在着一定的问题。

那么是哪些问题呢?

首先,如果通过IP来限制请求频率,容易导致一些误杀,比如我一个地方出口IP就那么几个,而访问者一多的话,请求频率很容易到上限,那么那个地方的用户就都访问不了你的网站了。

于是你会说,我用SESSION来限制就有这个问题了。嗯,你的SESSION为攻击者敞开了一道大门。为什么呢?看了上文的你可能已经大致知道了,因为就像那个“红包拿来”的扬声器一样,很多语言或者框架中的SESSION是能够伪造的。以PHP为例,你可以在浏览器中的cookie看到PHPSESSIONID,这个ID不同的话,session也就不同了,然后如果你杜撰一个PHPSESSIONID过去的话,你会发现,服务器也认可了这个ID,为这个ID初始化了一个会话。那么,攻击者只需要每次发完包就构造一个新的SESSIONID就可以很轻松地躲过这种在session上的请求次数限制。

那么我们要如何来做这个请求频率的限制呢?

首先,我们先要一个攻击者无法杜撰的sessionID,一种方式是用个池子记录下每次给出的ID,然后在请求来的时候进行查询,如果没有的话,就拒绝请求。这种方式我们不推荐,首先一个网站已经有了session池,这样再做个无疑有些浪费,而且还需要进行池中的遍历比较查询,太消耗性能。我们希望的是一种可以无状态性的sessionID,可以吗?可以的。

rewrite_by_lua '

     local random = ngx.var.cookie_random

     if(random == nil) then
         random = math.random(999999)
     end

     local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
     if (ngx.var.cookie_token ~= token) then
         ngx.header["Set-Cookie"] = {"token=" .. token, "random=" .. random}
         return ngx.redirect(ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri)
     end
';

大家是不是觉得好像有些眼熟?是的,这个就是上节的完美版的配置再加个随机数,为的是让同一个IP的用户也能有不同的token。同样的,只要有nginx的第三方模块提供散列和随机数功能,这个配置也可以不用lua直接用纯配置文件完成。

有了这个token之后,相当于每个访客有一个无法伪造的并且独一无二的token,这种情况下,进行请求限制才有意义。

由于有了token做铺垫,我们可以不做什么白名单、黑名单,直接通过limit模块来完成。

http{
     ...
     limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
}

然后我们只需要在上面的token配置后面中加入

limit_req zone=session_limit burst=5;

于是,又是两行配置便让nginx在session层解决了请求频率的限制。不过似乎还是有缺陷,因为攻击者可以通过一直获取token来突破请求频率限制,如果能限制一个IP获取token的频率就更完美了。可以做到吗?可以。

http{
     ...
     limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
     limit_req_zone $binary_remote_addr $uri zone=auth_limit:3m rate=1r/m;
}

location /{

     limit_req zone=session_limit burst=5;
     rewrite_by_lua '
     local random = ngx.var.cookie_random
     if (random == nil) then
         return ngx.redirect("/auth?url=" .. ngx.var.request_uri)
     end
     local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
     if (ngx.var.cookie_token ~= token) then
         return ngx.redirect("/auth?url=".. ngx.var.request_uri)
     end
';

}

location /auth {
     limit_req zone=auth_limit burst=1;

     if ($arg_url = "") {
         return403;
     }

     access_by_lua '
         local random = math.random(9999)
         local token = ngx.md5("opencdn" .. ngx.var.remote_addr .. random)
         if (ngx.var.cookie_token ~= token) then
             ngx.header["Set-Cookie"] = {"token=" .. token, "random=" .. random}
             return ngx.redirect(ngx.var.arg_url)
         end
     ';
}

我想大家也应该已经猜到,这段配置文件的原理就是:把本来的发token的功能分离到一个auth页面,然后用limit对这个auth页面进行频率限制即可。这边的频率是1个IP每分钟授权1个token。当然,这个数量可以根据业务需要进行调整。

需要注意的是,这个auth部分我lua采用的是access_by_lua,原因在于limit模块是在rewrite阶段后执行的,如果在rewrite阶段302的话,limit将会失效。因此,这段lua配置我不能保证可以用原生的配置文件实现,因为不知道如何用配置文件在rewrite阶段后进行302跳转,也求大牛能够指点一下啊。

当然,你如果还不满足于这种限制的话,想要做到某个IP如果一天到达上限超过几次之后就直接封IP的话,也是可以的,你可以用类似的思路再做个错误页面,然后到达上限之后不返回503而是跳转到那个错误页面,然后错误页面也做个请求次数限制,比如每天只能访问100次,那么当超过报错超过100次(请求错误页面100次)之后,那天这个IP就不能再访问这个网站了。

于是,通过这些配置我们便实现了一个网站访问频率限制。不过,这样的配置也不是说可以完全防止了攻击,只能说让攻击者的成本变高,让网站的扛攻击能力变强,当然,前提是nginx能够扛得住这些流量,然后带宽不被堵死。如果你家门被堵了,你还想开门营业,那真心没有办法了。

然后,做完流量上的防护,让我们来看看对于扫描器之类的攻击的防御。

0x03 防扫描

ngx_lua_waf模块

这个是一个不错的waf模块,这块我们也就不再重复造轮子了。可以直接用这个模块来做防护,当然也完全可以再配合limit模块,用上文的思路来做到一个封IP或者封session的效果。

0x04 总结

本文旨在达到抛砖引玉的作用,我们并不希望你直接单纯的复制我们的这些例子中的配置,而是希望根据你的自身业务需要,写出适合自身站点的配置文件。

 

这篇文章并不是奶牛原创的,但是奶牛觉得非常好,之前应该是发布在乌云上面的,但是乌云现在已经人去楼空了,希望这篇很好的文章可以保存下来。

Linux使用Curl并启用cookie功能

奶牛最近发现有的网站为了阻止第7层网络的攻击,选择了使用cookie进行阻止恶意攻击。比如Voxility在防护启用的时候,就会用如下的方式:

<html>
<head><meta http-equiv="refresh" content="1" /><meta http-equiv="cache-control"                                                                                                                                                              content="max-age=0" /><meta http-equiv="cache-control" content="no-cache" /><met                                                                                                                                                             a http-equiv="expires" content="-1" /><meta http-equiv="expires" content="Tue, 0                                                                                                                                                             1 Jan 1980 1:00:00 GMT" /><meta http-equiv="pragma" content="no-cache" /></head>
<script type="text/javascript">
<!--
function getCookie(w){
        cName = "";
        pCOOKIES = new Array();
        pCOOKIES = document.cookie.split('; ');
        for(bb = 0; bb < pCOOKIES.length; bb++){
                NmeVal  = new Array();
                NmeVal  = pCOOKIES[bb].split('=');
                if(NmeVal[0] == w){
                        cName = unescape(NmeVal[1]);
                }
        }
        return cName;
}
function printCookies(w){
        cStr = "";
        pCOOKIES = new Array();
        pCOOKIES = document.cookie.split('; ');
        for(bb = 0; bb < pCOOKIES.length; bb++){
                NmeVal  = new Array();
                NmeVal  = pCOOKIES[bb].split('=');
                if(NmeVal[0]){
                        cStr += NmeVal[0] + '=' + unescape(NmeVal[1]) + '; ';
                }
        }
        return cStr;
}
function setCookie(name, value, expires, path, domain, secure){
        var vlad = name + "=" + escape(value);
        if(expires){
                expires = setExpiration(expires);
                vlad += ";expires=" + expires;
        }
        if(path){
                vlad += ";path=" + path;
        }
        if(domain){
                vlad += ";domain=" + domain;
        }
        if(secure){
                vlad += ';secure';
        }
document.cookie = vlad;
}
function setExpiration(cookieLife){
    var today = new Date();
    var expr = new Date(today.getTime() + cookieLife * 24 * 60 * 60 * 1000);
    return  expr.toGMTString();
}
function delete_cookie(name){
  document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
// -->
</script>
<script type="text/javascript">
<!--
delete_cookie('hasVisitedSite');
setCookie('hasVisitedSite', 'Yes', '3', '/', '300', '');
delete_cookie('fb624561f4f8b25819e9b3c4fad70f85');
setCookie('fb624561f4f8b25819e9b3c4fad70f85', 'Yes', '3', '/', '300', '');
-->
</script><body></body></html>

如果我们直接curl加网址的话得到的就是这种情况。如果我们想访问真实内容需要启用cookie。

curl -c cookie.txt www.nenew.net
curl -b cookie.txt www.nenew.net

当第一次访问的时候,奶牛保存cookie信息在cookie.txt中,第二次访问包含cookie.txt中的信息,就可以访问到真实页面了。

关于Prism Syntax Highlighter中Command Line插件的配置教程

Prism Syntax Highlighter是一款wordpress中的代码高亮插件,奶牛非常喜欢它的Command Line功能,但是默认集成的情况并不好,奶牛就自己查看代码来处理了一下。我们先看看效果吧。

正常带行号格式:

这是第一行文字
这是第二行文字
这是第三行文字

Bash命令格式:

uname -a
ifconfig
ls

Bash命令带输出格式:

uname -a
Linux nenew.net 4.15.0-041500-generic #201801282230 SMP Sun Jan 28 22:31:30 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
ifconfig
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:36614 errors:0 dropped:0 overruns:0 frame:0
          TX packets:36614 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:18070026 (18.0 MB)  TX bytes:18070026 (18.0 MB)

使用行号的时候格式:

Output Lines:2,4-11

这就可以设置为单行或者多行同时输出显示了。

插件下载地址:Prism Syntax Highlighter

修改后的editor-plugin.js文件下载:editor-plugin.js

安装插件后替换掉其中的./assets/editor-plugin.js文件,然后在插件的设置页面中激活功能即可实现bash的命令行和行号的输出功能。

Ubuntu安装配置Linux性能实时监测工具NetData

最近测试了ntopng,虽然普通版本的也还凑合,但是还是企业版的实时展示奶牛更喜欢,可是普通版本就没有实时功能了,所以就又找了一下,发现在github上这个netdata的star最多,目前已经达到了28.9K,感兴趣的朋友可以去https://github.com/topics/monitor看看。

言归正传,奶牛来说说netdata的安装过程。

安装:

curl -Ss 'https://raw.githubusercontent.com/firehol/netdata-demo-site/master/install-required-packages.sh' >/tmp/kickstart.sh && bash /tmp/kickstart.sh -i netdata-all

安装netdata编译所需组件,这里奶牛以全监控安装方式安装

git clone https://github.com/firehol/netdata.git --depth=1
cd netdata
./netdata-installer.sh --dont-start-it

这里根据提示进行安装即可,安装完成后我们可以通过service netdata start 来启动。

配置:

这里有个有趣的现象,在/etc/netdata/netdata.conf文件中并没有配置选项,而是需要从web下载配置文件,当然这个web是localhost了。

#  wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf
#  curl -o /etc/netdata/netdata.conf http://localhost:19999/netdata.conf

所以呢,就按照这个说明把netdata.conf文件下载下来就可以配置了。

在此,奶牛更希望我们以反向代理的方式进行访问netdata,所以奶牛并不推荐直接将netdata端口暴露出来,所以我们将netdata.conf文件中的

[web]
	bind to = 127.0.0.1

修改为绑定到本地地址。最后我们可以通过Nginx反向代理来进行访问,或者如果你不介意,不这样配置也无所谓。

VPS磁盘空间大小调整小记

MySQL的日志文件又一次把奶牛博客的磁盘撑爆了,今天一下班发现网站挂了,然后ping还可以通,就进ssh看看究竟,然后很无语,mysql无法关闭也无法启动,df -h看了一下,发现/目录已经爆满。之前已经遇到过一次这种情形了,所以直接去看MySQL的日志文件,果然,几个G在那里堆着,删删删,反正也懒得去分析日志找原因了。删了之后发现/目录还是占用了近80%的空间,呃,这就有点儿纠结了吧,然后仔细看了一下df -h,我去,VPS还自带分区的,两块盘,一块30G的盘竟然挂载到了/mnt目录下,好吧,还有这种玩儿法。然后就想把这个vdb的硬盘给挂载到/目录下,但是回头一想,LVM貌似也不合适,没法直接加的,因为/是vda1这个主分区在挂载,所以,好吧,直接把vdb分给/usr目录吧,反正一般的东西也就扔这里面的,比如那个可恶的MySQL日志文件。

cp -Rf -p /usr /mnt

其中的-p参数是为了保证文件的uid和gid都保持不变,当然,这里是使用root帐号来操作的。

然后

umount /dev/vdb
mount /dev/vdb /usr
df -h

查看下确实挂载没有问题了,然后程序也都正常运行,之后再修改fstab

vim /etc/fstab

将原先挂载在/mnt目录下的vdb修改为挂载在/usr目录下。

最后再说下MySQL的日志,配置文件在/etc/my.cnf下,可以进行自动过期处理等操作,也可以直接注释掉不记录日志。

Nginx强制http跳转至https

奶牛启用https后发现还是在http和https共存的时候出现一些小问题,遂决定使用Nginx强制跳转https。

vim /usr/local/nginx/conf/vhost/nenew.net.conf

在配置文件的listen 80这段中插入语句

rewrite ^(.*)$  https://www.nenew.net$1 permanent;

然后重启Nginx即可。

Ubuntu安装goaccess进行Nginx日志分析

今天测试了一下goaccess,但是感觉并没有想像的那么好,也没详细考证config文件的配置,就默认配置来说,可能奶牛想要的关键词统计并没有在上面,好吧,还是记录一下安装过程吧。这里奶牛以Ubuntu server为例,以Nginx作为web服务器进行配置。

apt-get install libncursesw5-dev libgeoip-dev
mkdir goaccess
wget http://tar.goaccess.io/goaccess-1.2.tar.gz
tar -xzvf goaccess-1.2.tar.gz
cd goaccess-1.2/
./configure --enable-utf8 --enable-geoip=legacy
make
make install

这里我们就完成了goaccess的安装,下面进行config文件的更改,位置为/usr/local/etc//goaccess.conf

vim /usr/local/etc/goaccess.conf
log-format COMBINED

将log文件的格式设置为COMBINED。

最后goaccess就可以使用了。在wwwlogs目录下

goaccess access.log -o report.html

然后我们就可以通过report.html来查看统计信息了。

 

军哥LNMP下typecho的rewrite配置隐藏index.php

最近新建了站点优推网并且开始使用typecho,安装配置过程中发现自带的rewrite无法隐藏index.php,解决方案是修改

/usr/local/nginx/conf/vhost/www.affu.net.conf

配置文件,将include enable-php.conf;修改为include enable-php-pathinfo.conf;

然后再在typecho的设置中启用固定链接即可正常显示。

最近打算将博客转入Sharktech的服务器,并且将使用7年的wordpress更换为typecho,wordpress不可说不好,但是现在感觉太臃肿了,对于一个写博客的感觉还是不太需要这么臃肿,轻便最好,再续…

Ubuntu下安装Intel 9260AC无线网卡教程

奶牛很久没有写ubuntu的文章了,虽然自己的服务器依然是使用ubuntu server,但是比较懒,也没有太多折腾,今天记录一下intel最新的9260ac无线网卡在ubuntu下的安装与使用。

Intel的中文支持页面更新还是有些缓慢,所以驱动程序我们可以在英文页面找到。Linux* Support for Intel® Wireless Adapters

官方页面给出的安装介绍如下

To install firmware:

    Copy the files into the distribution-specific firmware directory, /lib/firmware.
    If the directory does not work, consult your distribution documentation.
    If you configure the kernel yourself, make sure firmware loading is enabled.

首先是下载驱动程序:Intel 9260AC驱动

官方提到intel无线网卡驱动是依赖iwlwifi来加载驱动的,但是安装官方的方法将驱动文件解压后拷贝至/lib/firmware文件夹中重启是无法实现加载的,至少奶牛这里不行,也许你们把内核更新到最新大概可能会实现自动识别吧,但是奶牛把自己折腾的过程分享下。

DeviceKernelsFirmware
Intel® Wireless-AC 92604.14+iwlwifi-9260-th-b0-jf-b0-34.618819.0.tgz

官方的说明是内核要在4.14+,现在内核已经到了4.15RC版本了,4.14也更新到了4.14.10,奶牛第一次是安装的4.14版本内核,结果发现设备可以识别了,但是无法使用。然后找到iwlwifi的支持页面

页面中又说到iwlwifi-fixes with fixes for the current kernel release cycle,所以我们又去看iwlwifi-fixes,在2017-12-05这天更新了内容iwlwifi: add new cards for 9260 and 22000 series

好吧,看样子内核需要在12.05之后,奶牛这里安装的是4.14.10,看日期4.14.04也是12.05日,所以就索性当4.14.04之后的内核版本才支持吧。更新内核也很简单

mkdir kernel
cd kernel
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14.10/linux-headers-4.14.10-041410_4.14.10-041410.201712291810_all.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14.10/linux-headers-4.14.10-041410-generic_4.14.10-041410.201712291810_amd64.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.14.10/linux-image-4.14.10-041410-generic_4.14.10-041410.201712291810_amd64.deb
dpkg -i *.deb

奶牛这里是安装的64位ubuntu操作系统,32位操作系统的同志自己去http://kernel.ubuntu.com/~kernel-ppa/mainline下面找对应的内核版本就好了。

更新内核后重启系统,安装wicd进行无线网卡管理,不要问奶牛为啥要装wicd,因为奶牛发现系统自带的那个管理器还是识别不了。。。好吧,就此折腾完毕。

 

 

关于bench.sh网站的实现

最近看到一条命令很有意思,一起来和大家解读一下,这个网站就是bench.sh

可能搞VPS的朋友很熟悉一条命令

wget -qO- bench.sh | bash

用这条命令可以直接实现linux下运行bench.sh的命令,刚开始奶牛也挺疑惑的,难道wget还自带这种功能,其实不然,命令中的bench.sh是个网站,对,是个网站,访问网站我们可以看到如下提示:

嗨!欢迎使用 Bench.sh

你可以使用以下命令来查看您的 Linux 系统信息,还可以测试网络带宽及硬盘读写速率

wget -qO- bench.sh | bash

或者

curl -Lso- bench.sh | bash

有意思的事儿发生了对么?为什么我们访问网站和我们wget得到的效果不同?因为网站对于user agent进行了识别。如果我们在命令行中执行

wget -O- bench.sh

我们可以看到如下结果:

Resolving bench.sh (bench.sh)... 149.202.55.78
Connecting to bench.sh (bench.sh)|149.202.55.78|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://86.re/bench.sh [following]
Resolving 86.re (86.re)... 104.224.156.154
Connecting to 86.re (86.re)|104.224.156.154|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7017 (6.9K) [application/x-sh]
Saving to: 'STDOUT'

好了,我们看到了一个302的字眼,这个是通过重定向来实现的,直接将使用wget和curl的user agent重定向到了http://86.re/bench.sh这个文件,所以我们wget或者curl得到的是这个文件。这里奶牛猜测原作者应该是用rewrite来写的,直接用web服务器进行ua判定之后rewrite到了这个文件。

奶牛的文章到这里并没有结束,来想想还除了rewrite还有没有其它的实现方法?奶牛感觉还有至少两种实现方法,反代可以实现,还有直接在服务器内进行根目录的重定向也可以实现。奶牛就把后者的实现过程来说一下。web服务器奶牛用的nginx。对网站conf进行配置修改:

server
    {
        listen 80;
        #listen [::]:80;
        server_name bench.2fu.org ;
        set $target bench.2fu.org ;
        if ( $http_user_agent ~* "(wget)|(curl)" )
        {
                set $target bench.2fu.org/true ;
        }
        index index.html index.htm index.php default.html default.htm default.php;
        root  /home/wwwroot/$target;

        include none.conf;
        #error_page   404   /404.html;

        # Deny access to PHP files in specific directory
        #location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }

        include enable-php.conf;

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

        location ~ /.well-known {
            allow all;
        }

        location ~ /\.
        {
            deny all;
        }

        access_log  /home/wwwlogs/bench.2fu.org.log;
    }

然后将true目录下建立一个index.html然后内容就是bench.sh。这样实现的效果是:

Resolving bench.2fu.org (bench.2fu.org)... 
Connecting to bench.2fu.org (bench.2fu.org)|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7017 (6.9K) [text/html]
Saving to: 'STDOUT'

没有302,奶牛更喜欢200 OK 。其实nginx服务器要是折腾折腾配置也很有意思。