196の日記

セキュリティ系の勉強、その他開発メモとか雑談、忘れそうな計算式などを書き溜める場所になっています. githhubはUnity触っていた頃ものがメイン https://github.com/196kakinuma

ruby チュートリアル8章 メモ

セッションとクッキー

混同しがち(自分だけかも、)HTTPはステートレスなプロトコルなので、セッションのような物を実装するには、状態を保存するものが別に必要なる。それの一つがクッキー。クッキーはユーザのブラウザ内に保存される小さなテキスト。一方セッションはそもそも何かを継続的に行なっているものを指すらしく、接続し続けることもその中の一つと言う解釈でしょうか。

ログインページ

チュートリアルではこれから生成するセッションのコントローラがそのままログインページ系を司どるように作るそうです。言葉にするとわかりにくいので下にそれっぽいルーティングを示します。loginと言うコントローラを作るのではなく、セッションを開始終了のためのページがログイン/ログアウトってイメージですね。なお、セッションコントローラの処理はこれだけでよいので、generateで無駄なアクションを作ったり、resourcesで無駄にルーティングを作るのは避けます。

#config/route.rb
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'


renderメソッドとアクション

下のようにflashに値を格納した後にrenderを呼ぶと、flashは予期しない挙動をとる。と言うのも、rendernewを呼ぶとそれはアクションとは認められないため必然的にflashの生存時間が1アクション分伸びてしまう。

#app/controllers/sessions_controller.rb
 def create
    if ~~
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end


flash.nowメソッドを利用することでこれを回避することができる。

 flash.now[:danger] = 'Invalid email/password combination'


findメソッドとfind_byメソッド

User.find(session[:user_id])でユーザ検索を行った場合、ユーザIDが存在していない状況(まだログインしていない、session[:user_id]の値がnilになる状態)だと、例外が発生する。一方User.find_by(id: session[:user_id])は、例外を発生せずnilを返す。

fixtureとFactorybot

チュートリアルではMinitest利用なのでfixtureを利用しているが、RSpecを使用していならFactorybotでカバーできる。個人的には下のようにしてチュートリアルのテストコードを実装しています。

#spec/controllers/user_controller_spec.rb
let(:user){build(:user)}
    let(:post_create){
      post( :create, params: {
        user: {
          name:   user.name,
          email:  user.email,
          password: user.password,
          password_confirmation: user.password_confirmation 
        }
    })}

    it 'ユーザが新規に生成される' do
      expect{post_create}.to change{User.count}.by(1)
    end

t-pot 観察 #6 明け方からアクセス増加

こんにちは。目立つログが残っていたので、メモしておきます。まず、今月のここまでのハニポ全体の記録をみてみます。
f:id:thinline196:20181111140554p:plain

今の所今月一のアクセス数です。では中身を見てみます。上のグラフと形が似てしまっていますが、今日(14時まで)の記録です。
f:id:thinline196:20181111140714p:plain

Cowrieが検知してますが、どうやら一度にあのアクセス量を受けていたわけでは無いようです。では国別とポート別に分けてみます。
f:id:thinline196:20181111140903p:plain
f:id:thinline196:20181111141018p:plain

詳細

中国からは211.143.198[.]237から16731程度のアクセス、アメリカからは1162.252.106[.]245から8300程度のアクセスで、どちらもsshへ向いてましたが時間帯は被っておらず。ロシアからはhttpへのアクセスですがこれもピークは他のアクセスとは被ってはおらず128.0.31[.]26から2000程度のアクセスでした。現地時間的にはアメリカとロシアからは夕方頃、中国は明け方頃ですね 。


3つの国からのアクセスは特に珍しくもなかったのですが、時間帯が近いのは珍しいと思います。(少なくとも私の環境だと) ここ最近だとMiraiが怪しいのかと思いますが、httpは確かmiraiの守備範囲ではなかった気がするので(IoTでは無い)、仮にmiraiのbotnetが動き出していたのなら、ロシアのは別攻撃ですね。そうすると中国とアメリカからのピーク時間が微妙に近いのもなんとなく腑に落ちるかもしれない?

ruby チュートリアル7章 メモ

サイトにデバッグ情報表示

下のコードをレイアウトのコードに挿入することで、描画されるページの状態を把握するのに役立つ情報をサイトに表示することができる。if~文は開発環境でのみデバッグ情報を表示するよう指定している。

#app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>


Sassのミックスイン

ミックスインを使用することでcssルールのグループをパッケージ化して簡単に複数要素に対して使用することができる。例えば、要素のサイズや見た目をいろんな場所で統一したい場合、あらかじめレイアウトをパッケージ化しておけば、そのパッケージをインポートするだけで、レイアウトを適用することできます。

#app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}
.
.
.
/* miscellaneous */

