从 0 开始构建一个 "固若金汤" 的nginx

0x01 关于 nginx

1
2
3
4
5
配置灵活,简单,运行时占用系统资源较少,功能模块繁多,可扩展性极强,基于 C ,整个工具大小1M左右
支持高并发,仅限于全部为纯静态文件的情况下,因为最终还要取决于后端 [ 脚本和数据库 ] 的实际处理速度
默认 nginx 会自动选择最佳的网络I/O模型,和nginx不同的是,apache默认就会使用select模型,效率较低
上面这些话的意思也就是说,你的C要牛逼到一定的程度,不然,想做深度二次开发基本是扯淡,真心挺佩服毛子的
更多说明,请直接参考官方文档...

0x02 正确理解 linux 对文件,目录,[ 读 写 执行 ] 权限的真正含义,这东西不能靠干说,因为根本理解不扎实,大家可以自己在系统中创建两个普通用户,不停地切换目录文件权限,以深入仔细体会,后面网站目录权限设置要用到这些基础,如果连这些都搞不清,想灵活应用就难了,耐心点,等透彻理解之后,你就会发现真TM简单

目录 读[r / 4] 写[w / 2] 执行[x / 1]

1
2
3
读: ls , dir ... 表示可查看该目录下的文件列表
写: rm , mv, cp, mkdir ,touch ... 表示可在该目录下创建,删除,修改文件或者子目录,不过在这之前,一定要先有执行权限,不然进都进不去,又怎么写呢
执行: cd ... 表示可进入该目录

文件 读[r / 4] 写[w / 2] 执行[x / 1]

1
2
3
读: cat , tac , more , less ,head , tail ... 表示可查看该文件中的内容
写: vi , nano , echo ... 只表示可对文件中的 内容 进行增删改,删除文件还要取决于当前用户对该文件所在目录是否有写权限
执行: 可执行文件,shell脚本... 在linux中任何文件都可以有执行权限,但只有可执行文件和脚本才能真正执行

0x03 nginx 容易出现问题的一些点,只要不是直接可以远程利用的,暂不必过于紧张:

1
2
3
4
nginx 本身可能存在的一些漏洞,如,各类 敏感信息泄露,RCE,DDOS [根据目前已公开的暂未知]...
各种配置错误,如,目录遍历造成的敏感信息泄露,低版本的解析漏洞...
也有躺枪的可能,第三方模块和库漏洞,如,心脏滴血...
...

0x04 常用功能

1
2
3
web [ 各类常规web服务功能,如虚拟主机,基本认证... ]
负载均衡 [ 反向代理 ]
缓存 [ web缓存 ]

0x05 在开始说nginx之前,我们先来简单理解下select模型 ,poll 模型epoll 模型 ,后续有空咱们会再单独拿出来细说,毕竟不是这次的重点,所以只能先简单科普下,能理解大致工作流程即可

IO多路复用

1
2
3
4
5
三种模型同属IO多路复用机制,即同时监视多个描述符,一旦某个描述符读或写就绪,就通知相应的程序进行对应的读写操作
通俗来讲,就是单个进程可以同时处理多个网络连接的IO,原理即通过select,poll,epoll函数不断轮询所负责的所有socket
当某个socket处于就绪状态,就通知指定的用户进程去处理,但select,poll,epoll本质上又都是同步I/O
因为他们都需要在读写事件就绪后自己负责进行读写,也就是说,整个读写过程是处于阻塞状态的
而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷到用户空间

select 模型

1
2
3
4
select目前几乎支持所有的平台,同步IO处理读写会一直处于阻塞状态,select就是这样,调用select函数后会一直处于阻塞状态,直到有描述符就绪
select缺点之一就是,单个进程能够监视的文件描述符的数量有大小限制
在linux上一般为1024,虽然可以通过修改宏定义或者重新编译内核的方式提升这一限制,但这样也会造成效率低下
selectsocket进行扫描时属于线性扫描,即逐个轮询,效率较低

poll模型

