通过Nginx启用HTTP验证(HTTP Authentication)

在后台我经常可以看到有扫描器通过wp-login.php穷举密码登陆试图wordpress,也可以看到web应用方面的各种服务,我们始终无法防御各种各样未知的web漏洞,所以我想引入http验证是非常不错的一种手段。
通过http验证我们可以阻挡各种未经授权的访问以缓解各种漏洞。因为启用之后只有输入正确的用户密码才允许访问web内容。
首先配置HTTP认证我们需要创建认证账户密码文件。
我们可以通过openssl或者htapasswd来创建
方法1:htapasswd

#要使用htapasswd需要安装apache2-utils
sudo apt-get install apache2-utils
htpasswd -nb username password >> /etc/nginx/http_passwd

方法2:使用openssl

#printf "username:$(openssl passwd -crypt password)\n" >> /etc/nginx/http_passwd
#Apache variant加密方式,更安全
printf "username:$(openssl passwd -apr1 password)\n" >> /etc/nginx/http_passwd
# cat /etc/nginx/http_passwd

创建了passwd文件(/etc/nginx/http_passwd)之后,我们需要修改Nginx的配置启用http验证。
Ubuntu上Nginx配置文件在/etc/nginx/sites-enabled 下面。
我们这里对应是/etc/nginx/sites-enabled/exvs
为需要保护的文件或者目录添加类似下面这样的内容

location / {
    auth_basic           "Need HTTP Authentication";
    auth_basic_user_file /etc/nginx/http_passwd;
}

关于配置的详细信息可阅读Nginx文档:https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html
最后重启Nginx
systemctl restart nginx
再次访问制定的文件或者目录则需要http验证了。

2017/01/15 记一次phpmyadmin的漏洞修复

这两天阿里云发短信提醒我的机器安装存在漏洞的phpmyadmin,上控制台一看提醒有这么个提示。

通过CVE编号CVE-2016-6617我找到了官方对于此漏洞的详细说明。
https://www.phpmyadmin.net/security/PMASA-2016-40/
官方给此漏洞的编号是:PMASA-2016-40
对此漏洞的描述是:A vulnerability was reported where a specially crafted database and/or table name can be used to trigger an SQL injection attack through the export functionality.
翻译过来就是,通过特殊的数据库,表名会导致使用导出功能的时候产生SQL注入的漏洞。
所有早于4.6.4之前的版本都会收到此漏洞影响。目前最新版4.6.5已经修复此漏洞,所以直接升级到最新版本的phpmyadmin即可。

值得注意的是,我使用的Ubuntu 16.04 LTS包版本库中提供的最新版是phpmyadmin_4.5.4.1-2ubuntu2,也就是说通过apt-get安装的phpmyadmin仍然存在此漏洞,直接apt-get upgrade并不能解决问题,这也是阿里云提示检测到漏洞的原因。

如果无法确定自己安装的包版本
可通过下列命令查询包版本

dpkg -s phpmyadmin
#或者
dpkg -s phpmyadmin | grep "Version"

修复方案:
方案1:可以登陆phpmyadmin官网https://www.phpmyadmin.net下载最新版的包覆盖安装路径即可。
方案2:对于Ubuntu用户可以使用phpmyadmin官方提供的ppa源,这样就能直接更新到最新的包。
官方提供的安装手册链接:https://docs.phpmyadmin.net/en/latest/setup.html
具体到Ubuntu用户来说,可执行下列命令

sudo apt-get install software-properties-common
#添加PPA源
sudo add-apt-repository ppa:nijel/phpmyadmin
sudo apt-get update
sudo apt-get upgrade
#如果提示The following packages have been kept back
#那么可以使用sudo apt-get dist-upgrade完成更新

由于我希望能尽量使用包管理器解决问题,这样能够长期保持最新版本,所以我选择了方案2。
至此phpmyadmin就更新完毕了。

Ubuntu 16.04.01 LTS 安装 Nginx PHP MYSQL环境

blog之前运行在Ubuntu 14.04上,虽然是LTS版本不过毕竟新的LTS版本16.04已经出来了这么久,得益于VPS供应商增加了16.04的镜像故找个时间把blog重新安装了一下,这里留下记录仅供参考。

