Web安全:細說後端密碼安全防範

上一次筆者寫了一篇關於安全的文章:【Web安全:細說前端XSS攻擊與防範】投石問路,效果還不錯,深受鼓舞。

其實和web相關的安全問題不僅於此,大致有【XSS攻擊】、【前端CSRF攻擊】、【前端cookies問題】、【前端點擊劫持問題】、【HTTP傳輸安全】、【DOS攻擊】、【後端密碼安全】、【SQL注入】等一系列問題,慢慢給大家總結出來吧。。。
(爲什麼這麼多“前端…”?emmmmmmm,可能是怕攻擊頻繁,對服務器造成隱患吧。但其實這些前端/後端的防禦措施一般都要和後端/前端結合起來才能夠更有效的運行)

今天就和各位嘮嘮“後端密碼安全”,簡單說說(其實說是沒啥說的…),本文代碼主要適用於中小型項目,如有不當之處,還請之處嘞。

fj

密碼安全兩三事

現在一般一提起密碼安全,就會想到這些年“大火的”數據庫泄露 —— 事實上,這也確實是現在密碼安全防範的一個重點:
泄露渠道:

  • 數據庫被“偷”
  • 服務器被入侵
  • 通訊被竊聽
  • 內部人員泄露數據
  • 撞庫

有了早些年處於熱搜的“CSDN幾百萬用戶數據泄露”、“京東賬號密碼數據庫泄露”、“12306數據庫泄露”事件,業界終於認識到數據庫的“不太安全性”,以及做出了一些基本的防禦措施,比如:

  • 嚴禁密碼明文存儲(防泄露)
  • 單向變換(防泄露)
  • 密碼本身複雜度要求(防猜解)
  • “加鹽”(防猜解)

後來做了多少努力咱暫且不提,但是現在所說密碼安全的防禦措施,大多數人應該都下意識想到哈希算法,它有這樣幾個特點:

  1. 明文-密文一一對應
  2. 雪崩效應:只要有一點不對應,則後面所有的都不對應
  3. 密文無法反推到明文

哈希算法常見有:MD5、SHA1、SHA256…

說到這就會有人問了:第三個特點怎麼體現?現在有好多這種MD5反推的網站耶。。。
那是對於簡單一些的明文:比如123456之類的。因爲他們用的是暴力破解(建立了數據字典 —— 也就是我們常說的“彩虹表”)。
可想而知,對於一些非常複雜的密碼,而且甚至於加密了好多次,那破解幾乎就無從談起了(字典存儲是需要空間的,查字典也是要時間的):

表達式 難易程度
md5(明文)=密文
md5(md5(明文))=密文
md5(md5(md5(明文)))=密文 稍難
md5(sha1(明文))=密文 稍難
md5(md5(md5(明文+一個複雜字符串)))=密文
md5(sha256(sha1(明文)))=密文

經驗:用足夠複雜的密碼對抗彩虹表,但儘量不要讓用戶“承擔”這件事。

fj

密碼安全實戰片段

這些天恰好筆者寫了一個類似每日新聞的項目,如果做大的話,這種項目的登錄註冊必須要進行簽名/加密,否則在評論區可能會有“意想不到的”錯誤。
(前端代碼省略,主要來看一下後端代碼中關於登錄密碼的片段)
這裏筆者用的是node.js,前端用的node模塊——ejs,簽名所用md5模塊:
ml

lib文件夾下的common.js文件

//md5的封裝
const crypto=require('crypto');

module.exports={
	MD5_SUFFIX:'ao8durq34fb3($&896359lhd8q是奧迪會36412#',
	md5:function(str){
		var obj=crypto.createHash('md5');   //以MD5格式簽名
		obj.update(str);
		return obj.digest('hex');   //hex:16進制(輸出格式)
	}
};

這個文件就是對MD5模塊的簡單封裝,MD5_SUFFIX這個變量就是上文所說的那個爲了增加複雜度而在明文後加的“一個很複雜的字符串”。

crypto模塊爲node中的MD5模塊所在模塊,有不明白的請移步node官網

這裏我們再簡化一下場景:假如這個項目中有個【管理員】,它的賬號和密碼是固定的——
我們可以通過MD5將“(原定)明文密碼”進行簽名,然後將簽名後的密碼字符串添加到數據庫對應字段裏。在用戶以【管理員】賬號登錄時,對用戶輸入字符串(明文)進行MD5加密,然後比對數據庫中字符串,即可。

項目>md5_2.js

const common=require('./lib/common');

var str='123456';
var str2=common.md5(str+'ao8durq34fb3($&896359lhd8q是奧迪會36412#');   //或者:str+common.MD5_SUFFIX;
console.log(str2);

總之,str+後面的字符串一定要跟上面common文件裏的屬性變量MD5_SUFFIX一致。
1


然後就到了最重要的部分了:驗證!
這裏需要注意一點的是:如果是用post提交數據的話,後端一定要用post接收數據,否則get請求不會被響應,打印出來數據就是undefined了。

route>admin.js

const express=require('express');
const common=require('../lib/common');
const mysql=require('mysql');

//node連接mysql
var db=mysql.createPool({host:'localhost',user:'root',password:'root',database:'xxx'});
module.exports=function(){
	var router=express.Router()
	router.use((req,res,next)=>{
		if(!req.session['admin_id'] && req.url!='/login'){
			res.redirect('/admin/login');
		}else{
			next();
		}
	});
	router.get('/login',(req,res)=>{
		res.render('admin/login.ejs',{});
	});
	router.post('/login',(req,res)=>{
		var username=req.body.username;
		var pass=common.md5(req.body.password+common.MD5_SUFFIX);
		//node操作mysql語句
		db.query(`SELECT * FROM XXX WHERE USERNAME='${username}'`,(err,data)=>{
			if(err){
				res.status(500).send('databases error').end();
			}else{
				if(data.length==0){
					res.status(400).send('empty databases').end();
				}else{
					if(data[0].password==pass){
						req.session['admin_id']=data[0].ID;
						res.redirect('/admin/');
					}else{
						res.status(400).send('密碼錯誤').end();
					}
				}
			}
		})
	});
	router.get('/',(req,res)=>{
		res.send('進入首頁了').end();
	})
	return router;
}

這裏有幾個比較重要的點:

  1. req.body——這裏使用的body-parser中間件(在主文件中),這個中間件可以讓post數據以請求體的形式傳到後端
  2. 第八行以use起頭——use其實是get和post的“合體”,常用在不確定請求會以哪種形式發送時使用,這裏用use而且不明確【監聽路由】,意味着:無論走到哪裏,都要經過這個路由監聽函數
  3. 如果是get進來(/login)的,說明什麼數據都沒有攜帶,就渲染出登錄的模板,如果有數據(第二次進這個網站或者提交數據後)就去判斷

這個文件最後將router暴露了出去 —— 它當然要被引用,但這裏只是登錄設置,同樣的,在一個項目中還有其他各種模塊…所以我們採用“二級路由”的設置方式:

主文件server.js

//...
//1.獲取請求數據
server.use(bodyParser.urlencoded());
server.use(multerObj.any());
//2.cookie、session
//3.模板
//4.router
server.use('/',require('./web.js')());   //其他頁面
server.use('/admin/',require('./route/admin.js')());
//5.default——static
//...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章