譯:h4d35
預估稿費:120RMB
投稿方式:發(fā)送郵件至linwei#360.cn,或登陸網頁版在線投稿
前言
本篇文章主要介紹了在一次漏洞懸賞項目中如何利用配置錯誤挖到一個認證繞過漏洞。
從JS文件中發(fā)現(xiàn)認證繞過漏洞
本文內容源自一個私有漏洞賞金計劃。在這個漏洞計劃中,接受的漏洞范圍限于目標網站少數幾個公開的功能。基于前期發(fā)現(xiàn)的問題(當我被邀請進這個計劃時,其他人一共提交了5個漏洞),似乎很難再挖到新的漏洞。同時,在賞金詳情中提到了這樣一句話:
如果你成功進入管理頁面,請立即報告,請勿在/admin中進行進一步的測試。
然而,目標網站中存在一個僅限于未認證和未經授權的用戶訪問的管理頁面。當我們訪問/login或/admin時會跳轉到https://bountysite.com/admin/dashboard?redirect=/。
對登錄頁面進行暴力破解也許是一個可行方案,但是我并不喜歡這種方式。看一下網頁源碼,沒什么有用的內容。于是我開始查看目標網站的結構。似乎目標網站的JS文件都放在少數幾個文件夾中,如/lib、/js、/application等。
有意思!
祭出神器BurpSuite,使用Intruder跑一下看能否在上述文件夾中找到任何可訪問的JS文件。將攻擊點設置為https://bountysite.com/admin/dashboard/js/*attack*.js。注意,不要忘記.js擴展名,這樣如果文件能夠訪問則返回200響應。確實有意思!因為我找到了一些可訪問的JS文件,其中一個文件是/login.js。
訪問這個JS文件https://bountysite.com/admin/dashboard/js/login.js,請求被重定向至管理頁面:) 。但是,我并沒有查看該文件的權限,只能看到部分接口信息。
但是我并沒有就此止步。這看起來很奇怪,為什么我訪問一個.js文件卻被作為HTML加載了呢?經過一番探查,終于發(fā)現(xiàn),我能夠訪問管理頁面的原因在于*login*。是的,只要在請求路徑/dashboard/后的字符串中含有*login*(除了'login',這只會使我回到登錄頁面),請求就會跳轉到這個管理接口,但是卻沒有正確的授權。
我繼續(xù)對這個受限的管理接口進行了進一步的測試。再一次查看了頁面源碼,試著搞清楚網站結構。在這個管理接口中,有其他一些JS文件能夠幫助我理解管理員是如何執(zhí)行操作的。一些管理操作需要一個有效的令牌。我試著使用從一個JS文件中泄露的令牌執(zhí)行相關管理操作,然并卵。請求還是被重定向到了登錄頁面。我發(fā)現(xiàn)另外一個真實存在的路徑中也部署了一些內容,那就是/dashboard/controllers/*.php。
再一次祭出BurpSuite,使用Intruder檢查一下是否存在可以從此處訪問的其他任何路徑。第二次Intruder的結果是,我發(fā)現(xiàn)幾乎不存在其他無需授權即可訪問的路徑。這是基于服務器返回的500或者200響應得出的結論。
回到我在上一步偵察中了解到的網站結構中,我發(fā)現(xiàn)這些路徑是在/controllers中定義的,通過/dashboard/*here*/進行訪問。但是直接訪問這些路徑會跳轉到登錄頁面,似乎網站對Session檢查得還挺嚴格。此時我又累又困,幾乎都打算放棄了,但是我想最后再試一把。如果我利用與訪問管理頁面相同的方法去執(zhí)行這些管理操作會怎么樣呢?很有趣,高潮來了:) 我能夠做到這一點。
通過訪問/dashboard/photography/loginx,請求跳轉到了Admin Photography頁面,并且擁有完整的權限!
從這里開始,我能夠執(zhí)行和訪問/dashboard/*路徑下的所有操作和目錄,這些地方充滿了諸如SQL注入、XSS、文件上傳、公開重定向等漏洞。但是,我沒有繼續(xù)深入測試,因為這些都不在賞金計劃之內,根據計劃要求,一旦突破管理授權限制,應立即報告問題。此外,根據管理頁面顯示的調試錯誤信息可知,我之所以能夠訪問到管理頁面,是因為應用程序在/dashboard/controllers/*文件中存在錯誤配置。期望達到的效果是:只要請求鏈接中出現(xiàn)*login*,就重定向至主登錄頁面,然而,實際情況并不如人所愿。
后記
總之,這是有趣的一天!我拿到了這個漏洞賞金計劃最大金額的獎勵。
務端和客戶端之間是通過session(會話)來連接溝通。當客戶端的瀏覽器連接到服務器后,服務器就會建立一個該用戶的session。每個用戶的session都是獨立的,并且由服務器來維護。每個用戶的session是由一個獨特的字符串來識別,成為session id。用戶發(fā)出請求時,所發(fā)送的http表頭內包含session id 的值。服務器使用http表頭內的session id來識別時哪個用戶提交的請求。
session保存的是每個用戶的個人數據,一般的web應用程序會使用session來保存通過驗證的用戶賬號和密碼。在轉換不同的網頁時,如果需要驗證用戶身份,就是用session內所保存的賬號和密碼來比較。session的生命周期從用戶連上服務器后開始,在用戶關掉瀏覽器或是注銷時用戶session_destroy函數刪除session數據時結束。如果用戶在20分鐘內沒有使用計算機的動作,session也會自動結束。
php處理session的應用架構
會話劫持
會話劫持是指攻擊者利用各種手段來獲取目標用戶的session id。一旦獲取到session id,那么攻擊者可以利用目標用戶的身份來登錄網站,獲取目標用戶的操作權限。
攻擊者獲取目標用戶session id的方法:
1)暴力破解:嘗試各種session id,直到破解為止。
2)計算:如果session id使用非隨機的方式產生,那么就有可能計算出來
3)竊取:使用網絡截獲,xss攻擊等方法獲得
會話劫持的攻擊步驟
實例
session_start();
if (isset($_POST["login"]))
{
$link = mysql_connect("localhost", "root", "root")
or die("無法建立MySQL數據庫連接:" . mysql_error());
mysql_select_db("cms") or die("無法選擇MySQL數據庫");
if (!get_magic_quotes_gpc())
{
$query = "select * from member where username=’" . addslashes($_POST["username"]) .
"’ and password=’" . addslashes($_POST["password"]) . "’";
}
else
{
$query = "select * from member where username=’" . $_POST["username"] .
"’ and password=’" . $_POST["password"] . "’";
}
$result = mysql_query($query)
or die("執(zhí)行MySQL查詢語句失敗:" . mysql_error());
$match_count = mysql_num_rows($result);
if ($match_count)
{
$_SESSION["username"] = $_POST["username"];
$_SESSION["password"] = $_POST["password"];
$_SESSION["book"] = 1;
mysql_free_result($result);
mysql_close($link);
header("Location: http://localhost/index.php?user=" .
$_POST["username"]);
}
session_start();
訪客的 Session ID 是:echo session_id(); ?>
訪客:echo htmlspecialchars($_GET["user"], ENT_QUOTES); ?>
book商品的數量:echo htmlspecialchars($_SESSION["book"], ENT_QUOTES); ?>
如果登錄成功,使用
$_SESSION["username"] 保存賬號
$_SESSION["password"] 保存密碼
#_SESSION["book"] 保存購買商品數目
登錄以后顯示
開始攻擊
//attack.php
php
// 打開Session
session_start();
echo "目標用戶的Session ID是:" . session_id() . "<br />";
echo "目標用戶的username是:" . $_SESSION["username"] . "<br />";
echo "目標用戶的password是:" . $_SESSION["password"] . "<br />";
// 將book的數量設置為2000
$_SESSION["book"] = 2000;
?>
提交 http://localhost/attack.php?PHPSESSID=5a6kqe7cufhstuhcmhgr9nsg45 此ID為獲取到的客戶session id,刷新客戶頁面以后
客戶購買的商品變成了2000
session固定攻擊
黑客可以使用把session id發(fā)給用戶的方式,來完成攻擊
http://localhost/index.php?user=dodo&PHPSESSID=1234 把此鏈接發(fā)送給dodo這個用戶顯示
然后攻擊者再訪問 http://localhost/attack.php?PHPSESSID=1234 后,客戶頁面刷新,發(fā)現(xiàn)
商品數量已經成了2000
防范方法
1)定期更改session id
函數 bool session_regenerate_id([bool delete_old_session])
delete_old_session為true,則刪除舊的session文件;為false,則保留舊的session,默認false,可選
在index.php開頭加上
session_start();
session_regenerate_id(TRUE);
……
這樣每次從新加載都會產生一個新的session id
2)更改session的名稱
session的默認名稱是PHPSESSID,此變量會保存在cookie中,如果黑客不抓包分析,就不能猜到這個名稱,阻擋部分攻擊
session_start();
session_name("mysessionid");
……
3)關閉透明化session id
透明化session id指當瀏覽器中的http請求沒有使用cookies來制定session id時,sessioin id使用鏈接來傳遞;打開php.ini,編輯
session.use_trans_sid = 0
代碼中
int_set("session.use_trans_sid", 0);
session_start();
……
4)只從cookie檢查session id
session.use_cookies = 1 表示使用cookies存放session id
session.use_only_cookies = 1 表示只使用cookies存放session id,這可以避免session固定攻擊
代碼中
int_set("session.use_cookies", 1);
int_set("session.use_only_cookies", 1); p>
5)使用URL傳遞隱藏參數
session_start();
$seid = md5(uniqid(rand()), TRUE));
$_SESSION["seid"] = $seid;
攻擊者雖然能獲取session數據,但是無法得知$seid的值,只要檢查seid的值,就可以確認當前頁面是否是web程序自己調用的。
者:蔣蜀黍,Python愛好者社區(qū)專欄作者
網址:https://mp.weixin.qq.com/s/tfWsiy_LxQSJKUAvB49U0g
1、概覽
1.1、實例引入
# 引入Requests庫 import requests # 發(fā)起GET請求 response = requests.get('https://www.baidu.com/') # 查看響應類型 requests.models.Response print(type(response)) # 輸出狀態(tài)碼 print(response.status_code) # 輸出響應內容類型 text print(type(response.text)) # 輸出響應內容 print(response.text) # 輸出cookies print(response.cookies)
1.2、各種請求方式
import requests # 發(fā)起POST請求 requests.post('http://httpbin.org/post') # 發(fā)起PUT請求 requests.put('http://httpbin.org/put') # 發(fā)起DELETE請求 requests.delete('http://httpbin.org/delete') # 發(fā)送HEAD請求 requests.head('http://httpbin.org/get') # 發(fā)送OPTION請求 requests.options('http://httpbin.org/get')
2、請求
2.1 、基本GET請求
2.1.1、基本寫法
import requests response = requests.get('http://httpbin.org/get') print(response.text)
2.1.2、帶參數的GET請求
import requests response = requests.get('http://httpbin.org/get?name=jyx&age=18') print(response.text)
2.1.3、帶參數的GET請求(2)
import requests # 分裝GET請求參數 param = {'name':'jyx','age':19} # 設置GET請求參數(Params) response = requests.get('http://httpbin.org/get',params=param) print(response.text)
2.1.4、解析json
import requests response = requests.get('http://httpbin.org/get') # 獲取響應內容 print(type(response.text)) # 如果響應內容是json,就將其轉為json print(response.json()) # 輸出的是字典類型 print(type(response.json()))
2.1.5、獲取二進制數據
import requests response = requests.get('http://github.com/favicon.ico') # str,bytes print(type(response.text),type(response.content)) # 輸出響應的文本內容 print(response.text) # 輸出響應的二進制內容 print(response.content) # 下載二進制數據到本地 with open('favicon.ico','wb') as f: f.write(response.content) f.close()
2.1.6、添加headers
import requests # 設置User-Agent瀏覽器信息 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" } # 設置請求頭信息 response = requests.get('https://www.zhihu.com/explore',headers=headers) print(response.text)
2.2、基本POST請求
import requests # 設置傳入post表單信息 data= { 'name':'jyx', 'age':18} # 設置請求頭信息 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36" } # 設置請求頭信息和POST請求參數(data) response = requests.post('http://httpbin.org/post', data=data, headers=headers) print(response.text)
3、響應
3.1 response屬性
import requests response = requests.get('http://www.jianshu.com/') # 獲取響應狀態(tài)碼 print(type(response.status_code),response.status_code) # 獲取響應頭信息 print(type(response.headers),response.headers) # 獲取響應頭中的cookies print(type(response.cookies),response.cookies) # 獲取訪問的url print(type(response.url),response.url) # 獲取訪問的歷史記錄 print(type(response.history),response.history)
3.2、 狀態(tài)碼判斷
import requests response = requests.get('http://www.jianshu.com/404.html') # 使用request內置的字母判斷狀態(tài)碼 if not response.status_code == requests.codes.ok: print('404-1') response = requests.get('http://www.jianshu.com') # 使用狀態(tài)碼數字判斷 if not response.status_code == 200: print('404-2')
3.3 requests內置的狀態(tài)字符
100: ('continue',), 101: ('switching_protocols',), 102: ('processing',), 103: ('checkpoint',), 122: ('uri_too_long', 'request_uri_too_long'), 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\o/', '?'), 201: ('created',), 202: ('accepted',), 203: ('non_authoritative_info', 'non_authoritative_information'), 204: ('no_content',), 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 208: ('already_reported',), 226: ('im_used',), # Redirection. 300: ('multiple_choices',), 301: ('moved_permanently', 'moved', '\'), 302: ('found',), 303: ('see_other', 'other'), 304: ('not_modified',), 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 308: ('permanent_redirect', 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 # Client Error. 400: ('bad_request', 'bad'), 401: ('unauthorized',), 402: ('payment_required', 'payment'), 403: ('forbidden',), 404: ('not_found', '-'), 405: ('method_not_allowed', 'not_allowed'), 406: ('not_acceptable',), 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 408: ('request_timeout', 'timeout'), 409: ('conflict',), 410: ('gone',), 411: ('length_required',), 412: ('precondition_failed', 'precondition'), 413: ('request_entity_too_large',), 414: ('request_uri_too_large',), 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 417: ('expectation_failed',), 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 421: ('misdirected_request',), 422: ('unprocessable_entity', 'unprocessable'), 423: ('locked',), 424: ('failed_dependency', 'dependency'), 425: ('unordered_collection', 'unordered'), 426: ('upgrade_required', 'upgrade'), 428: ('precondition_required', 'precondition'), 429: ('too_many_requests', 'too_many'), 431: ('header_fields_too_large', 'fields_too_large'), 444: ('no_response', 'none'), 449: ('retry_with', 'retry'), 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 499: ('client_closed_request',), # Server Error. 500: ('internal_server_error', 'server_error', '/o\', '?'), 501: ('not_implemented',), 502: ('bad_gateway',), 503: ('service_unavailable', 'unavailable'), 504: ('gateway_timeout',), 505: ('http_version_not_supported', 'http_version'), 506: ('variant_also_negotiates',), 507: ('insufficient_storage',), 509: ('bandwidth_limit_exceeded', 'bandwidth'), 510: ('not_extended',), 511: ('network_authentication_required', 'network_auth', 'network_authentication'),
4、高級操作
4.1、文件上傳
import requests files = {'file':open('favicon.ico','rb')} # 往POST請求頭中設置文件(files) response = requests.post('http://httpbin.org/post',files=files) print(response.text)
4.2、獲取cookies
import requests response = requests.get('https://www.baidu.com') print(response.cookies) for key,value in response.cookies.items(): print(key,'=====',value)
4.3、會話維持
4.3.1、普通請求
import requests requests.get('http://httpbin.org/cookies/set/number/12456') response = requests.get('http://httpbin.org/cookies') # 本質上是兩次不同的請求,session不一致 print(response.text)
4.3.2、會話維持請求
import requests # 從Requests中獲取session session = requests.session() # 使用seesion去請求保證了請求是同一個session session.get('http://httpbin.org/cookies/set/number/12456') response = session.get('http://httpbin.org/cookies') print(response.text)
4.4、證書驗證
4.4.1、無證書訪問
import requests response = requests.get('https://www.12306.cn') # 在請求https時,request會進行證書的驗證,如果驗證失敗則會拋出異常 print(response.status_code)
4.4.2、關閉證書驗證
import requests # 關閉驗證,但是仍然會報出證書警告 response = requests.get('https://www.12306.cn',verify=False) print(response.status_code)
4.4.3、消除關閉證書驗證的警告
from requests.packages import urllib3 import requests # 關閉警告 urllib3.disable_warnings() response = requests.get('https://www.12306.cn',verify=False) print(response.status_code)
4.4.4、手動設置證書
import requests # 設置本地證書 response = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key')) print(response.status_code)
4.5、代理設置
4.5.1、設置普通代理
import requests proxies = { "http": "http://127.0.0.1:9743", "https": "https://127.0.0.1:9743", } # 往請求中設置代理(proxies ) response = requests.get("https://www.taobao.com", proxies=proxies) print(response.status_code)
4.5.2、設置帶有用戶名和密碼的代理
import requests proxies = { "http": "http://user:password@127.0.0.1:9743/", } response = requests.get("https://www.taobao.com", proxies=proxies) print(response.status_code)
4.5.3、設置socks代理
pip3 install 'requests[socks]
import requests proxies = { 'http': 'socks5://127.0.0.1:9742', 'https': 'socks5://127.0.0.1:9742' } response = requests.get("https://www.taobao.com", proxies=proxies) print(response.status_code)
4.6、超時設置
import requests from requests.exceptions import ReadTimeout try: # 設置必須在500ms內收到響應,不然或拋出ReadTimeout異常 response = requests.get("http://httpbin.org/get", timeout=0.5) print(response.status_code) except ReadTimeout: print('Timeout')
4.7、認證設置
import requests from requests.auth import HTTPBasicAuth r = requests.get('http://120.27.34.24:9001', auth=HTTPBasicAuth('user', '123')) # r = requests.get('http://120.27.34.24:9001', auth=('user', '123')) print(r.status_code)
4.8、異常處理
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。