之前服务器使用的apache,不过更新版本的时候考虑到http2的支持,所以服务器这次选择了nginx来作为web服务。
很多网站表示Ubuntu 16.04的nginx不支持http2需要手动编译,然而截止到目前为止仓库里最新的deb包已经默认支持http2。

#运行apt-cache show nginx可以查看nginx包版本
apt-cache show nginx
##################################################################################
Package: nginx
Version: 1.10.0-0ubuntu0.16.04.4
#安装后运行nginx -V可获得nginx的版本信息,可以从获取的信息中看到--with-http_v2_module
#故目前的包版本中已经支持http2,无需额外编译安装

那么直接apt-get一下安装nginx即可

apt-get -y install nginx

然后安装mysql

apt-get -y install mysql-server mysql-client

可以运行一个mysql的安全设置向导mysql_secure_installation进行一些安全性配置。使用脚本可以做到,更改root密码、移除MySQL的匿名用户、禁止root远程登录、删除test数据库。使用上面的这些选项可以提高MySQL的安全。
由于blog的vps配置比较低,所以为了预防内存不足需要修改一下mysqld的配置。
修改/etc/mysql/mysql.conf.d/mysqld.cnf

innodb_buffer_pool_size = 32M

最后安装php,Ubuntu 16.04默认已经是php7了,而不是php5,需要注意一下。

apt-get -y install php php-mysql

安装完成之后需要对nginx做一些配置。Ubuntu下面默认的配置目录是/etc/nginx/。
首先是/etc/nginx/nginx.conf
这里我修改了server_tokens选项为off
server_tokens的作用是隐藏Nginx的版本号,隐藏web服务的版本可以提高攻击难度,有利于提高安全性。

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;
	server_tokens off;
	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
# 
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}

接着是/etc/nginx/sites-enabled,nginx启动时会读取目录下的所有配置。
我个人删除了/etc/nginx/sites-enabled/default
因为我个人的配置准备直接写在一个文件里,所以就没必要使用default了

rm /etc/nginx/sites-enabled/default
touch /etc/nginx/sites-enabled/exvs

出于安全性考虑,我选择全站强制https,所以80端口只需要做一些跳转工作就行了。配置如下。

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    #几个常见的安全http头
    #HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload";
    add_header Content-Security-Policy "script-src 'self'";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    
    # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://www.exvs.org$request_uri;
}

80端口就只有一个作用,返回301跳转到https链接。关于几个常见的安全http头我们大致解释一下。
Strict-Transport-Security(HSTS)
一个网站接受一个HTTP的请求,然后跳转到HTTPS,用户可能在开始跳转前,通过没有加密的方式和服务器对话,比如,用户输入http://example.com,传统的做法是服务器返回301跳转到https://example.com。但是服务器返回301时并不是加密的,这将导致服务器返回的结果容易篡改,从而产生了劫持的现象。启用HSTS后,用户访问网站后浏览器会把网站加入HSTS列表,之后如果再次访问则强制https访问,从而尽可能的避免劫持的产生。
注意的是,一旦启用在参数时间内不能关闭https访问。
Content-Security-Policy
W3C的Content Security Policy,简称 CSP,CSP旨在减少跨站脚本攻击。这里我们配置为Content-Security-Policy: default-src ‘self’
这样可以阻止非本站的资源被浏览器加载,这样即使页面遭到篡改仍然不会加载非本站的元素。
X-Frame-Options
设置这个http头之后将会阻止浏览器通过FRAME加载你的网站页面。
X-XSS-Protection
设置此http头后,如果IE检测到了跨站脚本攻击(Cross-Site Scripting),那么IE将对页面做相应的修改以阻止攻击的发生。

80端口配置好了,那么剩下的就是配置443端口。HTTPS配置起来比较麻烦。
我们选择Nginx其中一个考虑是默认支持http2,启用http2很简单。
listen 443 ssl http2;
只要像这样配置端口即可。
出于安全性考虑,我选择放弃浏览器兼容。
SSLv2和SSLv3目前已经被证明是不安全的了,目前为止最新的TLS标准是1.2。所以我选择只支持TLS1.2。通过如下配置完成。
ssl_protocols TLSv1.2;
注意:只支持TLSv1.2代表着放弃很大部分老旧浏览器,他们将无法访问。
ssl_ciphers用来控制加密协议,放弃低强度的加密算法很显然有助于提高安全性。具体放弃或者应该启用什么样的算法我推荐直接参考https://cipherli.st
或者
https://wiki.mozilla.org/Security/Server_Side_TLS
ssl_certificate
相信大家很熟悉了,TLS证书,我这里选择的是Let’s Encrypt的证书。个人推荐使用acme.sh签发证书。参见github的地址https://github.com/Neilpang/acme.sh
ssl_dhparam
通过DHE密钥,我们能获得更高的安全性.Diffie Hellman 算法会确保预主密钥绝不会离开客户端和服务器,而且不能被中间人攻击所拦截。
我们可以通过openssl生成一个强DH密钥。

