Webセキュリティの小部屋

Twitter のフォローはこちらから Facebook ページはこちら RSSフィードのご登録はこちらから
公開日:2013年7月29日
最終更新日:2020年8月13日

JSON セキュリティ:WCF で JSON.NET を使う

JSON のセキュリティ対策は、以下の記事に記載した通りですが、これを ASP.NET で実現するにはどうしたらよいでしょうか。この記事では、JSON セキュリティ対策のサンプルプログラムを提示します。

ASP.NET で JSON データを返す Web サービスを作成するには、.NET Framework 3.0 から提供されている WCF(Windows Communication Foundation) を使用します。

また、ASP.NET 内のデータは .NET のクラスを使用しますが、クラスのインスタンスを JSON の形式に変換するのに、今回はオープンソースの JSON.NET を使用します。JSON.NET は、.NET Framework 2.0 以降で動作します。

概要

この記事で作成する WCF の Web サービスでは、JSON.NET を使用して .NET のインスタンスを JSON に変換します。

クライアントからリクエストを受け取ると、Web サービスは該当するデータを検索して、一致したデータを JSON の形式でクライアントに返します。

呼び出し元のクライアントは、jQuery と prototype.js を使用した HTML を作成します。

準備

今回は、Visual Studio 2012 for Web を使用して Web サービスを作成しますが、その前に NuGet を使用して、JSON.NET, jQuery, prototype.js のパッケージをインストールしておきます。

WCF Web サービスの作成

WCF の Web サービスを作成するには、WCF のインターフェース、インターフェースの実装、Web サービスの定義が必要です。また、データクラスとして Person クラスを作成します。

WCF のインターフェース

