Repository: oppia/oppia

Issue:
OPEN URL REDIRECTION Vulnerability in   https://www.oppia.org/signup?return_url=
Hello,

The url https://www.oppia.org/signup?return_url=<any_website> is open to redirection. 
after login if the user visit the above URL he will be redirected to the <any_webiste> provided.

I've started patching this vulnerability,  i'm adding  further redirection restrictions to only *.oppia.com and oppia.com.

Hence the end result will be :

1. The site will only redirect to their oppia domains , if the provided site is not a *.oppia.com it will redirect to the error page.

so if  https://www.oppia.org/signup?return_url=https://*.oppia.com  it will redirect to *.oppia.com
and other domains like https://www.oppia.org/signup?return_url=https://google.com will go to error page

Please do comment, this is open to suggestions.

Thanks 

OPEN URL REDIRECTION Vulnerability in   https://www.oppia.org/signup?return_url=
Hello,

The url https://www.oppia.org/signup?return_url=<any_website> is open to redirection. 
after login if the user visit the above URL he will be redirected to the <any_webiste> provided.

I've started patching this vulnerability,  i'm adding  further redirection restrictions to only *.oppia.com and oppia.com.

Hence the end result will be :

1. The site will only redirect to their oppia domains , if the provided site is not a *.oppia.com it will redirect to the error page.

so if  https://www.oppia.org/signup?return_url=https://*.oppia.com  it will redirect to *.oppia.com
and other domains like https://www.oppia.org/signup?return_url=https://google.com will go to error page

Please do comment, this is open to suggestions.

Thanks 



Discussion:
@nishealajohn, If you are working on a PR for this. You can submit the same explaining changes done in the PR description itself.
Hi @1995YogeshSharma ,
Sure :), i was in seek for some better implementation than this.
i'll raise a PR soon.
@nishealajohn, If you are working on a PR for this. You can submit the same explaining changes done in the PR description itself.
Hi @1995YogeshSharma ,
Sure :), i was in seek for some better implementation than this.
i'll raise a PR soon.

Resolving patch:
diff --git a/core/controllers/profile.py b/core/controllers/profile.py
--- a/core/controllers/profile.py
+++ b/core/controllers/profile.py
@@ -14,6 +14,8 @@
 
 """Controllers for the profile page."""
 
+import re
+
 from core.controllers import acl_decorators
 from core.controllers import base
 from core.domain import email_manager
@@ -248,7 +250,9 @@ class SignupPage(base.BaseHandler):
     def get(self):
         """Handles GET requests."""
         return_url = str(self.request.get('return_url', self.request.uri))
-
+        # Validating return_url for no external redirections.
+        if re.match('^/[^//]', return_url) is None:
+            return_url = '/'
         if user_services.has_fully_registered(self.user_id):
             self.redirect(return_url)
             return


Test patch:
diff --git a/core/controllers/profile_test.py b/core/controllers/profile_test.py
--- a/core/controllers/profile_test.py
+++ b/core/controllers/profile_test.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 """Tests for the profile page."""
+import re
 
 from constants import constants
 from core.domain import exp_domain
@@ -45,6 +46,51 @@ def test_going_somewhere_else_while_signing_in_logs_user_out(self):
 
         self.logout()
 
+    def test_to_check_url_redirection_in_signup(self):
+        """To validate the redirections from return_url."""
+        self.login(self.EDITOR_EMAIL)
+        response = self.get_html_response(feconf.SIGNUP_URL)
+        csrf_token = self.get_csrf_token_from_response(response)
+
+        # Registering this user fully.
+        self.post_json(
+            feconf.SIGNUP_DATA_URL,
+            {'username': 'abc', 'agreed_to_terms': True},
+            csrf_token=csrf_token)
+
+        def strip_domain_from_location_header(url):
+            """To strip the domain form the location url."""
+            splitted_url = re.match(r'(http[s]?:\/\/)?([^\/\s]+\/)(.*)', url)
+            return splitted_url.group(3)
+
+        response = self.get_html_response(
+            '/signup?return_url=https://google.com', expected_status_int=302)
+        self.assertEqual('', strip_domain_from_location_header(
+            response.headers['location']))
+
+        response = self.get_html_response(
+            '/signup?return_url=//google.com', expected_status_int=302)
+        self.assertEqual('', strip_domain_from_location_header(
+            response.headers['location']))
+
+        response = self.get_html_response(
+            '/signup?return_url=/page#hello', expected_status_int=302)
+        self.assertEqual('page', strip_domain_from_location_header(
+            response.headers['location']))
+
+        response = self.get_html_response(
+            '/signup?return_url=/page/hello', expected_status_int=302)
+        self.assertEqual('page/hello', strip_domain_from_location_header(
+            response.headers['location']))
+
+        response = self.get_html_response(
+            '/signup?return_url=/page/hello?id=tests', expected_status_int=302)
+        self.assertEqual(
+            'page/hello?id=tests', strip_domain_from_location_header(
+                response.headers['location']))
+
+        self.logout()
+
     def test_accepting_terms_is_handled_correctly(self):
         self.login(self.EDITOR_EMAIL)
         response = self.get_html_response(feconf.SIGNUP_URL)