成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從 WTForm 的 URLXSS 談開源組件的安全性

lordharrd / 639人閱讀

摘要:能夠開發(fā)開源組件的開發(fā)者本身素質(zhì)相對(duì)較高,代碼質(zhì)量較高,也使開源組件出漏洞的可能性較小。這種對(duì)比明顯反應(yīng)出開源組件和開源應(yīng)用在安全漏洞關(guān)注度上的差距。另一個(gè)例子,我們通過修改邏輯運(yùn)算符改變開發(fā)者正常的判斷流程,造成安全問題。

開源組件是我們大家平時(shí)開發(fā)的時(shí)候必不可少的工具,所謂『不要重復(fù)造輪子』的原因也是因?yàn)?,大量封裝好的組件我們?cè)陂_發(fā)中可以直接調(diào)用,減少了重復(fù)開發(fā)的工作量。
開源組件和開源程序也有一些區(qū)別,開源組件面向的使用者是開發(fā)者,而開源程序就可以直接面向用戶。開源組件,如JavaScript里的uploadify,php里的PHPExcel等;開源程序,如php寫的wordpress、joomla,node.js寫的ghost等。
就安全而言,毋庸置疑,開源組件的漏洞影響面遠(yuǎn)比開源軟件要大。但大量開源組件的漏洞卻很少出現(xiàn)在我們眼中,我總結(jié)了幾條原因:

開源程序的漏洞具有通用性,很多可以通過一個(gè)通用的poc來測(cè)試全網(wǎng),更具『商業(yè)價(jià)值』;而開源組件由于開發(fā)者使用方法不同,導(dǎo)致測(cè)試方法不統(tǒng)一,利用門檻也相對(duì)較高

大眾更熟悉開源軟件,如wordpress,而很少有人知道wordpress內(nèi)部使用了哪些開源組件。相應(yīng)的,當(dāng)出現(xiàn)漏洞的時(shí)候人們也只會(huì)認(rèn)為這個(gè)漏洞是wordpress的漏洞。

慣性思維讓人們認(rèn)為:『庫』里應(yīng)該不會(huì)有漏洞,在代碼審計(jì)的時(shí)候也很少會(huì)關(guān)注import進(jìn)來的第三方庫的代碼缺陷。所以,開源組件爆出的漏洞也較少。

能夠開發(fā)開源組件的開發(fā)者本身素質(zhì)相對(duì)較高,代碼質(zhì)量較高,也使開源組件出漏洞的可能性較小。

組件漏洞多半有爭(zhēng)議性,很多鍋分不清是組件自身的還是其使用者的,很多問題我們也只能稱其為『特性』,但實(shí)際上這些特性反而比某些漏洞更可怕。

特別是現(xiàn)在國內(nèi)浮躁的安全氛圍,可以明顯感受到第一條原因。就前段時(shí)間出現(xiàn)的幾個(gè)影響較大的漏洞:Java反序列化漏洞、joomla的代碼執(zhí)行、redis的寫ssh key,可以明顯感覺到后兩者炒的比前者要響,而前者不慍不火的,曝光了近一年才受到廣泛關(guān)注。
Java反序列化漏洞,恰好就是典型的『組件』特性造成的問題。早在2015年的1月28號(hào),就有白帽子報(bào)告了利用Apache Commons Collections這個(gè)常用的Java庫來實(shí)現(xiàn)任意代碼執(zhí)行的方法,但并沒有太多關(guān)注(原來國外也是這樣)。直到11月有人提出了用這個(gè)方法攻擊WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等應(yīng)用的時(shí)候,才被突然炒起來。
這種對(duì)比明顯反應(yīng)出『開源組件』和『開源應(yīng)用』在安全漏洞關(guān)注度上的差距。
我個(gè)人在烏云上發(fā)過幾個(gè)組件漏洞,從前年發(fā)的ThinkPHP框架注入,到后面的Tornado文件讀取,到slimphp的XXE,基本都是我自己在使用完這些組件后,對(duì)整體代碼做code review的時(shí)候發(fā)現(xiàn)的。
這篇文章以一個(gè)例子,簡(jiǎn)單地談?wù)勅绾螌?duì)第三方庫進(jìn)行code review,與如何正確使用第三方庫。

