xref: /plugin/hideip/action.php (revision e6a02230d1edb6feccdf1c861381fc2faf08118b)
1*e6a02230Stracker-user<?php
2*e6a02230Stracker-user/**
3*e6a02230Stracker-user * Hide IP — action component.
4*e6a02230Stracker-user *
5*e6a02230Stracker-user * Hooks INIT_LANG_LOAD (same point in DokuWiki's boot the anonip plugin uses)
6*e6a02230Stracker-user * and rewrites the server-side IP variables so that every later piece of code
7*e6a02230Stracker-user * — clientIP(), $INPUT->server->str('REMOTE_ADDR'), changelog writers, page
8*e6a02230Stracker-user * metadata, the mailer's X-Originating-IP header, AJAX info, etc. — sees a
9*e6a02230Stracker-user * constant placeholder instead of the real address.
10*e6a02230Stracker-user *
11*e6a02230Stracker-user * Why a constant ('0.0.0.0') rather than a session-hashed pseudo-IPv6:
12*e6a02230Stracker-user *  - This wiki has no anonymous edits; the username field already
13*e6a02230Stracker-user *    differentiates editors. A pseudo-IP adds no investigative value.
14*e6a02230Stracker-user *  - Less surface area for re-identification attacks. The original anonip
15*e6a02230Stracker-user *    leaked the first IPv4 octet via auth_browseruid(); even the fork's
16*e6a02230Stracker-user *    session-hash variant still depends on session-stability assumptions.
17*e6a02230Stracker-user *  - Page locking is not affected: inc/common.php::lock() writes only the
18*e6a02230Stracker-user *    username when REMOTE_USER is set (which it always will be here), and
19*e6a02230Stracker-user *    unlock() has session_id() as a fallback even when it isn't.
20*e6a02230Stracker-user */
21*e6a02230Stracker-user
22*e6a02230Stracker-useruse dokuwiki\Extension\ActionPlugin;
23*e6a02230Stracker-useruse dokuwiki\Extension\EventHandler;
24*e6a02230Stracker-useruse dokuwiki\Extension\Event;
25*e6a02230Stracker-user
26*e6a02230Stracker-userclass action_plugin_hideip extends ActionPlugin
27*e6a02230Stracker-user{
28*e6a02230Stracker-user    /** The placeholder all anonymised reads will return. */
29*e6a02230Stracker-user    const PLACEHOLDER_IP = '0.0.0.0';
30*e6a02230Stracker-user
31*e6a02230Stracker-user    /**
32*e6a02230Stracker-user     * @param EventHandler $controller
33*e6a02230Stracker-user     */
34*e6a02230Stracker-user    public function register(EventHandler $controller)
35*e6a02230Stracker-user    {
36*e6a02230Stracker-user        // INIT_LANG_LOAD fires after init.php has applied any trusted-proxy
37*e6a02230Stracker-user        // X-Forwarded-For rewriting (lines ~500/550 of init.php), so by the
38*e6a02230Stracker-user        // time we run, $_SERVER['REMOTE_ADDR'] holds the resolved client IP
39*e6a02230Stracker-user        // that DokuWiki would normally record. We clobber it before any
40*e6a02230Stracker-user        // downstream code reads it again.
41*e6a02230Stracker-user        $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'handleAnonymise');
42*e6a02230Stracker-user    }
43*e6a02230Stracker-user
44*e6a02230Stracker-user    /**
45*e6a02230Stracker-user     * @param Event $event  unused, dispatch signature only
46*e6a02230Stracker-user     * @param mixed $param  unused
47*e6a02230Stracker-user     */
48*e6a02230Stracker-user    public function handleAnonymise(Event $event, $param)
49*e6a02230Stracker-user    {
50*e6a02230Stracker-user        // Direct $_SERVER write: DokuWiki's dokuwiki\Input\Server class uses
51*e6a02230Stracker-user        // $access =& $_SERVER (see inc/Input/Server.php), so $INPUT->server
52*e6a02230Stracker-user        // reads pick up the new value without anything else to do.
53*e6a02230Stracker-user        $_SERVER['REMOTE_ADDR'] = self::PLACEHOLDER_IP;
54*e6a02230Stracker-user
55*e6a02230Stracker-user        // Also clear every common forwarding header. inc/Ip.php::clientIps()
56*e6a02230Stracker-user        // walks HTTP_X_FORWARDED_FOR and appends every IP it finds; if we
57*e6a02230Stracker-user        // left these set, the real client address would still leak into
58*e6a02230Stracker-user        // clientIP(false) (multi-IP mode) and into header logs/UIs that
59*e6a02230Stracker-user        // consume those raw values.
60*e6a02230Stracker-user        foreach (
61*e6a02230Stracker-user            [
62*e6a02230Stracker-user                'HTTP_X_FORWARDED_FOR',
63*e6a02230Stracker-user                'HTTP_X_REAL_IP',
64*e6a02230Stracker-user                'HTTP_CLIENT_IP',
65*e6a02230Stracker-user                'HTTP_FORWARDED',
66*e6a02230Stracker-user                'HTTP_CF_CONNECTING_IP',   // Cloudflare
67*e6a02230Stracker-user                'HTTP_TRUE_CLIENT_IP',     // Akamai / Cloudflare Enterprise
68*e6a02230Stracker-user            ] as $header
69*e6a02230Stracker-user        ) {
70*e6a02230Stracker-user            if (isset($_SERVER[$header])) unset($_SERVER[$header]);
71*e6a02230Stracker-user        }
72*e6a02230Stracker-user    }
73*e6a02230Stracker-user}
74