気をつけないと、Ruby on Railsで勝手にDBが書き換えられちゃう?

Posted on

 ふと思いついたネタを。単純なネタですがこの点に関する注意をしている記事を見た記憶がないので。

概要

 scaffoldは気をつけて使わないと、ユーザから上書きされてはいけないフィールドが書き換えられる恐れあり。scaffoldは便利だけど、そのままではすこし危険

scaffoldは便利だけど…

 とりあえずRuby on Railsが有名だと思うのでRuby on Rails(バージョン2.2.3)で書きますが、grailsでも同様の問題がありました。

 さて、まずはscaffoldしないと話になりません:

% rails test
% cd test
% script/generate scaffold User name:string password:string is_admin:boolean

 としてモデル・コントローラ・ビューを自動生成できますよね。このうち、おそらく管理者かどうかをチェックするためにつけたと思われる「is_admin」が、そのままだと一般ユーザから書き換え放題になります。

 どうやるかって?簡単です。newアクションのviewはこうなりますよね。

20100918_01.png

 この時の、生成されるhtmlのうち:

<p>
<label for="user_is_admin">Is admin</label><br />
<input id="user_is_admin" name="user[is_admin]" type="checkbox" value="1" /><input name="user[is_admin]" type="hidden" value="0" />
</p>

 のチェックボックスの<input>とその後のhiddenな<input>が並んだところをコピーして控えておきます。この部分は、このアプリだけでなく他のアプリを攻撃する際にも使い回せます。(name属性だけ書き換えて再利用します。)

 さて。実際のアプリにおいて、is_adminは管理者が設定するフラグであって、ユーザ側から選択できてはマズいですから、削除すると思います。こんな感じで:

<h1>New user</h1>
<% form_for(@user) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :password %><br />
<%= f.text_field :password %>
</p>
//-------------ここから削除------------------
<p>
<%= f.label :is_admin %><br />
<%= f.check_box :is_admin %>
</p>
//-------------ここまで削除------------------
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', users_path %> 

 そうするとこんな感じの画面になって、一見ユーザからは管理人であるかどうかは選択できなくなりますよね:

20100918_02.png

 実際に、このまま普通に作っても、「Is admin: 」となって、falseになっていることが分かります:

20100918_08.png

 ところがどっこい、動的にHTMLを書き換えてさっきメモした<input>のコードを動的にフォームの中にコピーすると管理者になれちゃいます!w

 FirefoxのFirebugや、Chromeのデベロッパーツールを使えば簡単にHTMLを動的に書き換えることできます。プラグインをインストールする必要が無くて楽なChromeで試してみます。

 デベロッパーメニューはこちらにあります†1: 

20100918_03.png

 次に、動的に書き換えるためにformのところをクリックして、「Edit as HTML」を選びます:

20100918_04.png

 すると編集できるようになるので、先程の<input>のコードを<form>のすぐ次くらいにでも張り付けます:

20100918_05.png

 これで終わりです。適当にエディット領域外をクリックして編集を終了すると…

20100918_06.png

 チェックボックスが出てきます。あとはご想像のとおり。これを黒くチェックして、適当にIDとパスワードを入力すると…

20100918_07.png

 はい、管理者ユーザになれました。

 まあたしかに、is_adminという名前が分からないと書き換えはできないので、ソースコードを公開してないアプリなら即危ないというわけではありませんが、セキュリティ的にはやっぱりマズイと思います。特にオープンソースアプリ…大丈夫でしょうか?

対策

Userコントローラのcreateアクションのうち、

@user = User.new(params[:user])

 この部分を、

@user = User.new(params[:user])
@user.is_admin = false

 にするとか(今確認したらredmineだとこうしてる)、

@user = User.new(:name=> params[:user][:name], :password=> params[:user][:password])

 として、そのアクションで何を書き換えるのかをはっきりさせる、とかでしょうか。

なんだかなあ

 すごく単純なミスですが、実を言うと今まで全く気づかなかったので今回掲載しました^^;UFO大好き霊子さんにもこのミスがあります。証明写真作成工房は修正しておきました。

 出力されるformの中身を自動的にチェックして、formの中身以外のがリクエストに混入してたらアウトって出来ないのかなあ?その方がrailsっぽいよね。Ajaxで動的にフォームを生成するときは、そのための設定をするとか。

 ソースコード読んだら修正できるだろうか?

  • †1: Dev channelなので、多少画面はお手持ちのChromeと異なる可能性があります