.debug_dump {
  clear: both;
  float: left;
  width: 100%;
  margin-top: 45px;
  @include box_sizing;
}


RESTアーキテクチャの適用

データの作成、表示、更新、削除をリソースとして扱うこと。これらに対応する4つの操作(GET,POST,PATCH,DELETE)を各アクションに割り当てる必要がある。RailsのREST機能が有効だと、GETリクエストは自動的にshowアクションとして扱われる。この場合、リソースへの参照はリソース名とユニークIDをURIとして使用することになり、id=1のユーザの参照はusers/1となる。これをルーティングに設定するには下のようにする。

#config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
 . . .
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
  resources :users
end


UsersリソースをRESTfulなアクションにした場合以下のようになる。

f:id:thinline196:20181106163627p:plain

debuggerメソッド

byebug gemのdebuggerメソッド。下のようにコード中に挟むとサーバを立ち上げたコンソール画面から、その場所のデバッグを行うことができる。

#app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
    debugger
  end

  def new
  end
end


Gravatar

プロフィール写真をアップロードして、指定したメールアドレスと関連付けることができ、画像パスを生成するだけでその画像を引っ張ってこれる。チュートリアルでは以下のようなヘルパーを作成して画像を引っ張ってきていたが、このチュートリアルに最適かは疑問。後々使うのであればまた別ですが。

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


オプション引数

メソッドを定義した際に予めデフォルト値を与えてやれば、引数に値を指定しなかった場合自動でデフォルトの値が使用される。もちろん値を渡した場合はその値が優先される。これをオプション引数といい下のように定義する。

  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


またRuby2.0から導入されたキーワード引数を使用することで同じものをより短く実装が可能。

  def gravatar_for(user, size: 80)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end


これのメソッドの呼び出し方は下のようにする。

<%gravatar_for @user%>  #デフォルトの80が適用される
<%gravatar_for @user, size:200%> #引数で渡した200がsizeに適用される。


form_forメソッド

form_forメソッドはActiveRecordのオブジェクトを取り込んで、そのオブジェクトの属性を使用してフォームを構築してくれる。

<%= form_for(@user) do |f| %>
  .
  .
<% end %>


fオブジェクトはHTMLフォームに対応するメソッドが呼び出されると、@userの属性を設定できる特別なHTMLを返す。labelの:nameシンボルは@userの属性になくてもエラーは出ない。シンボル名がそのままラベルに変換される。text_fieldの方はuserの属性に含まれるものをシンボルとして渡さないとエラーになる。

<%= f.label :name %>
<%= f.text_field :name %>

上を実行すると下のように展開される。

  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />


text_fieldを使用すると一般的な入力フォームが、password_fieldを使用すると文字隠しが適用される入力フォームが生成されるなど、柔軟に対応できていることもわかります。
form自身のhtmlは以下のように生成される。

<form action="/users" class="new_user" id="new_user" method="post">


form_forは生成時に読み込んだ@userがUserクラスであり新しいUserであることを自動で認識し、actionとmethodを生成する。RESTfulな設計なのでpostはcreateに対応しており、Userクラスのcreateアクションが実行されるといった流れ。classとidはformヘルパーに対して特に直接的な結びつきは持たない。

Strong Parameters

user情報入力後、その値を全てそのままUser生成に使用してしまったら、意図しないパラメータも更新されてしまう可能性がある。そのため、予め設定したものだけのみ、入力情報から取り出せるよにするべきである。

@user = User.new(params[:user]) #:userに格納された全ての情報を使用しようとする
@user = User.new(params.require(:user).permit(:name, :email, :password, :password_confirmation)) #許可した値だけ取り出される。


例えばcreateアクション時に簡単に呼び出せるようなメソッドをprivateに実装しておくことが考えられる。

  def create
    @user = User.new(user_params)
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end


app/views/sharedディレクト

複数のビューで使用されるパーシャルはこの下に保存される。(習慣なので自動でやるわけではない)

pluralize

英語テキスト専用のヘルパー。一つ目の引数の値(数字)によって二つ目の文字を複数形に変更してくれる。

redirect_to

redirect_to @userredirect_to user_url(@user)と等価。これは以下の挙動と同じだが、あくまで挙動が同じだけ。route.rbでresourcesを指定していないものに対しては使用できない(user_urlがresourcesを使用した場合のみ利用可能になるから)

redirect_to "/users/#{@user.id}" # 条件付き(後述)
or
redirect_to user_url(id: @user.to_param)
or
redirect_to user_url(id: @user.id)


flash

ページが切り替わった時にユーザに向けて表示するメッセージを生成管理してくれるメソッド。コントローラのアクション定義の際にflashにシンボルと紐づけてメッセージを格納すると、次のアクションするまでその値を保持してくれる。(createアクション内で格納したメッセージは次のアクションで飛んだページでは残っているが、もう一度アクションをすると消える)

