-
-
Save pome1618/45b301ed41e4a255f457d816484a9f1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
badsns2019 作業記録 | |
【1.前提情報】 | |
参加ページ[1-1] | |
GitHub[1-2] | |
[1-1] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | |
[1-2] https://github.com/ommadawn46/badsns2019 | |
【2.BadSNSに対する調査】 | |
"BadSNS"で検索をしたところ、Bad SNSはにしむねあさんが開発した診断練習用Webアプリケーションであることがわかった。 | |
また、WEB+DB PRESS Vol.103にて詳細な解説があるらしい。書籍は購入していないが、目次から | |
・新規ユーザー登録画面に潜む脆弱性:OSコマンドインジェクション,ディレクトリトラバーサル | |
・ログイン画面とメイン画面に潜む脆弱性:SQLインジェクション,アクセス制御の不備,XSS | |
と記述されているため、そのあたりを探る。 | |
なお、あるページ[2-1]より使用ツールとしてBrakemanが記述されているため最初にBrakemanを試し、次にVulsやMetasploitを試す。 | |
追記:Vulsの結果とDockerfileより、Metasploitの優先度は下げる | |
[2-1] https://www.farend.co.jp/blog/2018/06/badsns/ | |
【3.dockerの準備】 | |
Dockerの実行環境は、最近Docker for Windowsがリリースされたためそちらを用いる。 | |
DockerImageのダウンロードはGitHubのReadmeの通り、 | |
$ docker pull ommadawn46/badsns2019 | |
実行は | |
$ docker run --privileged -d --rm -p 10080:80 --name badsns2019 ommadawn46/badsns2019 | |
である。また、動作はブラウザから | |
http://localhost:10080/ 及び http://localhost:10080/mailhog/ にアクセスすることで確認できる。 | |
【4.Brakemanの実行】 | |
$ docker pull ommadawn46/badsns2019 | |
$ docker run --privileged -d --rm -p 10080:80 --name badsns2019 ommadawn46/badsns2019 | |
にてDockerImageのダウンロード、実行を行う。その後、 | |
$ docker ps | |
にて実行中のコンテナIDを控える。 | |
$docker exec -it [コンテナID] bash | |
にて対象のbashに入る。ここからはBrakemanのインストールと実行だが、[4-1]を参考に | |
$ gem install brakeman | |
$ brakeman /var/www/app/sns/ | |
とすると脆弱性の診断を行うことができる。 | |
また、BrakemanのオプションとしてはAとw1があり、Aはすべての種類、w1はすべてのレベルを検査する。 | |
$ brakeman /var/www/app/sns/ > brakeman.txt | |
$ brakeman -A -w1 /var/www/app/sns/ > brakeman_A_w1.txt | |
としてリダイレクトして保存した。 | |
Security Warningsはオプション無しで14、オプションありで18となった。 | |
詳細はbrakeman_A_w1.txtを参照 | |
[4-1] https://blog-ja.sideci.com/entry/2017/07/06/110000 | |
【5.Vulsの使用】 | |
badsnsはUbuntu16の上に構成されているため、 | |
Vulsを[5-1] [5-2]を参考に、インストールする | |
[Install requirements] | |
$ apt-get update | |
$ apt -y install sqlite git debian-goodies gcc make wget | |
$ wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | |
$ tar -C /usr/local -xzf go1.14.2.linux-amd64.tar.gz | |
$ mkdir $HOME/go | |
$ apt-get install vim | |
$ vim /etc/profile.d/goenv.sh | |
export GOROOT=/usr/local/go | |
export GOPATH=$HOME/go | |
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin | |
$ source /etc/profile.d/goenv.sh | |
[Deploy go-cve-dictionary] | |
$ mkdir /var/log/vuls | |
$ chown root /var/log/vuls | |
$ chmod 700 /var/log/vuls | |
$ mkdir -p $GOPATH/src/github.com/kotakanbe | |
$ cd $GOPATH/src/github.com/kotakanbe | |
$ git clone https://github.com/kotakanbe/go-cve-dictionary.git | |
$ cd go-cve-dictionary | |
$ make install | |
$ cd $HOME | |
$ for i in `seq 2002 $(date +"%Y")`; do go-cve-dictionary fetchnvd -years $i; done | |
$ for i in `seq 1998 $(date +"%Y")`; do go-cve-dictionary fetchjvn -years $i; done | |
[Deploy goval-dictionary] | |
$ mkdir -p $GOPATH/src/github.com/kotakanbe | |
$ cd $GOPATH/src/github.com/kotakanbe | |
$ git clone https://github.com/kotakanbe/goval-dictionary.git | |
$ cd goval-dictionary | |
$ make install | |
$ ln -s $GOPATH/src/github.com/kotakanbe/goval-dictionary/oval.sqlite3 $HOME/oval.sqlite3 | |
$ goval-dictionary fetch-ubuntu 16 | |
[Deploy Vuls] | |
$ mkdir -p $GOPATH/src/github.com/future-architect | |
$ cd $GOPATH/src/github.com/future-architect | |
$ git clone https://github.com/future-architect/vuls.git | |
$ cd vuls | |
$ make install | |
[execute Vuls] | |
$ cd $HOME | |
$ vim config.toml | |
[servers] | |
[servers.localhost] | |
host = "localhost" | |
port = "local" | |
$ vuls configtest | |
$ vuls scan | |
$ vuls report -format-one-line-text | |
$ vuls report -format-full-text>vuls_format_full_text.txt | |
$ vuls report -format-full-text -ignore-unfixed > vuls_format_full_text_ignore_unfixed.txt | |
$ vuls tui | |
$ vuls tui -ignore-unfixed | |
[5-1] https://vuls.io/docs/ja/install-manually-centos.html | |
[5-2] https://www.virment.com/how-to-install-vuls-on-ubuntu/ | |
【6.Vulsのリモートスキャン】 | |
[6-1]からCentOS8.1.1911のISOイメージをダウンロードし、Hyper-Vマネージャで仮想マシンを新規作成しインストール。 | |
その後、先程と同様に公式サイトに従いVulsのインストールを行う。 | |
診断対象の対象の仮想マシンはそのままではコンテナの22番ポートにアクセスできないため、 | |
$ docker run --privileged -d --rm -p 10080:80 -p 10022:22 --name badsns2019 ommadawn46/badsns2019 | |
としてlocalhost:10022でコンテナの22番ポートにアクセスできるようにする。 | |
$ apt-get update | |
$ apt-get install openssh-server vim | |
$ /etc/init.d/ssh start | |
$ vim /etc/ssh/sshd_config | |
PermitRootLoginをyesへ | |
$ ssh localhost #ssh接続確認 | |
ここでホストからTera Termでsshの疎通確認 | |
また、Vuls用仮想マシンからも疎通確認 | |
疎通確認が取れたあとは鍵生成を行い、Tera Termで公開鍵を貼り付けて鍵認証を使えるようにする | |
Vuls用仮想マシンにて | |
$ ssh-keygen -t rsa | |
$ cat ~/.ssh/id_rsa.pub | |
としてコピー | |
診断対象にて | |
$ mkdir ~/.ssh | |
$ chmod 700 ~/.ssh | |
$ touch ~/.ssh/authorized_keys | |
$ chmod 600 ~/.ssh/authorized_keys | |
$ vim ~/.ssh/authorized_keys | |
として~/.ssh/authorized_keysにペースト | |
次にVuls用仮想マシンにて | |
$ ssh -p 10022 root@192.168.123.7 -i ~/.ssh/id_rsa | |
$ cd $HOME | |
$ cat config.toml | |
[servers] | |
[servers.ubuntu] | |
host = "192.168.123.7" | |
port = "10022" | |
user = "root" | |
keyPath = "/root/.ssh/id_rsa" | |
$ vuls configtest ubuntu | |
$ vuls scan ubuntu | |
今回はTUIで十分情報は閲覧できるので、 | |
$ vuls tui | |
$ vuls tui -ignore-unfixed | |
として情報を見る | |
また、ログはvuls_report_format_full_text.txtなどを参照。 | |
[6-1] https://www.centos.org/download/ | |
【7.Burp Suiteによる検証】 | |
Burp Suiteによってフォワードプロキシを構成し、パラメータの書き換えを行う | |
インストールは[7-1]からダウンロードし、インストールする。 | |
起動したら[Temporary project]から[Use Burp defaults]とする。 | |
[Proxy]タブから[Options]を選択し、デフォルトで設定されているProxy Listenersのポート番号を20080に変更する。 | |
また、PCのプロキシ設定からプロキシサーバを127.0.0.1:20080とする。 | |
この状態でbadsns2019へのアクセス経路は以下のようになる。 | |
ブラウザ---127.0.0.1:20080---[Burp Suite]---127.0.0.1:10080---badsns2019:80 | |
Brakemanにより発覚した脆弱性より、icon登録時に渡されるresize_max_pixelパラメータに数字ではなくコマンド(e.g.";touch aaa;")を渡すと、 | |
WorkingDirectory(/var/www/app/sns/)にて実行される。 | |
実際に新規ユーザ登録画面にてアイコンをアップロードする際に、Burp Suiteでresize_max_pixelパラメータに";touch aaa;"を渡したところ、 | |
/var/www/app/sns/にaaaが作成されていることを確認した。 | |
[7-1] https://portswigger.net/burp/releases/professional-community-2020-4 | |
【8.OSコマンドインジェクションの対策】 | |
修正方法としてはpxには数値のみが格納されるように、 | |
px = params[:resize_max_pixel].to_i | |
とし、それに伴い5行目のif文に | |
params[:resize_max_pixel].to_i > 0 | |
を加える。 | |
if px = params[:resize_max_pixel].to_i and params[:resize_max_pixel].to_i > 0 | |
また、dest_file_nameに使用されるparams[:image]はpng|jpeg|jpg|gifに制限したほうが安全そう | |
そのため、5行目のif文について | |
File.extname(file_name).match(/png|jpeg|jpg|gif/) | |
を | |
File.extname(file_name).match(/^\.png$|^\.jpeg$|^\.jpg$|^\.gif$/) | |
とする。 | |
Brakemanにより発覚したopen(params[:url])についての脆弱性より、攻撃可能であることは検証できなかったものの、 | |
break unless URI.parse(params[:url]) and page = open(params[:url]) rescue nil | |
としてバリデーションを行った | |
【9.SQLインジェクションに対するプレースホルダの使用による対策】 | |
Brakemanで発見されたSQLインジェクションについて、各所でプレースホルダーを使用する | |
app/controllers/friends_controller.rb:16 | |
if User.column_names.include?(order.split(' ',2)[0]) and order.split(' ',2)[1].downcase.match(/asc|desc/) | |
app/controllers/friends_controller.rb:22 | |
friends = User.where("name LIKE '%#{params[:name]}%' AND id NOT IN (#{ignore_ids.join(', ')})").order('name ASC') | |
friends = User.where('name LIKE ? AND id NOT IN (?)',"%#{params[:name]}%","#{ignore_ids.join(', ')}").order('name ASC') | |
app/controllers/sessions_controller.rb:6 | |
user = User.find_by "login_id='#{params[:login_id]}' and pass='#{ Digest::MD5.hexdigest params[:pass] }'" | |
user = User.where("login_id=? and pass=?",params[:login_id],Digest::MD5.hexdigest(params[:pass])).take | |
【10.mal1c10us-ng1nxの除去】 | |
Dockerfileではmal1c10us-ng1nxをベースにするように書かれているが、b4ckd00rなどが入っているため、 | |
mal1c10us-ng1nxのベースであるubuntu:16.04を直接ベースとする。 | |
また、nginxなど必要そうなパッケージに関してはDockerfileで直接インストール、自動起動設定する。 | |
FROM ommadawn46/mal1c10us-ng1nx:latest => FROM ubuntu:16.04 | |
vsftpd,fcgiwrap,netcatも入れているものの、サーバの要件によっては必要ない気がする。 | |
というかvsftpdはAnonymousアクセスを許可しているため危うい気がし、netcatは簡単バックドア製造機にしか見えないので消す。 | |
【11.Rubyのバージョン】 | |
もとのDockerfileではRuby2.4を使用していたが、Ruby2.6が利用可能になっているため、 | |
当該箇所をRuby2.6に書き換える | |
【12.gitとDocker build】 | |
git for windowsをインストールし、[1-1]の手順に従いリポジトリを引っ張ってくる。 | |
また、docker buildでDockerImageを生成できるか確認する。 | |
なお、gemで一度だけエラーが発生し、その対策として以下を挿入した | |
RUN gem install nokogiri -v '1.10.2' --source 'https://rubygems.org/' | |
しかし消しても動作したため、もともと記述もなかったことを考慮し、削除。 | |
rc.localは最新のUbuntuでは起動時に実行されないようで、 | |
rc-local.serviceを登録しsystemdから実行されるようにする。 | |
なお、ファイルはUTF-8NかつLFじゃないとエラーが頻発する。 | |
Tera Padから編集してエラーが発生し、苦労した。 | |
【13.MailHogについて】 | |
MailHogは最新版を引っ張ってきているため、 | |
おそらく脆弱性はない気がする | |
ただしCVEやJVNで検索をかけたのち、 | |
badsns2019独自の設定、特にパーミッションを精査する予定 | |
追記:資料から関係ないらしいことが判明。優先度最低へ | |
【14.MD5の廃止とSHA256の導入】 | |
MD5の使用箇所について、GitHubの検索機能より | |
sessions_controller.rb:6 | |
user.rb:16 | |
index.html:25 | |
password_reset_controller.rb:17 | |
の4箇所であることがわかった。 | |
SHA256を使用するにはDigest::MD5.hexdigestからDigest::SHA256.hexdigestとする。 | |
また、それに伴い文字列のサイズが変化するため、データベースの構成を確認する。 | |
schema.rbより、userテーブルのpassカラムはt.stringとして宣言されていることがわかった。そのため変更不要を確認。 | |
なおMD5は16byte(ダイジェスト32文字),SHA256は32byte(ダイジェスト64文字)となる。 | |
データベースの初期データはbad_sns_production.sqlに記述されているが、これはMD5のものなので、SHA256に変更する。 | |
雑なパスワードはGoogle検索でもとの文字列が簡単にわかるので、できるだけ再現する(hash.txt参照)。 | |
また、ソルトとして雑にlogin_idを使用して、SHA256(SHA256(pass)+SHA256(login_id))をパスワードとして保存する | |
sessions_controller.rb:6 | |
user = User.where("login_id=? and pass=?",params[:login_id],Digest::SHA256.hexdigest(Digest::SHA256.hexdigest(params[:pass])+Digest::SHA256.hexdigest(params[:login_id]))).take | |
user.rb:16 | |
self.pass = Digest::SHA256.hexdigest (Digest::SHA256.hexdigest(self.pass)+Digest::SHA256.hexdigest(self.login_id)) | |
password_reset_controller.rb:17 | |
user.update(pass: Digest::SHA256.hexdigest(Digest::SHA256.hexdigest(params[:pass])+Digest::SHA256.hexdigest(user.login_id))) | |
【15.リセットトークンの修正】 | |
リセットトークンの発行はpassword_reset_controller.rbより、[id, login_id, name, email]から生成されている。 | |
secret_key_baseは秘密であると仮定しても、少なくとも以下の3点が問題となる。 | |
・毎回同じリセットトークンが生成されること | |
・有効期限が指定されていること | |
・暗号化にECBモードが指定されていること | |
上2点に関してはデータベースのUserテーブルに時刻情報を格納することが単純な解決策となるが、 | |
提出期限まで8時間程度しかない現状でデータベースがからむ大規模な修正はしたくないので後回し。 | |
ECBモードからCBCモードへの変更は2箇所の修正のみで完了するため、CBCモードを使用するように修正する。 | |
【16.vulsとbrakemanによる再スキャン】 | |
vulsによるスキャン結果は--ignore-unfixedで0件となったので良しとする。 | |
brakemanについてはSQLインジェクションの見逃しが発覚 | |
friends_controller.rb:17 | |
friends = User.where("id IN (#{friend_ids.join(', ')})").order(order) | |
friends = User.where("id IN (?)","#{friend_ids.join(', ')}").order(order) | |
friends=ついてはなぜか動作しなかった。 | |
等価に見えるのになぜかプレースホルダーが動かん。 | |
ロールバック。 | |
users_controller.rb:22 | |
send_data File.read "#{Rails.root}/public/icons/#{user[:icon_file_name]}", disposition: 'inline' | |
send_data File.read "#{Rails.root}/public/icons/#{Shellwords.escape(user[:icon_file_name])}", disposition: 'inline' | |
feeds_controller.rb:30 | |
img.write "#{Rails.root}/public/images/#{image_file_name}" | |
img.write "#{Rails.root}/public/images/#{Shellwords.escape(image_file_name)}" | |
feeds = Feed.joins(:user).where("user_id IN (#{scope_user_ids.join(', ')})").select('feeds.*,users.name').order('id DESC').limit(30) | |
feeds = Feed.joins(:user).where("user_id IN (?)","#{scope_user_ids.join(', ')}").select('feeds.*,users.name').order('id DESC').limit(30) | |
feeds = Feed.joins(:user).where("user_id IN (#{scope_user_ids.join(', ')}) and feeds.id > ?", params[:id].to_i).select('feeds.*,users.name').order('id DESC') | |
feeds = Feed.joins(:user).where("user_id IN (?) and feeds.id > ?","#{scope_user_ids.join(', ')}", params[:id].to_i).select('feeds.*,users.name').order('id DESC') | |
feeds = Feed.joins(:user).where("user_id IN (#{scope_user_ids.join(', ')}) and feeds.id < ?", params[:id].to_i).select('feeds.*,users.name').order('id DESC').limit(30) | |
feeds = Feed.joins(:user).where("user_id IN (?) and feeds.id < ?","#{scope_user_ids.join(', ')}", params[:id].to_i).select('feeds.*,users.name').order('id DESC').limit(30) | |
上記の3つのfeed=についてはなぜか動作しなかった。 | |
Rails触るの初めてだし精進が必要。 | |
【X.PUT,DELETEメソッドのサポート】 | |
curlにて検査したところ、OPTIONS,PUT,DELETEメソッドはサポートされていないようであることがわかった。 | |
雑に調べたものの、怪しくなさそうな雰囲気なので後回し。 | |
追記:password_resetにてPATCH,PUT、sessionsにてDELETE、その他でGET,POSTのサポートを確認 | |
とりあえず後回し | |
【X.TLS対応】 | |
秘密情報を扱う以上TLSを使いたいが、ドメインもない上に手間がかかるので後回し。 | |
production.rbにconfig.force_ssl = trueを追記することで暗号化可能らしい。 | |
【Z.のこり】 | |
任意コード実行はbackdoor? | |
任意OSコマンドはopenやreadのコード? | |
他に漏洩はvsftpd?そういえば/friendsとかのAPIでpass含む一行まるまるjsonで返してるからBurpで全部見れたような...(時間切れ) | |
管理者に漏れるのは、平文パスワードをサーバ側で処理してるから?ブラウザ側でハッシュかけてから送れという話?でもなんか別にある気がする。 | |
データベースの不正操作はSQLインジェクションな気がするが、他にもありそうな気がする。 | |
他のユーザになりすますのはなんだろ。セッションハイジャック、というかtoken取得?JWTは知らないけれども、元にしてる情報から見た感じ毎回同じものが生成されそう? | |
サーバ上の任意のファイルが読み出せるのは権限関係?この手のものだとディレクトリトラバーサルもありそう | |
XSSについてはどこかわからない。投稿やDBからの読み出し、ユーザ情報やリンクなど確認場所が多そう。 | |
そもそも文脈的にapplicationの鍵が取れる気がする。 | |
あとはnginxとかのコンフィグや、Dockerfileで指定している権限周りとか | |
nginx-defaultより、末尾が.phpならPHPが実行されるようなので、不必要なら消しておきたい。おそらくOSコマンドインジェクションやアップロード系と組み合わせる? | |
ImageMagic(+GhostScript)もまだなにか隠れていそう | |
資料の『スコアシートについて』の部分、パスごとに得点項目があるのは、このパスに少なくとも1つは脆弱性がある...? | |
気になるけれども時間もないし、そもそも研究を進めないとやばいのでここで終了。 | |
【Z.プレースホルダーについての勘違い】 | |
feeds = Feed.joins(:user).where("user_id IN (#{scope_user_ids.join(', ')})").select('feeds.*,users.name').order('id DESC').limit(30) | |
feeds = Feed.joins(:user).where("user_id IN (?)","#{scope_user_ids.join(', ')}").select('feeds.*,users.name').order('id DESC').limit(30) | |
例えば上記の2つについて、実際に生成されるSQLを.to_sqlによって確認したところ、前者は 1,2,3 後者は '1,2,3' とされるため結果が異なることがわかった。 | |
よく考えたらプレースホルダーなのでそれはそう。 | |
というかlogin_idと勘違いしてインジェクションされるかと思ったけれども、 | |
よく見たらただのidだしbad_sns_production.sqlに記述してある定義からしてidにはintegerしか入らないのでそこまで危険ではない...? | |
【Z.icon_file_nameの書き換え】 | |
ユーザの登録時の処理はregister.jsより、icon_file_nameをサーバ側でSecureRandom.uuidをもとに生成したのち、 | |
一度クライアント側に返し、それを再度クライアントが送信する形になっている。 /icons => クライアント => /users | |
このときクライアントが編集して送信することにより、OSコマンドインジェクションが可能となる可能性がある。 | |
具体的にはusers_controller.rbでFile.readにそのまま渡しているところが危うい。ひとまずShellwords.escapeで対応。 | |
また、schema.rb、user.rb、bad_sns_production.sqlを確認したところ長さ確認はしてないようにみえる。 | |
DBの容量を無駄に逼迫させる攻撃が可能となる可能性があるため、各ファイルにて対応が必要。だが、未実施。 | |
【Z.OWASP ZAPによる検査】 | |
OWASP ZAPは使用したことがなかったため、簡単に使用してみた。 | |
インストーラを起動し、Javaがないと怒られ、Javaをインストールし、また怒られ、オフライン版の64bitを入れ、その後入った。 | |
手順としては[ツール]-[オプション]からローカルプロキシを選択し、PC側でもプロキシを合わせる。 | |
次にbadsnsにアクセスすると左側の列にある[サイト]にbadsnsのURLが表示されるため、右クリックからコンテキストに追加。 | |
その後、同様にURLを右クリックから[攻撃]-[動的スキャン]を選択。 | |
その結果は下側の[アラート]タブに表示されるが、メニューバーの[レポート]からHTML形式で生成したほうが見やすい。 | |
生成したものを../log/owasp_zap_default.htmlとして保存した。 | |
なお、対象としたbadsnsは手を加える前の初期状態のものである。 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment