Security Modifications for 1.3.x

Forum
Last Post
Threads / Messages

Kesstryl

Member
Member
Joined
Feb 22, 2012
Messages
206
Points
28
Mysidian Dollar
11,706
Even if you are lucky enough to have access to your server's php.ini file where you can harden many of your server features against attacks, there are many vectors of attack, so having multiple layers of protection is the smart thing to do. Also some people can only afford shared hosting, and that means you don't have control over what security features are set server side. Here are some small changes you can do to make certain very important things behave more securely with Mysidia. Steps 1 and 2 will require that your site runs with SSL over https and uses PHP 7.3 or greater.

Note - these changes were tested on 1.3.6, so you might need to look in different files on older versions, but the changes themselves should work if you find the correct corresponding class and method.

If you are using a crappy enough host that doesn't provide you with an SSL certificate (and I recommend that you don't use such a host), you can use Cloudflare to get a free one and have your site traffic go through Cloudflare's servers, which also gives a speed boost through it's caching system as well as some other necessary security features https://www.cloudflare.com/plans/

I would use Cloudflare anyway for the caching and extra security features beyond a free SSL certificate. Their site has tutorials so let's move on.

This section helps secure your site at the application level, and these settings won't hurt to have. Layers of protection are good as there are attacks that could rewrite your htaccess file or change your server at the command line (if this happens, you've got bigger problems, so we won't overthink it at this point). You can Google what exactly these security features do if you want a greater understanding (see bottom for some good resources to start with), but for space, I'll just drop in the code.

1. Sessions

Go to this file and the constructor:

resource\core\session.php