openssl dhparam -out dhparam.pem 4096

做完这些配置如下所示

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_dhparam /etc/nginx/tls-cert/exvs/dhparam.pem;
    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate /etc/nginx/tls-cert/exvs/fullchain.pem;
    ssl_certificate_key /etc/nginx/tls-cert/exvs/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;


    # modern configuration. tweak to your needs.
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';
    ssl_prefer_server_ciphers on;

    # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    
    # OCSP Stapling ---
    # fetch OCSP records from URL in ssl_certificate and cache them
    ssl_stapling on;
    ssl_stapling_verify on;

    ## verify chain of trust of OCSP response using Root CA and Intermediate certs
    #ssl_trusted_certificate /etc/nginx/tls-cert/exvs/root_ca_cert_plus_intermediates;
    #resolver 208.67.222.222 208.67.220.220 valid=300s;
    resolver 208.67.222.222 208.67.220.220;
    resolver_timeout 5s;

    server_name exvs.org;
    return 301 https://www.exvs.org$request_uri;
}

之后我还需要安装phpmyadmin方便配置,一样通过apt-get就可以安装。

apt-get install phpmyadmin php-zip

如果不安装php-zip那么在导入zip格式压缩后的sql数据是会提示“您正在载入不支持的压缩格式”
备注:由于Ubuntu源提供的包版本可能相对较phpmyadmin较为老旧,固推荐使用phpmyadmin官方提供的ppa源来保持更新。关于PPA源的有关信息可参见本站文章https://www.exvs.org/?p=263

剩下的就是站点的配置,包括启用PHP配合Nginx,还有配置phpmyadmin的别名,配置如下。
当然,虚拟机主机不想配置的话直接软连接一下也是可以的。

ln -s /usr/share/phpmyadmin/ /var/www/exvs/phpmyadmin
server {
	listen 443 http2;
	listen [::]:443 http2;
	root /var/www/exvs;

	# Add index.php to the list if you are using PHP
	index index.php index.html index.htm index.nginx-debian.html;
	server_name www.exvs.org;
    
	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

    location ~ /phpmyadmin/.+\.php.*$ {
        if ($fastcgi_script_name ~ /phpmyadmin/(.+\.php.*)$) {
            set $valid_fastcgi_script_name $1;
        }
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /usr/share/phpmyadmin/$valid_fastcgi_script_name;
        include  fastcgi_params;
    }
    location ~ /phpmyadmin/?(.*)$ {
        alias /usr/share/phpmyadmin/$1;
        #root /usr/share/phpmyadmin;
        index index.php;
    }

    # deny access to .htaccess files, if Apache's document root
	# concurs with nginx's one
	#
	location ~ /\.ht {
		deny all;
	}
    location ~ /.*\.config {
		deny all;
	}
    location ~ /.*\.user\.ini {
		deny all;
	}
    location ~ /wp-config\.php {
		deny all;
	}
    location ~ /wp-config(.*)\.php {
		deny all;
	}
    location ~ /xmlrpc\.php {
		deny all;
	}
    
	# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
	#
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
	
	#	# With php7.0-cgi alone:
	#	fastcgi_pass 127.0.0.1:9000;
	#	# With php7.0-fpm:
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
	}
}

那么到此只需要导入之前wordpress的文件,恢复数据库那么blog就重新配置完成了。
配置完成后最后只需要使用
systemctl restart nginx
重启nginx即可。
配置完成后可以通过ssllabs来检测https的安全性。
https://www.ssllabs.com/ssltest/
如果按照本文进行配置的话,应该会得到A+评级。
https://www.ssllabs.com/ssltest/analyze.html?d=exvs.org