Freemarker中如何避免xss漏洞

什么是XSS漏洞

試想一下,如果我們開發(fā)一個訂單系統(tǒng)分瘦,訂單名稱如果沒有做限制拇砰,允許用戶輸入任意字符,那么就有產(chǎn)生XSS的危險匕垫。攻擊者可以很容易編寫一個惡意JS腳本,然后將當(dāng)前登錄用戶的cookie或者其他敏感信息抓取到僧鲁,發(fā)送給攻擊者自己,這就是XSS(跨站腳本)攻擊。如何解決這個問題寞秃,首先我們想到的是在用戶輸入訂單的時候斟叼,我們對訂單名稱做限制,不允許輸入特殊字符春寿。這樣是可以避免的朗涩,不過對于一個大的系統(tǒng)來說,用戶可以輸入的字段太多了绑改,如果能夠全部校驗谢床,是最好的。如果不能做的話绢淀,還可以讓前端在做展示的時候進(jìn)行html轉(zhuǎn)義處理一下萤悴,這樣原本scirpt標(biāo)簽以及當(dāng)中的內(nèi)容就被當(dāng)做一個字符串展示出來瘾腰,而不是當(dāng)做代碼執(zhí)行了皆的。一般現(xiàn)在的reactjs等前端框架已經(jīng)默認(rèn)支持防xss了。今天我遇到的問題是蹋盆,有一部分freemarker寫的頁面存在XSS的問題费薄。

FreeMarker中解決XSS

可以通過對用戶輸入的字段進(jìn)行html轉(zhuǎn)義,來有效的避免XSS問題栖雾。freemarker模板中的變量通過{value}這樣的形式引入楞抡,但是挨個修改成{value?html}的形式未免工作量太大;查閱官方文檔析藕,通過escape標(biāo)簽可以對整個模板中$號引入的變量全部進(jìn)行一次html轉(zhuǎn)義召廷。比如:

<#assign x = "<test>">
<#macro m1>
  m1: ${x}
</#macro>
<#escape x as x?html>
  <#macro m2>m2: ${x}</#macro>
  ${x}
  <@m1/>
</#escape>
${x}
<@m2/>

會輸出:

  &lt;test&gt;
  m1: <test>
<test>
m2: &lt;test&gt;

這種方式完全滿足現(xiàn)在的改造需求。在這個項目中账胧,freemarker模板是存儲在數(shù)據(jù)庫中竞慢,所有用到freemarker渲染的地方均是用的同一個入口;這樣工作量就不大了治泥。我們知道筹煮,freemarker加載模板是通過TemplateLoader這個接口來實現(xiàn)的;只需要在加載模板的時候在模板的頭部加上<#escape x as x?html>在尾部加上</#escape就可以對模板中所有的變量進(jìn)行html轉(zhuǎn)義了居夹。通過這種方式數(shù)據(jù)庫中的數(shù)據(jù)也不用修改败潦,將來生成模板,也不需要考慮做html轉(zhuǎn)義的問題准脂。

public interface TemplateLoader {
    
    public Object findTemplateSource(String name)
    throws IOException;

    public long getLastModified(Object templateSource);
  
    public Reader getReader(Object templateSource, String encoding) throws IOException;
 
    public void closeTemplateSource(Object templateSource) throws IOException;
    
}

由于實現(xiàn)邏輯還是從外部讀取字符串加載劫扒,所以TemplateLoader接口支持html轉(zhuǎn)義的實現(xiàn)和StringTemplateLoader的邏輯差不多。

public class HtmlEscapeTemplateLoader implements TemplateLoader {

    private static final String HTML_ESCAPE_PREFIX = "<#escape x as x?html>";
    private static final String HTML_ESCAPE_SUFFIX = "</#escape>";

    // 為了支持并發(fā)狸膏,這里采用ConcurrentMap
    private final Map<String, StringTemplateSource> templates = Maps.newConcurrentMap();

    public void putTemplate(String name, String templateContent) {
        putTemplate(name, templateContent, System.currentTimeMillis());
    }

    public void putTemplate(String name, String templateContent, long lastModified) {
        templates.put(name, new HtmlEscapeTemplateLoader.StringTemplateSource(name, templateContent, lastModified));
    }

    public boolean removeTemplate(String name) {
        return templates.remove(name) != null;
    }

    public void closeTemplateSource(Object templateSource) {
    }

    public Object findTemplateSource(String name) {
        return templates.get(name);
    }

    public long getLastModified(Object templateSource) {
        return ((HtmlEscapeTemplateLoader.StringTemplateSource) templateSource).lastModified;
    }

    public Reader getReader(Object templateSource, String encoding) throws IOException {
        Reader reader = new StringReader(((StringTemplateSource) templateSource).templateContent);
        String templateText = IOUtils.toString(reader);
        return new StringReader(HTML_ESCAPE_PREFIX + templateText + HTML_ESCAPE_SUFFIX);
    }

