Skip to content

Commit dd70c59

Browse files
author
epriestley
committedJul 17, 2012
Use OpaqueEnvelopes for all passwords in Phabricator
Summary: See D2991 / T1526. Two major changes here: - PHP just straight-up logs passwords on ldap_bind() failures. Suppress that with "@" and keep them out of DarkConsole by enabling discard mode. - Use PhutilOpaqueEnvelope whenever we send a password into a call stack. Test Plan: - Created a new account. - Reset password. - Changed password. - Logged in with valid password. - Tried to login with bad password. - Changed password via accountadmin. - Hit various LDAP errors and made sure nothing appears in the logs. Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D2993
1 parent ae2e73c commit dd70c59

File tree

8 files changed

+50
-31
lines changed

8 files changed

+50
-31
lines changed
 

‎scripts/user/account_admin.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@
166166
$editor->makeAdminUser($user, $set_admin);
167167

168168
if ($changed_pass !== false) {
169-
$editor->changePassword($user, $changed_pass);
169+
$envelope = new PhutilOpaqueEnvelope($changed_pass);
170+
$editor->changePassword($user, $envelope);
170171
}
171172

172173
$user->saveTransaction();

‎src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public static function enableDiscardMode() {
2929
self::$discardMode = true;
3030
}
3131

32+
public static function disableDiscardMode() {
33+
self::$discardMode = false;
34+
}
35+
3236
public static function getErrors() {
3337
return self::$errors;
3438
}

