從黑客角度深入剖析跨站請(qǐng)求偽造
在對(duì)Web應(yīng)用程序進(jìn)行測(cè)試的過程中,我們使用的自動(dòng)化工具有時(shí)候會(huì)漏掉一些非常嚴(yán)重的安全漏洞??缯菊?qǐng)求偽造便是其中之一。因此,對(duì)于Web應(yīng)用測(cè)試來說,通過人工方式分析代碼是非常重要的一種測(cè)試手段。
在近期的Web應(yīng)用測(cè)試過程中,我利用手工方式發(fā)現(xiàn)了若干個(gè)CSRF漏洞,如果將其串聯(lián)使用,就能演變成更加嚴(yán)重的安全漏洞。下面,本文將詳細(xì)介紹這些漏洞的發(fā)現(xiàn)過程,并演示如何利用它們來控制受害者的賬戶。需要注意的是,為了保護(hù)客戶信息,部分內(nèi)容已經(jīng)做了模糊處理。
0×01 艱難的發(fā)現(xiàn)之旅
為了展開跨站請(qǐng)求偽造攻擊,首先要進(jìn)行一些偵查工作。為此,我根據(jù)客戶提供的低權(quán)限賬戶登錄了需要測(cè)試的Web應(yīng)用。在這個(gè)網(wǎng)站上,大部分頁面都要求預(yù)先知道帳戶名、產(chǎn)品序列號(hào)等信息。由于我對(duì)這些一無所知,所以只好轉(zhuǎn)而考察賬戶簡(jiǎn)介頁面。這個(gè)頁面允許用戶更新他們的帳號(hào)信息,例如姓名、電子郵件地址、城市、省份,等等。
對(duì)于瀏覽器,我通常選擇Firefox,這是因?yàn)榭梢允褂肨amperData插件來方便地操作表單的字段,來觀察是否發(fā)生有趣的事情。比如,我經(jīng)常在引號(hào)或星號(hào)之間插入一些奇怪的數(shù)據(jù),來探索各種注入攻擊。就本例而言,我發(fā)現(xiàn)了一個(gè)會(huì)隨其他賬戶信息一同傳輸?shù)碾[藏表單字段,這個(gè)字段的名稱為_RequestVerificationToken。我不斷擺弄這個(gè)字段,但是就我觀察而言,并沒有找到注入漏洞。但是,我卻注意到了一個(gè)重要的事情,那就是這個(gè)站點(diǎn)好像并不在意我對(duì)這個(gè)字段所做的各種修改。
這對(duì)我來說是非常奇怪的。根據(jù)這個(gè)字段的名稱來看,它好像是用來驗(yàn)證每個(gè)提交的請(qǐng)求是否來自該網(wǎng)站實(shí)際用戶的一項(xiàng)安全措施。這通常是用來防止跨站請(qǐng)求偽造(CSRF)攻擊的,但是,這個(gè)頁面看上去并沒有驗(yàn)證這個(gè)令牌,這就意味著這個(gè)信息更新表單容易受到CSRF的影響。
在默默記住這一點(diǎn)之后,我開始繼續(xù)測(cè)試該網(wǎng)站的其他部分。我想,如果一個(gè)頁面有這種漏洞,那么其他頁面也很可能有這種漏洞。不幸的是,事實(shí)并非如此。對(duì)于所有其他頁面,只要修改這個(gè)令牌就會(huì)導(dǎo)致服務(wù)器錯(cuò)誤,并且表單也無法進(jìn)行處理。也就是說,除了個(gè)人信息頁面之外,其他地方的大門都被堵上了。接下來,我們就要想辦法利用它來進(jìn)行更加邪惡的事情。
0×02 忘記密碼
就像某些網(wǎng)站一樣,這個(gè)站點(diǎn)也提供了一個(gè)“忘記密碼”功能。這個(gè)功能的初衷,當(dāng)然是為哪些忘掉密碼的用戶提供幫助的。這個(gè)自動(dòng)化系統(tǒng)非常簡(jiǎn)單,用戶將其用戶名輸入到相應(yīng)的表單字段,然后點(diǎn)擊提交按鈕即可。如果用戶名是有效的,系統(tǒng)就會(huì)給該用戶的賬戶生成一個(gè)新的隨機(jī)密碼,并將其發(fā)送至該用戶賬戶綁定的電子郵箱中。這個(gè)功能不僅對(duì)于用戶來說非常簡(jiǎn)單,同時(shí),也為黑客提供了極大的方便。
我意識(shí)到,如果我可以利用CSRF漏洞把用戶的電子郵件地址更新為自己的郵箱地址,那么,我就能夠利用忘記密碼功能令系統(tǒng)把用戶的密碼發(fā)給我,而不是用戶。不幸的是,我很快就遇到了一個(gè)難題。
客戶(這里指example.com)提供給我的測(cè)試帳戶所綁定的電子郵件地址中含有客戶的域名,如bob@example.com。雖然系統(tǒng)允許將這個(gè)電子郵件地址改為我想要的地址,但是這里有一個(gè)問題,當(dāng)讓系統(tǒng)向我的電子郵箱發(fā)送新密碼時(shí),實(shí)際收到的卻是一個(gè)服務(wù)器錯(cuò)誤。不知道什么原因,它似乎只喜歡類似bob@example.com這樣的郵箱。
經(jīng)過進(jìn)一步的手工測(cè)試后,我發(fā)現(xiàn)它可以接受任何用戶名,只要是以example.com結(jié)尾即可。我不知道到底為何會(huì)發(fā)生這種情況,因?yàn)樵谙到y(tǒng)中的其他帳戶包含了來自不同域的電子郵件地址。我覺得,這背后肯定還進(jìn)行了某些額外的安全驗(yàn)證,比如根據(jù)白名單對(duì)比電子郵件地址的域名等。但是,我的個(gè)人電子郵件地址或域名肯定是不在這個(gè)表中的。因此,我們需要借助于下面介紹的技巧。
0×03 Gmail來解圍
我有一種預(yù)感,即電子郵件地址的白名單匹配過程是可以破解的,后來事實(shí)證明,我的感覺還是很準(zhǔn)的:我發(fā)現(xiàn),任何電子郵件地址都是可以繞過白名單的,只要該地址的郵箱名稱部分含有“example.com”字樣即可。舉例來說,example.com@otherdomain.com就完全可以達(dá)到我們的目的。因此,我們只需要建立一個(gè)郵箱名部分含有example.com的電子郵件賬戶就行了。
現(xiàn)在,該Gmail上場(chǎng)了。之所以選擇Gmail,是因?yàn)樗试SGmail用戶名之后、@符號(hào)之前使用加號(hào),這個(gè)特性對(duì)我們來說非常有用。舉例來說,如果你的電子郵件地址是rick@gmail.com,那么你也可以使用rick+test@gmail.com來接收郵件,實(shí)際上郵件還是會(huì)發(fā)送到rick@gmail.com郵箱中,因?yàn)镚mail會(huì)忽略加號(hào)和@符號(hào)之間的所有內(nèi)容。
考慮到這一點(diǎn),我又登錄到Web應(yīng)用程序進(jìn)行測(cè)試,并把帳戶的電子郵件地址更新為“rick+example.com@gmail.com”。果然,這種方法確實(shí)是可行的,密碼重置功能給我發(fā)來了一個(gè)新的密碼。既然如此,下面就該從poc驗(yàn)證這個(gè)CSRF漏洞的可利用性了。
0×04 一個(gè)簡(jiǎn)單的Web表單
由于這個(gè)有漏洞的應(yīng)用使用POST方法來提交數(shù)據(jù),所以我必須構(gòu)建自己的表單來達(dá)成目標(biāo)。由于這里只是一個(gè)poc,所以不要求太過精致。我建立的這個(gè)表單含有一些隱藏的文本字段,當(dāng)個(gè)人簡(jiǎn)介更新時(shí),所有的表單字段都會(huì)被傳送給這個(gè)Web應(yīng)用。這其中包括一個(gè)CSRF令牌,不過由于這個(gè)應(yīng)用沒能有效對(duì)其進(jìn)行驗(yàn)證,所以該令牌的值實(shí)際上是無關(guān)緊要的。這里最為重要的部分是EmailAddress字段,雖然這個(gè)字段是隱藏的,但是卻包含一個(gè)重要的缺省值,即rick+example.com@gmail.com。
- <form action="https://www.example.com/account/EditProfile/" id="EditProfile" method="post" name="EditProfile"> <input name="__RequestVerificationToken" type="hidden" value="ANYTHING CAN GO HERE" />
- <input id="PasswordChgRequired" name="PasswordChgRequired" type="hidden" value="No" />
- <input id="UsersName" name="UsersName" type="hidden" value="ANITIAN" />
- <input id="returnURL" type="hidden" />
- <input class="userName text-box" id="FirstName" maxlength="25" name="FirstName" style="width:160px" type="hidden" value="Anitian" />
- <input class="userName text-box" id="LastName" maxlength="25" name="LastName" style="width:160px" type="hidden" value="Anitian" />
- <input id="Phone" name="Phone" style="width:160px" type="hidden" value="" />
- <input id="Phone" name="Phone" type="hidden" value="" />
- <input id="PhoneExt" maxlength="5" name="PhoneExt" style="width:160px" title="1 to 5 numbers. " type="hidden" value=" " />
- <input id="PhoneExt" name="PhoneExt" type="hidden" value=" " />
- <input class="text-box" id="City" maxlength="20" name="City" placeholder="Office Location" style="width:160px" title="Enter the city of your office location." type="hidden" value="Winterville" />
- <input id="EmailAddress" maxlength="50" name="EmailAddress" style="width:394px" title="Email is limited to 50 characters." type="hidden" value="rick+test.com@gmail.com" />
- <input class="jobFunc" id="JobFunction2" name="JobFunction2" type="hidden" value="Vice President" />
- <input class="jobFunc" id="JubFunction" name="JobFunction" type="hidden" value="ACCOUNT HACKED">
- Click this awesome button!
- <input id="submit" type="submit" value="Save" />
- </form>
在這個(gè)表單中,唯一可見的部分就是一個(gè)提交按鈕。為了進(jìn)行poc,我將這個(gè)新的網(wǎng)頁加載到了自己本地的計(jì)算機(jī)上。對(duì)于實(shí)際的攻擊者來說,他們可以將其托管到互聯(lián)網(wǎng)上的某個(gè)Web服務(wù)器上面,并且很可能是他們已經(jīng)成功入侵的系統(tǒng)上所搭建的服務(wù)器。一旦加載之后,受害者將會(huì)看到如下所示的頁面:
當(dāng)然,這個(gè)頁面看起來有些簡(jiǎn)陋,但是對(duì)于poc來說已經(jīng)足夠了。接下來,我們必須自己來扮演受害者了。為此,我首先打開了瀏覽器,并使用正常賬號(hào)登錄上這個(gè)Web應(yīng)用,這一切跟正常用戶沒有什么區(qū)別。然后,我將前面的惡意表單頁面上載到了另一個(gè)標(biāo)簽中。攻擊者很可能會(huì)利用電子郵件將這個(gè)鏈接發(fā)給我,然后,我單擊了這個(gè)按鈕,象期望的那樣,攻擊成功了。
這個(gè)惡意網(wǎng)頁會(huì)把所有信息遞交給了目標(biāo)站點(diǎn),并按照要求更新了電子郵件地址字段。需要引起注意的是,即使目標(biāo)頁面已經(jīng)在受害者瀏覽器中被關(guān)閉的情況下,這次攻擊依然能夠得手,因?yàn)橹灰脩舻臅?huì)話依然是打開的,那就足夠了。許多時(shí)候,一個(gè)會(huì)話可以在很長(zhǎng)一段時(shí)間保持打開狀態(tài),即使網(wǎng)頁關(guān)閉之后亦是如此。現(xiàn)在,攻擊者唯一需要去做的就是,利用密碼重置功能通過電子郵件獲得受害者(這里就是我們自己)的新密碼了。
這樣一來,受害者的賬號(hào)就完全處于我們的控制之下了。#p#
0×05 進(jìn)一步改進(jìn)攻擊手法
需要注意的是,該攻擊手法具有兩個(gè)缺點(diǎn),一是要求受害者參與其中, 因?yàn)槭芎φ咧辽傩枰L問一個(gè)惡意頁面,這樣該手法才能成功。就本例來說,受害者還必須按下一個(gè)按鈕,不過,利用Javascript可以實(shí)現(xiàn)在頁面加載時(shí)自動(dòng)完成表單的提交動(dòng)作。
另一個(gè)問題是,你必須知道受害者的用戶名。為了利用忘記密碼功能,我們必須知道受害者的用戶名才行。幸運(yùn)的是,我發(fā)現(xiàn)了另外一個(gè)辦法,可以巧妙地繞開這個(gè)問題。
0×06 隱秘的表單字段
我注意到,個(gè)人簡(jiǎn)介的更新頁面還會(huì)提交一個(gè)用于UsersName的字段。這引起了我的興趣,因?yàn)轫撁姹旧硎菬o法修改這個(gè)用戶名字段的。這是一個(gè)靜態(tài)的條目,即總是保持不變。出于好奇,我決定再次嘗試通過Tamper Data來對(duì)這個(gè)變量進(jìn)行修改。讓我驚訝的是,它居然奏效了。實(shí)際上,該網(wǎng)站不僅接受了我改過的用戶名,而且連該帳戶的用戶名也改過來了。當(dāng)然,我們無法將用戶名更新為業(yè)已存在的那些用戶名,但是我們可以更新為隨機(jī)選擇的、不常用的用戶名,這是很容易成功的。
為了利用這一點(diǎn),我們只需更新自己的惡意頁面中的表單,讓其username字段包含一個(gè)新的、當(dāng)前未曾使用的值即可?,F(xiàn)在,當(dāng)受害者點(diǎn)擊了該提交按鈕之后,不僅他們的電子郵件地址會(huì)被更新,而且就連他們的用戶名也會(huì)變成我們之前設(shè)定的值。這就意味著,這個(gè)鏈接可以發(fā)送給任何已經(jīng)登錄該網(wǎng)站的人,他們的賬戶都可能會(huì)失竊。再進(jìn)一步,我們甚至可以將這個(gè)過程腳本化,這樣就可以成批地把不同的表單發(fā)送給不同的人員。只要他們輕輕點(diǎn)擊一下,就會(huì)全部中招。
0×07 如何保護(hù)自己
那么,我們?nèi)绾伪Wo(hù)自己免受此類攻擊的威脅呢? 實(shí)際上,這種攻擊之所以會(huì)發(fā)生,主要是由于兩個(gè)核心漏洞導(dǎo)致的,下面我們就介紹如何處理這個(gè)問題。
在這里,最嚴(yán)重的問題就是CSRF攻擊,如果沒有它,就不會(huì)發(fā)生后面的事情了。為了防御CSRF攻擊,OWASP組織已經(jīng)提供了多種不同的方法,不過最為安全的方法就是在隱藏字段中使用一個(gè)唯一的令牌。這也是我們這個(gè)示例網(wǎng)站所試圖去做的。如果能夠徹底實(shí)施的話,CSRF攻擊的得手機(jī)會(huì)就會(huì)大大降低。但是,只要有一個(gè)頁面沒有徹查該令牌的話,攻擊者仍然有機(jī)可乘。這是因?yàn)閷?duì)于他們來說,只要有一個(gè)漏洞就足夠了,并且不要忘了,那些心懷不軌的家伙們可有的是時(shí)間。
另一個(gè)問題與用戶輸入驗(yàn)證有關(guān)。盡管這個(gè)系統(tǒng)看上去會(huì)利用某種白名單來檢查電子郵件地址,但是這種方法是無效的。它只是檢查電子郵件地址中是否存在白名單中的域名而已,只要有就行,不管出現(xiàn)在什么地方。實(shí)際上,它應(yīng)該重點(diǎn)檢查電子郵件地址的最后部分,或者對(duì)整個(gè)電子郵件地址進(jìn)行更加嚴(yán)格的檢查。此外,更為安全的做法是,當(dāng)用戶更新個(gè)人簡(jiǎn)介信息時(shí),應(yīng)該要求他們重新輸入自己當(dāng)前的密碼。當(dāng)然,在重新輸入密碼的時(shí)候,還可以加入CAPTCHA驗(yàn)證,因?yàn)閻阂釽eb表單是無法自動(dòng)識(shí)別CAPTCHA的。
最終的漏洞還涉及到用戶輸入內(nèi)容的驗(yàn)證。根據(jù)個(gè)人信息更新頁面的設(shè)計(jì)來看,這個(gè)頁面好像并沒有打算讓用戶更新他們的用戶名。在原始的表單中,用戶名字段是不允許編輯的。如果果真如此的話,那么這個(gè)表單根本就不應(yīng)該將用戶名作為一個(gè)字段進(jìn)行提交。即使由于某種原因,必須提交這個(gè)字段的話,那么該頁面也應(yīng)該進(jìn)行重寫,以禁止編輯用戶名。
0×08 小結(jié)
CSRF攻擊是攻擊者用來訪問你的數(shù)據(jù)的方法之一。不過,只掃描站點(diǎn)漏洞是遠(yuǎn)遠(yuǎn)不夠的。這需要仔細(xì)審查該網(wǎng)站運(yùn)作方式,以檢測(cè)更細(xì)微的瑕疵。本文通過實(shí)例說明,我們不僅能夠找出這種缺陷的,并且還能幫助客戶防御此類攻擊,從而避免出現(xiàn)災(zāi)難性的數(shù)據(jù)破壞。