0

Last year, I began building a website for an organisation. It contains a contact form implemented in PHP, with a Securimage captcha.

Today I checked the database table into which contact messages are archived, and discovered it to be full of spam! The spams started in August 2022, about a month after the contact form was put live, and the rate of spams peaked in August-September at 11 per day. Now they're one every 1-2 days, which is still a far cry from the level that having a captcha is meant to get it down to. Going by the recent logs, it doesn't seem to be a brute-force attack or anything like that.

The code to render the contact form is like this:

<form method="post" action="contact">
    <div class="tabForm">
    <?php
        renderInlineField('name', 'Your name:');
        renderInlineField('email', 'Email address:');
        renderInlineField('subject', 'Subject:');
    ?>
        <p><?php renderLabel('message', 'Message:'); ?> <textarea style="width: 100%; height: 15em;" name="message" id="message"><?php echo htmlspecialchars($message); ?></textarea></p>
        <p><img id="captcha" src="securimage/securimage_show.php" alt="[CAPTCHA Image]" /></p>
        <p><audio id="captcha_one_audio" preload="none" controls="controls">
            <source id="captcha_one_source_wav" src="securimage/securimage_play.php?id=<?php echo uniqid(); ?>" type="audio/wav" />
        </audio></p>
        <p><?php renderLabel('captcha', 'Please enter the characters you see/hear:'); ?> <input type="text" name="captcha" id="captcha" size="10" maxlength="6" /></p>
        <p><input type="submit" value="Submit" /></p>
    </div>
</form>

The two functions (besides uniqid) are defined as follows:

function renderInlineField($fieldName, $htmlLabel) {
    global $errors;
    echo "<p class='inline'>";
    renderLabel($fieldName, $htmlLabel);
    echo "<input type='text' name='$fieldName' id='$fieldName' maxlength='255' value='", htmlspecialchars(@$_POST[$fieldName]), "' /></p>";
}

function renderLabel($fieldName, $htmlLabel) {
    global $errors;
    echo "<label for='$fieldName'>";
    if (!empty($errors[$fieldName])) echo "<span class='error'>($errors[$fieldName])</span> ";
    echo "$htmlLabel</label>";
}

The code that verifies the captcha:

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $name = sanitise($_POST['name']);
    $email = trim($_POST['email']);
    $subject = sanitise($_POST['subject']);
    $message = trim($_POST['message']);
    $captcha = $_POST['captcha'];
    
    $errors = [];
    
    session_start();
    require_once 'securimage/securimage.php';
    $securimage = new Securimage();

    if (empty($name)) $errors['name'] = 'missing';
    
    if (empty($email)) {
        $errors['email'] = 'missing';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'not valid';
    }
    if (empty($message)) $errors['message'] = 'missing';
    
    if (empty($captcha)) {
        $errors['captcha'] = 'missing';
    } else if (!$securimage->check($captcha)) {
        $errors['captcha'] = 'not entered correctly';
    }
    
    if (empty($errors)) {
        // code to store the message in the database and email it to the intended recipient
    }
}

My questions are:

  • Can anyone see any holes in this code which a spambot would be able to exploit?
  • Has anyone found a solution that works?
Stewart
  • 3,935
  • 4
  • 27
  • 36
  • Not saying this is the case for your org, but there are services that sell Captcha solving/bypass on a per-use basis. That's something that will not be countered any time soon, unfortunately. – Aaron Meese Jun 11 '23 at 19:39
  • @AaronMeese Hmm. Surely if spammers can find these services, so can the police, and get them shut down. Unless they're based in a very anarchic country. Failing that, maybe the solution would be not one of making the captcha itself more secure against such attacks, but one of adding more layers to the overall process. – Stewart Jun 11 '23 at 20:33
  • You could try going to the police but you're probably better off switching to reCaptcha. – pguardiario Jun 11 '23 at 23:51

0 Answers0