磯野ー!ワードサラダ作ってアフィリエイト付スパムブログしようぜー!

Posted on

 この記事は「[痛デバドラ] /dev/louise_love作ってみた [Linux]」の解説記事です。内部でルイズコピペを自動生成するために使った、ワードサラダの技術について解説しています。

ワードサラダ??

 みなさんはワードサラダってご存知でしょうか。サンプルとしてこのBlogを上げておきます。なんだか、日本語が変じゃありませんか?ところどころ読めるけど、なんだか変なような…。

着目すべきは、「核抑止力」を「強化」するという表現である。。

これについては核武装大腸がんの予防論を参照。。

 核武装大腸がん???なんじゃそら。

 こういった、ぱっと見日本語に見えるんだけど、ちゃんと読むと意味不明、という文章を「ワードサラダ」と呼びます。†1スパマーがプログラムで自動生成して、スパム広告用のBlogに利用しているみたいです。†2

 さて、こんな文章どうやれば作れるんでしょう?ワードサラダの作り方を書いてあるサイトは結構あるんですが、ちゃんと動くソースコードが貼ってあるサイトはなかなかないんで、実際にやってみましょう。

実際にRubyで作ってみよう

 ソースコードは「[痛デバドラ] /dev/louise_love作ってみた [Linux]」の「辞書作成ユーティリティ」の中にありますので、DLしてみてください。githubでも公開していて、Webから見るならこちらの方が便利かもしれません。

 なお、利用にはYahoo!デベロッパーネットワークのアプリケーションIDが必要です(OAuthは利用しません)。取得したIDは、parser.rb冒頭の「APP_ID」にセットしてください。

大まかな流れ

  1. 文章を形態素解析
  2. マルコフ連鎖のための辞書を作成
  3. 辞書に基づいて実際にマルコフ連鎖して文章を生成

形態素解析

 形態素解析とは、文章を解析して、単語にわけ、それぞれの品詞についても調べる事です。受験の時に古文で同じことを手動でさせられましたが(涙)、受験でも何でもありませんのでコンピュータに自動で解析させましょう。

 形態素解析のソフトにはMeCab等のローカルのライブラリを使うのが定石みたいですが、インストールがめんどくさいので、Yahoo!の構文解析WebAPIを使おうと思います。Web経由なんであまり高速に大量のデータは処理はできませんが、まあ実験には十分でしょう。

 さらに係り受け解析の結果も使うとそれっぽい日本語が作れるようですが、目的がルイズコピペの無限生成なんで、そこまでやらなくてもそれっぽい文章ができると思います。係受け解析の結果も利用するうまい文章作成ソフトができたら/あったら教えてください。

 以下、実際に行なってるコードです。「libparser.rb」内にあります。

class YahooParser < Parser
	API_SERVER = 'jlp.yahooapis.jp'
	API_URL = '/MAService/V1/parse';
	BOM_UTF8 = [0xef, 0xbb, 0xbf].pack("c3")
	def initialize(api_id = APP_ID)
		@data = "appid=#{api_id}&results=ma&sentence="
	end
	def parse(text)
		http = Net::HTTP.new(API_SERVER);
		resp = http.post(API_URL,@data+URI.encode("#{text}"));
		if resp.code != '200'
			raise "Yahoo! Japan Parser unavailable. Return Code:#{resp.code}"
		end
		body = resp.body;
		doc = REXML::Document.new(body).elements['ResultSet/ma_result/word_list/'];
		text = Text.new();
		doc.elements.each('word') {|item|
			surface = item.elements['surface'].text if item.elements['surface'] != nil
			next if surface.empty? || surface == BOM_UTF8
			reading = item.elements['reading'].text if item.elements['reading'] != nil
			pos = item.elements['pos'].text if item.elements['pos'] != nil
			baseform = item.elements['baseform'].text if item.elements['baseform'] != nil
			text << Word.new(surface,reading,pos,baseform)
		}
		return text;
	end
