my blog my blog

Category: Wordpress
通过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 总结

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

 

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

关于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的命令行和行号的输出功能。

WordPress 4.6使用原生字体代替谷歌字体

 

WordPress使用原生字体取代谷歌字体,所选用字体覆盖了所有主流操作系统,包括:安卓,苹果iOS,Windows,苹果macOS,以及Linux。

 

完整的 CSS 代码是这样的

font-family: -apple-system,            BlinkMacSystemFont,            "Segoe UI",            Roboto,            Oxygen-Sans,            Ubuntu,            Cantarell,            "Helvetica Neue",            sans-serif; 

这其中:

-apple-system: 用于 Safari 浏览器(iOS及macOS),和 Firefox (macOS);
BlinkMacSystemFont: 用于 Chrome 浏览器(macOS);
Segoe UI: 用于微软 Windows 系统;
Roboto: 用于安卓系统及 Chrome OS 系统;
Oxygen-Sans: 用于 KDE 系统;
Ubuntu: 用于 Ubuntu 系统;
Cantarell: 用于 GNOME 系统;
Helvetica Neue: 用于 macOS 10.11 之前的版本;
sans-Serif: 标准会滚字体,以上都没有的话,用这个字体。

可以看出,在 Safari ,Chrome,以及 iOS 和 macOS 上的 Firefox 浏览器中,都将调用系统界面默认字体;而在其他平台上,都将根据具体的字体名字来进行调用。

Nginx服务器配置多域名指向同服务器

 

当我们用Nginx服务器添加新的虚拟主机的时候,都会生成一个vhost的conf文件,当我们把同一个域名的@和www两个地址都直接A解析到Nginx服务器的时候,会发现只有我们添加的vhost里面的那个域名会被正常解析,如果我们想把@和www两个地址都直接解析到这个vhost上,我们可以这样做。

  1. vim /usr/local/nginx/conf/vhost/www.nenew.net.conf 
  2. 修改字段server_name,比如如下方式 
  3. server_name nenew.net www.nenew.net; 

这样子可以直接将nenew.net和www.nenew.net都指向到这个vhost而不用什么301 302那种跳转,好久不搞linux了,奶牛打算弄个新站,重新拾起web。

设置完成后重启Nginx就搞定了。

wordpress主题制作去除wp_nav_menu()函数生成li标签教程

 

奶牛想要写一个wordpress主题,把Twenty Thirteen给做个大手术吧,换上自己需要的东西,今天开始。问题好多,php不熟,wp函数不熟,一个问题,查了半天,代码一行,搞定。

  1. <?php echo strip_tags(wp_nav_menu( array( 'container' => false,'echo' =>false,'items_wrap' => '%3$s','before'=>'<div class="element">','after'=>'</div>') ),"<div><a>"); ?> 

解析下,首先是wp_nav_menu的参数,container就是外面默认包裹的ul标签,这个直接去掉,echo这个参数是把函数的结果作为一个值返回而不是直接输出,items_wrap是单条链接的输出,直接输出即可,before和after这里可以用div来做包裹,自己根据个人需要来,最后就是strip_tags函数了,直接strip_tags("something","tag")将不需要剔除的标签保留,需要剔除的剔除。

关于锚链接跟页面分页

 

很多东西总是用,总是忘,让人很无奈。锚链接的东西自己至少已经查过三次了,还是老忘,继续记录。

锚链接比较常见的应用就是在页面内上下的切换,比如你的回到顶部/底部功能,页面内链接跳转,都可以用锚链接来做。具体实现方法是:

1.建立锚点

锚点就是你链接想到达的目的地,建立方法是定义一个id给<a></a>,比如回到顶部的话,我们需要在最顶部放置一个锚点。

  1. <a id=top>Homepage</a> 

2.建立锚链接

锚点有了,直接把链接挂过去,就是直接引用锚点的id

  1. <a href="#top">back to top</a> 

搞定。

下面说说页面分页,这里是指打印时候的一个分页,页面上看不出来,实际是一个css的属性。可以在<>中直接引用或者自己定义page-break-before的css。

  1. style="page-break-before:always" 

 

WordPress高亮插件推荐之Syntax Highlighter Compress

 

前几天顾客买主机的时候说想让奶牛帮着安装个高亮插件,奶牛平时虽然有写一些代码出来,但是都是命令类的,所以也没用过高亮代码。前阵子帮群里MM小青青在blogcn那里调用过它们的高亮插件,每次插入代码都需要手动来调整,不是很舒服,这次介绍的这款插件Syntax Highlighter Compress可以直接在工具栏中找到,插入代码的时候直接插入就ok,不用手动切换到html上来修改代码了。

