Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adloox Analytics Module: apply 'js' param constraint #12618

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

jimdigriz
Copy link
Contributor

Type of change

  • Bugfix
  • Feature
  • Other

Description of change

The publisher is allowed to provide an override URL for the Adloox Analytics Module which us use as the location to load the verification JS by way of loadExternalScript.

Anything is allowed, whilst in practice only sub-domains of adlooxtracking.{com,ru} are meaningful.

This PR changes the module to only accept sub-domains of adlooxtracking.{com,ru} for the value of the option js.

Other information

The trigger for this change is a client had a setup where a bad actor could provide a URL for their own script instead before the module initialised.

At a glance (51Degrees, aaxBlockmeterRtd, browsiRtd, ...) I suspect there are other modules with a similar problem.

Maybe it worth amending loadExternalScript to include an enforcer that which validates against a list of module provided (sub)domains to make review/vetting easier? When not supplied, the debug version can state something like "pants may explode" or prompt a code reviewer to require better justification for arbitrary URL external script loading.

@patmmccann
Copy link
Collaborator

How did the bad actor access the page? I wonder if we could track if this config ever changes in a pv after being set? If the bad actor could modify prebid config, why did it need to use the vector to load script instead of just loading it directly?

Copy link
Collaborator

@patmmccann patmmccann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have a test failure; also do you not want to let pubs self-host?

