

采集内容
- 插件端返回的code信息,如图所示

找接口

- 通过测试可以发现,该请求有两个必要参数
-
access-token
-
cb-user-id
-
参数破解
Access-toke
-
通过分析插件源码,该源码由webpack打包而成
-
通过debugger, 找到函数入口点,找到对应使用的加密模块即可完成破解
-
函数入口点找寻如下:
- 全局搜索 access-token,下断点

- 通过断点,可以发现, token生成主逻辑为:S((new Date).toISOString().substr(0, 16) + e).toString()

- 往下单步调试可以发现,S方法为调用的加密方法 _createHelper, 该方法其实为魔改后的md5方法

- 往上找S方法的初始定义位置

- 可以发现是典型的webpack打包后的调用方式,跟进 n 找webpack加载器

- 找到加载器后,将加载器取出,在本地运行补全相应的模块就好了,补全代码如下:
!function (e) {
t = {};
function n(o) {
var r = t[o];
if (void 0 !== r)
return r.exports;
var i = t[o] = {
exports: {}
};
return e[o].call(i.exports, i, i.exports, n),
i.exports
};
n(8214);
}({
2480: ()=>{},
8214: function (e, t, n) {
var o;
e.exports = (o = n(8249),
function (e) {
var t = o
, n = t.lib
, r = n.WordArray
, i = n.Hasher
, a = t.algo
, l = [];
!function () {
for (var t = 0; t < 64; t++)
l[t] = 4294967296 * e.abs(e.sin(t + 1)) | 0
}();
var s = a.MD5 = i.extend({
_doReset: function () {
this._hash = new r.init([1732584193, 4023233417, 2562383102, 271733878])
},
_doProcessBlock: function (e, t) {
for (var n = 0; n < 16; n++) {
var o = t + n
, r = e[o];
e[o] = 16711935 & (r << 8 | r >>> 24) | 4278255360 & (r << 24 | r >>> 8)
}
var i = this._hash.words
, a = e[t + 0]
, s = e[t + 1]
, f = e[t + 2]
, m = e[t + 3]
, h = e[t + 4]
, w = e[t + 5]
, g = e[t + 6]
, v = e[t + 7]
, y = e[t + 8]
, C = e[t + 9]
, A = e[t + 10]
, b = e[t + 11]
, E = e[t + 12]
, H = e[t + 13]
, S = e[t + 14]
, x = e[t + 15]
, P = i[0]
, I = i[1]
, T = i[2]
, k = i[3];
P = c(P, I, T, k, a, 7, l[0]),
k = c(k, P, I, T, s, 12, l[1]),
T = c(T, k, P, I, f, 17, l[2]),
I = c(I, T, k, P, m, 22, l[3]),
P = c(P, I, T, k, h, 7, l[4]),
k = c(k, P, I, T, w, 12, l[5]),
T = c(T, k, P, I, g, 17, l[6]),
I = c(I, T, k, P, v, 22, l[7]),
P = c(P, I, T, k, y, 7, l[8]),
k = c(k, P, I, T, C, 12, l[9]),
T = c(T, k, P, I, A, 17, l[10]),
I = c(I, T, k, P, b, 22, l[11]),
P = c(P, I, T, k, E, 7, l[12]),
k = c(k, P, I, T, H, 12, l[13]),
T = c(T, k, P, I, S, 17, l[14]),
P = u(P, I = c(I, T, k, P, x, 22, l[15]), T, k, s, 5, l[16]),
k = u(k, P, I, T, g, 9, l[17]),
T = u(T, k, P, I, b, 14, l[18]),
I = u(I, T, k, P, a, 20, l[19]),
P = u(P, I, T, k, w, 5, l[20]),
k = u(k, P, I, T, A, 9, l[21]),
T = u(T, k, P, I, x, 14, l[22]),
I = u(I, T, k, P, h, 20, l[23]),
P = u(P, I, T, k, C, 5, l[24]),
k = u(k, P, I, T, S, 9, l[25]),
T = u(T, k, P, I, m, 14, l[26]),
I = u(I, T, k, P, y, 20, l[27]),
P = u(P, I, T, k, H, 5, l[28]),
k = u(k, P, I, T, f, 9, l[29]),
T = u(T, k, P, I, v, 14, l[30]),
P = p(P, I = u(I, T, k, P, E, 20, l[31]), T, k, w, 4, l[32]),
k = p(k, P, I, T, y, 11, l[33]),
T = p(T, k, P, I, b, 16, l[34]),
I = p(I, T, k, P, S, 23, l[35]),
P = p(P, I, T, k, s, 4, l[36]),
k = p(k, P, I, T, h, 11, l[37]),
T = p(T, k, P, I, v, 16, l[38]),
I = p(I, T, k, P, A, 23, l[39]),
P = p(P, I, T, k, H, 4, l[40]),
k = p(k, P, I, T, a, 11, l[41]),
T = p(T, k, P, I, m, 16, l[42]),
I = p(I, T, k, P, g, 23, l[43]),
P = p(P, I, T, k, C, 4, l[44]),
k = p(k, P, I, T, E, 11, l[45]),
T = p(T, k, P, I, x, 16, l[46]),
P = d(P, I = p(I, T, k, P, f, 23, l[47]), T, k, a, 6, l[48]),
k = d(k, P, I, T, v, 10, l[49]),
T = d(T, k, P, I, S, 15, l[50]),
I = d(I, T, k, P, w, 21, l[51]),
P = d(P, I, T, k, E, 6, l[52]),
k = d(k, P, I, T, m, 10, l[53]),
T = d(T, k, P, I, A, 15, l[54]),
I = d(I, T, k, P, s, 21, l[55]),
P = d(P, I, T, k, y, 6, l[56]),
k = d(k, P, I, T, x, 10, l[57]),
T = d(T, k, P, I, g, 15, l[58]),
I = d(I, T, k, P, H, 21, l[59]),
P = d(P, I, T, k, h, 6, l[60]),
k = d(k, P, I, T, b, 10, l[61]),
T = d(T, k, P, I, f, 15, l[62]),
I = d(I, T, k, P, C, 21, l[63]),
i[0] = i[0] + P | 0,
i[1] = i[1] + I | 0,
i[2] = i[2] + T | 0,
i[3] = i[3] + k | 0
},
_doFinalize: function () {
var t = this._data
, n = t.words
, o = 8 * this._nDataBytes
, r = 8 * t.sigBytes;
n[r >>> 5] |= 128 << 24 - r % 32;
var i = e.floor(o / 4294967296)
, a = o;
n[15 + (r + 64 >>> 9 << 4)] = 16711935 & (i << 8 | i >>> 24) | 4278255360 & (i << 24 | i >>> 8),
n[14 + (r + 64 >>> 9 << 4)] = 16711935 & (a << 8 | a >>> 24) | 4278255360 & (a << 24 | a >>> 8),
t.sigBytes = 4 * (n.length + 1),
this._process();
for (var l = this._hash, s = l.words, c = 0; c < 4; c++) {
var u = s[c];
s[c] = 16711935 & (u << 8 | u >>> 24) | 4278255360 & (u << 24 | u >>> 8)
}
return l
},
clone: function () {
var e = i.clone.call(this);
return e._hash = this._hash.clone(),
e
}
});
function c(e, t, n, o, r, i, a) {
var l = e + (t & n | ~t & o) + r + a;
return (l << i | l >>> 32 - i) + t
}
function u(e, t, n, o, r, i, a) {
var l = e + (t & o | n & ~o) + r + a;
return (l << i | l >>> 32 - i) + t
}
function p(e, t, n, o, r, i, a) {
var l = e + (t ^ n ^ o) + r + a;
return (l << i | l >>> 32 - i) + t
}
function d(e, t, n, o, r, i, a) {
var l = e + (n ^ (t | ~o)) + r + a;
return (l << i | l >>> 32 - i) + t
}
t.MD5 = i._createHelper(s),
t.HmacMD5 = i._createHmacHelper(s),
global.generate_token = i._createHelper(s)
}(Math),
o.MD5)
},
8249: function (e, t, n) {
var o;
e.exports = (o = o || function (e, t) {
var o;
if ("undefined" != typeof window && window.crypto && (o = window.crypto),
"undefined" != typeof self && self.crypto && (o = self.crypto),
"undefined" != typeof globalThis && globalThis.crypto && (o = globalThis.crypto),
!o && "undefined" != typeof window && window.msCrypto && (o = window.msCrypto),
!o && void 0 !== n.g && n.g.crypto && (o = n.g.crypto),
!o)
try {
o = n(2480)
} catch (e) {
}
var r = function () {
if (o) {
if ("function" == typeof o.getRandomValues)
try {
return o.getRandomValues(new Uint32Array(1))[0]
} catch (e) {
}
if ("function" == typeof o.randomBytes)
try {
return o.randomBytes(4).readInt32LE()
} catch (e) {
}
}
throw new Error("Native crypto module could not be used to get secure random number.")
}
, i = Object.create || function () {
function e() {
}
return function (t) {
var n;
return e.prototype = t,
n = new e,
e.prototype = null,
n
}
}()
, a = {}
, l = a.lib = {}
, s = l.Base = {
extend: function (e) {
var t = i(this);
return e && t.mixIn(e),
t.hasOwnProperty("init") && this.init !== t.init || (t.init = function () {
t.$super.init.apply(this, arguments)
}
),
t.init.prototype = t,
t.$super = this,
t
},
create: function () {
var e = this.extend();
return e.init.apply(e, arguments),
e
},
init: function () {
},
mixIn: function (e) {
for (var t in e)
e.hasOwnProperty(t) && (this[t] = e[t]);
e.hasOwnProperty("toString") && (this.toString = e.toString)
},
clone: function () {
return this.init.prototype.extend(this)
}
}
, c = l.WordArray = s.extend({
init: function (e, t) {
e = this.words = e || [],
this.sigBytes = null != t ? t : 4 * e.length
},
toString: function (e) {
return (e || p).stringify(this)
},
concat: function (e) {
var t = this.words
, n = e.words
, o = this.sigBytes
, r = e.sigBytes;
if (this.clamp(),
o % 4)
for (var i = 0; i < r; i++) {
var a = n[i >>> 2] >>> 24 - i % 4 * 8 & 255;
t[o + i >>> 2] |= a << 24 - (o + i) % 4 * 8
}
else
for (var l = 0; l < r; l += 4)
t[o + l >>> 2] = n[l >>> 2];
return this.sigBytes += r,
this
},
clamp: function () {
var t = this.words
, n = this.sigBytes;
t[n >>> 2] &= 4294967295 << 32 - n % 4 * 8,
t.length = e.ceil(n / 4)
},
clone: function () {
var e = s.clone.call(this);
return e.words = this.words.slice(0),
e
},
random: function (e) {
for (var t = [], n = 0; n < e; n += 4)
t.push(r());
return new c.init(t, e)
}
})
, u = a.enc = {}
, p = u.Hex = {
stringify: function (e) {
for (var t = e.words, n = e.sigBytes, o = [], r = 0; r < n; r++) {
var i = t[r >>> 2] >>> 24 - r % 4 * 8 & 255;
o.push((i >>> 4).toString(16)),
o.push((15 & i).toString(16))
}
return o.join("")
},
parse: function (e) {
for (var t = e.length, n = [], o = 0; o < t; o += 2)
n[o >>> 3] |= parseInt(e.substr(o, 2), 16) << 24 - o % 8 * 4;
return new c.init(n, t / 2)
}
}
, d = u.Latin1 = {
stringify: function (e) {
for (var t = e.words, n = e.sigBytes, o = [], r = 0; r < n; r++) {
var i = t[r >>> 2] >>> 24 - r % 4 * 8 & 255;
o.push(String.fromCharCode(i))
}
return o.join("")
},
parse: function (e) {
for (var t = e.length, n = [], o = 0; o < t; o++)
n[o >>> 2] |= (255 & e.charCodeAt(o)) << 24 - o % 4 * 8;
return new c.init(n, t)
}
}
, f = u.Utf8 = {
stringify: function (e) {
try {
return decodeURIComponent(escape(d.stringify(e)))
} catch (e) {
throw new Error("Malformed UTF-8 data")
}
},
parse: function (e) {
return d.parse(unescape(encodeURIComponent(e)))
}
}
, m = l.BufferedBlockAlgorithm = s.extend({
reset: function () {
this._data = new c.init,
this._nDataBytes = 0
},
_append: function (e) {
"string" == typeof e && (e = f.parse(e)),
this._data.concat(e),
this._nDataBytes += e.sigBytes
},
_process: function (t) {
var n, o = this._data, r = o.words, i = o.sigBytes, a = this.blockSize, l = i / (4 * a),
s = (l = t ? e.ceil(l) : e.max((0 | l) - this._minBufferSize, 0)) * a, u = e.min(4 * s, i);
if (s) {
for (var p = 0; p < s; p += a)
this._doProcessBlock(r, p);
n = r.splice(0, s),
o.sigBytes -= u
}
return new c.init(n, u)
},
clone: function () {
var e = s.clone.call(this);
return e._data = this._data.clone(),
e
},
_minBufferSize: 0
})
, h = (l.Hasher = m.extend({
cfg: s.extend(),
init: function (e) {
this.cfg = this.cfg.extend(e),
this.reset()
},
reset: function () {
m.reset.call(this),
this._doReset()
},
update: function (e) {
return this._append(e),
this._process(),
this
},
finalize: function (e) {
return e && this._append(e),
this._doFinalize()
},
blockSize: 16,
_createHelper: function (e) {
return function (t, n) {
return new e.init(n).finalize(t)
}
},
_createHmacHelper: function (e) {
return function (t, n) {
return new h.HMAC.init(e, n).finalize(t)
}
}
}),
a.algo = {});
return a
}(Math),
o)
}
})
function I(e) {
if (typeof e === "string" && e.length <= 64) {
console.log((new Date()).toISOString().substr(0, 16) + e)
return global.generate_token((new Date()).toISOString().substr(0, 16) + e).toString();
} else {
return "";
}
}
```7.
传入参数,即可得到加密后的结果

8. 传入的参数为 cb-user-id
## Cb-user-id
- 该参数由服务器端返回,需要结合ip信息,浏览器请求头,时区偏移量进行请求即可获得
- 该参数在插件装到浏览器上后会自动发起请求进行获取,抓包即可获得
- 接口信息记录如下, 该请求无需带参数和cookie:
```Plain
GET https://smart.couponbirds.com/v1/commonLog/uniqueId HTTP/1.1
Host: smart.couponbirds.com
Connection: keep-alive
cb-type: default
cb-app-version-num: 40120
cb-user-id: null
cb-default-user-id: null
cb-ads-default-visitor-id: null
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
cb-app-type: chrome
cb-default-visitor-id: null
cb-app-version: 4.1.20
cb-baseBrowserInfo: {"browserOs":"Win32","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","browserVersion":"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36","hardwareConcurrency":8,"language":"en-US","screenResolution":null,"screenColorDepth":null,"timezone":0}
Accept: */*
Sec-Fetch-Site: none
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9
```-
请求结果如下:

# 风控检测
1. couponbirds 单个代理只能生成3-4个用户id,请求多了会返回429
2. 采集时可以用一个用户id, 多个代理切换采集
3. couponbirds 对代理质量要求较高,生成用户的接口有cf防护,该处猜测会过滤大量ip
4. 时区偏移量,最好和ip地区一致,该参数偶尔会出现影响,还需校验测试
5. 单个用户可采集量为:200-300个,到达限制后,不封账号,但是会返回空数据,第二天可解除限制