替换 Discuz! 6 的默认验证码

PTB 今年已经是第 11 年了,代码也是 10 年前的代码了。
这次又遇到什么问题了呢?就是我们的验证码机制被攻破了。
而且其实挺遗憾的,因为 PTB 最初的安全设计是严进宽出,即你注册需要有相当繁琐的步骤和漫长的验证期,之后几乎没有什么限制。

然后很尴尬,我们被撞库了。

就是前一分钟我们还在微信群里面谈笑风生一分钟之后刷出来发现对面那个人的 ID 在论坛灌了 1000 多个台湾找小姐这样。

然后我就按照之前双子的思路打开了验证码。

但是很尴尬, Discuz 6 有一个很严重的逻辑漏洞。这其实也不是逻辑漏洞,是当年大家对 ajax 的理解都不到位而已。

按照惯例,我们来读代码。

DISCUZ_ROOT./ajax.php:64


...
} elseif($action == 'checkseccode') {

	if($seclevel) {
		$tmp = $seccode;
	} else {
		$key = $seccodedata['type'] != 3 ? '' : $_DCACHE['settings']['authkey'].date('Ymd');
		list($tmp, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secc'], 'DECODE', $key));
		if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
			showmessage('submit_seccode_invalid');
		}
	}
	seccodeconvert($tmp);
	strtoupper($seccodeverify) != $tmp && showmessage('submit_seccode_invalid');
...

以及

DISCUZ_ROOT./include/global.func.php:888


function submitcheck($var, $allowget = 0, $seccodecheck = 0, $secqaacheck = 0) {
	if(empty($GLOBALS[$var])) {
		return FALSE;
	} else {
		global $_SERVER, $seclevel, $seccode, $seccodedata, $seccodeverify, $secanswer, $_DCACHE, $_DCOOKIE, $timestamp, $discuz_uid;
		if($allowget || ($_SERVER['REQUEST_METHOD'] == 'POST' && $GLOBALS['formhash'] == formhash() && (empty($_SERVER['HTTP_REFERER']) ||
			preg_replace("/https?:\/\/([^\:\/]+).*/i", "\\1", $_SERVER['HTTP_REFERER']) == preg_replace("/([^\:]+).*/", "\\1", $_SERVER['HTTP_HOST'])))) {
        		if($seccodecheck) {
        			if(!$seclevel) {
        				$key = $seccodedata['type'] != 3 ? '' : $_DCACHE['settings']['authkey'].date('Ymd');
        				list($seccode, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secc'], 'DECODE', $key));
        				if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
        					showmessage('submit_seccode_invalid');
        				}
        				dsetcookie('secc', '', -86400 * 365);
        			} else {
        				$tmp = substr($seccode, 0, 1);
        			}
        			seccodeconvert($seccode);
        			if(strtoupper($seccodeverify) != $seccode) {
        				showmessage('submit_seccode_invalid');
        			}
				$seclevel && $seccode = random(6, 1) + $tmp * 1000000;
        		}
			if($secqaacheck) {
        			if(!$seclevel) {
        				list($seccode, $expiration, $seccodeuid) = explode("\t", authcode($_DCOOKIE['secq'], 'DECODE'));
        				if($seccodeuid != $discuz_uid || $timestamp - $expiration > 600) {
        					showmessage('submit_secqaa_invalid');
        				}
        				dsetcookie('secq', '', -86400 * 365);
        			}
        			require_once DISCUZ_ROOT.'./forumdata/cache/cache_secqaa.php';
        			if(md5($secanswer) != $_DCACHE['secqaa'][substr($seccode, 0, 1)]['answer']) {
        			        showmessage('submit_secqaa_invalid');
        			}
				$seclevel && $seccode = random(1, 1) * 1000000 + substr($seccode, -6);
        		}
			return TRUE;
		} else {
			showmessage('submit_invalid');
		}
	}
}

不难看出,其实对于验证码的过期,后台的判断只是基于时间(600秒)和是否是属于这个用户。
这其实是有很大的问题的,因为没有重试次数限制,对于不复杂的非中文验证码,一秒钟发出几千个请求cover掉所有数字和大部分字母组合是很轻松的事情。
因此干脆就把它从代码里面摘掉。

主要修改的是下面几个文件。因为现在就是这么做的,因此具体的细节就不展开说了。

include/javascript/post_editor.js
摘掉里面对原有验证码的前端校验

template/seccheck.htm
摘掉原有验证码的展示代码,同位置增加极验的前端代码

ajax.php
屏蔽掉前端校验接口
global.func.php
common.inc.php
摘掉原有验证码后端校验的内容部分,保留 Cookies 校验部分
增加极验后端校验代码

总结一下,其实 discuz 6 在代码设计上真的是完美无缺的。这么老的代码改起来也没有什么不适感(最起码比在公司里吃的那些 s**t 要好得多太多了)。基本就是搜“submit_seccode_invalid”就可以解决这次的问题。
总之双站都是靠极验老版本去 cover 机器人了,毕竟防御价值没那么高,就不去针对 geetest v3 做适配升级了。
屏蔽掉机器人就已经解决绝大部分问题了。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据