end

 概ね、’net/http’を使ってAPIのURLにPOSTで文章を送り、結果(XMLで帰ってきます)を’rexml/document’で読み込んで、形態素解析の結果を得るだけです。あえて上げれば、”elements[‘ResultSet/ma_result/word_list/’]”として、DOMを操作する手間を少し省くのがコツ…?

 それぞれの要素の意味は、

  • surface:字面です。「桃色」だったら「桃色」
  • reading:読み方です。「桃色」だったら「ももいろ」
  • pos:品詞です。「桃色」だったら「名詞」
  • baseform:動詞だった場合の”終止形”です。「届け」だったら「届く」。でも、今回の文章生成には使ってません。

マルコフ連鎖のための辞書を作成

 さて、以上の形態素解析は自然言語をコンピュータで扱うなら定番の処理で、ワードサラダ生成の処理はここからが本番です。”markov.rb”内にあります。

class Dic
	def initialize()
		@dic = Hash.new();
		@dic2 = Hash.new();
		@size = 0;
	end
	def add(text)
		if @first == nil
			@first = text[0]
			@second = text[1]
		end
		#二階
		for i in 0..text.size-2
			word = text[i]
			if @dic[word.reading] == nil
				@dic[word.reading] = Array.new
			end
			@dic[word.reading] << text[i+1]
			@size += 1;
		end
		#三階
		for i in 0..text.size-3
			word = text[i]
			word2 = text[i+1]
			if @dic2[word.reading] == nil
				@dic2[word.reading] = Hash.new
			end
			if @dic2[word.reading][word2.reading] == nil
				@dic2[word.reading][word2.reading] = Array.new
			end
			@dic2[word.reading][word2.reading] << text[i+2]
		end
	end
=begin
・・・
(略)
・・・
=end
end

 簡単にいえば、「ある単語の後にはどんな単語が来るか」というインデックスをつくっています。

20100907_louise01.png

 こういう感じの対応表をハッシュと配列を使って記録しています。ハッシュのキーには字面ではなくよみがなを使っていて、これはなんとなくです。なお、この図とは違って同じ単語も何度でも登録していて、一応後で文章を生成したときに、「ルイズ」の後の単語の出現頻度が元の文章と同じになるように調整しています。

 さらに、これだけだと文章があまりにも不安定になるので、「ルイズ/へ」→「届け」、という、ハッシュのキーが二段階になっている辞書もつくっています。これが「三階のマルコフ連鎖」と書かれているところです。

辞書に基づいて実際にマルコフ連鎖して文章を生成

 というわけで、作った辞書を使って実際に文章を生成してみましょう。同じくmarkov.rb内にあります。

class Dic
=begin
・・・
(略)
・・・
=end
	def next(word = nil,word2 = nil)
		if word != nil && word2 != nil
			# 三階の連鎖
			array = @dic2[word.reading][word2.reading];
			if array == nil #辞書に入っていない場合は、二階のマルコフ連鎖を行う
				array = @dic[word2.reading];
			end
			return array[rand(array.length)];
		end
		# 最初
		if word == nil && word2 == nil
			return @first
		end
		# どちらかnullでない方
		left = (word == nil ? word2 : word);
		# 次
		if left == @first
			return @second
		end
		# どっちかが足りない=二階の連鎖を行わざるをえない
		array = @dic[left.reading];
		return array[rand(array.length)];
	end
end

 このdic#next()に、ふたつ前の単語といっこ前の単語を渡すと、今度出力されるべき単語が返されます。

 上で作った辞書からランダムに選ぶだけです。三階のための辞書は場合によっては組み合わせによって該当する辞書が空の場合があるので、その場合は二階の辞書を使います。(二階の辞書は、文章の最後の単語以外は必ず対応する単語があります)

実際につくった文章

% ruby markov.rb <文章>

 とすると実際に文章が生成されます。copipe/内にいくつか文章がすでにあります。

ルイズコピペ

ルイズ!ルイズ!ルイズ・フランソワーズたんの桃色ブロンドの髪をクンカクンカしたいお!モフモフ!髪髪モフモフ!カリカリモフモフ…きゅんきゅい!!!!にゃあああああああ!かわいい!ルイズ!ルイズぅぅうううわぁああああああああああああああああああああああ!!

小説11巻のルイズちゃんは現実じゃないんだねっ!

この!ちきしょ!やめてやる!!