WTForm中的弱validator

WTForms是python web開發(fā)中重要的一個(gè)組件,它提供了簡(jiǎn)單的表單生成、驗(yàn)證、轉(zhuǎn)換等功能,是眾多python web框架(特別是flask)不可缺少的輔助庫之一。
WTForms中有一個(gè)重要的功能就是對(duì)用戶輸入進(jìn)行檢查,在文檔中被稱為validator:
http://wtforms.readthedocs.org/en/latest/validators.html

A validator simply takes an input, verifies it fulfills some criterion, such as a maximum length for a string and returns. Or, if the validation fails, raises a ValidationError. This system is very simple and flexible, and allows you to chain any number of validators on fields.

我們可以簡(jiǎn)單地使用其內(nèi)置validator對(duì)數(shù)據(jù)進(jìn)行檢查,比如我們需要用戶輸入一個(gè)『不為空』、『最短10個(gè)字符』、『最長64個(gè)字符』的『URL地址』,那么我們就可以編寫如下class:

class MyForm(Form):
    url = StringField("Link", validators=[DataRequired(), Length(min=10, max=64), URL()])

以flask為例,在view視圖中只需調(diào)用validate()函數(shù)即可檢查用戶的輸入是否合法:

@app.route("/", methods=["POST"])
def check():
    form = MyForm(flask.request.form)
    if form.validate():
        pass # right input
    else:
        pass # bad input

典型的敏捷開發(fā)手段,減少了大量開發(fā)工作量。
但我自己在做code review的過程中發(fā)現(xiàn),WTForms的內(nèi)置validators并不可信,與其說是不可信,不如說在安全性上部分validator完全不起任何作用。
就拿上訴代碼為例子,這段代碼真的可以檢查用戶輸入的數(shù)據(jù)是否是一個(gè)『URL』么?我們看到wtforms.validators.URL()類:

class URL(Regexp):
    """
    Simple regexp based url validation. Much like the email validator, you
    probably want to validate the url later by other means if the url must
    resolve.

    :param require_tld:
        If true, then the domain-name portion of the URL must contain a .tld
        suffix.  Set this to false if you want to allow domains like
        `localhost`.
    :param message:
        Error message to raise in case of a validation error.
    """
    def __init__(self, require_tld=True, message=None):
        regex = r"^[a-z]+://(?P[^/:]+)(?P:[0-9]+)?(?P/.*)?$"
        super(URL, self).__init__(regex, re.IGNORECASE, message)
        self.validate_hostname = HostnameValidation(
            require_tld=require_tld,
            allow_ip=True,
        )

    def __call__(self, form, field):
        message = self.message
        if message is None:
            message = field.gettext("Invalid URL.")

        match = super(URL, self).__call__(form, field, message)
        if not self.validate_hostname(match.group("host")):
            raise ValidationError(message)

其繼承了Rexexp類,實(shí)際上就是對(duì)用戶輸入進(jìn)行正則匹配。我們看到它的正則:

regex = r"^[a-z]+://(?P[^/:]+)(?P:[0-9]+)?(?P/.*)?$"

可見,這個(gè)正則與開發(fā)者理解的URL嚴(yán)重的不匹配。大部分的開發(fā)者希望獲得的URL是一個(gè)『HTTP網(wǎng)址』,但這個(gè)正則匹配到的卻寬泛的太多了,最大特點(diǎn)就是其可匹配任意protocol。
最容易想到的一個(gè)攻擊方式就是利用Javascript協(xié)議觸發(fā)的XSS,比如我傳入的url是

javascript://...xss code

WTForms將認(rèn)為這是一個(gè)合法的URL,并存入數(shù)據(jù)庫。而在業(yè)務(wù)邏輯中URL通常是輸出在超鏈接的href屬性中,而href屬性支持利用Javascript偽協(xié)議執(zhí)行JavaScript代碼。那么,這里就有極大的可能構(gòu)造一個(gè)XSS攻擊。
另一個(gè)草草編寫的validator是wtforms.validators.Email()類,查看其代碼:

