Skip to content

Fix antispam honeypot precedence bug (and/or vs && ||)#218

Open
HomesONE wants to merge 1 commit into
gmazoyer:mainfrom
HomesONE:fix/antispam-honeypot-precedence
Open

Fix antispam honeypot precedence bug (and/or vs && ||)#218
HomesONE wants to merge 1 commit into
gmazoyer:mainfrom
HomesONE:fix/antispam-honeypot-precedence

Conversation

@HomesONE

Copy link
Copy Markdown

Problem

In AntiSpam::check_user() (includes/antispam.php:126-127) two flag assignments use PHP's low-precedence and/or operators. Those bind less tightly than =, so:

$in_period = !empty($result) and time() < $result['no_query_period'];

evaluates as

($in_period = !empty($result)) and (time() < $result['no_query_period']);

— the right-hand expression is computed but discarded. $in_period ends up being just !empty($result), so the no_query_period timestamp check is silently dropped and stale rows keep returning "still in period" past their expiry (until clean_no_query_periods() evicts them on the next run).

The second line:

$obvious_spam = !isset($_POST['dontlook']) or !empty($_POST['dontlook']);

becomes $obvious_spam = !isset($_POST['dontlook']). The actual honeypot test (!empty($_POST['dontlook'])) is also discarded. Practical effect:

  • A bot that fills the hidden dontlook field — the thing the honeypot is supposed to catch — gets $obvious_spam = false (because isset() is true → !isset() is false), so no penalty is applied.
  • A custom client that omits the field entirely (most legitimate clients won't, since the bundled form always renders it) triggers $obvious_spam = true and gets the degree = 512 mega-ban — an unintended outcome.

Fix

Use &&/|| (which bind tighter than =) and parenthesize for clarity:

\$in_period = (!empty(\$result) && time() < \$result['no_query_period']);
\$obvious_spam = (isset(\$_POST['dontlook']) && !empty(\$_POST['dontlook']));

The \$obvious_spam condition is also inverted to express the actually-intended logic: "the honeypot field is present AND non-empty".

Impact

  • No change for legitimate users: the bundled form always submits an empty dontlook field, so \$obvious_spam stays false (same as before, just via correct logic).
  • Bots that fill the honeypot now actually receive the long ban.
  • Returning users no longer get spurious "in period" rejections from stale rows whose timestamp has already elapsed.

Repro

\$ php -r '\$result = [\"no_query_period\" => 0]; \$in_period = !empty(\$result) and time() < \$result[\"no_query_period\"]; var_dump(\$in_period);'
bool(true)        // wrong: timestamp is 0, \"in period\" should be false

\$ php -r '\$result = [\"no_query_period\" => 0]; \$in_period = (!empty(\$result) && time() < \$result[\"no_query_period\"]); var_dump(\$in_period);'
bool(false)       // correct

Found via static review of v2.3.1. (Replaces #217, which I closed to recreate from a clean fork.)

In `AntiSpam::check_user()` the two flag assignments used PHP's
low-precedence `and`/`or` operators, which bind *less* tightly
than `=` — so what reads as

  $in_period = !empty($result) and time() < $result['no_query_period'];

actually evaluates as

  ($in_period = !empty($result)) and (time() < $result['no_query_period']);

The right-hand expression is computed but discarded, and
`$in_period` ends up being just `!empty($result)` — the
`no_query_period` timestamp check is silently dropped, so stale
rows keep returning "still in period" past their expiry until
`clean_no_query_periods()` evicts them on the next run.

  $obvious_spam = !isset($_POST['dontlook']) or !empty($_POST['dontlook']);

likewise becomes `$obvious_spam = !isset($_POST['dontlook'])`,
so the honeypot fires when the hidden field is *missing* (false
for the bundled form) and never when a bot fills it — the
intended `!empty($_POST['dontlook'])` test is discarded.

Replace with `&&`/`||` and parenthesize so the intended semantics
hold:
  - `$in_period`: a row exists AND its no_query_period has not
    elapsed.
  - `$obvious_spam`: the honeypot field is present AND non-empty.

No behavioural change for legitimate users (bundled form always
submits an empty dontlook field). Bots that fill the honeypot
now actually receive the long ban they were meant to receive.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant