本节内容:
nginx 正向代理,nginx缓存文件
与nginx 反向代理并缓存静态文件的区别在于:这是内部机器用来通过Nginx上外网的方式。
其他配置差不多,仅下面有区别:
listen 83;
location / {
resolver 8.8.8.8;
proxy_pass http://$http_host$uri$is_args$args;
proxy_cache STATIC;
proxy_cache_valid 200 10d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
}
}
关键点:
proxy_pass 后面的配置。
您可能感兴趣的文章:
nginx正向代理配置简单一例
nginx中配置proxy正向代理
本节内容:
Nginx日志模块
在Nginx中,ngx_errlog_module模块专门用于处理nginx日志信息,是nginx的core模块之一;
通过解析error_log配置项将不同等级的日志信息输出到指定的文件中。nginx启动过程中在解析配置文件时遇到error_log 配置项就调用errlog模块的ngx_error_log函数来解析。
ngx_error_log函数将error_log配置项的值保存在ngx_cycle->new_log成员中,当配置文件中有多条error_log配置项生效时,通过ngx_cycle->new_log.next成员将它们组织起来。
如果配置文件中没有error_log配置项,在配置文件解析完后调用errlog模块的ngx_log_open_default函数将日志等级默认置为NGX_LOG_ERR,日志文件设置为NGX_ERROR_LOG_PATH(该宏是在configure时指定的)。
由此可看无论配置文件中是否有error_log配置项始终会有日志输出,nginx中禁止输出日志唯一的办法:
将error_log配置项的输出日志文件名指定为/dev/null。
1,nginx日志等级
debug级别最低,stderr级别最高;圆括号中的数据是对应日志等级的值。
2,error_log配置项
默认: error_log logs/error.logs error
日志有三种输出方式,输出到文件(file)、输出到屏幕(stderr)、输出到syslog(syslog),以debug_开头的表示输出的调试日志类型。输出日志信息时转入的日志等级大于等于指定的等级时才会输出日志,如:
所有大于等于err等级的日志信息输出到logs/err.logs文件中,即err、crit、alert、emerg、stderr日志信息被输出。
当配置文件中有多条error_log配置项生效时,情况就不一样,如下所示:
error_log logs/alert.logs alert
当日志级别大于alert时会同时输出到warn.logs及alert.logs文件中,当日志级别介于alert到warn之间时只输出到warn.logs文件中。当日志级别低于warn时不会输出。
3,保存日志对象的结构体
ngx_uint_t log_level; //日志等级
ngx_open_file_t *file; // 记录日志文件信息
ngx_atomic_uint_t connection; //引用该日志对象的连接数
ngx_log_handler_pt handler; //输出日志时的回调函数,日志等级不为debug时有效。
void *data; //配合handler成员使用,给handler回调函数传数据
/*
* we declare "action" as "char *" because the actions are usually
* the static strings and in the "u_char *" case we have to override
* their types all the time
*/
char *action; //记录日志前,nginx当前的动作(即正在做什么)
ngx_log_t *next; //指向下一个日志对象
};
4,常用的日志输出接口:
if ((log)->log_level >= level) ngx_log_error_core(level, log, __VA_ARGS__)
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const charchar *fmt, ...);
#define ngx_log_debug(level, log, ...) \
if ((log)->log_level & level) \
ngx_log_error_core(NGX_LOG_DEBUG, log, __VA_ARGS__)
ngx_log_error宏先判断传入的level是否小于ngx_cycle->new_log.log_level(当存在多个log对象时new_log保存的是优先级最低的对象,即等级值最大的对象)。如果level小于就不需要输出日志。
ngx_log_error_core函数参数:
level表示日志等级,log表示日志对象,err通常设置为等于errno。
5,ngx_log_open_default函数:
ngx_int_t
ngx_log_open_default(ngx_cycle_t *cycle)
{
static ngx_str_t error_log = ngx_string(NGX_ERROR_LOG_PATH);
/***配置文件中不存在生效的error_log配置项时new_log.file就为空***/
if (cycle->new_log.file == NULL) {
cycle->new_log.file = ngx_conf_open_file(cycle, &error_log);
if (cycle->new_log.file == NULL) {
return NGX_ERROR;
}
cycle->new_log.log_level = NGX_LOG_ERR;
}
return NGX_OK;
}
6,ngx_error_log函数:
static charchar *
ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, voidvoid *conf)
{
ngx_log_t *dummy;
//new_log用于保存优先级最低的日志对象
dummy = &cf->cycle->new_log;
return ngx_log_set_log(cf, &dummy);
}
7,ngx_log_set_log函数:
ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head)
{
ngx_log_t *new_log;
ngx_str_t *value, name;
/***第一次被调用时log_level ==0 ,后续被调用时需重新申请日志对象***/
if (*head != NULL && (*head)->log_level == 0) {
new_log = *head;
} else {
new_log = ngx_pcalloc(cf->pool, sizeof(ngx_log_t));
if (new_log == NULL) {
return NGX_CONF_ERROR;
}
if (*head == NULL) {
*head = new_log;
}
}
/***elts保存有error_log配置项参数,value[0] == "error_log", ...***/
value = cf->args->elts;
/***日志信息将输出到屏幕***/
if (ngx_strcmp(value[1].data, "stderr") == 0) {
ngx_str_null(&name);
cf->cycle->log_use_stderr = 1;
} else {
/***取出文件名***/
name = value[1];
}
/***分配一个文件对象。ngx_cycle->open_files成员中保存有已经打开的文件对象,如果name文件已打开直接返
回该对象,否则重新申请。***/
new_log->file = ngx_conf_open_file(cf->cycle, &name);
if (new_log->file == NULL) {
return NGX_CONF_ERROR;
}
/***根据error_log配置项设置日志等级***/
if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}
/***将日志对象插入到ngx_cycle->new_log队列中***/
if (*head != new_log) {
ngx_log_insert(*head, new_log);
}
return NGX_CONF_OK;
}
8,ngx_log_set_levels函数:
ngx_log_set_levels(ngx_conf_t *cf, ngx_log_t *log)
{
ngx_uint_t i, n, d, found;
ngx_str_t *value;
if (cf->args->nelts == 2) {
log->log_level = NGX_LOG_ERR;
return NGX_CONF_OK;
}
value = cf->args->elts;
for (i = 2; i < cf->args->nelts; i++) {
found = 0;
/***匹配日志等级***/
for (n = 1; n <= NGX_LOG_DEBUG; n++) {
if (ngx_strcmp(value[i].data, err_levels[n].data) == 0) {
if (log->log_level != 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate log level \"%V\"",
&value[i]);
return NGX_CONF_ERROR;
}
log->log_level = n;
found = 1;
break;
}
}
/***匹配debug日志类型***/
for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1) {
if (ngx_strcmp(value[i].data, debug_levels[n++]) == 0) {
if (log->log_level & ~NGX_LOG_DEBUG_ALL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid log level \"%V\"",
&value[i]);
return NGX_CONF_ERROR;
}
log->log_level |= d;
found = 1;
break;
}
}
/***没有找到,该条error_log配置项语法错误***/
if (!found) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid log level \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
}
/***当日志等级为debug时,将该日志对象的等级置为debug all,表示输出所有debug信息***/
if (log->log_level == NGX_LOG_DEBUG) {
log->log_level = NGX_LOG_DEBUG_ALL;
}
return NGX_CONF_OK;
}
9,ngx_log_insert函数:
static void
ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log)
{
ngx_log_t tmp;
/***新插入的对象等级小于队列头等级时交换对象值***/
if (new_log->log_level > log->log_level) {
/*
* list head address is permanent, insert new log after
* head and swap its contents with head
*/
tmp = *log;
*log = *new_log;
*new_log = tmp;
log->next = new_log;
return;
}
/***在列队中找合适的位置,将新对象插入其中***/
while (log->next) {
if (new_log->log_level > log->next->log_level) {
new_log->next = log->next;
log->next = new_log;
return;
}
log = log->next;
}
log->next = new_log;
}
您可能感兴趣的文章:
nginx 日志分析的实例学习
Nginx关闭日志的方法
Nginx日志分析 Nginx日志切割与Awstats配置
nginx日志配置、Nginx日志分割
有关nginx日志格式的设计分享
nginx日志统计访问时间的例子
nginx日志配置文件的格式说明
一个分割ngnix网站日志的Shell脚本
nginx日志报大量400错误的解决方法
nginx关闭favicon.ico日志记录的方法
nginx与apache日志格式的区别
nginx日志中记录cookie的实现方法
修改nginx访问日志的时间格式的方法
Nginx日志中过滤关键字的写法
nginx 日志分析实例
本节内容:
nginx安全配置
一,简易版
比如,社区在搞福利,在广场上给大家派发红包。而坏人派了一批人形的机器人(没有语言模块)来冒领红包,聪明工作人员需要想出办法来防止红包被冒领。
于是工作人员在发红包之前,会给领取者一张纸,上面写着“红包拿来”,如果那人能念出纸上的字,那么就是人,给红包,如果你不能念出来,那么请自觉。于是机器人便被识破,灰溜溜地回来了。
在这个比喻中,人就是浏览器,机器人就是攻击器,可以通过鉴别cookie功能(念纸上的字)的方式来鉴别他们。
Nginx的配置文件:
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,红包拿来”。于是一群只会嗡嗡叫着“红包拿来”的机器人又被撵回去了。
当然,为了配合说明问题,每个机器人是有户口本的,被赶回去的原因是不会念自己的名字,虽然这个有点荒诞,唉。
然后,来看下这种方式的配置文件写法
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模块来进行实现。
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看来,确实攻击流量和普通浏览流量是一样的。那么如何防御呢?下节会告诉你答案。
四,请求频率限制
不得不说,很多防CC的措施是直接在请求频率上做限制来实现的,但是,很多都存在着一定的问题。
那么是哪些问题呢?
首先,如果通过IP来限制请求频率,容易导致一些误杀,比如我一个地方出口IP就那么几个,而访问者一多的话,请求频率很容易到上限,那么那个地方的用户就都访问不了你的网站了。
于是你会说,我用SESSION来限制就有这个问题了。嗯,你的SESSION为攻击者敞开了一道大门。为什么呢?看了上文的你可能已经大致知道了,因为就像那个“红包拿来”的扬声器一样,很多语言或者框架中的SESSION是能够伪造的。以PHP为例,你可以在浏览器中的cookie看到PHPSESSIONID,这个ID不同的话,session也就不同了,然后如果你杜撰一个PHPSESSIONID过去的话,你会发现,服务器也认可了这个ID,为这个ID初始化了一个会话。那么,攻击者只需要每次发完包就构造一个新的SESSIONID就可以很轻松地躲过这种在session上的请求次数限制。
那么要如何来做这个请求频率的限制呢?
首先,先要一个攻击者无法杜撰的sessionID,一种方式是用个池子记录下每次给出的ID,然后在请求来的时候进行查询,如果没有的话,就拒绝请求。这种方式不推荐,首先一个网站已经有了session池,这样再做个无疑有些浪费,而且还需要进行池中的遍历比较查询,太消耗性能。希望的是一种可以无状态性的sessionID,可以吗?可以的。
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模块来完成。
...
limit_req_zone $cookie_token zone=session_limit:3m rate=1r/s;
}
然后只需要在上面的token配置后面中加入
于是,又是两行配置便让nginx在session层解决了请求频率的限制。不过似乎还是有缺陷,因为攻击者可以通过一直获取token来突破请求频率限制,如果能限制一个IP获取token的频率就更完美了。可以做到吗?可以。同时,也要感谢一下Tengine,Tengine的limit模块可以支持多个变量。本文的所有的实验均在Tengine下完成。
...
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;
}
[plain] view plaincopy
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 = "") {
return 403;
}
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能够扛得住这些流量,然后带宽不被堵死。如果你家门被堵了,你还想开门营业,那真心没有办法了。
然后,做完流量上的防护,让来看看对于扫描器之类的攻击的防御。
六,防扫描
ngx_lua_waf模块 https://github.com/loveshell/ngx_lua_waf
一个不错的waf模块,这块也无需重复造轮子。可以直接用这个模块来做防护,当然也完全可以再配合limit模块,用上文的思路来做到一个封IP或者封session的效果。