插件支持的代码类型有:

AppleScript, ActionScript3, Bash/shell, Coldfusion, C#, C++, CSS, Delphi, Diff, Erlang, Groovy, JavaScript, Java, JavaFX, Perl, PHP, Plain Text, Python, Ruby, Scala, SQL, Visual Basic , XML

Syntax Highlighter Compress下载:点此下载

安装方法,直接解压缩到wordpress的wp-content/plugins下面就可以了。

从Akismet黑名单中解救自己的邮箱网站信息

 

最近跑去别人的blog留言,发现自己的评论老是莫名其妙的被吃掉,Wordpress吃评论的东东一想就是Akismet,难道自己被打入黑名单了?不是吧,奶牛这么兢兢业业的也被打进去了是不是太残忍了,不行,咱得自救啊~~~

google了一下有同样问题的朋友还不少,方法嘛就是手动提交申请到Akismet的官网,来解救自己的帐号。奶牛说下方法:

1.进入网址http://akismet.com/contact/来打开akismet的联系页面,页面全英文,不过没关系,奶牛领着你一步步走。

Questions, comments and criticisms are all welcome.

下方选择 I think Akismet is catching my comments by mistake

我认为Akismet把我的评论误吞了

2.回答那个数学问题?答案自己算哈Your name填你常用的网名,留言那个, 填写你常用的邮箱,也填平时留言那个,Your blog URL你博客的地址,API key (if known)这里选填,直接掠过就好了,

My comments were caught by your Akismet and i think that my infomation is in your blacklist.I want get myself out your blacklist and can leave comments on others’ blog.

3.提交等回复,奶牛昨晚提交的今天就收到了邮件回复

Hi,

Sorry for the problem.  I believe it is fixed now.


-- 
Mark 

吼吼,搞定,黑名单自救完成。

最后想说一句,如果看到自己的垃圾评论中有非垃圾评论,一定一定要把他们救出来啊~~~

Godaddy的数据库备份导入到其他服务器出错的解决方法

 

奶牛有个站在Godaddy放着,前几天Godaddy的主机到期了,所以那个站就转出了,根目录下的内容没啥问题,直接上传就好了,但是数据库却让奶牛犯了难,在cpanel的PHPadmin中怎么导入都出错,这可怎么弄,莫非是数据库不兼容,不至于吧,数据的内容应该问题不大,感觉应该是文件的头部有些问题,索性就拿来自己的奶牛博客的MySQL数据库比较了下,这一比较还真发现了问题了。

用Notepad++打开了奶牛博客的数据量跟的另一个网站的数据库,从头开始一行行比较,其实那个MySQL数据库是明文存放的,打开后会发现文件的内容很清晰,当对比到数据库名称的那行就发现问题了,Godaddy生成的备份多了一行

CREATE DATABASE `XXX` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `XXX`;

感觉这一行也没啥问题啊,但是去掉后对整个数据库的结构也没啥影响,就直接去掉了,然后保存回到Cpanel下进行数据库导入,成功。

仔细想想,八成是因为数据库的版本不太一样,导致这行内容无法正常识别,这行是注释的文字符的类型跟整理类型,管它呢,删除后正常导入就ok啦~~~

Cpanel后台不给力,WordPress博客开启gzip压缩方法介绍

 

前阵子换了服务器后发现流量飞增,自己当时也没想到是没有开启gzip的原因,因为上个服务器默认开启了,当自己想到的时候从后台登录cpanel一看,晕,竟然没有那个优化网站的选项,google之,发现网上介绍的方法也很多,有改php.ini的,有改.htaccess的,呃,方法很多,奶牛也测试了一些,最后,种种原因,木有成功,不是主页不正常显示就是内部错误,这个方法毕竟都是有适应环境的,所以嘛,嘻嘻,不解释。

今天放一个奶牛测试成功了的方法,这个方法针对wordpress博客哦,操作很简单,只要在博客根目录下的index.php文件中加入一行代码就可以了。

找到根目录下的index.php文件中的

define('WP_USE_THEMES', true);

注意,这里的index.php文件是根目录下的,不是主题目录下的哦~

将上面代码的后面添加一行

ob_start('ob_gzhandler');

就可以了,保存退出,上传文件。然后从http://tool.chinaz.com/Gzips/等类似网站就可以查询自己的网站是否开启了gzip,嘻嘻,去试试吧~~~开启gzip可以压缩网页传输的文件,加快网站的载入速度,挺不错滴~