bot_protection.php 8.0 KB

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