Better

Ethan的博客,欢迎访问交流

站点被机器人疯狂灌水

本站点找我功能,被机器人疯狂灌水,实在是恼火的很哇,本功能设计之处是想会不会遇到志同道合的人,结果却被一堆机器人关注,简直浪费我表情,是时候与它做斗争,顺便小小迭代一波博客了。

日志

首先需要解决的就是日志问题,主要有一下两个

  • 日志重复
  • 日志未分片

日志重复只要是因为使用 forever 启动应用,会默认记录日志,加上站点使用了 winston 模块做日志记录,既然站点本身有日志输出功能了,因此第一步就是关闭 forever 的日志功能

首先使用 forever cleanlogs 清除历史日志,然后启动脚本是添加 -s 参数,具体如下

forever -s start your-script.js

使用 winston 记录时,并没有做日志分片,时间一久,日志文件肯定是越来越大的,就会导致读文件和写文件更占内存,合理的方式就是对日志进行分片,比如按照每天或每周等进行分开存放,这样操作后,除了保障性能外,也方便日后使用日志进行错误排查

首先看看之前的代码

app.use(expressWinston.errorLogger({
    transports: [
        new winston.transports.Console({
            json: true,
            colorize: true
        }),
        new winston.transports.File({
            filename: 'logs/error.log'
        })
    ]
}));

这段代码的作用是将日志打印到控制台,同时输出到指定文件,由于我们总是指定的同一个文件,因此日志便会越来,这里我们引入 winston-daily-rotate-file 模块,它扩充了 winston 模块,使其支持日志分片。具体配置代码如下

app.use(expressWinston.errorLogger({
    transports: [
        new (winston.transports.DailyRotateFile)({
            filename: path.join(process.env.LOGPATH, `${fileName}-%DATE%.log`),
            datePattern: 'YYYY-MM-DD-HH',
            maxSize: '20m',
            maxFiles: '7d'
        })
    ]
}));

简单介绍一下以上字段的意思

  • filename:日志文件名称,可以通过 %DATE% 占位符获得时间
  • datePattern:日志分片模式,例子中是按小时进行分隔,根据实际项目情况而定
  • maxSize:单日志文件最大大小,超过就会进行分片
  • maxFiles:指日志文件最大个数或者最大保存天数,超过会删除旧数据,不设置不删除

特殊情况:本站点由于引用了 formidable 模块,导致请求体数据的获取不是通过 req.body,而是 req.fields 因此还需加上如下代码才算完整

expressWinston.requestWhitelist.push('fields')

机器人灌水

灌水机器人原理:利用抓包工具构造和发送 http 请求,发送非浏览器发送但和浏览器一模一样的 http 请求,而服务器端无法判断是真正的浏览器还是虚拟的浏览器。

常用的防止攻击的方法

  • Token:放一个隐藏可变的 Token (可以理解为密钥),每次提交与服务器校对,若通不过则为外部提交。
  • 图片验证码:用户手动输入正确的图形验证码来证明不是机器人
  • 隐藏域 input:设个空的 input 框设置为隐藏,如果提交过来的数据有值,则为外部提交,因为页面是看不到的,也就不可能提交数据。

通过 Token 的方式感觉不太可行,因为机器人可能会分析你整个 HTML 表单结构,自然就能得到 Token 且正常提交。

通过图形验证码是最常用的一种方式,但缺点就是提高了用户的使用成本,干扰了用户的正常行为。

查阅后发现有个思路不错:为什么人类通过填充验证码证明他们是人类?应该是让机器人通过 JavaScript 来证明它们不是机器人。

因此我们可以基于两个方法来识别机器人

  • 不可见的 JS 验证码
  • 不可见的输入框陷阱

不可见的 JS 验证码方法基于一个事实:机器人在他们的在程序中不会执行 JavaScript 代码。因此我们可以在默认输出的 HTML 表单中添加一个隐藏域,正确答案通过 JS 代码进行填充,服务端获取对应数据,且与正确值进行比对,如果不对就认定为机器人。

不可见的输入框陷阱是基于一个事实:大多数机器人遇到 email 或者 url 关键字表单会自动填充一些信息。同样我们在表单中添加一个隐藏域,name 可以类似为 email-url-website 这种,由于用户是无法看到这个字段的,因此不可能填写值,而机器人碰到这些就有可能去填写值,因此我们只需要在服务端判断该字段是否有值,如果有值就认定为机器人。

还有一个相比要用户填写验证码简单一些的方法是:使用诱饵表单项。在你的表单里面,放一个单行文本框,文本框内写着:如果你不是机器人,请删掉这行数据。机器人来了,肯定不管三七二一直接提交。那么我们在后台判断,凡是填写了表单的访问都是机器人,凡是删除这行数据的人都是访客。

CSRF 误解

这段时间对 CSRF 攻击有了一点新的理解。问题就是:既然浏览器有跨域限制,为何还会存在 CSRF 攻击呢?

首先我们知道 ajax 请求是不能跨域的,而且对于非同源站点的 cookie 也不会随请求发送。因此要想构成 CSRF 攻击,使用 ajax 就行不通了。

因此我们常看到的 CSRF 攻击的手段有

  • 如果请求支持 GET,则直接使用标签的 href 属性或者 src 属性就即可,
  • 如果请求只支持 POST 方式,仍然可以通过构建 form 表单的方式进行提交

那为何对于 ajax 有如此限制,href、src 属性或 form 表单却没有呢?查阅资料主要有两个原因

  • 历史原因要保持兼容性
  • form 表单会刷新页面不会把结果返回给原页面脚本,所以相对安全,而 ajax 是可以读取响应内容的,因此不允许这样做

浏览器同源策略本质:一个域名的 JS,在未经允许的情况下,不得读取另一个域名内容,但浏览器并不阻止你向另一个域名发送请求



留言