Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
This library facilitates the creation of PoTokens by directly interacting with BotGuard's API.
This library does not bypass BotGuard; you still need an environment that passes its checks to use it. It is simply a reverse-engineered implementation of the same process that YouTube’s web player uses.
The library is not affiliated with Google or YouTube in any way. It is an independent project created for educational purposes. I am not responsible for any misuse of this library.
Please refer to the provided examples here.
Below is a brief overview of the process to generate a PoToken for those interested in the inner workings of the library. This information is based on my own research and may become outdated as Google updates its systems.
First, retrieve the VM's script and program:
curl --request POST \
--url 'https://jnn-pa.googleapis.com/$rpc/google.internal.waa.v1.Waa/Create' \
--header 'Content-Type: application/json+protobuf' \
--header 'x-goog-api-key: AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw' \
--header 'x-user-agent: grpc-web-javascript/0.1' \
--data '[ "requestKeyHere" ]'
Once the response data is available, it must be descrambled and parsed:
// ...
const buffer = base64ToU8(scrambledChallenge);
const descrambled = new TextDecoder().decode(buffer.map((b) => b + 97));
const challengeData = JSON.parse(descrambled);
The descrambled data should consist of a message ID, the interpreter JavaScript, the interpreter hash, a program, and the script's global name.
To make the VM available, evaluate the script:
const interpreterJavascript = bgChallenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue;
if (interpreterJavascript) {
new Function(interpreterJavascript)();
} else throw new Error('Could not load VM');
If everything goes well, you should be able to access it like so:
const globalObject = window || globalThis;
console.log(globalObject[challengeData.globalName]);
This is a crucial step. The Integrity Token is retrieved from an attestation server and relies on the result of the BotGuard challenge, likely to assess the integrity of the runtime environment. To "solve" this challenge, you need to invoke BotGuard and pass the retrieved program as its first argument.
// ...
if (!this.vm)
throw new Error('[BotGuardClient]: VM not found in the global object');
if (!this.vm.a)
throw new Error('[BotGuardClient]: Could not load program');
const vmFunctionsCallback = (
asyncSnapshotFunction,
shutdownFunction,
passEventFunction,
checkCameraFunction
) => {
Object.assign(this.vmFunctions, { asyncSnapshotFunction, shutdownFunction, passEventFunction, checkCameraFunction });
};
try {
this.syncSnapshotFunction = await this.vm.a(this.program, vmFunctionsCallback, true, undefined, () => { /** no-op */ }, [ [], [] ])[0];
} catch (error) {
throw new Error(`[BotGuardClient]: Failed to load program (${(error as Error).message})`);
}
The second parameter should be a callback function, where BotGuard will return several functions. In our case, we are mainly interested in asyncSnapshotFunction
.
Once asyncSnapshotFunction
is available, call it with the following arguments:
contentBinding
(Optional).signedTimestamp
(Optional).webPoSignalOutput
(Optional but required for our case, BotGuard will fill this array with a function to get a PoToken minter).skipPrivacyBuffer
(Optional, not sure what this one is/does).async snapshot(args) {
return new Promise((resolve, reject) => {
if (!this.vmFunctions.asyncSnapshotFunction)
return reject(new Error('[BotGuardClient]: Async snapshot function not found'));
this.vmFunctions.asyncSnapshotFunction((response) => resolve(response), [
args.contentBinding,
args.signedTimestamp,
args.webPoSignalOutput,
args.skipPrivacyBuffer
]);
});
}
Then:
const webPoSignalOutput = [];
const botguardResponse = await snapshot({ webPoSignalOutput });
If everything was done correctly, you should have a token and an array with one or more functions.
Now we can create the payload for the Integrity Token request. It should be an array of two items: the request key and the token.
curl --request POST \
--url 'https://jnn-pa.googleapis.com/$rpc/google.internal.waa.v1.Waa/GenerateIT' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json+protobuf' \
--header 'x-goog-api-key: AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw' \
--header 'x-user-agent: grpc-web-javascript/0.1' \
--data '[ "requestKeyHere", "$abcdeyourbotguardtokenhere" ]'
If the API call is successful, you will receive a JSPB (json+protobuf) response that looks like this:
[
"azXvdvYQKz8ff4h9PjIlQI7JUOTtYnBdXEGs4bmQb8FvmFB+oosILg6flcoDfzFpwas/hitYcUzx3Qm+DFtQ9slN",
43200,
100,
]
The first item is the Integrity Token, the second is the TTL (Time to Live), and the third is the refresh threshold.
Store the token and the array we obtained earlier. We'll use them to construct the PoToken.
Call the first function in the webPoSignalOutput
array with the Integrity Token (in bytes) as an argument:
const getMinter = webPoSignalOutput[0];
if (!getMinter)
throw new Error('PMD:Undefined');
const mintCallback = await getMinter(base64ToU8(integrityTokenResponse.integrityToken ?? ''));
if (!(mintCallback instanceof Function))
throw new Error('APF:Failed');
If successful, you'll receive a function to mint PoTokens. Call it with your Visitor ID (or Data Sync ID if you're signed in) as an argument:
const result = await mintCallback(new TextEncoder().encode(identifier));
if (!result)
throw new Error('YNJ:Undefined');
if (!(result instanceof Uint8Array))
throw new Error('ODM:Invalid');
const poToken = u8ToBase64(result, true);
console.log(poToken);
The result will be a sequence of bytes, about 110–128 bytes in length. Base64 encode it, and you'll have a PoToken!
YouTube's web player checks the "sps" (StreamProtectionStatus
) of each media segment request (only if using UMP
or SABR
; our browser example uses UMP
) to determine if the stream needs a PoToken.
Distributed under the MIT License.
FAQs
A JavaScript library for interfacing with Botguard.
The npm package bgutils-js receives a total of 615 weekly downloads. As such, bgutils-js popularity was classified as not popular.
We found that bgutils-js demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.