1
2
3
4
poll本质上和select区别不太大,它将用户传入的数组拷贝到内核空间,然后轮询每个描述符对应的设备状态
如果设备就绪,则在设备等待队列中加入一项并继续遍历,如果遍历完所有描述符后没有发现就绪设备
则挂起当前进程,直到设备就绪或者超时,被唤醒后它又要再次遍历描述符,整个过程经历了多次无用的遍历
另外,它不再使用select"key-value"的传递方式,且pollfd没有最大数量限制

epoll模型

1
2
3
4
5
6
7
8
9
属于select和poll的增强改进版,epoll对文件描述符的操作有两种模式
LT模式:
当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件
下次调用epoll_wait时,会再次响应应用程序并通知此事件,效率相对来讲较高
ET模式:
当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件
如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件

0x06 此次演示环境,如下:

1
2
CentOS6.8 x86_64 最小化,带基础库安装 eth0: 192.168.3.42 eth1: 192.168.4.14 eth2: 192.168.5.14
nginx-1.12.2.tar.gz

0x07 下载,编译安装 nginx 1.12.2 ,注意,这里暂时就用最新版的稳定版本,实际生产环境中版本可以稍低一些,稳定为主,选择版本时应尽量避开一些已知的高危漏洞版本

创建nginx运行账户,尽量以伪用户身份来运行nginx服务,切记,千万不要直接用root权限来运行nginx,否则别人拿到的webshell权限很可能直接是root权限的 [ 如果你后面的的fastcgi服务端 [php-fpm] 和 nginx使用的是同一用户身份来运行,就很容造成这种情况 ]

1
# useradd -s /sbin/nologin -M nginx

先安装好所需的各中依赖库和编译器,下载 nginx-1.12.2.tar.gz 源码包

1
2
3
4
# yum install pcre pcre-devel openssl openssl-devel gcc gcc-c++ automake zlib zlib-devel -y
# wget http://nginx.org/download/nginx-1.12.2.tar.gz
# tar xf nginx-1.12.2.tar.gz
# cd nginx-1.12.2

源码级修改nginx默认版本号,虽然,还可以后续在配置文件中改,但那个改的毕竟不彻底,它只是简单的把详细的版本号给去掉了,nginx字样还留着呢,其实,这里你改成 apache 也许会更具迷惑性,因为通过在url中转换大小写请求依然是可以试出来目标机器到底是linux还是windows平台,大家看到,我这里是直接改成了IIS 8.5,正常来讲 IIS 8.5 所对应的系统平台应该是windows server 2012R2,但如果别人此时试出来我的机器却是linux平台,很可能就会产生怀疑,因为linux平台是不可能装IIS的,起码暂时是不能的,如果你这里换成apache也许对方就信了,因为apache在linux平台部署再正常不过,然后入侵者,可能就会去尝试一些和apache相关的漏洞,也就是说,你把入侵者引向了一条根本不通的路,另外,有很多获取web服务器banner的扫描工具,基本也都是靠截取的http响应头中server字段的内容 真正靠服务器指纹识别的那种不算,这样一来,也顺便把别人的工具都蒙骗了,反正,尽可能在信息搜集阶段就扰乱入侵者的视线,因为有些漏洞只针对特定类型,特定版本,这样可以一定程度上扰乱对方的判断和后续的渗透方式

1
# vi src/core/nginx.h

查看nginx支持的所有模块,可根据自己的实际需求选择性的启用,对于一些曾经爆过严重漏洞的模块,要格外仔细小心,每个功能模块在其官方文档中都有详细说明及使用样例,具体可参考 http://nginx.org/en/docs/

1
# ./configure --help

开始编译安装nginx,具体编译参数,如下

1
2
3
# ./configure --prefix=/usr/local/nginx-1.12.2 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module
# make && make install
# ln -s /usr/local/nginx-1.12.2/ /usr/local/nginx