GET リクエストでパラメーターを受け付け、JSON で値を返すよう定義します。
・IService1.cs (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Web;
using System.ServiceModel.Channels;

namespace WcfService1
{
  [ServiceContract]
  public interface IService1
  {
    [OperationContract]
    [WebInvoke(UriTemplate = "GetPerson?name={name}", Method = "GET")]
    Message GetPerson(string name);
  }
}

・IService1.vb(VB.NET)

Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Channels

<ServiceContract()>
Public Interface IService1

  <OperationContract()>
  <WebInvoke(UriTemplate:="GetPerson?name={name}", Method:="GET")>
  Function GetPerson(ByVal name As String) As Message

End Interface

インターフェースの実装

まず、リクエストヘッダーを見て XMLHttpRequest のリクエストかチェックします。そして、受け取ったパラメーターを条件にしてデータを検索し、一致したデータを JSON に変換して値を返します。また、セキュリティ上必要なレスポンスヘッダーも付与します。

・Service1.svc.cs(C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Web;
using System.ServiceModel.Channels;
using Newtonsoft.Json;


namespace WcfService1
{
  public class Service1 : IService1
  {
    public Message GetPerson(string name)
    {
      //check XMLHttpRequest/JSONP
      string xhr = WebOperationContext.Current.IncomingRequest.Headers["X-Requested-With"];

      if (xhr != "XMLHttpRequest")
      {
        WebOperationContext ctx = WebOperationContext.Current;
        ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Forbidden;
        throw new HttpException("Forbidden");
      }

      //create data
      List<Person> list = new List<Person>();
      list.Add(new Person() { Name = "Yamada", Age = 10 });
      list.Add(new Person() { Name = "Sato", Age = 20 });
      list.Add(new Person() { Name = "Tanaka", Age = 30 });

      //select person by LINQ
      var person = from t in list
             where t.Name == name 
             select t;

      //object to json by JSON.NET
      string body = JsonConvert.SerializeObject(person);

      //Add 'X-Content-Type-Options: nosniff' header
      WebOperationContext
        .Current.OutgoingResponse.Headers.Add
        ("X-Content-Type-Options", "nosniff");

      //Return JSON
      return WebOperationContext.Current.CreateTextResponse(body,
        "application/json; charset=utf-8",
        Encoding.UTF8); 
    }
  }
}

・Service1.svc.vb(VB.NET)

Imports System.ServiceModel.Channels
Imports Newtonsoft.Json

Public Class Service1
  Implements IService1

  Public Sub New()
  End Sub

  Public Function GetPerson(name As String) As Message Implements IService1.GetPerson
    'check XMLHttpRequest/JSONP'
    Dim xhr As String = WebOperationContext.Current.IncomingRequest.Headers("X-Requested-With")

    If xhr <> "XMLHttpRequest" Then
      Dim ctx As WebOperationContext = WebOperationContext.Current
      ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Forbidden
      Throw New HttpException("Forbidden")
    End If


    'create data'
    Dim list As List(Of Person) = New List(Of Person)
    list.Add(New Person() With {.Name = "Yamada", .Age = 10})
    list.Add(New Person() With {.Name = "Sato", .Age = 20})
    list.Add(New Person() With {.Name = "Tanaka", .Age = 30})

    'select person by LINQ'
    Dim person = From t In list _
           Where t.Name = name _
           Select t

    'object to json by JSON.NET'
    Dim body As String = JsonConvert.SerializeObject(person)

    'Add 'X-Content-Type-Options: nosniff' header'
    WebOperationContext.Current.OutgoingResponse.Headers.Add("X-Content-Type-Options", "nosniff")

    'Return JSON'
    Return WebOperationContext.Current.CreateTextResponse(body,
        "application/json; charset=utf-8",
        Encoding.UTF8)
  End Function
End Class

Web サービスの定義

Service1.svc ファイルをマークアップで表示し、Factory="System. ServiceModel. Activation. WebServiceHostFactory" を追加します。

・C#

<%@ ServiceHost Language="C#" Debug="true" Service="WcfService1.Service1" CodeBehind="Service1.svc.cs" Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

・VB.NET

<%@ ServiceHost Language="VB" Debug="true" Service="WcfService2.Service1" CodeBehind="Service1.svc.vb"  Factory="System.ServiceModel.Activation.WebServiceHostFactory"%>

データクラスの作成

Person クラスを作成します。

・Person.cs (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WcfService1
{
  public class Person
  {
    public string Name { get; set; }
    public int Age { get; set; }
  }
}

・Person.vb(VB.NET)

Public Class Person
  Private _Name As String
  Public Property Name() As String
    Get
      Return _Name
    End Get
    Set(ByVal value As String)
      _Name = value
    End Set
  End Property

  Private _Age As Integer
  Public Property Age() As Integer
    Get
      Return _Age
    End Get
    Set(ByVal value As Integer)
      _Age = value
    End Set
  End Property

End Class

クライアントの作成

Web サービスにリクエストを送信し、レスポンスのデータを表示するクライアントを作成します。レスポンスのデータを表示する際、HTML エスケープしていることに注意してください。

jQuery で作成

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title></title>
  <script src="Scripts/jquery-2.0.3.min.js"></script>
  <script type="text/javascript">
    $.ajax({
      type: "GET",
      url: "Service1.svc/GetPerson",
      data: "name=Yamada",
      success: function (data, textStatus, xhr) {

        //HTML escape and append
        $("#person").text(data[0].Name + " " + data[0].Age); 
      }
    });
  </script>
</head>
<body>
  <div id="person"></div>
</body>
</html>

実行結果は、以下のようになります。

Yamada 10

jQuery によって、以下の行がリクエストヘッダーに追加されていることに注意してください。

  • X-Requested-With: XMLHttpRequest

GET http://localhost:58069/Service1.svc/GetPerson?name=Yamada HTTP/1.1
Host: localhost:58069
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36
Referer: http://localhost:58069/HtmlPage1.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: ja,en-US;q=0.8,en;q=0.6

以下の2行が、レスポンスヘッダー追加されていることに注意してください。

  • Content-Type: application/json; charset=utf-8
  • X-Content-Type-Options: nosniff

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 31
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Content-Type-Options: nosniff
X-AspNet-Version: 4.0.30319
X-SourceFiles: =(cut)
X-Powered-By: ASP.NET
Date: Mon, 29 Jul 2013 13:02:18 GMT

prototype.js で作成

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <title></title>
  <script src="Scripts/prototype.js"></script>
  <script type="text/javascript">
    new Ajax.Request("Service1.svc/GetPerson", {
      method: "get",
      parameters:{name:"Yamada"},
      onSuccess: function (data) {
        //Text to JSON and security check
        var json = data.responseText.evalJSON(true);
        var str = json[0].Name + " " + json[0].Age;
        $("person").update(str.escapeHTML()); //HTML escape and update
      }
    });

  </script>
</head>
<body>
  <div id="person"></div>
</body>
</html>

実行結果は、以下のようになります。

Yamada 10

prototype.js によって、以下の行がリクエストヘッダー追加されていることに注意してください。

  • X-Requested-With: XMLHttpRequest

GET http://localhost:58069/Service1.svc/GetPerson?name=Yamada HTTP/1.1
Host: localhost:58069
Connection: keep-alive
Accept: text/javascript, text/html, application/xml, text/xml, */*
X-Prototype-Version: 1.7
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36
Referer: http://localhost:58069/HtmlPage2.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: ja,en-US;q=0.8,en;q=0.6

以下の2行がレスポンスヘッダー追加されていることに注意してください。

  • Content-Type: application/json; charset=utf-8
  • X-Content-Type-Options: nosniff

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 31
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Content-Type-Options: nosniff
X-AspNet-Version: 4.0.30319
X-SourceFiles: =(cut)
X-Powered-By: ASP.NET
Date: Mon, 29 Jul 2013 13:09:09 GMT

参考

Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。


スポンサーリンク





カテゴリー:Webアプリケーションセキュリティ対策

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA