Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
A small library used in the Browser and NodeJS to vet URIs (to mitigate vulnerabilities) with confidence. In other words, It's the DOMPurify for URIs. A uniform resource locator (URL) is, in fact, a subset of uniform resource identifiers (URI). Therefore, this library covers the super set of all resource identifiers where possible.
There are many web-based zero-day vulnerabilities that can be expolited in Browsers/NodeJS servers using Standard and/or Custom URI schemes. Certain browsers like Safari and Firefox are usually subceptible to launching such URIs without a prompt or restrictions and enable Arbitrary File Execution, Remote Code Execution and/or Connection String Pollution (on the server) where possible. This is why this library was built. It moves to create a layer of protection for your web applications both on the Browser and on the Server (NodeJS only) by blocking badly formed/suspicious URIs.
Furthermore, other solutions like braintree/sanitize-url are quite naive and a bit too specific in it's approach to URL sanitization. Also, most web front-end frameworks like Angular and Vue (safe for React) do not do a very robust and serious (non-trivial) job of sanitiziting URLs either. This is why this library is very important to web application developers who need reliability in sanitizing URLs.
This library has been validated against popular malicious URIs delineated here and here
Install using npm
npm install urisanity
or install using yarn
.
yarn add urisanity
All you need to do is import the package appropriately depending on the environment (Browser OR Node) being used
Using a
script
tag directly inside a web page
<script type="text/javascript" src="https://unpkg.com/browse/urisanity@0.1.6/dist/urisanity.min.js" crossorigin="anonymous"></script>
import as ES6 module - no setup required
import URISanity from 'urisanity';
const sanitizedUrl = URISanity.vet('blob:https://www.foo-.evil.com/undefined', {
// All flag options set - valid
allowScriptOrDataURI: false,
allowFileSystemURI: false,
allowCommsAppURI: true,
allowDBConnectionStringURI: false,
allowBrowserSpecificURI: false,
allowWebTransportURI: false,
allowServiceAPIURI: false,
});
console.log(sanitizedUrl); // "about:blank"
const sanitizedDBUri = URISanity.vet("jdbc:sqlserver://;servername=server_name;integratedSecurity=true;authenticationScheme=JavaKerberos", {
// One flag option set - valid
allowDBConnectionStringURI: true,
allowFileSystemURI: false, // you can omit this since it's `false`
allowCommsAppURI: false, // you can omit this since it's `false`
allowScriptOrDataURI: false, // you can omit this since it's `false`
allowWebTransportURI: false, // you can omit this since it's `false`
allowServiceAPIURI: false // you can omit this since it's `false`
})
console.log(sanitizedDBUri) // "jdbc:sqlserver://;servername=server_name;integratedSecurity=true;authenticationScheme=JavaKerberos"
const sanitizedCustomUrl = URISanity.vet(
'icloud-sharing://www.icloud.com/photos/01eFfrthOPvnfZqlKMn', {
/* No flag options set - valid */
});
console.log(sanitizedCustomUrl); // "about:blank"
const santizedBadUrl = URISanity.vet('http://aa.com/</script>"><img src=x onerror="prompt(document.domain)">)', {
allowWebTransportURI: true,
allowScriptOrDataURI: true
})
console.log(sanitizedBadUrl); // "about:blank"
const paramValue = URISanity.extractParamValueFromUri(
'https://www.example.com?xyz=%200000#intro',
'xyz'
);
console.log(paramValue); // " 0000"
const checkPassed = URISanity.checkParamsOverWhiteList(
'grpc://api.broker.rt-msg.io:443?user=sal%C3%A1ta',
['user']
);
console.log(checkPassed); // true
const isSame = URISanity.isSameOrigin(window.location.href)
console.log(isSame) // true
Setup an env file in your NodeJS app and include an
ORIGIN
ORIGIN=http://127.0.0.1:4050
const URISanity = require('urisanity');
const sanitizedFileUrl = URISanity.vet(
'file://www.airbnb.com/Users/xxx/Desktop/index.html',
{
allowWebTransportURI: true
}
);
console.log(sanitizedFileUrl) // "about:blank"
const URISanity = require('urisanity');
let sanitizedUrl = URISanity.vet(
'file://www.airbnb.com/Users/xxx/Desktop/index.html',
{
allowWebTransportURI: false,
allowFileSystemURI: true
}
);
console.log(sanitizedUrl) // "file://www.airbnb.com/Users/xxx/Desktop/index.html"
You can make use of Trusted Types while using URI Sanity. An excerpt from a 2021 report from Google on Trusted Types reads:
Trusted Types are supported in several popular frameworks and libraries including Angular, React (with a feature flag), Lit, Karma, and Webpack. Enforcing Trusted Types in applications built on top of these frameworks is now relatively simple; in some cases no application-level code changes are required.
Before the advent of Trusted Types (specifically, in the days of Angular 1.x), frontend web engineers used this approach in sanitizing URIs for web applications and it was grossly inefficient and/or naive. This is also another approach that still doesn't cater to a much braoder system for URI sanitization. Now, with URISanity, you have the broader systems needed for quality URI sanitization.
import URISanity from 'urisanity';
import DOMPurify from 'dompurify';
window.addEventListener('securitypolicyviolation', console.error.bind(console));
/* @HINT: feature / object detection */
if (typeof window.trustedTypes !== 'undefined') {
trustedTypes.createPolicy('default', {
createHTML: (html) => {
/* @HINT:
sanitize all potentially malicious characters from HTML string
*/
return DOMPurify.sanitize(html, {
USE_PROFILES: {
html: true,
svg: true,
},
})
},
createScriptURL: (url) => {
/* @HINT:
vet URL string and return "about:blank" if URL string is suspicious
*/
return URISanity.vet(url, {
allowWebTransportURI: true,
})
},
});
}
URISanity can be used to improve the web security of browser API sinks (injection sinks) that make use if URIs and aren't covered and/or catered for by Trusted Types and basic CSP. By instrumenting these API sinks (sinks for Document Object Model / Browser Object Model) and utilizing browser custom event API(s), the solution is quite elegant. Take a look below:
Let's define some basic browser custom events and their handler
/* @NOTE: Can also be the "connect-src" whitelist of urls from a CSP directive */
/* @HINT: The whitelist below is for excluding URLs that are not known to the web app and may be expoitative/suspicious */
const whitelistedURLEndpoints = [
'https://www.facebook.com/tr',
'https://www.google-analytics.com/collect'
]
/* @HINT: Setting custome events to check and validate URLs */
document.addEventListener( 'beforerequest', onBeforeURIUsed, false )
document.addEventListener( 'beforeinclude', onBeforeURIUsed, false )
/* @HINT: Event handler common to the two events above */
function onBeforeURIUsed ( event ) {
/* @CHECK: https://www.npmjs.com/package/urisanity ; urisanity */
/* @HINT: Vet the URL endpoint being requested/included for safety */
if (window.urisanity.vet(
event.detail.endpoint,
{ allowWebTransportURI: true }
) !== 'about:blank') {
const { origin, pathname } = new URL(event.detail.endpoint)
/* @HINT: Make sure the endpoint being requested/included is part of the whitelist */
if (whitelistedURLEndpoints.includes(`${origin}${pathname}`)) {
if (origin.includes('.google-analytics.')) {
/* @HINT: Check that only the request params we need are attached */
/* @HINT: Any other extra params should not be allowed */
if (window.urisanity.checkParamsOverWhiteList(
event.detail.endpoint,
['tid', 'cid'],
event.detail.data
)) {
return;
}
}
}
}
/* @HINT: trigger an error to be thrown when the endpoint is not in the whitelist above */
/* @HINT: Or the validation above for any origin (or for google-analytics) doesn't pass */
event.preventDefault()
}
Now, let's instrument certain browser API sinks and make them able to throw errors on suspicion of a malformed/malicious API.
/*!
* FIRST SECTION
*
*/
/* @HINT: Extract the native definitions of these APIs from the DOM Interfaces */
const originalSetAttributeMethod = HTMLElement.prototype.setAttribute
/* @HINT: Create a new definition for `setAttribute` that instruments the API to detect suspicious URIs */
HTMLElement.prototype.setAttribute = function setAttribute (attributeName, newValue) {
const that = this;
const previousValue = that.getAttribute(attributeName);
const timerID = window.setTimeout(function () {
/* @HINT: Stop [ DOMSubtreeModified ] event from firing before [ DOMAttrModified ] event */
originalSetAttributeMethod.call(that, attributeName, newValue);
}, 0);
/* @HINT: Whenever the attribute name is `href`, then check the URL that is the value */
if (attributeName === 'href') {
/* @HINT: Fire a custom event `beforeinclude` to track manual whitelisting of URL endpoints */
let event = new window.CustomEvent('beforeinclude', {
detail: {
endpoint: newValue,
sink: "HTMLElement.setAttribute",
data: null
},
bubbles: true,
cancelable: true
});
/* @HINT: Detect if the dispatched custom event was cancelled by a call to `event.preventDefault()` */
/* @HINT: If the event was cancelled, it means the URL endpoint above was disallowed by the checks */
const cancelled = !document.dispatchEvent(event)
/* @HINT: If it's cancelled, stop the `setTimeout` call above from being executed by clearing the timeout */
/* @HINT: Also, we throw an error to stop the call to `setAttribute` from being requested */
if (cancelled) {
window.clearTimeout(timerID)
throw new Error(
"Suspicious Activity: "
+
event.detail.endpoint
+
" request, using [ " + event.detail.data + " ] in "
+
" [ " + event.detail.sink + " ]"
)
}
}
/* @HINT: When listening to mutation events, might be okay to stagger certain event sequences properly */
if (newValue !== previousValue) {
let event = document.createEvent("MutationEvent");
event.initMutationEvent(
"DOMAttrModified",
true,
false,
that,
previousValue || "",
newValue || "",
attributeName,
(previousValue === null) ? event.ADDITION : event.MODIFICATION
);
that.dispatchEvent(
event
);
}
};
/*!
* NEXT SECTION
*
*/
/* @HINT: craete a function/constructor that does nothing a.k.a no-operation function */
const noop = function noOperation () {}
/* @HINT: Copy out the user-agent interface function `sendBeacon` */
const NativeSendBeacon = window.Navigator.prototype.sendBeacon || noop
window.Navigator.prototype.sendBeacon = function sendBeacon (url, data) {
/* @HINT: Fire a custom event `beforerequest` to track manual whitelisting of URL endpoints */
const event = new window.CustomEvent('beforerequest', {
detail: {
endpoint: url,
method: "POST",
sink: "Navigator.sendBeacon",
data: data
},
bubbles: true,
cancelable: true
})
/* @HINT: Detect if the dispatched custom event was cancelled by a call to `event.preventDefault()` */
/* @HINT: If the event was cancelled, it means the URL endpoint above was disallowed by the checks */
const cancelled = !document.dispatchEvent(event)
/* @HINT: If it's cancelled, we throw an error to stop the call to `sendBeacon` from being requested */
if (cancelled) {
throw new Error(
"Suspicious Activity: "
+
event.detail.endpoint
+
" request, using [ " + event.detail.data + " ] in "
+
" [ " + event.detail.sink + " ]"
)
}
/* @HINT: If all checks out and no error was thrown above then proceed as usual */
return NativeSendBeacon.call(this, url, data);
};
/* @HINT: define property `name` on custom function */
Object.defineProperty(sendBeacon, 'name', {
writable: false,
value: 'sendBeacon'
});
/* @HINT: define property function `toString` on custom function */
Object.defineProperty(sendBeacon, 'toString', {
writable: true,
value: function toString () {
return NativeSendBeacon.toString()
}
})
/* @HINT: Take care of the special Firefox/IceWeasel (Gecko) property `toSource` */
if ('toSource' in NativeSendBeacon) {
Object.defineProperty(sendBeacon, 'toSource', {
writable: true,
value: function toSource () {
return NativeSendBeacon.toSource()
}
})
}
Finally, the code above in the event handler get triggered whenever navigator.sendBeacon()
is called and the URLs are using URISanity. The zhorn package provides all of this functionality and depends on URISanity.
Here is a brief guide to using this library and it's API method(s)
When using the
.vet(uri: String [, options: Object])
API method, there are flag option(s) to filter out different URI categories in the vetting process. They are as follows:
URISanity.vet(uri: String [, options: Object]): String
The
.vet(uri: String [, options: Object])
method is used to sanitize a URI of any standard form to ensure that it doesn't contain unwanted and/or malicious content. Only the second argument is optional. If the second ( options ) argument isn't passed, it means that all flag options arefalse
.
URISanity.extractParamValueFromUri(uri: String, queryParamName: String): String
The
.extractParamValueFromUri(uri: String, queryParamName: String)
method is used to extract the value of a query parameter from a given URI. Both arguments are not optional.
URISanity.checkParamsOverWhiteList(uri: String, paramNamesWhiteList: Array [, querySearch: String]): Boolean
The
.checkParamsOverWhiteList(uri: String, queryParamNames: Array [, querySearch: String | Object])
method is used to check whether the params (query OR body) associated with a given URI is correct, allowed and valid for it's use case. Only the third argument for this method is optional.
URISanity.isSameOrigin(uri: String): Boolean
The
.isSameOrigin(uri: String)
method is used to check if the URI being inspected has the sam origin (protocol + host) as the environment (Browser or NodeJS). The only argument for this method is not optional.
MIT License
If you wish to contribute to this project, you are very much welcome. Please, create an issue first before you proceed to create a PR (either to propose a feature or fix a bug). Make sure to clone the repo, checkout to a contribution branch and build the project before making modifications to the codebase.
Run all the following command (in order they appear) below:
$ npm run lint
$ npm run build
$ npm run test
You can find the TS declaration here (simply copy from the gist and paste in the root of your project as urisanity.d.ts
)
FAQs
vet URIs in web and web-like applications with confidence
The npm package urisanity receives a total of 73 weekly downloads. As such, urisanity popularity was classified as not popular.
We found that urisanity 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
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.