1 (edited by variousbagels 2008-11-30 19:48)

Topic: [Mod] ColorCaptcha

In Practice: here
By Itself: here
EDIT:: please see here for the newer version.

I was tossing this idea around in my head for a while before I started using PunBB.  The idea is simple: have the user differentiate between colors of characters.  The user would only enter in the characters of a certain color to pass the captcha.  It was meant to be easy for the user to understand but difficult for a bot to.  The bot would try to enter in all of the letters, normally, and fail the test.
I'm not saying that it's 100% uncrackable, which I'm sure it isn't, but I think it is a nice idea.  I have not seen this done anywhere before, but it might have been.
Here is the modified "image.php" code from the "pun_antispam" extension:

<?php

/**
 * Generates a CAPTCHA picture
 *
 * @copyright Copyright (C) 2008 PunBB, partially based on code by Jamie Furness
 * @license http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
 * @package pun_antispam
 * modified by James Anderson :)
 */

// Generate a random string
function pun_antispam_rand_str()
{
    return strtr(substr(strtolower(md5(uniqid(rand(), 1))), 2, 6), 'abcdef', '165380');
}

// Output CAPTCHA string into an image
function pun_antispam_image($string)
{
    $im      = imagecreatetruecolor(100, 18);
    // Make the background white
    imagefilledrectangle($im, 0, 0, 99, 17, 0xFFFFFF);
    $red     = imagecolorallocate($im, 255, 0, 0);
    $blue    = imagecolorallocate($im, 0, 0, 255);
    $noise   = imagecolorallocate($im, 255, 0, 0);
    $x = 0;
    while($x <= 5) {
        $usechars  .= substr($string, mt_rand(0, strlen($string)-1), 1);
        $color = rand(0,1); //is this character red or blue? make it up
        $pallet = $pallet.$color;    //This attaches a number, 0 or 1, to each character, determining whether it is red or blue
                                    //pallet is an all numerical string. there are no letters in pallet
        $x++;
    }
    $pallet{0} = rand(0,1); //if the first is red, the last has to be blue, and vice versa
    if($pallet{0} == 1) {   //this is to ensure that there will always be at least one red and at least one blue
        $pallet{5} = 0;
    }
    else {
        $pallet{5} = 1;
    }
    $px      = rand(1,10); //moves it around a bit, x

//draw each character
//reads pallet to see whether the letter is red or blue
    $j = 0;
    while($j <= 5) {
        if($pallet{$j} == 0) {
            $y = rand(2,4);
            imagestring($im, 5, $px, $y, $usechars{$j}, $red);
            $list .= $usechars{$j}; //make the list of red chars
        }
        elseif($pallet{$j} == 1) {
            $y = rand(2,4);
            imagestring($im, 5, $px, $y, $usechars{$j}, $blue);
        }
        else { die("Something went wrong in pallet. It didn't make any colors."); }
        $j++;
        $px = $px + 15; //move for the next char
    }
    //make some noise
    while($k<(100*18)/1000) { //where it says 1000, raise/lower the value to increase or decrease the noise leve. Higher numbers = LESS noise
        imageline($im, mt_rand(0,100), mt_rand(0,18), mt_rand(0,100), mt_rand(0,18), $noise);
        $k++;
    }
    sleep(1);
    header('Content-type:image/gif');
    header('Cache-Control: no-cache, must-revalidate');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    header('Pragma: no-cache');

    imagegif($im, null, 30);
    imagedestroy($im);

    return $list; //the list of red chars
}

session_start();

$pun_antispam_string = pun_antispam_rand_str();
$_SESSION['pun_antispam_text'] = pun_antispam_image($pun_antispam_string);

?>

And I modified the English language file (pun_antispam.php) too, to make it more obvious to the user:

<?php

// Language definitions used in pun_antispam
$lang_pun_antispam = array(
    'Captcha'                        =>    'Captcha',
    'Captcha Info'                    =>    'Please enter <i>only</i> the <b><font color="red">red</font></b> characters in the image.',
    'Invalid Text'                    =>    'The captcha text you entered appears to be wrong. <b>Please <i>only</i> enter the <font color="red">red</font> characters.</b>',

    'Captcha admin head'            =>    'Setup which actions are protected by a captcha',
    'Captcha admin info'            =>    'You may enable captcha in certain areas of your forum if you are experiencing problems with spam.',
    'Captcha admin legend'            =>    'Enable Captcha',

    'Captcha registrations info'    =>    'Require a captcha before users may register. This can be helpful to stop spam.',
    'Captcha login info'            =>    'Require a captcha when logging in. This can be helpful to stop brute-force attacks.',
    'Captcha reset info'            =>    'Require a captcha to be entered when a user tries to reset their password.',
    'Captcha guestpost info'        =>    'Require a captcha for guest posting (if enabled one).'
);

?>

Please feel free to make any further modifications if PunBB's license allows it.  You can see this in practice here and the image by itself here.

I also thought about this usage with colorblind users.  Although the colors might not look like how a non-colorblind user sees, they would still be able to tell the difference between red and blue.  The colorblind would most likely know how they see red and how they see blue, and could still pass the captcha.

