安全的關(guān)鍵.png)
GraphQL API滲透測(cè)試指南
項(xiàng)目gitee地址:https://gitee.com/shawn_chen_rtz/netease_cloud_music.git,歡迎star。
需要安裝依賴(lài)requests、pycrypto模塊,可通過(guò)pip命令。
pip install requests==2.27.1
pip install pycrypto==2.6.1
通過(guò)歌曲名獲取到歌曲id對(duì)于后續(xù)的獲取精彩評(píng)論和歌詞信息至關(guān)重要。
網(wǎng)易云音樂(lè)的根據(jù)歌曲名稱(chēng)獲取對(duì)應(yīng)歌曲id的接口信息:http://music.163.com/api/search/get,請(qǐng)求method為GET,入?yún)⒏袷絳“s”:song_name,”type”:1,”limit”:1},如下,
GET http://music.163.com/api/search/get
入?yún)⒏袷剑?br />
{
's': song_name, # 搜索關(guān)鍵字,本文中項(xiàng)目實(shí)現(xiàn)傳入待查詢(xún)歌曲名稱(chēng)
'type': 1, # 搜索類(lèi)型:1為單曲,2為專(zhuān)輯,3為歌手,4為歌詞
'limit': 1, # 返回?cái)?shù)量,本文中項(xiàng)目實(shí)現(xiàn),設(shè)置為1,即返回1首
}
其中入?yún)⒌膮?shù)‘s’表示查詢(xún)關(guān)鍵字,接收我們傳入的歌曲名稱(chēng);
參數(shù)‘type’表示查詢(xún)對(duì)象分類(lèi),在項(xiàng)目實(shí)現(xiàn)中設(shè)置值為1(表示搜索類(lèi)型為歌曲);
參數(shù)‘limit’表示返回?cái)?shù)據(jù)數(shù)量,在項(xiàng)目實(shí)現(xiàn)中設(shè)置值為1(只取1首)。
在項(xiàng)目中定義一個(gè)查詢(xún)歌曲id的方法search_song_id(),具體代碼實(shí)現(xiàn)如下,
import requests
import json
def search_song_id(song_name):
# 網(wǎng)易云音樂(lè)API地址
url = 'http://music.163.com/api/search/get'
# 參數(shù)設(shè)置
params = {
's': song_name, # 搜索關(guān)鍵字
'type': 1, # 搜索類(lèi)型:1為單曲,2為專(zhuān)輯,3為歌手,4為歌詞
'limit': 1, # 返回?cái)?shù)量
}
# 發(fā)送GET請(qǐng)求
response = requests.get(url, params=params)
# 解析JSON數(shù)據(jù)
data = response.json()
# 檢查是否有結(jié)果
if data['result']['songs']:
# 返回歌曲ID
return data['result']['songs'][0]['id']
else:
return None
if __name__ == "__main__":
# 使用函數(shù)查找歌曲ID
song_id = search_song_id('Your Song Name')
print(song_id)
song_id = search_song_id('偏偏喜歡你')
print(song_id)
接口http://music.163.com/api/search/get請(qǐng)求后返回的數(shù)據(jù)組織結(jié)構(gòu)如下,
根據(jù)接口返回的數(shù)據(jù)組織結(jié)構(gòu),歌曲id即data[‘result’][‘songs’][0][‘id’]。
通過(guò)上一節(jié)獲取到的歌曲id查詢(xún)歌曲歌詞。
網(wǎng)易云音樂(lè)對(duì)應(yīng)接口信息:http://music.163.com/api/song/lyric?id=song_id+&lv=1&tv=-1,其中song_id替換為目標(biāo)歌曲id,該接口請(qǐng)求Method為GET。
定義了一個(gè)獲取歌曲歌詞的方法get_lyric(),具體代碼實(shí)現(xiàn)如下,
import requests
import json
def get_lyric(song_id):
headers = {
"user-agent":"Mozilla/5.0",
"Referer":"http://music.163.com",
"Host":"music.163.com"
}
if not isinstance(song_id,str):
song_id = str(song_id)
url = f"http://music.163.com/api/song/lyric?id={song_id}+&lv=1&tv=-1"
r = requests.get(url,headers=headers)
r.raise_for_status()
r.encoding = r.apparent_encoding
json_obj = json.loads(r.text)
lyric = json_obj['lrc']['lyric']
#print(type(lyric))
return lyric
方法return返回歌曲歌詞lyric。
首先要找到返回歌曲精彩評(píng)論數(shù)據(jù)的目標(biāo)接口,訪問(wèn)網(wǎng)易云音樂(lè)網(wǎng)頁(yè),搜索某歌曲進(jìn)入歌曲頁(yè)面,F(xiàn)12打開(kāi)瀏覽器開(kāi)發(fā)者工具—網(wǎng)絡(luò),
刷新頁(yè)面,篩選請(qǐng)求列表,定位到目標(biāo)接口https://music.163.com/weapi/comment/resource/comments/get?csrf_token=,請(qǐng)求Method為POST,接口表單參數(shù)有兩個(gè),分別是params和encSecKey,如下面2張圖所示,
可以看到向接口提交的兩個(gè)表單參數(shù)都是一長(zhǎng)串的字符串,顯然是經(jīng)過(guò)加密的,在下一節(jié)將對(duì)參數(shù)的加密邏輯進(jìn)行探索分析。
對(duì)接口參數(shù)加密方式的分析可以說(shuō)是整個(gè)項(xiàng)目中最具挑戰(zhàn)的點(diǎn),事實(shí)上就是一種逆向工程,我們要大膽的假設(shè),小心地求證。當(dāng)然這種大膽假設(shè)是在有一定技術(shù)基礎(chǔ)的前提下做出的合理假設(shè),在本項(xiàng)目中涉及有前端、后端、加/解密等技術(shù)。F12瀏覽器開(kāi)發(fā)者工具-網(wǎng)絡(luò),重復(fù)刷新歌曲頁(yè)面,可以看到接口參數(shù)的值發(fā)生了變化但是值對(duì)應(yīng)的字符串長(zhǎng)度保持不變,可以猜到參數(shù)的加密過(guò)程中有隨機(jī)性操作且明文的結(jié)構(gòu)可能是固定的。進(jìn)一步點(diǎn)擊目標(biāo)接口請(qǐng)求的啟動(dòng)器標(biāo)簽,觀察其請(qǐng)求啟動(dòng)器鏈,可以看到有一個(gè)js文件,如下圖所示,
js文件的域名為https://s3.music.126.net,于是去開(kāi)發(fā)者工具-源代碼/來(lái)源中去找這個(gè)js文件,找到如下圖,
根據(jù)域名信息找到該js文件,可以看到其源碼。全文搜索參數(shù)params和encSecKey,果然有定位到,
從此處可以確認(rèn)這兩個(gè)參數(shù)是經(jīng)過(guò)js文件中的代碼實(shí)現(xiàn)的加密功能。接下來(lái),就是分析這個(gè)js文件,確認(rèn)加密邏輯和所加密的明文文本信息。
在js文件中,找到這么一串代碼片段(js代碼),
!function() {
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
function d(d, e, f, g) {
var h = {}
, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
window.asrsea = d,
window.ecnonasr = e
}();
和
var bVi0x = window.asrsea(JSON.stringify(i1x), bse6Y(["流淚", "強(qiáng)"]), bse6Y(Qu9l.md), bse6Y(["愛(ài)心", "女孩", "驚恐", "大笑"]));
e1x.data = j1x.cr1x({
params: bVi0x.encText,
encSecKey: bVi0x.encSecKey
})
從上面兩個(gè)js代碼片段可以一步一步地逆向分析:
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
接收一個(gè)參數(shù),可以看出a函數(shù)實(shí)現(xiàn)的功能是從大、小寫(xiě)字母和0~9數(shù)字這個(gè)范圍中隨機(jī)挑選字符組成長(zhǎng)度為參數(shù)a的隨機(jī)字符串返回。比如調(diào)用a函數(shù),a(16)即返回一個(gè)長(zhǎng)度為16的隨機(jī)字符串;
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
接收兩個(gè)參數(shù),對(duì)參數(shù)a進(jìn)行AES加密,加密模式為CBC,將結(jié)果轉(zhuǎn)為字符串形式返回;
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
接收三個(gè)參數(shù),對(duì)參數(shù)a進(jìn)行RSA加密,返回的結(jié)果是十六進(jìn)制的字符串形式;
function d(d, e, f, g) {
var h = {}, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
可以明確d函數(shù)體中的處理邏輯:
以上就是js文件中定義的完整加密處理過(guò)程,加密邏輯確認(rèn)達(dá)成。函數(shù)定義了之后,js需要調(diào)用以上函數(shù)用以生成加密后的參數(shù),這個(gè)過(guò)程的關(guān)鍵點(diǎn)在于明確加密函數(shù)d被調(diào)用時(shí)接收的4個(gè)參數(shù)值,即明文文本信息和相關(guān)。如何明確呢?F12打開(kāi)瀏覽器開(kāi)發(fā)者工具打斷點(diǎn)進(jìn)行調(diào)試。
在圖示js文件標(biāo)記的所在行打斷點(diǎn),刷新網(wǎng)易云歌曲頁(yè)面,點(diǎn)擊右側(cè)的箭頭,不斷的繼續(xù)執(zhí)行腳本,同時(shí)觀察頁(yè)面元素的加載情況(隨著不斷地往下執(zhí)行腳本,頁(yè)面元素像歌詞、評(píng)論等信息會(huì)逐個(gè)顯示出來(lái));在瀏覽器開(kāi)發(fā)者工具—控制臺(tái)中打印加密方法的4個(gè)參數(shù)JSON.stringify(i1x), bse6Y([“流淚”, “強(qiáng)”]), bse6Y(Qu9l.md), bse6Y([“愛(ài)心”, “女孩”, “驚恐”, “大笑”]),每次點(diǎn)擊繼續(xù)執(zhí)行腳本執(zhí)行到斷點(diǎn)處,都打印一次,可以看到后面的三個(gè)參數(shù)bse6Y([“流淚”, “強(qiáng)”]), bse6Y(Qu9l.md), bse6Y([“愛(ài)心”, “女孩”, “驚恐”, “大笑”])的值都是固定不變的,分別是’010001’、’00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7’、‘0CoJUm6Qyw8W8jud’,如下圖,
同時(shí)可以看到第一個(gè)參數(shù)在調(diào)試過(guò)程中不斷地發(fā)生變化,不像其他參數(shù)是固定不變的,那么如何確定其值呢?每首歌對(duì)應(yīng)的是各自的評(píng)論數(shù)據(jù),一一對(duì)應(yīng)關(guān)系,不會(huì)對(duì)不上號(hào),不會(huì)錯(cuò)號(hào),所以合理推斷參數(shù)值中應(yīng)該會(huì)有歌曲的id信息。在調(diào)試的過(guò)程中,不斷執(zhí)行腳本,不斷在斷點(diǎn)處懸停,當(dāng)頁(yè)面上的評(píng)論數(shù)據(jù)第一次出現(xiàn)時(shí),屆時(shí)打印出的console.log(JSON.stringify(i1x))值就是精彩評(píng)論接口參數(shù)加密函數(shù)被調(diào)用時(shí)傳入的正確值。經(jīng)調(diào)試得到結(jié)果,第一個(gè)參數(shù)的值是'{“rid”:”R_SO_4_139357″,”threadId”:”R_SO_4_139357″,”pageNo”:”1″,”pageSize”:”20″,”cursor”:”-1″,”offset”:”0″,”orderType”:”1″,”csrf_token”:””}’,可以看到其中“R_SO_4_139357”中的139357正是對(duì)應(yīng)歌曲的id,這也驗(yàn)證了我們之前的推測(cè)。以上就全部確定了js文件中調(diào)用加密函數(shù)d(window.asrsea()方法)時(shí)所傳入的4個(gè)參數(shù)值,即window.asrsea(‘{“rid”:”R_SO_4_139357″,”threadId”:”R_SO_4_139357″,”pageNo”:”1″,”pageSize”:”20″,”cursor”:”-1″,”offset”:”0″,”orderType”:”1″,”csrf_token”:””}’,’010001′,’00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7′,‘0CoJUm6Qyw8W8jud’)。明文文本信息和相關(guān)確認(rèn)達(dá)成。接下來(lái)就是使用Python代碼替換JavaScript重寫(xiě)上述a、b、c函數(shù)功能,并按照d函數(shù)體中的處理過(guò)程進(jìn)行調(diào)用,獲取到加密參數(shù)值,此內(nèi)容將在下一節(jié)進(jìn)行介紹,read on.
js中的a函數(shù),對(duì)應(yīng)Python實(shí)現(xiàn)如下,
import random
def generate_random_strs(length):
"""
生成固定長(zhǎng)度的字符串
:param length: 指定生成的字符串長(zhǎng)度
:return: 返回length長(zhǎng)度的字符串
"""
string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
i = 0
random_strs = ""
while i < length:
e = random.random() * len(string)
e = math.floor(e)
random_strs = random_strs + string[e]
i = i + 1
return random_strs
js中的b函數(shù)(AES加密),對(duì)應(yīng)Python實(shí)現(xiàn)如下,
from Crypto.Cipher import AES
import base64
def AESencrypt(msg,key):
"""
AES加密
:param msg:
:param key:
:return:
"""
# 查缺
padding = 16 - len(msg) % 16
# 補(bǔ)缺
msg = msg + padding * chr(padding)
# 用來(lái)加密或者解密的初始向量(必須是16位)
iv = "0102030405060708"
cipher = AES.new(key,AES.MODE_CBC,iv)
# 加密后得到的是bytes類(lèi)型的數(shù)據(jù)
encryptedbytes = cipher.encrypt(msg.encode('utf-8'))
# 使用Base64進(jìn)行編碼,返回byte字符串
encodestrs = base64.b64encode(encryptedbytes)
# 對(duì)byte字符串按utf-8進(jìn)行解碼
enctext = encodestrs.decode("utf-8")
return enctext
js中的c函數(shù)(RSA加密),對(duì)應(yīng)Python實(shí)現(xiàn)如下,
import codecs
def RSAencrypt(randomstrs,key,f):
"""
RSA加密
:param randomstrs:
:param key:
:param f:
:return:
"""
string = randomstrs[::-1]
text = bytes(string,'utf-8')
seckey = int(codecs.encode(text,encoding='hex'),16) ** int(key,16) % int(f,16)
return format(seckey,'x').zfill(256)
接下來(lái),我們只要按照js d函數(shù)的處理邏輯對(duì)generate_random_strs(length)、AESencrypt(msg,key)、RSAencrypt(randomstrs,key,f)函數(shù)依次調(diào)用,即可獲得加密參數(shù)值。在上一節(jié)的最后部分,已經(jīng)確定了js加密函數(shù)d被調(diào)用時(shí)所接收的參數(shù)值,其中3個(gè)是固定不變的值,第1個(gè)參數(shù)是含有歌曲id的固定格式的值,用Python拼裝一下,獲取加密參數(shù)的代碼如下,
def get_params(song_id):
"""
獲取加密參數(shù)encText,encSecKey
:params song_id: str
:return:
"""
# 用于獲取某首歌的評(píng)論數(shù)據(jù)
msg = '{"rid":"R_SO_4_' + song_id + '","threadId":"R_SO_4_' + song_id + '","pageNo":"1","pageSize":"20","cursor":"-1","offset":"0","orderType":"1","csrf_token":""}'
key = '0CoJUm6Qyw8W8jud'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
e = '010001'
enctext = AESencrypt(msg,key)
i = generate_random_strs(16)
encText = AESencrypt(enctext,i)
encSecKey = RSAencrypt(i,e,f)
return encText,encSecKey
調(diào)用get_params(song_id),最終返回加密參數(shù)值encText,encSecKey。
根據(jù)上一節(jié)中返回的加密參數(shù)值進(jìn)行接口請(qǐng)求,最終獲取歌曲精彩評(píng)論數(shù)據(jù),代碼實(shí)現(xiàn)如下,
import requests
from utils import get_params
from get_song_id import search_song_id
from get_song_lyric import get_lyric
song_name = input("請(qǐng)輸入歌曲名:")
song_id = search_song_id(song_name)
# 返回的是數(shù)字類(lèi)型,轉(zhuǎn)成字符串類(lèi)型
song_id = str(song_id)
print("查找到的歌曲id:",song_id)
if song_id:
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
params = get_params(song_id)
data = {"params":params[0],"encSecKey":params[1]}
resp = requests.post(url=url,data=data)
result = resp.json()
hot_comments = result['data']['hotComments']
if hot_comments:
hot_comments = [{"content":hot['content'],"timeStr":hot['timeStr'],"likedCount":hot['likedCount'],"user":hot['user']['nickname'],"avatarUrl":hot['user']['avatarUrl']} for hot in hot_comments]
for hot in hot_comments:
print(hot)
else:
print("該歌曲暫無(wú)熱評(píng)!")
else:
print("未查到該歌曲!")
本文提供了一個(gè)完整的教程,教讀者如何使用Python來(lái)獲取網(wǎng)易云音樂(lè)中特定歌曲的熱評(píng)和歌詞,包括詳細(xì)的代碼實(shí)現(xiàn)和步驟說(shuō)明。
目的:開(kāi)發(fā)一個(gè)Python程序,用于獲取網(wǎng)易云音樂(lè)中特定歌曲的精彩評(píng)論和歌詞。
功能:
項(xiàng)目地址:https://gitee.com/shawn_chen_rtz/netease_cloud_music
環(huán)境準(zhǔn)備:需要安裝requests和pycrypto模塊,可通過(guò)pip命令安裝。
獲取歌曲id:通過(guò)歌曲名搜索,獲取歌曲id。使用網(wǎng)易云音樂(lè)的搜索API http://music.163.com/api/search/get。
獲取歌曲歌詞:使用歌曲id調(diào)用歌詞API http://music.163.com/api/song/lyric?id=song_id&lv=1&tv=-1,替換其中的song_id。
獲取歌曲精彩評(píng)論:
結(jié)果展示:
文章轉(zhuǎn)自微信公眾號(hào)@仰望天空的蝸牛
GraphQL API滲透測(cè)試指南
Python + BaiduTransAPI :快速檢索千篇英文文獻(xiàn)(附源碼)
掌握ChatGPT API集成的方便指南
node.js + express + docker + mysql + jwt 實(shí)現(xiàn)用戶(hù)管理restful api
nodejs + mongodb 編寫(xiě) restful 風(fēng)格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創(chuàng)建REST API
使用 Django 和 Django REST 框架構(gòu)建 RESTful API:實(shí)現(xiàn) CRUD 操作
ASP.NET Web API快速入門(mén)介紹
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)