    private static class StringTemplateSource {
        private final String name;
        private final String templateContent;
        private final long lastModified;

        StringTemplateSource(String name, String templateContent, long lastModified) {
            if (name == null) {
                throw new IllegalArgumentException("name == null");
            }
            if (templateContent == null) {
                throw new IllegalArgumentException("source == null");
            }
            if (lastModified < -1L) {
                throw new IllegalArgumentException("lastModified < -1L");
            }
            this.name = name;
            this.templateContent = templateContent;
            this.lastModified = lastModified;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            HtmlEscapeTemplateLoader.StringTemplateSource other = (HtmlEscapeTemplateLoader.StringTemplateSource) obj;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }


        @Override
        public String toString() {
            return name;
        }

    }

    /**
     * Show class name and some details that are useful in template-not-found errors.
     *
     * @since 2.3.21
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClassNameForToString(this));
        sb.append("(Map { ");
        int cnt = 0;
        for (String name : templates.keySet()) {
            cnt++;
            if (cnt != 1) {
                sb.append(", ");
            }
            if (cnt > 10) {
                sb.append("...");
                break;
            }
            sb.append(StringUtil.jQuote(name));
            sb.append("=...");
        }
        if (cnt != 0) {
            sb.append(' ');
        }
        sb.append("})");
        return sb.toString();
    }

    private static String getClassNameForToString(TemplateLoader templateLoader) {
        final Class tlClass = templateLoader.getClass();
        final Package tlPackage = tlClass.getPackage();
        return tlPackage == Configuration.class.getPackage() || tlPackage == TemplateLoader.class.getPackage()
                ? tlClass.getSimpleName() : tlClass.getName();
    }
}

至此實現(xiàn)了在freemarker中自動進(jìn)行html轉(zhuǎn)義避免XSS問題的過程沟饥。參考網(wǎng)上有些資料可以修改freemarker的源碼,對其在進(jìn)行$號解析的時候,自動加上html轉(zhuǎn)義的邏輯闷板,也是可以的澎灸。如果某些特殊情況下,就是需要展示html形式的內(nèi)容而不需要轉(zhuǎn)義遮晚,或者有人錯誤的將變量進(jìn)行了一次轉(zhuǎn)義比如寫成${!(value)?html}的形式性昭,會怎樣?

如果就是需要展示html形式的內(nèi)容而不需要轉(zhuǎn)義县遣,可以用<#noescape>標(biāo)簽將不需要轉(zhuǎn)義的變量包裹起來糜颠,這樣就算外層有<#escape>也不會進(jìn)行轉(zhuǎn)義了。如果在外部有<#escape>的情況下萧求,變量自身又做了一次轉(zhuǎn)義其兴,那么該變量會被轉(zhuǎn)義兩次。正常字符不會有影響夸政,含有特殊字符的話元旬,可能比較難看了。

參考

https://blog.csdn.net/shadowsick/article/details/80768868

轉(zhuǎn)自: http://heqiao2010.github.io

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末守问,一起剝皮案震驚了整個濱河市匀归,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耗帕,老刑警劉巖穆端,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仿便,居然都是意外死亡体啰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門嗽仪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荒勇,“玉大人,你說我怎么就攤上這事钦幔≌硖耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵鲤氢,是天一觀的道長搀擂。 經(jīng)常有香客問我,道長卷玉,這世上最難降的妖魔是什么哨颂? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮相种,結(jié)果婚禮上威恼,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好箫措,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布腹备。 她就那樣靜靜地躺著,像睡著了一般斤蔓。 火紅的嫁衣襯著肌膚如雪植酥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天弦牡,我揣著相機(jī)與錄音友驮,去河邊找鬼。 笑死驾锰,一個胖子當(dāng)著我的面吹牛卸留,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椭豫,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼耻瑟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捻悯?” 一聲冷哼從身側(cè)響起匆赃,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤淤毛,失蹤者是張志新(化名)和其女友劉穎今缚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體低淡,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡姓言,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔗蹋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片何荚。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猪杭,靈堂內(nèi)的尸體忽然破棺而出餐塘,到底是詐尸還是另有隱情,我是刑警寧澤皂吮,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布戒傻,位于F島的核電站,受9級特大地震影響蜂筹,放射性物質(zhì)發(fā)生泄漏需纳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一艺挪、第九天 我趴在偏房一處隱蔽的房頂上張望不翩。 院中可真熱鬧,春花似錦、人聲如沸口蝠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妙蔗。三九已至俱箱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灭必,已是汗流浹背狞谱。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留禁漓,地道東北人跟衅。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像播歼,于是被迫代替她去往敵國和親伶跷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容