あっあんああっああんあアン様ぁあああ!!いやぁああああああああああああん!!!あぁあ!!俺の想いよルイズへ届け!!

あっあんああっああんあアン様ぁあ!!あ…小説もアニメもよく考えたら…

ルイズちゃんは現実じゃない?にゃああああ…あっあっー!あぁああああああ!!!

あぁ!クンカクンカ!

ルイズルイズぅううぁわぁああああああああああああ!!

スーハースーハー!

吉野家

そんな事より1よ、ボケが。

150円。

お前は本当につゆだくで、それに大盛りギョク(玉子)。これ最強。

そしたらなんか人がめちゃくちゃいっぱいで座れないんです。吉野家。

よーしパパ特盛頼んじゃうぞー、とか言ってるの。もう見てらんない。

そこでまたぶち切れですよ。ボケが。

ねぎだくってなさいってこった。

素人にはお薦め出来ない。

Uの字テーブルの向かいに座った奴といつ喧嘩が始まってもおかしくない、

刺すか刺されるか、そんな雰囲気がいいんじゃねーか。おめでてーな。

お前は本当につゆだくを食いたいのかと。

そこでまたぶち切れですよ。ボケが。

吉野家ってのはな、もっと殺伐としてるべきなんだよ。ボケが。

得意げな顔して何が、つゆだくって言いたいだけ

ステーキを食べたくなったので

ステーキを食べたくなったのでステーキの話を書きますが、とある日僕が気づいたのです!

やっぱりそうだ、確信した!僕の好きな食べ物は、ステーキより高いステーキソースさえあれば、そもそも本体は肉でステーキ。ちょっと新ワードですが。

それ以来、ステーキソースをかけて食べるのでは!?

ってことで、食べる!うわーこれもうまい!!

やっぱりそうだ、確信した!僕の好きな食べ物は、ステーキより高いステーキソースをかけて食べるのでは!?

と、盛り上がったところで本題に行きます。

結果から言えば牛となんら変わりませんでしたYO!

ということで、食べる!うわーこれもうまい!!

という点です。

それ以来、ステーキソースでステーキを食べておりました。

しかし!ようやく今日気づいたのです!

と、

 割とサクサク読めて、でも意味がさっぱり分からない感覚は再現できましたかね?そういえば、難しい本読んだ時とかもそんな気分になるよね…。

 はっ!哲学の論文をこれに掛けたら誰も分からないのでは!?

  • †1: 他の例としてこのサイトを挙げておきます。
  • †2: このBlogだと広告が貼ってませんが…一体なんのために作ったんだ、このBlog。

「一応動く」Linux用デバイスドライバをつくろう

Posted on

 これは「[痛デバドラ] /dev/louise_love作ってみた [Linux]」の解説記事です。

 今回は「Linux2.6系(今回は2.6.32)からドライバとして認識されるには最低限何をしないといけないのか」をまとめてみます。

 えらく貧相な話ですが、最初のとっかかりとしては需要のある話かなあ、と。というかですね、自分自身が欲しかったのですよ。

キャラクタ型とブロック型デバイス

 Linuxのデバイスは、おおよそ二つの種類に分けられる…らしいです。ブロック型というのは固定長の長さずつでしか入出力出来ないデバイスで、キャラクタ型はそれ以外、だそうです。とりあえず、The Linux Kernel APIを見ると別々にAPIが定義されてるので、OSからもかなり別物として定義されているようです。

 今回は、とりあえず情報も多く楽なキャラクタ型デバイスを作ります。

サンプルソースコード

[痛デバドラ] /dev/louise_love作ってみた [Linux]」のソースコードを使って解説します。githubでも公開していて、Webから見るならこちらの方が便利かもしれません。

デバイスドライバに最低限必要な関数

 カーネルモジュールとして名乗るために、名前やライセンスを名乗る必要があります。

MODULE_AUTHOR("PSI");
MODULE_DESCRIPTION("\"I love Louise tan!\" MODULE");
MODULE_LICENSE("GPL");

 なお、”MODULE_LICENSE(“GPL”);”ですが、これがオープンソース系ライセンスでないととロードした時に

