Add a simple CAPTCHA to the sign up form

Message ID 20190906192804.64231-1-lfleischer@archlinux.org
State New
Headers show
Series
  • Add a simple CAPTCHA to the sign up form
Related show

Commit Message

Lukas Fleischer Sept. 6, 2019, 7:28 p.m. UTC
Add a CAPTCHA to protect against automated account creation. The CAPTCHA
changes whenever three new accounts are registered.

Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
---
This is a first attempt to stop the recent wave of spammers. Other
counter-measures will be implemented if it is not effective.

 web/html/register.php              | 14 +++++-
 web/lib/acctfuncs.inc.php          | 74 +++++++++++++++++++++++++++++-
 web/template/account_edit_form.php | 11 +++++
 3 files changed, 95 insertions(+), 4 deletions(-)

Comments

Eli Schwartz Sept. 17, 2019, 2:51 p.m. UTC | #1
On 9/6/19 3:28 PM, Lukas Fleischer wrote:
> Add a CAPTCHA to protect against automated account creation. The CAPTCHA
> changes whenever three new accounts are registered.
> 
> Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
> ---
> This is a first attempt to stop the recent wave of spammers. Other
> counter-measures will be implemented if it is not effective.

So far seems like it may be helping.

>  web/html/register.php              | 14 +++++-
>  web/lib/acctfuncs.inc.php          | 74 +++++++++++++++++++++++++++++-
>  web/template/account_edit_form.php | 11 +++++
>  3 files changed, 95 insertions(+), 4 deletions(-)


> + * Return the CAPTCHA challenge for a given salt.
> + *
> + * @param string $salt The salt to be used for the CAPTCHA computation.
> + *
> + * @return string The challenge as a string.
> + */
> +function get_captcha_challenge($salt) {
> +	$token = substr(md5($salt), 0, 3);
> +	return "pacman -V|sed -r 's#[0-9]+#" . $token . "#g'|md5sum|cut -c1-6";
> +}

But I think we need to mention LC_ALL=C here. See e.g.
https://bugs.archlinux.org/task/63808

Patch

diff --git a/web/html/register.php b/web/html/register.php
index 368999a..a426482 100644
--- a/web/html/register.php
+++ b/web/html/register.php
@@ -36,7 +36,12 @@  if (in_request("Action") == "NewAccount") {
 		0,
 		in_request("CN"),
 		in_request("UN"),
-		in_request("ON"));
+		in_request("ON"),
+		0,
+		"",
+		in_request("captcha_salt"),
+		in_request("captcha"),
+	);
 
 	print $message;
 
