校園黑客,python突破校園飲水機餘額限制!
Python 和放大鏡的二進位制程式碼
0x00 目錄
0x01 前言
0x02 利用fd分析與實戰
0x03 Python+burp簡易搭建代理伺服器
0x04 實現自動化exp
0x05 結束語
0x01 前言 本文只是對某校園熱水服務app做個測試,其實本人並沒有做大壞事,並未傳播相關技術,文章以下內容的敏感部分會打碼,並且相關廠商已經正在進行漏洞修復,大家看看就好。文章後會提供“Python簡易搭建代理伺服器”的關鍵程式碼,程式碼簡單而且開源,我自己也加了點註釋。最後的exp只針對本app,感興趣的朋友可以看看。以下內容包含個人見解,輕噴。若有誤導懇請指出。
0x02 利用fd分析與實戰
要對這app做測試,首先了解下app的運作流程。
登入後,app主介面如下
邏輯簡單,步驟如下:
1.app使用藍芽連線水錶。
2.先扣你10塊餘額,轉到預扣款裡(不夠10元扣除所有)
3.水錶上顯示10塊錢,也就是你的預扣款(我只有6元所有會顯示6元),水錶出水
4.用水,水錶上的金額隨著用水量而減少
5.停止用水,水錶上顯示的金額就是餘額,將返還給餘額
這樣分析還不夠,我們利用fiddler看看app向伺服器的HTTP請求內容
怎麼抓手機包看這,講的比我好我就不多說了:如何用Fiddler對Android應用進行抓包
app啟動時的HTTP資料請求截圖:
一共請求了5次,沒發現什麼地方有問題。要想知道這些請求都是做什麼的,可以看看請求的get的內容以及post的內容,還可以看看網站的響應內容。從截圖中fiddler右下角的“Message=未發現版本”就可以猜想這個http請求可能是檢查更新的請求。
我們用的是“fiddler”,大家都知道的,許多利用都是改金額,現在還沒用水,還沒發生金額的變化,咱們現在用水試試。
點選“點選開始用水”按鈕,看看app截圖以及fiddler的http請求
app扣除了6塊,轉到了預扣款裡,水錶上顯示6.00元,<img>假裝有水錶照片.jpg</img>
看fiddler截圖,一共有4個http請求,注意紅框,“00A3AAAF”是本次用水的訂單號,之後會用到。
看了下這些http請求,發現並未和伺服器發生有關金額的資訊互動。
我們進行下個步驟,停止用水讓它退還餘額。
app截圖,以及fiddler抓取到的金額資料截圖:
可以看到之前紅框框起來的訂單號在結賬返還金額的時候用到了,不用改。bal的值明顯就是退還金額,將他改為6,也就是我的預扣款試試。成功退還所有預扣款!(這app有兩次http請求返回的餘額,內容都相同,都需要改為6,另一次就不貼截圖了)
點選確認結賬就餘額就能變回6元了,這個“確認結賬”按鈕實際上是個重新整理命令。
0x03 Python+burp簡易搭建代理伺服器
之前已經用fd實現漏洞利用,但是每次都需要手動改,如果寫個指令碼會比較快一些。正巧我在某同性交友網看到了“Python搭建代理伺服器”的程式碼,就想試試能不能通過這個程式碼稍加更改實現自動化Exp。
貼出原始碼:
#coding:utf-8 import socket import thread import urlparse import select BUFLEN = 8192 class Proxy(object): def __init__(self, conn, addr): self.source = conn self.request = "" self.headers = {} self.destnation = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.run() #用來構造header def get_headers(self): header = '' while True: header += self.source.recv(BUFLEN) index = header.find(' ') if index > 0: break # firstLine,self.request=header.split(' ',1) firstLine = header[:index] self.request = header[index + 1:] self.headers['method'], self.headers['path'], self.headers['protocol'] = firstLine.split() #用來發送請求 def conn_destnation(self): url = urlparse.urlparse(self.headers['path']) hostname = url[1] port = "80" if hostname.find(':') > 0: addr, port = hostname.split(':') else: addr = hostname port = int(port) ip = socket.gethostbyname(addr) print ip, port self.destnation.connect(('127.0.0.1', 8080)) #可以填burpsuite的代理 data = "%s %s %s " % (self.headers['method'], self.headers['path'], self.headers['protocol']) self.destnation.send(data + self.request) #傳送請求 print data + self.request #傳送響應的結果 def renderto(self): readsocket = [self.destnation] while True: data = '' (rlist, wlist, elist) = select.select(readsocket, [], [], 3) if rlist: data = rlist[0].recv(BUFLEN) if len(data) > 0: self.source.send(data)#傳送響應的結果 else: break # readsocket[0].close(); def run(self): self.get_headers() self.conn_destnation() self.renderto() class Server(object): def __init__(self, host, port, handler=Proxy): self.host = host self.port = port self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server.bind((host, port)) self.server.listen(5) self.handler = handler def start(self): while True: try: conn, addr = self.server.accept() thread.start_new_thread(self.handler, (conn, addr)) except: pass if __name__ == '__main__': s = Server('127.0.0.1', 6666) #這裡寫本地ip,監聽的埠。 s.start()
關鍵部分我寫了註釋,還有程式碼不清楚格式是否會亂,我在文後以附件的形式貼出來吧
這次配合burpsuite來使用,使用方法:第90行(倒數第二行)寫入之前用fiddler改包時手機wifi設定的代理,以及埠號,用來監聽手機發出的http請求。第44行寫burpsuite監聽的地址,從手機上獲取的http請求都會轉發給burpsuite,burpsuite設定我也貼出來吧:
設定好後,只要執行程式碼,就可以實現代理服務。雖然想要代理直接連burp就好,但是這提供了一個可以使用PY程式碼進行一定程度的中間人操作的環境。
0x04 實現自動化exp
PS:看到這裡就可以不看了,因無法提供app與藍芽水錶,以下內容多半無法看明白。。
之前實現了代理,那麼現在就要開始改程式碼了,將程式碼改成漏洞app的漏洞利用工具。
因為這個不是通用型漏洞,漏洞利用工具也只能針對這個app,所以以下內容不存在乾貨,感興趣的可以看下去。
思路:通過這個代理,將停止用水後“結賬”請求中返還的金額替換為預扣款的金額,即返還所有金額。
還有個:對app的http請求分析的越透徹,exp就可能寫的越簡單思路越清晰。
漏洞利用程式方法眾多,以下將按我自己的思路
從之前的fd測試可以知道,“用水”與“結賬返還金額”是兩個獨立的http請求部分,根據常識“結賬”肯定要在“用水”之後,那我們可以在app執行“用水”之後就馬上向伺服器請求偽造的“結賬”http請求,因偽造的“結賬”請求只是PC機跳過app與伺服器互動,所以執行結賬後不會影響藍芽水錶的供水。這種思路有個前提,就是能成功偽造“結賬”請求,如果無法偽造,也可以在“結賬”請求發出時攔截並改包,這樣理論上是不會出問題的。
偽造“結賬”需要在“用水”之後,那怎麼判斷“用水”請求?
用 if 判斷資料的特徵,簡單明瞭。因為只是判斷,不是攔截,所以只需要在conn_destnation函式的結尾,也就是destnation.send() 執行後,新增 if 判斷即可,如果是“用水”便開始偽造“結賬”。因為結賬需要訂單號,所以可以用re模組正則表示式匹配“用水”請求的特徵時順帶取出訂單號,見截圖:
訂單號正好包含在URL中,真方便。
用if判斷不為空就可以剛剛獲取的訂單號進行偽造的“結賬”了
偽造“結賬”請求需要類似cookies的憑證,憑證存在header裡,可以直接使用“用水”請求的header。
可是!!我發現用水、結賬、查餘額等一系列操作是使用的header中的憑證永不過期,若重登會給予新的憑證,但舊憑證依舊可以使用!所以就可以偷懶直接貼一份header使用。
有了憑證就可以為所欲為了,偽造“結賬”請求除了訂單號還需要知道預扣款,前面提到過,預扣款一般為10元,但有時候餘額不足預扣款就不足10元,所以為了程式相容性,還需要獲取下預扣款。既然有了憑證為所欲為,那麼可以再偽造下一個能讓伺服器返回預扣款的請求即可。
見上圖:有了憑證,並且正好伺服器返回的是json形式的,直接用Python 的 eval,再獲取對應鍵值就行。
然後就是執行偽造的“結賬”請求:
0x05 結束語
終於結束了,感謝看我這篇文章的人,更感謝從頭到尾看完的人,不枉我寫這篇文章,畢竟對於你們來說是沒見過的app,這太抽象了,並且文章內容無聊,而且讀程式碼遠比寫程式碼困難,遺憾的是不能提供實驗環境。要是有人看完我會很開心,謝謝啦QAQ