@@ -74,11 +74,38 @@ public function __construct()
7474 } else {
7575 // set last connect time
7676 $ playersModel = $ this ->load ->model ('Players ' );
77- $ playersModel ('set_last_connect ' , $ this ->player ->id , $ _SERVER [ ' REMOTE_ADDR ' ] , $ _REQUEST ['action ' ] ?? '' );
77+ $ playersModel ('set_last_connect ' , $ this ->player ->id , $ this -> currentPlayerIpSpoofable () , $ _REQUEST ['action ' ] ?? '' );
7878 return true ;
7979 }
8080 }
8181
82+ private function currentPlayerIpSpoofable () {
83+ // this is spoofable so should only be used for reporting player IP last connect and not for IP authentication
84+ $ headers = [
85+ 'HTTP_CF_CONNECTING_IP ' , // Cloudflare
86+ 'HTTP_X_FORWARDED_FOR ' , // Most common proxy header
87+ 'HTTP_X_REAL_IP ' , // Nginx proxy/FastCGI
88+ 'HTTP_CLIENT_IP ' , // Some proxies
89+ 'HTTP_X_FORWARDED ' , // General forward
90+ 'HTTP_FORWARDED_FOR ' , // General forward
91+ 'HTTP_FORWARDED ' , // General forward
92+ 'REMOTE_ADDR ' // Direct connection
93+ ];
94+
95+ foreach ($ headers as $ header ) {
96+ if (!empty ($ _SERVER [$ header ])) {
97+ // If X-Forwarded-For contains multiple IPs, get the first one
98+ if ($ header === 'HTTP_X_FORWARDED_FOR ' ) {
99+ $ ips = explode (', ' , $ _SERVER [$ header ]);
100+ return trim ($ ips [0 ]);
101+ }
102+ return $ _SERVER [$ header ];
103+ }
104+ }
105+
106+ return $ _SERVER ['REMOTE_ADDR ' ]; // Fallback
107+ }
108+
82109 private function loadPlayer ($ player_id )
83110 {
84111 $ this ->db ->where ('id ' , $ player_id );
@@ -112,6 +139,7 @@ private function auth()
112139 $ this ->db ->update ('players ' , ['password ' => $ new_password_hash ]);
113140 }
114141
142+ // note to use $_SERVER['REMOTE_ADDR'] for real IP ($this->currentPlayerIpSpoofable() accounts for reverse proxies, but is spoofable)
115143 if (!$ password_match || ($ _SERVER ['REMOTE_ADDR ' ] != $ this ->player ->ip_address && !empty ($ this ->player ->ip_address ))) {
116144 return false ;
117145 } else {
0 commit comments