自動化尋找 AngularJS CSP Bypass 中 prototype.js 的替代品
2022-9-1 19:31:10 Author: blog.huli.tw(查看原文) 阅读量:32 收藏

因為 CSP 中有 cdnjs,所以我們可以引入其他的 library,這邊我們挑的是 AngularJS,引入了以後我們就可以用 CSTI 的方式注入底下這一段:

這邊為什麼是 $on.curry.call() 呢?你可以把它換成 window 看看,會發現沒有反應,這是因為 AngularJS 的 expression 是放在一個 scope object 裡面,你沒辦法直接存取到 window 或是 window 上的屬性。

而這邊有另一個重點是 CSP 沒有開 unsafe-eval,所以你也不能直接 constructor.constructor('alert(1)')() 之類的。

這個 function 會加在 Function.prototype 上面,而重點其實只有第一行:if (!arguments.length) return this;,如果沒有帶參數的話,會直接回傳 this。在 JavaScript 裡面,如果你用 call 或是 apply 來呼叫函式的話,第一個參數可以指定 this 的值,如果沒有傳的話就會是預設值,在嚴格模式底下是 undefined,非嚴格模式底下是 window

這也是為什麼 $on.curry.call() 會是 window,因為 $on 是個 function,所以呼叫 $on.curry.call() 的時候,由於 this 沒帶所以預設是 window,參數也沒帶,因此 curry 這個函式就會根據第一行的條件句,把 this 也就是 window 回傳回來。

第一點很重要,因為前面有提過在 expression 裡面沒辦法存取到 window,所以一般的 library 加的東西其實也是拿不到的,但 prototype.js 是把東西放在 prototype 上面,所以可以透過 prototype 來存取到新增的 method。

知道了原理之後,就知道該怎麼找替代品了,只要找到有相同功能的就好了。而此時我突然想到以前寫過的一篇文章:Don’t break the Web:以 SmooshGate 以及 keygen 為例,在裡面我有提到因為 MooTools 習慣在 prototype 上面新增東西,導致原本要叫做 flatten 的 method 只好改名叫 flat(後來看 maple 的 writeup 才知道原來 Array.prototype.includes 不叫 Array.prototype.contains 也是因為 MooTools)

Array.prototype.eraseArray.prototype.empty 兩個函式都會回傳 this,所以底下兩個方法都可以拿到 window:

其中有一些細節的部分端看個人想要怎麼處理,例如說更精緻一點的話可以針對套件的所有版本都做測試,但是那樣做的話測試量可能會變五到十倍,由於我只是想做個初步的研究,所以不考慮套件版本,一律使用最新版的。

去 cdnjs 的網站上面觀察一下,可以發現背後是去呼叫放在 algolia 的 API,algolia 其實有提供把所有資料拉回來的方法,但官網的 api key 不支援,然後分頁的話又會受到限制,只能拿到前 1000 筆結果。

於是,我找到了 search 的 API,先假設每個字母開頭的套件不會超過 1000 個,就可以從 a-zA-Z0-9 去尋找以每個字母開頭的套件,藉此繞過 1000 筆的限制,讀到所有套件的資料。

而這個 API 的規則也很簡單,網址就是:https://api.cdnjs.com/libraries/${套件名稱}/${版本},所以只要把上一步的列表整理一下拿去打 API,就可以拿到每一個套件有哪些檔案:

在 cdnjs 上的套件有 4000 多個,如果一個一個跑的話,那就必須跑 4000 多遍,但其實符合我們條件的應該是少數,所以我選擇 10 個一組去跑,原因是 10 個套件的檔案應該不至於到真的太多,不用怕載入時間很長。如果這 10 個套件都沒有更動 prototype,那就下一組,如果有的話,就用類似二分搜的方式去找出哪些套件有改動到。

