環境:
后台使用的python - flask
前台使用angular框架
1.一個跨域post的樣例:
跨域post有多種實現方式:
1.CORS:http://blog.csdn.net/hfahe/article/details/7730944
2.利用iframe
3.server proxy:https://en.wikipedia.org/wiki/Proxy_server
樣例使用的為iframe,想要證明,在沒有進行csrf防御時,隨意攻擊者能夠利用javascript發送 post 請求。從而簡單的提交或獲取數據資料;
1.本地test.html頁面
<html>
<head>
<title>POST</title>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
</head>
<body>
<input type="button" onclick="test();" value="test"/>
<script type="text/javascript"> function test() { crossDomainPost({ url: 'http://localhost:5000/test', param: {a: '1', b: '2'}, onSubmit: function (e) { console.log(e); } }) } function crossDomainPost(config) { var def = { url : '', //提交的地址 param : {}, //提交的參數 delay : 1000, //延遲獲取參數的時間。單位為毫秒 onSubmit : function (i) {} //提交成功后的回調函數,參數為跳轉的IFRAME }; config = $.extend({}, def, config); if (!config.url) { config.onSubmit({error: 'URL is Empty!'}); return; } /****baseMethod****/ /** * 生成隨機的10位字符,且唯一 * @returns {string} */ var createGuid = function () { var guid = ""; for (var i = 1; i <= 10; i++) { guid += Math.floor(Math.random() * 16.0).toString(16); } return guid; }, /** * 刪除指定的節點 * @param _element 要刪除的節點 */ removeElement = function (_element) { var _parentElement = _element.parentNode; if (_parentElement) { _parentElement.removeChild(_element); } } // Add the iframe with a unique name var iframe = document.createElement("iframe"); var uniqueString = createGuid(); document.body.appendChild(iframe); iframe.style.display = "none"; iframe.contentWindow.name = uniqueString; // construct a form with hidden inputs, targeting the iframe var form = document.createElement("form"); form.target = uniqueString; form.action = config.url; form.method = "POST"; // repeat for each parameter for (var item in config.param) { var input = document.createElement("input"); input.type = "hidden"; input.name = item; input.value = config.param[ item ]; form.appendChild(input); } document.body.appendChild(form); try{ form.submit(); }catch(e){ console.log('error'); consoel.log(e); } setTimeout(function () { config.onSubmit(iframe); removeElement(form); //移除form }, config.delay); } </script>
</body>
</html>
2.后台接收代碼
這里僅接收POST的請求
@app.route('/test' ,methods=['POST'])
def test():
print 'param is :';
print request.form
return jsonify(data='2222')
3.控制台輸出:
服務端處理了非站內的請求
4.瀏覽器輸出:
本地test.html獲取到返回信息
2.CSRF
跨站請求攻擊。簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己以前認證過的站點並運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。因為瀏覽器以前認證過。所以被訪問的站點會覺得是真正的用戶操作而去運行。
這利用了web中用戶身份驗證的一個漏洞:簡單的身份驗證僅僅能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自願發出的。
你這能夠這么理解CSRF攻擊:攻擊者盜用了你的身份,以你的名義發送惡意請求。CSRF能夠做的事情包含:以你名義發送郵件。發消息,盜取你的賬號。甚至於購買商品,虛擬貨幣轉賬……造成的問題包含:個人隱私泄露以及財產安全。
1.原理
從上圖能夠看出,要完畢一次CSRF攻擊,受害者必須依次完畢兩個步驟:
- 登錄受信任站點A,並在本地生成Cookie。
- 在不登出A的情況下,訪問危急站點B。
看到這里。你或許會說:“假設我不滿足以上兩個條件中的一個,我就不會受到CSRF的攻擊”。
是的,確實如此。但你不能保證下面情況不會發生:
1.你不能保證你登錄了一個站點后,不再打開一個tab頁面並訪問另外的站點。
2.你不能保證你關閉瀏覽器了后,你本地的Cookie立馬過期,你上次的會話已經結束。
3.上圖中所謂的攻擊站點。可能是一個存在其它漏洞的可信任的常常被人訪問的站點。
原理具體:http://www.80sec.com/csrf-securit.html
2.常見的攻擊類型:
1.GET類型的CSRF
僅僅須要一個HTTP請求。就能夠構造一次簡單的CSRF。
樣例:
銀行站點A:它以GET請求來完畢銀行轉賬的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危急站點B:它里面有一段HTML的代碼例如以下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先。你登錄了銀行站點A,然后訪問危急站點B,噢,這時你會發現你的銀行賬戶少了1000塊
為什么會這樣呢?原因是銀行站點A違反了HTTP規范,使用GET請求更新資源。
在訪問危急站點B的之前。你已經登錄了銀行站點A,而B中的以GET的方式請求第三方資源(這里的第三方就是指銀行站點了,原本這是一個合法的請求,但這里被不法分子利用了)。所以你的瀏覽器會帶上你的銀行站點A的Cookie發出Get請求,去獲取資源http://www.mybank.com/Transfer.php?toBankId=11&money=1000 ,結果銀行站點服務器收到請求后,覺得這是一個更新資源操作(轉賬操作),所以就立馬進行轉賬操作
2.POST類型的CSRF
如上邊的跨域POST樣例
3.怎樣防御CSRF
1.提交驗證碼
在表單中添加一個隨機的數字或字母驗證碼。通過強制用戶和應用進行交互。來有效地遏制CSRF攻擊。
2.Referer Check
檢查假設是非正常頁面過來的請求,則極有可能是CSRF攻擊。
3.token驗證
- 在 HTTP 請求中以參數的形式添加一個隨機產生的 token,並在服務器端建立一個攔截器來驗證這個 token,假設請求中沒有
token 或者 token 內容不對,則覺得可能是 CSRF 攻擊而拒絕該請求。 - token須要足夠隨機
- 敏感的操作應該使用POST。而不是GET,以form表單的形式提交。能夠避免token泄露。
4.在 HTTP 頭中自己定義屬性並驗證
這樣的方法也是使用 token 並進行驗證。這里並非把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自己定義的屬性里。通過 XMLHttpRequest 這個類,能夠一次性給全部該類請求加上 csrftoken 這個 HTTP 頭屬性。並把 token 值放入當中。這樣攻克了上種方法在請求中添加 token 的不便。同一時候,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用操心 token 會透過 Referer 泄露到其它站點中去。
4.關於token
- Token 應該被保存起來(放到 local / session stograge 或者 cookies)
- Tokens 除了像 cookie 一樣有有效期。而且你能夠有很多其它的操作方法。一旦 token 過期,僅僅須要又一次獲取一個。你能夠使用一個接口去刷新 token。你甚至能夠把 token 原來的公布時間也保存起來。而且強制在兩星期后又一次登錄什么的。假設你須要撤回 tokens(當 token 的生存期比較長的時候這非常有必要)那么你須要一個 token 的生成管理器去作檢查。
- Local / session storage 不會跨域工作,請使用一個標記 cookie
- 有須要的話,要加密而且簽名 token
- 將 JSON Web Tokens 應用到 OAuth 2
使用Token
1.引入csrf
from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()
app = Flask(__name__)
csrf.init_app(app)
app.config['SECRET_KEY']='myblog'
2.在站內頁面上head中,添加token
<meta name="csrf-token" content="{{ csrf_token() }}">
3.配置angular提交表頭
app.config(function ($httpProvider) {
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
});
4.再次測試跨域post
后台輸出:
前台輸出:
關於webapp跨域Post使用token思路:
- 移動端登錄時,服務端驗證表單信息。登陸成功,生成token,返回給client;
- client將token存在localstorage/sessionstorage中。每次提交表單。都須要攜帶token;
- 服務端獲取請求。假設沒有token,則忽略請求;
出現的問題:
服務端須要限制登陸次數
解決方法:
client添加登陸間隔。請求一次后,等待x秒才干再次請求
服務端做cas驗證服務端須要保存用戶的token,及過期時間;
解決方法:
能夠將token 保存在Memcache,數據庫中,redis- client存token時。須要對token加密
解決方法:
在存儲的時候把token進行對稱加密存儲,用時解開
將請求URL、時間戳、token三者進行合並加鹽簽名。服務端校驗有效性
當然。以上僅僅防君子,不防小人
參考:
跨域post請求:http://blog.csdn.net/doraeimo/article/details/7329779
跨站請求偽造:http://www.jianshu.com/p/7f33f9c7997b
token:http://alvinzhu.me/blog/2014/08/26/10-things-you-should-know-about-tokens/