1. 程式人生 > >如何突破汽車之家的"CSS ::before 偽元素混淆"反採集策略

如何突破汽車之家的"CSS ::before 偽元素混淆"反採集策略

如何突破汽車之家的"CSS ::before 偽元素混淆"反採集策略

釋出時間:2018-08-28 來源:未知 瀏覽:

http://www.site-digger.com/html/articles/20180828/663.html


分析汽車之家車型引數列表,發現頁面上有部分字元在HTML原始碼(執行時)中卻找不到,很奇怪。仔細分析,原來是通過CSS :before 偽元素加了混淆,如下圖示例:

核心程式碼如下所示:

view plaincopy to clipboardprint?

  1. HTML程式碼:  
  2.    
  3.   
  4. CSS程式碼:   
  5. .hs_kw5_configpl::before {  
  6.     content: "上市";  
  7. }  

熟悉DOM操作的同學知道,可以通過元素的getPropertyValue('content')方法來獲取CSS偽元素的content屬性,如下是示例程式碼:

view plaincopy to clipboardprint?

  1. window.getComputedStyle(  
  2. document.querySelector('.hs_kw5_configpl'), ':before'  
  3. ).getPropertyValue('content');  

在Console裡測試一下,你會發現很奇怪,竟然獲取不到有效值(如下圖所示),本應該返回"上市",卻返回了"content"。

看來哪裡又被動了手腳,TA是怎麼做到的?

仔細分析發現一個路徑包含GetModelConfig1.ashx的Aajx應答資料裡含有JS程式碼,分析程式碼發現該上述SPAN元素是JS動態建立的,並發現相關可疑程式碼,如下所示(格式化後):

view plaincopy to clipboardprint?

  1. function(element, pseudoElt) {  
  2.     if (pseudoElt != undefined && typeof(pseudoElt) == 'string' && pseudoElt.toLowerCase().indexOf(':before') > -1) {  
  3.         var obj = {};  
  4.         // 重點是下面這句  
  5.         obj.getPropertyValue = function(x) {  
  6.             return x;  
  7.         };  
  8.         return obj;  
  9.     } else {  
  10.         return window.hs_fuckyou(element, pseudoElt);  
  11.     }  
  12. };  

原來是getPropertyValue()方法被偷樑換柱(改寫)了!難怪呼叫無法獲取預期的值。

 

繼續分析JS程式碼,重點看SPAN元素的動態建立的過程,其中有如下程式碼:

