WebOrbiton
v1.0.0.0

StocketBase

162 lines · 6.7 KB
  1. <?php
  2. declare(strict_types=1);
  3. final class AntiBot
  4. {
  5. private const CHARACTERS = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
  6. private const LENGTH = 6;
  7. private const MAX_AGE = 1800;
  8. private const MIN_FORM_AGE = 2;
  9. public static function boot(string $scope): void
  10. {
  11. self::validateScope($scope);
  12. $key = self::key($scope);
  13. if (!isset($_SESSION[$key]) || !is_array($_SESSION[$key])) {
  14. self::rotate($scope);
  15. }
  16. }
  17. public static function refresh(string $scope): void
  18. {
  19. self::validateScope($scope);
  20. self::rotate($scope);
  21. }
  22. public static function field(string $scope): string
  23. {
  24. self::boot($scope);
  25. $started = self::key($scope) . '_started';
  26. return '<input type="hidden" name="antibot_started" value="' . (int) $_SESSION[$started] . '">'
  27. . '<input type="text" name="website" value="" tabindex="-1" autocomplete="off" aria-hidden="true" class="antibot-trap">';
  28. }
  29. public static function verify(string $scope, ?string $answer, ?string $started, ?string $honeypot): bool
  30. {
  31. self::validateScope($scope);
  32. $key = self::key($scope);
  33. $challenge = $_SESSION[$key] ?? null;
  34. $now = time();
  35. $valid = is_array($challenge)
  36. && is_string($answer)
  37. && is_string($started)
  38. && is_string($honeypot)
  39. && $honeypot === ''
  40. && isset($challenge['token'], $challenge['started'])
  41. && hash_equals((string) $challenge['token'], strtoupper(trim($answer)))
  42. && ctype_digit($started)
  43. && (int) $started === (int) $challenge['started']
  44. && $now - (int) $challenge['started'] >= self::MIN_FORM_AGE
  45. && $now - (int) $challenge['created'] <= self::MAX_AGE;
  46. self::rotate($scope);
  47. return $valid;
  48. }
  49. public static function image(string $scope): string
  50. {
  51. self::boot($scope);
  52. $token = (string) $_SESSION[self::key($scope) . '_token'];
  53. $width = 200;
  54. $height = 70;
  55. $length = strlen($token);
  56. $svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' . $width . '" height="' . $height . '" viewBox="0 0 ' . $width . ' ' . $height . '" role="img" aria-label="Security verification code">';
  57. $svg .= '<defs>';
  58. $svg .= '<filter id="antibot-noise">';
  59. $svg .= '<feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/>';
  60. $svg .= '<feColorMatrix type="saturate" values="0"/>';
  61. $svg .= '<feBlend in="SourceGraphic" mode="multiply" result="blend"/>';
  62. $svg .= '<feComposite in="blend" in2="SourceGraphic" operator="in"/>';
  63. $svg .= '</filter>';
  64. $svg .= '</defs>';
  65. $backgrounds = ['#f0f4ff', '#fff4f0', '#f0fff4', '#fdfdf0', '#f4f0ff'];
  66. $svg .= '<rect width="' . $width . '" height="' . $height . '" fill="' . $backgrounds[array_rand($backgrounds)] . '" rx="6"/>';
  67. $svg .= '<filter id="antibot-wave">';
  68. $svg .= '<feTurbulence type="turbulence" baseFrequency="0.02 0.05" numOctaves="2" result="turb"/>';
  69. $svg .= '<feDisplacementMap in="SourceGraphic" in2="turb" scale="4" xChannelSelector="R" yChannelSelector="G"/>';
  70. $svg .= '</filter>';
  71. $lineColors = ['#c0c8e0', '#e0c0c0', '#c0e0c0', '#d0d0c0', '#d0c0e0'];
  72. for ($i = 0; $i < 6; $i++) {
  73. $x1 = random_int(0, $width);
  74. $y1 = random_int(0, $height);
  75. $x2 = random_int(0, $width);
  76. $y2 = random_int(0, $height);
  77. $color = $lineColors[array_rand($lineColors)];
  78. $strokeWidth = round(random_int(10, 20) / 10, 1);
  79. $svg .= '<line x1="' . $x1 . '" y1="' . $y1 . '" x2="' . $x2 . '" y2="' . $y2 . '" stroke="' . $color . '" stroke-width="' . $strokeWidth . '" opacity="0.7"/>';
  80. }
  81. $dotColors = ['#9090b0', '#b09090', '#90b090'];
  82. for ($i = 0; $i < 40; $i++) {
  83. $cx = random_int(0, $width);
  84. $cy = random_int(0, $height);
  85. $radius = random_int(1, 3);
  86. $svg .= '<circle cx="' . $cx . '" cy="' . $cy . '" r="' . $radius . '" fill="' . $dotColors[array_rand($dotColors)] . '" opacity="0.5"/>';
  87. }
  88. $characterColors = ['#1a3a8f', '#8f1a1a', '#1a6e1a', '#6e1a6e', '#1a5a6e', '#8f4a00', '#2a2a8f', '#6e1a3a'];
  89. $slotWidth = ($width - 20) / $length;
  90. for ($i = 0; $i < $length; $i++) {
  91. $char = htmlspecialchars($token[$i], ENT_XML1);
  92. $x = 10 + $slotWidth * $i + $slotWidth / 2;
  93. $y = random_int(38, 50);
  94. $rotate = random_int(-18, 18);
  95. $scaleX = round(random_int(85, 115) / 100, 2);
  96. $scaleY = round(random_int(90, 110) / 100, 2);
  97. $fontSize = random_int(26, 34);
  98. $color = $characterColors[array_rand($characterColors)];
  99. $svg .= '<text'
  100. . ' x="' . round($x, 1) . '"'
  101. . ' y="' . $y . '"'
  102. . ' font-size="' . $fontSize . '"'
  103. . ' font-family="Georgia, serif"'
  104. . ' font-weight="bold"'
  105. . ' fill="' . $color . '"'
  106. . ' text-anchor="middle"'
  107. . ' transform="rotate(' . $rotate . ' ' . round($x, 1) . ' ' . $y . ') scale(' . $scaleX . ' ' . $scaleY . ')"'
  108. . ' filter="url(#antibot-wave)"'
  109. . '>' . $char . '</text>';
  110. }
  111. $svg .= '<rect width="' . $width . '" height="' . $height . '" fill="url(#antibot-noise)" opacity="0.08" rx="6"/>';
  112. $svg .= '</svg>';
  113. return '<img src="data:image/svg+xml;base64,' . base64_encode($svg) . '" width="' . $width . '" height="' . $height . '" alt="Security verification code" draggable="false">';
  114. }
  115. private static function rotate(string $scope): void
  116. {
  117. $key = self::key($scope);
  118. $started = time();
  119. $token = '';
  120. for ($i = 0; $i < self::LENGTH; $i++) {
  121. $token .= self::CHARACTERS[random_int(0, strlen(self::CHARACTERS) - 1)];
  122. }
  123. $_SESSION[$key] = [
  124. 'token' => $token,
  125. 'started' => $started,
  126. 'created' => $started,
  127. ];
  128. $_SESSION[$key . '_token'] = $token;
  129. $_SESSION[$key . '_started'] = $started;
  130. }
  131. private static function key(string $scope): string
  132. {
  133. self::validateScope($scope);
  134. return 'antibot_' . $scope;
  135. }
  136. private static function validateScope(string $scope): void
  137. {
  138. if (!in_array($scope, ['user', 'team', 'contact'], true)) {
  139. throw new InvalidArgumentException('Invalid antibot scope.');
  140. }
  141. }
  142. }
  143. ​