bot_protection.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <?php
  2. class bot_protection{
  3. public function __construct($frontend, $get, $filters, $page, $output){
  4. if(config::RATELIMIT_BURST !== 0){
  5. // ratelimit requests per IP
  6. if(config::RATELIMIT_IP_SOURCE === false){
  7. $source = "r" . $_SERVER["REMOTE_ADDR"];
  8. }else{
  9. $source = "r" . $_SERVER[explode(";", strtoupper(str_replace("-", "_", config::IP_SOURCE)))[0]];
  10. }
  11. // counter
  12. // resets on every req
  13. $counter = apcu_fetch($source);
  14. $counter = (int)$counter + 1;
  15. if(
  16. (
  17. CONFIG::RATELIMIT_PUNISH !== 0 &&
  18. $counter > config::RATELIMIT_BURST + 1
  19. ) &&
  20. (
  21. (
  22. $output === false &&
  23. CONFIG::RATELIMIT_PUNISH_API === true
  24. ) ||
  25. $output === true
  26. )
  27. ){
  28. $block_time = config::RATELIMIT_TIME + (($counter - 1) * config::RATELIMIT_PUNISH);
  29. }else{
  30. $block_time = config::RATELIMIT_TIME;
  31. }
  32. apcu_store($source, $counter, $block_time);
  33. if($counter > config::RATELIMIT_BURST){
  34. if($output === false){
  35. http_response_code(429);
  36. echo json_encode([
  37. "status" => "You love 4get too much. Try again in " . $this->format_time($block_time) . "."
  38. ]);
  39. die();
  40. }
  41. $payload = [
  42. "timetaken" => microtime(true),
  43. "class" => "",
  44. "right-left" => "",
  45. "right-right" => "",
  46. "left" =>
  47. '<div class="infobox">' .
  48. '<h1>You\'re damaging my calm</h1>' .
  49. 'You love 4get too much. Try again in <b>' . $this->format_time($block_time) . '</b>.' .
  50. '</div>'
  51. ];
  52. $frontend->loadheader(
  53. $get,
  54. $filters,
  55. $page,
  56. false
  57. );
  58. echo $frontend->load("search.html", $payload);
  59. die();
  60. }
  61. }
  62. // check if we want captcha
  63. if(config::BOT_PROTECTION !== 1){
  64. if($output === true){
  65. $frontend->loadheader(
  66. $get,
  67. $filters,
  68. $page
  69. );
  70. }
  71. return;
  72. }
  73. /*
  74. Validate cookie, if it exists
  75. */
  76. if(isset($_COOKIE["pass"])){
  77. if(
  78. // check if key is not malformed
  79. preg_match(
  80. '/^k[0-9]+\.[A-Za-z0-9_]{20}$/',
  81. $_COOKIE["pass"]
  82. ) &&
  83. // does key exist
  84. apcu_exists($_COOKIE["pass"])
  85. ){
  86. // exists, increment counter
  87. $inc = apcu_inc($_COOKIE["pass"]);
  88. // we start counting from 1
  89. // when it has been incremented to 102, it has reached
  90. // 100 reqs
  91. if($inc >= config::MAX_SEARCHES + 2){
  92. // reached limit, delete and give captcha
  93. apcu_delete($_COOKIE["pass"]);
  94. }else{
  95. // the cookie is OK! dont die() and give results
  96. if($output === true){
  97. $frontend->loadheader(
  98. $get,
  99. $filters,
  100. $page
  101. );
  102. }
  103. return;
  104. }
  105. }
  106. }
  107. if($output === false){
  108. http_response_code(401); // forbidden
  109. echo json_encode([
  110. "status" => "The \"pass\" token in your cookies is missing or has expired!!"
  111. ]);
  112. die();
  113. }
  114. /*
  115. Validate form data
  116. */
  117. $lines =
  118. explode(
  119. "\r\n",
  120. file_get_contents("php://input")
  121. );
  122. $invalid = false;
  123. $answers = [];
  124. $key = false;
  125. $error = "";
  126. foreach($lines as $line){
  127. $line = explode("=", $line, 2);
  128. if(count($line) !== 2){
  129. $invalid = true;
  130. break;
  131. }
  132. preg_match(
  133. '/^c\[([0-9]+)\]$/',
  134. $line[0],
  135. $regex
  136. );
  137. if(
  138. $line[1] != "on" ||
  139. !isset($regex[0][1])
  140. ){
  141. // check if its the v key
  142. if(
  143. $line[0] == "v" &&
  144. preg_match(
  145. '/^c[0-9]+\.[A-Za-z0-9_]{20}$/',
  146. $line[1]
  147. )
  148. ){
  149. $key = apcu_fetch($line[1]);
  150. apcu_delete($line[1]);
  151. }
  152. break;
  153. }
  154. $regex = (int)$regex[1];
  155. if(
  156. $regex >= 16 ||
  157. $regex <= -1
  158. ){
  159. $invalid = true;
  160. break;
  161. }
  162. $answers[] = $regex;
  163. }
  164. // dedup
  165. $answers = array_unique($answers);
  166. if(
  167. !$invalid &&
  168. $key !== false // has captcha been gen'd?
  169. ){
  170. $check = count($key);
  171. // validate answer
  172. for($i=0; $i<count($answers); $i++){
  173. if(in_array($answers[$i], $key)){
  174. $check--;
  175. }else{
  176. $check = -1;
  177. break;
  178. }
  179. }
  180. if($check === 0){
  181. // we passed the captcha
  182. // set cookie
  183. $inc = apcu_inc("cookie");
  184. $key = "k" . $inc . "." . $this->randomchars();
  185. apcu_inc($key, 1, $stupid, 86400);
  186. apcu_dec(intdiv(time(), 3600) . ".bot_requests", 1, $s, 262800);
  187. setcookie(
  188. "pass",
  189. $key,
  190. [
  191. "expires" => time() + 86400, // expires in 24 hours
  192. "samesite" => "Lax",
  193. "path" => "/"
  194. ]
  195. );
  196. $frontend->loadheader(
  197. $get,
  198. $filters,
  199. $page
  200. );
  201. return;
  202. }else{
  203. $error = "<div class=\"quote\">You were <a href=\"https://www.youtube.com/watch?v=e1d7fkQx2rk\" target=\"_BLANK\" rel=\"noreferrer nofollow\">kicked out of Mensa.</a> Please try again.</div>";
  204. }
  205. }
  206. apcu_inc(intdiv(time(), 3600) . ".bot_requests", 1, $s, 259200);
  207. $key = "c" . apcu_inc("captcha_gen", 1) . "." . $this->randomchars();
  208. $payload = [
  209. "timetaken" => microtime(true),
  210. "class" => "",
  211. "right-left" => "",
  212. "right-right" => "",
  213. "left" =>
  214. '<div class="infobox">' .
  215. '<h1>IQ test</h1>' .
  216. 'IQ test has been enabled due to bot abuse on the network.<br>' .
  217. 'Solving this IQ test will let you make 100 searches today. I will add an invite system to bypass this soon...' .
  218. $error .
  219. '<form method="POST" enctype="text/plain" autocomplete="off">' .
  220. '<div class="captcha-wrapper">' .
  221. '<div class="captcha">' .
  222. '<img src="captcha?v=' . $key . '" alt="Captcha image">' .
  223. '<div class="captcha-controls">' .
  224. '<input type="checkbox" name="c[0]" id="c0">' .
  225. '<label for="c0"></label>' .
  226. '<input type="checkbox" name="c[1]" id="c1">' .
  227. '<label for="c1"></label>' .
  228. '<input type="checkbox" name="c[2]" id="c2">' .
  229. '<label for="c2"></label>' .
  230. '<input type="checkbox" name="c[3]" id="c3">' .
  231. '<label for="c3"></label>' .
  232. '<input type="checkbox" name="c[4]" id="c4">' .
  233. '<label for="c4"></label>' .
  234. '<input type="checkbox" name="c[5]" id="c5">' .
  235. '<label for="c5"></label>' .
  236. '<input type="checkbox" name="c[6]" id="c6">' .
  237. '<label for="c6"></label>' .
  238. '<input type="checkbox" name="c[7]" id="c7">' .
  239. '<label for="c7"></label>' .
  240. '<input type="checkbox" name="c[8]" id="c8">' .
  241. '<label for="c8"></label>' .
  242. '<input type="checkbox" name="c[9]" id="c9">' .
  243. '<label for="c9"></label>' .
  244. '<input type="checkbox" name="c[10]" id="c10">' .
  245. '<label for="c10"></label>' .
  246. '<input type="checkbox" name="c[11]" id="c11">' .
  247. '<label for="c11"></label>' .
  248. '<input type="checkbox" name="c[12]" id="c12">' .
  249. '<label for="c12"></label>' .
  250. '<input type="checkbox" name="c[13]" id="c13">' .
  251. '<label for="c13"></label>' .
  252. '<input type="checkbox" name="c[14]" id="c14">' .
  253. '<label for="c14"></label>' .
  254. '<input type="checkbox" name="c[15]" id="c15">' .
  255. '<label for="c15"></label>' .
  256. '</div>' .
  257. '</div>' .
  258. '</div>' .
  259. '<input type="hidden" name="v" value="' . $key . '">' .
  260. '<input type="submit" value="Check IQ" class="captcha-submit">' .
  261. '</form>' .
  262. '</div>'
  263. ];
  264. $frontend->loadheader(
  265. $get,
  266. $filters,
  267. $page,
  268. false
  269. );
  270. echo $frontend->load("search.html", $payload);
  271. die();
  272. }
  273. private function format_time($seconds){
  274. $hours = floor($seconds / 3600);
  275. $minutes = floor(($seconds % 3600) / 60);
  276. $secs = $seconds % 60;
  277. if($hours > 0){
  278. return sprintf('%dh%02dm%02ds', $hours, $minutes, $secs);
  279. }
  280. return sprintf('%dm%02ds', $minutes, $secs);
  281. }
  282. private function randomchars(){
  283. $chars =
  284. array_merge(
  285. range("A", "Z"),
  286. range("a", "z"),
  287. range(0, 9)
  288. );
  289. $chars[] = "_";
  290. $c = count($chars) - 1;
  291. $key = "";
  292. for($i=0; $i<20; $i++){
  293. $key .= $chars[random_int(0, $c)];
  294. }
  295. return $key;
  296. }
  297. }