どうも、もりこういちろうです。
Ansibleで自分のサーバを4つ作ってみました。
Ansibleをインストールして構成ファイルを作れば自由に編集可能、デザインも後から自由にアレンジできるので難しいプログラミングなしで何台でも、それこそ100台でもあなた好みのサーバを自由自在に作成出来ます。
まるで新しいダンスを作っているみたい。
なかなかいい感じに仕上がりました。
インターネットを通じてより多くのことを知って貰うために、Wix.comであなただけの本格ホームページを作成しましょう。
目的
第46回、セキュリティカンファレンス「ゆるいハッキング大会 in Tokyo」開催のお知らせ
自社主催のハッキングカンファレンス用のやられ役サーバのひな形として作成しています。インターネット公開用の設定ではないので参考程度にして下さいませ。
環境
- CentOS7
- Ansible2.7.7
Ansible2.7以外では当記事のコードは上手く動かないと考えられます、Ansibleはバージョンの後方互換性が薄いからです。
構成
Ansibleサーバ
- 192.168.11.100
Ansibleクライアント
- 192.168.11.101
- 192.168.11.102
- 192.168.11.103
- 192.168.11.104
WEBとDBが1つのサーバで動作するシンプル構成 × 4台
4台作る目的はカンファレンスの攻撃参加者が30人程いるので攻撃を分散する為と、1つのサーバがハックされても他の台で楽しめるようにする為です。
Ansibleサーバ設定
1 |
# hostnamectl set-hostname ansible.example.net |
1 2 3 4 |
# vi /etc/sysconfig/selinux #SELINUX=enforcing SELINUX=disabled |
1 |
# reboot now |
1 2 3 4 5 |
yum -y install epel-release yum -y install ansible yum -y install MySQL-python yum -y install libselinux-python yum -y install rsync |
1 2 3 4 5 6 7 8 |
# ansible --version ansible 2.7.7 config file = /etc/ansible/ansible.cfg configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python2.7/site-packages/ansible executable location = /usr/bin/ansible python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] |
鍵の作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# ssh-keygen -t rsa -b 4096 Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Created directory '/root/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: SHA256:K1Jo3WhHCIgkHVdeM/mlTS80tE9Oz0JVetr+hm+aQak root@ansible.example.net The key's randomart image is: +---[RSA 4096]----+ |o+.oo.. +. .. +| |o o. o o.o =. o | | o .. *.o= .| | o + o o*.B | | o = S .B +| | . o . . o o | | . . . E ...| | . . .o+| | o=o| +----[SHA256]-----+ |
ターゲット用のサーバ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# vi .ssh/config Host yuruhack-web101 HostName 192.168.11.101 Host yuruhack-web102 HostName 192.168.11.102 Host yuruhack-web103 HostName 192.168.11.103 Host yuruhack-web104 HostName 192.168.11.104 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# ssh-copy-id yuruhack-web101 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub" The authenticity of host '192.168.11.101 (192.168.11.101)' can't be established. ECDSA key fingerprint is SHA256:8BSC790xfrVG130x7Lklexd2nrrAtLmSQPujj5FAdYg. ECDSA key fingerprint is MD5:fb:12:e5:e5:ec:79:a1:28:ee:7a:ed:71:37:98:36:02. Are you sure you want to continue connecting (yes/no)? yes /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys root@192.168.11.101's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'yuruhack-web101'" and check to make sure that only the key(s) you wanted were added. |
他のサーバにも鍵を渡していく
1 2 3 |
ssh-copy-id yuruhack-web102 ssh-copy-id yuruhack-web103 ssh-copy-id yuruhack-web104 |
1 2 3 4 5 6 7 |
# vi /etc/ansible/hosts [web] 192.168.11.101 192.168.11.102 192.168.11.103 192.168.11.104 |
疎通確認
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# ansible all -i /etc/ansible/hosts -m ping 192.168.11.102 | SUCCESS => { "changed": false, "ping": "pong" } 192.168.11.101 | SUCCESS => { "changed": false, "ping": "pong" } 192.168.11.104 | SUCCESS => { "changed": false, "ping": "pong" } 192.168.11.103 | SUCCESS => { "changed": false, "ping": "pong" } |
Ansibleサーバから操作出来ることを確認
設定ファイルの作成
1 |
# mkdir -p /etc/ansible/yuruhack.example.net/v1/web/etc/httpd/conf.d |
変数ファイルの作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# vi /etc/ansible/yuruhack.example.net/v1/var_yml --- usergroup: user: name: webadmin group: apache password: webadminPassword app_path: "/var/www/vhosts/yuruhack.example.net/httpdocs/" mysql_root_password: rootdbpassword ssh_port: 722 mynetwork: 192.168.11.0/24 dbName: wpdb dbUser: wpdbuser dbPassword: wpP@ssWordYuruHack dbNetwork: localhost |
WordPressファイルのダウンロード
1 2 3 4 5 |
yum install wget unzip mkdir -p /etc/ansible/yuruhack.example.net/v1/web/httpdocs/ wget https://ja.wordpress.org/latest-ja.zip unzip latest-ja.zip mv wordpress/* /etc/ansible/yuruhack.example.net/v1/web/httpdocs/ |
バーチャルホストファイルのひな形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# vi /etc/ansible/yuruhack.example.net/v1/web/etc/httpd/conf.d/yuruhack.example.net.conf <VirtualHost *:80> ServerAdmin root ServerName yuruhack.example.net DocumentRoot /var/www/vhosts/yuruhack.example.net/httpdocs/ <IfModule mod_rewrite.c> Include /etc/httpd/conf.d/mod_dosdetector_rewrite.conf </IfModule> </VirtualHost> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# vi /etc/ansible/yuruhack.example.net/v1/web/etc/httpd/conf.d/mod_dosdetector_rewrite.conf ## リライトルール RewriteEngine On # for DoS attack RewriteCond %{ENV:SuspectDoS} .+ [OR] RewriteCond %{ENV:SuspectHardDoS} .+ # クラスAのローカルIPアドレス帯を全て除外 RewriteCond %{REMOTE_ADDR} !^(10\.[0-9]+\.[0-9]\.[0-9])$ # クラスBのローカルIPアドレス帯を全て除外 RewriteCond %{REMOTE_ADDR} !^(172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]+\.[0-9]+)$ # クラスCのローカルIPアドレス帯を全て除外 RewriteCond %{REMOTE_ADDR} !^(192\.168\.[0-9]+\.[0-9]+)$ # 対クローラー排除 RewriteCond %{HTTP_USER_AGENT} !(google|yahoo|msn|bing) [NC] RewriteRule .* - [R=506,L] ErrorDocument 506 "Yararetazee!! you get wpP@ssWordYuruHack" |
1 2 |
mkdir -p /etc/ansible/yuruhack.example.net/v1/web/etc/vsftpd/user_conf touch /etc/ansible/yuruhack.example.net/v1/web/etc/vsftpd/chroot_list |
1 2 3 |
# vi /etc/ansible/yuruhack.example.net/v1/web/etc/vsftpd/user_conf/webadmin local_root=/var/www/vhosts/ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# vi /etc/ansible/yuruhack.example.net/v1/web/etc/vsftpd/vsftpd.conf local_enable=YES write_enable=YES local_umask=022 dirmessage_enable=YES connect_from_port_20=YES xferlog_std_format=YES listen=YES listen_ipv6=NO pam_service_name=vsftpd userlist_enable=YES tcp_wrappers=YES anonymous_enable=NO ascii_upload_enable=YES ascii_download_enable=YES ftpd_banner=Welcome to blah FTP service. chroot_local_user=YES chroot_list_enable=YES chroot_list_file=/etc/vsftpd/chroot_list ls_recurse_enable=YES pam_service_name=vsftpd tcp_wrappers=YES userlist_deny=NO pasv_min_port=50000 pasv_max_port=50030 chroot_local_user=YES force_dot_files=YES xferlog_file=/var/log/vsftpd.log xferlog_std_format=NO log_ftp_protocol=YES #ssl_enable=YES #pasv_addr_resolve=YES pasv_address= #rsa_cert_file=/etc/pki/tls/certs/ftp.pem #require_ssl_reuse=NO #force_local_logins_ssl=NO #force_local_data_ssl=NO #port_enable=YES #allow_anon_ssl=NO use_localtime=YES allow_writeable_chroot=YES user_config_dir=/etc/vsftpd/user_conf #listen_port=21 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# vi /etc/ansible/yuruhack.example.net/v1/web/etc/vsftpd/user_list # vsftpd userlist # If userlist_deny=NO, only allow users in this file # If userlist_deny=YES (default), never allow users in this file, and # do not even prompt for a password. # Note that the default vsftpd pam config also checks /etc/vsftpd/ftpusers # for users that are denied. root bin daemon adm lp sync shutdown halt mail news uucp operator games nobody webadmin |
playbookの作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# vi /etc/ansible/yuruhack.example.net/v1/playbook.yml --- - hosts: web become: yes vars_files: - var_yml tasks: - name: Firewalldの状態チェック command: systemctl is-active firewalld register: firewalld_result changed_when: False ignore_errors: True - name: Firewalldの起動, 自動起動設定 service: name=firewalld state=started enabled=yes - name: Firewalld設定 80許可 firewalld: permanent=True port=80/tcp state=enabled immediate=true - name: Firewalld設定 443許可 firewalld: permanent=True port=443/tcp state=enabled immediate=true - name: Firewalld設定 21許可 firewalld: permanent=True port=21/tcp state=enabled immediate=true - name: Firewalld設定 50000-50030許可 firewalld: permanent=True port=50000-50030/tcp state=enabled immediate=true - name: EPEL, remi リポジトリの追加 yum: name: "{{ package }}" state: latest vars: package: - epel-release - http://rpms.famillecollet.com/enterprise/remi-release-7.rpm - name: Basicツールのインストール yum: name: "{{ package }}" state: latest vars: package: - zip - unzip - git - gcc - name: Apacheのインストール yum: name: "{{ package }}" state: latest vars: package: - httpd - httpd-devel - name: MariaDBクライアントインストール yum: name: "{{ package }}" state: latest vars: package: - MySQL-python - mariadb - name: PHPインストール yum: name: "{{ package }}" state: latest vars: package: - php - php-mysql - php-mbstring - php-gd - php-devel - php-xml - php-pdo - name: PHPのタイムゾーン設定 replace: dest: /etc/php.ini regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" with_items: - { regexp: "^;date.timezone =", replace: "date.timezone = Asia/Tokyo" } - { regexp: "^expose_php = On", replace: "expose_php = Off" } - name: APPフォルダ作成 file: path="{{ app_path }}" state=directory owner=root group=root mode=0755 - name: Apache設定 Ansibleサーバから設定ファイルを複製 copy: src: "{{ item }}" dest: /etc/httpd/conf.d/ owner: root group: root mode: 0644 with_fileglob: - "./web/etc/httpd/conf.d/*.conf" - name: Apache設定 index.phpをDirectoryIndexに登録する replace: dest: /etc/httpd/conf/httpd.conf regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" with_items: - { regexp: "^DirectoryIndex index.html", replace: "DirectoryIndex index.php index.html" } - { regexp: "^User apache", replace: "User {{ usergroup.user.name }}" } - name: バーチャルホスト ServerName設定 replace: dest: /etc/httpd/conf.d/yuruhack.example.net.conf regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" with_items: - { regexp: "^ServerName=", replace: "ServerName {{ ansible_default_ipv4.address }}" } - name: clone mod_dosdetector git: repo=https://github.com/stanaka/mod_dosdetector.git dest=/usr/local/src/mod_dosdetector - name: mod_dosdetecotr インストール時のエラー防止用コマンド用リンク作成 file: src: /usr/bin/apxs dest: /usr/sbin/apxs state: link - name: make mod_dosdetector command: make install chdir=/usr/local/src/mod_dosdetector - name: WEBユーザの作成.1 Salt作成 shell: "mktemp -u | awk '{print substr($0, length($0)-8+1)}'" register: salt - name: WEBユーザの作成.2 パスワード用ハッシュ作成 shell: python -c 'import crypt; print crypt.crypt("{{ usergroup.user.password }}", "$6${{ salt.stdout }}")' register: hash_password - name: WEBユーザの作成.3 ユーザ作成 user: name={{ usergroup.user.name }} password={{ hash_password.stdout }} groups={{usergroup.user.group}} - name: バーチャルホストディレクトリの権限変更 file: path: /var/www/vhosts owner: "{{ usergroup.user.name }}" group: apache recurse: yes - name: AnsibleサーバからWordPressファイルを複製 synchronize: src=./web/httpdocs/ dest=/var/www/vhosts/yuruhack.example.net/httpdocs/ - name: パーミッション設定 file: path: "{{ app_path }}/" state: directory owner: "{{ usergroup.user.name }}" group: apache mode: 0755 recurse: yes - name: Apacheの起動 systemd: name: httpd.service state: restarted daemon_reload: yes enabled: yes - name: vsftpdインストール yum: name: "{{ package }}" state: latest vars: package: - vsftpd - name: vsftpd設定 Ansibleサーバから設定ファイルを複製 synchronize: src=./web/etc/vsftpd/ dest=/etc/vsftpd/ - name: vsftpd設定 replace: dest: /etc/vsftpd/vsftpd.conf regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" with_items: - { regexp: "^pasv_address=", replace: "pasv_address={{ ansible_default_ipv4.address }}" } - name: vsftpd起動 systemd: name: vsftpd.service state: restarted daemon_reload: yes enabled: yes - name: MariaDB インストール yum: name: "{{ package }}" state: latest vars: package: - MySQL-python - mariadb - mariadb-libs - mariadb-server - mariadb-devel - name: MySQL起動, 自動起動 service: name=mariadb state=started enabled=yes - name: DBの作成 mysql_db: name={{ dbName }} state=present - name: DBユーザの作成 mysql_user: name: "{{ dbUser }}" password: "{{ dbPassword }}" priv: "{{ dbName }}.*:ALL" host: "{{ dbNetwork }}" state: present - name: ローカル接続制限無効化 replace: > dest=/etc/my.cnf regexp='^bind-address' replace='#bind-address' - name: MySQL再起動, 自動起動 service: name=mariadb state=restarted enabled=yes - name: phpMyAdminインストール yum: name: "{{ package }}" state: latest vars: package: - phpMyAdmin - name: phpMyAdminアクセス権限の変更 ローカルネットワークのアクセス許可 replace: dest: /etc/httpd/conf.d/phpMyAdmin.conf regexp: "{{ item.regexp }}" replace: "{{ item.replace }}" with_items: - { regexp: "Require ip 127.0.0.1", replace: "Require ip 127.0.0.1 {{ mynetwork }}" } - name: Apache再起動 service: name=httpd state=restarted enabled=yes - name: Firewalldの起動, 自動起動設定 service: name=firewalld state=started enabled=yes - name: Firewalld設定 3306許可 ローカルネットワークのみDBへの接続許可 firewalld: permanent=True port=3306/tcp source={{ mynetwork }} state=enabled immediate=true - name: Firewalld設定 80許可 phpMyAdmin用 firewalld: permanent=True port=80/tcp state=enabled immediate=true - name: Firewalldの再起動, 自動起動設定 service: name=firewalld state=restarted enabled=yes - hosts: all become: yes vars_files: - var_yml remote_user: root tasks: - name: yumアップデート yum: name=* state=latest - name: SELinux-1. SELinux用のPythonモジュールをインストール yum: name=libselinux-python state=installed - name: SELinux-2. SELinuxの無効化 selinux: state=disabled register: selinux - name: SELinux-3. サーバの再起動 shell: sleep 2 && shutdown -r now async: 1 poll: 0 become: true ignore_errors: true - name: SELinux-4. サーバの起動を待つ wait_for_connection: delay: 30 timeout: 300 - name: SELinux-5. 疎通確認 ping: |
1 2 |
構文チェック # ansible-playbook /etc/ansible/yuruhack.example.net/v1/playbook.yml --syntax-check |
今回は実行しませんが、ドライランでテスト実行も出来ます。
1 2 |
ドライラン # ansible-playbook /etc/ansible/yuruhack.example.net/v1/playbook.yml --check |
クライアントサーバ側にインストールしないと存在しない設定ファイルの操作をplaybookで指定しているのでドライランではエラーになるはずです。
どっかーん! 一気にサーバを作っちゃお~!!
実行
1 |
# ansible-playbook /etc/ansible/yuruhack.example.net/v1/playbook.yml |
確認しようね!
http://192.168.11.101/
http://192.168.11.102/
http://192.168.11.103/
http://192.168.11.104/
4台のWordPress用サーバが作成出来ました。
お疲れ様です。
ここからいくつか脆弱性を埋め込んでいきます。