Twitch Enhanced Viewer JeetBot – OAuth Token Exposure Through Browser Extension

Threat Overview

Socket Threat Research found that the cross-store Twitch Enhanced Viewer | JeetBot browser extension forwarded users’ live Twitch OAuth session tokens through proxy infrastructure operated by a Russian-language commercial bot service. Socket reported approximately 31,000 users across Chrome and Firefox. Current v85.x builds at the time of Socket’s September 11 report appended the full account-scoped token as an auth query parameter when redirecting Twitch playlist requests through operator-controlled proxies, causing bearer credentials to be exposed in proxy request logs. Earlier v4.x builds POSTed tokens to dedicated set-token endpoints. On September 14, The Hacker News reported that the developer characterized the behavior as an oversight, released Firefox version 85.8.7 to stop forwarding the token, and submitted an equivalent Chrome update for review. Updating or disabling the extension does not revoke tokens already transmitted.

References

Impacted Systems

Product: Twitch Enhanced Viewer | JeetBot browser extension. Browsers: Google Chrome and Mozilla Firefox; project documentation also advertises compatibility with Opera, Yandex, and Vivaldi. Chrome extension ID: pnhhdhhcadcjfckjhpmjneldiegbojfb. Firefox extension ID: twitchenhancedviewer@example.com. Socket observed token-forwarding behavior in v85.2.2 through v85.6.1 and historical direct token POST behavior in v4.x. The developer states Firefox 85.8.7 fixes the issue; an equivalent Chrome update was still under review when The Hacker News published on September 14. Published package SHA-256 values: Chrome e17e1e671b597da19b89c5b2d0fa821e90e627860e756ae4947ed27c035330a8; Firefox 141c35607dc0e8e400deb380126b3052bd0495b4d37c8dd979c6aa0204b142fc.

Why this matters

Browser extensions execute inside a trusted user context and can access authenticated web sessions. Although this incident centers on Twitch rather than a core enterprise SaaS platform, it is a current example of session-token exposure through an officially distributed extension and provides concrete IOCs useful for extension governance and endpoint hunting.

Exploitation Status

Confirmed token-forwarding behavior based on Socket’s analysis of extension code and infrastructure. Socket states that account-scoped Twitch OAuth tokens were forwarded to operator-controlled proxies. The developer disputes malicious intent and describes the design as an oversight, but acknowledges the prior token transmission and changed the implementation. No evidence reviewed for this package establishes that every exposed token was subsequently abused.

What this hunt looks for

Published proxy domains and IP addresses, the Chrome extension ID on disk, published package hashes, browser connections to proxy infrastructure, repeated contact with token-forwarding hosts, and historical set-token endpoints.

Required logs

Defender XDR endpoint network and file telemetry in DeviceNetworkEvents and DeviceFileEvents is preferred. Firewall or proxy telemetry normalized into CommonSecurityLog provides a fallback for the published domains, IPs, and historical token-forwarding endpoints. Defender XDR Device* tables use their native Timestamp field.

First Pass – Published JeetBot Proxy Domains

let Domains=dynamic(["enhanced.jeetbot.cc","enhanced-1.jeetbot.cc","ext-03.jeetbot.cc","proxy.morphilina.me","ext-styles.jeetbot.cc","api.jeetbot.cc","img.drisnya.online","thebeholder-proxy.deno.dev","proxy.thebeholder.deno.net"]);
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteUrl in~ (Domains)
| project Timestamp,DeviceName,InitiatingProcessAccountName,InitiatingProcessFileName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

Published JeetBot Infrastructure IPs

let IPs=dynamic(["152.53.177.186","132.243.113.25","80.74.26.162"]);
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteIP in (IPs)
| project Timestamp,DeviceName,InitiatingProcessAccountName,InitiatingProcessFileName,InitiatingProcessCommandLine,RemoteUrl,RemoteIP,RemotePort
| order by Timestamp desc

Chrome Extension ID on Disk

