diff --git a/.gitignore b/.gitignore index 3f61187e..03ef8272 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Local Config /public/include/config/global.inc.php +/public/include/config/security.inc.php # Templates /public/templates/compile/*.php @@ -14,7 +15,6 @@ # Test configs public/include/config/global.inc.scrypt.php public/include/config/global.inc.sha.php -exploits/* # IDE Settings /.idea/* diff --git a/cronjobs/shared.inc.php b/cronjobs/shared.inc.php index ecc3da11..fc0d4c9a 100644 --- a/cronjobs/shared.inc.php +++ b/cronjobs/shared.inc.php @@ -18,6 +18,21 @@ limitations under the License. */ +define('SECURITY', '*)WT#&YHfd'); +// Whether or not to check SECHASH for validity, still checks if SECURITY defined as before if disabled +define('SECHASH_CHECK', false); + +// Nothing below here to configure, move along... + +// change SECHASH every second, we allow up to 3 sec back for slow servers +if (SECHASH_CHECK) { + function fip($tr=0) { return md5(SECURITY.(time()-$tr).SECURITY); } + define('SECHASH', fip()); + function cfip() { return (fip()==SECHASH||fip(1)==SECHASH||fip(2)==SECHASH) ? 1 : 0; } +} else { + function cfip() { return (@defined('SECURITY')) ? 1 : 0; } +} + // MODIFY THIS // We need to find our include files so set this properly define("BASEPATH", "../public/"); diff --git a/public/include/admin_checks.php b/public/include/admin_checks.php new file mode 100644 index 00000000..1a28da32 --- /dev/null +++ b/public/include/admin_checks.php @@ -0,0 +1,113 @@ +isAdmin(@$_SESSION['USERDATA']['id'])) { + + if (!include_once(INCLUDE_DIR . '/lib/jsonRPCClient.php')) die('Unable to load libs'); + + $notice = array(); + $enotice = array(); + $error = array(); + + // setup some basic stuff for checking + $apache_user = posix_getuid(); + $apache_user = (function_exists('posix_getpwuid')) ? posix_getpwuid($apache_user) : $apache_user; + + // setup checks + // check if memcache isn't available but enabled in config -> error + if (!class_exists('Memcached') && $config['memcache']['enabled']) { + $error[] = "You have memcache enabled in your config and it's not available. Install the package on your system."; + } + // if it's not enabled, test it if it exists, if it works -> error tell them to enable, -> otherwise notice it's disabled + if (!$config['memcache']['enabled']) { + if (PHP_OS == 'WINNT') { + require_once(CLASS_DIR . 'memcached.class.php'); + } + if (class_exists('Memcached')) { + $memcache_test = @new Memcached(); + $memcache_test_add = @$memcache_test->addServer($config['memcache']['host'], $config['memcache']['port']); + $randmctv = rand(5,10); + $memcache_test_set = @$memcache_test->set('test_mpos_setval', $randmctv); + $memcache_test_get = @$memcache_test->get('test_mpos_setval'); + } + if (class_exists('Memcached') && $memcache_test_get == $randmctv) { + $error[] = "You have memcache disabled in the config and it's available & works! Enable it."; + } else { + $notice[] = "Memcache is disabled; Almost every linux distro has packages for it, you should be using it if you can."; + } + } + // check if we can write templates/cache and templates/compile -> error + if (!is_writable(THEME_DIR.'/cache')) { + $error[] = "templates/cache folder is not writable for uid {$apache_user['name']}"; + } + if (!is_writable(THEME_DIR.'/compile')) { + $error[] = "templates/compile folder is not writable for uid {$apache_user['name']}"; + } + // check if daemon can connect -> error + try { + if ($bitcoin->can_connect() !== true) { + $error[] = "Unable to connect to coin daemon using provided credentials"; + } + } catch (Exception $e) { + } + // if coldwallet is not empty, check if the address is valid -> error + if (!empty($config['coldwallet']['address'])) { + try { + if ($bitcoin->can_connect() == true) { + $validate_cold_address = $bitcoin->validateaddress($config['coldwallet']['address']); + if (!$validate_cold_address['isvalid']) { + $error[] = "Your cold wallet address is SET and INVALID"; + } + } + } catch (Exception $e) { + } + } + // if database connection fails -> error + $db_connect = new mysqli($config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['name'], $config['db']['port']); + if (mysqli_connect_errno() || !array_key_exists('client_info', $db_connect)) { + $error[] = "Unable to connect to mysql using provided credentials"; + } + if (($config['strict'] || $config['mc_antidos']) && !$config['memcache']['enabled']) { + $error[] = "strict or mc_antidos are enabled and memcache is not, memcache is required to use these."; + } + // poke stratum using gettingstarted details -> enotice + $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + if ($socket !== false) { + $address = @gethostbyname($config['gettingstarted']['stratumurl']); + $result = @socket_connect($socket, $address, $config['gettingstarted']['stratumport']); + if ($result !== 1) { + $enotice[] = "We tried to poke your Stratum server using config->gettingstarted details but it didn't respond"; + } + $close = @socket_close($socket); + } + // security checks + // strict not on -> notice + if (!$config['strict']) { + $notice[] = "strict is disabled - if you have memcache, you should turn this on."; + } + // salts too short -> notice, salts default -> error + if ((strlen(SALT) < 24) || (strlen(SALTY) < 24) || SALT == 'PLEASEMAKEMESOMETHINGRANDOM' || SALTY == 'THISSHOULDALSOBERRAANNDDOOM') { + if (SALT == 'PLEASEMAKEMESOMETHINGRANDOM' || SALTY == 'THISSHOULDALSOBERRAANNDDOOM') { + $error[] = "You absolutely SHOULD NOT leave your SALT or SALTY default"; + } else { + $notice[] = "SALT or SALTY is too short, they should be more than 24 characters and changing them will require registering again."; + } + } + + // display the errors + foreach ($enotice as $en) { + $_SESSION['POPUP'][] = array('CONTENT' => $en, 'TYPE' => 'info'); + } + if (!count($notice) && !count($error)) { + $_SESSION['POPUP'][] = array('CONTENT' => 'The config options we checked seem OK', 'TYPE' => 'success'); + } else { + foreach ($notice as $n) { + $_SESSION['POPUP'][] = array('CONTENT' => $n, 'TYPE' => 'warning'); + } + foreach ($error as $e) { + $_SESSION['POPUP'][] = array('CONTENT' => $e, 'TYPE' => 'errormsg'); + } + } +} + +?> \ No newline at end of file diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 8fee8e48..5042e09e 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -71,4 +71,8 @@ require_once(INCLUDE_DIR . '/lib/scrypt.php'); // Include our versions require_once(INCLUDE_DIR . '/version.inc.php'); +if ($user->isAdmin(@$_SESSION['USERDATA']['id'])) { + //include_once(INCLUDE_DIR . '/admin_checks.inc.php'); +} + ?> diff --git a/public/include/classes/memcache_ad.class.php b/public/include/classes/memcache_ad.class.php index 57ef2ca7..25b48cf5 100644 --- a/public/include/classes/memcache_ad.class.php +++ b/public/include/classes/memcache_ad.class.php @@ -4,66 +4,119 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; class MemcacheAntiDos { public $cache; - public static $key = 'mcad_'; - public static $request_model = array( - 'ident' => '', - 'last_hit' => 0, - 'last_flush' => 0, - 'hits_since_flush' => 0 - ); public $rate_limit_this_request = false; - public function __construct($config, &$memcache, $userORip, $request, $mcSettings) { + public $rate_limit_api_request = false; + public $rate_limit_site_request = false; + public function __construct($config, &$memcache, $userORip, $request='', $mcSettings) { $this->cache = $memcache; // set our config options - $per_page = $config['per_page']; - $flush_sec = $config['flush_seconds']; - $rate_limit = $config['rate_limit']; + $per_page = ''; + $flush_sec_api = $config['flush_seconds_api']; + $rate_limit_api = $config['rate_limit_api']; + $flush_sec_site = $config['flush_seconds_site']; + $rate_limit_site = $config['rate_limit_site']; + $ajax_add = $config['ajax_hits_additive']; unset($config); // prep stuff we need to check this request - $key_md5 = substr(md5($userORip), 0, 4); - $request_md5 = substr(md5($request), 0, 4); - $request_key = $mcSettings['keyprefix'].self::$key.$key_md5."_".$request_md5."_".$per_page; - $request_data = $this->cache->get($request_key); + $key_md5 = md5($mcSettings['keyprefix'].$userORip); + $request_data = $this->cache->get($key_md5); $now = time(); + $max_req_flush = max(array($flush_sec_api,$flush_sec_site)); // check the request if (is_array($request_data)) { // this request key already exists, update it - $request_data['ident'] = $key_md5; - $request_data['last_hit'] = $now; - $request_data['hits_since_flush'] += 1; + $request_data['la'] = $now; + if ($request == 'api') { + $request_data['ha'] += 1; + if ($ajax_add) { + $request_data['hn'] += 1; + } + } else { + $request_data['hn'] += 1; + } // not rate limited yet, update the rest of the object - if ($request_data['hits_since_flush'] < $rate_limit) { - if (($request_data['last_flush'] + $flush_sec) <= $now || ($request_data['last_hit'] + $flush_sec) <= $now) { - // needs to be flushed - $request_data['hits_since_flush'] = 0; - $request_data['last_hit'] = 0; - $request_data['last_flush'] = $now; - // update the object - $this->cache->set($request_key, $request_data, $flush_sec); - $this->rate_limit_this_request = false; + if (($request_data['hn'] < $rate_limit_site) && ($request_data['ha'] < $rate_limit_api)) { + + if (((($request_data['hnl'] + $flush_sec_site) <= $now) || ($request_data['hal'] + $flush_sec_api) <= $now) || (($request_data['la'] + $max_req_flush) <= $now)) { + // needs to be flushed & updated + $new = $this->getRequestBase(); + $new['key'] = $key_md5; + $new['sid'] = session_id(); + $new['ua'] = md5($_SERVER['HTTP_USER_AGENT']); + $new['ip'] = $key_md5; + $new['la'] = $now; + $new['hal'] = ((($request_data['hal'] + $flush_sec_api) <= $now)) ? $now : 1; + $new['hnl'] = ((($request_data['hnl'] + $flush_sec_site) <= $now)) ? $now : 1; + $this->cache->set($key_md5, $new, $max_req_flush); + $this->rate_limit_api_request = ($request_data['ha'] >= $rate_limit_api) ? true : false; + $this->rate_limit_site_request = ($request_data['hn'] >= $rate_limit_site) ? true : false; + //$this->rate_limit_this_request = false; } else { // no flush, just update - $this->cache->set($request_key, $request_data, $flush_sec); - $this->rate_limit_this_request = false; + $new = $this->getRequestBase(); + $new['key'] = $key_md5; + $new['sid'] = session_id(); + $new['ua'] = md5($_SERVER['HTTP_USER_AGENT']); + $new['ip'] = $key_md5; + $new['la'] = time(); + $new['ha'] = $request_data['ha']; + $new['hal'] = $request_data['hal']; + $new['hn'] = $request_data['hn']; + $new['hnl'] = $request_data['hnl']; + $this->cache->set($key_md5, $new, $max_req_flush); + //$this->rate_limit_this_request = false; + $this->rate_limit_api_request = ($request_data['ha'] >= $rate_limit_api) ? true : false; + $this->rate_limit_site_request = ($request_data['hn'] >= $rate_limit_site) ? true : false; } } else { // too many hits, we should rate limit this - $this->rate_limit_this_request = true; + //$this->rate_limit_this_request = true; + $this->rate_limit_api_request = ($request_data['ha'] >= $rate_limit_api) ? true : false; + $this->rate_limit_site_request = ($request_data['hn'] >= $rate_limit_site) ? true : false; } } else { // doesn't exist for this request_key, create one - $new_data = self::$request_model; - $new_data['ident'] = $key_md5; - $new_data['last_hit'] = time(); - $new_data['hits_since_flush'] = 1; - $new_data['last_flush'] = $now; - $this->cache->set($request_key, $new_data, $flush_sec); + $new = $this->getRequestBase(); + $new['key'] = $key_md5; + $new['sid'] = session_id(); + $new['ua'] = md5($_SERVER['HTTP_USER_AGENT']); + $new['ip'] = $key_md5; + $new['la'] = time(); + if ($request == 'api') { + $new['ha'] += 1; + if ($ajax_add) { + $new['hn'] += 1; + } + } else { + $new['hn'] += 1; + } + $this->cache->set($key_md5, $new, $max_req_flush); $this->rate_limit_this_request = false; } } + public function getRequestBase() { + $new = array( + 'key' => '', + 'sid' => '', + 'ua' => '', + 'ip' => '', + 'la' => 0, + 'hn' => 0, + 'hnl' => 0, + 'ha' => 0, + 'hal' => 0 + ); + return $new; + } public function rateLimitRequest() { return $this->rate_limit_this_request; } + public function rateLimitSite() { + return $this->rate_limit_site_request; + } + public function rateLimitAPI() { + return $this->rate_limit_api_request; + } } ?> \ No newline at end of file diff --git a/public/include/classes/strict.class.php b/public/include/classes/strict.class.php index db23cd13..9a535c54 100644 --- a/public/include/classes/strict.class.php +++ b/public/include/classes/strict.class.php @@ -1,108 +1,140 @@ bind_address !== $this->server_http_host) { - return false; - } else { - return true; - } + public function session_delete_key($key) { + $read = $this->memcache->delete($key); } - - public function verify_client($ip) { - if ($this->started && $this->memcache_handle !== null && $this->verify_server()) { - $read_client = $this->memcache_handle->get(md5((string)$ip)); - if (is_array($read_client) && $read_client[0] == session_id()) { - return true; - } else { - return false; + private $validation_misses = 0; + private $initial_ua; + public function create_or_update_client($client, $force=false, $login=false) { + $read = $this->memcache->get($client['key']); + // this needs to be available later + $update = array('key' => '','sid' => '','ua' => '','ip' => '','la' => 0,'hn' => 0,'hnl' => 0,'ha' => 0,'hal' => 0); + $update['sid'] = $client['sid']; + $update['ua'] = md5($this->initial_ua); + $update['ip'] = $client['ip']; + $update['la'] = time(); + $update['key'] = md5($this->memcache_key.$client['ip']); + $validation_misses = 0; + if ($read !== false) { + $read_model = array('key' => '','sid' => '','ua' => '','ip' => '','la' => 0,'hn' => 0,'hnl' => 0,'ha' => 0,'hal' => 0); + $read_model['sid'] = @$read['sid']; + $read_model['ip'] = @$read['ip']; + $read_model['ua'] = @$read['ua']; + $read_model['la'] = @$read['la']; + $read_model['key'] = md5($this->memcache_key.$read['ip']); + // key already exists, update + if ($this->validate_client) { + if ($this->verify_client($read_model, $update, $login)) { + $update_client = $this->memcache->set($update['key'], $update); + } } } else { - return false; - } - } - - public function update_client($ip) { - if ($this->started && $this->memcache_handle !== null && $this->verify_client($ip)) { - $this->memcache_handle->set(md5((string)$ip), array($this->current_session_id, time())); - } - } - - public function set_cookie($ip) { - if ($this->started && $this->memcache_handle !== null && $this->verify_server() && $this->verify_client($ip)) { - @setcookie(session_name(), session_id(), $this->config_dura, $this->config_path, $this->config_domain, $this->config_secure, $this->config_httponly); - } - } - - public function destroy_session($ip) { - if ($this->started && $this->verify_server() && $this->verify_client($ip)) { - $this->memcache_handle->delete(md5((string)$ip)); - if (ini_get('session.use_cookies')) { - setcookie(session_name(), '', time() - 42000, $config_path, $config_domain, $config_secure, $config_httponly); - } - session_destroy(); - session_regenerate_id(true); - } - } - - public function create_session($ip) { - if (!$this->verify_server()) { - return false; - } else { - $session_start = @session_start(); - if (!$session_start) { - session_destroy(); - session_regenerate_id(true); - session_start(); - $this->update_client($ip); - $this->started = true; - $this->current_session_id = session_id(); - $this->set_cookie($ip); - return true; - } else { - $this->update_client($ip); - $this->started = true; - $this->current_session_id = session_id(); - $this->set_cookie($ip); - return true; + $update_client = $this->memcache->set($client['key'], $client); + if ($force && $login) { + $update_client = $this->memcache->set($update['key'], $update); } } } - - public function __construct($config, &$memcache, $server_host) { - $this->config_dura = $config['cookie']['duration']; - $this->config_path = $config['cookie']['path']; - $this->config_domain = $config['cookie']['domain']; - $this->config_secure = $config['cookie']['secure']; - $this->config_httponly = $config['cookie']['httponly']; - if ($config['strict__enforce_ssl']) $config['strict__bind_protocol'] = 'https'; - $this->bind_address = $config['strict__bind_protocol']."://".$config['strict__bind_host'].":".$config['strict__bind_port']; - $this->server_http_host = $config['strict__bind_protocol']."://".$_SERVER['HTTP_HOST'].":".$config['strict__bind_port']; - $this->memcache_handle = $memcache; - unset($config); - $this->set_cookie_params((time()+$this->config_dura), $this->config_path, $this->config_domain, $this->config_secure, $this->config_httponly); + public function verify_client($client_model, $data, $login=false) { + $fails = 0; + $fails += ((count($client_model)) !== (count($data))) ? 1 : 0; + $fails += ($client_model['ua'] !== $data['ua']) ? 1 : 0; + $fails += ($client_model['ip'] !== $data['ip']) ? 1 : 0; + $now = time(); + $this->validation_misses = $fails; + if ($fails > $this->validate_client_num && $login == false) { + // something changed + $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); + $location = (@$_SERVER['HTTPS'] == "on") ? 'https://' : 'http://'; + $location .= $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME']; + $this->session_delete_key($client_model['key']); + $this->session_delete_key($data['key']); + @session_start(); + @session_regenerate_id(true); + $_SESSION = null; + $_SESSION['POPUP'][] = array('CONTENT' => "Session revoked due to a change in your client. You may have a plugin messing with your useragent, or your IP address may have changed.", 'TYPE' => 'warning'); + $location.= '?page=login'; + if (!headers_sent()) exit(header('Location: ' . $location)); + exit(''); + } + return ($fails > 0) ? false : true; + } + public function read_if_client_exists($client_key) { + if ($this->memcache !== null) { + $exists = $this->memcache->get($client_key); + } + return ($exists !== null) ? $exists : false; + } + public function regen_session_id() { + $sidbefore = @session_id(); + @session_regenerate_id(true); + $sid = session_id(); + return $sid; + } + public function __construct($config, &$memcache) { + $this->initial_ua = $_SERVER['HTTP_USER_AGENT']; + $this->memcache = $memcache; + $this->memcache_key = $config['memcache']['keyprefix']; + if ($config['strict__verify_client']) { + $this->validate_client = true; + $this->validate_client_ip = $config['strict__verify_client_ip']; + $this->validate_client_ua = $config['strict__verify_client_useragent']; + $this->validate_client_sid = $config['strict__verify_client_sessionid']; + $this->validate_client_num = 0; + if ($config['strict__verify_server']) { + $proto = (@$_SERVER['HTTPS'] == "on") ? 'https' : 'http'; + $location = $proto."://".$_SERVER['SERVER_NAME'] . $_SERVER['SERVER_PORT']; + if ($config['strict__verify_server']) { + if ($config['strict__bind_protocol']."://".$config['strict__bind_host'].$config['strict__bind_port'] !== $location) { + return false; + } + } + } + $client = array('key' => '','sid' => '','ua' => '','ip' => '','la' => 0,'hn' => 0,'hnl' => 0,'ha' => 0,'hal' => 0); + $client['ua'] = md5($_SERVER['HTTP_USER_AGENT']); + $client['ip'] = md5($_SERVER['REMOTE_ADDR']); + $client['la'] = time(); + $client['key'] = md5($this->memcache_key.$client['ip']); + $read = $this->read_if_client_exists($client['key']); + } + session_set_cookie_params((time()+$config['cookie']['duration']), $config['cookie']['path'], $config['cookie']['domain'], false, true); + $session_start = @session_start(); + $client['sid'] = session_id(); + $valid_session_id = $this->valid_session_id($client['sid']); + if (!$valid_session_id || !$session_start) { + @session_destroy(); + $client['sid'] = $this->regen_session_id(); + session_start(); + } + if ($read !== null) { + // client exists, verify + $this->create_or_update_client($client, true, false); + + } else { + // doesn't exist + $this->create_or_update_client($client, true, true); + } + @setcookie(session_name(), $client['sid'], (time()+$config['cookie']['duration']), $config['cookie']['path'], $config['cookie']['domain'], false, true); + // post changes validate + if ($this->validate_client) { + $read_post = $this->read_if_client_exists($client['key']); + if ($read_post !== null) { + $this->verify_client($client, $read_post, true); + } + } } } @@ -115,26 +147,28 @@ class mysqli_strict extends mysqli { $acopy = $args; $nargs = count($args); for($i=1;$i<$nargs;$i++) { - $pos = substr($paramTypes, ($i-1), 1); + $ipos = ($i-1); + $pos = substr($paramTypes, $ipos, 1); switch ($pos) { case 's': $return_str = filter_var($acopy[$i], FILTER_VALIDATE_STRING, FILTER_NULL_ON_FAILURE); - return ($return_str !== null) ? (string)$return_str : false; + $acopy[$i] = ($return_str !== null) ? (string)$return_str : null; break; case 'i': $return_int = filter_var($acopy[$i], FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE); - return ($return_int !== null) ? (int)$return_int : false; + $acopy[$i] = ($return_int !== null) ? (int)$return_int : null; break; case 'd': $return_dbl = filter_var($acopy[$i], FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE); - return ($return_dbl !== null) ? (float)$return_dbl : false; + $acopy[$i] = ($return_dbl !== null) ? (float)$return_dbl : null; break; case 'b': $return_bool = filter_var($acopy[$i], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); - return ($return_bool !== null) ? (bool)$return_bool : false; + $acopy[$i] = ($return_bool !== null) ? (bool)$return_bool : null; break; } } + return (in_array(null, $acopy)); } } } diff --git a/public/include/classes/user.class.php b/public/include/classes/user.class.php index d7385b8a..1c0feee0 100644 --- a/public/include/classes/user.class.php +++ b/public/include/classes/user.class.php @@ -494,13 +494,10 @@ class User extends Base { $this->debug->append("STA " . __METHOD__, 4); $this->debug->append("Log in user to _SESSION", 2); if ($this->config['strict']) { - if ($this->session->verify_server()) { - if ($this->session->create_session($_SERVER['REMOTE_ADDR'])) { - $this->session->update_client($_SERVER['REMOTE_ADDR']); - $_SESSION['AUTHENTICATED'] = '1'; - $_SESSION['USERDATA'] = $this->user; - } - } + session_regenerate_id(true); + $_SESSION['AUTHENTICATED'] = '1'; + // $this->user from checkUserPassword + $_SESSION['USERDATA'] = $this->user; } else { session_regenerate_id(true); $_SESSION['AUTHENTICATED'] = '1'; @@ -537,10 +534,11 @@ class User extends Base { session_destroy(); // Enforce generation of a new Session ID and delete the old session_regenerate_id(true); + // Enforce a page reload and point towards login with referrer included, if supplied $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); $pushto = $_SERVER['SCRIPT_NAME'].'?page=login'; - $location = @$_SERVER['HTTPS'] ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $pushto : 'http://' . $_SERVER['SERVER_NAME'] . $port . $pushto; + $location = (@$_SERVER['HTTPS'] == 'on') ? 'https://' . $_SERVER['SERVER_NAME'] . $port . $pushto : 'http://' . $_SERVER['SERVER_NAME'] . $port . $pushto; // if (!headers_sent()) header('Location: ' . $location); exit(''); } @@ -804,7 +802,7 @@ class User extends Base { * @param none * @return bool **/ - public function isAuthenticated($logout=true) { +public function isAuthenticated($logout=true) { $this->debug->append("STA " . __METHOD__, 4); if (@$_SESSION['AUTHENTICATED'] == true && !$this->isLocked($_SESSION['USERDATA']['id']) && diff --git a/public/include/config/global.inc.dist.php b/public/include/config/global.inc.dist.php index 63bee3a6..63c44b83 100644 --- a/public/include/config/global.inc.dist.php +++ b/public/include/config/global.inc.dist.php @@ -1,25 +1,6 @@ Memcache configuration - * Check -> Memcache anti resource-dos - * - * Runs a FILTER_VALIDATE_*TYPE on every parameter of bind_param - * Verifies server vs. bound protocol/host/port set below - * Enables memcache rate limiting of requests - * Verifies client when creating/resuming from a session - */ -$config['strict'] = true; -$config['strict__enforce_ssl'] = false; -$config['strict__bind_protocol'] = 'http'; -$config['strict__bind_host'] = 'localhost'; -$config['strict__bind_port'] = 80; - /** * Do not edit this unless you have confirmed that your config has been updated! * This is used in the version check to ensure you run the latest version of the configuration file. @@ -27,6 +8,11 @@ $config['strict__bind_port'] = 80; **/ $config['version'] = '0.0.7'; +/** + * Unless you disable this, we'll do a quick check on your config first. + */ +$config['skip_config_tests'] = false; + // Our include directory for additional features define('INCLUDE_DIR', BASEPATH . 'include'); @@ -119,64 +105,6 @@ $config['coldwallet']['address'] = ''; $config['coldwallet']['reserve'] = 50; $config['coldwallet']['threshold'] = 5; -/** - * E-mail confirmations for user actions - * - * Explanation: - * To increase security for users, account detail changes can require - * an e-mail confirmation prior to performing certain actions. - * - * Options: - * enabled : Whether or not to require e-mail confirmations - * details : Require confirmation to change account details - * withdraw : Require confirmation to manually withdraw/payout - * changepw : Require confirmation to change password - * - * Default: - * enabled = true - * details = true - * withdraw = true - * changepw = true - */ -$config['twofactor']['enabled'] = true; -$config['twofactor']['options']['details'] = true; -$config['twofactor']['options']['withdraw'] = true; -$config['twofactor']['options']['changepw'] = true; - -/** - * CSRF protection - * - * Explanation: - * To help protect against CSRF, we can generate a hash that changes every minute - * and is unique for each user/IP and page or use, and check against that when a - * form is submitted. - * - * Options: - * enabled = Whether or not we will generate & check for valid CSRF tokens - * Default: - * enabled = true - */ -$config['csrf']['enabled'] = true; - -/** - * Lock account after maximum failed logins - * - * Explanation: - * To avoid accounts being hacked by brute force attacks, - * set a maximum amount of failed login or pin entry attempts before locking - * the account. They will need to contact site support to re-enable the account. - * - * This also applies for invalid PIN entries, which is covered by the pin option. - * - * Workers are not affected by this lockout, mining will continue as usual. - * - * Default: - * login = 3 - * pin = 3 - **/ -$config['maxfailed']['login'] = 3; -$config['maxfailed']['pin'] = 3; - /** * Getting Started Config * diff --git a/public/include/config/security.inc.dist.php b/public/include/config/security.inc.dist.php new file mode 100644 index 00000000..66612efe --- /dev/null +++ b/public/include/config/security.inc.dist.php @@ -0,0 +1,136 @@ + Memcache configuration + * Check -> Memcache anti resource-dos + * + * Options Default Explanation + * ------- + ------- + ----------- + * strict : true : Whether or not to use strict mode + * __https_only : false : Requires/pushes to https + * __mysql_filter : true : Uses a mysqli shim to use php filters on all incoming data + * __verify_client : true : Verifies the client using specified settings + * __verify_client_ip : true : If the client request suddenly switches IP, trigger a failure + * __verify_client_useragent : true : If the client request suddenly switches Useragent, trigger a failure + * __verify_client_sessionid : true : If the client request suddenly switches SessionID, trigger a failure + * __verify_client_fails : 0 : Maximum number of client-side inconsistencies to accept before revoking sessions + * __verify_server : false : Verifies the server is valid for this request + * __bind_protocol : https : Server validate protocol; http or https + * __bind_host : '' : Server validate host; ie. your domain or subdomain + * __bind_port : 443 : Server validate port; 80 / 443 / something else + **/ +$config['strict'] = true; +$config['strict__https_only'] = false; +$config['strict__mysql_filter'] = true; +$config['strict__verify_client'] = true; +$config['strict__verify_client_ip'] = true; +$config['strict__verify_client_useragent'] = true; +$config['strict__verify_client_sessionid'] = true; +$config['strict__verify_client_fails'] = 0; +$config['strict__verify_server'] = false; +$config['strict__bind_protocol'] = 'https'; +$config['strict__bind_host'] = ''; +$config['strict__bind_port'] = 443; + +/** + * Memcache anti resource-dos protection / request rate limiting + * + * Explanation: + * Because bots/angry users can just fire away at pages or f5 us to death, we can attempt to rate limit requests + * using memcache - now shares data with session manager. + * + * Options: + * enabled = Whether or not we will try to rate limit requests + * protect_ajax = If enabled, we will also watch the ajax calls for rate limiting and kill bad requests + * ajax_hits_additive = If enabled, ajax hits will count towards the site counter as well as the ajax counter + * flush_seconds_api = Number of seconds between each flush of user/ajax counter + * rate_limit_api = Number of api requests allowed per flush_seconds_api + * flush_seconds_site = Number of seconds between each flush of user/site counter + * rate_limit_site = Number of site requests allowed per flush_seconds_site + * ignore_admins = Ignores the rate limit for admins + * error_push_page = Page/action array to push users to a specific page, look in the URL! + * Empty = 'You are sending too many requests too fast!' on a blank page + * Default: + * enabled = true + * protect_ajax = true + * ajax_hits_additive = false + * flush_seconds_api = 60 + * rate_limit_api = 20 + * flush_seconds_site = 60 + * rate_limit_site = 30 + * ignore_admins = true + * error_push_page = array('page' => 'error', 'action' => 'ratelimit'); + */ +$config['mc_antidos']['enabled'] = true; +$config['mc_antidos']['protect_ajax'] = true; +$config['mc_antidos']['ajax_hits_additive'] = false; +$config['mc_antidos']['flush_seconds_api'] = 60; +$config['mc_antidos']['rate_limit_api'] = 20; +$config['mc_antidos']['flush_seconds_site'] = 60; +$config['mc_antidos']['rate_limit_site'] = 30; +$config['mc_antidos']['ignore_admins'] = true; +$config['mc_antidos']['error_push_page'] = array('page' => 'error', 'action' => 'ratelimit'); + +/** + * CSRF protection config + * + * Explanation: + * To help protect against CSRF, we can generate a hash that changes every minute + * and is unique for each user/IP and page or use, and check against that when a + * form is submitted. + * + * Options: + * enabled = Whether or not we will generate/check for valid CSRF tokens + * Default: + * enabled = true + */ +$config['csrf']['enabled'] = true; + +/** + * E-mail confirmations for user actions + * + * Explanation: + * To increase security for users, account detail changes can require + * an e-mail confirmation prior to performing certain actions. + * + * Options: + * enabled : Whether or not to require e-mail confirmations + * details : Require confirmation to change account details + * withdraw : Require confirmation to manually withdraw/payout + * changepw : Require confirmation to change password + * + * Default: + * enabled = true + * details = true + * withdraw = true + * changepw = true + */ +$config['twofactor']['enabled'] = true; +$config['twofactor']['options']['details'] = true; +$config['twofactor']['options']['withdraw'] = true; +$config['twofactor']['options']['changepw'] = true; + +/** + * Lock account after maximum failed logins + * + * Explanation: + * To avoid accounts being hacked by brute force attacks, + * set a maximum amount of failed login or pin entry attempts before locking + * the account. They will need to contact site support to re-enable the account. + * + * This also applies for invalid PIN entries, which is covered by the pin option. + * + * Workers are not affected by this lockout, mining will continue as usual. + * + * Default: + * login = 3 + * pin = 3 + **/ +$config['maxfailed']['login'] = 3; +$config['maxfailed']['pin'] = 3; + +?> \ No newline at end of file diff --git a/public/include/database.inc.php b/public/include/database.inc.php index 870e6dfa..1146e239 100644 --- a/public/include/database.inc.php +++ b/public/include/database.inc.php @@ -2,7 +2,7 @@ $defflip = (!cfip()) ? exit(header('HTTP/1.1 401 Unauthorized')) : 1; // Instantiate class, we are using mysqlng -if ($config['strict']) { +if ($config['strict'] && $config['strict__mysql_filter']) { $mysqli = new mysqli_strict($config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['name'], $config['db']['port']); } else { $mysqli = new mysqli($config['db']['host'], $config['db']['user'], $config['db']['pass'], $config['db']['name'], $config['db']['port']); diff --git a/public/include/pages/login.inc.php b/public/include/pages/login.inc.php index ff3b36ae..84964672 100644 --- a/public/include/pages/login.inc.php +++ b/public/include/pages/login.inc.php @@ -24,10 +24,22 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserIdByEmail // Check if recaptcha is enabled, process form data if valid if (!$setting->getValue('recaptcha_enabled') || !$setting->getValue('recaptcha_enabled_logins') || ($setting->getValue('recaptcha_enabled') && $setting->getValue('recaptcha_enabled_logins') && $rsp->is_valid)) { if (!$config['csrf']['enabled'] || $config['csrf']['enabled'] && $csrftoken->valid) { + // check if login is correct if ($user->checkLogin(@$_POST['username'], @$_POST['password']) ) { - $port = ($_SERVER["SERVER_PORT"] == "80" or $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); - $location = @$_SERVER['HTTPS'] ? 'https://' : 'http://'; - $location .= $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME'] . '?page=dashboard'; + $port = ($_SERVER["SERVER_PORT"] == "80" || $_SERVER["SERVER_PORT"] == "443") ? "" : (":".$_SERVER["SERVER_PORT"]); + $location = (@$_SERVER['HTTPS'] == "on") ? 'https://' : 'http://'; + $location .= $_SERVER['SERVER_NAME'] . $port . $_SERVER['SCRIPT_NAME']; + if ($config['strict']) { + $update = array('key' => '','sid' => '','ua' => '','ip' => '','la' => 0,'hn' => 0,'hnl' => 0,'ha' => 0,'hal' => 0); + $session->regen_session_id(); + $update['sid'] = session_id(); + $update['ua'] = md5($_SERVER['HTTP_USER_AGENT']); + $update['ip'] = md5($_SERVER['REMOTE_ADDR']); + $update['la'] = time(); + $update['key'] = md5($update['ip']); + $session->create_or_update_client($update, true, true); + $location.= '?page=dashboard'; + } if (!headers_sent()) header('Location: ' . $location); exit(''); } else { @@ -40,7 +52,7 @@ if ($setting->getValue('maintenance') && !$user->isAdmin($user->getUserIdByEmail $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid Captcha, please try again.', 'TYPE' => 'errormsg'); } } - // Load login template $smarty->assign('CONTENT', 'default.tpl'); + ?> diff --git a/public/include/pages/logout.inc.php b/public/include/pages/logout.inc.php index 9b6e12a6..c15f350a 100644 --- a/public/include/pages/logout.inc.php +++ b/public/include/pages/logout.inc.php @@ -1,7 +1,18 @@ logoutUser(); +if ($config['strict']) { + $user->logoutUser(); + $update = $session::$client_model; + $update['sid'] = session_id(); + $update['ua'] = $_SERVER['HTTP_USER_AGENT']; + $update['ip'] = $_SERVER['REMOTE_ADDR']; + $update['la'] = time(); + $update['key'] = md5($update['ua'].$update['ip']); + $session->create_or_update_client($update, true); +} else { + $user->logoutUser(); +} $smarty->assign("CONTENT", "default.tpl"); ?> diff --git a/public/include/version.inc.php b/public/include/version.inc.php index 6206c0a2..d1db6f62 100644 --- a/public/include/version.inc.php +++ b/public/include/version.inc.php @@ -1,7 +1,7 @@ addServer($config['memcache']['host'], $config['memcache']['port']); } -if ($config['strict']) { +if ($config['memcache']['enabled'] && $config['strict'] || $config['mc_antidos']['enabled']) { require_once(CLASS_DIR . '/memcache_ad.class.php'); - $session = new SessionManager($config, $memcache, $_SERVER['HTTP_HOST']); - $user->setSessionManager($session); - if ($session->verify_server()) { - $session->create_session($_SERVER['REMOTE_ADDR']); - if ($session->verify_client($_SERVER['REMOTE_ADDR'])) { - $session->update_client($_SERVER['REMOTE_ADDR']); - } +} + +if ($config['memcache']['enabled'] && $config['strict']) { + $session = new strict_session($config, $memcache); + if ($config['strict__verify_server'] && !$session) { + // server not verified, session manager will kill the client verification failures + exit(header('HTTP/1.1 401 Unauthorized')); } } else { - session_set_cookie_params(time()+$config['cookie']['duration'], $config['cookie']['path'], $config['cookie']['domain'], $config['cookie']['secure'], $config['cookie']['httponly']); $session_start = @session_start(); + session_set_cookie_params(time()+$config['cookie']['duration'], $config['cookie']['path'], $config['cookie']['domain'], $config['cookie']['secure'], $config['cookie']['httponly']); if (!$session_start) { session_destroy(); session_regenerate_id(true); @@ -79,11 +83,12 @@ if ($config['strict']) { } @setcookie(session_name(), session_id(), time()+$config['cookie']['duration'], $config['cookie']['path'], $config['cookie']['domain'], $config['cookie']['secure'], $config['cookie']['httponly']); } + // Rate limiting -if ($config['memcache']['enabled'] && $config['mc_antidos']['enabled'] || $config['strict']) { +if ($config['memcache']['enabled'] && ($config['mc_antidos']['enabled'] || $config['strict'])) { $skip_check = false; - $per_page = ($config['mc_antidos']['per_page']) ? $_SERVER['QUERY_STRING'] : ''; // if this is an api call we need to be careful not to time them out for those calls separately + $per_page = ''; $ajax_calls = array( array('api', 'getuserbalance'), array('api', 'getnavbardata'), @@ -96,9 +101,7 @@ if ($config['memcache']['enabled'] && $config['mc_antidos']['enabled'] || $confi } $is_ajax_call = ($iac > 0) ? true : false; if ($is_ajax_call && $config['mc_antidos']['protect_ajax']) { - // we set this to navbar on purpose - if they screw with the REQUEST by adding more - // params it still gets added under navbar so multiple requests will still get capped - $per_page = 'navbar'; + $per_page = 'api'; } else if ($is_ajax_call && !$config['mc_antidos']['protect_ajax']) { // protect isn't on, we'll ignore it $skip_check = true; @@ -107,9 +110,13 @@ if ($config['memcache']['enabled'] && $config['mc_antidos']['enabled'] || $confi } if (!$skip_check) { $mcad = new MemcacheAntiDos($config['mc_antidos'], $memcache, $_SERVER['REMOTE_ADDR'], $per_page, $config['memcache']); - $rate_limit_reached = $mcad->rateLimitRequest(); + $rate_limit_reached_site = $mcad->rateLimitSite(); + $rate_limit_reached_api = $mcad->rateLimitAPI(); + if ($rate_limit_reached_api && $is_ajax_call && $config['mc_antidos']['protect_ajax']) { + exit(header('HTTP/1.1 401 Unauthorized')); + } $error_page = $config['mc_antidos']['error_push_page']; - if ($rate_limit_reached == true) { + if ($rate_limit_reached_site == true) { if (!is_array($error_page) || count($error_page) < 1 || (empty($error_page['page']) && empty($error_page['action']))) { die("You are sending too many requests too fast!"); } else { @@ -120,6 +127,11 @@ if ($config['memcache']['enabled'] && $config['mc_antidos']['enabled'] || $confi } } +// Quick config check +if (@$_SESSION['USERDATA']['is_admin'] && (!$config['skip_config_tests'])) { + require_once(INCLUDE_DIR. '/admin_checks.php'); +} + // Create our pages array from existing files if (is_dir(INCLUDE_DIR . '/pages/')) { foreach (glob(INCLUDE_DIR . '/pages/*.inc.php') as $filepath) { diff --git a/public/templates/mpos/login/default.tpl b/public/templates/mpos/login/default.tpl index b9c8ef56..b407e2a1 100644 --- a/public/templates/mpos/login/default.tpl +++ b/public/templates/mpos/login/default.tpl @@ -1,6 +1,5 @@
-

Login with existing account