整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          《你不知道的 Blob》番外篇

          《你不知道的 Blob》番外篇

          習時間:2020.06.06

          學習章節:《你不知道的 Blob》


          原文對 Blob 的知識點介紹得非常完整清晰,本文通過四個問題來總結本文核心知識:

          1. Blob 是什么?
          2. Blob 怎么用?
          3. Blob 有哪些使用場景?
          4. Blob 與 ArrayBuffer 有何區別?

          一、Blob 是什么?

          Blob(Binary Large Object)表示二進制類型的大對象,通常是影像、聲音或多媒體文件。MySql/Oracle數據庫中,就有一種Blob類型,專門存放二進制數據。在 JavaScript 中 Blob 對象表示一個不可變、原始數據的類文件對象,它不一定非得是大量數據,也可以表示一個小型文件的內容。另外,JavaScript 中的 File 接口是基于 Blob,繼承 Blob 的功能并將其擴展使其支持用戶系統上的文件。

          二、Blob 怎么用?

          Blob 由一個可選字符串 type 和 blobParts 組成,其中, type 通常為 MIME 類型。

          MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型,常見有:超文本標記語言文本 .html text/html 、PNG圖像 .png image/png 、普通文本 .txt text/plain 等。

          1. 構造函數

          Blob 構造函數語法為:

          const myBlob=new Blob(blobParts[, options])
          

          入參:

          • blobParts:它是一個由 ArrayBuffer,ArrayBufferView,Blob,DOMString 等對象構成的數組。DOMStrings 會被編碼為 UTF-8。
          • options :一個可選的對象,包含以下兩個屬性: type :默認值為 "" ,表示將會被放入到 blob 中的數組內容的 MIME 類型。endings :默認值為 "transparent",用于指定包含行結束符 \n 的字符串如何被寫入。它是以下兩個值中的一個:"native",代表行結束符會被更改為適合宿主操作系統文件系統的換行符,或者 "transparent",代表會保持 blob 中保存的結束符不變。

          出參:返回一個新創建的 Blob 對象,其內容由參數中給定的數組串聯組成。

          2. 屬性和方法

          2.1 屬性介紹

          Blob 對象擁有 2 個屬性:

          • size :只讀,表示 Blob 對象中所包含的數據大小(以字節為單位);
          • type :只讀,值為字符串,表示該 Blob 對象所包含數據的 MIME 類型。若類型未知,則該屬性值為空字符串。

          2.2 方法介紹

          • slice([start[, end[, contentType]]]) :返回一個新的 Blob 對象,包含了源 Blob 對象中指定范圍內的數據。
          • stream():返回一個能讀取 Blob 內容的 ReadableStream 。
          • text():返回一個 Promise 對象且包含 Blob 所有內容的 UTF-8 格式的 USVString 。
          • arrayBuffer():返回一個 Promise 對象且包含 Blob 所有內容的二進制格式的 ArrayBuffer 。

          注意:** Blob 對象是不可改變的**,但是可以進行分割,并創建出新的 Blob 對象,將它們混合到一個新的 Blob 中。類似于 JavaScript 字符串:我們無法更改字符串中的字符,但可以創建新的更正后的字符串。

          3. 簡單上手

          3.1 示例1:從字符串創建 Blob

          let myBlobParts=['<html><h2>Hello Leo</h2></html>']; // 一個包含DOMString的數組
          let myBlob=new Blob(myBlobParts, {type : 'text/html', endings: "transparent"}); // 得到 blob
          
          console.log(myBlob.size + " bytes size");
          // Output: 31 bytes size
          console.log(myBlob.type + " is the type");
          // Output: text/html is the type
          


          3.2 示例2:從類型化數組和字符串創建 Blob

          JavaScript類型化數組是一種類似數組的對象,并提供了一種用于 訪問原始二進制數據的機制 。并且在類型數組上調用 Array.isArray() 會返回 false 。詳細可參考MDN《JavaScript 類型化數組》章節。

          let hello=new Uint8Array([72, 101, 108, 108, 111]); // 二進制格式的 "hello"
          let blob=new Blob([hello, ' ', 'leo'], {type: 'text/plain'});
          // Output: "Hello leo"
          

          3.3 示例3:組裝新的 Blob

          由于 Blob 對象是不可改變的,但我們可以進行分割,并組裝成一個新的 Blob 對象:

          let blob1=new Blob(['<html><h2>Hello Leo</h2></html>'], 
             {type : 'text/html', endings: "transparent"});
          let blob2=new Blob(['<html><h2>Happy Boy!</h2></html>'], 
             {type : 'text/html', endings: "transparent"});
          let slice1=blob1.slice(16);
          let slice2=blob2.slice(0, 16);
          
          await slice1.text();
          // currtent slice1 value: "Leo</h2></html>"
          await slice2.text();
          // currtent slice2 value: "<html><h2>Happy "
          
          let newBlob=new Blob([slice2, slice1], 
             {type : 'text/html', endings: "transparent"});
          await newBlob.text();
          // currtent newBlob value: "<html><h2>Happy Leo</h2></html>"
          

          三、Blob 有哪些使用場景?

          1. 圖片本地預覽

          這里整理 2 種圖片本地預覽的方式:

          1. 使用 DataURL 方式;
          2. 使用 Blob URL/Object URL 方式;
          <body>
              <h1>1.DataURL方式:</h1>
              <input type="file" accept="image/*" onchange="selectFileForDataURL(event)">
              <img id="output1">
          
              <h1>2.Blob方式:</h1>
              <input type="file" accept="image/*" onchange="selectFileForBlob(event)">
              <img id="output2">
          
              <script>
                  // 1.DataURL方式:
                  async function selectFileForDataURL() {
                      const reader=new FileReader();
                      reader.onload=function () {
                          const output=document.querySelector("#output1")
                          output.src=reader.result;
                      }
                      reader.readAsDataURL(event.target.files[0]);
                  }
          
                  //2.Blob方式:
                  async function selectFileForBlob(){
                      const reader=new FileReader();
                      const output=document.querySelector("#output2");
                      const imgUrl=window.URL.createObjectURL(event.target.files[0]);
                      output.src=imgUrl;
                      reader.onload=function(event){
                          window.URL.revokeObjectURL(imgUrl);
                      }
                  }
              </script>
          </body>
          

          上面主要介紹 Blob URL 和 Data URL 兩種方式實現圖片本地預覽,這兩個類型的區別在**《五、拓展》**中介紹。

          2. 圖片本地預覽 + 分片上傳

          實現本地預覽:將 input 獲取到的 file 對象,通過實例化 FileReader ,賦值給變量 reader ,調用reader 的 readAsDataURL 方法,將 file 對象轉換為 dataURL ,然后監聽 reader 的 onload 屬性,獲取到讀取結果 result ,然后設置為圖片的 src 值。 實現分片上傳:由于 File 是特殊類型的 Blob,可用于任意 Blob 類型的上下文,所以針對大文件傳輸,我們可以使用 slice 方法進行文件切割,分片上傳。

          <body>
              <input type="file" accept="image/*" onchange="selectFile(event)">
              <button onclick="upload()">上傳</button>
              <img id="output">
          
              <script>
                  const chunkSize=10000;
                  const url="https://httpbin.org/post";
                  async function selectFile(){
                     // 本地預覽
                      const reader=new FileReader();
                      reader.onload=function(){
                          const output=document.querySelector("#output")
                          output.src=reader.result;
                      }
                      reader.readAsDataURL(event.target.files[0]);
          
                     // 分片上傳
                      await upload(event.target.files[0]);
                  }
                  async function upload(files){
                      const file=files;
                      for(let start=0; start < file.size; start +=chunkSize){
                          const chunk=file.slice(start, start + chunkSize + 1);
                          const fd=new FormData();
                          fd.append("data", chunk);
                          await fetch(url, { method: "post", body: fd }).then((res)=>{
                              console.log(res)
                              res.text();
                          });
                      }
                  }
              </script>
          </body>
          

          3. 圖片本地預覽 + 分片上傳 + 暫停 + 續傳

          <body>
              <input type="file" accept="image/*" onchange="selectFile(event)">
              <button onclick="upload()">上傳</button>
              <button onclick="pause()">暫停</button>
              <button onclick="continues()">繼續</button>
              <img id="output" src="" alt="">
          
              <script>
                  const chunkSize=30000;
                  let start=0, curFile, isPause=false;
                  const url="https://httpbin.org/post";
                  async function selectFile(){
                      const reader=new FileReader();
                      reader.onload=function(){
                          const output=document.querySelector("#output")
                          output.src=reader.result;
                      }
                      reader.readAsDataURL(event.target.files[0]);
                      curFile=event.target.files[0];
                  }
                  async function upload(){
                      const file=curFile;
                      for(start; start < file.size; start +=chunkSize){
                          if(isPause) return;
                          const chunk=file.slice(start, start + chunkSize + 1);
                          const fd=new FormData();
                          fd.append("data", chunk);
                          await fetch(url, { method: "post", body: fd }).then((res)=>{
                                  res.text()
                              }
                          );
                          if(chunk.size < chunkSize){
                              uploadSuccess();
                              return;
                          }
                      }
                  }
                  function pause(){
                      isPause=true;
                  }
                  function continues(){
                      isPause=false;
                      upload();
                  }
                  function uploadSuccess(){
                      isPause=false;
                      start=0;
                  }
              </script>
          </body>
          

          4. 從互聯網下載數據

          在實現“從互聯網下載數據”方法時,我們使用 createObjectURL 顯示圖片,在請求互聯網圖片時,我們有兩種方式:

          • 使用 XMLHttpRequest ;
          • 使用 fetch ;
          <body>
              <button onclick="download1()">使用 XMLHttpRequest 下載</button>
              <button onclick="download2()">使用 fetch 下載</button>
              <img id="pingan">
              <script>
                  const url="http://images.pingan8787.com/TinyCompiler/111.png";
                  const pingan=document.querySelector('#pingan');
                  function download1 (){
                      const xhr=new XMLHttpRequest();
                      xhr.open('GET', url);
                      xhr.responseType='blob';
                      xhr.onload=()=> {
                          renderImage(xhr.response);
                      }
                      xhr.send(null);
                  }
                  function download2 (){
                      fetch(url).then(res=> {
                          return res.blob();
                      }).then(myBlob=> {
                          renderImage(myBlob);
                      })
                  }
          
                  function renderImage (data){
                      let objectURL=URL.createObjectURL(data);
                      pingan.src=objectURL;
                     // 根據業務需要手動調用 URL.revokeObjectURL(imgUrl)
                  }
              </script>
          </body>
          

          5. 下載文件

          通過調用 Blob 的構造函數來創建類型為 "text/plain" 的 Blob 對象,然后通過動態創建 a 標簽來實現文件的下載。

          <body>
              <button onclick="download()">Blob 文件下載</button>
          
              <script>
                  function download(){
                      const fileName="Blob文件.txt";
                      const myBlob=new Blob(["一文徹底掌握 Blob Web API"], { type: "text/plain" });
                      downloadFun(fileName, myBlob);
                  }
                  function downloadFun(fileName, blob){
                      const link=document.createElement("a");
                      link.href=URL.createObjectURL(blob);
                      link.download=fileName;
                      link.click();
                      link.remove();
                      URL.revokeObjectURL(link.href);
                  }
              </script>
          </body>
          

          6. 圖片壓縮

          當我們希望本地圖片在上傳之前,先進行一定壓縮,再提交,從而減少傳輸的數據量。在前端我們可以使用 Canvas 提供的 toDataURL() 方法來實現,該方法接收 type 和 encoderOptions 兩個可選參數:

          • type 表示圖片格式,默認為 image/png ;
          • encoderOptions 表示圖片質量,在指定圖片格式為 image/jpeg 或 image/webp 的情況下,可以從 0 到 1 區間內選擇圖片質量。如果超出取值范圍,將會使用默認值 0.92,其他參數會被忽略。
          <body>
              <input type="file" accept="image/*" onchange="loadFile(event)" />
              <script>
                  // compress.js
                  const MAX_WIDTH=800; // 圖片最大寬度
                 // 圖片壓縮方法
                  function compress(base64, quality, mimeType) {
                      let canvas=document.createElement("canvas");
                      let img=document.createElement("img");
                      img.crossOrigin="anonymous";
                      return new Promise((resolve, reject)=> {
                          img.src=base64;
                          img.onload=()=> {
                              let targetWidth, targetHeight;
                              if (img.width > MAX_WIDTH) {
                                  targetWidth=MAX_WIDTH;
                                  targetHeight=(img.height * MAX_WIDTH) / img.width;
                              } else {
                                  targetWidth=img.width;
                                  targetHeight=img.height;
                              }
                              canvas.width=targetWidth;
                              canvas.height=targetHeight;
                              let ctx=canvas.getContext("2d");
                              ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除畫布
                              ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                              let imageData=canvas.toDataURL(mimeType, quality / 100); // 設置圖片質量
                              resolve(imageData);
                          };
                      });
                  }
          
                  // 為了進一步減少傳輸的數據量,我們可以把它轉換為 Blob 對象
                  function dataUrlToBlob(base64, mimeType) {
                      let bytes=window.atob(base64.split(",")[1]);
                      let ab=new ArrayBuffer(bytes.length);
                      let ia=new Uint8Array(ab);
                      for (let i=0; i < bytes.length; i++) {
                          ia[i]=bytes.charCodeAt(i);
                      }
                      return new Blob([ab], { type: mimeType });
                  }
          
                  // 通過 AJAX 提交到服務器
                  function uploadFile(url, blob) {
                      let formData=new FormData();
                      let request=new XMLHttpRequest();
                      formData.append("image", blob);
                      request.open("POST", url, true);
                      request.send(formData);
                  }
          
                  function loadFile(event) {
                      const reader=new FileReader();
                      reader.onload=async function () {
                          let compressedDataURL=await compress(
                              reader.result,
                              90,
                              "image/jpeg"
                          );
                          let compressedImageBlob=dataUrlToBlob(compressedDataURL);
                          uploadFile("https://httpbin.org/post", compressedImageBlob);
                      };
                      reader.readAsDataURL(event.target.files[0]);
                  };
              </script>
          </body>
          

          其實 Canvas 對象除了提供 toDataURL() 方法之外,它還提供了一個 toBlob() 方法,該方法的語法如下:

          canvas.toBlob(callback, mimeType, qualityArgument)
          

          和 toDataURL() 方法相比,toBlob() 方法是異步的,因此多了個 callback 參數,這個 callback 回調方法默認的第一個參數就是轉換好的 blob文件信息。

          7. 生成 PDF 文檔

          在瀏覽器端,利用一些現成的開源庫,比如 jsPDF,我們也可以方便地生成 PDF 文檔。

            <body>
              <h3>客戶端生成 PDF 示例</h3>
              <script src="https://unpkg.com/jspdf@latest/dist/jspdf.min.js"></script>
              <script>
                (function generatePdf() {
                  const doc=new jsPDF();
                  doc.text("Hello semlinker!", 66, 88);
                  const blob=new Blob([doc.output()], { type: "application/pdf" });
                  blob.text().then((blobAsText)=> {
                    console.log(blobAsText);
                  });
                })();
              </script>
            </body>
          

          其實 jsPDF 除了支持純文本之外,它也可以生成帶圖片的 PDF 文檔,比如:

          let imgData='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/...'
          let doc=new jsPDF();
          
          doc.setFontSize(40);
          doc.text(35, 25, 'Paranyan loves jsPDF');
          doc.addImage(imgData, 'JPEG', 15, 40, 180, 160);
          

          四、Blob 與 ArrayBuffer 有何區別?

          1. 定義區別

          ArrayBuffer 對象用于表示通用的,固定長度的原始二進制數據緩沖區。且不能直接操縱 ArrayBuffer 的內容,需要創建一個類型化數組對象或 DataView 對象,該對象以特定格式表示緩沖區,并使用該對象讀取和寫入緩沖區的內容。 Blob 類型的對象表示不可變的類似文件對象的原始數據。Blob 表示的不一定是 JavaScript 原生格式的數據。File 接口基于 Blob,繼承了Blob 功能并將其擴展為支持用戶系統上的文件。 Blob 類型只有 slice 方法,用于返回一個新的 Blob 對象,包含了源 Blob 對象中指定范圍內的數據。 對比發現,ArrayBuffer 的數據,是可以按照字節去操作的,而 Blob 只能作為一個完整對象去處理。所以說,ArrayBuffer 相比 Blob 更接近真實的二進制,更底層。

          2. 兩者互轉

          2.1 ArrayBuffer 轉 Blob

          只需將 ArrayBuffer 作為參數傳入即可:

          const buffer=new ArrayBuffer(16);
          const blob=new Blob([buffer]);
          

          2.2 Blob 轉 ArrayBuffer

          需要借助 FileReader 對象:

          const blob=new Blob([1,2,3,4,5]);
          const reader=new FileReader();
          
          reader.onload=function() {
              console.log(this.result);
          }
          reader.readAsArrayBuffer(blob);
          

          3. 其他區別

          1. 需要使用寫入/編輯操作時使用 ArrayBuffer,否則使用 Blob 即可;
          2. Blob 對象不可變,而 ArrayBuffer 可以通過 TypedArrays 或 DataView 操作;
          3. Blob 可以位于磁盤、高速緩存內存和其他不同用位置,而 ArrayBuffer 存在內存中,可以直接操作;

          4. Ajax 中使用 Blob 和 ArrayBuffer

          function GET(url, callback) {
            let xhr=new XMLHttpRequest();
            xhr.open('GET', url, true);
            xhr.responseType='arraybuffer'; // or xhr.responseType="blob";
            xhr.send();
          
            xhr.onload=function(e) {
              if (xhr.status !=200) {
                alert("Unexpected status code " + xhr.status + " for " + url);
                return false;
              }
              callback(new Uint8Array(xhr.response)); // or new Blob([xhr.response]);
            };
          }
          

          五、拓展

          1. Blob URL 和 Data URL 區別

          1.1 格式不同

          Blob URL 格式如 blob:域名/uuid , Data URL 格式如: data:[<mediatype>][;base64],<data> 。mediatype 是個 MIME 類型的字符串,例如 "image/jpeg" 表示 JPEG 圖像文件。如果被省略,則默認值為 text/plain;charset=US-ASCII。

          1.2 長度不同

          Blob URL 一般長度較短,而 Data URL 因為直接存儲圖片 base64 編碼后的數據,往往比較長。

          1.3 XMLHttpRequest 支持情況不同

          Blob URL 可以很方便使用 XMLHttpRequest 獲取源數據( xhr.responseType='blob' ),而 Data URL 并不是所有瀏覽器都支持通過 XMLHttpRequest 獲取源數據的。

          1.4 使用場景不同

          Blob URL 只能在當前應用內使用,把 Blob URL 復制到瀏覽器地址欄是無法獲取數據,而 Data URL 則可以在任意瀏覽器中使用。

          六、總結

          本文中我們主要通過 4 個問題來復習了 Blob 知識點:“Blob 是什么”、“Blob 怎么用”、“Blob 使用場景”和“Blob 與 ArrayBuffer 區別”,在“Blob 使用場景”部分中,也主要介紹了我們實際開發中非常常見的“圖片預覽”、“圖片下載”和“生成文件”的場景。在文章最后,也通過和大家復習了“Blob URL 和 Data URL 區別”,讓我們對 Blob 有更深的認識。


          輪播圖已經是一個很常見的東西,尤其是在各大App的首頁頂部欄,經常會輪番顯示不同的圖片。

          一提到輪播圖如何實現時,很多人的第一反應就是使用Javascript的定時器,當然這種方法是可以實現的。不過就是有些繁瑣,今天這篇文章我們來看看如何不用Javascript,而使用純CSS代碼去實現輪播圖吧。

          這篇文章的所有代碼我都放在了github上,感興趣的同學可以去看看。

          https://github.com/zhouxiongking/article-pages/blob/master/articles/carousel/carousel.html

          CSS

          實現效果

          首先我們來看看只使用CSS實現的輪播圖效果。

          實現效果圖

          具體分析

          看到上述的實現效果后,我們來具體分析下頁面的構成。

          • 頁面在布局上首先要有5張圖片,圖片固定寬度。

          • 每張圖片對應一個標題,標題通過ul>li實現,事先算好寬度,跟隨圖片一起滾動。

          • 下邊有個1,2,3,4,5表示圖片順序的索引,鼠標放上去后會顯示對應的圖片。

          HTML頁面

          接下來我們通過代碼層面去看看整個效果是如何實現的。

          首先來看看HTML頁面的實現,代碼中都有每個區域的描述。

          HTML頁面

          CSS部分

          實現這個效果主要是通過CSS代碼的,其代碼量比較大,我們分開來看。

          • 外層容器

          對于最外層容器我們設置絕對定位,方便圖片標題子元素的定位。

          外層容器

          • 圖片標題

          對于圖片的標題我們也采用絕對定位,并且讓標題橫向一行展示,方便在動畫的時候直接橫向滾動。

          得到的代碼如下所示。

          圖片標題

          • 圖片與圖片容器

          接下來是設置圖片容器屬性以及圖片的基本大小。

          圖片容器也采用絕對定位,寬度可以動態設置,根據圖片數量計算。每張圖片設置寬度與高度,得到的代碼如下。

          圖片與圖片容器

          • 圖片動畫效果

          然后設置圖片的動畫效果,對于任意的圖片都有進入和靜止兩個狀態,中間的效果可以任意定制。

          在這里,中間效果設置成5%的間隔,其他時間在進行位置的切換,因為圖片是處于水平分布,通過設置margin-left的值為負數進行偏移即可。

          圖片動畫效果

          • 數字索引的基本屬性

          對于下面的數字圖標也是通過基本的CSS屬性進行設置的,包括寬高,行高,透明度等等。

          在鼠標移動到對應的數字上后,數字會顯示不同的顏色。而且在鼠標停留在數字上后,動畫效果會暫停。

          數字索引基本屬性

          • 數字索引的偏移量

          因為數字是水平方向展示的,因此要設定每個數字的水平偏移量。

          數字水平偏移量

          • 鼠標停在數字上的動畫效果

          然后就是處理鼠標停留在數字上的動畫效果,因為每張圖片對應特定的數字,需要計算出每次的動畫開始位置和結束位置。

          鼠標停在數字上的動畫效果

          • 動畫效果賦予指定的數字

          最后一步就是將定義的動畫效果賦予指定的數字上,每個數字都有特定的id。得到的代碼如下。

          動畫效果賦予指定的數字

          至此所有步驟完成了,就可以得到文章開始的動畫效果了。

          結束語

          這篇文章完全通過CSS實現了一個輪播圖的效果,相比于使用JS來說減少頁面阻塞程度,效果更好。

          今天介紹一種走私技術,HTML Smuggling,這不是一種特別新的攻擊方法,但是它確實在越來越多的攻擊場景中出現,其中就包括網絡釣魚。接下來我們就來了解一下HTML走私的來龍去脈以及它在釣魚中是怎樣被利用的吧。


          HTML走私介紹

          什么是HTML走私?HTML走私是指,瀏覽器根據HTML文件的內容在主機上創建惡意文件,而不是直接轉發/下載惡意軟件的一種技術。HTML走私可以通過在HTML頁面中將惡意文件隱藏為編碼后的“string”來繞過外圍安全性檢查和在線檢測。

          大多數周邊/在線安全檢測方法都是通過匹配文件中的某些特定模式。但在HTML走私的情況下,惡意文件被嵌在瀏覽器中,所以檢測這樣隱藏的代碼是困難的。而且由于代碼中的混淆,甚至檢測HTML走私這種情況本身也很困難。

          利用方法

          在去年也就是2020年的網絡釣魚調查中,我遇到了兩種本質上不同的HTML走私方式。這兩種方案都將數據存儲在HTML文件中,并且提供了無需向服務器發送額外請求就可以“下載”數據的方法。此外,這兩種方案都是基于HTML5的下載屬性(雖然不是強制性的但是有好處)。

          Javascript Blob

          先介紹第一種方案,基于JavaScript Blob,也是我經常看到的方案。Blob對象表示一個不可變、原始數據的類文件對象。它的數據可以按照文本或二進制的格式進行讀取,也可以轉換為ReadableStream來讀取。借助Blob,我們可以將我們的惡意文件存儲在HTML代碼中,然后將其置入瀏覽器中,而不是直接向Web服務器發送文件上傳請求。

          我們可以通過下面兩個鏈接來創建我們的HTML頁面:

          https://gist.github.com/darmie/e39373ee0a0f62715f3d2381bc1f0974

          https://developer.mozilla.org/en-US/docs/Web/API/Blob

          要存儲文件,首先,需要將文件編碼為base64,可以在PowerShell中使用一下代碼來實現:

          $base64string=[Convert]::ToBase64String([IO.File]::ReadAllBytes($FileName))
          

          然后替換下面html文件中<>所指的值。fileName變量表示你希望在默認情況下下載文件的名稱。base64_encoded_file變量存儲base64編碼后的文件。

          <html> 
          <body> 
          <script> 
          
              var fileName=<>
              var base64_encoded_file=<>
              function _base64ToArrayBuffer(base64,mimeType) { 
                  var binary_string=window.atob(base64); 
                  var len=binary_string.length; 
                  var bytes=new Uint8Array( len ); 
                  for (var i=0; i < len; i++)        { 
                      bytes[i]=binary_string.charCodeAt(i); 
                      } 
                  return URL.createObjectURL(new Blob([bytes], {type: mimeType})) 
              } 
          
              var url=_base64ToArrayBuffer(base64_encoded_file,'octet/stream') 
          
              const link=document.createElement('a'); 
              document.body.appendChild(link); 
          
              link.href=url; 
              link.download=fileName; 
              link.innerText='Download'; 
          
          </script> 
          </body> 
          </html>
          

          現在只要我們點擊上面的鏈接,就會開始下載步驟。

          DataURL

          實現HTML走私的另一種方案是使用DataURL。這種解決方案就不需要使用JavaScript了。使用DataURL進行HTML走私的目標就是將較小的文件嵌入HTML文件中。

          雖然上面強調了小文件,但是實際上,限制也不是很嚴格。DataURL的最大長度由瀏覽器中的最大URL長度決定。比如,在Opera中,這個大小是65535個字符,雖然這個字節數對于轉移1080p的電影來說是遠遠不夠的,但是用來反彈shell已經足夠了。

          這種方法比第一種基于JS Blob的要簡單得多。先看一下語法:

          data::前綴;

          [<mediatype>]:可選項,是一個MIME類型的字符串,比如我們最常見的image/jpeg表示JPEG圖像,如果缺省,默認值為text/plain;charset=US-ASCII

          [;base64]:可選項,如果數據是base64編碼的,那么就用;base64標記

          ,:將數據與控制信息分開;

          <data>:數據本身。

          <html>
          <body>
          <a href="data:text/x-powershell;base64,aXBjb25maWcNCmhvc3RuYW1l" 
              download="test.ps1">
              Download
          </a>
          </body>
          </html>
          

          這是一個簡單的HTML例子,加載該頁面的時候會顯示一個download按鈕。我們點擊下載,瀏覽器就會將test.ps1文件保存到我們的電腦上。而寫入的內容自然就是aXBjb25maWcNCmhvc3RuYW1lbase64解碼后的內容。

          發現了一個比較有意思的事情。當講將<a>標簽中的download去掉后,也就是變成了下面的樣子:

          <html>
          <body>
          <a href="data:text/x-powershell;base64,aXBjb25maWcNCmhvc3RuYW1l" >
              Download
          </a>
          </body>
          </html>
          

          我們重新在Chrome瀏覽器中打開并嘗試下載,這個時候發現,這個按鈕已經失效了,點擊不會有任何反應。但是用Firefox打開,依舊可以下載文件,只不過firefox會創建一個隨機的文件名,并且保存的文件不會有后綴:

          綜上,我們可以將DataURL和JS Blob相結合來添加一個函數,當然我們可以不適用任何腳本,但是和上面的例子中的問題一樣,顯然會更到更多的限制。

          為什么攻擊者會使用HTML走私?

          HTML走私是釣魚攻擊一個很好的補充。在HTML走私的幫助下,攻擊者可以逃避一些檢測和記錄機制。在這種方式下,文件被“注入”到HTML文檔中,而絕大多數的安全解決方案根本不會去檢測。

          當然HTML走私也有一些缺點。首先,它需要用戶交互,雖然瀏覽器會自動處理惡意文件,但是下載到用戶主機還是需要用戶同意,除非配置了未經用戶確認的自動下載。而且即使文件被下載了,還是需要將其執行。


          利用HTML走私來釣魚

          上面介紹了兩種常見的HTML走私方法,回到文章的主題,如何利用HTML走私來進行釣魚呢?這里介紹兩種比較常見的方法。首先,第一種是讓電子郵件包含一個指向外部網頁的鏈接,而該指向的網頁使用了HTML走私技術。這是比較常見的郵件釣魚方式。另一種方法是,在電子郵件中添加一個HTML附件,該HTML文件走私了惡意代碼,這種情況下,用戶打開的就是本地的HTML文件,不需要訪問外部站點。

          在這兩種情況下,好處是惡意內容很難被檢測到,因為惡意代碼隱藏在HTML內容中。通過這種方式,可以繞過很多道安全防御。

          正常文件下載

          首先,我們來看一下正常的文件下載步驟是怎樣的。在下圖的例子中,用戶訪問一個網站,然后下載一個exe文件:

          1 . 首先,用戶打開一個正常的網站。這個網站可以是用戶自己輸入的,也可以從包含鏈接的釣魚郵件中獲得。
          2 . 瀏覽器向網站發送HTTP請求。如果存在代理,那么這個流量就會走代理。
          3 . 代理會檢查瀏覽器請求的URL。因為許多的代理都有檢查域信譽的能力,如果代理認為請求的域是惡意的,那么Proxy不會進行后續的請求,通信將在這一步就停止;反之,代理會將請求轉發給Web服務器。
          4 . Web服務器返回請求的資源給代理,在本例中,Web服務器返回的就是一個普通的HTML頁面。
          5 . 代理檢查Web服務器的響應。在這一步會進行各種過濾。代理可以阻止用戶下載一些類型的文件。此外,代理可以有一個內置的沙箱來測試文件是否安全,而HTML文件通常不會再沙箱中進行測試就被允許通過代理轉發給用戶。
          6 . 頁面通過代理轉發給瀏覽器。此時判斷文件可信度的角色就變成了用戶,用戶可以決定瀏覽器中顯示的某個文件是否安全,如果用戶信任,就可以進行文件下載,在本例中,該文件是一個exe文件。
          7 . 瀏覽器向Web服務器發送文件下載請求。
          8 . 代理的處理和之前相同,這一步沒什么區別。
          9 . Web服務器將請求的exe文件發送給代理。
          10 . 當代理獲得該exe文件時候,它會對其進行綜合性評估。如果設置了網絡中禁止下載exe文件,那么顯然,exe下載請求在這一步就結束了,文件將無法到達用戶的機器。代理還會檢查文件是否是已知的惡意文件。此外,它還可以決定是不是要將文件放到沙箱中進行測試。這里有一個關鍵點,當代理知道這個文件是一個可執行文件時候,它可以決定是不是要繼續轉發。而且,網絡分路器(Network Taps)可以檢測到這個exe文件,如果它們認為該文件是惡意的,也可以阻止用戶下載該文件。
          11 . 如果一切順利,那么文件就到達了它的最終目的地——用戶的電腦。

          在上面的步驟中,最重要的一步就是第10步。這一步有普通文件下載和HTML走私下載的最大區別。在正常下載的情況下,exe文件易于檢測和評估(比如檢查散列,沙箱執行檢查,收集頭文件信息來判斷等等)。而在HTML走私的情況下,文件不會以一種未編碼的方式在網絡上傳播,它總是會被嵌入到另一個HTML文件中。

          通過e-mail中的外部鏈接

          首先,先介紹一種很常見的走私技巧。在這種情況下,攻擊者會將事先準備好的鏈接放入電子郵件中。該鏈接會指向包含走私文件的外部鏈接。在這種情況下,惡意的內容不會通過電子郵件的網關,而是以隱藏的形式,通過代理被用戶訪問。

          1 . 有一個帶著外部鏈接的e-mail向目標用戶發送。
          2 . 郵件網關對郵件進行檢查。在這一步有很多種檢查方法,但在本例的場景下,我們就認為郵件網關并不會阻攔該郵件,因為這里沒有包含什么惡意文件。
          3 . 用戶點擊郵件中的鏈接。
          4 . 默認瀏覽器啟動。
          5 . 瀏覽器向存儲(或是生成)HTML文檔的外部站點發送HTTP請求。
          6 . 代理會進行一些必要的檢查,但是如果該URL并沒有被加入黑名單,那么該請求就會被轉發到該站點所對應的服務器。
          7 . 服務器返回HTML頁面以及隱藏在其中的exe文件。exe文件是以字符串的形式存儲在文檔中,此時它已經變成了HTML內容的一部分。
          8 . 代理會檢查響應內容。在本例中,響應只是一個HTML文件,代理不會去攔截HTML文件。而且,HTML文件在絕大多數情況下不會被轉發到沙箱中執行檢查。因而,隱藏在HTML文檔中的惡意內容幾乎不會在這里被檢測到。當然通過其他的檢測技術,它們也可能能識別到HTML走私技術(但是這里指的也只是HTML走私這件事本身,而不是隱藏在HTML中的惡意文件)。現在我們就認為代理沒有識別出這個情況,將響應HTML頁面轉發給瀏覽器。
          9 . 瀏覽器重新拼接嵌入在HTML頁面中的字符串,然后啟動下載進程。
          10 . 最后惡意exe文件被存儲到用戶的電腦上。

          我們可以看到,在這種情況下,e-mail的安全檢查機制并不能起到太大的作用。因為電子郵件本身不是惡意的,其中的鏈接可以是合法的鏈接。

          代理當然可以查看HTML文件,但是很少有安全性檢測方案會對HTML文件進行徹底地處理。大多數情況下,這些文件都是被允許通過代理的。IDS解決方案還傾向于依賴檢測文件擴展名或是magic bytes(主要是通過分析文件的第一個字節來判斷文件的類型),因此文件中的編碼字符串很難被它們捕獲。但是其實我們可以創建一個模式匹配解決方案來檢測一些隱藏在HTML中的東西。這在后面會提到。

          通過電子郵件中的HTML附件

          這種方法是指包含走私的HTML文件被附加到了電子郵件中。

          1 . 郵件網關收到一份帶有HTML附件的電子郵件。
          2 . 郵件網關會對電子郵件中附帶的HTML進行檢查。如果檢查未通過,那么郵件網關會阻擊電子郵件的后續轉發或者選擇刪除附件。但是,現在還沒有聽說過什么電子郵件安全解決方案能夠通過靜態分析或者動態分析來檢測HTML走私(即使是在HTML走私的是惡意代碼。)
          3 . 用戶打開HTML附件。
          4 . 瀏覽器打開并加載HTML文檔。
          5 . 瀏覽器處理HTML文件的內容,處理被走私的文件并將其提供給用戶下載。
          6 . 文件被保存到計算機上。當然,在這一步,要么需要用戶的批準,要么需要瀏覽器設置了自動下載,就是未經確認的下載。

          這種方法相比上一種的好處就在于,它不需要通過任何代理。而且,網絡上的IDS/IPS安全檢查通常不會去檢查電子郵件,所以,如果攻擊者能夠成功欺騙電子郵件網關,那么在網絡上就沒有其他的難處了。


          防御與檢測

          在這一小節中我們來探討一下如何防御與檢測HTML走私。

          在發生了HTML走私的情況下,我們可以有兩種檢測方式,我們要么就關注HTML走私技術本身,要么可以嘗試捕獲隱藏在HTML文檔中的惡意代碼。

          防御方法

          這里介紹幾種防御方法。

          1. 禁止JavaScript執行

          雖然對于HTML走私沒有什么萬能的防御辦法。禁用JavaScript看起來是一種可行的方案,畢竟一種HTML走私技術就是基于JavaScript Blob的,但是,在企業環境中禁用JS肯定會帶來很多問題。當然,如果在你遇到的顯示場景中,禁用JS是可行的,那么也可以嘗試這種方法,那么在這個時候,你只需要用模式匹配去識別DataURL是不是存在HTML走私情況就好。

          2. 禁止郵件中的HTML附件

          HTML走私的一種類型是使用HTML文件作為附件。不幸的是,這在很多場景中都是常見的,實際上,現在有很多帶有HTML附件的合法的、與業務相關的電子郵件在增加。另一方面,不法分子也利用HTML附件來進行釣魚。

          如果你能在不影響業務的情況下阻止帶有HTML附件的郵件,那么對于利用郵件中帶有的HTML來進行釣魚的路就被堵住了。那此時我們只需要關注郵件中附帶的鏈接了。

          我們可以看到,前面這兩種防御方法都無法同時覆蓋所有的HTML走私情況。在最好的情況下,我們可以使用這兩種方案來防住部分HTML走私;在最壞的情況下,我們甚至都不能使用這些防御解決方案,因為這些方案在大多數現在企業環境中是不適用的。

          3. Windows應用程序防護(Microsoft Defender Application Guard)

          Microsoft Defender Application Guard是Windows提供的一種安全解決方案,主要用于隔離各種試圖使用來自不可信源資源的應用程序,后面簡稱MDAG。MDAG有兩種不同的形式。第一種是Application Guard for Edge,它主要是用來隔離企業定義的不受信任(或不特別受信任)的站點,在企業的員工瀏覽Internet時保護公司。Application Guard通過在一個啟用了Hyper-V的隔離容器中打開站點來實現這一點。而且,它還與Firefox和Chrome瀏覽器兼容。

          另一個版本是Application Guard for Office。它能夠保護可信資源不受不可信資源的攻擊。它也是通過在一個容器中打開不受信任的Word、Excel、PowerPoint文檔來實現。當然,這也和HTML走私無關,所以就不繼續討論了。

          Application Guard for Edge

          我們已經知道,Application Guard for Edge主要是通過在隔離的虛擬環境中打開不受信任的鏈接來保護我們的系統。如此一來,一個未知的URL,或者說是一個惡意的URL就不會對主機造成危害了。下載的惡意文件會被隔離存儲在隔離的環境中,而不會直接與宿主機接觸。

          如果一個鏈接在電子郵件中到達,并且給定的URL并不在Application Guard的白名單中,那么它就會在一個隔離的Edge中被打開。因為對于該URL的訪問是在隔離的環境中進行的,那么每個文件下載行為也都是在這個隔離的環境中進行的,因此被走私的惡意文件不會對主機造成真實傷害。

          當然,這個解決方案也并不是十全十美的。它也有一些缺點:

          1 . 為了使Edge能發揮作用,你需要使用白名單和untrust來標記一切網站。這在大公司中,意味著相關的管理員需要承受巨大的負載。因為用戶會不斷地進行異常請求。
          2 . 雖然Application Guard能夠防住以鏈接形式附在電子郵件中的HTML走私請求,但是它無法防御以電子郵件附件形式傳遞的HTML走私。這些附件到達用戶主機后,會作為本地文件在瀏覽器中打開,因此,這些文件不會被當作不受信任的域來處理,不會在隔離的Edge容器中打開這些HTML文件,也就不會觸發Application Guard。因此通過電子郵件附件進行的HTML走私在這種情況下依然可以成功。
          3 . 會影響用戶訪問站點的速度,因為需要啟動容器。

          檢測方法

          1. 模式匹配

          如果HTML文件中沒有利用JS來走私,而是僅僅使用了DataURL,在這種情況下,我們可以使用模式匹配(比如匹配data:)來判斷是不是發生了HTML走私。我們可以實用IDS或是簡單的YARA工具來檢測,比如github上的開源工具LaikaBOSS就是一款可用的檢測工具。

          KaikaBOSS對于下載的文件(HTML文件),甚至是電子郵件附帶的文件,都非常有效。它可以檢測大量的數據,伸縮性很好,所以即使在大型環境中,也同樣可以派上用場。

          如果使用了JavaScript,那么可能代碼是被混淆過的,此時模式匹配可能就不太有效果了。我們只能用其他的方法來檢測混淆的代碼。

          2. 檢測瀏覽器是否通過網絡下載文件

          在使用了HTML走私的場景下,被下載的文件是在瀏覽器中創建的,而在一般的下載場景中,我們可以看到,是需要我們向Web服務器發起請求,然后Web服務器傳送給我們的,而且還會經過代理,經由代理檢查文件的安全性。因此我們還可以通過關注普通文件下載和基于HTML文件創建下載文件之間的區別來鑒別HTML走私。因此我們可以通過查看代理日志、觀察是否有瀏覽器進行在主機上創建文件等方式來查看是不是發生了HTML走私。

          這種方式的缺點在于:

          1 . 許多流量在默認情況下不會通過代理。比如,一些公司會配置它們的網絡,使其內部網站無法通過代理訪問。這個時候,如果通過瀏覽器生成了一個文件,但是沒有代理日志。這種情況經常發生,不應該基于這種情況創建警報。
          2 . 在一些情況下,用戶可以繞過代理,也不會產生代理日志。
          3 . 可以在瀏覽器中打開本地文件,然后保存為新文件,這也是瀏覽器創建的文件,同樣沒有代理日志。

          如果用戶是在上面的情況下進行合法行為,我們都不應該進行攔截,因此難點就在于我們很難區分這是不是一種走私情況。

          3. 沙箱執行

          您可以將HTML文件發送到沙箱,然后使用沙箱中的瀏覽器打開它。不是通過互聯網訪問的HTML文件除了一些臨時文件之外,不應該在機器上創建任何文件。理論上,如果在沒有網絡訪問的情況下,HTML文件在沙箱中創建了文件,那么沙箱應該要發出一個警報來提醒用戶。

          但是,經驗表明,HTML文件通常不會放到沙箱中打開。它們可以通過靜態分析進行檢查(如LaikaBoss),也可以動態分析進行檢查,但是動態分析將花費更多的時間,并且會顯著降低網絡速度。

          但是沙箱這個解決方案在大多數的情況下不是特別友好,因為在轉發給用戶之前打開每個HTML文件負載也挺大的。而且,如果惡意文件是需要用戶點擊下載的,那么這種方式也行不通,只有在頁面配置了自動下載的情況下,我們才能通過沙箱來檢測。所以總體來說,這也不是一個特別好的方法。

          4. 使用Zone.Identifier

          另一種方式是使用取證分析中可能會使用的技術——Zone.Identifier來區分是不是發生了HTML走私。

          由HTML走私創建的文件的Zone.Identifier是這樣的(在新版Edge、Chrome和Firefox測試過):

          [ZoneTransfer]
          ZoneId=3
          HostUrl=about:internet
          

          而正常下載的文件的Zone.Identifier則是(這在不同的瀏覽器之間存在區別):

          [ZoneTransfer]
          ZoneId=3
          ReferrerUrl=https://www.sans.org/security-resources/posters/windows-forensic-analysis/170/download
          HostUrl=https://www.sans.org/security-resources/posters/windows-forensic-analysis/170/download
          

          這種方法的局限就在于,它只能在Windows(NTFS)上這樣做。因此這種方式也不是完全可靠的。

          5. Chrome組件

          Chrome會存儲下載文件的來源,如果源是JavaScript Blob或是DataURL,那么這些信息就會作為源顯示。

          當我們打開一個本地的HTML被走私文件:

          當然上面的信息不會告訴我們該被走私的惡意文件是從哪里下載的。

          當我們訪問遠程的走私HTML文件時,我們就可以看到相應的惡意文件下載URL了,但是也僅僅是在走私文件是使用了JS Blob時能看到,在使用DataURL時則不會顯示遠程鏈接:

          這種情況相比前面的幾種方法看起來更加實用。在遠程訪問,并使用了JS Blob的情況下,我們甚至能看到惡意文件的真實來源,如果攻擊者使用了DataURL,我們還可以利用這個信息重新生成文件,這在一些場景下可能也是有用的。


          總結

          總體來說,目前想要完全檢測和防御HTML走私是一件比較困難的事情。當然,上面介紹的方法也只是相對簡單地介紹了HTML走私下的釣魚事情,在現實攻擊場景中,可能會遇到更復雜的情況。


          主站蜘蛛池模板: 91久久精品国产免费一区| 狠狠做深爱婷婷综合一区 | 无码少妇A片一区二区三区| 日本免费电影一区| 无码人妻精品一区二区| 风间由美在线亚洲一区| 东京热人妻无码一区二区av| 男插女高潮一区二区| 一级毛片完整版免费播放一区| 国产精品视频分类一区| 不卡无码人妻一区三区音频| 国产成人精品久久一区二区三区| 精品一区二区三区四区在线| 在线精品一区二区三区| 国产日韩一区二区三免费高清| 好吊妞视频一区二区| 久久久久人妻一区精品性色av| 一区二区不卡久久精品| 风间由美在线亚洲一区| 51视频国产精品一区二区| 久久人妻内射无码一区三区| 亚洲国产专区一区| 视频在线观看一区二区三区| 亲子乱av一区区三区40岁| 亚洲一区二区三区偷拍女厕 | 中文字幕日韩一区二区不卡 | 一区二区三区电影网| 国产成人久久一区二区不卡三区 | 亚洲av鲁丝一区二区三区| 香蕉久久AⅤ一区二区三区| 日韩一区二区三区不卡视频| 在线观看一区二区三区av| 蜜桃视频一区二区三区在线观看 | 亚洲一区二区三区无码影院| 精品日韩一区二区| 毛片无码一区二区三区a片视频| 亚洲av无码片区一区二区三区| 亚欧成人中文字幕一区| 国产内射999视频一区| 中文字幕一区二区三区在线不卡| 冲田杏梨高清无一区二区|