@@ -59,7 +64,12 @@  if (in_request("Action") == "NewAccount") {
 			0,
 			in_request("CN"),
 			in_request("UN"),
-			in_request("ON"));
+			in_request("ON"),
+			0,
+			"",
+			in_request("captcha_salt"),
+			in_request("captcha")
+		);
 	}
 } else {
 	print '<p>' . __("Use this form to create an account.") . '</p>';
diff --git a/web/lib/acctfuncs.inc.php b/web/lib/acctfuncs.inc.php
index dc44484..b0513c6 100644
--- a/web/lib/acctfuncs.inc.php
+++ b/web/lib/acctfuncs.inc.php
@@ -62,17 +62,25 @@  function html_format_pgp_fingerprint($fingerprint) {
  * @param string $ON Whether to notify of ownership changes
  * @param string $UID The user ID of the displayed user
  * @param string $N The username as present in the database
+ * @param string $captcha_salt The salt used for the CAPTCHA.
+ * @param string $captcha The CAPTCHA answer.
  *
  * @return void
  */
 function display_account_form($A,$U="",$T="",$S="",$E="",$H="",$P="",$C="",$R="",
-		$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="") {
+		$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="",$captcha_salt="",$captcha="") {
 	global $SUPPORTED_LANGS;
 
 	if ($TZ == "") {
 		$TZ = config_get("options", "default_timezone");
 	}
 
+	if ($captcha_salt != get_captcha_salt()) {
+		$captcha_salt = get_captcha_salt();
+		$captcha = "";
+	}
+	$captcha_challenge = get_captcha_challenge($captcha_salt);
+
 	include("account_edit_form.php");
 	return;
 }
@@ -103,11 +111,13 @@  function display_account_form($A,$U="",$T="",$S="",$E="",$H="",$P="",$C="",$R=""
  * @param string $ON Whether to notify of ownership changes
  * @param string $UID The user ID of the modified account
  * @param string $N The username as present in the database
+ * @param string $captcha_salt The salt used for the CAPTCHA.
+ * @param string $captcha The CAPTCHA answer.
  *
  * @return array Boolean indicating success and message to be printed
  */
 function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$H="",$P="",$C="",
-		$R="",$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="") {
+		$R="",$L="",$TZ="",$HP="",$I="",$K="",$PK="",$J="",$CN="",$UN="",$ON="",$UID=0,$N="",$captcha_salt="",$captcha="") {
 	global $SUPPORTED_LANGS;
 
 	$error = '';
@@ -269,6 +279,18 @@  function process_account_form($TYPE,$A,$U="",$T="",$S="",$E="",$H="",$P="",$C=""
 		}
 	}
 
+	if (!$error && $TYPE == "new" && empty($captcha)) {
+		$error = __("The CAPTCHA is missing.");
+	}
+
+	if (!$error && $TYPE == "new" && $captcha_salt != get_captcha_salt()) {
+		$error = __("This CAPTCHA has expired. Please try again.");
+	}
+
+	if (!$error && $TYPE == "new" && $captcha != get_captcha_answer($captcha_salt)) {
+		$error = __("The entered CAPTCHA answer is invalid.");
+	}
+
 	if ($error) {
 		$message = "<ul class='errorlist'><li>".$error."</li></ul>\n";
 		return array(false, $message);
@@ -1445,3 +1467,51 @@  function account_comments_count($uid) {
 	$result = $dbh->query($q);
 	return $result->fetchColumn();
 }
+
+/*
+ * Compute the CAPTCHA salt. The salt changes based on the number of registered
+ * users. This ensures that new users always use a different salt.
+ *
+ * @return string The current salt.
+ */
+function get_captcha_salt() {
+	$dbh = DB::connect();
+	$q = "SELECT count(*) FROM Users";
+	$result = $dbh->query($q);
+	$user_count = $result->fetchColumn();
+	return 'aurweb-' . floor($user_count / 3);
+}
+
+/*
+ * Return the CAPTCHA challenge for a given salt.
+ *
+ * @param string $salt The salt to be used for the CAPTCHA computation.
+ *
+ * @return string The challenge as a string.
+ */
+function get_captcha_challenge($salt) {
+	$token = substr(md5($salt), 0, 3);
+	return "pacman -V|sed -r 's#[0-9]+#" . $token . "#g'|md5sum|cut -c1-6";
+}
+
+/*
+ * Compute CAPTCHA answer for a given salt.
+ *
+ * @param string $salt The salt to be used for the CAPTCHA computation.
+ *
+ * @return string The correct answer as a string.
+ */
+function get_captcha_answer($salt) {
+	$token = substr(md5($salt), 0, 3);
+	$text = <<<EOD
+
+ .--.                  Pacman v$token.$token.$token - libalpm v$token.$token.$token
+/ _.-' .-.  .-.  .-.   Copyright (C) $token-$token Pacman Development Team
+\  '-. '-'  '-'  '-'   Copyright (C) $token-$token Judd Vinet
+ '--'
+                       This program may be freely redistributed under
+                       the terms of the GNU General Public License.
+
+EOD;
+	return substr(md5($text . "\n"), 0, 6);
+}
diff --git a/web/template/account_edit_form.php b/web/template/account_edit_form.php
index 38d5274..5e84aa7 100644
--- a/web/template/account_edit_form.php
+++ b/web/template/account_edit_form.php
@@ -174,6 +174,17 @@ 
 		</p>
 	</fieldset>
 
+	<?php if ($A != "UpdateAccount"): ?>
+	<fieldset>
+		<legend><?= __("To protect the AUR against automated account creation, we kindly ask you to provide the output of the following command:") ?> <code><?= htmlspecialchars($captcha_challenge) ?></code></legend>
+		<p>
+			<label for="id_captcha"><?= __("Answer") ?>:</label>
+			<input type="text" size="30" maxlength="6" name="captcha" id="id_captcha" value="<?= htmlspecialchars($captcha, ENT_QUOTES) ?>" /> (<?= __("required") ?>)
+			<input type="hidden" name="captcha_salt" value="<?= htmlspecialchars($captcha_salt) ?>" />
+		</p>
+	</fieldset>
+	<?php endif; ?>
+
 	<fieldset>
 		<p>
 			<label></label>