编译安装没问题以后,我们先大致看看 nginx 的基本目录结构,初步了解下里面的文件工具都是干什么用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# tree -L 2 /usr/local/nginx
├── client_body_temp
├── conf nginx 配置文件目录
│   ├── fastcgi.conf 后端动态脚本接口配置,如,php,java...
│   ├── fastcgi.conf.default
│   ├── fastcgi_params
│   ├── fastcgi_params.default
│   ├── koi-utf
│   ├── koi-win
│   ├── mime.types
│   ├── mime.types.default
│   ├── nginx.conf nginx 主配置文件
│   ├── nginx.conf.bak
│   ├── nginx.conf.default
│   ├── scgi_params
│   ├── scgi_params.default
│   ├── uwsgi_params
│   ├── uwsgi_params.default
│   └── win-utf
├── fastcgi_temp
├── html nginx 站点目录
│   ├── 50x.html
│   └── index.html
├── logs nginx 自身日志目录
│   ├── access.log 访问日志
│   └── error.log 错误日志
├── proxy_temp
├── sbin nginx 服务管理工具
│   └── nginx
├── scgi_temp
└── uwsgi_temp

再来查看nginx的详细版本,可以看到,我们刚刚在源码中修改的版本号,此时已经生效了

1
# /usr/local/nginx/sbin/nginx -v

关于nginx服务管理工具本身的一些用法

1
# /usr/local/nginx/sbin/nginx -V 查看编译时的详细参数,如果当初nginx不是你编译的

1
2
3
4
5
6
7
8
# cat /usr/local/nginx/logs/error.log nginx错误日志,nginx在运行中有任何错误,都可以尝试去该日志文件中查找原因
# /usr/local/nginx/sbin/nginx -h 查看nginx管理工具使用帮助
# /usr/local/nginx/sbin/nginx 启动nginx服务
# netstat -tulnp | grep ":80" 查看nginx进程是否存在
# /usr/local/nginx/sbin/nginx -s quit 关闭nginx服务
# /usr/local/nginx/sbin/nginx -s reload 平滑重启nginx
# echo "/usr/local/nginx/sbin/nginx" >> /etc/rc.local 加入自启动
# cd /usr/local/nginx/conf/ && mv nginx.conf nginx.conf.bak && egrep -v "^$|#" nginx.conf.default > nginx.conf 简化配置文件

0x08 装完以后,我们就开始真正配置nginx,下面是关于nginx.conf中各标签段用途的简要说明,注意,在配置nginx时,所有语句结尾必须有分号,基本配置语法

main区

1
2
3
worker_processes 1; # 指定进程个数,一般和实际机器可用的CPU核数保持一致
error_log logs/error.log error; # 指定错误日志存放位置,级别推荐crit或error
pid logs/nginx.pid # 进程id号存放位置

事件区 可以在此区域指定处理模型,如,epoll或select

1
2
3
4
5
events {
worker_connections 1024; # 每个进程能处理的最大并发连接数
multi_accept on;
use epoll; # 使用epoll模型
}

http 区 主要用来配置和http自身相关的一些参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
http {
include mime.types;
default_type application/octet-stream;
sendfile on; # 如果是磁盘IO 重负载业务,可以设为off,如,专门提供下载
keepalive_timeout 65;
autoindex off; # 禁止目录遍历,其实默认就是禁止的
# 一个sever标签段即代表一个站点,这里可能也是我们后续要打交道最多的区域
# server 标签段实际中也可以单独抠到指定文件中,然后再用include包含到nginx.conf中,如,各类虚拟主机
server {
listen 80; # 监听的ip和web端口
server_name localhost; # 指定域名
location / { # 定义站点目录位置和索引文件,location 即表示符合什么样的uri就做什么样的操作
root html; # 这里都是相对目录,都是相对于nginx的安装目录
index index.html index.htm; # 定义好主页索引文件
}
# 当出现如下响应状态码时,就返回指定的错误页
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

0x09 配置基于各种类型的虚拟主机,在nginx中,每一个server标签段即表示一个虚拟主机即一个站点,也就是说,在同一台机器上可以同时有N个虚拟主机,只需要在后面接着一直加server段即可,注意,如果客户端直接用ip访问,默认nginx会读取第一个server标签段的配置进行响应,因为nginx无法通过请求头中的host内容来确定是哪个虚拟主机,如果你不在host中指定域名,它默认就会选择第一个

先处理好nginx.conf

1
# vi /usr/local/nginx/conf/nginx.conf

1
2
3
worker_processes 1;
error_log logs/error.log error;
pid logs/nginx.pid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
autoindex off;
# 包含外部配置文件,简化nginx.conf配置,方便后续维护,推荐把不同的server分别定义再不同的文件中,如下
include extra/bwapp.conf;
include extra/dvws.conf;
include extra/drupal7.conf;
include extra/status.conf;
include extra/rwrites.conf;
}

配置基于域名的虚拟主机,通常都是指需要直接暴露在公网中的站点,即所谓的各种子域

1
2
3
4
# mkdir /usr/local/nginx/conf/extra
# mkdir /usr/local/nginx/html/bwapp
# echo "<h2>Hello bwapp ^_^</h2>" > /usr/local/nginx/html/bwapp/index.html
# vi /usr/local/nginx/conf/extra/bwapp.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server {
listen 80;
# 设置别名,其实是可以用url重写来实现的,不过别名效率更高,起码不用再向服务器端请求一次
server_name bwapp.org test.bwapp.org;
location / {
root html/bwapp;
index index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html/bwapp;
}
}

