Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
譯:h4d35
預(yù)估稿費:120RMB
投稿方式:發(fā)送郵件至linwei#360.cn,或登陸網(wǎng)頁版在線投稿
前言
本篇文章主要介紹了在一次漏洞懸賞項目中如何利用配置錯誤挖到一個認(rèn)證繞過漏洞。
從JS文件中發(fā)現(xiàn)認(rèn)證繞過漏洞
本文內(nèi)容源自一個私有漏洞賞金計劃。在這個漏洞計劃中,接受的漏洞范圍限于目標(biāo)網(wǎng)站少數(shù)幾個公開的功能。基于前期發(fā)現(xiàn)的問題(當(dāng)我被邀請進這個計劃時,其他人一共提交了5個漏洞),似乎很難再挖到新的漏洞。同時,在賞金詳情中提到了這樣一句話:
如果你成功進入管理頁面,請立即報告,請勿在/admin中進行進一步的測試。
然而,目標(biāo)網(wǎng)站中存在一個僅限于未認(rèn)證和未經(jīng)授權(quán)的用戶訪問的管理頁面。當(dāng)我們訪問/login或/admin時會跳轉(zhuǎn)到https://bountysite.com/admin/dashboard?redirect=/。
對登錄頁面進行暴力破解也許是一個可行方案,但是我并不喜歡這種方式。看一下網(wǎng)頁源碼,沒什么有用的內(nèi)容。于是我開始查看目標(biāo)網(wǎng)站的結(jié)構(gòu)。似乎目標(biāo)網(wǎng)站的JS文件都放在少數(shù)幾個文件夾中,如/lib、/js、/application等。
有意思!
祭出神器BurpSuite,使用Intruder跑一下看能否在上述文件夾中找到任何可訪問的JS文件。將攻擊點設(shè)置為https://bountysite.com/admin/dashboard/js/*attack*.js。注意,不要忘記.js擴展名,這樣如果文件能夠訪問則返回200響應(yīng)。確實有意思!因為我找到了一些可訪問的JS文件,其中一個文件是/login.js。
訪問這個JS文件https://bountysite.com/admin/dashboard/js/login.js,請求被重定向至管理頁面:) 。但是,我并沒有查看該文件的權(quán)限,只能看到部分接口信息。
但是我并沒有就此止步。這看起來很奇怪,為什么我訪問一個.js文件卻被作為HTML加載了呢?經(jīng)過一番探查,終于發(fā)現(xiàn),我能夠訪問管理頁面的原因在于*login*。是的,只要在請求路徑/dashboard/后的字符串中含有*login*(除了'login',這只會使我回到登錄頁面),請求就會跳轉(zhuǎn)到這個管理接口,但是卻沒有正確的授權(quán)。
我繼續(xù)對這個受限的管理接口進行了進一步的測試。再一次查看了頁面源碼,試著搞清楚網(wǎng)站結(jié)構(gòu)。在這個管理接口中,有其他一些JS文件能夠幫助我理解管理員是如何執(zhí)行操作的。一些管理操作需要一個有效的令牌。我試著使用從一個JS文件中泄露的令牌執(zhí)行相關(guān)管理操作,然并卵。請求還是被重定向到了登錄頁面。我發(fā)現(xiàn)另外一個真實存在的路徑中也部署了一些內(nèi)容,那就是/dashboard/controllers/*.php。
再一次祭出BurpSuite,使用Intruder檢查一下是否存在可以從此處訪問的其他任何路徑。第二次Intruder的結(jié)果是,我發(fā)現(xiàn)幾乎不存在其他無需授權(quán)即可訪問的路徑。這是基于服務(wù)器返回的500或者200響應(yīng)得出的結(jié)論。
回到我在上一步偵察中了解到的網(wǎng)站結(jié)構(gòu)中,我發(fā)現(xiàn)這些路徑是在/controllers中定義的,通過/dashboard/*here*/進行訪問。但是直接訪問這些路徑會跳轉(zhuǎn)到登錄頁面,似乎網(wǎng)站對Session檢查得還挺嚴(yán)格。此時我又累又困,幾乎都打算放棄了,但是我想最后再試一把。如果我利用與訪問管理頁面相同的方法去執(zhí)行這些管理操作會怎么樣呢?很有趣,高潮來了:) 我能夠做到這一點。
通過訪問/dashboard/photography/loginx,請求跳轉(zhuǎn)到了Admin Photography頁面,并且擁有完整的權(quán)限!
從這里開始,我能夠執(zhí)行和訪問/dashboard/*路徑下的所有操作和目錄,這些地方充滿了諸如SQL注入、XSS、文件上傳、公開重定向等漏洞。但是,我沒有繼續(xù)深入測試,因為這些都不在賞金計劃之內(nèi),根據(jù)計劃要求,一旦突破管理授權(quán)限制,應(yīng)立即報告問題。此外,根據(jù)管理頁面顯示的調(diào)試錯誤信息可知,我之所以能夠訪問到管理頁面,是因為應(yīng)用程序在/dashboard/controllers/*文件中存在錯誤配置。期望達(dá)到的效果是:只要請求鏈接中出現(xiàn)*login*,就重定向至主登錄頁面,然而,實際情況并不如人所愿。
后記
總之,這是有趣的一天!我拿到了這個漏洞賞金計劃最大金額的獎勵。
Axios 是一個基于 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。我們知道 Promise 是 js 異步的一種解決方案,它最大的特性就是可以通過 .then 的方式來進行鏈?zhǔn)秸{(diào)用。
其實說白了axios是對ajax的封裝,axios有的ajax都有,ajax有的axios不一定有,總結(jié)一句話就是axios是ajax,ajax不止axios。
axios的使用比較簡單,文檔講得也非常清晰,你應(yīng)該先閱讀axios的官方文檔:axios文檔。
在html頁面中直接引入使用:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
以下案例中的get請求地址為crmeb相關(guān)演示站地址,可用于測試獲取!
<script>
const url = 'https://store.crmeb.net/api/pc/get_category_product'
axios({
url: url,
method: 'get', // 這里可以省略,默認(rèn)為get
}).then(res => {
// 返回請求到的數(shù)據(jù)
console.log(res)
}).catch(err => {
// 返回錯誤信息
console.log(err)
})
</script>
<script>
const url = 'https://store.crmeb.net/api/pc/get_category_product'
axios({
url: url,
method: 'get', // 這里可以省略,默認(rèn)為get
// 這里的鍵值對會拼接成這樣url?page=1&limit=3
params: {
page: 1,
limit: 3
}
}).then(res => {
// 返回請求到的數(shù)據(jù)
console.log(res)
}).catch(err => {
// 返回錯誤信息
console.log(err)
})
</script>
<script>
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
}).then(res => {
// 返回請求到的數(shù)據(jù)
console.log(res)
}).catch(err => {
// 返回錯誤信息
console.log(err)
});
</script>
如果在開發(fā)中需要等到多個接口的數(shù)據(jù)同時請求到后才能繼續(xù)后邊的邏輯,那么即可使用并發(fā)請求,axios并發(fā)請求,使用all方法,all方法的參數(shù)為一個數(shù)組,數(shù)組的每個值可以為一次請求,請求完成后直接.then即可合并兩次請求的數(shù)據(jù),返回結(jié)果為一個數(shù)組!
<script>
axios.all([
axios({
url: 'https://store.crmeb.net/api/pc/get_products',
params: {
page: 1,
limit: 20,
cid: 57,
sid: 0,
priceOrder: '',
news: 0,
}
}),
axios({
url: 'https://store.crmeb.net/api/pc/get_company_info',
})
]).then(results => {
console.log(results)
})
</script>
如果你想自動把這個數(shù)組展開的話在then()方法中傳入axios.spread()方法即可,如下所示:
<script>
axios.all([
axios({
url: 'https://store.crmeb.net/api/pc/get_products',
params: {
page: 1,
limit: 20,
cid: 57,
sid: 0,
priceOrder: '',
news: 0,
}
}),
axios({
url: 'https://store.crmeb.net/api/pc/get_company_info',
})
]).then(axios.spread((res1, res2) => {
console.log(res1);
console.log(res2);
}))
</script>
但在使用vue組件化開發(fā)的時候一般我們會通過npm安裝,引入項目!
npm install axios --save
一般在實際項目中我們并不會像上邊這樣直接去使用axios請求數(shù)據(jù),而是將axios封裝在一個單獨的文件,這樣做的目的主要是用來抽取公共邏輯到一個配置文件里,對這些公共邏輯做一個封裝,即使某一天這個axios框架不維護了,或者出現(xiàn)了重大bug也不再修復(fù)的時候,我們可以只修改配置文件即可達(dá)到全局修改的目的,如果把每次請求邏輯都寫到對應(yīng)的組件中,那修改起來簡直就是一個噩夢!
在項目的src目錄下創(chuàng)建一個network文件夾,再在其中創(chuàng)建一個request.js文件,路徑為:src/network/request.js
// src/network/request.js
// 引入axios
import axios from 'axios'
// 這里未使用default導(dǎo)出,是為了以后的擴展,便于導(dǎo)出多個方法
export function request(config){
// 創(chuàng)建axios實例
const instance = axios.create({
// 這里定義每次請求的公共數(shù)據(jù),例如全局請求頭,api根地址,過期時間等
// 具體參數(shù)可參考axios的官方文檔
baseURL: 'http://demo26.crmeb.net/api',
timeout: 5000
})
// 攔截請求,如果獲取某個請求需要攜帶一些額外數(shù)據(jù)
instance.interceptors.request.use(
config => {
console.log(config);
return config;
}, err => {
console.log(err);
})
// 攔截響應(yīng)
instance.interceptors.response.use(
res => {
console.log(res)
return res.data
}, err => {
console.log(err)
}
)
// 發(fā)送請求
return instance(config)
一般我們會將所有的請求放在一個api.js文件中,將每次請求封裝為一個方法,比如我這里會在request.js的同目錄創(chuàng)建一個api.js文件封裝我們所有的請求。
import { request } from '../api/request'
// 獲取分類
export const getHomeCategory = () => {
return request({
url: '/category'
})
}
// 獲取banner圖
export const getHomeBanner = () => {
return request({
url: '/pc/get_banner'
})
}
之后再在組件中引入調(diào)用導(dǎo)出的相關(guān)接口方法即可,如:
import { getHomeBanner } from "../network/api"
getHomeBanner().then(res => {
console.log(res)
})
以上就是一個簡單的封裝,其中有個攔截請求和攔截響應(yīng),可能很多初學(xué)的人理解起來有點吃力,我在這里以個人淺見闡述,希望能帶給你些許啟發(fā)!
還是發(fā)揮閱讀理解能力,攔截攔截其實就是此路是我開,此樹是我栽,要想過此路,留下買路錢,攔截請求就是比如某些請求需要攜帶一些額外的信息才能訪問,實際項目中最常見的就是需要登錄后才能查看的信息,請求中就必須攜帶token才能訪問,就可以在這里處理,還有攔截響應(yīng),比如請求到數(shù)據(jù)之后,發(fā)現(xiàn)不符合要求,先攔下來處理一下,再返回給前端,這就是一個攔截器的基本工作流程!
如下所示:
// 攔截請求,如果獲取某個請求需要攜帶一些額外數(shù)據(jù)
instance.interceptors.request.use(
config => {
console.log(config);
return config;
}, err => {
console.log(err);
})
// 攔截響應(yīng)
instance.interceptors.response.use(
res => {
console.log(res)
return res.data
}, err => {
console.log(err)
}
)
axios還為我們提供了一些全局配置,如下:
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
當(dāng)然也可以將其配置在我們之前創(chuàng)建的axios實例中,使其只作用于某個實例!
然后來看一下 axios 的所有配置信息:
數(shù)據(jù)來自axios中文文檔
各種請求體格式中,文件上傳是比較特殊的一種,通常其content-type請求頭為multipart/form-data,可以提交文本與文件混合的請求數(shù)據(jù)。這一節(jié)我們通過兩個例子來看看postman怎么調(diào)用文件上傳的請求。
以特斯汀學(xué)院自動化測試平臺項目上傳頭像圖片的接口為例。
項目地址:http://www.testingedu.com.cn/mypro/#/login
接口地址:http://www.testingedu.com.cn/mypro/api/user/setavatar
從接口抓包信息中可以看到,Content-Type為multipart/form-data; boundary=----WebKitFormBoundarysArkjRsb6TbgepSl,其中的boundry是作為請求體多個部分的參數(shù)的分割線邊界的,從請求體內(nèi)容第一行就可以看到這個分割線的值。
在Postman中完成該文件上傳接口的調(diào)用時,需要選擇body中的form-data,在填寫內(nèi)容時注意將鼠標(biāo)移到key列輸入框的右側(cè),會出現(xiàn)一個下拉框,可以選擇參數(shù)類型為Text或者File,針對文件參數(shù)選擇File,并填寫抓包信息中獲取到的鍵名file,最后在VALUEL列中選擇要上傳的文件。
設(shè)置完請求體之后查看請求頭可以看到Content-Type的值已經(jīng)被自動設(shè)置為multipart/form-data,而boundry字段則是在請求發(fā)送時計算得到。所以使用postman完成文件上傳接口請求時,并不需要額外設(shè)置Content-Type。
要注意,設(shè)置頭像接口需要前置調(diào)用測試平臺登錄接口之后才能正常完成請求,否則會提示缺少user_id字段,在請求前先參考json格式請求章節(jié)的示例登錄接口http://www.testingedu.com.cn/mypro/api/user/login完成登錄操作之后再調(diào)用設(shè)置頭像接口。
這里,我們完成了一個只有文件參數(shù)的文件上傳接口,接下來,再看一個除了文件參數(shù),還有文本格式參數(shù)的文件上傳接口。
以特斯汀電商項目個人信息修改頭像的接口為例。
項目地址:http://www.testingedu.com.cn:8000/Home/User/info.html
接口地址:http://www.testingedu.com.cn:8000/index.php/home/Uploadify/imageUp/savepath/head_pic/pictitle/banner/dir/images.html
從抓包信息中可以看到,請求體中包含了多段由boundry分割開的請求參數(shù)內(nèi)容,除了上傳的文件參數(shù)之外,還包含了部分純文本內(nèi)容的參數(shù)。
將fiddler切換到WebForms格式顯示則可以看到完整的參數(shù)列表,每一行的參數(shù)名為其中name指定的字段。
在Postman中完成請求時,針對同時出現(xiàn)文件和文本格式的請求,在選擇填寫的參數(shù)類型時,根據(jù)對應(yīng)類型進行選擇并填寫。
由此可以看到,文件上傳格式處理時,在Postman里只需要按照請求參數(shù)格式選擇并逐個填寫,并不復(fù)雜。
至此,在Postman中完成常見的幾種請求體格式的請求操作都已實現(xiàn)。
回顧總結(jié)一下,常用的接口測試請求體的編輯格式包括如下幾種,和Content-Type頭域分別對應(yīng):
在Postman中使用x-www-form-urlencoded進行填寫,或者使用raw格式填寫,再手動設(shè)置Content-Type
在Postman中使用raw格式選擇json完成填寫。
在Postman中使用raw格式填寫,再手動設(shè)置Content-Type為text/xml。
在Postman中使用form-data填寫,注意文件和文本類型格式,分別選擇Text和File格式。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。