Please leave any questions or comments, you can use this in any way you'd like as long as it follows PunBB's license. I'm actually not familiar with the license myself, so if I wasn't even allowed to edit this extension then I apologize. Please also feel free to make any improvements you can think of.

Re: [Mod] ColorCaptcha

Nice, thanks!

Re: [Mod] ColorCaptcha

Glad you like it! Do you see anything that could be changed?

Re: [Mod] ColorCaptcha

mmmm no

Re: [Mod] ColorCaptcha

http://garciat.us.to/pics/image.php.gif

You could make it more random by adding other colors (only 2 will be used on a single image), randomizing the color needed for the input (it's always red), adding some letters, and maybe using a longer string.

6 (edited by variousbagels 2008-11-30 20:50)

Re: [Mod] ColorCaptcha

Edit: Fixed a few bugs. The code below is now corrected.

Garciat wrote:

You could make it more random by adding other colors (only 2 will be used on a single image), randomizing the color needed for the input (it's always red), adding some letters, and maybe using a longer string.

                        Garciat - Die hard

My Extensions and Me

Your wish is my command.

image.php has been changed to:

<?php

/**
 * Generates a CAPTCHA picture
 *
 * @copyright Copyright (C) 2008 PunBB, partially based on code by Jamie Furness
 * @license http://www.gnu.org/licenses/gpl.html GPL version 2 or higher
 * @package pun_antispam
 * modified by James Anderson :)
 */

// Generate a random string
function pun_antispam_rand_str()
{
    return strtr(substr(strtolower(md5(uniqid(rand(), 1))), 2, 6), 'abcdef', '165380');
}
//decide what color to use
function pun_antispam_random_color() {
    $random_color = rand(0, 2);
    switch($random_color) {
        case "0";
        $use = "red";
        break;
        case "1";
        $use = "blue";
        break;
        case "2";
        $use = "green";
        break;
    }
    return $use;
}

// Output CAPTCHA string into an image
function pun_antispam_image($string, $usecolor)
{
    $im      = imagecreatetruecolor(100, 18);
    // Make the background white
    imagefilledrectangle($im, 0, 0, 99, 17, 0xFFFFFF);
    //other colors
    $red     = imagecolorallocate($im, 255, 0, 0);
    $blue    = imagecolorallocate($im, 0, 0, 255);
    $green   = imagecolorallocate($im, 0, 168, 20);
    $noise   = imagecolorallocate($im, 255, 0, 0);
    $x = 0;
    while($x <= 5) {
        $color = rand(0,2); //is this character red or blue? make it up
        $pallet = $pallet.$color;    //This attaches a number, 0 or 1, to each character, determining whether it is red or blue
                                    //pallet is an all numerical string. there are no letters in pallet
        $x++;
    }
    //if the first is red, the last has to be blue, and vice versa
    //this is to ensure that there will always be at least one red and at least one blue
    //also if the first one is blue, then the second one is green, and if the last one is blue, then the next to last one is green
    $pallet{0} = rand(0,1);
    if($pallet{0} == 1) { //the first one is blue
        $pallet{5} = 0; //the last one is red
        $pallet{1} = 2; //the second one is green
    }
    elseif($pallet{0} == 0) { //the first one is red
        $pallet{5} = 1; //the last one is blue
        $pallet{4} = 2; //the second to last one is green
    }
    $px      = rand(1,10); //moves it around a bit, x

//draw each character
//reads pallet to see whether the letter is red or blue
    $j = 0;
    while($j <= 5) {
        $color = rand(0, 2);
        if($pallet{$j} == 0) {
            $y = rand(2,4);
            imagestring($im, 5, $px, $y, $string{$j}, $red);
            if($usecolor == "red")
                $list .= $string{$j}; //make the list of red chars
        }
        elseif($pallet{$j} == 1) {
            $y = rand(2,4);
            imagestring($im, 5, $px, $y, $string{$j}, $blue);
            if($usecolor == "blue")
                $list .= $string{$j}; //make the list of blue chars
        }
        elseif($pallet{$j} == 2) {
            $y = rand(2,4);
            imagestring($im, 5, $px, $y, $string{$j}, $green);
            if($usecolor == "green")
                $list .= $string{$j}; //make the list of green chars
        }
        else { die("Something went wrong in pallet. It didn't make any colors."); }
        $j++;
        $px = $px + 15; //move for the next char
    }
    //make some noise
    while($k<(100*18)/1000) { //where it says 1000, raise/lower the value to increase or decrease the noise leve. Higher numbers = LESS noise
        imageline($im, mt_rand(0,100), mt_rand(0,18), mt_rand(0,100), mt_rand(0,18), $noise);
        $k++;
    }
    sleep(1);
    header('Content-type:image/gif');
    header('Cache-Control: no-cache, must-revalidate');
    header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
    header('Pragma: no-cache');

    imagegif($im, null, 30);
    imagedestroy($im);

    return $list; //the list of red chars
}

session_start();
$pun_antispam_string = pun_antispam_rand_str();
$_SESSION['use_color'] = pun_antispam_random_color();
$_SESSION['pun_antispam_text'] = pun_antispam_image($pun_antispam_string, $_SESSION['use_color']);
?>

and please also modify pun_antispam.php to this:

<?php
<?php
if(!@defined($_SESSION)) {
    session_start();
}
// Language definitions used in pun_antispam
$lang_pun_antispam = array(
    'Captcha'                        =>    'Captcha',
    'Captcha Info'                    =>    'Please enter <i>only</i> the <b><font color="'.$_SESSION['use_color'].'">'.$_SESSION['use_color'].'</font></b> character(s) in the image.',
    'Invalid Text'                    =>    'The captcha text you entered appears to be wrong. <b>Please <i>only</i> enter the <font color="'.$_SESSION['use_color'].'">'.$_SESSION['use_color'].'</font> character(s).</b>',

    'Captcha admin head'            =>    'Setup which actions are protected by a captcha',
    'Captcha admin info'            =>    'You may enable captcha in certain areas of your forum if you are experiencing problems with spam.',
    'Captcha admin legend'            =>    'Enable Captcha',

    'Captcha registrations info'    =>    'Require a captcha before users may register. This can be helpful to stop spam.',
    'Captcha login info'            =>    'Require a captcha when logging in. This can be helpful to stop brute-force attacks.',
    'Captcha reset info'            =>    'Require a captcha to be entered when a user tries to reset their password.',
    'Captcha guestpost info'        =>    'Require a captcha for guest posting (if enabled one).'
);
?>

Also modify post.php in your installation's root directory. Find

($hook = get_hook('po_end_validation')) ? eval($hook) : null;

and change that to

($hook = get_hook('po_end_validation')) ? @eval($hook) : null;

Edit:
And find in login.php in your installation's root directory

($hook = get_hook('li_login_form_submitted')) ? eval($hook) : null;

and change that to

($hook = get_hook('li_login_form_submitted')) ? @eval($hook) : null;

Find in login.php in your installation's root directory

<?php ($hook = get_hook('li_login_pre_remember_me_checkbox')) ? eval($hook) : null; ?>

and change that to

<?php ($hook = get_hook('li_login_pre_remember_me_checkbox')) ? @eval($hook) : null; ?>

I decided to leave the numbers as they were because I suppose the PunBB developers had a reason for it. I also removed one of the pesky lines of code that was actually generating a random string from the already generated random string. That wasn't good... ;p
The reason for the @'s is to suppress the errors that would be generated by conflicting sessions.  In the end, there is only one session created.  The errors are not visible because of the @'s and won't interfere with anything.  I didn't want to have to modify the hook for the extension which is the reason why I just added the @ in post.php instead of just removing the session_start in the hook.

Anyway, please keep up the feedback, I'd like to improve it even more.

Re: [Mod] ColorCaptcha

variousbagels wrote:

I decided to leave the numbers as they were because I suppose the PunBB developers had a reason for it.

You can use any numbers or letters you prefer.

variousbagels wrote:

The reason for the @'s is to suppress the errors that would be generated by conflicting sessions.

Is there errors in original pun_antispam extension? Please, post here error messages and describe when they appear.

Re: [Mod] ColorCaptcha

parpalak wrote:
variousbagels wrote:

I decided to leave the numbers as they were because I suppose the PunBB developers had a reason for it.

You can use any numbers or letters you prefer.

variousbagels wrote:

The reason for the @'s is to suppress the errors that would be generated by conflicting sessions.

Is there errors in original pun_antispam extension? Please, post here error messages and describe when they appear.

No, pun_antispam, the unmodded version, is error-free, as far as I can see.  The problem with adding the features that Garciat suggested is that the language file needed to receive session variables. So when I added session_start to the pun_antispam language file, it interfered with the hooks that also started sessions.  The extension still works because a session was started, though php returns a warning.

9 (edited by User33 2008-12-02 02:00)

Re: [Mod] ColorCaptcha

Can't you do something like

$lang_pun_antispam['Captcha info'] = 'Please enter <i>only</i> the <b><font color="%1$s">%1$s</font></b> character(s) in the image.';

then, when the string is needed

sprintf($lang_pun_antispam['Captcha info'], $_SESSION['use_color']);

Re: [Mod] ColorCaptcha

Thanks very much for the reply, but I'm not sure that would work, Garciat.  It looks like the language file is loaded before the image is, so the only way to really pass the variable (as far as I can see) is a session variable.  Are there any alternative methods?

11

Re: [Mod] ColorCaptcha

Exactly what I said? I actually meant that you should edit the 'Captcha info' string (and 'Invalid text', accordingly) in the $lang_pun_antispam array with the string I provided, and then use the sprintf() function to "pass" on the variable to the string when it is needed.

For example, if you open up 'lang/English/index.php', you'll notice that the first value says:

Moderated by %s

Why "%s"? Because on line 193 of 'index.php' the sprintf() is used to replace it with the mod's username. I haven't really tried sprintf() with a $_SESSION variable, but it should definitely work.