【Rails入門】flashの使い方まとめ | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト
なお、次のアクションにも残さなかったり、逆に永続的に保持させることも可能。

content_tag

railsのヘルパー。content_tag(タグの名前)といった形で使用し、タグを動的に生成してくれる。チュートリアルではflash用のhtmlを見やすいように書き換えるために使用した。

#before
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>

#after
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>


今回のintegrationテストに関して

今回のサインアップのテストはチュートリアル上ではIntegrationテスト(featureテスト)の位置付けで行われていた。しかし、その内容はコントローラのテストとした方がよい可能性がある。
と言うのも、featureテストはcontrollerを挟んだviewをテストするイメージであり、今回のテストは完全にcontrollerのメソッド内で完結している処理だからである。
f:id:thinline196:20181109125000p:plain


では、viewのテストとは?一般にはviewのテストは書かないという風潮があるかもしれない。。何かを表示するにはほぼ必然的にcontrollerを経由し、それはつまりfeatureテストになる。(getしたページのタイトルが予期したものか等) またそもそもページに何のコンテンツが表示されているかのテストを始めると、保守コストが高すぎてやってられるか〜!となる話もあったりなかったり。
(念のためですが、書いている方もしっかりいらっしゃるので決して必要ないと言うことではありませんよ=> RSpec使ってERBに対するspecをどう書くか? - Qiita)


ちなみに、viewのヘルパーのテストは行う可能性が十分にあるので注意。

t-pot 観察 #5 イギリスからのpop3ブルーフォース

2018/11/6 12:00~12:30

イギリスからpop3(110番)へのブルートフォース攻撃honeytrapで検知。大した量じゃないけれど、通常時は観測しないイベントだったのでここに書いておきます。
f:id:thinline196:20181107015220p:plain f:id:thinline196:20181107015238p:plain f:id:thinline196:20181107015241p:plain

Suricataでのアラート名はET SCAN Rapid POP3 Connections - Possible Brute Force Attackで、攻撃元IPアドレス51.68.174[.]15でした。下は24時間分のt-potの検知グラフですが、全体へのアクセスに対してここだけ特徴的に飛び出しているのがわかります。 f:id:thinline196:20181107015808p:plain

dionaea virustotalを有効にする

ふとこの記事が何故か下書きで眠っていました。書きかけだったのでしょうか、、?とりあえず公開しておきます。

導入


Dionaeaのバージョンは0.6.0です。

/opt/dionaea/etc/dionaea/ihandlers-available/virustotal.yamlを/opt/dionaea/etc/dionaea/ihandlers-enabled/virustotal.yamlへコピーする。

$ cp /opt/dionaea/etc/dionaea/ihandlers-available/virustotal.yaml /opt/dionaea/etc/dionaea/ihandlers-enabled/virustotal.yaml

ihandlers-enabled/virustotal.yamlのapikeyの部分に自分のvirustotalのapikeyを打ち込む

- name: virustotal
  config:
    # grab it from your virustotal account at My account -> Inbox -> Public API
    apikey: "*********************************************"
    file: "/opt/dionaea/var/dionaea/vtcache.sqlite"

virustotalapiキーは、自分のvirustotalアカウントを作った後、自分のSettingsのページに飛べばあります。終わったらdionaeaの再起動をかけます。

するとyamlファイル通り/opt/dionaea/var/dionaea/vtcache.sqliteファイルが生成されているかと思います。

一応、sqlite3コマンドで開くことができるので、時間をおいてから中身を見てみるのも良いですね。

ruby チュートリアル6章 メモ

Model

データモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ。データベースとのやり取りを行うライブラリはActiveRecordで、データオブジェクトの生成・保存・検索のメソッドをもつ。よって、SQL文を書かない。マイグレーション機能はデータの定義をRubyでできるようにしてくれている。

モデルの生成

$ rails generate model User name:string email:string

コントローラ名には複数形を使って、モデル名には単数形を使用する。これによって生成されるテーブル名は複数形 (users) になる。上のコマンドで以下のマイグレーションファイルが生成される。タイムスタンプを名前に使用する事で多人数での開発でもコンフリクトを起こさず順番を保持することができる。t.timestampsは特別なコマンドで、created_atとupdated_atという2つの「マジックカラム (Magic Columns)」を作成する。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプの役割。また、記述されていないが、id属性が自動で生成される。

# db/migrate/[timestamp]_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

マイグレーションは以下のコマンドで実行される。

$ bin/rails db:migrate

ロールバック

マイグレーションロールバックが容易にできる。

$ rails db:rollback

マイグレーションファイルのchangeメソッドはdrop_tablecreate_tableメソッドがそれぞれ対応していることを理解しており、マイグレーションファイルを呼ぶだけでロールバックが可能になっている。一部カラム削除等の操作はchangeメソッドを使用せず、updownメソッドを自分で別々に定義する必要がある。

