本站点找我功能,被机器人疯狂灌水,实在是恼火的很哇,本功能设计之处是想会不会遇到志同道合的人,结果却被一堆机器人关注,简直浪费我表情,是时候与它做斗争,顺便小小迭代一波博客了。
日志
首先需要解决的就是日志问题,主要有一下两个
- 日志重复
- 日志未分片
日志重复只要是因为使用 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,在未经允许的情况下,不得读取另一个域名内容,但浏览器并不阻止你向另一个域名发送请求