前端攻擊主要包括XSS(跨站腳本攻擊)、CSRF(跨站請求僞造)、SQL注入。「Noodles」的技術週刊 中有詳細解釋。javascript
它們的發生是在用戶惡意輸入和抓包修改狀況下,因爲先後端沒有作字符過濾,致使惡意代碼的執行。html
前端的轉義是必不可少的,爲了防止抓包修改參數值,咱們重點放在後端。網上有個XSSProject,地址爲:http://yunjiechao-163-com.ite...,其中封裝好了一些功能,特別方便。。。前端
也能夠用下面的代碼:java
/** * * XSS過濾 * @author Alex * */ public class XSSFilter implements Filter{ public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletRequest request = (HttpServletRequest) arg0; HttpServletResponse response = (HttpServletResponse) arg1; XssAndSqlHttpServletRequestWrapper xssRequest = new XssAndSqlHttpServletRequestWrapper(request);//採用包裝器過濾掉惡意字符 arg2.doFilter(xssRequest, response); } }
/** * * XSS包裝器 * @author Alex * */ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper { private Logger log = Logger.getLogger(getClass()); private HttpServletRequest orgRequest = null; public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; } @Override public String getParameter(String name) { String value = null; try { //不過濾菜單 if(!name.equals("menuHtml")){//本身的菜單,無視掉 value = super.getParameter(xssEncode(name)); if (value != null) { value = URLDecoder.decode(value, Constant.UTF);//處理中文亂碼 value = xssEncode(value); } }else{ value = super.getParameter(name); } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error(e.getMessage()); } return value; } @Override public String getHeader(String name) {//請求頭也可能插入 String value = null; try { value = super.getHeader(xssEncode(name)); if (value != null) { value = xssEncode(value); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); log.error(e.getMessage()); } return value; } private static String xssEncode(String s) {//替換成中文字符 if (s == null || s.isEmpty()) { return s; }else{ s = stripXSSAndSql(s); } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '>': sb.append(">");// 轉義大於號 break; case '<': sb.append("<");// 轉義小於號 break; case '\'': sb.append("'");// 轉義單引號 break; case '\"': sb.append(""");// 轉義雙引號 break; case '&': sb.append("&");// 轉義& break; case '#': sb.append("#");// 轉義# break; default: sb.append(c); break; } } return sb.toString(); } public HttpServletRequest getOrgRequest() { return orgRequest; } public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssAndSqlHttpServletRequestWrapper) { return ((XssAndSqlHttpServletRequestWrapper) req).getOrgRequest(); } return req; } public static String stripXSSAndSql(String value) { if (value != null) { // Avoid anything between script tags Pattern scriptPattern = Pattern.compile("<[\r\n| | ]*script[\r\n| | ]*>(.*?)</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid anything in a src="http://www.yihaomen.com/article/java/..." type of e-xpression scriptPattern = Pattern.compile("src[\r\n| | ]*=[\r\n| | ]*[\\\"|\\\'](.*?)[\\\"|\\\']", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome </script> tag scriptPattern = Pattern.compile("</[\r\n| | ]*script[\r\n| | ]*>", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Remove any lonesome <script ...> tag scriptPattern = Pattern.compile("<[\r\n| | ]*script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid eval(...) expressions scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid e-xpression(...) expressions scriptPattern = Pattern.compile("e-xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); // Avoid javascript:... expressions scriptPattern = Pattern.compile("javascript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid vbscript:... expressions scriptPattern = Pattern.compile("vbscript[\r\n| | ]*:[\r\n| | ]*", Pattern.CASE_INSENSITIVE); value = scriptPattern.matcher(value).replaceAll(""); // Avoid onload= expressions scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); value = scriptPattern.matcher(value).replaceAll(""); } return value; } }
web.xml中添加python
<filter> <filter-name>XSSFilter</filter-name> <filter-class>com.xxx.filter.XSSFilter</filter-class> </filter> <filter-mapping> <filter-name>XSSFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
用Python的flask框架演示,代碼很簡單,順便演示下flask模板的注入web
服務端代碼:express
#!/usr/bin/python # -*- coding: UTF-8 -*- from flask import Flask, request, render_template_string, render_template, make_response app = Flask(__name__) @app.route('/hello-injection') def hello_inject(): person = {'name': 'asd', 'secret': 'aaaaaaaaaaa'} if request.args.get('name'): person['name'] = request.args.get('name') template = '''<h2>Hello %s</h2>''' % person['name'] return render_template_string(template, person=person) def get_user_file(f_name): with open(f_name) as f: return f.readlines() app.jinja_env.globals['get_user_file'] = get_user_file if __name__ == "__main__": app.run(debug=True)
運行好後,訪問以下幾個地址:flask
(1)模板字符串中字符串拼接或替換引起的安全隱患segmentfault
http://127.0.0.1:5000/hello-injection?name=ForrestX386.{{person.secret}}
,頁面顯示:Hello ForrestX386.aaaaaaaaaaa,祕鑰aaaaaaaaaaa被泄漏。後端
http://127.0.0.1:5000/hello-injection?name=ForrestX386.{{get_user_file('E:\ssd.txt')}}
,顯示ssd.txt文件中的內容。
緣由:Jinja2在模板渲染的時候將person['name']的值替換掉{{person['name']}}, 而不會對person['name']內容進行二次渲染(這樣即便`person['name']中含有{{}}也不會進行渲染,而只是把它當作普通字符串)。
(2)render_template_string 的安全隱患
咱們知道Flask render_template函數在模板渲染(使用Jinja2模板引擎)的時候,會自動對模板(常見的模板後綴才進行自動HTML轉碼,好比.html,.htm等,不常見的模板後綴是不會進行HTML自動編碼的,下面會介紹到)內容進行HTML實體編碼,從而避免XSS漏洞的發生,可是Flask中的render_template_string 函數卻不會對要渲染的模板字符串進行自動HTML實體編碼,存在XSS安全隱患
http://127.0.0.1:5000/hello-injection?name=ForrestX386.<script>alert(1)</script>
,若是Chrome沒有顯示彈出,請設置:
上圖的設置會取消瀏覽器的xss-auditor啓動(一種瀏覽器內建的xss防護模塊,可阻止大多數反射型xss)。若是不想使用上圖的方式啓動,能夠將服務端代碼改成:
@app.route('/hello-injection') def hello_inject(): person = {'name': 'asd', 'secret': 'aaaaaaaaaaa'} if request.args.get('name'): person['name'] = request.args.get('name') template = '''<h2>Hello %s</h2>''' % person['name'] response = make_response(person['name']) response.headers['X-XSS-Protection'] = '0' # xss auditor關閉 return response
將xss-auditor關閉能夠彈出。若是開啓呢?還能彈出嗎?修改爲如下代碼:
@app.route('/hello-injection') def hello_inject(): person = {'name': 'asd', 'secret': 'aaaaaaaaaaa'} if request.args.get('name'): person['name'] = request.args.get('name') template = '''<h2>Hello %s</h2>''' % person['name'] response = make_response(person['name'].replace('>', '>;')) response.headers['X-XSS-Protection'] = '1' # xss auditor開啓 return response
居然能夠彈出。。。這裏介紹下auditor的:
一般狀況下,咱們都會對用戶提交的數據進行一些處理,若是這些處理致使和提交的內容不同了,可是仍然能夠執行,好比像本例同樣。那麼xss auditor 就無能爲力了。不過xss auditor自己的智能度也挺高,像字符編碼,大小寫變化這種變化依然躲不過xss auditor。
在過濾器或攔截器中判讀請求頭的Referer值,若是同域名就能夠繼續訪問,不然請求被攔截。
String referer = request.getHeader("Referer"); if(referer.startsWith("http://www.xxx.com")){ chain.doFilter(request, response); }else{ return ; }
弊端:有些網站或用戶會停用Referer,因此上面這種方式會致使正經常使用戶也不能訪問系統。
這種方式用的最多,是將Token做爲每次請求的參數來驗證請求是否有效。
弊端:黑客可經過發送用戶連接,盜取Token值。
爲什麼用JWT?
白話意思是用戶的信息可放在客戶端保存,代替以前服務器session的保存方式。爲了契合先後分離的說法。因此請不要認爲JWT能夠預防CSRF,這是一種錯誤的理解!
目前存儲JWT的方式有如下幾種:
一、Cookie存取
優勢:不易遭受XSS(可設置HttpOnly)
弊端:易遭受CSRF。
二、LocalStorage
優勢:不產生CSRF、存儲量大
缺點:易遭受XSS、難清除(Android機很難清除)
三、
YOU stick the (JWT) token in the Authorization HTTP header of a request.
這是http://stackoverflow.com/描述的一種方法。
總結起來就是各有優缺點。我的以爲CSRF較難防護,看我的輕重程度了。
下面是代碼:
其中有些是本身項目的邏輯,請需修改。
/** * * @author Alex * */ public class JWTFilter implements Filter{ private Logger log = Logger.getLogger(getClass()); public void destroy() { // TODO Auto-generated method stub } public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletRequest request = (HttpServletRequest) arg0; HttpServletResponse response = (HttpServletResponse) arg1; AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(); try { String jwt = null; Cookie[] cookies = request.getCookies(); for(Cookie cookie:cookies){ if(cookie.getName().equals("jwt")){ jwt = cookie.getValue(); break; } } String referer = request.getHeader("Referer"); String userName = (String) principal.getName(); if(referer!=null&&referer.indexOf("/XXX/login")!=-1&&jwt==null){//登陸時設置JWT及判斷Cookie中有無JWT jwt = JWTWrapper.createJWT(userName);//userName單點登陸用戶名 response.addHeader("Set-Cookie", "jwt="+jwt+";Path=/;HttpOnly");//防止JS獲取Cookie }else{ int judge = JWTWrapper.judgeJWT(jwt, userName);//判斷JWT是否過時 if(judge==-1){//被篡改 return ; }else if(judge==0){//過時或將要過時 String jwt_new = JWTWrapper.createJWT(userName); response.addHeader("Set-Cookie", "jwt="+jwt_new+";Path=/;HttpOnly"); } } arg2.doFilter(request, response); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); log.error(e.getMessage()); } } public void init(FilterConfig arg0) throws ServletException { // TODO Auto-generated method stub } }
/** * * JWT包裝器 * @author Alex * */ public class JWTWrapper { private static String iss = "XXX";//簽發者 private static Long exp_add = Long.valueOf(30*60*1000);//過時時間半小時 private static String des_key = "XXXXXXX";//des密鑰 /** * 建立JWT * @param aud 接收方 */ public static String createJWT(String aud){ String jwt = null; try { Long iat = System.currentTimeMillis();//簽發時間,應該用秒 String header = "{\"typ\":\"JWT\",\"alg\":\"DES\"}";//頭部 String payload = "{\"iss\":\""+iss+"\",\"aud\":\""+aud+"\",\"iat\":"+iat+",\"exp\":"+(iat+exp_add)+"}";//載荷 String signature = null;//簽名 header = Base64.encodeBase64URLSafeString(header.getBytes(Constant.UTF)); payload = Base64.encodeBase64URLSafeString(payload.getBytes(Constant.UTF)); signature = DesUtil.encrypt(header+"."+payload, des_key);//可用其它加密 jwt = header+"."+payload+"."+signature; } catch (Exception e) { // TODO: handle exception e.printStackTrace(); System.out.println(e.getMessage()); } return jwt; } /** * 驗證JWT * @param jwt * @param aud 接收方 * @return */ public static int judgeJWT(String jwt,String aud){ int judge = -1;//-1:篡改的JWT try { if(jwt!=null&&jwt.indexOf(".")!=-1){//分割JWT String[] strs = jwt.split("\\."); String signature_new = strs[0]+"."+strs[1]+"."+DesUtil.encrypt(strs[0]+"."+strs[1], des_key);//簽名 if(signature_new.startsWith(jwt)){//未被篡改 String payload = new String(Base64.decodeBase64(strs[1]), Constant.UTF);//載荷 JSONObject JO = JSONObject.fromObject(payload); if(JO.getString("iss").equals(iss)&&JO.getString("aud").equals(aud)){ Long exp = JO.getLong("exp"); Long iat = System.currentTimeMillis();//簽發時間 if(exp>iat&&(exp-iat)>1*60*1000){//過時時間>1分鐘 judge = 1;//正常,不需更新JWT }else{ judge = 0;//JWT過時或將要過時 } } } } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); System.out.println(e.getMessage()); } return judge; } }
先這樣吧,不太會寫文章,但願你們海涵。