Securimage AJAX Validation: Legacy Guide & Migration Path
Securimage supported AJAX validation through its securimage_show.php and custom validation endpoints. If you're maintaining a legacy application that still uses this, here's how it worked — and more importantly, how to replace it with a maintained alternative.
Securimage has reached end-of-life and is no longer receiving updates. The last meaningful update was 2018. It has known remote code execution vulnerabilities (CVE-2017-14077, found in the AJAX example file) that will never be patched. The author recommends switching to hCaptcha. Do not deploy Securimage in new projects.
How Securimage AJAX Worked
The typical pattern used three components:
1. CAPTCHA Image Endpoint
<?php
// securimage_show.php — generated the CAPTCHA image
// This file was included with the Securimage distribution
require_once 'securimage/securimage.php';
$img = new Securimage();
$img->show(); // Outputs image and stores code in session
2. AJAX Validation Endpoint
<?php
// validate_captcha.php — checked the user's input via AJAX
session_start();
require_once 'securimage/securimage.php';
$securimage = new Securimage();
$valid = $securimage->check($_POST['captcha_code'] ?? '');
header('Content-Type: application/json');
echo json_encode(['valid' => $valid]);
3. Frontend AJAX Call
// Client-side validation before form submission
document.getElementById('myForm').addEventListener('submit', function(e) {
e.preventDefault();
const code = document.getElementById('captchaInput').value;
fetch('/validate_captcha.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'captcha_code=' + encodeURIComponent(code),
})
.then(r => r.json())
.then(data => {
if (data.valid) {
e.target.submit(); // Proceed with real submission
} else {
alert('Incorrect CAPTCHA. Please try again.');
// Reload the CAPTCHA image
document.getElementById('captchaImg').src =
'/securimage/securimage_show.php?' + Date.now();
}
});
});
The Security Problem
This AJAX pattern had a fundamental flaw: developers often built validation endpoints that checked the user's input without clearing the session code on failure. This let bots brute-force the CAPTCHA code via the AJAX endpoint — trying codes rapidly until one matched — then submitting the form with the correct value. Additionally, many implementations validated the code via AJAX but forgot to verify it again on final form submission, creating a bypass where bots could skip the AJAX check entirely.
Migration Checklist
- Search your codebase for
Securimage,securimage_show, andsecurimage.phpreferences. - Remove the Securimage library directory and Composer dependency (if installed via Composer).
- Remove the AJAX validation endpoint — modern CAPTCHAs verify as part of the form submission, not separately.
- Add Turnstile or ALTCHA to your form HTML and update your server-side handler to verify the token.
- Remove session-based CAPTCHA code —
$_SESSION['securimage_code']and related cleanup. - Test with AJAX submissions to confirm the token is included in your FormData or fetch payload.
Modern Replacements with AJAX Support
Every modern CAPTCHA service works natively with AJAX forms because they use token-based verification instead of session-based codes.
Cloudflare Turnstile (Recommended)
Turnstile generates a token client-side that you include in your AJAX payload. No separate validation endpoint needed.
// AJAX form submission with Turnstile
document.getElementById('myForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
// Turnstile auto-injects cf-turnstile-response into the form
const response = await fetch('/submit.php', {
method: 'POST',
body: formData,
});
const result = await response.json();
if (result.success) {
// Handle success
} else {
// Reset Turnstile for retry
turnstile.reset();
}
});
<?php
// submit.php — verify Turnstile token server-side
// Full implementation: /documentation/php-turnstile
$ch = curl_init('https://challenges.cloudflare.com/turnstile/v0/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'secret' => getenv('TURNSTILE_SECRET') ?: '',
'response' => $_POST['cf-turnstile-response'] ?? '',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
]);
$data = json_decode(curl_exec($ch) ?: '{}', true);
curl_close($ch);
header('Content-Type: application/json');
echo json_encode(['success' => $data['success'] ?? false]);
See our Turnstile PHP integration guide for complete setup including test keys.
ALTCHA (Self-Hosted, No External API)
If you chose Securimage specifically to avoid third-party services, ALTCHA is the modern equivalent. It runs entirely on your server with AJAX-native proof-of-work challenges.
<!-- ALTCHA widget handles AJAX automatically -->
<script src="https://cdn.jsdelivr.net/npm/altcha/dist/altcha.min.js" defer></script>
<form id="myForm">
<altcha-widget challengeurl="/altcha-challenge.php"></altcha-widget>
<!-- Widget submits solved proof-of-work in 'altcha' field -->
</form>
See our PHP CAPTCHA code examples for the full ALTCHA server-side implementation.
Verdict
Securimage's AJAX validation was a reasonable approach for its time, but session-based CAPTCHA verification has been superseded by token-based systems that are simpler, more secure, and natively AJAX-compatible. Replace Securimage with Cloudflare Turnstile for the easiest migration, or ALTCHA if you need self-hosted operation.