Warning: loading /lib/modules/..../***.o will taint the kernel: non-GPL License - Proprietary

という感じのワーニングメッセージが出るそうですが、Linuxantという会社は

MODULE_LICENSE("GPL\0for files in the \"GPL\" directory; for others, only LICENSE file applies");

 としてその汚染メッセージの表示を回避したとか…。よう考えるわ。

 さて。まず、初期化と終了のための関数が必要です。初期化はinit_module、終了処理はcleanup_moduleという関数で行ってください。どうしても別の名前が良い時は、

module_init( 初期化する関数名 );
module_exit( 終了処理をする関数名 );

 というのを関数外に書くことで代用できます。

初期化処理

int  init_module( void ){
	if ( register_chrdev( 0x0721, "louise_love", &louise_fops ) ) {
		printk( KERN_INFO "louise_love : louise chan ha genjitsu ja nai!?\n" );
		return -EBUSY;
	}
	buildDictionary();//ルイズコピペ表示のための辞書作成処理
	printk( KERN_INFO "louise_love : kunka kunka.\n" );
	return 0;
}

 0を返すと初期化に成功したとカーネルが判断し、ロードされます。register_chrdevでデバイスとしてカーネルに登録でき、この時の0x0721はメジャー番号と言って、あとで/dev/louise_loveとドライバの紐付けを行う際に必要な番号、”louise_love”はデバイスドライバの名前(lsmodしたときに見れるやつ)、louise_fopsはfile_operations構造体で、カーネルからデバイスを操作するときに使われる関数で、あとで定義します。

 register_chrdevの詳しい説明はこちらで。

 printkというのが出てきますが、これはprintfのカーネル版で、dmesgした時等に表示されます。フォーマットストリングの前にはKERN_INFO等を付けないと表示されません。詳しくはこちら。

終了処理

void cleanup_module( void ){
	unregister_chrdev( 0x0721, "louise_love" );
	printk( KERN_INFO "louise_love : Harukeginia no louise he todoke !!\n" );
}

 unregister_chrdevでアンロードするだけです。特に説明しません。詳しくはこちら

デバイス操作関数

 カーネルはregister_chrdevに渡すfile_operations構造体に記された関数を通してデバイスを操作します。デバイスドライバを作るには、そのうち最低限以下を実装しなければなりません。

  • open
  • release
  • readかwrite

 ここを見てプロトタイプの通りに実装してください。

 open( struct inode* inode, struct file* filp )のinodeはとりあえず無視していいみたいで、filpのfilp->private_dataに自分でkmallocを使って確保したデータを渡すことで、このデータも使ってwriteやreadが制御できます。

 あとは多分ソース見れば分かるかな…?

デバイスドライバをコンパイルするための最低限のMakefile

 ”-DMODULE -D__KERNEL__”を付ければ普通にコンパイルできるよ!って話もあったんですが出来ませんでした。-Iとかも色々いじったりもしたんですが通りません。昔の話みたいです。

 というわけで、こちらのサイトのMakefileを流用しましょう。

 ファイル名はMakefile(先頭大文字)じゃないとエラーが出るみたいです。

 今回は、このMakefileにこんな行も追加してインストール・アンインストールもできるようになっています。

install: all
	sudo mknod /dev/louise_love c 0x0721 0
	sudo chmod 0666 /dev/louise_love
	sudo insmod louise_love.ko

uninstall:
	sudo rmmod louise_love
	sudo rm -f /dev/louise_love

 mknodで特殊ファイルを作成&0x0721というメジャー番号を使って作成しています。cはキャラクタデバイスで、0x0721はメジャー番号で、register_chrdevしたときの数字とあわせてください。0(マイナー番号)はちょっとよくわかりまへん…。マニュアルはこちら

 chmod 666はただたんに、誰でも読み書きできるようにしているだけです。

 特殊ファイルを作ったあとにinsmodして作ったカーネルモジュールを読み込んで、/dev/louise_loveの制御を作ったドライバに任せています。

 uninstallでrmmod <モジュール名>することでドライバをアンロード、スペシャルファイルは普通にrmdirで削除できます。

 今回分かったんですが、bashでのif文はこういう構造なんですね。

if <条件:[ -e /tmp/...]など>; ←文の句切れ
then コマンド1; ←then~コマンドまでで一文
else コマンド2; 同様 else~コマンドで一文
fi

 いやーこれに気づかずに結構時間が掛かりました…。thenとコマンド1のあいだ、elseとコマンド2のあいだに;をいれるとエラーがでちゃうんだもん。

さらなる情報を得るには(参考サイト一覧)

 デバイスドライバのインターフェーイスは割と変わるみたいなので、他の記事を参考にする場合はバージョン情報や書かれた月日は参考にしたほうがいいです。

 ちなみにこの記事はカーネル2.6.32を対象に書いています。

[痛デバドラ] /dev/louise_love作ってみた [Linux]

Posted on

全自動無限ルイズコピペ生成器搭載!

これは何?

Linux2.6(32bit/64bit)用デバイスドライバです(2.6.32で動作確認しています)。インストールすると/dev/louise_loveが現れ、読み出すとルイズコピペをマルコフ連鎖アルゴリズムに基づいて無限に自動生成し続けます。

ちなみに/dev/louise_loveへ書き込むとすべて書き込みを読み捨てますので、自分の書いたポエムも安心して読んでもらえます。

デモ

ソースコード

使い方

まずはダウンロードして解凍し、フォルダを移動しましょう。

% git clone https://github.com/ledyba/louise_love_driver.git
% cd louise_love_driver/driver

コンパイルするには、普通のプログラムのようにmakeしてください。

% make

デバイスドライバドライバと同時に、ユーザランドで動くアプリケーションとしてもコンパイルされているので、いきなりデバイスドライバとして組み込むのが不安な方は、安全なユーザランドで試すことができます。それには、

% make test
もしくは
% ./userland_louise_love

として実行してください。

では、実際にデバイスドライバとしてインストールしてみましょう。root権限が必ず必要です。

% sudo make install

おめてとうございます、インストールが完了しました。ルイズたんへの愛を得るにはこのようにしてください。

% cat /dev/louise_love

ゆっくりルイズたんへの愛を噛み締めたい場合は、moreコマンドなどを使うと良いでしょう。

ハードディスクやSSD、USBメモリをルイズたんへの愛で埋める場合には、次のようにしてください(注意:これをやるとHDDのデータが壊れます)。

% dd if=/dev/louise_love of=/dev/sda

/dev/louiseのパーミッションは666なので、書き込みも行うことができます。書き込んだデータはすべてスルーしますので、恥ずかしいポエムも安心して書き込むことができます。

% cat my_poem.txt > /dev/louise_love

昨今はアニメが多いですから、ルイズたんへの100年の恋も冷めてしまうかもしれません(一期やってたのもう4年くらい前だし)。でも大丈夫、アンインストールは次のように簡単にできます。

(インストール時に解凍したフォルダで)
% sudo make uninstall

今後の展開

簡単なソースではあるんで、デバドラやマルコフ連鎖の超簡単なサンプルとしてご活用ください。適当に書いたのでソースコードはむちゃくちゃ汚いですがw なお、/dev/yoshinoyaなども(つくろうと思えば)簡単にできます。

任意の文章に置き換えるには、ユーティリティをダウンロードして、

% ruby make_c_source.rb <文章ファイル> > c_source.txt

として出力結果のc_source.txtの中身を、louise_love.cの該当部分に適当に貼りつけてください。

ちなみに、

% ruby markov.rb <文章ファイル>

とするとrubyを使っていくつか文章を作ってくれます。

反省点

あまりカーネルランドをいじった感が無いのがちょっと…。8割のコードはAPIの名前さえ変えればユーザランドでも動きますし、そもそもデバイスドライバなのに、ハードウェアを触っていません。

解説記事

ちなみに:ユーラランドとカーネルランド時のパフォーマンスの違い

ユーザランド時

% ./userland_louise_love | dd of=/dev/null
1130544+0 記録始め
1130544+0 記録終わり
578838528 バイト (579 MB) コピー終了, 25.5481 s, 22.7 MB/s

カーネルランド時†1

% cat /dev/louise_love | dd of=/dev/null
3619712+0 記録始め
3619712+0 記録終わり
1853292544 バイト (1.9 GB) コピー終了, 25.9583 s, 71.4 MB/s

単純比較していいのかわからないけどさすがカーネルランド3倍はえーw

  • †1: 条件を近づける?ために、一回パイプ通してます。でもddで直接やっても変わらなかった。