class Email(Regexp):
    """
    Validates an email address. Note that this uses a very primitive regular
    expression and should only be used in instances where you later verify by
    other means, such as email activation or lookups.

    :param message:
        Error message to raise in case of a validation error.
    """
    def __init__(self, message=None):
        self.validate_hostname = HostnameValidation(
            require_tld=True,
        )
        super(Email, self).__init__(r"^.+@([^.@][^@]+)$", re.IGNORECASE, message)

    def __call__(self, form, field):
        message = self.message
        if message is None:
            message = field.gettext("Invalid email address.")

        match = super(Email, self).__call__(form, field, message)
        if not self.validate_hostname(match.group(1)):
            raise ValidationError(message)

看看他的正則^.+@([^.@][^@]+)$,這個(gè)正則根本無法檢測(cè)用戶的輸入是否是Email。最前面的.+就讓一切壞字符全進(jìn)入了數(shù)據(jù)庫。
所以我私下稱URL()和Email()為URL Finder和Email Finder,而非validator,因?yàn)樗麄兏緹o法驗(yàn)證用戶輸入,倒是更適合作為爬蟲查找目標(biāo)的finder。

利用弱validator構(gòu)造XSS

這個(gè)漏洞實(shí)際上是出現(xiàn)在我寫的某個(gè)網(wǎng)站中。這個(gè)網(wǎng)站允許訪客輸入其博客地址,而后臺(tái)使用URL()對(duì)地址的合法性進(jìn)行驗(yàn)證,在用戶主頁其他用戶可以點(diǎn)擊其頭像訪問博客。
整個(gè)過程如下: https://gist.github.com/phith0n/807869afbe1365015627

#(?ˉωˉ?) coding:utf8 (?ˉωˉ?)
import os
import flask
from flask import Flask
from wtforms.form import Form
from wtforms.validators import DataRequired, URL
from wtforms import StringField
app = Flask(__name__)

class UrlForm(Form):
    url = StringField("Link", validators=[DataRequired(), URL()])

@app.route("/", methods=["GET", "POST"])
def show_data():
    form = UrlForm(flask.request.form)
    if flask.request.method == "POST" and form.validate():
        url = form.url.data
    else:
        url = flask.request.url
    return flask.render_template("form.html", url=url, form=form)

if __name__ == "__main__":
    app.debug = False
    app.run(os.getenv("IP", "0.0.0.0"), int(os.getenv("PORT", 8080)))


    
        
        test
    
    
        

{% if form.url.errors %} {{ form.url.errors|join(" ") }} {% endif %}

your input url {{ url }}

demo頁面: https://flask-form-phith0n.c9users.io/ 可供測(cè)試。
那么,這段代碼存在漏洞嗎?回顧URL的正則:

regex = r"^[a-z]+://(?P[^/:]+)(?P:[0-9]+)?(?P/.*)?$"
super(URL, self).__init__(regex, re.IGNORECASE, message)

有個(gè)//,實(shí)在討厭,將后面的內(nèi)容全部注釋掉了,導(dǎo)致我不能直接執(zhí)行JavaScript。繞過方法也簡(jiǎn)單,因?yàn)?/是單行注釋,所以只需換行即可。
但這里正則修飾符是re.IGNORECASE,并沒有re.S,這就導(dǎo)致一旦出現(xiàn)換行這個(gè)正則將不再匹配。
不過這個(gè)問題很快也有了答案,在JavaScript中,可以代表換行的字符有 u2028和u2029,而在正則里換行僅僅是 ,所以我只要通過u2028或u2029這兩個(gè)字符代替換行即可。(u2028的url編碼為%E2%80%A8)
所以,傳入url如下即可:

javascript://www.baidu.com/?alert(1)

輸入以上url,提交后點(diǎn)擊鏈接即可觸發(fā):

