1. <tbody id="z2biv"><center id="z2biv"></center></tbody><form id="z2biv"><strike id="z2biv"></strike></form>

        <progress id="z2biv"></progress>
        15320004362

        從SUCTF2019到python源碼

        日期:2019年11月19日 11:02 訪問:562 作者:必火安全學院
        第十一期開班時間:2021年9月14日

        搶先領取全套VIP視頻教程

        +10天免費學習名額

          已有8166人參加


        視頻課程

        姓名 選填

        電話

          張*燕188****220722分鐘前

          王*軍186****864498分鐘前

          李*如189****445354分鐘前

        >>  稍后老師聯系您發送相關視頻課程  <<



        報名CTF挑戰賽,  預約名師指導

          已有 2366 人參加
        姓名 選填

        電話

          郭*明170****234291分鐘前

          趙*東189****289646分鐘前

          蔡*培135****589722分鐘前





           

        網絡安全滲透測試群(必火安全學院):信息安全滲透測試群

        護網行動日薪千元(初級中級高級)群:護網行動必火業余班級

        前言

        前段時間打的SUCTF2019中有一個題目叫Pythongin思路大概來源于黑帽大會

        https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

        不怎么明白漏洞的原理,就準備復現一下,在復現的過程中出現了很多坑,來記錄一下。
        sucf2019

        踩坑過程

        整個SUCTF2019的源碼都已經開源了,地址如下

        https://github.com/team-su/SUCTF-2019

        具體題目的分析過程就不再贅述了,感覺師傅們分析的一個比一個詳細,我在文末也放了幾個師傅的分析的writeup

        搭建好docker的環境,直接按照文檔中的命令可以直接復現成功

        docker build -t dockerflask .
        
        docker run -p 3000:80 dockerflask
        open http://localhost:3000
        

        然后按照題目的payload可以直接復現成功

        然而問題來了,我比較懶,我是直接把文件放到了我的sublime 然后,使用一個官方的payload 居然沒有打成功懷疑是我的環境和編輯器的編碼出現了問題,然后放到我的WSL系統里面運行(這里說一句題外話,最近剛剛給自己的電腦安裝了WSL win下的ubuntu,感覺很好用 ,想用linux的時候不必再去開虛擬機了)大家可以去嘗試一下,同時也順便美化一下自己的終端。

        再win下的環境是

        在linux下的環境是

         /mnt/d/CTF/SUCTF  python3
        Python 3.6.8 (default, Jan 14 201911:02:34)
        [GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
        Type "help""copyright""credits" or "license" for more information.
        >>>
        

        在win下的環境是

        C:\Users\11466>python3
        Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 201922:22:05) [MSC v.1916 64 bit (AMD64)] on win32
        Type "help""copyright""credits" or "license" for more information.
        >>>
        

        可以看版本不一樣,同時python最后更新的時間也不一樣問題就出現在這里了。

        對源碼做出的簡單的修改,用來測試payload

        def getUrl2(url):
            host = parse.urlparse(url).hostname
            if host == 'suctf.cc':
                return "我扌 your problem? 111"
            parts = list(urlsplit(url))
            host = parts[1]
            if host == 'suctf.cc':
                return "我扌 your problem? 222 " + host
            newhost = []
            for h in host.split('.'):
                newhost.append(h.encode('idna').decode('utf-8'))
            parts[1] = '.'.join(newhost)
            #去掉 url 中的空格
            finalUrl = urlunsplit(parts).split(' ')[0]
            host = parse.urlparse(finalUrl).hostname
            if host == 'suctf.cc':
                return "success"
            else:
                return "我扌 your problem? 333"
        if __name__=="__main__":
            # get_unicode()
            # try:
            #     print_unicode()
            # except:
        
            #     print("something_error")
            url = "file://suctf.c?sr%2ffffffflag @111"
            print(url)
            print(getUrl2(url))
            # print(getUrl(url))
            # get_unicode()
        

        可以出運行不同的結果:

        在win下

        file://suctf.c?sr%2ffffffflag @111
        Traceback (most recent call last):
          File "1.py", line 72in <module>
            print(getUrl2(url))
          File "1.py", line 45in getUrl2
            host = parse.urlparse(url).hostname
          File "E:\python3\lib\urllib\parse.py", line 368in urlparse
            splitresult = urlsplit(url, scheme, allow_fragments)
          File "E:\python3\lib\urllib\parse.py", line 461in urlsplit
            _checknetloc(netloc)
          File "E:\python3\lib\urllib\parse.py", line 407in _checknetloc
            "characters under NFKC normalization")
        ValueError: netloc 'suctf.cc/usr%2ffffffflag @111' contains invalid characters under NFKC normalization
        

        在WSL下

         /mnt/d/CTF/NUCA  python3 1.py
        file://suctf.c?sr%2ffffffflag @111
        success
        

        原來沒怎么分析過python的源碼

        但是從報錯信息上可以找到,問題就出現在python3\lib\urllib\parse.py

        于是就來簡單分析了下parse.py的源碼

        源碼對比

        在win下是比較新的一個python,很明顯對于這類漏洞已經修補

        在WSL下的是一個比較舊的python

        使用在win下找到E:\python3\lib\urllib\parse.py

        在WSL下找到 /usr/lib/python3.6/urllib/parse.py

         /mnt/d/CTF/SUCTF  python3
        Python 3.6.8 (default, Jan 14 2019, 11:02:34)
        [GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
        Type "help""copyright""credits" or "license" for more information.
        >>> import sys
        >>> sys.path
        ['''/usr/lib/python36.zip''/usr/lib/python3.6''/usr/lib/python3.6/lib-dynload''/home/fangzhang/.local/lib/python3.6/site-packages''/usr/local/lib/python3.6/dist-packages''/usr/lib/python3/dist-packages']
        >>>
         /mnt/d/CTF/SUCTF cd /usr/lib/python3.6/urllib/
         /usr/lib/python3.6/urllib 
        

        使用文本對比工具

        得到以下結果:

        --- E:\python3\Lib\urllib\parse.py
        +++ /usr/lib/python3.6/urllib 
        @@ -390,21 +390,6 @@
                 if wdelim >= 0:                    # if found
                     delim = min(delim, wdelim)     # use earliest delim position
             return url[start:delim], url[delim:]   # return (domain, rest)
        -
        -def _checknetloc(netloc):
        -    if not netloc or netloc.isascii():
        -        return
        -    # looking for characters like \u2100 that expand to 'a/c'
        -    # IDNA uses NFKC equivalence, so normalize for this check
        -    import unicodedata
        -    netloc2 = unicodedata.normalize('NFKC', netloc)
        -    if netloc == netloc2:
        -        return
        -    _, _, netloc = netloc.rpartition('@'# anything to the left of '@' is okay
        -    for c in '/?#@:':
        -        if c in netloc2:
        -            raise ValueError("netloc '" + netloc2 + "' contains invalid " +
        -                             "characters under NFKC normalization")
        
         def urlsplit(url, scheme='', allow_fragments=True):
             """Parse a URL into 5 components:
        @@ -424,6 +409,7 @@
             i = url.find(':')
             if i > 0:
                 if url[:i] == 'http': # optimize the common case
        +            scheme = url[:i].lower()
                     url = url[i+1:]
                     if url[:2] == '//':
                         netloc, url = _splitnetloc(url, 2)
        @@ -434,8 +420,7 @@
                         url, fragment = url.split('#', 1)
                     if '?' in url:
                         url, query = url.split('?', 1)
        -            _checknetloc(netloc)
        -            v = SplitResult('http', netloc, url, query, fragment)
        +            v = SplitResult(scheme, netloc, url, query, fragment)
                     _parse_cache[key] = v
                     return _coerce_result(v)
                 for c in url[:i]:
        @@ -458,7 +443,6 @@
                 url, fragment = url.split('#', 1)
             if '?' in url:
                 url, query = url.split('?', 1)
        -    _checknetloc(netloc)
             v = SplitResult(scheme, netloc, url, query, fragment)
             _parse_cache[key] = v
             return _coerce_result(v)
        @@ -600,7 +584,7 @@
             # if the function is never called
             global _hextobyte
             if _hextobyte is None:
        -        _hextobyte = {(a + b).encode(): bytes.fromhex(a + b)
        +        _hextobyte = {(a + b).encode(): bytes([int(a + b, 16)])
                               for a in _hexdig for b in _hexdig}
             for item in bits[1:]:
                 try:
        @@ -750,7 +734,7 @@
         _ALWAYS_SAFE = frozenset(b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                                  b'abcdefghijklmnopqrstuvwxyz'
                                  b'0123456789'
        -                         b'_.-~')
        +                         b'_.-')
         _ALWAYS_SAFE_BYTES = bytes(_ALWAYS_SAFE)
         _safe_quoters = {}
        
        @@ -782,17 +766,14 @@
             Each part of a URL, e.g. the path info, the query, etc., has a
             different set of reserved characters that must be quoted.
        
        -    RFC 3986 Uniform Resource Identifiers (URI): Generic Syntax lists
        +    RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
             the following reserved characters.
        
             reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
        -                  "$" | "," | "~"
        +                  "$" | ","
        
             Each of these characters is reserved in some component of a URL,
             but not necessarily in all of them.
        -
        -    Python 3.7 updates from using RFC 2396 to RFC 3986 to quote URL strings.
        -    Now, "~" is included in the set of reserved characters.
        
             By default, the quote function is intended for quoting the path
             section of a URL.  Thus, it will not encode '/'.  This character
        

        可以明顯得看出,主要是多了一個處理函數

        -def _checknetloc(netloc):
        -    if not netloc or netloc.isascii():
        -        return
        -    # looking for characters like \u2100 that expand to 'a/c'
        -    # IDNA uses NFKC equivalence, so normalize for this check
        -    import unicodedata
        -    netloc2 = unicodedata.normalize('NFKC', netloc)
        -    if netloc == netloc2:
        -        return
        -    _, _, netloc = netloc.rpartition('@'# anything to the left of '@' is okay
        -    for c in '/?#@:':
        -        if c in netloc2:
        -            raise ValueError("netloc '" + netloc2 + "' contains invalid " +
        -                             "characters under NFKC normalization")
        

        同時也可以看出,這次主要更新的地方。

        如下對上面的代碼進行分析

        源碼分析

        unicode規范化處理

        如下引用一下https://python3-cookbook.readthedocs.io

        關于unicode的規范化處理,有如下說明

        unicode的規范化格式有幾種,每種的處理方式有些不一樣。  
            NFC  
            Unicode 規范化格式 C。如果未指定 normalization-type,那么會執行 Unicode 規范化。  
            NFD  
            Unicode 規范化格式 D。  
            NFKC  
            Unicode 規范化格式 KC。  
            NFKD  
            Unicode 規范化格式 KD。  
        

        在Unicode中,某些字符能夠用多個合法的編碼表示。為了說明,考慮下面的這個例子:

        >>> s1 = 'Spicy Jalape\u00f1o'
        >>> s2 = 'Spicy Jalapen\u0303o'
        >>> s1
        'Spicy Jalapeño'
        >>> s2
        'Spicy Jalapeño'
        >>> s1 == s2
        False
        >>> len(s1)
        14
        >>> len(s2)
        15
        >>>
        

        這里的文本”Spicy Jalapeño”使用了兩種形式來表示。
        第一種使用整體字符”ñ”(U+00F1),第二種使用拉丁字母”n”后面跟一個”~”的組合字符(U+0303)。

        在需要比較字符串的程序中使用字符的多種表示會產生問題。
        為了修正這個問題,你可以使用unicodedata模塊先將文本標準化:

        >>> import unicodedata
        >>> t1 = unicodedata.normalize('NFC', s1)
        >>> t2 = unicodedata.normalize('NFC', s2)
        >>> t1 == t2
        True
        >>> print(ascii(t1))
        'Spicy Jalape\xf1o'
        >>> t3 = unicodedata.normalize('NFD', s1)
        >>> t4 = unicodedata.normalize('NFD', s2)
        >>> t3 == t4
        True
        >>> print(ascii(t3))
        'Spicy Jalapen\u0303o'
        >>>
        

        normalize() 第一個參數指定字符串標準化的方式。NFC表示字符應該是整體組成(比如可能的話就使用單一編碼),而NFD表示字符應該分解為多個組合字符表示。

        Python同樣支持擴展的標準化形式NFKC和NFKD,它們在處理某些字符的時候增加了額外的兼容特性。比如:

        >>> s = '\ufb01' # A single character
        >>> s
        '?'
        >>> unicodedata.normalize('NFD', s)
        '?'
        # Notice how the combined letters are broken apart here
        >>> unicodedata.normalize('NFKD', s)
        'fi'
        >>> unicodedata.normalize('NFKC', s)
        'fi'
        >>>
        

        漏洞分析

        根據以上分析

        主要的修復方式就是通過對url中的unicode進行規范化處理了,現在通過具體的例子來分析一哈。

        import unicodedata
        netloc2 = unicodedata.normalize('NFKC', netloc)
        if netloc == netloc2:
            return
        

        用我們的WSL的環境(也就是沒有打上補丁的環境)進行測試

        >>> from urllib.parse import urlsplit
        >>> u = "https://example.com\uFF03@bing.com"
        #不處理的結果
        >>> SplitResult(scheme='https', netloc='example.com#@bing.com', path='', query='', fragment='')
        #規范化處理的結果 
        >>>import unicodedata
        >>>u2 = unicodedata.normalize('NFKC', u)
        >>> urlsplit(u2)
        SplitResult(scheme='https', netloc='example.com', path='', query='', fragment='@bing.com')
        #特殊編碼處理的結果
        >>>u3 = u.encode("idna").decode("ascii")
        >>> urlsplit(u3)
        SplitResult(scheme='https', netloc='example.com', path='', query='', fragment='@bing.com')
        

        以上就是漏洞的原理,不同的編碼經處理之后,經過urlsplit() 處理之后,得到的的netloc是不一樣的

        IDNA(Internationalizing Domain Names in Applications)IDNA是一種以標準方式處理ASCII以外字符的一種機制,它從unicode中提取字符,并允許非ASCII碼字符以允許使用的ASCII字符表示。

        unicode轉ASCII發生在IDNA中的TOASCII操作中。如果能通過TOASCII轉換時,將會以正常的字符呈現。而如果不能通過TOASCII轉換時,就會使用“ACE標簽”,“ACE”標簽使輸入的域名能轉化為ASCII碼

        所以在新的urlsplit函數中會增加一個判斷,如果規范化處理的結果和原來的結果一樣,才能返回正確的值。

        題目分析

        def getUrl():
            url = request.args.get("url")
            host = parse.urlparse(url).hostname
            if host == 'suctf.cc':
                return "我扌 your problem? 111"
            parts = list(urlsplit(url))
            host = parts[1]
            if host == 'suctf.cc':
                return "我扌 your problem? 222 " + host
            newhost = []
            for h in host.split('.'):
                newhost.append(h.encode('idna').decode('utf-8'))
            parts[1] = '.'.join(newhost)
            #去掉 url 中的空格
            finalUrl = urlunsplit(parts).split(' ')[0]
            host = parse.urlparse(finalUrl).hostname
            if host == 'suctf.cc':
                return urllib.request.urlopen(finalUrl, timeout=2).read()
            else:
                return "我扌 your problem? 333"
        
        if __name__ == "__main__":
            app.run(host='0.0.0.0', port=80)
        

        根據以上分析,題目就比較簡單了,只需要滿足hostnameencode('idna').decode('utf-8'))處理之前不是suctf.cc 處理之后是suctf.cc就好了

        后記

        漏洞不難理解,只是覺得應該記錄一下

        看到了一句話,摘自某位大佬:

        如果翻譯器對程序進行了徹底的分析而非某種機械的變換, 而且生成的中間程序與源程序之間已經沒有很強的相似性, 我們就認為這個語言是編譯的. 徹底的分析和非平凡的變換, 是編譯方式的標志性特征.

        如果你對知識進行了徹底的分析而非某種機械的套弄, 在你腦中生成的概念與生硬的文字之間已經沒有很強的相似性, 我們就認為這個概念是被理解的. 徹底的分析和非凡的變換, 是獲得真知的標志性特征.

        與君共勉。

        參考鏈接

        參考很多鏈接,不過我覺得,遇到問題看官方的文檔和源碼更有效果

        https://xz.aliyun.com/t/6042#toc-29

        https://www.anquanke.com/post/id/184858#h3-13

        https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

        https://bugs.python.org/issue36216

        https://python3-cookbook.readthedocs.io/zh_CN/latest/c02/p09_normalize_unicode_text_to_regexp.html

        两个人BD高清在线观看2018年|亚洲中文字幕无亚洲人成影院|天天看黄片靠逼视频免费看|国产一级aa无码大片293