Index: captcha.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/captcha/captcha.install,v
retrieving revision 1.11
diff -u -b -u -p -r1.11 captcha.install
--- captcha.install	19 Dec 2009 00:24:36 -0000	1.11
+++ captcha.install	28 May 2010 00:58:27 -0000
@@ -37,6 +37,12 @@ function captcha_schema() {
         'type' => 'serial',
         'not null' => TRUE,
       ),
+      'token' => array(
+        'description' => 'One time CAPTCHA token.',
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => FALSE,
+      ),
       'uid' => array(
         'description' => "User's {users}.uid.",
         'type' => 'int',
@@ -268,3 +274,15 @@ function captcha_update_6201() {
   $items[] = update_sql("UPDATE {captcha_points} SET module = 'captcha', type = 'Math' WHERE module = 'text_captcha' AND type = 'Text';");
   return $items;
 }
+
+
+/**
+ * Implementation of hook_update_N()
+ * Add a CAPTCHA token column to captcha_sessions table.
+ */
+function captcha_update_6202() {
+  $ret = array();
+  db_add_column(&$ret, 'captcha_sessions', 'token', 'varchar(64)');
+  return $ret;
+}
+
Index: captcha.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/captcha/captcha.module,v
retrieving revision 1.103.2.5
diff -u -b -u -p -r1.103.2.5 captcha.module
--- captcha.module	26 May 2010 10:51:05 -0000	1.103.2.5
+++ captcha.module	28 May 2010 00:58:27 -0000
@@ -213,6 +213,14 @@ function captcha_process($element, $edit
     '#value' => $captcha_sid,
   );
 
+  // Additional one time CAPTCHA token: store in database and send with form.
+  $captcha_token = md5(mt_rand());
+  db_query("UPDATE {captcha_sessions} SET token='%s' WHERE csid=%d", $captcha_token, $captcha_sid);
+  $element['captcha_token'] = array(
+    '#type' => 'hidden',
+    '#value' => $captcha_token,
+  );
+
   // Get implementing module and challenge for CAPTCHA.
   list($captcha_type_module, $captcha_type_challenge) = _captcha_parse_captcha_type($element['#captcha_type']);
 
@@ -457,9 +465,22 @@ function _captcha_get_posted_captcha_inf
     // No posted CAPTCHA info found (probably a fresh form).
     $post_data = array();
   }
-  // Return the posted form_id and CAPTCHA session ID.
+  // Get the posted form_id and CAPTCHA session ID.
   $posted_form_id = isset($post_data['form_id']) ? $post_data['form_id'] : NULL;
   $posted_captcha_sid = isset($post_data['captcha_sid']) ? $post_data['captcha_sid'] : NULL;
+
+  // Check if the posted CAPTCHA token is valid for the posted CAPTCHA session.
+  if ($posted_captcha_sid != NULL) {
+    $posted_captcha_token = isset($post_data['captcha_token']) ? $post_data['captcha_token'] : NULL;
+    $expected_captcha_token = db_result(db_query("SELECT token FROM {captcha_sessions} WHERE csid = %d", $posted_captcha_sid));
+    if ($expected_captcha_token != $posted_captcha_token) {
+      drupal_set_message(t('CAPTCHA session reuse attack detected.'), 'error');
+      // Invalidate the CAPTCHA session.
+      $posted_captcha_sid = NULL;
+    }
+    // Invalidate CAPTCHA token to avoid reuse.
+    db_query("UPDATE {captcha_sessions} SET token=NULL WHERE csid=%d", $captcha_sid);
+  }
   return array($posted_form_id, $posted_captcha_sid);
 }
 
@@ -478,16 +499,9 @@ function captcha_validate($element, &$fo
   // Get CAPTCHA response.
   $captcha_response = $form_state['values']['captcha_response'];
 
-  // We use the posted CAPTCHA session ID instead of
-  // $form_state['values']['captcha_sid'] because the latter contains the
-  // captcha_sid associated to the 'newly' generated element,
-  // while the former contains the captcha_sid of the posted form.
-  // In most cases both will be the same because of persistence.
-  // However, they will differ when the life span of the CAPTCHA session
-  // does not equal the life span of a multipage form and then we have to
-  // pick the right one.
-  list($posted_form_id, $posted_captcha_sid) = _captcha_get_posted_captcha_info($element, $form_state);
-  $csid = $posted_captcha_sid;
+  // Get CAPTCHA session from CAPTCHA info
+  // TODO: is this correct in all cases: see comment and code in previous revisions?
+  $csid = $captcha_info['captcha_sid'];
 
   $solution = db_result(db_query('SELECT solution FROM {captcha_sessions} WHERE csid = %d AND status = %d', $csid, CAPTCHA_STATUS_UNSOLVED));
 
