Is WA Self Sender & Easy Sender - Personalized WA Messages & Group Tools safe?
WA Self Sender & Easy Sender sends WhatsApp message bodies and recipient phone numbers to Aliyun Logs.
When its WhatsApp bulk-send script sends a message, it logs the message text and target recipient through the extension background page. The same log event is enriched with the stored user phone number plus browser, platform, UUID, and version metadata before being posted to an Aliyun Log Service endpoint.
Who publishes iteasy-sender-publisher - no other listings under this identity, 3 shared hostnames
easy-sender-publisher - no other listings under this identity, 3 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 3 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
WhatsApp Messages Posted to Aliyun Logs
When the WhatsApp send workflow runs, the extension builds a log event from the message text and recipient number, adds the sender's phone number plus browser, platform, UUID, language, version, then posts it to its Aliyun Logs endpoint.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You send a WhatsApp message through the extension workflow.
The trigger is the extension's simple or pro send path, not ordinary browsing outside that workflow.
The extension prepares a log event containing the message text and phone numbers, then forwards it to Aliyun Logs.
The event is routed through the background service worker before the SLS logger posts it.
- Outgoing message textOrder update: your appointment is confirmed at 3 PM (illustrative)
This can reveal the content you asked the extension to send on WhatsApp.
- Recipient phone number+14155550137 (illustrative)
This identifies which WhatsApp number was targeted by the send workflow.
- Stored sender phone number+442071838750 (illustrative)
This ties the event back to the WhatsApp account number saved by the extension.
- Browser and install metadataChrome on Linux, extension version 1.6.69, language en-US (illustrative)
This adds device and extension context to the same event.
- Content-Type
- application/x-protobuf
- x-log-apiversion
- 0.6.0
Shipped bundle functions and readable source-map code for the log path
Source-map main() function builds logData from contentTmp and phoneNumList[indexCount]
webpack:///./src/content-scripts/content-script.jsasync function main( phoneNumList, phoneNumSuccessList, phoneNumFailList, repeatList, oldSuccessAndFailList, indexCount, content, mediaType, minNum, maxNum, excelData, dailySendNums, sendMessageType, allDataImg, allDataVideo, allDataDocument, sendButtonValue, buttonInputValue, button_radio, isDeleteMessage, audio = {}) { // 检查是否达到发送上限 const { isReachSendLimit, showConfirmTitle } = checkDailySendNums(dailySendNums) if (isReachSendLimit) { chrome.storage.local.set({ stopFlag: true, stopFlagSimple: true }) chrome.runtime.sendMessage({ isReachSendLimit: isReachSendLimit, showConfirmTitle: showConfirmTitle, stopFlag: true, stopFlagSimple: true }) return } // 判断用户是否点击了stop按钮 if ( (sendMessageType === 'pro' && !stopFlag) || (sendMessageType === 'simple' && !stopFlagSimple) ) { // 将用户输入的手机号转化成列表 let phoneNumListLen = phoneNumList.length if (indexCount < phoneNumListLen) { // 处理发送内容 let tmpData = dealContentPlaceholder( // 判断是否为excelData? 处理当前number的content content, excelData, phoneNumList[indexCount], phoneNumList, indexCount ) let contentTmp = tmpData[0] let isExcelData = tmpData[1] let isPlaceHolder = tmpData[2] let isGroupData = await isGroupPhoneNumList(phoneNumList[indexCount]) // console.log('isGroupData' + isGroupData); let s_source = sendLogDetail(isExcelData, isGroupData) let isDelete = isDeleteMessage ? await _sendMessage({ echoDeleteMessage: true, phone: `${phoneNumList[indexCount]}@c.us` }) : isDeleteMessage console.log(isDelete) let sendStatus = await sendContent( phoneNumList[indexCount], contentTmp, mediaType, allDataImg, allDataVideo, allDataDocument, sendButtonValue, buttonInputValue, button_radio, audio ) if (isDelete) { _deleteMessage(`${phoneNumList[indexCount]}@c.us`) } if (sendStatus) { countSuccess += 1 phoneNumSuccessList.push(phoneNumList[indexCount]) const realPhoneNumSuccessList = await batchGetContactPhone(phoneNumSuccessList) chrome.storage.local.set({ countSuccess: phoneNumSuccessList.length, phoneNumSuccessList: realPhoneNumSuccessList, disabledSendingFlag: false, stopFlag: false, stopFlagSimple: false, isShowContinueBtn: false }) stopFlagSimple = false stopFlag = false chrome.runtime.sendMessage({ countSuccess: phoneNumSuccessList.length, disabledSendingFlag: false }) dailySendNums = incrDailySendNumsAndSendMsg(dailySendNums) } else { countFail += 1 phoneNumFailList.push(phoneNumList[indexCount]) const realPhoneNumFailList = await batchGetContactPhone(phoneNumFailList) chrome.storage.local.set({ countFail: phoneNumFailList.length, phoneNumFailList: realPhoneNumFailList, disabledSendingFlag: false, stopFlag: false, stopFlagSimple: false, isShowContinueBtn: false }) stopFlagSimple = false stopFlag = false chrome.runtime.sendMessage({ countFail: phoneNumFailList.length, disabledSendingFlag: false }) } //日志301301 日志302301 let eventNum = sendMessageType === 'simple' ? 301301 : 302301 let logData if (sendMessageType === 'simple') { logData = { s_content: contentTmp, s_source: s_source, s_min_num: minNum, s_max_num: maxNum, s_status: sendStatus ? 1 : 2, s_target_phone: phoneNumList[indexCount] } } else { logData = { s_content: contentTmp, s_source: s_source, s_attachment: mediaType ? 1 : 2, s_attachment_type: mediaType === 'img' ? 1 : mediaType === 'video' ? 2 : mediaType === 'doc' ? 3 : mediaType === 'contact' ? 4 : '', s_min_num: minNum, s_max_num: maxNum, s_status: sendStatus ? 1 : 2, s_placeholder: isPlaceHolder ? 1 : 2, s_button: sendButtonValue ? 1 : 2, s_target_phone: phoneNumList[indexCount], s_button_type: button_radio === 'Link' ? 2 : button_radio === 'Call' ? 3 : button_radio === 'Text' ? 1 : '', s_button_text: buttonInputValue } } // console.log(eventNum); // console.log(logData); sendLog(eventNum, logData) indexCount += 1 // 发送日志到GA // if (sendMessageType === 'pro') { // await visitor.event("发消息", "发消息-用户手机号-pro", myNumber).send(); // } // if (sendMessageType === 'simple') { // await visitor.event("发消息", "发消息-用户手机号-simple", myNumber).send(); // } // 获取重复手机号操作 let tmpValues = await getNoRepeatIndex( indexCount, phoneNumList, phoneNumSuccessList, phoneNumFailList, repeatList, oldSuccessAndFailList ) indexCount = tmpValues[0] repeatList = tmpValues[1] chrome.storage.local.set({ repeatList: repeatList }) let continueSendData = getContinueSendData( phoneNumList, phoneNumSuccessList, phoneNumFailList, repeatList, oldSuccessAndFailList, indexCount, content, mediaType, minNum, maxNum, excelData, sendMessageType, allDataImg, allDataVideo, allDataDocument, sendButtonValue, buttonInputValue, button_radio, isDeleteMessage ) let retryFailedSendData = getRetryFailedSendData( phoneNumList, phoneNumSuccessList, phoneNumFailList, repeatList, oldSuccessAndFailList, indexCount, content, mediaType, minNum, maxNum, excelData, sendMessageType, allDataImg, allDataVideo, allDataDocument, sendButtonValue, buttonInputValue, button_radio, isDeleteMessage ) chrome.storage.local.set({ continueSendData, retryFailedSendData }) // 如果发送的是最后一条消息,那么不用等待 if (indexCount < phoneNumListLen) { // 睡眠需要等待的时间,每发80条消息休眠3~5min if ((indexCount - 1) % 80 === 79) { await sleepBySeconds(180, 300) } else if (indexCount === 0) { //第一次发送等待时间设置为0 await sleepBySeconds(0, 0) } else { await sleepBySeconds(minNum, maxNum) } } return main( phoneNumList, phoneNumSuccessList, phoneNumFailList, repeatList, oldSuccessAndFailList, indexCount, content, mediaType, minNum, maxNum, excelData, dailySendNums, sendMessageType, allDataImg, allDataVideo, allDataDocument, sendButtonValue, buttonInputValue, button_radio, isDeleteMessage, audio ) } else { return new Promise((resolve) => { setTimeout(function () { //该部分以后可以做成导出,先注释备用 // alert("一共发送的手机号数量为:" + phoneNumList.length + "\n" + "发送成功的次数为:" + countSuccess + "\n" + // "发送失败的次数为:" + countFail + "\n" + "发送失败的电话号码为:" + phoneNumFailList.toString()); chrome.storage.local.set({ stopFlag: true, stopFlagSimple: true, anShowWaitSendTip: false, groupPhoneNumList: [] }) chrome.runtime.sendMessage({ stopFlag: true, stopFlagSimple: true, disabledSendingFlag: false }) }, 2000) resolve() }) } } else { // console.log("用户终止了content操作"); }}Source-map sendLog and dealLog functions forward and enrich the event
webpack:///./src/utils/log-util.jsfunction sendLog(eventType, otherParams) { chrome.runtime.sendMessage({ type: 'log', eventType: eventType, otherParams: otherParams })}const opts = { host: 'us-west-1.log.aliyuncs.com', project: 'extension-us', time: 0.05, count: 1, // 测试 // logstore: 'scrm-user-test' // 生产 logstore: LOGSTORE}import { EVENT_MAPPING } from '@/const/contant-log.js'const logger = new SlsWebLogger(opts)function dealLog(data) { let eventType = data.eventType let otherParams = data.otherParams // 从local Storage获取用户基本信息,经过简单处理确定sendData chrome.storage.local.get( [ 'browserInfo', 'platform', 'installTime', 'userPhoneNum', 'firstSendTime', 'uuid', 'version', 'language' ], function (res) { let browserInfo = res.browserInfo !== undefined ? res.browserInfo : '' let user_agent = browserInfo ? browserInfo.userAgent : '' let browser_type = browserInfo ? browserInfo.browser : '' let platform = res.platform !== undefined ? res.platform : '' let installTime = res.installTime !== undefined ? res.installTime : '' let phoneNum = res.userPhoneNum let firstSendTime = res.firstSendTime !== undefined ? res.firstSendTime : '' let uuid = res.uuid !== undefined ? res.uuid : '' let version = res.version !== undefined ? res.version : '' let language = res.language !== undefined ? res.language : '' let version_current = chrome.runtime.getManifest().version let eventTypeDesc = eventType === undefined ? null : EVENT_MAPPING[eventType] let sendData = { event_source: 3, event_type: eventType, event_type_desc: eventTypeDesc, event_time: Math.round(new Date() / 1000), install_time: installTime, s_first_send_time: firstSendTime, platform, user_agent, browser_type, uuid, version, version_current, language } // 如果获取到用户手机号 if (phoneNum !== undefined) { let PhoneNumber = require('awesome-phonenumber') let c = PhoneNumber('+' + phoneNum).getRegionCode() // -> 'SE' let pn = new PhoneNumber(phoneNum.toString(), c) let region = pn.getRegionCode() ? pn.getRegionCode() : '' let countryCode = getCountryNameByDomain(region) ? getCountryNameByDomain(region) : '' sendData.s_user_phone_num = phoneNum sendData.s_country_code = countryCode chrome.storage.local.set({ phoneNum, countryCode }) } else { sendData.s_user_phone_num = '' sendData.s_country_code = '' } // 如果有额外参数,需要修改sendData if (otherParams) { for (let key in otherParams) { sendData[key] = otherParams[key] } } // 发送阿里云日志 logger.send(sendData) } )}Source-map SLS logger turns queued logs into a POST body
webpack:///./node_modules/js-sls-logger-v3/dist/index.jskey: "logger", value: async function logger(payload = false) { var arr = payload !== false && payload.length > 0 ? payload : []; if (arr.length === 0) return; var url = "https://" + this.project + "." + this.host + "/logstores/" + this.logstore + "/track"; try { var reqPayload = JSON.stringify({ __logs__: arr }); let headers = new Headers({ "x-log-apiversion": "0.6.0", "x-log-bodyrawsize": reqPayload.length }) if (!this.compress) { let blob = new Blob([reqPayload], { type: "application/x-protobuf" }); headers.append('Content-Type', "application/x-protobuf") fetch(url, { method: "POST", headers, body: blob }).then(response => response) } else { headers.append("x-log-compresstype", "lz4") var input = Buffer.from(reqPayload); var maxOutputSize = lz4.encodeBound(reqPayload.length); var output = Buffer.alloc(maxOutputSize); var compressedSize = lz4.encodeBlock(input, output); output = output.slice(0, compressedSize); fetch(url, { method: "POST", headers, body: output }).then(response => response) } } catch (e) { console.log(e) } }- extension-us.us-west-1.log.aliyuncs.com
Receives extension log events through Alibaba Cloud Log Service; the code sets project extension-us, host us-west-1.log.aliyuncs.com, and logstore scrm-web-sender-juzhen.
Where it sends data
Destinations our analysis observed WA Self Sender & Easy Sender - Personalized WA Messages & Group Tools contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- extension-us.us-west-1.log.aliyuncs.comwidely used
WA Self Sender & Easy Sender - Personalized WA Messages & Group Tools sends data to extension-us.us-west-1.log.aliyuncs.com. A widely used service: 27 other extensions we have analysed send data here.