Web安全 - 預防前端滲透的編碼技巧

前端攻擊主要包括XSS(跨站腳本攻擊)、CSRF(跨站請求僞造)、SQL注入「Noodles」的技術週刊 中有詳細解釋。javascript

1、XSS&SQL注入

它們的發生是在用戶惡意輸入和抓包修改狀況下,因爲先後端沒有作字符過濾,致使惡意代碼的執行。html

一、XSS&SQL注入編碼技巧

前端的轉義是必不可少的,爲了防止抓包修改參數值,咱們重點放在後端。網上有個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。

2、CSRF

一、Referer

在過濾器或攔截器中判讀請求頭的Referer值,若是同域名就能夠繼續訪問,不然請求被攔截。

String referer = request.getHeader("Referer");
if(referer.startsWith("http://www.xxx.com")){
    chain.doFilter(request, response);
}else{
    return ;
}

弊端:有些網站或用戶會停用Referer,因此上面這種方式會致使正經常使用戶也不能訪問系統。

二、Token

這種方式用的最多,是將Token做爲每次請求的參數來驗證請求是否有效。

弊端:黑客可經過發送用戶連接,盜取Token值。

三、JWT

爲什麼用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;
    }
    
}

先這樣吧,不太會寫文章,但願你們海涵。

相關文章
相關標籤/搜索