‎src/applications/auth/controller/PhabricatorLDAPLoginController.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ public function processRequest() {
3737

3838
if ($request->isFormPost()) {
3939
try {
40-
$this->provider->auth($request->getStr('username'),
41-
$request->getStr('password'));
42-
40+
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
41+
$this->provider->auth($request->getStr('username'), $envelope);
4342
} catch (Exception $e) {
4443
$errors[] = $e->getMessage();
4544
}

‎src/applications/auth/controller/PhabricatorLoginController.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ public function processRequest() {
119119
if (!$errors) {
120120
// Perform username/password tests only if we didn't get rate limited
121121
// by the CAPTCHA.
122-
if (!$user || !$user->comparePassword($request->getStr('password'))) {
122+
123+
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
124+
125+
if (!$user || !$user->comparePassword($envelope)) {
123126
$errors[] = 'Bad username/password.';
124127
}
125128
}

‎src/applications/auth/ldap/PhabricatorLDAPProvider.php

+15-8
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function getLDAPVersion() {
5353
public function retrieveUserEmail() {
5454
return $this->userData['mail'][0];
5555
}
56-
56+
5757
public function retrieveUserRealName() {
5858
return $this->retrieveUserRealNameFromData($this->userData);
5959
}
@@ -106,9 +106,9 @@ public function getUserData() {
106106
return $this->userData;
107107
}
108108

109-
public function auth($username, $password) {
110-
if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) {
111-
throw new Exception('Username and/or password can not be empty');
109+
public function auth($username, PhutilOpaqueEnvelope $password) {
110+
if (strlen(trim($username)) == 0) {
111+
throw new Exception('Username can not be empty');
112112
}
113113

114114
$activeDirectoryDomain =
@@ -121,10 +121,17 @@ public function auth($username, $password) {
121121
$this->getBaseDN();
122122
}
123123

124-
$result = ldap_bind($this->getConnection(), $dn, $password);
124+
$conn = $this->getConnection();
125+
126+
// NOTE: It is very important we suppress any messages that occur here,
127+
// because it logs passwords if it reaches an error log of any sort.
128+
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
129+
$result = @ldap_bind($conn, $dn, $password->openEnvelope());
130+
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
125131

126132
if (!$result) {
127-
throw new Exception('Bad username/password.');
133+
throw new Exception(
134+
"LDAP Error #".ldap_errno($conn).": ".ldap_error($conn));
128135
}
129136

130137
$this->userData = $this->getUser($username);
@@ -184,14 +191,14 @@ public function search($query) {
184191
$row = array();
185192
$entry = $entries[$i];
186193

187-
// Get username, email and realname
194+
// Get username, email and realname
188195
$username = $entry[$this->getSearchAttribute()][0];
189196
if(empty($username)) {
190197
continue;
191198
}
192199
$row[] = $username;
193200
$row[] = $entry['mail'][0];
194-
$row[] = $this->retrieveUserRealNameFromData($entry);
201+
$row[] = $this->retrieveUserRealNameFromData($entry);
195202

196203

197204
$rows[] = $row;

‎src/applications/people/PhabricatorUserEditor.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,18 @@ public function updateUser(PhabricatorUser $user) {
127127
/**
128128
* @task edit
129129
*/
130-
public function changePassword(PhabricatorUser $user, $password) {
130+
public function changePassword(
131+
PhabricatorUser $user,
132+
PhutilOpaqueEnvelope $envelope) {
133+
131134
if (!$user->getID()) {
132135
throw new Exception("User has not been created yet!");
133136
}
134137

135138
$user->openTransaction();
136139
$user->reload();
137140

138-
$user->setPassword($password);
141+
$user->setPassword($envelope);
139142
$user->save();
140143

141144
$log = PhabricatorUserLog::newLog(

‎src/applications/people/controller/settings/panels/PhabricatorUserPasswordSettingsPanelController.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public function processRequest() {
5959
$errors = array();
6060
if ($request->isFormPost()) {
6161
if (!$valid_token) {
62-
if (!$user->comparePassword($request->getStr('old_pw'))) {
62+
$envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
63+
if (!$user->comparePassword($envelope)) {
6364
$errors[] = 'The old password you entered is incorrect.';
6465
$e_old = 'Invalid';
6566
}
@@ -85,9 +86,10 @@ public function processRequest() {
8586
// is changed here the CSRF token check will fail.
8687
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
8788

89+
$envelope = new PhutilOpaqueEnvelope($pass);
8890
id(new PhabricatorUserEditor())
8991
->setActor($user)
90-
->changePassword($user, $pass);
92+
->changePassword($user, $envelope);
9193

9294
unset($unguarded);
9395

‎src/applications/people/storage/PhabricatorUser.php

+14-14
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,18 @@ public function generatePHID() {
7474
PhabricatorPHIDConstants::PHID_TYPE_USER);
7575
}
7676

77-
public function setPassword($password) {
77+
public function setPassword(PhutilOpaqueEnvelope $envelope) {
7878
if (!$this->getPHID()) {
7979
throw new Exception(
8080
"You can not set a password for an unsaved user because their PHID ".
8181
"is a salt component in the password hash.");
8282
}
8383

84-
if (!strlen($password)) {
84+
if (!strlen($envelope->openEnvelope())) {
8585
$this->setPasswordHash('');
8686
} else {
8787
$this->setPasswordSalt(md5(mt_rand()));
88-
$hash = $this->hashPassword($password);
88+
$hash = $this->hashPassword($envelope);
8989
$this->setPasswordHash($hash);
9090
}
9191
return $this;
@@ -129,26 +129,26 @@ private function generateConduitCertificate() {
129129
return Filesystem::readRandomCharacters(255);
130130
}
131131

132-
public function comparePassword($password) {
133-
if (!strlen($password)) {
132+
public function comparePassword(PhutilOpaqueEnvelope $envelope) {
133+
if (!strlen($envelope->openEnvelope())) {
134134
return false;
135135
}
136136
if (!strlen($this->getPasswordHash())) {
137137
return false;
138138
}
139-
$password = $this->hashPassword($password);
140-
return ($password === $this->getPasswordHash());
139+
$password_hash = $this->hashPassword($envelope);
140+
return ($password_hash === $this->getPasswordHash());
141141
}
142142

143-
private function hashPassword($password) {
144-
$password = $this->getUsername().
145-
$password.
146-
$this->getPHID().
147-
$this->getPasswordSalt();
143+
private function hashPassword(PhutilOpaqueEnvelope $envelope) {
144+
$hash = $this->getUsername().
145+
$envelope->openEnvelope().
146+
$this->getPHID().
147+
$this->getPasswordSalt();
148148
for ($ii = 0; $ii < 1000; $ii++) {
149-
$password = md5($password);
149+
$hash = md5($hash);
150150
}
151-
return $password;
151+
return $hash;
152152
}
153153

154154
const CSRF_CYCLE_FREQUENCY = 3600;

0 commit comments

Comments
 (0)
Failed to load comments.