From f15407bc7516b5aa07b8632b2e89673a4188b188 Mon Sep 17 00:00:00 2001 From: thoughever <96242838+thoughever@users.noreply.github.com> Date: Mon, 17 Jan 2022 17:06:20 +0000 Subject: [PATCH] X-Real-IP support and Varnish PURGE config options X-Real-IP for core functionality Global config define REVERSE_PROXY_X_HEADERS Config host and port for varnish PURGE config option to specify PURGE protocol exception in curl purge now shows error code ipv6 x-real-ip addresses are now validated properly X-Forwarded-Proto enabled by define --- core/captcha.php | 6 +++--- core/imageboard/image.php | 2 +- core/sys_config.php | 1 + core/util.php | 28 ++++++++++++++++++++++++++-- ext/comment/main.php | 6 +++--- ext/image_view_counter/main.php | 4 ++-- ext/ipban/main.php | 7 ++++--- ext/log_db/main.php | 2 +- ext/log_logstash/main.php | 2 +- ext/log_net/main.php | 2 +- ext/notes/main.php | 4 ++-- ext/pm/main.php | 2 +- ext/pm_triggers/main.php | 2 +- ext/source_history/main.php | 2 +- ext/tag_history/main.php | 2 +- ext/user/main.php | 2 +- ext/varnish/main.php | 18 +++++++++++++++++- ext/wiki/main.php | 4 ++-- index.php | 2 +- tests/defines.php | 1 + 20 files changed, 71 insertions(+), 28 deletions(-) diff --git a/core/captcha.php b/core/captcha.php index dd0fde2b..ec79c2cb 100644 --- a/core/captcha.php +++ b/core/captcha.php @@ -11,7 +11,7 @@ function captcha_get_html(): string { global $config, $user; - if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) { + if (DEBUG && ip_in_range(get_real_ip(), "127.0.0.0/8")) { return ""; } @@ -34,7 +34,7 @@ function captcha_check(): bool { global $config, $user; - if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) { + if (DEBUG && ip_in_range(get_real_ip(), "127.0.0.0/8")) { return true; } @@ -42,7 +42,7 @@ function captcha_check(): bool $r_privatekey = $config->get_string('api_recaptcha_privkey'); if (!empty($r_privatekey)) { $recaptcha = new ReCaptcha($r_privatekey); - $resp = $recaptcha->verify($_POST['g-recaptcha-response'] ?? "", $_SERVER['REMOTE_ADDR']); + $resp = $recaptcha->verify($_POST['g-recaptcha-response'] ?? "", get_real_ip()); if (!$resp->isSuccess()) { log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes())); diff --git a/core/imageboard/image.php b/core/imageboard/image.php index e7f67893..9fec2bc0 100644 --- a/core/imageboard/image.php +++ b/core/imageboard/image.php @@ -385,7 +385,7 @@ class Image now(), :source )", [ - "owner_id" => $user->id, "owner_ip" => $_SERVER['REMOTE_ADDR'], + "owner_id" => $user->id, "owner_ip" => get_real_ip(), "filename" => $cut_name, "filesize" => $this->filesize, "hash" => $this->hash, "mime" => strtolower($this->mime), "ext" => strtolower($this->ext), "source" => $this->source diff --git a/core/sys_config.php b/core/sys_config.php index 04239397..90f11169 100644 --- a/core/sys_config.php +++ b/core/sys_config.php @@ -34,3 +34,4 @@ _d("EXTRA_EXTS", ""); // string optional extra extensions _d("BASE_HREF", null); // string force a specific base URL (default is auto-detect) _d("TRACE_FILE", null); // string file to log performance data into _d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds +_d("REVERSE_PROXY_X_HEADERS", false); // boolean get request IPs from "X-Real-IP" and protocol from "X-Forwarded-Proto" HTTP headers diff --git a/core/util.php b/core/util.php index 80a86894..a2774437 100644 --- a/core/util.php +++ b/core/util.php @@ -66,7 +66,7 @@ function contact_link(): ?string function is_https_enabled(): bool { // check forwarded protocol - if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { + if (REVERSE_PROXY_X_HEADERS && !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { $_SERVER['HTTPS']='on'; } return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); @@ -160,6 +160,29 @@ function check_im_version(): int return (empty($convert_check) ? 0 : 1); } +/** + * Get request IP + */ + +function get_remote_addr() { + return $_SERVER['REMOTE_ADDR']; +} +/** + * Get real IP if behind a reverse proxy + */ + +function get_real_ip() { + $ip = get_remote_addr(); + if (REVERSE_PROXY_X_HEADERS && isset($_SERVER['HTTP_X_REAL_IP'])) { + $ip = $_SERVER['HTTP_X_REAL_IP']; + if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + $ip = "0.0.0.0"; + } + } + + return $ip; +} + /** * Get the currently active IP, masked to make it not change when the last * octet or two change, for use in session cookies and such @@ -167,7 +190,7 @@ function check_im_version(): int function get_session_ip(Config $config): string { $mask = $config->get_string("session_hash_mask", "255.255.0.0"); - $addr = $_SERVER['REMOTE_ADDR']; + $addr = get_real_ip(); $addr = inet_ntop(inet_pton($addr) & inet_pton($mask)); return $addr; } @@ -799,3 +822,4 @@ function generate_key(int $length = 20): string return $randomString; } + diff --git a/ext/comment/main.php b/ext/comment/main.php index dc958b06..88d039d8 100644 --- a/ext/comment/main.php +++ b/ext/comment/main.php @@ -497,7 +497,7 @@ class CommentList extends Extension SELECT * FROM comments WHERE owner_ip = :remote_ip AND posted > now() - $window_sql - ", ["remote_ip"=>$_SERVER['REMOTE_ADDR']]); + ", ["remote_ip"=>get_real_ip()]); return (count($result) >= $max); } @@ -516,7 +516,7 @@ class CommentList extends Extension */ public static function get_hash(): string { - return md5($_SERVER['REMOTE_ADDR'] . date("%Y%m%d")); + return md5(get_real_ip() . date("%Y%m%d")); } private function is_spam_akismet(string $text): bool @@ -576,7 +576,7 @@ class CommentList extends Extension $database->execute( "INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ". "VALUES(:image_id, :user_id, :remote_addr, now(), :comment)", - ["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>$_SERVER['REMOTE_ADDR'], "comment"=>$comment] + ["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>get_real_ip(), "comment"=>$comment] ); $cid = $database->get_last_insert_id('comments_id_seq'); $snippet = substr($comment, 0, 100); diff --git a/ext/image_view_counter/main.php b/ext/image_view_counter/main.php index 991e4a12..415025b4 100644 --- a/ext/image_view_counter/main.php +++ b/ext/image_view_counter/main.php @@ -29,7 +29,7 @@ class ImageViewCounter extends Extension WHERE ipaddress=:ipaddress AND timestamp >:lasthour AND image_id =:image_id ", [ - "ipaddress" => $_SERVER['REMOTE_ADDR'], + "ipaddress" => get_real_ip(), "lasthour" => time() - $this->view_interval, "image_id" => $imgid ] @@ -50,7 +50,7 @@ class ImageViewCounter extends Extension "image_id" => $imgid, "user_id" => $user->id, "timestamp" => time(), - "ipaddress" => $_SERVER['REMOTE_ADDR'], + "ipaddress" => get_real_ip(), ] ); } diff --git a/ext/ipban/main.php b/ext/ipban/main.php index b48dd3b6..90a14e2f 100644 --- a/ext/ipban/main.php +++ b/ext/ipban/main.php @@ -128,8 +128,7 @@ class IPBan extends Extension // Check if our current IP is in either of the ban lists $active_ban_id = ( - $this->find_active_ban($ips, $_SERVER['REMOTE_ADDR'], $networks) ?? - $this->find_active_ban($ips, @$_SERVER['HTTP_X_FORWARDED_FOR'], $networks) + $this->find_active_ban($ips, get_real_ip(), $networks) ); // If an active ban is found, act on it @@ -139,10 +138,12 @@ class IPBan extends Extension return; } + $row_banner_id_int = intval($row['banner_id']); + $msg = $config->get_string("ipban_message_{$row['mode']}") ?? $config->get_string("ipban_message"); $msg = str_replace('$IP', $row["ip"], $msg); $msg = str_replace('$DATE', $row['expires'] ?? 'the end of time', $msg); - $msg = str_replace('$ADMIN', User::by_id($row['banner_id'])->name, $msg); + $msg = str_replace('$ADMIN', User::by_id($row_banner_id_int)->name, $msg); $msg = str_replace('$REASON', $row['reason'], $msg); $contact_link = contact_link(); if (!empty($contact_link)) { diff --git a/ext/log_db/main.php b/ext/log_db/main.php index 3ac6c715..440f516f 100644 --- a/ext/log_db/main.php +++ b/ext/log_db/main.php @@ -295,7 +295,7 @@ class LogDatabase extends Extension VALUES(now(), :section, :priority, :username, :address, :message) ", [ "section"=>$event->section, "priority"=>$event->priority, "username"=>$username, - "address"=>$_SERVER['REMOTE_ADDR'], "message"=>$event->message + "address"=>get_real_ip(), "message"=>$event->message ]); } } diff --git a/ext/log_logstash/main.php b/ext/log_logstash/main.php index 4f1577d5..db7c5012 100644 --- a/ext/log_logstash/main.php +++ b/ext/log_logstash/main.php @@ -21,7 +21,7 @@ class LogLogstash extends Extension #"@request" => $_SERVER, "@request" => [ "UID" => get_request_id(), - "REMOTE_ADDR" => $_SERVER['REMOTE_ADDR'], + "REMOTE_ADDR" => get_real_ip(), ], ]; diff --git a/ext/log_net/main.php b/ext/log_net/main.php index d5f7cb64..b5b4d90a 100644 --- a/ext/log_net/main.php +++ b/ext/log_net/main.php @@ -15,7 +15,7 @@ class LogNet extends Extension if ($this->count < 10) { // TODO: colour based on event->priority $username = ($user && $user->name) ? $user->name : "Anonymous"; - $str = sprintf("%-15s %-10s: %s", $_SERVER['REMOTE_ADDR'], $username, $event->message); + $str = sprintf("%-15s %-10s: %s", get_real_ip(), $username, $event->message); $this->msg($str); } elseif ($this->count == 10) { $this->msg('suppressing flood, check the web log'); diff --git a/ext/notes/main.php b/ext/notes/main.php index c8b85063..b9d32b6c 100644 --- a/ext/notes/main.php +++ b/ext/notes/main.php @@ -257,7 +257,7 @@ class Notes extends Extension " INSERT INTO notes (enable, image_id, user_id, user_ip, date, x1, y1, height, width, note) VALUES (:enable, :image_id, :user_id, :user_ip, now(), :x1, :y1, :height, :width, :note)", - ['enable'=>1, 'image_id'=>$imageID, 'user_id'=>$user_id, 'user_ip'=>$_SERVER['REMOTE_ADDR'], 'x1'=>$noteX1, 'y1'=>$noteY1, 'height'=>$noteHeight, 'width'=>$noteWidth, 'note'=>$noteText] + ['enable'=>1, 'image_id'=>$imageID, 'user_id'=>$user_id, 'user_ip'=>get_real_ip(), 'x1'=>$noteX1, 'y1'=>$noteY1, 'height'=>$noteHeight, 'width'=>$noteWidth, 'note'=>$noteText] ); $noteID = $database->get_last_insert_id('notes_id_seq'); @@ -423,7 +423,7 @@ class Notes extends Extension INSERT INTO note_histories (note_enable, note_id, review_id, image_id, user_id, user_ip, date, x1, y1, height, width, note) VALUES (:note_enable, :note_id, :review_id, :image_id, :user_id, :user_ip, now(), :x1, :y1, :height, :width, :note) ", - ['note_enable'=>$noteEnable, 'note_id'=>$noteID, 'review_id'=>$reviewID, 'image_id'=>$imageID, 'user_id'=>$user->id, 'user_ip'=>$_SERVER['REMOTE_ADDR'], + ['note_enable'=>$noteEnable, 'note_id'=>$noteID, 'review_id'=>$reviewID, 'image_id'=>$imageID, 'user_id'=>$user->id, 'user_ip'=>get_real_ip(), 'x1'=>$noteX1, 'y1'=>$noteY1, 'height'=>$noteHeight, 'width'=>$noteWidth, 'note'=>$noteText] ); } diff --git a/ext/pm/main.php b/ext/pm/main.php index c7ede0bd..4198b610 100644 --- a/ext/pm/main.php +++ b/ext/pm/main.php @@ -176,7 +176,7 @@ class PrivMsg extends Extension $from_id = $user->id; $subject = $_POST["subject"]; $message = $_POST["message"]; - send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message))); + send_event(new SendPMEvent(new PM($from_id, get_real_ip(), $to_id, $subject, $message))); $page->flash("PM sent"); $page->set_mode(PageMode::REDIRECT); $page->set_redirect(referer_or(make_link())); diff --git a/ext/pm_triggers/main.php b/ext/pm_triggers/main.php index 8eacccf8..5b36eb1a 100644 --- a/ext/pm_triggers/main.php +++ b/ext/pm_triggers/main.php @@ -18,7 +18,7 @@ class PMTrigger extends Extension global $user; send_event(new SendPMEvent(new PM( $user->id, - $_SERVER["REMOTE_ADDR"], + get_real_ip(), $to_id, $subject, $body diff --git a/ext/source_history/main.php b/ext/source_history/main.php index 36d00480..742ba1db 100644 --- a/ext/source_history/main.php +++ b/ext/source_history/main.php @@ -382,7 +382,7 @@ class SourceHistory extends Extension " INSERT INTO source_histories(image_id, source, user_id, user_ip, date_set) VALUES (:image_id, :source, :user_id, :user_ip, now())", - ["image_id"=>$image->id, "source"=>$new_source, "user_id"=>$user->id, "user_ip"=>$_SERVER['REMOTE_ADDR']] + ["image_id"=>$image->id, "source"=>$new_source, "user_id"=>$user->id, "user_ip"=>get_real_ip()] ); $entries++; diff --git a/ext/tag_history/main.php b/ext/tag_history/main.php index 4663266d..7d9579f4 100644 --- a/ext/tag_history/main.php +++ b/ext/tag_history/main.php @@ -381,7 +381,7 @@ class TagHistory extends Extension " INSERT INTO tag_histories(image_id, tags, user_id, user_ip, date_set) VALUES (:image_id, :tags, :user_id, :user_ip, now())", - ["image_id"=>$image->id, "tags"=>$new_tags, "user_id"=>$user->id, "user_ip"=>$_SERVER['REMOTE_ADDR']] + ["image_id"=>$image->id, "tags"=>$new_tags, "user_id"=>$user->id, "user_ip"=>get_real_ip()] ); $entries++; diff --git a/ext/user/main.php b/ext/user/main.php index 429a6902..703b56ac 100644 --- a/ext/user/main.php +++ b/ext/user/main.php @@ -204,7 +204,7 @@ class UserPage extends Extension $event->add_stats("Joined: $h_join_date", 10); if ($user->name == $event->display_user->name) { - $event->add_stats("Current IP: {$_SERVER['REMOTE_ADDR']}", 80); + $event->add_stats("Current IP: " . get_real_ip(), 80); } $event->add_stats("Class: $h_class", 90); diff --git a/ext/varnish/main.php b/ext/varnish/main.php index d240e344..82938d17 100644 --- a/ext/varnish/main.php +++ b/ext/varnish/main.php @@ -4,6 +4,14 @@ declare(strict_types=1); class VarnishPurger extends Extension { + public function onInitExt(InitExtEvent $event) + { + global $config; + $config->set_default_string('varnish_host', '127.0.0.1'); + $config->set_default_int('varnish_port', 80); + $config->set_default_string('varnish_protocol', 'http'); + } + private function curl_purge($path) { // waiting for curl timeout adds ~5 minutes to unit tests @@ -11,13 +19,21 @@ class VarnishPurger extends Extension return; } - $url = make_http(make_link($path)); + global $config; + $host = $config->get_string('varnish_host'); + $port = $config->get_int('varnish_port'); + $protocol = $config->get_string('varnish_protocol'); + $url = $protocol . '://'. $host . '/' . $path; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_PORT, $port); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PURGE"); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($httpCode != 200) { + throw new SCoreException('PURGE ' . $url . ' unsuccessful (HTTP '. $httpCode . ')'); + } curl_close($ch); assert(!is_null($result) && !is_null($httpCode)); //return $result; diff --git a/ext/wiki/main.php b/ext/wiki/main.php index bdf0a692..e92f8c79 100644 --- a/ext/wiki/main.php +++ b/ext/wiki/main.php @@ -254,7 +254,7 @@ class Wiki extends Extension " INSERT INTO wiki_pages(owner_id, owner_ip, date, title, revision, locked, body) VALUES (:owner_id, :owner_ip, now(), :title, :revision, :locked, :body)", - ["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'], + ["owner_id"=>$event->user->id, "owner_ip"=>get_real_ip(), "title"=>$wpage->title, "revision"=>$wpage->revision, "locked"=>$wpage->locked, "body"=>$wpage->body] ); } else { @@ -262,7 +262,7 @@ class Wiki extends Extension " UPDATE wiki_pages SET owner_id=:owner_id, owner_ip=:owner_ip, date=now(), locked=:locked, body=:body WHERE title = :title ORDER BY revision DESC LIMIT 1", - ["owner_id"=>$event->user->id, "owner_ip"=>$_SERVER['REMOTE_ADDR'], + ["owner_id"=>$event->user->id, "owner_ip"=>get_real_ip(), "title"=>$wpage->title, "locked"=>$wpage->locked, "body"=>$wpage->body] ); } diff --git a/index.php b/index.php index 51733ebc..7a4d9b03 100644 --- a/index.php +++ b/index.php @@ -63,7 +63,7 @@ try { $_SERVER["REQUEST_URI"] ?? "No Request", [ "user"=>$_COOKIE["shm_user"] ?? "No User", - "ip"=>$_SERVER['REMOTE_ADDR'] ?? "No IP", + "ip"=>get_real_ip() ?? "No IP", "user_agent"=>$_SERVER['HTTP_USER_AGENT'] ?? "No UA", ] ); diff --git a/tests/defines.php b/tests/defines.php index b0fa955f..ac6d4103 100644 --- a/tests/defines.php +++ b/tests/defines.php @@ -17,3 +17,4 @@ define("TIMEZONE", 'UTC'); define("BASE_HREF", "/test"); define("CLI_LOG_LEVEL", 50); define("STATSD_HOST", null); +define("REVERSE_PROXY_X_HEADERS", false);