In fact, actually when there is not Javascript activated, the form send passwords normally.
To detect the hashed passwords before the SQL check, I think it could be made by limit the password lenght  (always < of lenght hash)  or by activating the $_SESSION variable in  dependece of Javascript :
<script>
if (true) {
<? $_SESSION["t_parcial"]=sha1(date("l dS of F Y h:i:s A"));?>
}
</script>

the second one sounds more insecure, although allows some message to the visitor. If there is not session, then we can show some warning to the viistor.

Well,  just a pair of ideas. I will try some of both smile

that's a good idea smile

and maybe a function able to work with diferent form variables, because also there is the "change password" option in the Profile section.

To avoid Javascript maybe there is some way to apply a hash before sending password. Perhaps in dependence of the Session unique id? 
I don't know. If somebody want to polish this code or make somehting similar I would be glad to know.

** I have edited the message to add these two lines to keep clean the /tmp directory:

$_SESSION = array();
session_destroy();

regards,

(Modification for version 1.2.14)

A security hole in PunBB exist when we enter user/password in login.php page. Both are travelling in a open way.:

    POST /forums/login.php?action=in HTTP/1.1
    Host: punbb.org
    .....
    req_username=myname
    req_password=mypass
    login=Login


Database hashes becomes useless when  there is not difficulty in capturing passwords in Intranet, etc. So here there is a way to apply a secure one-way password approach by using Javascript SHA1 and MD5 libraries.
In this way, password is ciphered in the client side:

    POST /forums/login.php?action=in HTTP/1.1
    Host: myhost.com
        .....
    req_username=myname
    req_password=1538cbbd73ce4079230a93bb738b1867
    login=Login


When hash is never the same, it is a much better approach. Note DB hashes are useless when passwords are travelling in plain way.
Administrators must be aware of this.


HOW TO IMPLEMENT:
------------------
Download sha1.js and md5.js.
http://pajhome.org.uk/crypt/md5/sha1src.html
http://pajhome.org.uk/crypt/md5/md5src.html

Put both in the main PunBB folder, where is login.php page.

Make security backups of:
- header.php
- login.php



PAGE CHANGES:
------------

HEADER.PHP:

Find line:
<?php $max=10; ?>

...until line:
function process_form(the_form)

and between them, put this:

<?
/**** START ************************************************/    

// Hashing SHA1 or MD5
$urlogin = $_SERVER['REQUEST_URI']; 

if (preg_match('/login.php/i', $urlogin)) {    

    $sha1_available = (function_exists('sha1') || function_exists('mhash')) ? true : false;        

    if ($sha1_available) {
        $_SESSION["t_parcial"]=sha1(date("l dS of F Y h:i:s A"));        
        echo '<script src="sha1.js"></script>';
        $sha1ok = 1; 

    } else {
        $_SESSION["t_parcial"]=md5(date("l dS of F Y h:i:s A"));                
        echo '<script src="md5.js"></script>';
        $sha1ok = 0; 
    }    
}    

?>

<script type="text/javascript">
<!--
var max=<?php echo $max; ?>;
var randomValue1=Math.floor(Math.random()*max);
var randomValue2=Math.floor(Math.random()*max);
var theSum=randomValue1+randomValue2;
max*=2;

function upAndDown(the_form, sign){
    var theValue=the_form.elements["mathValue"].value;
    if(sign=="+" && theValue<max) theValue++;
    else if(sign=="-" && theValue>0) theValue--;
    the_form.elements["mathValue"].value=theValue;
}



<?     

if ($sha1ok == 1) {     ?>

function encrypt() {
    var sha1_pre=hex_sha1(document.login.req_password.value);
    var sha1=hex_sha1(sha1_pre+"<? echo sha1($_SESSION["t_parcial"] .getenv("REMOTE_ADDR")); ?>")
    document.login.req_password.value=sha1
}

<?        
$eslogin = '';
} else { ?>

function encrypt() {
    var md5_pre=md5(document.login.req_password.value)
    var md5=hex_md5(md5_pre+"<? echo md5($_SESSION["t_parcial"] .getenv("REMOTE_ADDR")); ?>")
    document.login.req_password.value=md5
}

<? 
    $eslogin = '';
} 
?>