DeviceFileEvents
| where Timestamp >= ago(30d)
| where FolderPath has @"\Extensions\pnhhdhhcadcjfckjhpmjneldiegbojfb\"
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,SHA256,InitiatingProcessFileName,InitiatingProcessCommandLine
| order by Timestamp desc

Published Extension Package Hashes

let Hashes=dynamic(["e17e1e671b597da19b89c5b2d0fa821e90e627860e756ae4947ed27c035330a8","141c35607dc0e8e400deb380126b3052bd0495b4d37c8dd979c6aa0204b142fc"]);
DeviceFileEvents
| where Timestamp >= ago(30d)
| where SHA256 in~ (Hashes)
| project Timestamp,DeviceName,ActionType,FileName,FolderPath,SHA256,InitiatingProcessFileName
| order by Timestamp desc

Browser Connections to JeetBot Proxy Infrastructure

let Domains=dynamic(["enhanced.jeetbot.cc","enhanced-1.jeetbot.cc","ext-03.jeetbot.cc","proxy.morphilina.me","ext-styles.jeetbot.cc","api.jeetbot.cc"]);
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteUrl in~ (Domains)
| where InitiatingProcessFileName in~ ("chrome.exe","firefox.exe","msedge.exe","opera.exe","vivaldi.exe")
| summarize FirstSeen=min(Timestamp),LastSeen=max(Timestamp),Connections=count(),Destinations=make_set(RemoteUrl,20),RemoteIPs=make_set(RemoteIP,20) by DeviceName,InitiatingProcessAccountName,InitiatingProcessFileName
| order by LastSeen desc

CommonSecurityLog Fallback for Published Domains and IPs

let Domains=dynamic(["enhanced.jeetbot.cc","enhanced-1.jeetbot.cc","ext-03.jeetbot.cc","proxy.morphilina.me","ext-styles.jeetbot.cc","api.jeetbot.cc","img.drisnya.online"]);
let IPs=dynamic(["152.53.177.186","132.243.113.25","80.74.26.162"]);
CommonSecurityLog
| where TimeGenerated >= ago(30d)
| where DestinationIP in (IPs) or RequestURL has_any (Domains) or DestinationHostName has_any (Domains)
| project TimeGenerated,SourceIP,DestinationIP,DestinationPort,DestinationHostName,RequestURL,DeviceAction,Message
| order by TimeGenerated desc

Devices Repeatedly Contacting Token-Forwarding Infrastructure

let Domains=dynamic(["enhanced.jeetbot.cc","enhanced-1.jeetbot.cc","ext-03.jeetbot.cc","proxy.morphilina.me"]);
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteUrl in~ (Domains)
| summarize Connections=count(),FirstSeen=min(Timestamp),LastSeen=max(Timestamp),Destinations=make_set(RemoteUrl,20),Processes=make_set(InitiatingProcessFileName,20) by DeviceName,InitiatingProcessAccountName
| where Connections >= 3
| order by Connections desc

Historical set-token Endpoints in Proxy or Firewall Logs

CommonSecurityLog
| where TimeGenerated >= ago(90d)
| where RequestURL has_any ("enhanced.jeetbot.cc/set-token","thebeholder-proxy.deno.dev/set-token","proxy.thebeholder.deno.net/set-token")
| project TimeGenerated,SourceIP,DestinationIP,DestinationPort,RequestMethod,RequestURL,DeviceAction,Message
| order by TimeGenerated desc

Detection Notes

Network connections to the published proxy infrastructure from Chrome or Firefox are the strongest Sentinel-available indicator because the browser-extension inventory TVM table is not actually ingested into Microsoft Sentinel. Microsoft documents DeviceTvmBrowserExtensions as Defender XDR Advanced Hunting data exposed only for schema visibility in Sentinel unless a custom ingestion path exists, so this post intentionally does not rely on that table. The Chrome extension ID can often be identified in the on-disk extension directory through DeviceFileEvents, but browser profile paths vary. The published hashes and infrastructure can change across extension updates. A network match shows contact with the operator infrastructure, not proof that a token was abused.

Leave a comment