public function __construct(){
// Start our session
if(!isset($_SESSION)) session_start();
$this->started = TRUE;

As of php 7.3+ you can now set session options in your session_start() with an array which tells your server how to handle the session with essential security, like in this example:

Code:
      if(!isset($_SESSION)) session_start(['use_cookies' => true, 'cookie_secure' => true, 'cookie_httponly' => true, 'use_only_cookies' => true, 'use_trans_sid' => false, 'use_strict_mode' => true, 'sid_bits_per_character' => 6, 'sid_length' => 128, 'cookie_samesite' => 'Strict' ]);

Next, we want to make sure the session id is refreshed when a user first logs in

service\applicationservice\accountservice.php

Go to the login method

public function login($username){
$mysidia = Registry::get("mysidia");
if($mysidia->session->clientip != $_SERVER['REMOTE_ADDR']) throw new Exception('Your IP has changed since last session, please log in again.');
else{
$mysidia->cookies->setcookies($username);
$mysidia->db->update("users", ["session" => $mysidia->cookies->getcookies("myssession")], "username = :username", ["username" => $username]);
if($this->mybbService->isEnabled()) $this->mybbService->login($username);
return TRUE;
}
}

Add in the session regenerate code:

Code:
    public function login($username){
        $mysidia = Registry::get("mysidia");
        if($mysidia->session->clientip != $_SERVER['REMOTE_ADDR']) throw new Exception('Your IP has changed since last session, please log in again.');
        else{
            session_regenerate_id(true);
            $mysidia->cookies->setcookies($username);
            $mysidia->db->update("users", ["session" => $mysidia->cookies->getcookies("myssession")], "username = :username", ["username" => $username]);
            if($this->mybbService->isEnabled()) $this->mybbService->login($username);
            return TRUE;
        }
    }



2. Cookies

resource\core\cookies.php

public function setcookies($username){
$mysidia = Registry::get("mysidia");
ob_start();
$Month = 2592000 + time();
$this->mysuid = $mysidia->db->select("users", ["uid"], "username = :username", ["username" => $username])->fetchColumn();
setcookie("mysuid", $this->mysuid, $Month, '/', $_SERVER['HTTP_HOST']);
....and so on

In PHP 7.3+ you can also set an options array inside of cookies to secure them. Mysida vanilla also uses MD5, and the admin session uses sha1 to hash the session id, both of which are unsafe and easy to crack. The following code upgrades that to sha256 (sha512 is better, but that will require updating your user table to store a longer session string with a length of 128. There are other hashing algorithms you can use for sessions):


Code:
    public function setcookies($username){
        $mysidia = Registry::get("mysidia");
        ob_start();
        $Month = 2592000 + time();
        $this->mysuid = $mysidia->db->select("users", ["uid"], "username = :username", ["username" => $username])->fetchColumn();
        setcookie("mysuid", $this->mysuid, ['expires' => $Month, 'path' => '/', 'domain'=> $_SERVER['HTTP_HOST'], 'secure' => TRUE, 'httponly' => TRUE, 'SameSite' => 'Strict']);
        $session = $mysidia->session->getid();
        $this->myssession = hash("sha256", $this->mysuid . $session);
        setcookie("myssession", $this->myssession, ['expires' => $Month, 'path' => '/', 'domain'=> $_SERVER['HTTP_HOST'], 'secure' => TRUE, 'httponly' => TRUE, 'SameSite' => 'Strict']);
        $this->mysactivity = time();
        setcookie("mysactivity", $this->mysactivity, ['expires' => $Month, 'path' => '/', 'domain'=> $_SERVER['HTTP_HOST'], 'secure' => TRUE, 'httponly' => TRUE, 'SameSite' => 'Strict']);
        $this->mysloginattempt = 0;
        setcookie("mysloginattempt", $this->mysloginattempt, ['expires' => $Month, 'path' => '/', 'domain'=> $_SERVER['HTTP_HOST'], 'secure' => TRUE, 'httponly' => TRUE, 'SameSite' => 'Strict']);
        ob_flush();
        return TRUE;

    }

You can set all of the cookies in this file to have this array in the cookies ['expires' => $Month, 'path' => '/', 'domain'=> $_SERVER['HTTP_HOST'], 'secure' => TRUE, 'httponly' => TRUE, 'SameSite' => 'Strict']


3. Meta Tags with Content Security Policy

When I look through some of the default Mysidia templates, some are structured like this

<html>
<head>
<title>{$browser_title}</title>
{$header->loadFavicon("{$home}favicon.ico")}
{$header->loadStyle("{$home}{$temp}{$theme}/style.css")}
{$header->loadStyles()}
{$header->loadAdditionalStyle()}
{$header->loadScript("//ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js")}
{$header->loadScripts()}
{$header->loadAdditionalScript()}
</head>

At minimum they should begin like this:

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>{$browser_title}</title>

The reason is you are declaring the page to be an HTML 5 page which automatically sets it to the mime type text/html and you are setting the charset to something standard for the rest of your output. Read the Content Type section on this page https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html

Next add a content security policy to a meta tag (note: Mysidia has a lot of inline styles dynamically generated by the php code that should be moved to the CSS style page, but for now 'unsafe inline' on the style-src-attr will allow them to work, and is exactly as it looks, unsafe. There are inline CSS attacks that exist, so we need to work on pulling styles from the CSS itself and not producing it inline. This will be a big undertaking as the current version of Mysidia has inline styles set in other places in the php files (many of which I'm still trying to dig out) and that takes control away from the site owner if they are used to using CSS or don't know where to find the current values from the code (like me). Temporarily taking away the 'unsafe-inline' and loading pages will remove padding from tables and other odd behaviors, so you can remove and add that little bit as you figure out what those values are and put them in the CSS. Once you tinker and figure out how to move various inline styles to the CSS, then you can simply delete the unsafe inline permanently and have a more secure site:

Code:
<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="Content-Security-Policy"
        content="default-src 'self';
        img-src 'self';
        style-src 'self';
        style-src-attr 'self' 'unsafe-inline';
        style-src-elem 'self';
        font-src 'self';
        script-src 'self' https://ajax.googleapis.com;
        script-src-elem 'self' https://ajax.googleapis.com;
        script-src-attr 'self' https://ajax.googleapis.com;
        frame-src 'self';
        object-src 'self';
        form-action 'self';
        ">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>{$browser_title}</title>
        {$header->loadFavicon("{$home}favicon.ico")}
        {$header->loadStyle("{$home}{$temp}{$theme}/style.css")}
        {$header->loadStyle("{$home}{$css}/menu.css")}
        {$header->loadStyles()}
        {$header->loadAdditionalStyle()}
        {$header->loadScript("https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js")}
        {$header->loadScripts()}
        {$header->loadAdditionalScript()}
    </head>

As icing, this code includes the meta viewport which helps your site scale with different screen sizes.

Security is a deep dive topic and should be researched extensively. These three changes will help mitigate many common vectors of attack, but they can't stop every attack from a poorly configured server. Security options can be added to the htaccess file, but not all web host servers allow configuration changes from them, so unless you know your web host lets you use them, they can't always be relied on for shared hosting. Also certain attacks can modify the htaccess file, so be aware of that. I will add more sections to this topic as I have time to work on them, and I will edit things as I learn more about this topic (Disclaimer - I am not a security expert, I am a self taught php user, like many here, who is passing on what I have learned so others can benefit, and hopefully have better security than what the vanilla script provides. There is so much more to security than what I can provide here, so see this as having a good beginning to your own learning and growth, and not as the final stop on this journey. I also welcome additional tips if you know some best practices that I am missing)

Resources to start with:
https://infosec.mozilla.org/
https://cheatsheetseries.owasp.org/
 
Last edited:
4. Using the .htaccess file in Mysidia's root

It is not good practice to rely on htaccess files for security, it is always best to configure these settings at your server level if you have access

But not everyone has server configuration access, and also extra layers of security are not going to hurt, so lets dig in.

Mysidia's vanilla htaccess file looks like this:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#RewriteCond %{HTTP_HOST} ^www\.(.*) [NC]
#RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301]
RewriteCond %{REQUEST_URI} !\.(js|css|html|xml|json|gif|jpg|png|ico)$ [NC]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^get/([0-9]+).gif$ /click/siggy/$1 [L]

We can add some extra features to this to improve some security for your site. I will add a commented code so you can understand what some of it does, and an uncommented one if you prefer to use that.

Commented code:

Code:
# prevent listing directories
Options All -Indexes
<IfModule mod_headers.c>
# Security settings sent in the header, you can google what each one does
# Note on the Content Security Policy, we have already set it in the header template with a meta tag, but optionally it can be used here too.
# Since frame ancestors can't be used in a meta tag, I added it here, but opted to leave the rest of the CSP in the meta tag for this example.
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
Header set Referrer-Policy "same-origin"
# Only connect to this site and subdomains via HTTPS for the next two years and also include in the preload list
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# block access to htaccess and other hidden files that start with a dot
RewriteRule (^\.|/\.) - [F]
# default Mysidia configurations
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# uncomment this as it is now needed
RewriteCond %{HTTP_HOST} ^www\.(.*) [NC]
# use https
RewriteCond %{HTTPS} off
#RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301] Do not ever use this, it is safe to delete
RewriteCond %{REQUEST_URI} !\.(js|css|html|xml|json|gif|jpg|png|ico|webp)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^get/([0-9]+).gif$ /click/siggy/$1 [L]
</IfModule>
# deny access to config files
<files config.php>
order allow,deny
deny from all
</files>
<files config_forums.php>
order allow,deny
deny from all
</files>

Uncommented code:

Code:
Options All -Indexes
<IfModule mod_headers.c>
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
Header set Referrer-Policy "same-origin"
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (^\.|/\.) - [F]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^www\.(.*) [NC]
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !\.(js|css|html|xml|json|gif|jpg|png|ico|webp)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^get/([0-9]+).gif$ /click/siggy/$1 [L]
</IfModule>
<files config.php>
order allow,deny
deny from all
</files>
<files config_forums.php>
order allow,deny
deny from all
</files>

If you are using a localhost without SSL to test your site (like WAMP server)
You can comment out the https stuff because it will break things from loading
Do not use this on a live production site
Code:
Options All -Indexes
<IfModule mod_headers.c>
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
Header set Referrer-Policy "same-origin"
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (^\.|/\.) - [F]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# RewriteCond %{HTTP_HOST} ^www\.(.*) [NC]
# RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !\.(js|css|html|xml|json|gif|jpg|png|ico|webp)$ [NC]
# RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
RewriteRule ^(.*)$ index.php [QSA,L]
RewriteRule ^get/([0-9]+).gif$ /click/siggy/$1 [L]
</IfModule>
<files config.php>
order allow,deny
deny from all
</files>
<files config_forums.php>
order allow,deny
deny from all
</files>

More information can be found here https://www.todhost.com/blog/how-to-secure-a-website-with-the-htaccess-file/
 
Last edited:
To Do - figure out how to add CSFR tokens to form data with Mysidia code
 

Similar threads

Users who are viewing this thread

  • Forum Contains New Posts
  • Forum Contains No New Posts

Forum statistics

Threads
4,277
Messages
33,118
Members
1,602
Latest member
BerrieMilk
BETA

Latest Threads

Top