這個(gè)漏洞很典型,任何開發(fā)者都不會(huì)想到如此平凡的一段代碼竟然隱藏著深層次的威脅。
有些人可能會(huì)覺得我這個(gè)demo并不能說明實(shí)際問題,我簡(jiǎn)單翻了一下github,不到5分鐘就找到了一個(gè)存在同樣問題的項(xiàng)目: https://github.com/1jingdian/1jingdian 。(雖然其站點(diǎn)已經(jīng)關(guān)閉,但代碼可以瀏覽)

https://github.com/1jingdian/1jingdian/blob/master/application/forms/user.py

class SettingsForm(Form):
    motto = StringField("座右銘")
    blog = StringField("博客", validators=[Optional(), URL(message="鏈接格式不正確")])
    weibo = StringField("微博", validators=[Optional(), URL(message="鏈接格式不正確")])
    douban = StringField("豆瓣", validators=[Optional(), URL(message="鏈接格式不正確")])
    zhihu = StringField("知乎", validators=[Optional(), URL(message="鏈接格式不正確")])

這里4個(gè)鏈接,全是用URL()來進(jìn)行驗(yàn)證。validate()通過后存入數(shù)據(jù)庫。
之后在個(gè)人頁面,提取出用戶信息傳入模板user/profile.html
https://github.com/1jingdian/1jingdian/blob/master/application/controllers/user.py#L14

def profile(uid, page):
    user = User.query.get_or_404(uid)
    votes = user.voted_pieces.paginate(page, 20)
    return render_template("user/profile.html", user=user, votes=votes)

跟進(jìn)一下profile.html
https://github.com/1jingdian/1jingdian/blob/master/application/templates/user/profile.html

{% from "macros/_user.html" import render_user_profile_header %}
...
{{ render_user_profile_header(user, active="votes") }}

調(diào)用了marco,傳入render_user_profile_header函數(shù),繼續(xù)跟進(jìn):
https://github.com/1jingdian/1jingdian/blob/master/application/templates/macros/_user.html#L37

{% macro render_user_profile_header(user, active="creates") %}
   ...
         
{% if user.blog %} {% endif %} {% if user.weibo %} {% endif %} {% if user.douban %} {% endif %}

這里將user.blog、user.weibo、user.douban都放入了a標(biāo)簽的href屬性。這一系列操作實(shí)際上就是我之前那個(gè)demo的縮影,最終導(dǎo)致傳入的url過濾不嚴(yán)產(chǎn)生XSS。

開源組件漏洞到底是誰的鍋?

這是屢次受到爭(zhēng)議的話題之一,很多人認(rèn)為開源組件之所以造成了漏洞,都是因?yàn)殚_發(fā)者不規(guī)范使用組件導(dǎo)致的。
我覺得認(rèn)定一個(gè)問題是開源組件的鍋,那么必須滿足以下條件:

開發(fā)者按照文檔常規(guī)的方法進(jìn)行開發(fā)

文檔并沒有說明如此開發(fā)會(huì)存在什么安全問題

同樣的開發(fā)方式在其他同類組件中沒有漏洞,而在該組件中產(chǎn)生漏洞

舉幾個(gè)例子,這個(gè)漏洞: http://www.wooyun.org/bugs/wooyun-2015-0101728 。首先滿足第一個(gè)條件,正常使用S函數(shù)。當(dāng)然文檔中也對(duì)安全進(jìn)行了說明:

但這個(gè)說明,我覺得是不夠的。你『可以』設(shè)置..參數(shù),避免緩存文件名『被猜測(cè)到』。文檔并沒有說明緩存文件名被猜測(cè)到有什么危害,也沒有強(qiáng)制要求設(shè)置這個(gè)參數(shù)。所以這個(gè)鍋,官方至少背一半。
再舉個(gè)例子: http://www.wooyun.org/bugs/wooyun-2010-0156208 ,很明顯的一個(gè)框架鍋,開發(fā)者在正常接收POST參數(shù)的時(shí)候就可以造成XXE漏洞,這個(gè)漏洞和開發(fā)者是沒有任何關(guān)系的。
另一個(gè)例子: http://www.wooyun.org/bugs/wooyun-2010-086742 ,我們通過修改邏輯運(yùn)算符改變開發(fā)者正常的判斷流程,造成安全問題。我們對(duì)比一下ThinkPHP和Codeigniter,CI中對(duì)于邏輯運(yùn)算符的位置就和TP不相同,它在『key』的位置:

