XVIT

Jul 14, 2026

蓝奏盘接口分析

1. 接口分析

1.1. 所有的URI

url method desc
https://accounts.woozooo.com/accounts.php GET/POST 登录用的接口
https://pc.woozooo.com/mydisk.php GET 用户登录页面数据接口 - html
https://pc.woozooo.com/doupload.php POST 基本所有的操作都需要用到这个接口
https://pc.woozooo.com/html5up.php POST 文件上传操作用这个接口

1.2. 认证数据分析

蓝奏云盘采用的是有状态的认证方式,通过acw_tccdn_sec_tcacw_sc__v2yloginsyloginphpdisk_infouag等cookie来维持登录状态。缺一不可并且有获取的先后顺序否则有部分参数无法获取到。

接下来我们分析一下登录接口获取需要的状态,这里我使用的抓包工具是Reqable,使用FiddlerCharles也可以。首先我们在登录页面输入账号密码登录,抓包分析。

蓝奏盘接口分析1

在这里我们可以分析得到https://accounts.woozooo.com/accounts.php通过GET请求可以得到我们需要的acw_tccdn_sec_tc,但是我们发现登录成功的时候不只是有这两个,还有一个叫acw_sc__v2,这个参数是通过我们请求网页返回的代码生成的,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<html>
<script>
var arg1 = 'ABB6C15E1A140E5C7773CC359857519FB5AAB082';
(function(a, c) {
var G = a0j,
d = a();
while (!![]) {
try {
var e = -parseInt(G(0x117)) / 0x1 * (parseInt(G(0x111)) / 0x2) + -parseInt(G(0xfb)) / 0x3 * (parseInt(G(0x10e)) / 0x4) + -parseInt(G(0x101)) / 0x5 * (-parseInt(G(0xfd)) / 0x6) + -parseInt(G(0x102)) / 0x7 * (parseInt(G(0x122)) / 0x8) + parseInt(G(0x112)) / 0x9 + parseInt(G(0x11d)) / 0xa * (parseInt(G(0x11c)) / 0xb) + parseInt(G(0x114)) / 0xc;
if (e === c) break;
else d['push'](d['shift']());
} catch (f) {
d['push'](d['shift']());
}
}
}(a0i, 0x760bf), !(function() {
var L = a0j,
j = (function() {
var B = !![];
return function(C, D) {
var E = B ? function() {
var H = a0j;
if (D) {
var F = D[H(0x10d)](C, arguments);
return D = null, F;
}
} : function() {};
return B = ![], E;
};
}()),
k = j(this, function() {
var I = a0j;
return k[I(0xff)]()[I(0x123)](I(0x10f))[I(0xff)]()[I(0x107)](k)[I(0x123)](I(0x10f));
});
k();
var l = (function() {
var B = !![];
return function(C, D) {
var E = B ? function() {
var J = a0j;
if (D) {
var F = D[J(0x10d)](C, arguments);
return D = null, F;
}
} : function() {};
return B = ![], E;
};
}());
(function() {
l(this, function() {
var K = a0j,
B = new RegExp(K(0x118)),
C = new RegExp(K(0x106), 'i'),
D = b(K(0x100));
!B[K(0x104)](D + K(0x105)) || !C[K(0x104)](D + K(0x11b)) ? D('0') : b();
})();
}());
for (var m = [0xf, 0x23, 0x1d, 0x18, 0x21, 0x10, 0x1, 0x26, 0xa, 0x9, 0x13, 0x1f, 0x28, 0x1b, 0x16, 0x17, 0x19, 0xd, 0x6, 0xb, 0x27, 0x12, 0x14, 0x8, 0xe, 0x15, 0x20, 0x1a, 0x2, 0x1e, 0x7, 0x4, 0x11, 0x5, 0x3, 0x1c, 0x22, 0x25, 0xc, 0x24], p = L(0x115), q = [], u = '', v = '', w = L(0x116), x = 0x0; x < arg1[w]; x++)
for (var y = arg1[x], z = 0x0; z < m[w]; z++) m[z] == x + 0x1 && (q[z] = y);
for (u = q[L(0xfc)](''), x = 0x0; x < u[w] && x < p[w]; x += 0x2) {
var A = (parseInt(u[L(0x11a)](x, x + 0x2), 0x10) ^ parseInt(p[L(0x11a)](x, x + 0x2), 0x10))[L(0xff)](0x10);
0x1 == A[w] && (A = '0' + A), v += A;
}
document[L(0x121)] = 'acw_sc__v2=' + v + L(0x120) + new Date(Date[L(0x119)]() + 0x36ee80)[L(0x10c)]() + L(0x109), document[L(0xfe)][L(0x103)]();
}()));
function a0i() {
var N = ['mJKZmgTStNvVyq', 'C3rYAw5N', 'y2fSBa', 'o2v4CgLYzxm9', 'y29VA2LL', 'mteZmZy3mNLbu2PszW', 'C2vHCMnO', 'D2HPBguGkhrYDwuPihT9', 'mJq1ndi0rKHuthnj', 'AM9PBG', 'nNH1rKHOuq', 'Bg9JyxrPB24', 'Dg9tDhjPBMC', 'Aw5PDa', 'mJi2odi1nwnMre1IyG', 'n0HxChPJva', 'CMvSB2fK', 'DgvZDa', 'y2HHAw4', 'xcTCkYaQkd86w2eTEKeTwL8KxvSWltLHlxPblvPFjf0Qkq', 'y29UC3rYDwn0B3i', 'y291BNrLCG', 'o21HEc1Hz2u9mZyWmdTWyxrOps87', 'zgvIDq', 'ywn0Aw9U', 'Dg9htvrtDhjPBMC', 'yxbWBhK', 'mJbiru1MChi', 'kcGOlISPkYKRksSK', 'z2DLCG', 'nKHJq01Aqq', 'nJe5nZu5ogH1twPUDa', 'C3rHDgvpyMPLy3q', 'mZu5mdu5mNbcB2Pxyq', 'mZaWmde3nJaWmdG1nJaWnJa2mtuWmtuZmZaWmZy5mdaYnZGWmdm3nq', 'BgvUz3rO', 'mtqWnti2shvUBNDv', 'zNvUy3rPB24GkLWOicPCkq', 'BM93', 'C2XPy2u', 'Aw5WDxq', 'ntm5BwrLuMXi'];
a0i = function() {
return N;
};
return a0i();
}
function a0j(a, b) {
var c = a0i();
return a0j = function(d, e) {
d = d - 0xfb;
var f = c[d];
if (a0j['tGHEKR'] === undefined) {
var g = function(l) {
var m = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';
var n = '',
o = '',
p = n + g;
for (var q = 0x0, r, s, t = 0x0; s = l['charAt'](t++); ~s && (r = q % 0x4 ? r * 0x40 + s : s, q++ % 0x4) ? n += p['charCodeAt'](t + 0xa) - 0xa !== 0x0 ? String['fromCharCode'](0xff & r >> (-0x2 * q & 0x6)) : q : 0x0) {
s = m['indexOf'](s);
}
for (var u = 0x0, v = n['length']; u < v; u++) {
o += '%' + ('00' + n['charCodeAt'](u)['toString'](0x10))['slice'](-0x2);
}
return decodeURIComponent(o);
};
a0j['CrMtTV'] = g, a = arguments, a0j['tGHEKR'] = !![];
}
var h = c[0x0],
i = d + h,
j = a[i];
if (!j) {
var k = function(l) {
this['jyamLv'] = l, this['WxwaRR'] = [0x1, 0x0, 0x0], this['GuwnVk'] = function() {
return 'newState';
}, this['CxWuMi'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*', this['FxAgTK'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';
};
k['prototype']['dQvDam'] = function() {
var l = new RegExp(this['CxWuMi'] + this['FxAgTK']),
m = l['test'](this['GuwnVk']['toString']()) ? --this['WxwaRR'][0x1] : --this['WxwaRR'][0x0];
return this['fQYikn'](m);
}, k['prototype']['fQYikn'] = function(l) {
if (!Boolean(~l)) return l;
return this['Hbwxmd'](this['jyamLv']);
}, k['prototype']['Hbwxmd'] = function(l) {
for (var m = 0x0, n = this['WxwaRR']['length']; m < n; m++) {
this['WxwaRR']['push'](Math['round'](Math['random']())), n = this['WxwaRR']['length'];
}
return l(this['WxwaRR'][0x0]);
}, new k(a0j)['dQvDam'](), f = a0j['CrMtTV'](f), a[i] = f;
} else f = j;
return f;
}, a0j(a, b);
}
function b(a) {
function c(d) {
var M = a0j;
if (typeof d === M(0x11e)) return function(e) {} [M(0x107)](M(0x124))[M(0x10d)](M(0x108));
else('' + d / d)[M(0x116)] !== 0x1 || d % 0x14 === 0x0 ? function() {
return !![];
} [M(0x107)](M(0x10a) + M(0x110))[M(0x11f)](M(0x10b)) : function() {
return ![];
} [M(0x107)](M(0x10a) + M(0x110))[M(0x10d)](M(0x113));
c(++d);
}
try {
if (a) return c;
else c(0x0);
} catch (d) {}
}
</script>
</html>

这一坨代码我们很清楚看到是混淆过的代码,反混淆之后主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var arg1 = '63DDDA15A534525421670FE294D75EE234A736D3';

// ========== 核心算法 ==========
// 置换表(40 个元素,用来打乱 arg1 的字符顺序)
var m = [
15, 35, 29, 24, 33, 16, 1, 38, 10, 9,
19, 31, 40, 27, 22, 23, 25, 13, 6, 11,
39, 18, 20, 8, 14, 21, 32, 26, 2, 30,
7, 4, 17, 5, 3, 28, 34, 37, 12, 36
];

// 固定密钥(40 位十六进制)
var p = '3000176000856006061501533003690027800375';

var q = [];

// 按置换表 m 重排 arg1 的字符
for (var x = 0; x < arg1.length; x++) {
for (var z = 0; z < m.length; z++) {
if (m[z] === x + 1) {
q[z] = arg1[x];
}
}
}
var u = q.join(''); // 重排后的字符串

// 逐字节与密钥 p 做 XOR
var v = '';
for (var x = 0; x < u.length && x < p.length; x += 2) {
var val = parseInt(u.slice(x, x + 2), 16) ^ parseInt(p.slice(x, x + 2), 16);
var hex = val.toString(16);
if (hex.length === 1) hex = '0' + hex;
v += hex;
}

console.log('acw_sc__v2=' + v)

python版本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def sign(args):
m = [
15, 35, 29, 24, 33, 16, 1, 38, 10, 9,
19, 31, 40, 27, 22, 23, 25, 13, 6, 11,
39, 18, 20, 8, 14, 21, 32, 26, 2, 30,
7, 4, 17, 5, 3, 28, 34, 37, 12, 36
]
key = '3000176000856006061501533003690027800375'

q = [""] * len(m)
for i in range(len(args)):
target = i + 1
for j in range(len(m)):
if(m[j] == target):
q[j] = args[i]

u = "".join(q)

v = ""
for x in range(0, min(len(u), len(key)), 2):
val = int(u[x:x+2], 16) ^ int(key[x:x+2], 16)
hex_str = format(val, '02x')
v += hex_str
return v

接下来我们就可以调用登录接口来登录获取剩下的cookie了,接口参数如下:

蓝奏盘接口分析2

如果返回图片中的数据即为成功登录。到目前为止我们就已经实现了蓝奏盘的登录,但是并不包括访问权限,所以我们接下来还需要获取权限的cookie。在我开发爬虫的时候分析发现我们可以在这里获取到ylogin这个参数,其他的需要继续往下分析。这时候我们在请求中得到的那一坨的链接就是我们获取yloginuagphpdisk_info的方法。

蓝奏盘接口分析3

到这里为止我们登录和权限的状态cookie就获取完毕了,后续在增、删、改、查的任何操作中都需要携带这个cookie去请求接口即可。下面为磁盘中文件操作的所有参数配置:

param desc method
task=47&folder_id=-1 获取个根目录下所有的文件夹 POST
task=18&folder_id={folder_id} 获取文件夹访问密码 POST
task=5&folder_id={folder_id}&pg={size} 获取对应文件夹下的所有文件 POST
task=22&file_id={file_id} 获取对应文件的密码和文件id,会返回蓝奏自定义的域名拼装后可以使用自带的下载 POST
task=23&file_id={file_id}&shows=1&shownames={pwd} 根据文件的id给对应的文件设置密码 POST
task=4&folder_id={folder_id}&folder_name={folder_name}&folder_description={desc} 根据文件夹ID给文件夹设置名字和简介 POST
task=16&folder_id={folder_id}&shows=1&shownames={pwd} 通过文件夹ID设置文件夹的密码 POST
task=2&parent_id=0&folder_name={folder_name}&folder_description={desc} 在根目录下创建文件夹 - 文件夹名字+简介 POST
task=3&folder_id={folder_id} 根据文件夹ID删除对应的文件夹 POST
task=6&file_id={file_id} 根据文件id删除对应的文件 POST

此接口为蓝奏盘的文件上传接口,文件只需要使用POST接口上传文件的二进制数据即可。

关于文件操作的接口暂时只分享这些,关于其他的接口可以自行去抓包分析一下。但是切记不可大批量请求导致对方服务器崩溃,否则对应的法律责任由您本人承担。

2. 总结

蓝奏云盘不同于其他的大厂采用前后分离的技术,而是采用传统的php+html混合写的,所以很多的内容需要正则去处理获取对应的数据,但是今天我们的接口都是返回了JSON的数据,极大的简化了开发的难度。我们可以使用这些接口制作自己的UI,同时这样也可以绕过他们强制规定的100MB文件大小限制,同时也可以绕过他们对于上传文件格式的限制。

3. 代码示例

浅浅贴一个代码供参考学习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import re
from utils import sign, open_json, get_req, post_req

jdp = open_json("config.json")

"""
请求页面拿到arg1和acw_sc__v2
"""
res = get_req(jdp["login_url"], None)
cookies = res.cookies
txt = res.text
arg = re.search(r"arg1='([^']+)'", txt).group(1) # 拿到arg1

"""
调用函数生成acw_sc__v2
"""
acw_sc__v2 = sign(arg)
acw_tc = cookies['acw_tc'] # acw_tc cookie
cdn_sec_tc = cookies['cdn_sec_tc'] # cdn_sec_tc cookie

headers = {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": f"acw_tc={acw_tc}; cdn_sec_tc={cdn_sec_tc}; acw_sc__v2={acw_sc__v2}"
}
param = "task=uselogin&username=xxxx&password=xxxxxx"
login = post_req(jdp["login_url"], headers=headers, data=param)
ylogins = login.cookies["ylogins"] # ylogins cookie
msg_url = login.json()["msgs"]

cookies_a = get_req(msg_url, headers=headers).cookies
uag = cookies_a["uag"] # uag cookie
ylogin = cookies_a["ylogin"] # ylogin cookie
phpdisk_info = cookies_a["phpdisk_info"] # phpdisk_info cookie
headers["Cookie"] = f"acw_tc={acw_tc}; cdn_sec_tc={cdn_sec_tc}; acw_sc__v2={acw_sc__v2}; ylogin={ylogin}; ylogins={ylogins}; phpdisk_info={phpdisk_info}; uag={uag}"

"""
获取文件夹列表
"""
def get_dir_list(doupload_url, uid):
res = post_req(f"{doupload_url}?uid={uid}", jdp["datas"][0]["folder_data"], headers=headers).json()
folder_list = [
{
"folder_name": item["name"],
"folder_id": item["fol_id"],
"folder_desc": item["folder_des"]
}
for item in res["text"]
]
return folder_list

print(get_dir_list(jdp["doupload"], ylogin))

config.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"login_url": "https://accounts.woozooo.com/accounts.php",
"disk_url": "https://pc.woozooo.com/mydisk.php",
"doupload": "https://pc.woozooo.com/doupload.php",
"fileUpload": "https://pc.woozooo.com/html5up.php",
"datas": [
{ "folder_data": "task=47&folder_id=-1" },
{ "folder_pwd": "task=18&folder_id={folder_id}" },
{ "folder_files": "task=5&folder_id={folder_id}&pg={size}" },
{ "file_info": "task=22&file_id={file_id}" },
{ "set_file_pwd": "task=23&file_id={file_id}&shows=1&shownames={pwd}" },
{ "set_folder_desc": "task=4&folder_id={folder_id}&folder_name={folder_name}&folder_description={desc}" },
{ "set_folder_pwd": "task=16&folder_id={folder_id}&shows=1&shownames={pwd}" },
{ "create_folder": "task=2&parent_id=0&folder_name={folder_name}&folder_description={desc}" },
{ "del_folder": "task=3&folder_id={folder_id}" },
{ "del_file": "task=6&file_id={file_id}" }
]
}

utils.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import requests
import json

"""
acw_sc__v2生成函数
"""
def sign(args):
m = [
15, 35, 29, 24, 33, 16, 1, 38, 10, 9,
19, 31, 40, 27, 22, 23, 25, 13, 6, 11,
39, 18, 20, 8, 14, 21, 32, 26, 2, 30,
7, 4, 17, 5, 3, 28, 34, 37, 12, 36
]
key = '3000176000856006061501533003690027800375'

q = [""] * len(m)
for i in range(len(args)):
target = i + 1
for j in range(len(m)):
if(m[j] == target):
q[j] = args[i]

u = "".join(q)

v = ""
for x in range(0, min(len(u), len(key)), 2):
val = int(u[x:x+2], 16) ^ int(key[x:x+2], 16)
hex_str = format(val, '02x')
v += hex_str
return v

"""
打开本地json
"""
def open_json(file_path):
with open(file_path, "r", encoding="utf-8") as f:
return json.load(f)

"""
request_get
"""
def get_req(url, headers):
return requests.get(url, headers=headers)

"""
request_post
"""
def post_req(url, data, headers):
return requests.post(url, headers=headers, data=data)

这里的代码仅供参考

OLDER >