/**** END ************************************************/

LOGIN.PHP
Replace all code with this:


<?php
/***********************************************************************

  Copyright (C) 2002-2005  Rickard Andersson (rickard@punbb.org)

  This file is part of PunBB.

  PunBB is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 2 of the License,
  or (at your option) any later version.

  PunBB is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA  02111-1307  USA

************************************************************************/

if (isset($_GET['action'])) {
    define('PUN_QUIET_VISIT', 1);
}

define('PUN_ROOT', './');
require PUN_ROOT.'include/common.php';


// Load the login.php language file
require PUN_ROOT.'lang/'.$pun_user['language'].'/login.php';

$action = isset($_GET['action']) ? $_GET['action'] : null;

if (isset($_POST['form_sent']) && $action == 'in') {

    session_start();
    
            
    $form_username = trim($_POST['req_username']);
    $form_password = trim($_POST['req_password']);


    $username_sql = ($db_type == 'mysql' || $db_type == 'mysqli') ? 'username=\''.$db->escape($form_username).'\'' : 'LOWER(username)=LOWER(\''.$db->escape($form_username).'\')';

    $result = $db->query('SELECT id, group_id, password, save_pass FROM '.$db->prefix.'users WHERE '.$username_sql) or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());
    list($user_id, $group_id, $db_password_hash, $save_pass) = $db->fetch_row($result);

    $authorized = false;

    if (!empty($db_password_hash))    {

        $sha1_in_db = (strlen($db_password_hash) == 40) ? true : false;
        $sha1_available = (function_exists('sha1') || function_exists('mhash')) ? true : false;

        if ($sha1_available)
            $t=sha1($_SESSION["t_parcial"] .getenv("REMOTE_ADDR"));
        else
          $t=md5($_SESSION["t_parcial"].getenv("REMOTE_ADDR"));
    

        if ($sha1_in_db && $sha1_available && sha1($db_password_hash.$t) == $form_password) 
            $authorized = true;
         else if (!$sha1_in_db && md5($db_password_hash.$t) == $form_password)     
         {
            $authorized = true;

         }
        
     }

    if (!$authorized)  {
             $_SESSION = array();
        session_destroy();
        message($lang_login['Wrong user/pass'].' <a href="login.php?action=forget">'.$xx.$lang_login['Forgotten pass'].'</a>');
       }
    

    // Update the status if this is the first time the user logged in
    if ($group_id == PUN_UNVERIFIED) 
        $db->query('UPDATE '.$db->prefix.'users SET group_id='.$pun_config['o_default_user_group'].' WHERE id='.$user_id) or error('Unable to update user status', __FILE__, __LINE__, $db->error());
    

    // Remove this users guest entry from the online list
    $db->query('DELETE FROM '.$db->prefix.'online WHERE ident=\''.$db->escape(get_remote_address()).'\'') or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());
        
    $expire = ($save_pass == '1') ? time() + 31536000 : 0;
    
    //pun_setcookie($user_id, $form_password_hash, $expire);  
    pun_setcookie($user_id, $db_password_hash, $expire);

       $_SESSION = array();
      session_destroy();
 
      redirect(htmlspecialchars($_POST['redirect_url']), $lang_login['Login redirect']);
    
}


else if ($action == 'out')
{
    if ($pun_user['is_guest'] || !isset($_GET['id']) || $_GET['id'] != $pun_user['id'])
    {
        header('Location: index.php');
        exit;
    }

    // Remove user from "users online" list.
    $db->query('DELETE FROM '.$db->prefix.'online WHERE user_id='.$pun_user['id']) or error('Unable to delete from online list', __FILE__, __LINE__, $db->error());

    // Update last_visit (make sure there's something to update it with)
    if (isset($pun_user['logged']))
        $db->query('UPDATE '.$db->prefix.'users SET last_visit='.$pun_user['logged'].' WHERE id='.$pun_user['id']) or error('Unable to update user visit data', __FILE__, __LINE__, $db->error());

    pun_setcookie(1, random_pass(8), time() + 31536000);

    redirect('index.php', $lang_login['Logout redirect']);
}