schema.rb

schema.rbにはマイグレートされたものが記録されていく。ロールバックすると、その分消える。

モデルの生成と保存

$u = User.new(name:"hoge") # hoge以外の値がnil
$u.save #idとタイムスタンプに値が入る

#上2行と同じ
$ User.create(name:"hoge")

validation

モデルのファイルに書く。例えば上のUserモデルであれば以下のようにする。

#app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
end

validatesはメソッドなので以下のようにカッコを使用してもかける。

class User < ApplicationRecord
  validates(:name, presence: true)
end

この検証をコンソールで確認することも可能。エラー内容を確認することもできる

$ rails console --sandbox
>> user = User.new(name: "", email: "mhartl@example.com")
>> user.valid?
=> false
>> user.errors.full_messages
=> ["Name can't be blank"]

インスタンス変数

Minitestではsetup, RSpecではbeforeあたりでテスト毎の実行前に呼びたい共通コードを書く。

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

頭に@をつけたインスタンス変数を使用すれば、このコード内でどこからでも参照可能になる。@をつけなければ、例えばit内で呼ぶことはできない。

DBレベルでの一意性

一意性を保ちたいものに対して、インデックスを追加し、インデックスの一意性を保つように変更する。

インデックスの追加

インデックスを追加することによって、find等の検索のスピードも早くなる。

$ bin/rails generate migration add_index_to_users_email

上のコードでファイルは生成されるが中身は無い状態なので自分で追加する必要がある。

class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true #trueにすることによって
  end
end

追記したら、以下のコマンドで反映。

$ bin/rails db:migrate

ActiveRecordのコールバックメソッド

ある特定の時点で呼ばれるメソッド。例えば、before_saveを使用すればユーザをデータベースに保存する前に特定の処理を挟むことができる。

class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

ハッシュ化されたパスワード

has_secure_passwordメソッドを呼び出す。ハッシュ関数を利用するにはgem 'bcrypt'のジェムを入れておくこと。利用するには以下のように追記すればok。

class User < ApplicationRecord
  .
  .
  .
  has_secure_password
end

これにより以下の利点がある。

  • セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
  • 2つのペアの仮想的な属性 (passwordとpassword_confirmation) が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される 。
  • authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。


よって、モデルにpassword_digestというカラムを追加する必要がある。

$ rails generate migration add_password_digest_to_users password_digest:string

末尾にto_usersとつけることで、自動でadd_password_digest_to_usersというマイグレーションファイルを生成してくれる。以下がそれ。

class AddPasswordDigestToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :password_digest, :string
  end
end

最後にいつも通りマイグレーションコマンドで変更を反映してあげればok。

T-pot観察 #4 2018/11/5 5060へのアクセス増加

全く書くことがなく気がついたらこんなに日が空いてしまいました。(パソコン修理とかしてたしね)
本日珍しくdionaeaの検知数がCowrieを超えたので報告しておきます。  

2018/11/5 00:30~

f:id:thinline196:20181106002930p:plain
上のグラフはここ一週間のt-potへの総アクセスを集計したものです。11/5の深夜0:30よりdionaeaへのアクセスが急激に増加しているのがわかります。 全てオランダからです。直近24時間分のグラフにまとめるとこのようになります。

f:id:thinline196:20181106003152p:plainf:id:thinline196:20181106003156p:plain

後で追記しますが、今の所ここらで集中アクセスが終わりそうな兆しも見えますね。もしそうであればちょうど24時間続いたことになります。 
追記(11/7):一日置いてみましたが、あれ以降アクセス数は再び平常値に戻りました。

下のグラフはポート番号別のアクセス記録になりますが、一目で5060番が狙われていたのがわかりますね。 

f:id:thinline196:20181106003502p:plain


使用されたIPアドレスも5060番に絞った時、特徴が出ますね。同じサービス元を利用した攻撃のようで、27.49.231[.]~系が使われていますね。一応アクセス数は合計42569回前後という形で収まりそうです。

f:id:thinline196:20181106003728p:plain



ポート5060

少々古いですが以下の記事を見つけました。5060/UDPSIP(Session Initiation Protocol)で使用されているそうです。
SIP関連機器を悪用する攻撃に注意--5060/UDPポートをスキャン、辞書攻撃 - CNET Japan

SIPは2つ以上のクライアント間でセッションを確立するために使用されるプロトコルで、wikiにはIP電話が例に挙げられていました。この記事の攻撃ではSIPサーバ等の関連機器からアカウント情報を窃盗し、そのアカウントを利用して不正な電話の発信に利用しているとのことでした。


普段見かけないアクセスがあるとワクワクしますね。以上。