view plaincopy to clipboardprint?

  1. function poL_(ctw_, RXe_) {  
  2.     tPn_[\u0027\u0027 + ILC_ + iSW_ + uIo_ + pEA_ + GEv_ + Ewc_ + EPk_ + Zfo_ + sfd_ + UkX_](XZS_(ctw_) + URD_() + \u0027\"\u0027 + RXe_ + \u0027\" }\u0027, 0);  
  3. // 可以將\u0027替換成'  

通過多次攔截應答修改資料(例如,修改為如下程式碼)測試發現,該函式的第二個引數Rxe_即為::before偽元素的content屬性值,第一個引數是一個索引。

view plaincopy to clipboardprint?

  1. function poL_(ctw_, RXe_) {  
  2.     // 看看ctw_和Rxe_引數具體是什麼  
  3.     console.log(ctw_ + '->' + RXe_));  
  4.     tPn_[\u0027\u0027 + ILC_ + iSW_ + uIo_ + pEA_ + GEv_ + Ewc_ + EPk_ + Zfo_ + sfd_ + UkX_](XZS_(ctw_) + URD_() + \u0027\"\u0027 + RXe_ + \u0027\" }\u0027, 0);  

PS:攔截修改HTTP應答資料可以用Fiddler實現,也可以用mitmproxy,這裡我們選擇後者,因為它支援外掛Python指令碼來實現資料修改功能。

如下圖所示為控制檯打印出的"索引->字元"對映表:

現在離成功很近了。這個索引數字具體有什麼意義呢?

例如,“上市”的索引為5,上面我們看到“上市”對應SPAN元素是 ,大膽猜測,對應的就是class中的數字5。經多次驗證,無誤。

 

現在思路有了:

(1)通過Selenium載入頁面(以自動完成資料動態載入,自動執行JS程式碼),並配置所有流量走mitmproxy;

(2)使用mitmproxy捕獲GetModelConfig1.ashx的應答資料,找到poL_(ctw_, RXe_)函式,注入我們的JS程式碼,以曝出對應的"索引和混淆字元"對映表;

(3)利用該表即可還原混淆為明文;

 

需要注意的是,上述程式碼中的函式名(poL_)以及引數名(ctw_, RXe_)是動態變化的,但是經過多次觀察發現是有規律的,通過如下方法可以定位該函式:

view plaincopy to clipboardprint?

  1. 查詢如下字串:  
  2. '+ \u0027\" }\u0027'  
  3. 定位前面出現的第一個function,即要注入的目標  
  4. 例如 function poL_(ctw_, RXe_)  
  5.   
  6. 用正則表示式可以表述為:  
  7. r'''''\s+(function\s+[^\{\}\(\)]+\(([^\{\}\(\)]+),([^\{\}\(\)]+)\)\{)[a-z\d\-\_]+\_\[.+?\+\s*\\u0027\\"\s*\}\\u0027'''  

程式碼自動注入這裡我們採用mitmproxy來實現,注入指令碼modify_response.py程式碼如下所示:

view plaincopy to clipboardprint?

  1. # coding: utf-8  
  2. # modify_response.py  
  3.   
  4. import re  
  5. from mitmproxy import ctx  
  6.       
  7. def response(flow):  
  8.     """修改應答資料 
  9.     """  
  10.     if 'GetModelConfig' in flow.request.url:  
  11.         # 汽車之家字元混淆(CSS :before 偽元素)破解  
  12.         ctx.log.info('*' * 120 + '\n Found {}.'.format(flow.request.url))  
  13.         m = re.compile(r'''''\s+(function\s+[^\{\}\(\)]+\(([^\{\}\(\)]+),([^\{\}\(\)]+)\)\{)[a-z\d\-\_]+\_\[.+?\+\s*\\u0027\\"\s*\}\\u0027''', re.IGNORECASE).search(flow.response.text)  
  14.         if m:  
  15.             # 提取函式名和引數  
  16.             function_name = m.groups()[0]  
  17.             param1 = m.groups()[1]  
  18.             param2 = m.groups()[2]  
  19.             ctx.log.info('Crack "CSS :before" in {}: "{}"'.format(function_name, flow.request.url))  
  20.             # 替換後的內容  
  21.             replacement = function_name + "document.body.appendChild(document.createTextNode('[' + {} + ']->{{' + {} + '}};'));".format(param1, param2)  
  22.             #replacement = function_name + "console.log({} + '->' + {});".format(param1, param2)  
  23.             flow.response.text = flow.response.text.replace(function_name, replacement)  

注入成功,頁面載入完畢後,在頁面底部會打印出我們想要的對映表:

有了這個對映表,就可以還原出明文了,程式碼如下所示:

view plaincopy to clipboardprint?

  1. def restore_css_confusion(html):  
  2.     """還原混淆字元 
  3.     html - 要處理的HTML文件原始碼; 
  4.     """  
  5.     confusions = {}  
  6.     for index, string in re.compile(r'\[(\d+)\]->\{([^<>\;]+)\};').findall(html):  
  7.         confusions[index] = common.normalize(string)  
  8.     # 逐一替換  
  9.     for span, index in re.compile(r'()').findall(html):  
  10.         original_text = confusions[index]  
  11.         print 'Restore "{}" into "{}"'.format(span, original_text)  
  12.         html = html.replace(span, original_text)  
  13.     return html  

還原之後就可以提取到正確的引數資訊了,如下圖所示:

最後,這裡有個演示視訊,可以看到實際的執行過程:

https://v.qq.com/x/page/e07680qdz2r.html

特別說明:該文章為鯤鵬資料原創文章 ,您除了可以發表評論外,還可以轉載到別的網站,但是請保留源地址,謝謝!!(尊重他人勞動,我們共同努力)

http://www.site-digger.com/html/articles/20180828/663.html