Ensure URL is relative before navigating via JavaScript's location.replace() - javascript

I have a login page https://example.com/login#destination where destination is the target URL the user was trying to navigate to when they were required to log in.
(i.e. https://example.com/destination)
The JavaScript I was thinking about using was
function onSuccessfulLogin() {
location.replace(location.hash.substring(1) || 'default')
}
This would result in an XSS vulnerability, by an attacker providing the link
https://example.com/login#javascript:..
Also I need to prevent navigation to a lookalike site after login.
https://example.com/login#https://looks-like-example.com
or https://example.com/login#//looks-like-example.com
How can I adjust onSuccessfulLogin to ensure the URL provided in the hash # portion is a relative URL, and not starting with javascript:, https:, // or any other absolute navigation scheme?
One thought is to evaluate the URL, and see if location.origin remains unchanged before navigating. Can you suggest how to do this, or a better approach?

From OWASP recommendations on Preventing Unvalidated Redirects and Forwards:
It is recommended that any such destination input be mapped to a value, rather than the actual URL or portion of the URL, and that server side code translate this value to the target URL.
So a safe approach would be mapping some keys to actual URLs:
// https://example.com/login#destination
var keyToUrl = {
destination: 'https://example.com/destination',
defaults: 'https://example.com/default'
};
function onSuccessfulLogin() {
var hash = location.hash.substring(1);
var url = keyToUrl[hash] || keyToUrl.defaults;
location.replace(url);
}
You could also consider providing only path part of the URL and appending it with a hostname in the code:
// https://example.com/login#destination
function onSuccessfulLogin() {
var path = location.hash.substring(1);
var url = 'https://example.com/' + path;
location.replace(url);
}
I would stick to the mapping though.

That is a very good point about the XSS vulnerability.
I believe all protocols only use English alphabetic characters, so a regex like /^[a-z]+:/i would check for those. Alternately if we're feeling more inclusive, /^[^:\/?]+:/ allows anything but a / or ? followed by a :. Then we can combine that with /^\/\/ to test for a protocol-free URL, which gives us:
// Either
var rexIsProtocol = /(?:^[a-z]+:)|(?:^\/\/)/i;
// Or
var rexIsProtocol = /(?:^[^:\/?]+:)|(?:^\/\/)/i;
Then the test is like this:
var url = location.hash.substring(1).trim(); // trim to deal with whitespace
if (rexIsProtocol.test(url)) {
// It starts with a protocol
} else {
// It doesn't
}
That said, the only one I think you need to be particularly bothered by is the javascript: pseudo-protcol, so you might just test for that.

Related

How to check if url scheme is present in a url string javascript

I am trying to solve an issue where I need to know if there is a URL scheme (not limited to http, https) prepended to my url string.
I could do link.indexOf(://); and then take the substring of anything before the "://", but if I have a case for eg:
example.com?url=http://www.eg.com
in this case, the substring will return me the whole string i.e.
example.com?url=http which is incorrect. It should return me "", since my url does not have a protocol prepended.
I need to find out whether the url is prepended with a protocol or not.
You can do it quite easily with a little bit of regex. The pattern /^[a-z0-9]+:\/\// will be able to extract it.
If you just want to test if it has it, use pattern.test() to get a boolean:
/^[a-z0-9]+:\/\//.test(url); // true
If you want what it is, use url.match() and wrap the protocol portion in parentheses:
url.match(/^([a-z0-9]+):\/\//)[1] // https
Here is a runnable example with a few example URLs.
const urls = ['file://test.com', 'http://test.com', 'https://test.com', 'example.com?http'];
console.log(
urls.map(url => (url.match(/^([a-z0-9]+):\/\//) || [])[1])
);
You could use the URL API which is supported in most browsers.
function getProtocol(str) {
try {
var u = new URL(str);
return u.protocol.slice(0, -1);
} catch (e) {
return '';
}
}
Usage
getProtocol('example.com?url=http://www.eg.com'); // returns ""
getProtocol('https://example.com?url=http://www.eg.com'); // returns "https"

Getting the proper URL in an angular project

I have a working Js plugin thats written in jQuery. For my plugin to work, I need to get the URL, but without the anchor tag that references an element by id. That is, only getting http://example.com/content/1/ instead of say http://example.com/content/1/#comments.
I am doing this with the following function :
var getProperURL = function() {
return window.location.protocol + '//' + window.location.hostname + window.location.pathname;
}
This works most of the time. However, I ran this on an angular project, and I only get the protocol and the hostname. How do I do this for AngularJs?
To retrieve only the path without the hash, you can use $location.path().
See the official documentation: location.path()
This method is getter / setter.
Return path of current url when called without any parameter.
Change path when called with parameter and return $location.
Note: Path should always begin with forward slash (/), this method will add the forward slash if it is missing.
Return full url representation with all segments encoded according to rules specified in RFC 3986.
Inject location and retrieve all datas you need. In the guide is plenty of examples:
// given url http://example.com/#/some/path?foo=bar&baz=xoxo
var path = $location.path();
// => "/some/path"
and in your website:
// given url http://example.com/content/1/#comments
var path = $location.path();
// => "/content/1"

How do we update URL or query strings using javascript/jQuery without reloading the page?

Is there a way to update the URL programatically without reloading the page?
EDIT: I added something in the title in post .I just want to make it clear that I don't want to reload the page
Yes and no. All the common web browsers has a security measure to prevent that. The goal is to prevent people from creating replicas of websites, change the URL to make it look correct, and then be able to trick people and get their info.
However, some HTML5 compatible web browsers has implemented an History API that can be used for something similar to what you want:
if (history.pushState) {
var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?myNewUrlQuery=1';
window.history.pushState({path:newurl},'',newurl);
}
I tested, and it worked fine. It does not reload the page, but it only allows you to change the URL query. You would not be able to change the protocol or the host values.
For more information:
http://diveintohtml5.info/history.html
https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history
Yes - document.location = "http://my.new.url.com"
You can also retrieve it the same way eg.
var myURL = document.location;
document.location = myURL + "?a=parameter";
The location object has a number of useful properties too:
hash Returns the anchor portion of a URL
host Returns the hostname and port of a URL
hostname Returns the hostname of a URL
href Returns the entire URL
pathname Returns the path name of a URL
port Returns the port number the server uses for a URL
protocol Returns the protocol of a URL
search Returns the query portion of a URL
EDIT:
Setting the hash of the document.location shouldn't reload the page, just alter where on the page the focus is. So updating to #myId will scroll to the element with id="myId". If the id doesn't exist I believe nothing will happen? (Need to confirm on various browsers though)
EDIT2: To make it clear, not just in a comment:
You can't update the whole URL with javascript without changing the page, this is a security restriction. Otherwise you could click on a link to a random page, crafted to look like gmail, and instantly change the URL to www.gmail.com and steal people's login details.
You can change the part after the domain on some browsers to cope with AJAX style things, but that's already been linked to by Osiris. What's more, you probably shouldn't do this, even if you could. The URL tells the user where he/she is on your site. If you change it without changing the page contents, it's becomes a little confusing.
You can use :
window.history.pushState('obj', 'newtitle', newUrlWithQueryString)
Use
window.history.replaceState({}, document.title, updatedUri);
To update Url without reloading the page
var url = window.location.href;
var urlParts = url.split('?');
if (urlParts.length > 0) {
var baseUrl = urlParts[0];
var queryString = urlParts[1];
//update queryString in here...I have added a new string at the end in this example
var updatedQueryString = queryString + 'this_is_the_new_url'
var updatedUri = baseUrl + '?' + updatedQueryString;
window.history.replaceState({}, document.title, updatedUri);
}
To remove Query string without reloading the page
var url = window.location.href;
if (url.indexOf("?") > 0) {
var updatedUri = url.substring(0, url.indexOf("?"));
window.history.replaceState({}, document.title, updatedUri);
}
Define a new URL object, assign it the current url, append your parameter(s) to that URL object and finally push it to your browsers state.
var url = new URL(window.location.href);
//var url = new URL(window.location.origin + window.location.pathname) <- flush existing parameters
url.searchParams.append("order", orderId);
window.history.pushState(null, null, url);
Yes
document.location is the normal way.
However document.location is effectively the same as window.location, except for window.location is a bit more supported in older browsers so may be the prefferable choice.
Check out this thread on SO for more info:
What's the difference between window.location and document.location in JavaScript?
Prefix URL changes with a hashtag to avoid a redirect.
This redirects
location.href += '&test='true';
This doesn't redirect
location.href += '#&test='true';
Plain javascript: document.location = 'http://www.google.com';
This will cause a browser refresh though - consider using hashes if you're in need of having the URL updated to implement some kind of browsing history without reloading the page. You might want to look into jQuery.hashchange if this is the case.
You'll need to be more specific. What do you mean by 'update the URL'? It could mean automatically navigating to a different page, which is certainly possible.
If you want to just update the contents of the address bar without reloading the page, see Modify the URL without reloading the page
Yes - document.location.hash for queries

JavaScript current URL check

I am wondering how I would get JavaScript to check if a user is on a certain URL so i can build an if statement from the result.
My reasoning is that if a user clicks on a link in the menu and they are currently on trucks.php the javascript will redirect them to a certain page. If they are not on trucks.php they will be directed to a different page.
Cheers guys.
The current location is in location.href.
The location object also contains some other useful fields:
location.hash: The part after the # in the URL
location.host: Hostname including port (if specified)
location.hostname: Just the hostname
location.pathname: The requested URI without protocol/host/port; starting with a /
location.port: The port - only if one is specified in the URL
location.protocol: Usually 'http:' or 'https:' - mind the colon at the end
In your case the most fail-safe way to check if the filename is trucks.php is this:
var parts = location.pathname.split('/');
if(parts[parts.length - 1] == 'trucks.php') {
location.href = 'some-other-page';
}
If you want to redirect without keeping the current page in history, use the following code instead of the location.href assignment:
location.replace('some-other-page');
Use window.location.href to get the current URL, or window.location.pathname to get just the path. For your specific problem, just the path name is required for the solution:
if (window.location.pathname == "/trucks.php")
window.location = "/somewhereelse.php";
Check out the MDC documentation for window.location.
Use window.location

Generate canonical / real URL based on base.href or location

Is there a method/function to get the canonical / transformed URL, respecting any base.href setting of the page?
I can get the base URL via (in jQuery) using $("base").attr("href") and I could use string methods to parse the URL meant to being made relative to this, but
$("base").attr("href") has no host, path etc attributes (like window.location has)
manually putting this together is rather tedious
E.g., given a base.href of "http://example.com/foo/" and a relative URL "/bar.js", the result should be: "http://example.com/bar.js"
If base.href is not present, the URL should be made relative to window.location.
This should handle non-existing base.href (using location as base in this case).
Is there a standard method available for this already?
(I'm looking for this, since jQuery.getScript fails when using a relative URL like "/foo.js" and BASE tag is being used (FF3.6 makes an OPTIONS request, and nginx cannot handle this). When using the full URL (base.href.host + "/foo.js", it works).)
Does this do the trick?
function resolveUrl(url){
if (!url) {
throw new Error("url is undefined or empty");
}
var reParent = /[\-\w]+\/\.\.\//, // matches a foo/../ expression
reDoubleSlash = /([^:])\/\//g; // matches // anywhere but in the protocol
// replace all // except the one in proto with /
url = url.replace(reDoubleSlash, "$1/");
var base = (document.getElementsByTagName('BASE')[0] && document.getElementsByTagName('BASE')[0].href) || "";
// If the url is a valid url we do nothing
if (!url.match(/^(http||https):\/\//)) {
// If this is a relative path
var path = (url.substring(0, 1) === "/") ? base : location.pathname;
if (path.substring(path.length - 1) !== "/") {
path = path.substring(0, path.lastIndexOf("/") + 1);
}
if (!url.match(/^(http||https):\/\//)) {
url = location.protocol + "//" + location.host + path + url;
}
}
// reduce all 'xyz/../' to just ''
while (reParent.test(url)) {
url = url.replace(reParent, "");
}
return url;
}
It is modified from some code I had around, so it hasn't been tested
js-uri is a useful javascript library to accomplish this: http://code.google.com/p/js-uri/
You would still need to handle the base.href vs window.location parts, but the rest could be accomplished with the library.

Categories

Resources