1
2
# /usr/local/nginx/sbin/nginx -t
# /usr/local/nginx/sbin/nginx

配置基于端口的虚拟主机,主要供内部人员办公使用,如各类oa…访问不同的端口即会访问到不同的站点目录

1
2
3
4
# mkdir /usr/local/nginx/html/dvws
# echo "<h2>Hello dvws ^_^</h2>" > /usr/local/nginx/html/dvws/index.html
# cp /usr/local/nginx/conf/extra/bwapp.conf /usr/local/nginx/conf/extra/dvws.conf
# vi /usr/local/nginx/conf/extra/dvws.conf

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 81;
server_name test.dvws.org;
location / {
root html/dvws;
index index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html/dvws;
}
}
1
2
# /usr/local/nginx/sbin/nginx -t
# /usr/local/nginx/sbin/nginx -s reload

配置基于ip的虚拟主机,需要一台服务器上同时有多个ip,

1
2
3
4
# mkdir /usr/local/nginx/html/drupal7
# echo "<h2>Hello drupal7 vuln ^_^</h2>" > /usr/local/nginx/html/drupal7/index.html
# cp /usr/local/nginx/conf/extra/bwapp.conf /usr/local/nginx/conf/extra/drupal7.conf
# vi /usr/local/nginx/conf/extra/drupal7.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
# 默认不给ip的话,表示监听任意ip,也就说,所有ip都可以来连
listen 192.168.3.42:82;
server_name test.drupal7.org;
location / {
root html/drupal7;
index index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html/drupal7;
}
}
1
2
# /usr/local/nginx/sbin/nginx -t
# /usr/local/nginx/sbin/nginx -s reload

0x10 利用nginx自身提供的状态模块,来监控指定机器的nginx服务运行状态,如下,直接新建一个server标签段,访问指定的域名即可查看对应nginx服务状态

1
# vi /usr/local/nginx/conf/extra/status.conf

1
2
3
4
5
6
7
8
server {
listen 80;
server_name test.status.org;
location / {
stub_status on;
access_log off;
}
}
1
2
# /usr/local/nginx/sbin/nginx -t
# /usr/local/nginx/sbin/nginx -s reload

0x11 详细配置nginx访问日志格式,日志格式最好定义在http区段中,而access_log最好放在每个server标签段中,为了便于后续审查分析,这里需要同时记录POSTCOOKIE中的数据 [可能会造成日志量激增],因为绝大多数入侵都是基于post请求和cookie机制的,光靠GET很难真正记录到什么有用的东西

1
# vi /usr/local/nginx/conf/nginx.conf

