fix(captcha): 防止空 captcha_id 请求极验 403

custom 类型 schema 现按 array 深度净化保存;前台启用但凭据未齐时跳过渲染,Geetest 客户端也校验 captchaId。
This commit is contained in:
2026-07-12 14:49:24 +08:00
parent 4fe4d79da0
commit b7dc3d170d
2 changed files with 97 additions and 15 deletions
@@ -365,7 +365,24 @@ class AKSRT_Admin_Schema_Provider {
case 'float':
return (float) $value;
case 'array':
return is_array($value) ? $value : [];
// 深层字符串净化,保留嵌套服务商配置(如 geetest.captcha_id)
if (!is_array($value)) {
return [];
}
$out = [];
foreach ($value as $k => $v) {
$key = is_string($k) ? sanitize_key($k) : $k;
if (is_array($v)) {
$out[$key] = $this->sanitize_value($v, 'array');
} elseif (is_bool($v)) {
$out[$key] = $v;
} elseif (is_int($v) || is_float($v)) {
$out[$key] = $v;
} else {
$out[$key] = sanitize_text_field((string) $v);
}
}
return $out;
default:
return sanitize_text_field((string) $value);
}
@@ -383,6 +400,8 @@ class AKSRT_Admin_Schema_Provider {
'radio' => 'string',
'checkbox' => 'array',
'repeater' => 'array',
// 自定义面板(验证码服务商等)按对象存储
'custom' => 'array',
'color' => 'string',
'icon' => 'string',
'media' => 'url',
+77 -14
View File
@@ -21,6 +21,35 @@ function aksrt_get_captcha_config() {
}
endif;
if (!function_exists('aksrt_captcha_provider_is_configured')):
/**
* 当前服务商必填凭据是否齐全(避免空 captcha_id 打到极验 403)。
*
* @param string $provider
* @param array $pcfg
* @return bool
*/
function aksrt_captcha_provider_is_configured($provider, $pcfg = array()) {
$provider = sanitize_key((string) $provider);
if ($provider === '') {
return false;
}
if (!is_array($pcfg)) {
$pcfg = array();
}
$required = aksrt_captcha_required_fields($provider);
if (empty($required)) {
return false;
}
foreach ($required as $field) {
if (!isset($pcfg[$field]) || trim((string) $pcfg[$field]) === '') {
return false;
}
}
return true;
}
endif;
if (!function_exists('aksrt_is_captcha_enabled_for')):
/**
* 检查指定场景是否启用人机验证
@@ -32,8 +61,14 @@ function aksrt_is_captcha_enabled_for($scene) {
$cfg = aksrt_get_captcha_config();
if (empty($cfg['enabled'])) return false;
if (empty($cfg['active_provider'])) return false;
$provider = (string) $cfg['active_provider'];
$pcfg = isset($cfg[$provider]) && is_array($cfg[$provider]) ? $cfg[$provider] : array();
// 启用但凭据未配齐时,前台不渲染/不拦截,避免空 captcha_id 请求第三方 403
if (!aksrt_captcha_provider_is_configured($provider, $pcfg)) {
return false;
}
$disabled = isset($cfg['disabled_scenarios']) ? $cfg['disabled_scenarios'] : array();
return !in_array($scene, $disabled);
return !in_array($scene, $disabled, true);
}
endif;
@@ -516,6 +551,18 @@ function aksrt_render_captcha($scene = 'comment') {
if (!aksrt_is_captcha_enabled_for($scene)) return;
$provider = $cfg['active_provider'];
$pcfg = isset($cfg[$provider]) && is_array($cfg[$provider]) ? $cfg[$provider] : array();
if (!aksrt_captcha_provider_is_configured($provider, $pcfg)) {
if (current_user_can('manage_options')) {
echo '<div class="aksrt-captcha-misconfig" style="font-size:12px;color:#d03050;margin:8px 0;">';
echo esc_html(sprintf(
/* translators: %s: captcha provider key */
__('人机验证服务商「%s」已启用但凭据未配齐,前台已跳过渲染。请到「功能&权限 → 登录权限」补全配置。', 'aksrt'),
$provider
));
echo '</div>';
}
return;
}
$uid = 'aksrt-captcha-' . $scene . '-' . wp_unique_id();
$vid = 'captcha_verified_' . $scene . '_' . wp_unique_id();
@@ -526,20 +573,20 @@ function aksrt_render_captcha($scene = 'comment') {
'scene' => $scene,
'uid' => $uid,
'vid' => $vid,
'tc' => array('app_id' => isset($pcfg['app_id']) ? $pcfg['app_id'] : ''),
'tc' => array('app_id' => isset($pcfg['app_id']) ? (string) $pcfg['app_id'] : ''),
'cf' => array(
'site_key' => isset($pcfg['site_key']) ? $pcfg['site_key'] : '',
'theme' => isset($pcfg['theme']) ? $pcfg['theme'] : 'auto',
'size' => isset($pcfg['size']) ? $pcfg['size'] : 'normal'
'site_key' => isset($pcfg['site_key']) ? (string) $pcfg['site_key'] : '',
'theme' => isset($pcfg['theme']) ? (string) $pcfg['theme'] : 'auto',
'size' => isset($pcfg['size']) ? (string) $pcfg['size'] : 'normal'
),
'gt' => array(
'captcha_id' => isset($pcfg['captcha_id']) ? $pcfg['captcha_id'] : '',
'product' => isset($pcfg['product']) ? $pcfg['product'] : 'popup'
'captcha_id' => isset($pcfg['captcha_id']) ? trim((string) $pcfg['captcha_id']) : '',
'product' => isset($pcfg['product']) && $pcfg['product'] !== '' ? (string) $pcfg['product'] : 'bind',
),
'al' => array(
'region' => isset($pcfg['region']) ? $pcfg['region'] : 'cn',
'prefix' => isset($pcfg['prefix']) ? $pcfg['prefix'] : '',
'scene_id' => isset($pcfg['scene_id']) ? $pcfg['scene_id'] : ''
'region' => isset($pcfg['region']) ? (string) $pcfg['region'] : 'cn',
'prefix' => isset($pcfg['prefix']) ? (string) $pcfg['prefix'] : '',
'scene_id' => isset($pcfg['scene_id']) ? (string) $pcfg['scene_id'] : ''
),
);
?>
@@ -921,10 +968,17 @@ function init(){
// ============ 极验 GeeTest v4 ============
function loadGeetest(){
showLoading();
var product=C.gt.product||'bind';
var captchaId=(C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : '';
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
if(!captchaId){
clearLoading();
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 登录权限」填写并保存','#e74c3c');
triggerCaptcha=function(){ showTip('极验 Captcha ID 未配置','#e74c3c'); };
return;
}
function render(){
clearLoading();
initGeetest4({captchaId:C.gt.captcha_id, product:product, hideSuccess:true}, function(capIns){
initGeetest4({captchaId:captchaId, product:product, hideSuccess:true}, function(capIns){
window['__gt_'+C.uid]=capIns;
function onSuccess(){
var r=capIns.getValidate();
@@ -1162,10 +1216,19 @@ function init(){
loadGeetest=function(){
showLoading();
var product=C.gt.product||'bind';
var captchaId=(C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : '';
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
if(!captchaId){
clearLoading();
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 登录权限」填写并保存', '#e74c3c');
triggerCaptcha=function(){
showTip('极验 Captcha ID 未配置', '#e74c3c');
};
return;
}
function render(){
clearLoading();
initGeetest4({captchaId:C.gt.captcha_id, product:product, hideSuccess:true}, function(capIns){
initGeetest4({captchaId:captchaId, product:product, hideSuccess:true}, function(capIns){
window['__gt_'+C.uid]=capIns;
function onSuccess(){
var result=capIns.getValidate();