1
0

bot_protection.php 8.4 KB

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