我們在套件還沒載入時,先記錄起每個 prototype 上面的屬性,載入套件以後再記錄一次然後跟之前做比對,就可以找出哪些是套件引入後才新增的屬性。然後我們也可以把結果分成兩種,一種是只要有改動到 prototype 就記下來,另外一種則是呼叫以後會回傳 window 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
[
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/asciidoctor.js/1.5.9/asciidoctor.min.js",
"functions": [
"Array.prototype.$concat",
"Array.prototype.$push",
"Array.prototype.$append",
"Array.prototype.$rotate!",
"Array.prototype.$shuffle!",
"Array.prototype.$sort",
"Array.prototype.$to_a",
"Array.prototype.$to_ary",
"Array.prototype.$unshift",
"Array.prototype.$prepend",
"String.prototype.$initialize",
"String.prototype.$chomp",
"String.prototype.$force_encoding",
"Function.prototype.$to_proc"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/jquery-ui-bootstrap/0.5pre/third-party/jQuery-UI-Date-Range-Picker/js/date.js",
"functions": [
"Number.prototype.milliseconds",
"Number.prototype.millisecond",
"Number.prototype.seconds",
"Number.prototype.second",
"Number.prototype.minutes",
"Number.prototype.minute",
"Number.prototype.hours",
"Number.prototype.hour",
"Number.prototype.days",
"Number.prototype.day",
"Number.prototype.weeks",
"Number.prototype.week",
"Number.prototype.months",
"Number.prototype.month",
"Number.prototype.years",
"Number.prototype.year"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/ext-core/3.1.0/ext-core.min.js",
"functions": [
"Function.prototype.createInterceptor"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/datejs/1.0/date.min.js",
"functions": [
"Number.prototype.milliseconds",
"Number.prototype.millisecond",
"Number.prototype.seconds",
"Number.prototype.second",
"Number.prototype.minutes",
"Number.prototype.minute",
"Number.prototype.hours",
"Number.prototype.hour",
"Number.prototype.days",
"Number.prototype.day",
"Number.prototype.weeks",
"Number.prototype.week",
"Number.prototype.months",
"Number.prototype.month",
"Number.prototype.years",
"Number.prototype.year"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/json-forms/1.6.3/js/brutusin-json-forms.min.js",
"functions": [
"String.prototype.format"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/inheritance-js/0.4.12/inheritance.min.js",
"functions": [
"Object.prototype.mix",
"Object.prototype.mixDeep"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/melonjs/1.0.1/melonjs.min.js",
"functions": [
"Array.prototype.remove"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/mootools/1.6.0/mootools-core-compat.min.js",
"functions": [
"Array.prototype.erase",
"Array.prototype.empty",
"Function.prototype.extend",
"Function.prototype.implement",
"Function.prototype.hide",
"Function.prototype.protect"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/mootools/1.6.0/mootools-core.min.js",
"functions": [
"Array.prototype.erase",
"Array.prototype.empty",
"Function.prototype.extend",
"Function.prototype.implement",
"Function.prototype.hide",
"Function.prototype.protect"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/opal/0.3.43/opal.min.js",
"functions": [
"Array.prototype.$extend",
"Array.prototype.$to_proc",
"Array.prototype.$to_a",
"Array.prototype.$collect!",
"Array.prototype.$delete_if",
"Array.prototype.$each_index",
"Array.prototype.$fill",
"Array.prototype.$insert",
"Array.prototype.$keep_if",
"Array.prototype.$map!",
"Array.prototype.$push",
"Array.prototype.$shuffle",
"Array.prototype.$to_ary",
"Array.prototype.$unshift",
"String.prototype.$as_json",
"String.prototype.$extend",
"String.prototype.$intern",
"String.prototype.$to_sym",
"Number.prototype.$as_json",
"Number.prototype.$extend",
"Number.prototype.$to_proc",
"Number.prototype.$downto",
"Number.prototype.$nonzero?",
"Number.prototype.$ord",
"Number.prototype.$times",
"Function.prototype.$include",
"Function.prototype.$module_function",
"Function.prototype.$extend",
"Function.prototype.$to_proc"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.3/prototype.min.js",
"functions": [
"Array.prototype.clear",
"Number.prototype.times",
"Function.prototype.curry"
]
},
{
"url": "https://cdnjs.cloudflare.com/ajax/libs/tmlib.js/0.5.2/tmlib.min.js",
"functions": [
"Array.prototype.swap",
"Array.prototype.eraseAll",
"Array.prototype.eraseIf",
"Array.prototype.eraseIfAll",
"Array.prototype.clear",
"Array.prototype.shuffle",
"Number.prototype.times",
"Number.prototype.upto",
"Number.prototype.downto",
"Number.prototype.step",
"Object.prototype.$extend",
"Object.prototype.$safe",
"Object.prototype.$strict"
]
}
]

透過把 cdnjs 上的套件資料都抓下來,以及使用 headless browser 幫忙驗證,我們成功找到了 11 個 prototype.js 的替代品,這些套件都會在 prototype 上面新增方法,而且呼叫這些方法以後都會回傳 this,可以藉由呼叫它來取得 window

另外,找出替代品其實也沒什麼太大的意義,只是好奇而已,因為通常也不會有網頁特別去擋 prototype.js,所以其實只要找到一個可以拿到 window 的套件就足夠了。


文章来源: https://blog.huli.tw/2022/09/01/angularjs-csp-bypass-cdnjs/
如有侵权请联系:admin#unsafe.sh