Laravel:不正アクセスを撃退するLOG&Redirectの仕組みを作ってみた
不正アクセスを撃退するLOG&Redirectの仕組み
システムを組んでいるとマジでムカついてくる不正アクセス。
なぜこうも不正アクセスが多いのかホント腹立たしく感じています。
と言う事で、以前の記事で攻撃を受けるURLを書いたりLogに残ったIPから地域調べてみたりしましたが今回は撃退偏です。
まずは仕組みを考える
- アクセスlogでIPとアクセスURLの記録をする
- 攻撃対象となっている存在しないURLをブラックURLとして管理する
- ブラックURLに対してアクセスしてきたIPをブラックIPとして管理する
- ブラックIPでも、ブラックURLアクセス5回までは許してやる(出来心的な?許容範囲)
- ブラックIPがアクセスしたURLはブラックURLに登録する
- 許容範囲を超えたブラックIPは別の場所に勝手に飛んでもらう
はい、こんな感じで行きましょう。
必要となるテーブルを作る
欲しいテーブルは3つです。
- 基本的なアクセスログ(アクションログ) ⇒ table名:actlogs
- ブラックURLリスト ⇒ table名:blackurllists
- ブラックIPリスト ⇒ table名:blackiplists
MigrationとModel、Controllerの箱を作ります。
#マイグレーション作成 php artisan make:migration create_actlogs_table php artisan make:migration create_blackurllists_table php artisan make:migration create_blackiplists_table #モデル作成 php artisan make:model Actlog php artisan make:model Blackurllist php artisan make:model Blackiplist #コントローラー作成 php artisan make:controller ActlogController --resource php artisan make:controller BlackurllistgController --resource php artisan make:controller BlackiplistController --resource
マイグレーションファイルを編集します。
#actlogのMigration
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateActlogsTable extends Migration
{
public function up()
{
Schema::create('actlogs', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable()->comment('作業者ID');
$table->string('route')->nullable();
$table->string('url')->nullable();
$table->string('method')->nullable();
$table->integer('status')->unsigned()->nullable();
$table->text('message')->nullable();
$table->string('remote_addr')->nullable();
$table->string('user_agent')->nullable();
$table->timestamps();
$table->index(['created_at']);
$table->index(['user_id']);
});
}
public function down()
{
Schema::dropIfExists('actlogs');
}
}
#blackurllistのMigration
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBlackurllistsTable extends Migration
{
public function up()
{
Schema::create('blackaurllists', function (Blueprint $table) {
$table->increments('id');
$table->string('accessurl',255)->comment('要チェックアクセス');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('blackaccesslists');
}
}
#blackiplistのMigration
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBlackiplistsTable extends Migration
{
public function up()
{
Schema::create('blackiplists', function (Blueprint $table) {
$table->increments('id');
$table->string('blackip',20)->comment('不正アクセスのあったIP');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('blackiplists');
}
}
マイグレーションを実行します。
php artisan migrate
これでテーブルが出来ました。
Modelを修正する
■モデル:Actlog
<?php
namespace App\;
use Illuminate\Database\Eloquent\Model;
class Actlog extends Model
{
protected $guarded = ['*'];
protected $casts = ['meta' => 'json'];
const UPDATED_AT = null;
public function __construct(array $attributes = []) {
parent::__construct($attributes);
$connection = env('DB_LOG_CONNECTION', env('DB_CONNECTION', 'mysql'));
}
protected $fillable = [
'user_id',
'route',
'url',
'method',
'status',
'message',
'remote_addr',
'user_agent',
];
protected $hidden = [
];
}
テーブル:actlogsにはjsonがそのまま入る可能性があります。
protected $casts = ['meta' => 'json']; はその為の設定です。
BlackurllistとBlackiplist はartisanで作ったものそのままでOK。
Controllerの修正
Viewへつなげる設定だけ行って後はお好みでOK。
Log記載ミドルウェアを作成
php artisan make:middleware ActlogMiddleware
作成したミドルウェアをkernelに登録します。
場所:Project >> app >> Http >> Middleware >> Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middleware = [
.
.
.
\App\Http\Middleware\ActlogMiddleware::class,
];
.
.
.
}
さぁ、これでした準備は整いました。
いよいよ不正アクセス締め出しシステムの本丸『ミドルウェア:Actlog』の造作です。
ActlogMiddleware.phpを作る
こんな感じにしました。
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\Actlog;
use Route;
use App\Models\Blackaccesslist;
use App\Models\Blackiplist;
use Carbon;
class ActlogMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
$this->actlog($request, $response->status());
return $response;
}
public function actlog($request, $status)
{
$user = $request -> user();
$data = [
'user_id' => $user ? $user->id : null,
'route' => Route::currentRouteName(),
'url' => $request -> path(),
'method' => $request -> method(),
'status' => $status,
'message' => count($request->toArray()) != 0 ? json_encode($request->toArray()) : null,
'remote_addr' => $request -> ip(),
'user_agent' => $request -> userAgent(),
];
#ブラックIPに登録があるか確認
$BlackIp = Blackiplist::where('blackip',$data['remote_addr'])->first();
#アクセスチェック
$BlackAccess = Blackurllist::where('accessurl',$data['url'])->where('onoff',0)->first();
#ログイン履歴があるか確認
$Actlogs = Actlog::where('remote_addr',$data['remote_addr'])->whereNotNull('user_id')->first();
#アクセス回数を取出す
$visitcount = 0;
if(!empty($BlackIp)){$visitcount = $BlackIp->count;}
switch ($visitcount){
case 0:
#ブラックIP値なし
$vol = self::checkBlackAccess($data,$Actlogs,$BlackAccess);
break;
case 1:
case 2:
case 3:
case 4:
case 5:
#ブラックIP値あり規定数以下
$vol = self::BlackCancelerPath($data,$Actlogs,$BlackIp,$BlackAccess);
break;
default :
#ブラックIP値あり既定数以上
$vol = self::BlackCancelerNG($data,$Actlogs,$BlackIp,$BlackAccess);
break;
}
switch($vol){
case 0:
case 1:
#通常処理
Actlog::create($data);
self::ErrorPageSeparater($status);
break;
default :
$input = array('https://google.co.jp','https://www.rakuten.ne.jp','https://amazon.cn');
#自動退場※勝手にリダイレクト
return redirect(array_rand($input,1));
break;
}
}
/*
* ■ブラックIP値なし
* 1. $Actlogに値がある時何もしない
* 2. $Actlogに値が無い時
* 3.不正URLへのアクセスか確認
* 4.不正URLへのアクセスの時
* 5.ブラックIPに記載
* 6.不正URLへのアクセスではない時
* 7.何もしない
*/
public function checkBlackAccess($data,$Actlogs,$BlackAccess)
{
$value = 0;
if(empty($Actlogs)){//正式ログイン履歴無し
if(!empty($BlackAccess)){//不正URLへのアクセスあり
$item = new Blackiplist;
$item->blackip = $data['remote_addr'];
$item->count = 1;
$item->onoff = 0;//非許可で登録
$item->save();
$value = 1;
}
}
return $value;
}
/*
* ■ブラックIP値あり既定数以上
* 1. $Actlogに値がある時何もしない
* 2. $Actlogに値が無い時
* 3.不正URLへのアクセスか確認
* 4.不正URLへのアクセスの時
* 5.ブラックIPの値を加算
* 6.不正URLを加算
* 7.不正URLへのアクセスではない時
* 7.不正URLにアクセスURLを記載※但しonoff=1で登録する
*/
public function BlackCancelerNG($data,$Actlogs,$BlackIp,$BlackAccess)
{
$value = 0;
if(empty($Actlogs)){//正規ログイン履歴なし
if(!empty($BlackAccess)){//不正URLへのアクセス
#IP最終アクセスを更新
$item = Blackiplist::where('id',$BlackIp->id);//ブラックIPリストをサーチ
$item->update(['count' => ($BlackIp->count)+1]);
#URL最終アクセスを更新
$item = Blackurllist::where('id',$BlackAccess->id);
$item->update(['count'=> ($BlackAccess->count)+1]);
$value = 2;
}else{
#念のため許可URLの記載も含めチェック ※'/'対策
$BlackAccess2 = Blackurllist::where('accessurl',$data['url'])->first();
if(!empty($BlackAccess2)){
#URL最終アクセスを更新
$item = Blackurllist::where('id',$BlackAccess2->id);
$item->update(['count'=> ($BlackAccess2->count)+1]);
}else{
#URLアクセスを新規作成
$item = new Blackurllist;
$item->accessurl = $data['url'];
$item->count = 1;
$item->onoff = 1;
$item->save();
}
$value = 2;
}
}
return $value;
}
/*
* ■ブラックIP値あり既定数以下
* 1. $Actlogに値がある時何もしない
* 2. $Actlogに値が無い時
* 3.不正URLへのアクセスか確認
* 4.不正URLへのアクセスの時
* 5.ブラックIPの値を加算
* 6.不正URLを加算
* 7.不正URLへのアクセスではない時
* 7.不正URLにアクセスURLを記載※但しonoff=1で登録する
*/
public function BlackCancelerPath($data,$Actlogs,$BlackIp,$BlackAccess)
{
$value = 0;
if(empty($Actlogs)){
if(!empty($BlackAccess)){
#IP最終アクセスを更新
$item = Blackiplist::where('id',$BlackIp->id);//ブラックIPリストをサーチ
$item->update(['count' => ($BlackIp->count)+1]);
#URL最終アクセスを更新
$item = Blackurllist::where('id',$BlackAccess->id);
$item->update(['count'=> ($BlackAccess->count)+1]);
$value = 1;
}else{
#念のため許可URLの記載も含めチェック ※'/'対策
$BlackAccess2 = Blackurllist::where('accessurl',$data['url'])->first();
if(!empty($BlackAccess2)){
#URL最終アクセスを更新
$item = Blackurllist::where('id',$BlackAccess2->id);
$item->update(['count'=> ($BlackAccess2->count)+1]);
}else{
#URLアクセスを新規作成
$item = new Blackurllist;
$item->accessurl = $data['url'];
$item->count = 1;
$item->onoff = 1;
$item->save();
}
$value = 1;
}
}
return $value;
}
#Errorフィルター
public function ErrorPageSeparater($status)
{
switch($status){
case 400:
return redirect('error/500');
break;
case 401:
return redirect('error/500');
break;
case 403:
return redirect('error/500');
break;
case 404:
return redirect('error/404');
break;
case 405:
return redirect('error/404');
break;
case 419:
return redirect('error/419');
break;
case 500:
return redirect('error/500');
break;
case 503:
return redirect('error/500');
break;
case 999:
return redirect('error/999');
break;
default :
break;
}
}
}
不正アクセス野郎だったら勝手にRedirect!
$input = array('https://google.co.jp','https://www.rakuten.ne.jp','https://amazon.cn');
return redirect(array_rand($input,1));
return redirect(array_rand($input,1));
まとめ
これ作ってモニタリングしてると、500回越えの不正アクセスがざらにある事がわかります。
それもACCESS元調べれば大体が中国。セキュリティしっかりしないとですね。