else if ($action == 'forget')
{
    if (!$pun_user['is_guest'])
        header('Location: index.php');

    if (isset($_POST['form_sent']))
    {
        require PUN_ROOT.'include/email.php';

        // Validate the email-address
        $email = strtolower(trim($_POST['req_email']));
        if (!is_valid_email($email))
            message($lang_common['Invalid e-mail']);

        $result = $db->query('SELECT id, username FROM '.$db->prefix.'users WHERE email=\''.$db->escape($email).'\'') or error('Unable to fetch user info', __FILE__, __LINE__, $db->error());

        if ($db->num_rows($result))
        {
            // Load the "activate password" template
            $mail_tpl = trim(file_get_contents(PUN_ROOT.'lang/'.$pun_user['language'].'/mail_templates/activate_password.tpl'));

            // The first row contains the subject
            $first_crlf = strpos($mail_tpl, "\n");
            $mail_subject = trim(substr($mail_tpl, 8, $first_crlf-8));
            $mail_message = trim(substr($mail_tpl, $first_crlf));

            // Do the generic replacements first (they apply to all e-mails sent out here)
            $mail_message = str_replace('<base_url>', $pun_config['o_base_url'].'/', $mail_message);
            $mail_message = str_replace('<board_mailer>', $pun_config['o_board_title'].' '.$lang_common['Mailer'], $mail_message);

            // Loop through users we found
            while ($cur_hit = $db->fetch_assoc($result))
            {
                // Generate a new password and a new password activation code
                $new_password = random_pass(8);
                $new_password_key = random_pass(8);

                $db->query('UPDATE '.$db->prefix.'users SET activate_string=\''.pun_hash($new_password).'\', activate_key=\''.$new_password_key.'\' WHERE id='.$cur_hit['id']) or error('Unable to update activation data', __FILE__, __LINE__, $db->error());

                // Do the user specific replacements to the template
                $cur_mail_message = str_replace('<username>', $cur_hit['username'], $mail_message);
                $cur_mail_message = str_replace('<activation_url>', $pun_config['o_base_url'].'/profile.php?id='.$cur_hit['id'].'&action=change_pass&key='.$new_password_key, $cur_mail_message);
                $cur_mail_message = str_replace('<new_password>', $new_password, $cur_mail_message);

                pun_mail($email, $mail_subject, $cur_mail_message);
            }

            message($lang_login['Forget mail'].' <a href="mailto:'.$pun_config['o_admin_email'].'">'.$pun_config['o_admin_email'].'</a>.');
        }
        else
            message($lang_login['No e-mail match'].' '.htmlspecialchars($email).'.');
    }


    $page_title = pun_htmlspecialchars($pun_config['o_board_title']).' / '.$lang_login['Request pass'];
    $required_fields = array('req_email' => $lang_common['E-mail']);
    $focus_element = array('request_pass', 'req_email');
    require PUN_ROOT.'header.php';




?>
<div class="blockform">
    <h2><span><?php echo $lang_login['Request pass'] ?></span></h2>
    <div class="box">
        <form id="request_pass" method="post" action="login.php?action=forget_2" onsubmit="this.request_pass.disabled=true;if(process_form(this)){return true;}else{this.request_pass.disabled=false;return false;}">
            <div class="inform">
                <fieldset>
                    <legend><?php echo $lang_login['Request pass legend'] ?></legend>
                    <div class="infldset">
                        <input type="hidden" name="form_sent" value="1" />
                        <input id="req_email" type="text" name="req_email" size="50" maxlength="50" />
                        <p><?php echo $lang_login['Request pass info'] ?></p>
                    </div>
                </fieldset>
            </div>
            <p><input type="submit" name="request_pass" value="<?php echo $lang_common['Submit'] ?>" /><a href="javascript:history.go(-1)"><?php echo $lang_common['Go back'] ?></a></p>
        </form>
    </div>
</div>
<?php

    require PUN_ROOT.'footer.php';
}


