Skip to content

Commit e7bace5

Browse files
committed
basic logging, adds logs folder to root dir
htaccess to block access to the logs by default, only log warnings simple config check to see if that folder is writable warning if changeNoFee is used warning if setLocked is used warning if changeAdmin is used warning if when logging in that IP is different than saved IP info if a login fails with bad user or password warning if a user is locked via failed logins info if an update/etc fails with bad pin warning if a user is locked via failed pins info when a pin request is sent warning when a pin request email doesn't send warning when trying to request pin reset and incorrect password info when a twofactor token sent warning if twofactor email doesn't send warning when a user tries to request multiple of the same type of token info when a twofactor token is deleted warning if a twofactor token fails to delete warning when an invalid change password token is used info on successful account update warning when reset password is called and IP doesn't match saved IP, info otherwise warning if isAuthenticated falls through and kills a session
1 parent afdf3ab commit e7bace5

13 files changed

+172
-29
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# Logs
1212
/cronjobs/logs/*.txt
1313
/cronjobs/logs/*.txt.*.gz
14+
/logs/*
1415

1516
# Test configs
1617
public/include/config/global.inc.scrypt.php

.htaccess

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ErrorDocument 404 /public/index.php?page=error&action=404
2+
RedirectMatch 404 /logs(/|$)
3+
Options -Indexes

logs/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hi

public/include/admin_checks.php

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
}
1616

1717
// setup checks
18+
// logging
19+
if ($config['logging']['enabled']) {
20+
if (!is_writable($config['logging']['path'])) {
21+
$error[] = "Logging is enabled but we can't write in the logging path";
22+
}
23+
}
1824
// check if memcache isn't available but enabled in config -> error
1925
if (!class_exists('Memcached') && $config['memcache']['enabled']) {
2026
$error[] = "You have memcache enabled in your config and it's not available. Install the package on your system.";

public/include/autoloader.inc.php

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// Default classes
1313
require_once(CLASS_DIR . '/debug.class.php');
1414
require_once(INCLUDE_DIR . '/lib/KLogger.php');
15+
if ($config['logging']['enabled']) {
16+
$log = new KLogger($config['logging']['path']."/".$config['logging']['file'], $config['logging']['level']);
17+
}
1518
if ($config['mysql_filter']) {
1619
require_once(CLASS_DIR . '/strict.class.php');
1720
}

public/include/classes/base.class.php

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public function getTableName() {
1919
public function setDebug($debug) {
2020
$this->debug = $debug;
2121
}
22+
public function setLog($log) {
23+
$this->log = $log;
24+
}
2225
public function setMysql($mysqli) {
2326
$this->mysqli = $mysqli;
2427
}

public/include/classes/notification.class.php

+4
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ public function updateSettings($account_id, $data) {
120120
$this->setErrorMessage($this->getErrorMsg('E0047', $failed));
121121
return $this->sqlError();
122122
}
123+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
124+
$this->log->LogInfo("User $account_id updated notification settings from [".$_SERVER['REMOTE_ADDR']."]");
125+
}
123126
return true;
124127
}
125128

@@ -154,6 +157,7 @@ public function sendNotification($account_id, $strType, $aMailData) {
154157

155158
$notification = new Notification();
156159
$notification->setDebug($debug);
160+
$notification->setLog($log);
157161
$notification->setMysql($mysqli);
158162
$notification->setSmarty($smarty);
159163
$notification->setConfig($config);

public/include/classes/payout.class.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,20 @@ public function createPayout($account_id=NULL, $strToken) {
4141
if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['withdraw']) {
4242
$tValid = $this->token->isTokenValid($account_id, $strToken, 7);
4343
if ($tValid) {
44-
$this->token->deleteToken($strToken);
44+
$delete = $this->token->deleteToken($strToken);
45+
if ($delete) {
46+
return true;
47+
} else {
48+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
49+
$this->log->LogInfo("User $account_id requested manual payout but the token deletion failed from [".$_SERVER['REMOTE_ADDR']."]");
50+
}
51+
$this->setErrorMessage('Unable to delete token');
52+
return false;
53+
}
4554
} else {
55+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
56+
$this->log->LogInfo("User $account_id requested manual payout using an invalid token from [".$_SERVER['REMOTE_ADDR']."]");
57+
}
4658
$this->setErrorMessage('Invalid token');
4759
return false;
4860
}
@@ -67,6 +79,7 @@ public function setProcessed($id) {
6779

6880
$oPayout = new Payout();
6981
$oPayout->setDebug($debug);
82+
$oPayout->setLog($log);
7083
$oPayout->setMysql($mysqli);
7184
$oPayout->setConfig($config);
7285
$oPayout->setToken($oToken);

public/include/classes/user.class.php

+115-24
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,23 @@ public function getSignupTime($id) {
6969
}
7070
public function changeNoFee($id) {
7171
$field = array('name' => 'no_fees', 'type' => 'i', 'value' => !$this->isNoFee($id));
72+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
73+
$this->log->LogWarn($this->getUserName($id)." changed no_fees to ".$this->isNoFee($id)." from [".$_SERVER['REMOTE_ADDR']."]");
74+
}
7275
return $this->updateSingle($id, $field);
7376
}
7477
public function setLocked($id, $value) {
7578
$field = array('name' => 'is_locked', 'type' => 'i', 'value' => $value);
79+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
80+
$this->log->LogWarn($this->getUserName($id)." changed is_locked to $value from [".$_SERVER['REMOTE_ADDR']."]");
81+
}
7682
return $this->updateSingle($id, $field);
7783
}
7884
public function changeAdmin($id) {
7985
$field = array('name' => 'is_admin', 'type' => 'i', 'value' => !$this->isAdmin($id));
86+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
87+
$this->log->LogWarn($this->getUserName($id)." changed is_admin to ".$this->isAdmin($id)." from [".$_SERVER['REMOTE_ADDR']."]");
88+
}
8089
return $this->updateSingle($id, $field);
8190
}
8291
public function setUserFailed($id, $value) {
@@ -145,6 +154,11 @@ public function checkLogin($username, $password) {
145154
$lastLoginTime = $this->getLastLogin($uid);
146155
$this->updateLoginTimestamp($uid);
147156
$getIPAddress = $this->getUserIp($uid);
157+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
158+
if ($getIPAddress !== $_SERVER['REMOTE_ADDR']) {
159+
$this->log->LogWarn("$username has logged in with a different IP [".$_SERVER['REMOTE_ADDR']."] saved is [$getIPAddress]");
160+
}
161+
}
148162
$setIPAddress = $this->setUserIp($uid, $_SERVER['REMOTE_ADDR']);
149163
$this->createSession($username, $getIPAddress, $lastLoginTime);
150164
if ($setIPAddress) {
@@ -172,11 +186,17 @@ public function checkLogin($username, $password) {
172186
}
173187
}
174188
$this->setErrorMessage("Invalid username or password");
189+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
190+
$this->log->LogInfo("$username failed login from [".$_SERVER['REMOTE_ADDR']."]");
191+
}
175192
if ($id = $this->getUserId($username)) {
176193
$this->incUserFailed($id);
177194
// Check if this account should be locked
178195
if (isset($this->config['maxfailed']['login']) && $this->getUserFailed($id) >= $this->config['maxfailed']['login']) {
179196
$this->setLocked($id, 1);
197+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
198+
$this->log->LogWarn("$username locked via failed logins from [".$_SERVER['REMOTE_ADDR']."] saved is [".$this->getUserIp($this->getUserId($username))."]");
199+
}
180200
if ($token = $this->token->createToken('account_unlock', $id)) {
181201
$aData['token'] = $token;
182202
$aData['username'] = $username;
@@ -203,17 +223,23 @@ public function checkPin($userId, $pin=false) {
203223
$pin_hash = $this->getHash($pin);
204224
if ($stmt->bind_param('is', $userId, $pin_hash) && $stmt->execute() && $stmt->bind_result($row_pin) && $stmt->fetch()) {
205225
$this->setUserPinFailed($userId, 0);
206-
return $pin_hash === $row_pin;
226+
return ($pin_hash === $row_pin);
227+
}
228+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
229+
$this->log->LogInfo($this->getUserName($userId)." incorrect pin from [".$_SERVER['REMOTE_ADDR']."]");
207230
}
208231
$this->incUserPinFailed($userId);
209232
// Check if this account should be locked
210233
if (isset($this->config['maxfailed']['pin']) && $this->getUserPinFailed($userId) >= $this->config['maxfailed']['pin']) {
211234
$this->setLocked($userId, 1);
235+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
236+
$this->log->LogWarn($this->getUserName($userId)." was locked via incorrect pins from [".$_SERVER['REMOTE_ADDR']."]");
237+
}
212238
if ($token = $this->token->createToken('account_unlock', $userId)) {
213239
$username = $this->getUserName($userId);
214240
$aData['token'] = $token;
215241
$aData['username'] = $username;
216-
$aData['email'] = $this->getUserEmail($username);;
242+
$aData['email'] = $this->getUserEmail($username);
217243
$aData['subject'] = 'Account auto-locked';
218244
$this->mail->sendMail('notifications/locked', $aData);
219245
}
@@ -234,17 +260,25 @@ public function generatePin($userID, $current) {
234260
$newpin = $this->getHash($newpin);
235261
$aData['subject'] = 'PIN Reset Request';
236262
$stmt = $this->mysqli->prepare("UPDATE $this->table SET pin = ? WHERE ( id = ? AND pass = ? )");
237-
238263
if ($this->checkStmt($stmt) && $stmt->bind_param('sis', $newpin, $userID, $current) && $stmt->execute()) {
239264
if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
240265
if ($this->mail->sendMail('pin/reset', $aData)) {
266+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
267+
$this->log->LogInfo($this->getUserName($userID)." was sent a pin reset from [".$_SERVER['REMOTE_ADDR']."]");
268+
}
241269
return true;
242270
} else {
271+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
272+
$this->log->LogWarn($this->getUserName($userID)." request a pin reset but the mailing failed from [".$_SERVER['REMOTE_ADDR']."]");
273+
}
243274
$this->setErrorMessage('Unable to send mail to your address');
244275
return false;
245276
}
246277
}
247278
}
279+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
280+
$this->log->LogWarn($this->getUserName($userID)." incorrect pin reset attempt from [".$_SERVER['REMOTE_ADDR']."]");
281+
}
248282
$this->setErrorMessage( 'Unable to generate PIN, current password incorrect?' );
249283
return false;
250284
}
@@ -319,14 +353,23 @@ public function sendChangeConfigEmail($strType, $userID) {
319353
default:
320354
$aData['subject'] = '';
321355
}
356+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
357+
$this->log->LogInfo($this->getUserName($userID)." was sent a $strType token from [".$_SERVER['REMOTE_ADDR']."]");
358+
}
322359
if ($this->mail->sendMail('notifications/'.$strType, $aData)) {
323360
return true;
324361
} else {
325362
$this->setErrorMessage('Failed to send the notification');
363+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
364+
$this->log->LogWarn($this->getUserName($userID)." requested a $strType token but the mailing failed from [".$_SERVER['REMOTE_ADDR']."]");
365+
}
326366
return false;
327367
}
328368
}
329-
$this->setErrorMessage('A request has already been sent to your e-mail address. Please wait 10 minutes for it to expire.');
369+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
370+
$this->log->LogWarn($this->getUserName($userID)." attempted to request multiple $strType tokens from [".$_SERVER['REMOTE_ADDR']."]");
371+
}
372+
$this->setErrorMessage('A request has already been sent to your e-mail address. Please wait an hour for it to expire.');
330373
return false;
331374
}
332375

@@ -351,25 +394,44 @@ public function updatePassword($userID, $current, $new1, $new2, $strToken) {
351394
}
352395
$current = $this->getHash($current);
353396
$new = $this->getHash($new1);
397+
if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) {
398+
$tValid = $this->token->isTokenValid($userID, $strToken, 6);
399+
if ($tValid) {
400+
if ($this->token->deleteToken($strToken)) {
401+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
402+
$this->log->LogInfo($this->getUserName($userID)." deleted change password token from [".$_SERVER['REMOTE_ADDR']."]");
403+
}
404+
// token deleted, continue
405+
} else {
406+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
407+
$this->log->LogWarn($this->getUserName($userID)." change password token failed to delete from [".$_SERVER['REMOTE_ADDR']."]");
408+
}
409+
$this->setErrorMessage('Token deletion failed');
410+
return false;
411+
}
412+
} else {
413+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
414+
$this->log->LogWarn($this->getUserName($userID)." attempted to use an invalid change password token from [".$_SERVER['REMOTE_ADDR']."]");
415+
}
416+
$this->setErrorMessage('Invalid token');
417+
return false;
418+
}
419+
}
354420
$stmt = $this->mysqli->prepare("UPDATE $this->table SET pass = ? WHERE ( id = ? AND pass = ? )");
355421
if ($this->checkStmt($stmt)) {
356422
$stmt->bind_param('sis', $new, $userID, $current);
357423
$stmt->execute();
358424
if ($stmt->errno == 0 && $stmt->affected_rows === 1) {
359-
// twofactor - consume the token if it is enabled and valid
360-
if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['changepw']) {
361-
$tValid = $this->token->isTokenValid($userID, $strToken, 6);
362-
if ($tValid) {
363-
$this->token->deleteToken($strToken);
364-
} else {
365-
$this->setErrorMessage('Invalid token');
366-
return false;
367-
}
425+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
426+
$this->log->LogInfo($this->getUserName($userID)." updated password from [".$_SERVER['REMOTE_ADDR']."]");
368427
}
369428
return true;
370429
}
371430
$stmt->close();
372431
}
432+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
433+
$this->log->LogWarn($this->getUserName($userID)." incorrect password update attempt from [".$_SERVER['REMOTE_ADDR']."]");
434+
}
373435
$this->setErrorMessage( 'Unable to update password, current password wrong?' );
374436
return false;
375437
}
@@ -434,20 +496,38 @@ public function updateAccount($userID, $address, $threshold, $donate, $email, $i
434496
$threshold = min($this->config['ap_threshold']['max'], max(0, floatval($threshold)));
435497
$donate = min(100, max(0, floatval($donate)));
436498

437-
// We passed all validation checks so update the account
438-
$stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, is_anonymous = ? WHERE id = ?");
439-
if ($this->checkStmt($stmt) && $stmt->bind_param('sddsii', $address, $threshold, $donate, $email, $is_anonymous, $userID) && $stmt->execute())
440-
// twofactor - consume the token if it is enabled and valid
441-
if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) {
442-
$tValid = $this->token->isTokenValid($userID, $strToken, 5);
443-
if ($tValid) {
444-
$this->token->deleteToken($strToken);
499+
// twofactor - consume the token if it is enabled and valid
500+
if ($this->config['twofactor']['enabled'] && $this->config['twofactor']['options']['details']) {
501+
$tValid = $this->token->isTokenValid($userID, $strToken, 5);
502+
if ($tValid) {
503+
if ($this->token->deleteToken($strToken)) {
504+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
505+
$this->log->LogInfo($this->getUserName($userID)." deleted account update token for [".$_SERVER['REMOTE_ADDR']."]");
506+
}
445507
} else {
446-
$this->setErrorMessage('Invalid token');
508+
$this->setErrorMessage('Token deletion failed');
509+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
510+
$this->log->LogWarn($this->getUserName($userID)." updated their account details but token deletion failed from [".$_SERVER['REMOTE_ADDR']."]");
511+
}
447512
return false;
448513
}
514+
} else {
515+
$this->setErrorMessage('Invalid token');
516+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
517+
$this->log->LogWarn($this->getUserName($userID)." attempted to use an invalid token account update token from [".$_SERVER['REMOTE_ADDR']."]");
518+
}
519+
return false;
520+
}
521+
}
522+
523+
// We passed all validation checks so update the account
524+
$stmt = $this->mysqli->prepare("UPDATE $this->table SET coin_address = ?, ap_threshold = ?, donate_percent = ?, email = ?, is_anonymous = ? WHERE id = ?");
525+
if ($this->checkStmt($stmt) && $stmt->bind_param('sddsii', $address, $threshold, $donate, $email, $is_anonymous, $userID) && $stmt->execute()) {
526+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
527+
$this->log->LogInfo($this->getUserName($userID)." updated their account details from [".$_SERVER['REMOTE_ADDR']."]");
449528
}
450529
return true;
530+
}
451531
// Catchall
452532
$this->setErrorMessage('Failed to update your account');
453533
$this->debug->append('Account update failed: ' . $this->mysqli->error);
@@ -542,7 +622,7 @@ public function logoutUser() {
542622
$port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]);
543623
$pushto = $_SERVER['SCRIPT_NAME'].'?page=login';
544624
$location = (@$_SERVER['HTTPS'] == 'on') ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $pushto : 'http://' . $_SERVER['SERVER_NAME'] . $port . $pushto;
545-
// if (!headers_sent()) header('Location: ' . $location);
625+
if (!headers_sent()) header('Location: ' . $location);
546626
exit('<meta http-equiv="refresh" content="0; url=' . $location . '"/>');
547627
}
548628

@@ -789,6 +869,13 @@ public function initResetPassword($username) {
789869
}
790870
$aData['username'] = $this->getUserName($this->getUserId($username, true));
791871
$aData['subject'] = 'Password Reset Request';
872+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
873+
if ($_SERVER['REMOTE_ADDR'] !== $this->getUserIp($this->getUserId($username, true))) {
874+
$this->log->LogWarn("$username requested password reset from [".$_SERVER['REMOTE_ADDR']."] saved is [".$this->getUserIp($this->getUserId($username, true))."]");
875+
} else {
876+
$this->log->LogInfo("$username requested password reset from [".$_SERVER['REMOTE_ADDR']."] saved is [".$this->getUserIp($this->getUserId($username, true))."]");
877+
}
878+
}
792879
if ($this->mail->sendMail('password/reset', $aData)) {
793880
return true;
794881
} else {
@@ -812,7 +899,10 @@ public function isAuthenticated($logout=true) {
812899
$this->getUserIp($_SESSION['USERDATA']['id']) == $_SERVER['REMOTE_ADDR']
813900
) return true;
814901
// Catchall
815-
if ($logout == true) $this->logoutUser($_SERVER['REQUEST_URI']);
902+
if ($this->config['logging']['enabled'] && $this->config['logging']['level'] > 0) {
903+
$this->log->LogWarn("Forcing logout, user is locked or IP changed mid session from [".$_SERVER['REMOTE_ADDR']."]");
904+
}
905+
if ($logout == true) $this->logoutUser();
816906
return false;
817907
}
818908

@@ -853,6 +943,7 @@ public function getCurrentIP($trustremote=true, $checkclient=false, $checkforwar
853943
// Make our class available automatically
854944
$user = new User();
855945
$user->setDebug($debug);
946+
$user->setLog($log);
856947
$user->setMysql($mysqli);
857948
$user->setSalt($config['SALT']);
858949
$user->setSmarty($smarty);

public/include/config/security.inc.dist.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44
/**
55
* Misc
66
* Extra security settings
7-
*
7+
*
88
**/
99
$config['https_only'] = false;
1010
$config['mysql_filter'] = true;
1111

12+
/**
13+
* Logging
14+
* Log security issues - 0 = disabled, 2 = everything, 3 = warnings only
15+
*
16+
*/
17+
$config['logging']['enabled'] = true;
18+
$config['logging']['level'] = 3;
19+
$config['logging']['path'] = realpath(BASEPATH.'../logs');
20+
$config['logging']['file'] = date('Y-m-d').'.security.log';
21+
1222
/**
1323
* Memcache Rate Limiting
1424
* Rate limit requests using Memcache

0 commit comments

Comments
 (0)