リバースシェル攻撃でサーバーを乗っ取る
2022年8月10日
0 分で読めます編集者注:2023年5月9日
2022年8月10日に公開されたこの記事を更新し、Snykがリバースシェル攻撃の防止にどのように役立つかを紹介しています。
お気に入りの言語でアプリケーションを作成して実行するのは、たいてい簡単です。作成したアプリケーションをデプロイして公開するのも、さほど難しくありません。せっかく作ったアプリケーションを誰かに乗っ取られ、システムを完全に掌握されるような事態は避けたいものです。この記事では、リバースシェル攻撃によってそれがどのように起こり得るのかを説明します。
この記事のコード例は、あくまで教育目的です。リモートシェル攻撃とは何か、アプリケーションでどのように発生するのかを解説することを目的としています。この記事やその他の例を使って誰かをハッキングすることは推奨しません。多くの国では、たとえ善意であっても、対象者の同意を得ずにハッキングすることは違法です。
リバースシェルとは?
リバースシェル(コールバックシェルとも呼ばれます)とは、標的のマシンからリモートホストに対して開始されるシェルセッションです。標的のマシンが、特定のホストとポートにセッションを開きます。リモートホストが適切なソフトウェアを使ってそのポートで待ち受けていれば、シェル接続を確立できます。重要なのは、接続を開始するのはリモートホストではなく、標的のマシンだという点です。
リモートシェル攻撃では、攻撃者は被害者のマシンにこのような接続を開始させようとします。攻撃が成功すると、対話型シェル(基本的にはターミナル)へのアクセスを確立し、被害者のマシンを乗っ取ることができます。
リバースシェル攻撃はどのように発生する?
多くの場合、アプリケーションにリモートコード実行の脆弱性があると、リバースシェル攻撃が発生します。攻撃者はアプリケーションの脆弱性を利用して、被害者のマシン上でシェルセッションを開始するコードを実行します。被害者は気づかないうちに接続を作成し、攻撃者は正しいポートで着信接続を待ち受けるだけです。接続が確立されると、攻撃者は被害者のマシンのシェルにアクセスし、さまざまな操作を行えるようになります。
テニスボールをイメージしてください。硬いものに向かってボールを投げると、跳ね返ってきます。あとは、適切な場所とタイミングで受け止めるだけです。
リバースシェル接続を作成する
リバースシェルを作成する方法は、使用する言語によっていくつかあります。ただし、リバースシェルのコードを実行する前に、着信接続を受け付ける正しいポートで待ち受けていることを確認する必要があります。
netcatで着信接続を待ち受ける
この用途に便利なのがnetcatです。netcat(略してnc)は、TCPまたはUDPを使ってネットワーク接続から読み取り、書き込みを行うネットワークユーティリティです。リバースシェルの接続先となるマシンでは、netcatを使って指定したポートへの着信接続を待ち受けられます。以下の例では、netcatでポート9001を待ち受けます。vパラメーターは必須ではありませんが、詳細な出力が表示されます。
Python、Java、Node.jsでリバースシェルを実行する
リバースシェルを設定する方法を2つ紹介します。どちらの例も、bashシェルがインストールされたシステムで使用できます。
リバースシェルのペイロード:
1つ目の方法では、プログラムからシェルを起動します。次に、適切なIPアドレスとポートを指定して、リモートコンピューターへのソケット接続を作成します。
最後に、シェルのファイル記述子(入力、出力、エラー)を、新しく作成したソケット接続に接続します。
Java
このJavaの例では、プロセスのInputStreamとErrorStreamを、リモートソケット接続のOutputStreamに転送します。逆方向の接続も必要です。Socket OutputStreamをbashプロセスのInputstreamに書き込みます。
Pythonのリバースシェル
このPythonスクリプトでは、stdin、stdout、stderrをソケット接続に接続します。Unix系システムでは、これらは最初の3つのファイル記述子です。次に、ptyを使ってbashを実行します。
Node.js
このNode.jsの例は、Pythonの例とよく似ています。bashを実行し、標準ファイル記述子をソケット接続に適切に接続します。
リバースシェルコマンドを実行する:
2つ目の方法は、もう少し簡潔です。多くの言語には、次のようにシェルコマンドを実行する方法があります。
Javaの
Runtime.getRuntime()Pythonの
os.system()Node.jsの
require(‘child_process’).exec()
これらの関数を使って、リバースシェルを開始するワンライナーのシェルコマンドを実行できます。
Java
Python
Node.js
ポート9001を待ち受けるnetcatコマンドを実行した状態で、このコードを実行すると、接続が確立され、以下のようなシェルコマンドを実行できることがわかります。