正常情況下key位置是不會(huì)被用戶控制的。所以,同樣的開發(fā)方式在CI里不存在問題,而在TP里就存在問題,這樣的地方我認(rèn)為也是ThinkPHP的鍋。
我們看本文提出的WTForm的問題,這個(gè)鍋其實(shí)WTForm可以不用獨(dú)自背。我們?cè)谖臋n中,可以看到它有模模糊糊地提到過validater不嚴(yán)謹(jǐn)?shù)膯栴}:

當(dāng)然,這個(gè)模糊的提示對(duì)于很多沒有安全基礎(chǔ)的人來說,很難起到作用。

開發(fā)者如何應(yīng)對(duì)潛在的組件『安全特性』

那么,沒有安全基礎(chǔ)的開發(fā)者,如何去應(yīng)對(duì)潛在的組件安全特性。
首先,我覺得經(jīng)常做code review是很有必要的,我會(huì)經(jīng)常把自己寫的代碼也當(dāng)做一個(gè)開源應(yīng)用進(jìn)行閱讀與審計(jì),此時(shí)會(huì)經(jīng)常發(fā)現(xiàn)一些之前沒注意到過的安全問題。
code review的過程中,要深入地跟進(jìn)一下第三方庫的源代碼,而不能僅僅是看自己寫的代碼,這樣才能發(fā)現(xiàn)一些潛在的特性。這些特性往往是造成漏洞的罪魁禍?zhǔn)住?br>另外,文檔的閱讀能力也是極其重要的一點(diǎn)。其實(shí)大量的『框架特性』,框架文檔中都有一定的說明。很多開發(fā)者更喜歡去看example,覺得看代碼比看文字(也許與英文閱讀能力也有關(guān)系)更直觀,而不愿詳細(xì)閱讀說明。這種做法實(shí)際上在安全上是非常危險(xiǎn)的,因?yàn)槭纠a通常都是官方給出的最簡(jiǎn)陋的代碼,可能會(huì)忽略很多必要的安全措施。
另外,具備一定的安全基礎(chǔ)是每個(gè)開發(fā)必要的素質(zhì),原因不必贅述。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/37763.html

相關(guān)文章

  • flask + wtform + google storage

    摘要:項(xiàng)目需要使用上傳下載文件到上,搜了一圈沒有能直接結(jié)合使用的插件,所以動(dòng)手造了個(gè)輪子。只實(shí)現(xiàn)了基本的上傳,下載的功能,后續(xù)可能會(huì)完善預(yù)覽權(quán)限控制等功能。然后在中使用該并傳遞給存到最后像使用普通的一樣在中即可。 項(xiàng)目需要使用 flask 上傳、下載文件到 google storage 上, 搜了一圈沒有能直接結(jié)合 wtform 使用的插件,所以動(dòng)手造了個(gè)輪子。 只實(shí)現(xiàn)了基本的上傳,下載的功...

    Cristic 評(píng)論0 收藏0
  • 獨(dú)家專訪阿里高級(jí)技術(shù)專家北緯:Dubbo開源重啟半年來快意江湖

    摘要:年,阿里巴巴在上開源,許多開發(fā)者及公司都青睞于使用來解決服務(wù)化問題。首先阿里巴巴將開源提到了新的戰(zhàn)略高度,去年云棲大會(huì)上阿里云宣布了加大技術(shù)投入擁抱開源的策略。自去年開源重啟以來,上的數(shù)增長接近,達(dá)到了。 摘要: 羅毅,花名北緯。這個(gè)名字,如果是混過天涯論壇的大齡網(wǎng)民應(yīng)該都不陌生,北緯67度3分周公子(簡(jiǎn)稱北緯)虐殺易燁卿MM的世紀(jì)大戰(zhàn)至今還是天涯神貼 ,當(dāng)時(shí)更是有看客賦詩形容:目睹此...

    Sanchi 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<