Despite an onslaught of scorn and ridicule Hugh stuck to his guns, and in 1894 founded The Sharper Image.
Daze of Our Lives

PHPBB 3.0x Registration Spam Control Mechanism

Forum registration is the gateway to all manner of forum spam and staunching spam at the front door is the most effective policy. In the case of PHPBB forums, a particular method has been developed that is not unique in concept, but perhaps in execution.

The method in question basically imposes the requirement of additional information to be included with CAPTCHA code. Early versions required only a particular character to be prepended to the confirmation code value retrieved in the PHPBB3 forum file, ucp_register.php. However, the further enhancement described here involves random characters that are to some degree cloaked from detection by spam bots.

The cloaking method used is based on this article on cloaking emails. The character, itself, is generated by the following method

#random generator of non-alphanumeric ascii characters
while (is_numeric($randchar = chr(rand(33, 64)))) { continue; }

This random value is passed to a template variable in url encoded form. When the template is downloaded, a javascript decoder function found on Eric Meyer’s web site, converts the character back.

The decode function shown here is placed in the head of the ucp_register.html template file.

  function decode() {
      var obj = document.getElementById('dencoder');
      var encoded = obj.value;
      obj.value = unescape(encoded.replace(/\+/g,  " "));
  }

  <!-- IF S_TIME -->
    window.onload = disable_and_handle;
    setInterval("disable(false)", {S_TIME});
  <!-- ENDIF -->
// ]]>
</script>

At the bottom of the file, a call to this function is entered,

</form>
<script>
document.getElementById('dencoder').value = "{CONFIRM_CHAR}";
decode();
</script>

<!-- INCLUDE overall_footer.html -->

The element with the id ‘dencoder’ is a text type input box which appears in this part of the same file,

  <tr>
    <td class="row1"><b class="genmed">{L_CONFIRM_CODE}: <span><input type="text" size="4" id="dencoder" name="confirm_char" value="{CONFIRM_CHAR}"></span></b><br /><span class="gensmall">{L_CONFIRM_CODE_EXPLAIN}</span></td>
    <td class="row2"><input class="post" type="text" name="confirm_code" size="9" maxlength="9" /></td>
  </tr>

The random character passed to the CONFIRM_CHAR template variable shown above is the value that appears in a text box next to the L_CONFIRM_CODE value.

I decided to put the char generator in the functions.php file, at the beginning of the function page_header(),

/**
* Generate page header
*/
while (is_numeric($randchar = chr(rand(33, 64)))) { continue; }

function page_header($page_title = '', $display_online_list = true)
{
  global $db, $config, $template, $SID, $_SID, $user, $auth, $phpEx, $phpbb_root_path, $randchar;

Note within the function the extra global variable, $randchar. Further down the functions.php file is the following assignment to the CONFIRM_CHAR variable,

  // The following assigns all _common_ variables that may be used at any point in a template.
  $template->assign_vars(array(
    'SITENAME'            => $config['sitename'],
    'SITE_DESCRIPTION'        => $config['site_desc'],
    'CONFIRM_CHAR'          => sprintf('%s', urlencode($randchar)),

Notice how $randchar is encoded at this point. This value will be passed to the aforementioned javascript function for conversion back to an ascii character which will appear in a text box on the registration form. Here’s a view of that text box.

form view

The prefix shown is the bang character. If you look at the source for that character, it shows up as a url encoded hex version as shown here,

source code

As you can see, the value that appears in the source code is “%21″ rather than “!”. Some characters generated are not susceptible to url encoding and will appear the same in both places, a fact that introduces further unpredictability, overall.

The text that appears in the confirmation image is edited in the /language/en/common.php file.

The operation that adds the random character to the confirmation code variable is a series of steps performed in the ucp_register.php file. Having gotten the character situated in an input form element, it was a matter of retrieving that form value and adding it to the CAPTCHA string.

So, the form value from the new input element was added to the $data array created in the file located here /includes/ucp/ucp_register.php,

$data = array(
  'username'      => utf8_normalize_nfc(request_var('username', '', true)),
  'new_password'    => request_var('new_password', '', true),
  'password_confirm'  => request_var('password_confirm', '', true),
  'email'        => strtolower(request_var('email', '')),
  'email_confirm'    => strtolower(request_var('email_confirm', '')),
  'confirm_code'    => request_var('confirm_code', ''),
  'confirm_char'    => request_var('confirm_char', ''),
  'lang'        => basename(request_var('lang', $user->lang_name)),
  'tz'        => request_var('tz', (float) $timezone),
);

The variable of interest here is confirm_char. I just aped the previous character in the array. The initialization section follows,

$error = validate_data($data, array(
  'username'      => array(
    array('string', false, $config['min_name_chars'], $config['max_name_chars']),
    array('username', '')),
  'new_password'    => array(
    array('string', false, $config['min_pass_chars'], $config['max_pass_chars']),
    array('password')),
  'password_confirm'  => array('string', false, $config['min_pass_chars'], $config['max_pass_chars']),
  'email'        => array(
    array('string', false, 6, 60),
    array('email')),
  'email_confirm'    => array('string', false, 6, 60),
  'confirm_code'    => array('string', !$config['enable_confirm'], 5, 9),
  'confirm_char'    => array('string', !$config['enable_confirm'], 1, 1),
  'tz'        => array('num', false, -14, 14),
  'lang'        => array('match', false, '#^[a-z_\-]{2,}$#i'),
));

Again, the confirm_char is the new neighbor of confirm_code. Note: the confirm_code upper length was changed from 8 to 9.

Finally there’s the assignment of the new variable to the confirm_code value,

$sql = 'SELECT code
  FROM ' . CONFIRM_TABLE . "
  WHERE confirm_id = '" . $db->sql_escape($confirm_id) . "'
    AND session_id = '" . $db->sql_escape($user->session_id) . "'
    AND confirm_type = " . CONFIRM_REG;
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
//$row['code'] = "@" .$row['code'];
$row['code'] = $data['confirm_char'] . $row['code'];
$db->sql_freeresult($result);

I left the old line in there to show the similarity between the two — the fixed character is the “at sign”. The variable $data['confirm_char'], which contains the random value from the form element with the name confirm_char is prepended to the current confirmation code.

As a consequence, the confirmation code now has to be accompanied by the extra random character in order to submit a registration form successfully. Attempts to detect the identity of the extra character via automation are likely to be stifled by the cloaking method applied to it.

You can do the same thing for anonymous posts, editing the posting.php and posting_body.html files accordingly. If you want to keep the original form language in the posting_body.html template for registered users, you’ll need to create separate template variables in the aforementioned common.php file to accomodate both.