if (!$pun_user['is_guest'])
    header('Location: index.php');

// Try to determine if the data in HTTP_REFERER is valid (if not, we redirect to index.php after login)
$redirect_url = (isset($_SERVER['HTTP_REFERER']) && preg_match('#^'.preg_quote($pun_config['o_base_url']).'/(.*?)\.php#i', $_SERVER['HTTP_REFERER'])) ? htmlspecialchars($_SERVER['HTTP_REFERER']) : 'index.php';

$page_title = pun_htmlspecialchars($pun_config['o_board_title']).' / '.$lang_common['Login'];
$required_fields = array('req_username' => $lang_common['Username'], 'req_password' => $lang_common['Password']);
$focus_element = array('login', 'req_username');


session_start();    

require PUN_ROOT.'header.php';


?>
<div class="blockform">
    <h2><span><?php echo $lang_common['Login'] ?></span></h2>
    <div class="box">
        <form id="login" name="login" method="post" action="login.php?action=in" onsubmit="encrypt()">
            <div class="inform">
                <fieldset>
                    <legend><?php echo $lang_login['Login legend'] ?></legend>
                        <div class="infldset">
                            <input type="hidden" name="form_sent" value="1" />
                            <input type="hidden" name="redirect_url" value="<?php echo $redirect_url ?>" />
                            <label class="conl"><strong><?php echo $lang_common['Username'] ?></strong><br /><input type="text" name="req_username" size="25" maxlength="25" tabindex="1" /><br /></label>
                            <label class="conl"><strong><?php echo $lang_common['Password'] ?></strong><br /><input type="password" name="req_password" size="16" maxlength="40" tabindex="2" /><br /></label>
                            <p class="clearb"><?php echo $lang_login['Login info'] ?></p>
                            <p><a href="register.php" tabindex="4"><?php echo $lang_login['Not registered'] ?></a>  
                            <a href="login.php?action=forget" tabindex="5"><?php echo $lang_login['Forgotten pass'] ?></a></p>
                        </div>
                </fieldset>
            </div>
            <p><input type="submit" name="login" value="<?php echo $lang_common['Login'] ?>" tabindex="3" /></p>
        </form>
    </div>
</div>
<?php

require PUN_ROOT.'footer.php';

code sure be improved although it works.

Hi,

yes, you are right. A good anti-spam system cannot depend only of Javascript.
It is only a way so these mods can work. 

Probably, a better system to validate the use of Javascript should include a session variable passed to the same javascript code.

regards,

yes, it is the cause. There is not Reply-to in the headers, and they are blocked. I have check it now with one Yahoo account.

To solve this, go to include/email.php:

line 78:

delete this line:

$headers = 'From: '.$from."\r\n".'Date: '.date('r')."\r\n".'MIME-Version: 1.0'."\r\n".'Content-transfer-encoding: 8bit'."\r\n".'Content-type: text/plain; charset='.$lang_common['lang_encoding']."\r\n".'X-Mailer: PunBB Mailer';

and replace with this:

        $replyto = $pun_config['o_webmaster_email'];

    if ($from != $pun_config['o_webmaster_email']) {
        $replyto = $from;
    } 
            
    $headers = 'From: '.$from."\r\n".'Date: '.date('r')."\r\n".'MIME-Version: 1.0'."\r\n".'Content-transfer-encoding: 8bit'."\r\n".'Content-type: text/plain; charset='.$lang_common['lang_encoding']."\r\n".'Reply-To: '.$replyto."\r\n".'X-Mailer: PunBB Mailer';

note the first line makes that all automatic messages they now have an automatic reply to your webmaster address. If you don't want replies in your main email address, then the best thing is creating a new e-mail account in your server, in example rubbish@mydomain.com. In this way,  those e-mails will be received in that account which must be cleaned periodically. In this case,  you will need to replace the first line of this patch with your new e-mail address for the unwanted replies:

    $replyto = 'rubbish@mydomain.com';

that's all. Rest of messages between users inside the forum will work normally.