@@ -109,6 +109,10 @@ analyticsAdapter.enableAnalytics = function(config) {
logError(MODULE, 'invalid js options value');
return;
}
if (isStr(config.options.js) && !/\.adlooxtracking\.(com|ru)$/.test(parseUrl(config.options.js, { 'noDecodeWholeURL': true }).host)) {
Copy link
Collaborator

@patmmccann patmmccann Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if TLD+1 is same as window.location?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to amend parseUrl to use new URL() (instead of <a/>) now you have ~20 other users of this in Prebid.js already? You could then potentially move them over to this; or would you prefer to deprecate this function altogether and have the modules themselves just inline new URL()?

As for the consequences, for us, this might affect an internal test page, but nothing we would not just workaround or fixup with a latter PR.

@jimdigriz
Copy link
Contributor Author

How did the bad actor access the page?

This is unknown, all I received was a screenshot that was taken by someone and was passed to me by someone else, so it is hearsay by at least a distance of two...probably more.

I wonder if we could track if this config ever changes in a pv after being set?

Not sure what you mean by 'pv' here, but I instead would just deep copy it on initialisation and have modules access this (immutable) copy instead of the original object directly.

Alternatively, patch the object (and children objects) to call your getters/setters by way of Object.define{Property,Properties}() to act as a trip wire.

If the bad actor could modify prebid config, why did it need to use the vector to load script instead of just loading it directly?

This was my thought too, not really sure why they bothered to even involve prebid.js if they had access to the JavaScript context; maybe the objective was to use our module as a smokescreen but then that does not entirely add up either.

The screenshot I received showed our domain serving malicious code but it was also claimed someone spoke to us to purge the offending code yet those with asset publishing access never received any such communication.

My guess so far is what was seen might have been an IDN homograph attack so it presented as our domain in the screenshot. What was actually purged was something in the publishers site which could explain why no one contacted us and everything got resolved.

Fortunately, this did prompt me to review that section of the code. Though we do not know if this was the root cause, or even if this happened, there is no point leaving a gate open needlessly so the PR is to close it.

@jimdigriz
Copy link
Contributor Author

jimdigriz commented Jan 6, 2025

also do you not want to let pubs self-host?

The pubs self host the prebid.js blob, but they do not generally self host the verification JS. If they need to, we would just have them patch the module in their own internal fork.

you have a test failure

Nevermind...it appears when I run the full test suite....working on it.

Not seeing it, what am I missing?

alex@hanzawa:~/src/adloox/Prebid.js$ git log --oneline --graph --decorate --all
* d0a48acce (HEAD -> adloox-subdomain, origin/adloox-subdomain, coremem/adloox-subdomain) Adloox Ad Server Video: remove un-necessary default parameter from test
* 7d0102b39 Adloox Analytics: enforce only adlooxtracking.com as a subdomain may be used
* 5e57caa8c (upstream/master, origin/master, origin/HEAD, coremem/master, master) Bitmedia Bidder Adapter : initial release (#12610)
* e16153eed MadSense Bid Adapter : initial release (#12546)
* 03a2ea56a Adtrgtme bid adapter changes (#12580)
* 6ba8de60f Adkernel: add UrekaMedia alias (#12614)
* 7e0cda11c Escalax Bid Adapter: initial release (#12483)
* f0301548a Attekmi add new alias artechnology (#12609)
* a363b69c9 Mobian RTD Module: Documentation update (#12608)
[snipped]

alex@hanzawa:~/src/adloox/Prebid.js$ gulp test-coverage --file 'test/spec/modules/adloox{AnalyticsAdapter,AdServerVideo,RtdProvider}_spec.js' 
[20:37:20] Using gulpfile ~/src/adloox/Prebid.js/gulpfile.js
[20:37:20] Starting 'test-coverage'...
[20:37:20] Starting 'clean'...
[20:37:20] Finished 'clean' after 21 ms
[20:37:20] Starting 'testCoverage'...
Starting chunk 1 of 1: test/spec/modules/adloox{AnalyticsAdapter,AdServerVideo,RtdProvider}_spec.js

START:
Webpack bundling...
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
asset commons.js 7.54 MiB [emitted] (name: commons) (id hint: commons)
asset runtime.js 14 KiB [emitted] (name: runtime)
asset dependencies.json 477 bytes [emitted]
asset adlooxAnalyticsAdapter_spec.1605910524.js 476 bytes [emitted] (name: adlooxAnalyticsAdapter_spec.1605910524)
asset adlooxAdServerVideo_spec.569559766.js 469 bytes [emitted] (name: adlooxAdServerVideo_spec.569559766)
asset adlooxRtdProvider_spec.1004497417.js 466 bytes [emitted] (name: adlooxRtdProvider_spec.1004497417)
asset pipeline_setup.2148849356.js 437 bytes [emitted] (name: pipeline_setup.2148849356)
asset hookSetup.1487957516.js 435 bytes [emitted] (name: hookSetup.1487957516)
asset test_deps.989609925.js 426 bytes [emitted] (name: test_deps.989609925)
webpack 5.94.0 compiled successfully in 5234 ms
06 01 2025 20:37:26.702:INFO [karma-server]: Karma v6.4.3 server started at http://localhost:9876/
06 01 2025 20:37:26.703:INFO [launcher]: Launching browsers ChromeHeadless with concurrency 5
06 01 2025 20:37:26.707:INFO [launcher]: Starting browser ChromeHeadless
06 01 2025 20:37:26.949:INFO [Chrome Headless 131.0.0.0 (Linux x86_64)]: Connected on socket RY4LsehuW4zLicL2AAAB with id 75576744
WARN: 'fun-hooks: referenced 'realTimeData' but it was never created'

Finished in 0.071 secs / 0.019 secs @ 20:37:27 GMT+0000 (Greenwich Mean Time)

SUMMARY:
✔ 38 tests completed
Chunk 1 of 1: done

[20:37:27] Finished 'testCoverage' after 6.7 s
[20:37:27] Finished 'test-coverage' after 6.72 s

@jimdigriz
Copy link
Contributor Author

How did the bad actor access the page?

This is unknown, all I received was a screenshot that was taken by someone and was passed to me by someone else, so it is hearsay by at least a distance of two...probably more.

Right, figured out some more by tracking down the source of the screenshots; a scam tracker posting on X.

It was claimed[1] Prebid.js was involved in the loading of our verification script but from another screenshot[2] it was our direct GAM/GPT integration (that chainloads a.js). You can determine this from the requests to our RTB API (https://p.adlooxtracking.com/q?v=...&...) where for our Prebid.js module the value of v is prefixed with pbjs-... whilst for the direct GAM/GPT integration we use gpt-.... From the screenshot it is not even clear Prebid.js were involved at all.

Inspecting the code for the our direct GAM/GPT code (https://p.adlooxtracking.com/gpt/a.js) the origin of the verification script is hardcoded, so no idea what the screenshot is actually showing but on the upside it does mean the IDN homograph theory is a dead end.

I suspect the researcher may have seen .../prebid in the request list, searched around and found our module that convinced themselves of what they were seeing...happens to the best of us.

Though for us, we still wish to lock down the sub-domains allowed in our module, but for yourselves maybe any urgency there was here may be dropped a notch as I am pretty certain our module (and thus Prebid.js) were not involved.

That said, maybe you consider users of loadExternalScript() are vulnerable if they can load any URL where a bad actor changes the configuration object (or some global window state) leading to loading in something unintended?

Thoughts? Maybe you have an alternative view of what may have happened here?

Cheers

[1] https://x.com/realScamSniffer/status/1871908228805878244
[2] https://x.com/realScamSniffer/status/1871823641765445672 - requests 9 and 10 go to our RTB API
[3] https://x.com/realscamsniffer/status/1871823660291616769

@dgirardi
Copy link
Collaborator

dgirardi commented Jan 7, 2025

That said, maybe you consider users of loadExternalScript() are vulnerable if they can load any URL where a bad actor changes the configuration object (or some global window state) leading to loading in something unintended?

It gets tricky if we can't trust configuration - external scripts are not the only attack vector. Off the top of my head I can think of renderers and mock creatives that can be set up with prebid config and would allow arbitrary code execution. I'm sure there's many, many more.

@patmmccann
Copy link
Collaborator

Thanks for all the detailed information! Glad you were vindicated in the end. I'm happy to merge once tests are passing and we'll discuss more in committee

@patmmccann patmmccann self-assigned this Jan 9, 2025
@jimdigriz
Copy link
Contributor Author

@patmmccann fixed the test, let me know if you need anything else, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants