<progress id="cjy1t"></progress>
    1. <button id="cjy1t"></button>

    2. <rp id="cjy1t"></rp>

    3. 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

      欧美视频无砖专区一中文字目_玩肥熟老妇bbw视频_久久五月精品中文字幕_女人zozozo人禽交