HumanVerification.php 7.15 KB
Newer Older
Dominik Hebeler's avatar
Dominik Hebeler committed
1
2
3
4
<?php

namespace App\Http\Middleware;

5
use Captcha;
Dominik Hebeler's avatar
Dominik Hebeler committed
6
use Closure;
7
use Cookie;
8
use Illuminate\Http\Response;
9
use Illuminate\Support\Facades\Redis;
Dominik Hebeler's avatar
Dominik Hebeler committed
10
use URL;
Dominik Hebeler's avatar
Dominik Hebeler committed
11
12
13
14
15
16
17
18
19
20
21
22

class HumanVerification
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
23
24
        // The specific user
        $user = null;
25
        $update = true;
26
        $prefix = "humanverification";
27
        $redis = Redis::connection('redisCache');
Dominik Hebeler's avatar
Dominik Hebeler committed
28
        try {
Dominik Hebeler's avatar
Dominik Hebeler committed
29
30
31
            $ip = $this->getIP();
            $id = hash("sha512", $ip);
            $uid = hash("sha512", $ip . $_SERVER["AGENT"] . "uid");
Dominik Hebeler's avatar
Dominik Hebeler committed
32
33
34
35
36
37
38
39
            unset($_SERVER["AGENT"]);

            /**
             * If the user sends a Password or a key
             * We will not verificate the user.
             * If someone that uses a bot finds this out we
             * might have to change it at some point.
             */
40
            if ($request->filled('password') || $request->filled('key') || Cookie::get('key') !== null || $request->filled('appversion') || !env('BOT_PROTECTION', false)) {
41
                $update = false;
Dominik Hebeler's avatar
Dominik Hebeler committed
42
43
                return $next($request);
            }
44

45
            # Get all Users of this IP
46
47
            $userList = $redis->smembers($prefix . "." . $id);
            $pipe = $redis->pipeline();
48

49
50
51
52
53
54
55
56
            foreach ($userList as $userid) {
                $pipe->hgetall($prefix . "." . $userid);
            }

            $usersData = $pipe->execute();

            $user = [];
            $users = [];
57
58
59
            # Lock out everyone in a Bot network
            # Find out how many requests this IP has made
            $sum = 0;
60
            foreach ($usersData as $index => $userTmp) {
61
                if (empty($userTmp)) {
62
63
                    // This is a key that has been expired and should be deleted
                    $redis->srem($prefix . "." . $id, $userList[$index]);
64
                    continue;
65
                }
66
67
68
69
70
71
72
73
74
75
76
77
78
                $userNew = ['uid' => $userTmp["uid"],
                    'id' => $userTmp["id"],
                    'unusedResultPages' => intval($userTmp["unusedResultPages"]),
                    'whitelist' => filter_var($userTmp["whitelist"], FILTER_VALIDATE_BOOLEAN),
                    'locked' => filter_var($userTmp["locked"], FILTER_VALIDATE_BOOLEAN),
                    "lockedKey" => $userTmp["lockedKey"],
                ];

                if ($uid === $userTmp["uid"]) {
                    $user = $userNew;
                } else {
                    $users[] = $userNew;
                }
79
                if (!$userNew["whitelist"]) {
80
                    $sum += intval($userTmp["unusedResultPages"]);
81
82
                }

83
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
84

85
86
            # If this user doesn't have an entry we will create one
            if (empty($user)) {
87
                $user =
Dominik Hebeler's avatar
Dominik Hebeler committed
88
                    [
89
90
91
92
93
94
95
                    'uid' => $uid,
                    'id' => $id,
                    'unusedResultPages' => 0,
                    'whitelist' => false,
                    'locked' => false,
                    "lockedKey" => "",
                ];
Dominik Hebeler's avatar
Dominik Hebeler committed
96
            }
97

Dominik Hebeler's avatar
Dominik Hebeler committed
98
99
100
101
102
103
104
105
106
107
108
            # A lot of automated requests are from websites that redirect users to our result page.
            # We will detect those requests and put a captcha
            $referer = URL::previous();
            # Just the URL-Parameter
            $refererLock = false;
            if (stripos($referer, "?") !== false) {
                $referer = substr($referer, stripos($referer, "?") + 1);
                $referer = urldecode($referer);
                if (preg_match("/http[s]{0,1}:\/\/metager\.de\/meta\/meta.ger3\?.*?eingabe=([\w\d]+\.){1,2}[\w\d]+/si", $referer) === 1) {
                    $refererLock = true;
                }
Dominik Hebeler's avatar
Dominik Hebeler committed
109

Dominik Hebeler's avatar
Dominik Hebeler committed
110
            }
111

Dominik Hebeler's avatar
Dominik Hebeler committed
112
            // Defines if this is the only user using that IP Adress
113
            $alone = true;
114
            foreach ($users as $userTmp) {
115
                if ($userTmp["uid"] != $uid && !$userTmp["whitelist"]) {
116
                    $alone = false;
117
118
                }

119
120
121
            }
            if ((!$alone && $sum >= 50 && !$user["whitelist"]) || $refererLock) {
                $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
122
            }
123

Dominik Hebeler's avatar
Dominik Hebeler committed
124
            # If the user is locked we will force a Captcha validation
125
            if ($user["locked"]) {
Dominik Hebeler's avatar
Dominik Hebeler committed
126
                $captcha = Captcha::create("default", true);
127
                $user["lockedKey"] = $captcha["key"];
Dominik Hebeler's avatar
Dominik Hebeler committed
128
129
130
131
132
133
134
135
136
                return
                new Response(
                    view('humanverification.captcha')
                        ->with('title', "Bestätigung erforderlich")
                        ->with('id', $uid)
                        ->with('url', url()->full())
                        ->with('image', $captcha["img"])
                );
            }
137

138
            $user["unusedResultPages"]++;
Dominik Hebeler's avatar
Dominik Hebeler committed
139

140
            if ($alone || $user["whitelist"]) {
Dominik Hebeler's avatar
Dominik Hebeler committed
141
142
143
144
                # This IP doesn't need verification yet
                # The user currently isn't locked

                # We have different security gates:
Dominik Hebeler's avatar
Dominik Hebeler committed
145
                #   50 and then every 25 => Captcha validated Result Pages
Dominik Hebeler's avatar
Dominik Hebeler committed
146
                # If the user shows activity on our result page the counter will be deleted
147
                if ($user["unusedResultPages"] === 50 || ($user["unusedResultPages"] > 50 && $user["unusedResultPages"] % 25 === 0)) {
148
                    $user["locked"] = true;
Dominik Hebeler's avatar
Dominik Hebeler committed
149
150
151
                }

            }
152
153
        } catch (\Predis\Connection\ConnectionException $e) {
            $update = false;
154
        } finally {
155
            if ($update) {
156

Dominik Hebeler's avatar
Dominik Hebeler committed
157
                // Update the user in the database
158
                $pipeline = $redis->pipeline();
159
160
161
162

                $pipeline->hmset($prefix . "." . $user['uid'], $user);
                $pipeline->sadd($prefix . "." . $user["id"], $user["uid"]);

163
164
165
166
                // Expire in two weeks
                $expireLong = 60 * 60 * 24 * 14;
                // Expire in 72h
                $expireShort = 60 * 60 * 72;
167
168

                if ($user["whitelist"]) {
169
                    $pipeline->expire($prefix . "." . $user['uid'], $expireLong);
170
                } else {
171
                    $pipeline->expire($prefix . "." . $user['uid'], $expireShort);
172
                }
173

174
                $pipeline->expire($prefix . "." . $user["id"], $expireLong);
175
176

                $pipeline->execute();
177
            }
Dominik Hebeler's avatar
Dominik Hebeler committed
178
        }
179

180
        $request->request->add(['verification_id' => $user["uid"], 'verification_count' => $user["unusedResultPages"]]);
Dominik Hebeler's avatar
Dominik Hebeler committed
181
        return $next($request);
Dominik Hebeler's avatar
Dominik Hebeler committed
182

Dominik Hebeler's avatar
Dominik Hebeler committed
183
    }
Dominik Hebeler's avatar
Dominik Hebeler committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

    private function getIP()
    {
        $ip = \Request::ip();
        $serverAddress = empty($_SERVER['SERVER_ADDR']) ? "144.76.88.77" : $_SERVER['SERVER_ADDR'];
        $queryUrl = "https://tor.metager.org?password=" . urlencode(env("TOR_PASSWORD")) . "&ra=" . urlencode($ip) . "&sa=" . urlencode($serverAddress) . "&sp=443";

        $ch = curl_init($queryUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 1);
        curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpcode === 200) {
            return "999.999.999.999";
        } else {
            return $ip;
        }
    }
Dominik Hebeler's avatar
Dominik Hebeler committed
204
}