Protecting Laravel Sites with IP Intelligence
Publikováno: 24.11.2018
From the moment you publish a website, you need to be wary of security. From hackers to script-kiddies, you can’t always be sure of where the next attack could come from.
So, as developers, ...
From the moment you publish a website, you need to be wary of security. From hackers to script-kiddies, you can’t always be sure of where the next attack could come from.
So, as developers, we are always on the prowl for the next best thing when it comes to protecting our website(s).
In this article, we will cover a simple way of how we can use IP intelligence to detect unwanted connections and protect against insecure requests.
What is IP Intelligence
When most people hear IP intelligence, the thing that comes to mind is “locating users from their IP address”. But, it doesn’t stop there, IP intelligence can be used to accomplish many things:
- Content personalization.
- Currency detection.
- Fraud prevention.
- Time Zone lookup.
- Language redirection.
The list above is just a handful of things that can be achieved using IP intelligence.
Building a service that can do all the things listed above can take a lot of time and resources. So, instead of building and managing such a service, that’s what the sponsor IPAPI of this article does.
Getting Started
Our firewall will be built as a middleware for our application, meaning a request coming into our app will pass through this “filter” and reject suspected bad actors.
For demonstrating how we could build a simple middleware to protect our apps, we will be creating a Laravel project. Note, the same can be done in any programming language of choice
composer create-project laravel/laravel firewall --prefer-dist
So, head over to IPAPI and create an account. After that, you’ll see a secret key that has a similar structure to 86ebc30b4adfc508e48bf1b489140fe3
. Grab whatever your own is and add it to your `.env` file.
IPAPI_ACCESS_KEY=86ebc30b4adfc508e48bf1b489140fe3
After that open config/services.php
and add the following array value.
'ip' => [
'key' => env('IPAPI_ACCESS_KEY'),
],
The last thing to do is to install GuzzleHttp
which will be used to make a request to IPAPI’s server.
composer require guzzlehttp/guzzle
After that, we can then build our middleware.
Making a Request to IPAPI’s Server
So, IPAPI offers two endpoints for us to use.
api.ipapi.com/api/<ip>
where we provide the IP we want to check.api.ipapi.com/check
will guess the incoming IP address and give a response (good for requests coming from the browser.
We are most interested in the first one because using the second one will retrieve the IP of our server instead of the incoming request. So, using the first one, we can capture the user’s IP and forward it to IPAPI.
After we create a request like:
GET https://api.ipapi.com/api/161.185.160.93?access_key=86ebc30b4adfc508e48bf1b489140fe3
The response will look something like this
{
"ip": "161.185.160.93",
"hostname": "161.185.160.93",
"type": "ipv4",
"continent_code": "NA",
"continent_name": "North America",
"country_code": "US",
"country_name": "United States",
"region_code": "NY",
"region_name": "New York",
"city": "Brooklyn",
"zip": "11238",
"latitude": 40.676,
"longitude": -73.9629,
"location": {
"geoname_id": 5110302,
"capital": "Washington D.C.",
"languages": [
{
"code": "en",
"name": "English",
"native": "English"
}
],
"country_flag": "http://assets.ipapi.com/flags/us.svg",
"country_flag_emoji": "????????",
"country_flag_emoji_unicode": "U+1F1FA U+1F1F8",
"calling_code": "1",
"is_eu": false
},
"time_zone": {
"id": "America/New_York",
"current_time": "2018-09-24T05:07:10-04:00",
"gmt_offset": -14400,
"code": "EDT",
"is_daylight_saving": true
},
"currency": {
"code": "USD",
"name": "US Dollar",
"plural": "US dollars",
"symbol": "$",
"symbol_native": "$"
},
"connection": {
"asn": 22252,
"isp": "The City of New York"
},
"security": {
"is_proxy": false,
"proxy_type": null,
"is_crawler": false,
"crawler_name": null,
"crawler_type": null,
"is_tor": false,
"threat_level": "low",
"threat_types": null
}
}
We can see that IPAPI does a lot of work for us. For this, however, for this article, we are interested in the “security” part of the response.
...
"security": {
"is_proxy": false,
"proxy_type": null,
"is_crawler": false,
"crawler_name": null,
"crawler_type": null,
"is_tor": false,
"threat_level": "low",
"threat_types": null
} ...
Taking a closer look at the security portion, we can see that IPAPI does a lot of checks for us. From giving the response a security rating, to checking if the incoming request is from the TOR network. It even tells us if a crawler is making the incoming request.
Creating Our Middleware
Middlewares are mechanisms that sit in-between an incoming request and your app. Scotch has a short intro to Laravel middlewares.
Now, we’ll move into the root of our project and run
php artisan make:middleware IPFirewall
After we’ve created the middleware, we can find it in app/Http/Middlewares/IPFirewall.php
you will see something similar to;
<?php
namespace App\Http\Middleware;
use Closure;
class IPFirewall
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
}
So, to protect our server, we can do this:
public function handle($request, Closure $next)
{
$ip = $request->ip();
$key = config('services.ip.key');
$url = "http://api.ipapi.com/api/{$ip}?access_key={$key}&security=1";
// make request
$client = new Client;
$response = $client->request('GET', $url);
$data = json_decode((string) $response->getBody(), true);
if (!array_key_exists('security', $data)) {
return false;
}
return $data['security']['threat_level'] === 'high' ? abort(403) : $next($request);
}
From the request above:
- We first get the incoming IP address of the user
- Then we build our request to send to IPAPI,
- When we get a response from IPAPI, we check if the security response exists
- Then if the request threat level is high, we want to restrict user access.
Improving for Performance
The above solution is not the best implementation we have. Because this means that the request is going to slow down for every incoming request.
Because Laravel has a cache layer, we can use that to our advantage by doing
public function handle($request, Closure $next)
{
$ip = $request->ip();
$insecureRequest = Cache::remember("firewall_$ip", function() use ($ip) {
// build parameters
$key = config('services.ip.key');
$url = "http://api.ipapi.com/api/{$ip}?access_key={$key}&security=1";
// make request
$client = new Client;
$response = $client->request('GET', $url);
$data = json_decode((string) $response->getBody(), true);
if (!array_key_exists('security', $data)) {
return false;
}
return $data['security']['threat_level'] === 'high' ?? false;
});
return $insecureRequest ? abort(403) : $next($request);
}
Calling Cache::remember()
will tell Laravel to fetch a value from the cache, if it doesn’t exist, it’ll run the closure and return the value from the closure to the cache.
Using the IP address as the unique key, Laravel will first try to fetch the IP’s threat status. If it the request is considered an insecure request, Laravel aborts. Otherwise, the request is allowed through, and we only need to perform the check every once in a while.
Conclusion
IPAPI is a great service used many companies, give them a shot and improve your application security.