上記の例はいずれも、自分自身へのリバースシェル接続を作成します。リモートマシンを対象にする場合は、IPアドレスを適切に変更してください。また、アクセスできたとしても、権限は被害者のマシンでこのコードを実行しているユーザーに依存します。権限を昇格させるには、さらに別の手順が必要な場合があります。
リモートコード実行の脆弱性を利用してリバースシェル攻撃を仕掛ける
このようなコード例を使って実際に攻撃するには、コード実行の脆弱性を悪用し、既存のシステムにコードを挿入する必要があります。その代表例が、2021年12月に発見されたLog4Shellの脆弱性です。インスタンス化時にコードを実行するガジェットクラスを挿入できました。多くの例では、電卓などを起動する無害な方法が紹介されていました。しかし、以下のコードは、この悪名高いLog4jの脆弱性を悪用するガジェットを作成します。Log4Shellを悪用すれば、電卓を起動するのではなく、リバースシェルを実行できるように利用できます。
ほぼすべてのリモートコード実行の脆弱性は、リバースシェル攻撃に利用できます。最近の例には、Spring4ShellやApache Commons ConfigurationのRCEがあります。どちらもLog4Shellほど深刻ではありませんでしたが、リバースシェル攻撃を仕掛け、標的のマシンを制御するために利用できる可能性があります。そのため、ユーザー入力が一部でも実行されないようにすることが重要です。
Snyk Open Sourceを使えば、Log4jの問題を含むオープンソースライブラリの脆弱性をアプリケーションでスキャンできます。以下は、この問題とその修正方法を示すSnyk IntelliJ IDEAプラグインの出力例です。

リバースシェル攻撃を防ぐ方法
攻撃者によるマシン上でのコード実行を防げれば、リバースシェル攻撃の可能性をほぼすべて排除できます。開発者が悪意のあるリバースシェル攻撃を防ぐためにできる対策をいくつか見てみましょう。
実行ステートメントを削除する。
exec()のように、スクリプトやその他のコードを実行できるステートメントは、できる限り避けてください。入力をサニタイズして検証する。すべての入力は悪意のあるものかもしれないと考えてください。直接入力されたユーザーデータだけではありません。たとえば、データベースのフィールドが実行処理への入力になる場合、データベースを攻撃される可能性があります。
アプリケーションの実行権限を制限する。アプリケーションをrootとして実行せず、必要最小限の権限を持つユーザーを作成してください。残念ながら、Dockerコンテナではよく見られる問題です。Dockerコンテナのデフォルトユーザーはrootだからです。
リモートコード実行につながる脆弱性を防ぐ。ライブラリやフレームワークが侵害された場合は、安全なバージョンに置き換えてください。
リモートコード実行の脆弱性は、たとえ想定しにくい用途であっても、ほぼすべてリバースシェル攻撃に悪用される可能性があります。
Snykが役立ちます!
Snykは、コードや依存関係をスキャンしてリバースシェル攻撃を防ぐのに役立ちます。独自コードに潜む潜在的なセキュリティ上の問題を指摘し、推移的依存関係に既知の脆弱性がないかを確認します。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。