1
2
3
4
5
6
7
http {
...
log_format main '$remote_addr - $remote_user [$time_local] '
' "$request" $request_body $status $body_bytes_sent '
' "$http_referer" "$http_user_agent" "$http_x_forwarded_for" "$dm_cookie" ';
...
}
1
# vi /usr/local/nginx/conf/extra/bwapp.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
# 注意,想记录cookie数据,需要先在指定的server标签段中定义,然后再到nginx.conf中的http段去引用
set $dm_cookie "";
if ($http_cookie ~* "(.+)(?:;|$)") {
set $dm_cookie $1;
}
listen 80;
server_name bwapp.org test.bwapp.org;
location / {
root html/bwapp;
index index.html;
}
access_log logs/access_bwapp.log main;
# access_log off; # 如果不想记录日志,可以手动关闭
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html/bwapp;
}
}

0x12 利用shell实现自动轮询nginx访问日志,即,所谓的定时日志切割,下面是按天切,根据你自己的实际需求也可按小时切,按分钟切,相应的改下脚本和定时任务的时间设置即可

1
# vi nginx_log.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash
Dateformat=`date +%Y%m%d -d -1day`
Basedir="/usr/local/nginx"
Nginxlogdir="$Basedir/logs"
Logname="access_bwapp"
[ -d $Nginxlogdir ] && cd $Nginxlogdir||exit 1
[ -f ${Logname}.log ]||exit 1
/bin/mv ${Logname}.log ${Dateformat}_${Logname}.log
$Basedir/sbin/nginx -s reload

把刚刚的脚本加到系统计划任务中,实现日志自动轮询切割

1
2
3
4
5
6
# chmod +x nginx_log.sh
# date -s "2014-09-23"
# /bin/sh nginx_log.sh # 多修改几次日期,看能不能正常切割
# hwclock --hctosys
# crontab -e
00 01 * * * /root/nginx_log.sh

0x13 测试url重写功能是否已经可用典型应用,如,伪静态,域名跳转,禁止访问敏感目录,尽量把不同站点的日志放在对应的server标签段中,防止日志单文件过大

1
2
permanent 永久跳转,实际中用的较多
redirect 临时跳转

1
# vi /usr/local/nginx/conf/extra/rwrites.conf
1
2
3
4
5
server {
listen 80;
server_name rewrite.org;
rewrite ^/(.*) https://klionsec.github.io/$1 permanent;
}
1
2
# /usr/local/nginx/sbin/nginx -t
# /usr/local/nginx/sbin/nginx -s reload

0x14 关于 rewrite 基本语法,并非今天的重点,我们后续再单独详细说明

1
rewrite regex replacement [flag];

0x15 nginx的fastcgi_pass模块 [ CGI 即通用网关接口 ] 和 php-fpm 之间的一些工作细节

1
2
3
4
5
6
7
nginx的fastcgi主要负责把web服务器和动态脚本进行分离
也就是说 nginx 只要检测到是动态脚本 [ 在nginx.conf判断文件后缀 ] 就直接抛给fastcgi [ 实际上是fastcgi_pass ]
然后fastcgi客户端 [ 即fastcgi_pass ] 会再丢给fastcgi服务端 [ php-fpm ]
wrapper 的意思就是用来监听某个socket,只要监听到指定的socket就自动调用php解析器去处理
说白点,fastcgi 其实就是一个接口,一个和其他应用程序之间通信的接口
顾名思义,在后端脚本 [ 可以是 java,php,.net...] 和 web服务器上都务必要先存在这么一个接口
之后,两端便通过这个接口进行各种数据通信,即所谓的C/S架构,nginx 为客户端[ fastcgi_pass ],后端脚本进程为服务端[ php-fpm ]

0x16 让nginx和php进行联动,当发现是php的后缀就抛给nginx的fastcgi_pass,再由fastcgi_pass丢给php-fpm去处理

1
# vi /usr/local/nginx/conf/extra/bwapp.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
set $dm_cookie "";
if ($http_cookie ~* "(.+)(?:;|$)") {
set $dm_cookie $1;
}
listen 80;
server_name bwapp.org test.bwapp.org;
root html/bwapp;
location / {
index index.html index.php;
}
location ~.*\.php?$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
access_log logs/access_bwapp.log main;
# access_log off;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html/bwapp;
}
}

0x17 严格控制站点目录权限,对于每个站点所在目录,属主,属组全部丢给root,然后目录一律755,文件一律644,因为上传目录还需要能让用户写,所以要把属主改为nginx,等会儿只需要用nginx来控制上传目录中的php文件不能解析即可