--

I don't understand exactly what you says. At least I don't see any code in register.php blocking Gmail addresses.
Gmail users are normally registered, at least in my forum.

What I have is several users of Yahoo and Hotmail, who cannot receive e-mails, both from registration process or  from the forgotten passwords.

I have check these messages truly are leaving from the server. Therefore there is something in the email headers that Yahoo and Hotmail don't like and probably they are blocked like spam. 

Specially, the e-mail headers lacks of a Reply to: field, and also they contain a
Received: from nobody by host.mydomain.com with local ....

maybe the sum of these two causes that blocking. I'm looking how replacing this issue.


Somebody know about that?

Hi,

this change allow moderators change the group of any user. Moderators cannot change others moderators.

profile.php
line 448 delete the line:

 if ($pun_user['g_id'] > PUN_ADMIN)

and put this:

 if ($pun_user['g_id'] > PUN_MOD)

In line 1501 search this segment:

<?php

        if ($pun_user['g_id'] == PUN_MOD)
        {

?>
                        <legend><?php echo $lang_profile['Delete ban legend'] ?></legend>
                        <div class="infldset">
                            <p><input type="submit" name="ban" value="<?php echo $lang_profile['Ban user'] ?>" /></p>
                        </div>
                    </fieldset>
                </div>

and below add this:

                <div class="inform">
                    <fieldset                        
                        <legend><?php echo $lang_profile['Group membership legend'] ?></legend>
                        <div class="infldset">
                            <select id="group_id" name="group_id">
<?php

                $result = $db->query('SELECT g_id, g_title FROM '.$db->prefix.'groups WHERE g_id!='.PUN_GUEST.' && g_id!='.PUN_ADMIN.' && g_id!='.PUN_MOD.' ORDER BY g_title') or error('Unable to fetch user group list', __FILE__, __LINE__, $db->error());

                while ($cur_group = $db->fetch_assoc($result))
                {
                    if ($cur_group['g_id'] == $user['g_id'] || ($cur_group['g_id'] == $pun_config['o_default_user_group'] && $user['g_id'] == ''))
                        echo "\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'" selected="selected">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
                    else
                        echo "\t\t\t\t\t\t\t\t".'<option value="'.$cur_group['g_id'].'">'.pun_htmlspecialchars($cur_group['g_title']).'</option>'."\n";
                }

?>
                            </select>
                            <input type="submit" name="update_group_membership" value="<?php echo $lang_profile['Save'] ?>" />
                        </div>
                    </fieldset>
                </div>
                <div class="inform">
                    <fieldset>

that's all.

yes, although the use of javascript is not bad idea at all.  In this example with this mod, just one must be careful so the bot don't parse the META.

In example:

<META HTTP-EQUIV=Refresh CONTENT="1; URL=checkjs.php">
<noscript><meta HTTP-EQUIV=Refresh CONTENT="0; URL=/forum/nojs.html"></noscript>
<? if ($_SESS['js'] == 1) { // ok } else { die("spam");   } ?>

and in checkjs.php:
<? 
  $_SESS['js'] = 1;
  header ("Location: register.php"); 
?>

as one simple idea which can be improved much more, of course. It force the visitor to use javascript to complete the registration.
If the bot comes from a form design of page register.php (which is very possible), then the execution will die.

I will implement this idea in my forum with the mod  of moaiamorfo, wich is enough to me.


best regards,

10

(11 replies, posted in Feature requests)

Hi,

well, it is a good general practice. But also because in my country there is a law which force to protect sensible data of users in websites involving commercial transactions, religion, politics and such things. I'm developing a site of such characteristics then I prefer to implement this.
So it is not to avoid authorities or something like that but all the contrary. Also, note in that case it would be ridiculous when logs of visitors are in the server anyway.  My concern is about the site that I'm developing so the sensible data  will not visible in the database. Or at least I want to make it with this intention.  If some day somebody can break this, it will be another problem.



best regards,

11

(11 replies, posted in Feature requests)

well, in fact the idea is not avoid quantum cryptography but just having the ip's and emails cyphered inside the BD.

Putting the function to decipher information outside of the public space, and enconded with eaccelerator and a key of a good lenght. This can be enough to me. This function would be validated against the Administrator IP, some characters of the agent navigator or whatever.

Just I wonder about the difficulty of having the ip's and e-mails cyphered because all the sql querys. Then to know if maybe it can be solved in a quick way or at the contrary one will need to apply this function everywhere.

best regards,

Hi all,

I have observed that some mods anti-spam doesn't work when Javascript is not active in the navigator.

I have check this with the mod of moaiamorfo http://punbb.org/forums/viewtopic.php?id=14069 .
When you disable Javascript, then registration is made it without need to enter nothing in the antispam inputs.
I fear it can happen with other mods using Javascript.

To avoid that, just include the following line in header.php (around line 64):

before <title>

<noscript><meta HTTP-EQUIV=Refresh CONTENT="0; URL=/forum/nojs.html"></noscript>

where /forum/nojs.html is the absolute path to a new html page to warn the user about the need to activate Javascript.


Check by yourself,


greetings,

13

(11 replies, posted in Feature requests)

well, I suppose any reverse function can works.  My idea is encoding the php scripts with eAccelerator.
Do you think there is any obstacle?. Please, tell me your thoughts smile

An example can be this from php.net:

<?php
 function encrypt($string, $key) {
   $result = '';
   for($i=0; $i<strlen($string); $i++) {
     $char = substr($string, $i, 1);
     $keychar = substr($key, ($i % strlen($key))-1, 1);
     $char = chr(ord($char)+ord($keychar));
     $result.=$char;
   }
   return base64_encode($result);

 }
 function decrypt($string, $key) {
   $result = '';
   $string = base64_decode($string);
   for($i=0; $i<strlen($string); $i++) {
     $char = substr($string, $i, 1);
     $keychar = substr($key, ($i % strlen($key))-1, 1);
     $char = chr(ord($char)-ord($keychar));
     $result.=$char;
   }
   return $result;
 }
?>

14

(11 replies, posted in Feature requests)

Hi,

lot of thanks for your extensive help. I think it solves most of my problems smile
Based on that, it works for some groups:

if ($pun_user['group_id'] != 1  && $pun_user['group_id'] != 2 && $pun_user['group_id'] != 4 && $pun_user['group_id'] != 5) {
   echo "Failure - ".$pun_user['username']." not permitted access.";
   require PUN_ROOT.'footer.php';
   exit();
}

I'm interested in implementing encryption for IP and email addresses in the database.  Just to add security and privacy in case of holes in the server.

I suppose it is not difficult while there is only need of a simple function with mcrypt or similar. However  I'm newbie with PunBB; then to save time I have asked about something similar.

anyway thanks a lot for the code and the mod.  smile

15

(11 replies, posted in Feature requests)

Hi,

please tell me if somebody know about the existence of mods for:

- Hide the users list for new users

- Moderate only the new user's posts

- Encrypt IPs in the database.



thanks,

Just to say I have finished the importation of the Yahoo list.

It was a little headache, specially to polish the different messages codifications. At the other, exportation towards PunBB was quite easy.
I leave his message here so if somebody need some comments just write me.

.. ok.  Thanks! 

Before start with this, I was reviewing several board systems before choosing PunBB. I choose PunBB because it's very speedy, clean and well-builded. Congratulations to the developers.

I'm making a PHP script to migrate a whole Yahoo list inside PunBB.  I have extracted the list using a third perl utility, and now I'm trying to parse the files with around 4.000 messages. Later I plane inserting them in PunBB tables and processing them.

If there is more people with experiences in this matter, please send me your thoughts!.


thanks smile

Hi all,

I'm migrating one Yahoo Groups list to PunBB. I have all messages ready to import, except that I don't understand the PunBB format date. 

I need to convert this format "Mon, 12 Mar 2007 19:32:21" to PunnBB format. Inside the PunBB MySQL tables I have this '1174525126'  but I don't know how to interpret this.

Please, some help!!


thanks,