As requested by Dries, I've created a set of functions which provide support for captcha form elements. I opted to use a text based question/answer captcha for my implementation instead of an image to prevent issues with visually impaired users. This implementation is also much less resource intensive.

The form_captcha() function sets an answer variable and returns a captcha field with a question and the valid_captcha() function compares an input with the variable.

These functions should make adding captcha support to modules trivial.

Example usage:

// add captcha field
$form .= form_captcha();

// validate captcha response
if (valid_captcha($_POST['captcha']))
  print ('valid');

Comments

tangent’s picture

StatusFileSize
new3.1 KB

Oops. I already noticed a bug. Shamefully, I haven't thoroughly tested these validation functions but the form function does what it should. The valid_captcha function may require more robust checking.

Steven’s picture

Variables are global, hence using variable_set for "captcha answer" won't work as it will change constantly. I suggest you use sessions instead ($_SESSION), associating the captcha with a person and a Drupal page.

I'm not too sure about the whole math approach. The question is output in text and can easily be converted back into a result by a script. This is 5 minutes of work for a spammer.

Overall, the code seems much too complicated for what it does. The code style is also not up to snuff (unenclosed strings, spaces, t() usage, ...).

If we do captchas, we should implement a more robust approach, probably image-based.

Steven’s picture

Here's a quick proof of concept using the arrays above:

  if (preg_match('/What is ([a-z]+) ([a-z ]+) ([a-z]+)\?/i', $q, $match)) {
    $match[1] = array_search($match[1], $numbers);
    $match[3] = array_search($match[3], $numbers);
    $match[2] = array_search($match[2], $operators);
    
    $answer = eval('return '. $match[1] . $match[2] . $match[3] . ';');
    print $answer;
  }
Steven’s picture

Err, these:

  $numbers = array(1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five',
                   6 => 'six', 7 => 'seven', 8 => 'eight', 9 => 'nine', 10 = 'ten',
                   11 => 'eleven', 12 => 'twelve');
  
  $operators = array('+' => 'plus', '-' => 'minus', '*' => 'multiplied by', '/' => 'divided by');

(note how the eval() usage in my code makes different code pieces unnecessary).

Bèr Kessels’s picture

Why not add an interface where admins can define their own questions, and offer a set of default questions that will do. Having consistant answers and questions will be cracked. Each pattern can be found by a spammer/hacker, so we must avoid using patterns in answers/questions.

Furhtermore, i think we need two captcha modules. The one existing useing images, and another one using text. That way admins can install (choose) the implementation they think best. (I, for one oppose against any form of image-captcha, since it makes Drupal inaccessible to anyone who is not visiting with a visual browser. And it breaks section 508 compat.)

dries’s picture

Textual questions introduce administrative overhead, and even if they are unique to your site they can be by-passed easily. It won't help to tell computers and humans apart. It merely adds an extra barrier.

I strongly favor image captcha's. Whether you use them (and lock out visually impaired users) is for the administrator to decide. Visual impaired users could always create a user account to circumvent captchas. We could explain this in the captcha's form description.

Stefan Nagtegaal’s picture

In my opinion this feature request is very much related to http://drupal.org/node/13539 and http://drupal.org/node/14708..
I think it's better to 1st implement a way to extend the comment forms and after that, the captcha.module could use an overhaul to support the new commentapi..

There is another big advantage we take from this approach, ie:
- Not everybody need captcha support in core, so like this we keep it simple, optional and flexible.

dries’s picture

"Not everybody need captcha support in core, so like this we keep it simple, optional and flexible."

A lot of forms (comment module, user module, node module, chat module, contact module, feedback module, etc) could use captcha support. Only a fraction of all forms can be extended using an API. They can only use it if there is a form_captcha() in core.

tangent’s picture

RE Comment #2

You're right about the global variables. I thought they were session based. I'll change that.

RE Comment #3 and #4

It is my understanding that use of eval() is to be avoided if possible due to the way that it functions which is why I used that method that I did. It may be a few extra logic steps but is likely more efficient.

As far as text versus images, if you read about captchas you'll find that several people have already written programs to reliably (30-100% depending on the obfuscation technique) read the text in an image. This coupled with a slight amount of brute force means that images are no more effective than complicated text. I will readily agree that my math questions are far to patterned to defeat a parsing script and were only used as a stopgap measure to defeat scripts that don't yet deal with captchas.

The text question generation algorithm could be much more sophisticated to create questions that follow a widely varying pattern, though this would created difficulty for localization as well as the fact that if a program can create a string another program can decrypt it.

Alternatively, using a database of questions and answers could easily work just as well for an attacker as for us.

If there is an ideal solution, I have yet to hear it.

I can easily replace the text with an image if that is the way the consensus goes but I agree with Bèr Kessels on this. Offering a configurable choice between question and image is also an option.

chx’s picture

+1 on form_captcha functionality. I like this question approach, images are very bothersome. However, division should not be used -- what is 7 / 3? There is no good answer for this.

tangent’s picture

You should notice that for the code crafts questions which can only have positive integer values, even for the division operator.

chx’s picture

Silly me. Of course, you are right. +1 for this from me.

tangent’s picture

StatusFileSize
new3.12 KB

Here's an updated patch which addresses the captcha variable issue.

dries’s picture

Small nit: we write $first_number, not $firstNumber (nor $firstnumber).

The math questions are way to easy to break/by-pass. Let's stop using that.

jeremy’s picture