1
2
3
4
# chown -R root.root /usr/local/nginx/html/bwapp/
# find /usr/local/nginx/html/bwapp/ -type f | xargs chmod 644
# find /usr/local/nginx/html/bwapp/ -type d | xargs chmod 755
# chown -R nginx.nginx /usr/local/nginx/html/bwapp/upload

0x18 把用户的所有数据全部死死控制在我们所指定的上传目录中,然后禁止用户在上传目录下执行后端脚本,这里默认是递归应用的,也就是说,即使用户想办法在upload目录下创建了子目录,把webshell传到了子目录里,也依然是无法执行的,当然,不仅仅是上传目录,所有你不想让执行后端脚本的目录,都可以把它加进去,这些配置务必全部加在server标签段的头部位置,注意,下面的配置也全部都是加到server标签段中的,另外,我们要时刻谨记,从客户端过来的一切数据都是有害的,这也是你后续做任何防御的基本准则

1
# vi /usr/local/nginx/conf/extra/bwapp.conf

1
2
3
4
5
6
7
server {
...
location ~ /(attachments|upload|static)/.*\.(php|php5|php4|cgi|jsp)?$ { # 你可以把所有可执行的脚本后缀都加进去,注意,linux严格区分大小写
deny all;
}
...
}

0x19 有时,当你发现,某单个ip瞬间访问过频,很可能就是别人在扫描,此时可直接先用nginx快速封ip,如果单单只是针对web的,大可不必用iptables来搞

1
2
3
4
5
server {
...
deny 192.168.3.0/24;
...
}

0x20 拒绝各种危险请求方法,这里的HEAD方法也可根据实际需求,做适当取舍,下面的意思就是除了GET,POST,HEAD请求方法之外,别的方法一律禁止

1
2
3
4
5
6
7
server {
...
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 403;
}
...
}

0x21 防止各类敏感文件泄露或被下载,下面有些文件后缀可自行根据实际业务需求进行取舍,并非一定要加上所有类型,这里只是简单样例

1
2
3
4
5
6
7
server {
...
location ~* .(txt|conf|bash_history|bash_profile|bashrc|xml|bak|sql|log|gz|zip|svn|git|inc|mdf|sh)$ {
deny all;
}
...
}

0x22 重定向到所有服务端错误到指定的404页面,尽量不要在前端留下任何错误,上线之前把所有的debug功能全部关闭,有错误,直接打log

1
2
3
4
5
6
7
8
server {
...
error_page 500 502 503 504 /404.html;
location = /404.html {
root html/bwapp;
}
...
}

0x23 防止跨目录,把webshell限死在当前站点目录下,让其上一级目录都翻不了,方法很简单,在每个站点根目录下,新建一个.user.ini文件,加入以下语句,open_basedir为当前站点目录的绝对路径,注意此文件权限,只有root能写,另外,关于.user.ini文件用途请直接参考php官方说明

1
2
3
4
# vi /usr/local/nginx/html/bwapp/.user.ini
open_basedir=/usr/local/nginx/html/bwapp:/tmp/:/proc/
# chown root.root .user.ini
# chmod 644 .user.ini

0x24 降低溢出风险,在awvs官方站点中有针对此项的详细说明

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
...
client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 1k;
large_client_header_buffers 2 1k;
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 5 5;
send_timeout 10;
...
}

0x25 尽可能防止各种恶意爬取及非正常访问,避免造成部分敏感信息泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
...
if ($http_user_agent ~ "Lua"){
return 403;
}
if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) { # 可以把你所知道的所有爬虫工具都写进去
return 403;
}
if ($http_user_agent ~ ^$) {
return 403;
}
...
}

0x26 限制各类搜索引擎蜘蛛的抓取频率,先在http标签段中定义好,再在server标签段中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http{
...
limit_req_zone $anti_spider zone=anti_spider:60m rate=100r/m; # 每分钟只能处理100个请求
...
}
server{
...
limit_req zone=anti_spider burst=5 nodelay;
if ($http_user_agent ~* "baiduspider|bingbot|Yahoo! Slurp|msnbot") { # 可以把你知道的所有的搜索引擎蜘蛛都加上
set $anti_spider $http_user_agent;
}
...
}

