最近看到一個有趣的網頁:lcamtuf.coredump.cx/squirrel/,或者說一張有趣的圖片——因為用網路瀏覽器開啟它看到的是一個網頁,用圖片瀏覽器開啟它看到的又是一張圖片。
在服務端要對同一個請求地址實現不同響應是十分簡單的,比如通過請求頭Accept
來判斷:
Accept=text/html
則返回超文字;Accept=image/*
則返回圖片;- ……
可是lcamtuf.coredump.cx/squirrel/這個網頁(或者說圖片)在脫離服務端的情況下,依然能夠呈現出網頁和圖片兩種檔案內容,這是怎樣實現的呢?
在前端沒有祕密,開啟網路瀏覽器的開發者工具檢視一下網址的響應內容:
響應內容中有熟悉HTML標籤,也有一大堆亂碼。這堆亂碼應該是圖片檔案的字元讀碼,但是為什麼在網頁上看不到呢?
原來HTML中使用了body { visibility: hidden; }
樣式和<!--
註釋標籤(瀏覽器自動補全),亂碼部分就這樣被隱藏了。
HTML內容:
<html><body><style>body { visibility: hidden; } .n { visibility: visible; position: absolute; padding: 0 1ex 0 1ex; margin: 0; top: 0; left: 0; } h1 { margin-top: 0.4ex; margin-bottom: 0.8ex; }</style><div class=n><h1><i>Hello, squirrel fans!</i></h1>This is an embedded landing page for an image. You can link to this URL and get the HTML document you are viewing right now (soon to include essential squirrel facts); or embed the exact same URL as an image on your own squirrel-themed page:<p><xmp><a href="http://lcamtuf.coredump.cx/squirrel/">Click here!</a></xmp><xmp><img src="http://lcamtuf.coredump.cx/squirrel/"></xmp><p>No server-side hacks involved - the magic happens in your browser. Let's try embedding the current page as an image right now (INCEPTION!):<p><img src="#" style="border: 1px solid crimson"><p>Pretty radical, eh? Send money to: lcamtuf@coredump.cx<!--
複製程式碼
然而在檢視圖片的時候,也沒看到HTML的內容,又是為什麼呢?
JPEG相關
首先可以確定這張圖片是JPEG格式,因為內容開頭有明顯的JFIF
標記(JFIF,是JPEG最常見的檔案儲存格式,也是標準的JPEG檔案轉換格式)。
JPEG格式定義了一系列標記碼,都是以0xFF開頭,常見的有:
0xFFD8
,SOI(Start Of Image),圖片開始標記;0xFFD9
,EOI(End Of Image),圖片結束標記;0xFFDA
,SOS(Start Of Scan),掃描開始標記;0xFFDB
,DQT(Define Quantization Table),定義量化表;0xFFC4
,DHT(Define Huffman Table),定義哈夫曼表;0xFFEn
,APPn(Application Specific),應用程式資訊;0xFFFE
,COM(Comment),註釋;- ……
圖片的註釋內容不會展示,很顯然我們可以把HTML隱藏在圖片註釋中。 用十六進位制讀取這張圖片,可以看到:
這張圖片中使用了註釋標記0xFFFE
,標記後面跟著0x0372
。0x0372
轉成十進位制是882
,而HTML內容的長度剛好是880個位元組(0x0372
本身佔2個位元組),所以可以知道,HTML內容就是寫在這張圖片註釋中。
程式碼實現
下面用PHP程式碼簡單實現一個將HTML內容嵌入JPEG中的函式:
<?php
function embedHtmlInJpeg($jpeg_file, $html_str, $html_file)
{
$length = strlen($html_str) + 2;
if ($length > 256 * 256 - 1) {
return false;
}
$content = '';
$reader = fopen($jpeg_file, 'rb');
$writer = fopen($html_file, 'wb');
$content = fread($reader, 2); // read 0xFFD8
fwrite($writer, $content); // write 0xFFD8
$header = 'FFFE' . sprintf('%04X', $length);
$header = pack('H*', $header);
$content = $header . $html_str;
fwrite($writer, $content); // write 0xFFFE
while (!feof($reader)) {
$content = fread($reader, 8192);
fwrite($writer, $content); // write else
}
fclose($reader);
fclose($writer);
return true;
}
// call it
embedHtmlInJpeg('lena.jpg',
'<html><body><style>body { visibility: hidden; } .n { visibility: visible; position: absolute; padding: 0 1ex 0 1ex; margin: 0; top: 0; left: 0; } h1 { margin-top: 0.4ex; margin-bottom: 0.8ex; }</style><div class=n><h1><i>This image is a page.</i></h1>Just open it in new tab.<p><img src="#" style="border: 1px solid crimson"><!--',
'lena.html');
複製程式碼
這張圖片是一個網頁,不信你就在新標籤頁中開啟它
點選這裡體驗。
實際應用
將一段HTML文字嵌入到一張圖片中,實際上,還沒什麼應用,哈哈哈?。
如果能將JS
或Shell
指令碼藏在圖片中,並能後期執行,那就有意思了;而且本身是一個圖片檔案,可以避過一些安全軟體的檢查。