However this is ultimately merged into core, I'd like to see an API that could be used by the spam module (and anything else). Specifically, on my site I'd want to configure things so that only content that the spam module thinks is spam prompts with a captcha image. (ie, a user enters a comment, the spam module rates it a 94% chance of being spam, so an error is displayed explaining the problem and telling the user he needs to validate that he's a real person by typing what is displayed in the captcha image...) I've had quite a bit of negative feedback from users regarding the displaying of captcha's, but I think this would be a good compromise.

Anonymous’s picture

realy cannot see the difference between introducing form_captcha() apis and extending form_pres to moer areas.
I favour the later, since it will maintain flexibility, extendibility and can ths be used by any sort of (captcha) module.

I for one am not at all happy with capchas, allthough I beleive for a lot of implementation they are usefullm, I will not want to use them for myself or my clients. therefre I beleive I (and all the people who think alike me in this cause) should also be served by using the Drupal Method: "Introduce a general standard method, that can be used by modules to add functionality. Rather than adding the functionality directly"

Bèr Kessels

tangent’s picture

RE: Comment #14

My next revision will either use an image or a more complex text question. I've been giving thought to the question problem but haven't come up with a definite solution yet.

A combination of the 2 methods would be even harder to defeat it seems. A question delivered in an obfuscated image would require a combination of things to circumvent. This will make me feel bad though for implementing something that will lock out a portion of users.

tangent’s picture

I haven't done anything on this over the holidays but I just thought I'd mention that to effectively obfuscate text characters in an image would require the GD filters, which are only available as of PHP5, or a similar library. The code that I posted to the captcha module uses a random truetype font for each character and rotates each character a bit as well as generating random arcs and lines for "noise" but these are unlikely to fool any decent OCR so it is really no better than text. Therefore, it is fairly important to warp the text to make it more difficult for an OCR tool to read. However, Drupal does not yet support PHP5 as far as I know.

tangent’s picture

In order to use images for this feature (instead of text) there are seemingly two options.

1. Generate and save an image at a specific URI and then return a string with html which contains a link to the image. A cron job would periodically delete generated image files.

This option has the disadvantage that file management must be done and would require that basic file management functionality would be a mandatory part of the core.

2. Return a string with html which contains a link to a URI which returns a dynamically created image.

This option is the implementation that I used in my update of the captcha module. It has the advantage that no files are stored locally. It is also easier to control the HTTP headers (to control caching) returned with the image in code than it is with files. However, this implementation would require a new URI to be created to access the image and would seem to be outside the scope of the requested form_captcha() function.

tangent’s picture

I'm still considering a text question based method which would be difficult to defeat with a simple regexpr. I've been thinking about scenarios where the answer is embedded in the question and takes a bit of understanding instead of simple computation.

What is the Nth word in this question?
What is the Nth letter in the Mth word in this question?
How many letters does the Nth word in this question have?
This question contains how many words?
How many words are there in this question?
The number of words in this question is what?

The question could be varied quite a lot which would make it much more difficult to simply craft a regexpr to calculate the answer. Additionally, when the module is translated and used in other languages the system would be even less likely to be defeated.

These thoughts may be outside the scope of discussion here but I don't want to give up on this without at least trying. I welcome feedback.

tangent’s picture

I have come to the conclusion that placing a turing (captcha) element on forms is (and will never be more than), at best, a short term solution to a long-term problem and that efforts should be placed elsewhere to solve the problem (submission content analysis, host filtering, identity enforcement, etc.). This being the case, I have lost motivation to work on this further.

To elaborate, the more sophisticated the attacker gets the more sophisticated the captcha must get. It is unlikely that a software generated test can be created that cannot be eventually circumvented by software. Further, adding this test is a detriment to usability, especially as the challenge becomes more complicated or reliant upon a single human sense.

See this study for further details.

The simple math question based solution that I implemented in my captcha.module update works for me now but I don't see an image captcha in the core as a long term solution. The requested solution is not impossible (or even difficult) to implement but it will be tedious to maintain assuming that circumvention techniques are frequently implemented by attackers.

If anyone has a differing opinion please share it. Otherwise, I'll set this issue as wontfix.

grantbow@civicspacelabs.org’s picture

It may not be perfect, and while the spam arms race may be a difficult one to keep on top of, something is better than nothing, isn't it?

grohk’s picture

I have to agree with Grantbow here. It may be a stop gap measure, but it is needed. Many people leave comments on my site that I appreciate and I would miss them if I was forced to shutdown anonymous semi-verified commenting. Captchas may present a usability problem in many ways, but not having the ability to leave offhand comments will squelch many more potential participants. We need to think about this more.

Jerk Face’s picture

If Drupal decides to support captchas, there needs to be a mechanism in place so that the blind can still access the site. Screen readers (obviously) do not pick up captchas, so there needs to be an alternate way of verifying that the user is a real person.

Nick

tangent’s picture

If we are willing to live with text captchas as a near term solution, it might be sufficient to implement a system where the admin can customize the questions such that an attacker would have to craft a different script for each site or create a database of questions culled from all sites.

Bèr Kessels’s picture

a captcha module would be the solution, but i remain against captcha in core.

Why not aim your arrows at aPIs for the requested forms for users, nodes and comments, and create a module that uses these hooks?

And last, but not least, I have spam module enabled. The first week some spam slipped trough the mazes, but the last three weeks only one spam was not cought, on approx 200 attempts to post spam, that is nearly perfect! I can therfore conclude that captcha to hold back spam might be usefull but is not required.

tangent’s picture

Assigned: tangent » Unassigned

Unassigning this since the feature seems forgotten or unwanted anyway. Should it be WONTFIXed?

chx’s picture

Status: Active » Closed (won't fix)

There. With forms API you can insert it if you still believe it's any good.