0x27 简单的防sql注入[实战中极不建议这么干],可以自行把各种注入语句里面的关键字都加进去,单单这样,还是很容易被bypass掉,比如,大小写,有些朋友可能会说,加个i不就行了,确实,这样是不区分大小写了,不过最好不要直接在nginx配置中这样搞,极易误报,而且设置白名单非常麻烦,这里仅仅只是为了告诉大家,可以用nginx这么玩,但自己真正部署的时候就不要这么干了,毕竟这防不住啥,推荐直接自行去定制各种开源waf

1
2
3
4
5
6
7
server {
...
if ($request_uri ~* (.*)(insert|select|delete|update|count|\*|%|master|truncate|declare|\'|\;|and|or|\(|\)|exec)(.*)$ ) {
rewrite ^(.*) https://klionsec.github.io redirect;
}
...
}

0x28 也可以尝试实时从nginx 访问日志中,快速捕捉各类敏感web攻击特征并预警,详情可参考本博客的另一篇文章 入侵取证 [ web日志分析初步 ]

0x29 合理配置robots.txt文件,尽量不要把一些敏感比较信息,如,各种账号密码,敏感参数,各种web,vpn入口等…都暴露给各种搜索引擎,另外,各类用于测试的探针如,phpinfo之流,就不用多说了吧,全部清干净,被人扫目录扫出来,前面有些东西就白干了

0x30 尽可能隐藏住后台,不要用太大众的名字或者网站域名什么的,反正别让用户能随便猜到就行,限制用户直接从公网访问到网站后台,可以在nginx中获取用户原始ip,然后判断只允许指定的内网段才能访问后台

0x31 利用lsyncd实时监控指定用户上传目录中的create事件,然后,过滤出可执行后缀脚本文件,打成zip,再配合百度WebShell检测引擎提供的扫描接口https://scanner.baidu.com 上传zip,初步实现全自动秒删webshell,据朋友反应,实际的检测效果还算不错

0x32 最后,定期去关注nginx官方发布的各种高危补丁,适时修补即可

0x33 更多,待续…

小结:
    实际部署过程中做好这些最基本的防御措施即可,nginx自身能做事情确实还有很多,但完全想利用nginx实现一个准waf的效果,毕竟不太现实,也真的没那必要,如果对安全性真的有特别要求,建议大家还是自行去深度定制各类开源waf,说这么多,只是想告诉大家,真正的防御并不是盲目的人云亦云,漫无目的的象征性做做样子,你自己也要时刻跟进入侵者的各种猥琐攻击手法,仔细不停地复现分析,适时针对性改进防御策略才行,在安全的字典里,没有一劳永逸,有的是只是永无休止的对抗,唯一的办法就是未雨绸缪,只有比敌人更勤奋,才有占得先机的可能,另外,这里说的单单也只是针对nginx,大家都很清楚,安全本来就是一个面,只是一个节点的安全,根本就算不上安全,另外,关于脚本自身的安全以后还会有非常大篇幅说明,web,作为入侵者的首选途径,也是我们的重点防御对象,如果真正防住了web,基本就防住了绝大部分的外部入侵,话说回来,这一切还都是建立在对方没有手握各种远程0day的情况下,如果人人都像NSA那样,基本也就没啥是安全的了,只需一个端口,就直接捅到你怀疑人生,当然,你的实际价值肯定要先远远要超过买0day的钱,别人才有可能会用0day,毕竟这个东西基本上就是一次性的,万一被被别人的蜜罐套走了,岂不得不偿失,这里突然想起别人说过的一句话0day固然可怕,但,你还不配,哈哈……说一千道一万,nginx的主要作用还是为了更好的提供web服务[ 一切还以满足实际业务需求和高性能为主 ],总不能一味地为了安全连钱都不挣了吧,好好活下去比什么都重要,今天不自觉废话稍微有点儿多了,大家不要建议哈,祝好运 ^_^