Amarronの日記

iOSやMac、Web系の記事を書きます。

iOS 保存領域 比較 〜NSUserDefaults, CoreData,Keychain,等〜

アプリ容量の制限について


アプリのストレージの最大容量は、おそらく制限なし(20M以上だとDL時にwifiが必要となる)

f:id:Amarron:20150314205528j:plain

保存領域について


種類 説明
アプリ領域 アプリが自由に使え、他のアプリから参照できない領域
OS領域 OSが使用するデータが保存されており、基本的にアプリから参照できない領域
共有領域 メディアライブラリ(写真、音楽等)やイベント(カレンダー等)、連絡先は全てのアプリから参照することができる領域(アプリからできる内容は、書き込みと閲覧のみ)

永続データの復元(比較)について


  • 復元時にデータも復元するには、外部サーバーにデータを保存するかiCloudを利用しないと殆ど初期化される
  • 保存領域が共有領域であれば、アプリ自体にiCloudの処理をいれなくても、共有領域のデータは復元できそう
種類 復元可否 備考
アーカイブ オブジェクトをバイナリ形式に変換してからファイルに永続化(一番基礎的な技術)
プロパティリスト - ユーザーが変更しないもの(定数)、plist
NSUserDefaults ユーザーが変更するもの
CoreData データ量多い
Keychain パスワードや個人情報(暗号化されていない場合復元されない)

アプリ内にDBとして保存したい場合realmがオススメです。(日本語のドキュメントもしっかりしていて、組み込みも簡単で、ブラウザも用意されています)

参考URL


Xcode デバッグ 値変更

概要

  • Xcodeでデバック中に、変数の値や関数の実行する方法の説明です
  • デバックエリアでLLDBコマンドを実行して確認します
  • 値の変更はデバックエリアで「po 変数名 = 値」で変更できます

LLDBとは、Mac OS X上のXcodeでデフォルトのデバッガで、デスクトップとiOSデバイスとシミュレータ上のC、のObjective-CC++Swiftデバッグをサポートしています。

f:id:Amarron:20150314202752p:plain

実行方法

  1. デバックエリアを表示(メニューバー->View->Debug Area->Show Debug Area)
  2. 止めたいところにブレークポイント設定
  3. プログラムの実行
  4. デバックエリアでLLDBコマンドの実行

実行例

  • 変数の値や関数の実行する場合は、「(lldb) po 変数名 または 関数名」を実行します

f:id:Amarron:20150314202812p:plain 「po self.dynamicType」でプロジェクト名とクラス名を表示している例

LLDBコマンド一覧(一部抜粋)

省略形 内容 正式名
po 式の評価(poはオブジェクト、pは基本型も可) expression -o --
h ヘルプの表示 help
s ステップイン thread step-in
c 続きを実行(次のbreakPointまで) process continue
br l breakPointを一覧表示 breakpoint list
bt 現スレッドのバックトレース情報を表示 thread backtrace

iOS AdMob 実装 方法

AdMob SDKの導入方法や手順が書いてあります。(iPhoneiPadが対象)

やりたいこと

iOSアプリにAdMobのバナー広告を表示させたい。
(AdMobとは、Googleが運営するアプリ専門のアドネットワーク。日本国内の収益性は微妙だが、海外の広告案件が豊富。)

f:id:Amarron:20150215023503p:plain

流れ

  1. AdMobに登録
  2. アプリにSDK組込
  3. 広告ユニットIDの作成

細かい部分はAdMobのスターターズガイドがしっかりしているので、その通り進めば多分大丈夫だと思います。

1. AdMobに登録

AdMobのサイトでユーザー登録をします。
「Sign Up」ボタン押下後適当に個人情報を入力して進みます。
(全文英語ですが、一般的なユーザー登録なので英語読めなくても何とかなるはず。。。)

f:id:Amarron:20150215024507p:plain

2. アプリにSDK組込

今回は、CocoaPodsを利用してSDKを組込みます。
CocoaPodsの利用方法がわからない方はこちら

f:id:Amarron:20150215023529p:plain

2-1. Podfileの作成・SDKインストール

ターミナルを起動しPodfileを作成しSDKをインストールします。

$ vi Podfile   # Podfileの作成
platform :ios, '8.0'
pod 'Google-Mobile-Ads-SDK', '~> 7.0'
$ pod install  # SDKインストール
Analyzing dependencies

CocoaPods 0.36.0.beta.2 is available.
To update use: `gem install cocoapods --pre`
[!] This is a test version we'd love you to try.

For more information see http://blog.cocoapods.org
and the CHANGELOG for this version http://git.io/BaH8pQ.

Downloading dependencies
Installing Google-Mobile-Ads-SDK (7.0.0)
Generating Pods project
Integrating client project

[!] From now on use `BannerExample.xcworkspace`.
$  # 「プロジェクト名.xcworkspace」が作成されていればインストール完了!

2-2. AdMobを実装

とりあえずSDKが読み込めているか試してみましょう

# swift
// Bridging-Header.h
@import GoogleMobileAds

// BannerExampleViewController.swift
class BannerExampleViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())")
    }
 }
# Objective-C
// BannerExampleViewController.m

@import GoogleMobileAds;
#import "ViewController.h"

@implementation BannerExampleViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  NSLog(@"Google Mobile Ads SDK version: %@", [GADRequest sdkVersion]);
}

@end

次に、バナーを作成し画面表示されたらOKです。
広告ユニットIDは、「3. 広告ユニットIDの作成」で作成し自分のIDに変更しましょう

# swift
// BannerExampleViewController.swift

// GADBannerViewDelegateを追加
class BannerExampleViewController, GADBannerViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())")
        
        var bannerView: GADBannerView = GADBannerView()
        bannerView = GADBannerView(adSize:kGADAdSizeBanner)
        // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照)
        bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716" 
        bannerView.delegate = self
        bannerView.rootViewController = self
        self.view.addSubview(bannerView)
        bannerView.loadRequest(GADRequest())
    }
}
# Objective-C
// BannerExampleViewController.h

// SDK から GADBannerView の定義をインポートする
#import "GADBannerView.h"

@interface BannerExampleViewController : UIViewController {
  // インスタンス変数として 1 つ宣言する
  GADBannerView *bannerView_;
}

@end

// BannerExampleViewController.m

#import "BannerExampleViewController.h"

@implementation BannerExampleViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // 画面上部に標準サイズのビューを作成する
  // 利用可能な広告サイズの定数値は GADAdSize.h で説明されている
  bannerView_ = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];

        // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照)  bannerView_.adUnitID = MY_BANNER_UNIT_ID;

  // ユーザーに広告を表示した場所に後で復元する UIViewController をランタイムに知らせて
  // ビュー階層に追加する
  bannerView_.rootViewController = self;
  [self.view addSubview:bannerView_];

  // 一般的なリクエストを行って広告を読み込む
  [bannerView_ loadRequest:[GADRequest request]];
}

- (void)dealloc {

  // プロジェクトで ARC を使用している場合は bannerView_ を解放しない
  [bannerView_ release];
  [super dealloc];
}

@end

広告のサイズやターゲットの設定はこちら

3. 広告ユニットIDの作成

  1. AdMob管理画面に「1. AdMobに登録」で作成したアカウントでログイン
  2. 広告ユニットIDの作成
    1. 「収益化->新しいアプリの収益化」ページまで移動
    2. アプリを追加
    3. 広告フォーマットの選択と広告ユニット名の設定
    4. 表示の設定方法
    5. 完了ボタン押下
  3. 広告ユニットIDをアプリに設定
    1. 「収益化->すべてのアプリ」ページまで移動
    2. 自分が追加したアプリの「広告ID」が発行されています

      f:id:Amarron:20150215023554p:plain

    3. 「2-2. AdMobを実装」で設定していた「広告ID」を自分の「広告ID」に変更しましょう

  4. 表示と計測の確認
    1. アプリを起動し広告が表示されているか確認しましょう(広告IDを変更した場合、よく見かける一般的な広告に差し替わっていると思います。)
    2. 広告を押下し外部サービスが起動することを確認しましょう
    3. 管理画面上で計測が正しくとれていることを確認しましょう(リアルタイムに反映されないので時間を置いて確認しましょう(私の場合1時間位で反映されていました))

以上で全て終了です。お疲れ様でした!

CoreData の 関連図 と 手順

概要

  • CoreDataの知識が曖昧だったのでクラスの関連図や手順についてのメモです(細かい実装方法や使い方は他の参考サイトを参考にしてください)
  • DB自体初めての人には登場人物が多くて難易度高めな気がします
  • 参考資料:iOS Core Data チュートリアル

関連図

f:id:Amarron:20150121224913p:plain

Core Data RDB
NSManagedObjectModel DB
Entity Table
NSManagedObject Record
Attribute Column
  • NSManagedObjectModel:管理オブジェクトモデル(データベース)

f:id:Amarron:20150121224955p:plain

  • NSManagedObject:管理オブジェクト(レコード)
  • NSManagedObjectContext:管理オブジェクトコンテキスト(オブジェクト空間、管理オブジェクトのコレクションを管理する)

f:id:Amarron:20150121225004p:plain

  • NSPersistentStoreCoordinator:永続ストアコーディネータ(ストアを管理して、管理オブジェクトコンテキストに、1つ の統一されたストアのファサードを提供する)

f:id:Amarron:20150121225014p:plain

作成手順

  1. プロジェクトの準備
    • Xcodeで、「iOS」セクションの「Window-basedApplication」テンプレーを使用してプロジェクトを作成します。オプション(Options)(Options)セクションで、ストレージにCore Dataを使用するスイッチ(「Use Core Data for storage」)を選択
      • AppDelegateクラスにCoreData用のプログラムが追加され、【プロジェクト名】.xcdatamodeldが作成される
  2. テーブルの作成
    • Xcodeのデータモデリングツール(プロジェクト名】.xcdatamodeld)を使用して、新規エンティティを作成
    • f:id:Amarron:20150121225041p:plain

    • そのエンティティを表すカスタムクラス(NSManagedObject)を作成(xcdatamodeldを選択し、「Create NSManagedObject」で作成可能)

  3. テーブルにデータ追加
    • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
    • NSEntityDescriptionでテーブルの指定
    • NSManagedObjectで値の受け渡し
  4. テーブルからデータ取得
    • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
    • NSFetchRequestでテーブルの指定
import CoreData

class MyClass : UIViewController {
// 3. テーブルにデータ追加
@IBAction func insert(sender: AnyObject) { 
        let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext!
        
        /* Create new ManagedObject */
        let entity = NSEntityDescription.entityForName("テーブル名", inManagedObjectContext: managedContext)
        let personObject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
        // let myPersonObject = NSManagedObjectを継承したカスタムクラス(entity: entity!, insertIntoManagedObjectContext: managedContext) // 自分で作成したカスタムを使う場合
        
        /* Set the name attribute using key-value coding */
        personObject.setValue("値", forKey: "カラム名")
        // myPersonObject.カラム名 = "値" // 自分で作成したカスタムを使う場合
        
        /* Error handling */
        var error: NSError?
        if !managedContext.save(&error) {
            println("Could not save \(error), \(error?.userInfo)")
        }
        println("object saved")
}

// 4. テーブルからデータ取得
@IBAction func selectAll(sender: AnyObject) { 
        /* Get ManagedObjectContext from AppDelegate */
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let manageContext:NSManagedObjectContext = appDelegate.managedObjectContext!

        /* Set search conditions */
        let fetchRequest = NSFetchRequest(entityName: "テーブル名")
        var error: NSError?
        
        /* Get result array from ManagedObjectContext */
        let fetchResults = manageContext.executeFetchRequest(fetchRequest, error: &error)
        if let results: Array = fetchResults {
            for obj:AnyObject in results {
                let name:String? = obj.valueForKey("カラム名") as? String
                println(name)
            }
            println(results.count)
        } else {
            println("Could not fetch \(error) , \(error!.userInfo)")
        }
}

まとめ

  • xcdatamodeldでテーブル情報(テーブル名、カラム名、カラム属性、リレーション等)を設定する
  • NSManagedObjectを継承したクラスをテーブルのカラム名を書いて、テーブルとの受け渡し用の入れ物に使う
  • NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
  • NSEntityDescriptionでテーブルの指定
  • データの保存場所を確認したい場合、applicationDocumentsDirectoryを見る
  • 個人的には、FMDBのライブラリを使って直接SQLでデータをやりとりした方が自由で楽なきがします。。。

参考サイト

Swift 文法

f:id:Amarron:20141231160353j:plain

概要

Swiftの入門的な部分をまとめてみました。
初心者向けに文法の部分を中心に書きました。
また、SwiftObjective-Cより直感的ですっきりしているので、
iOSの開発を始めたい方はSwiftでの開発がオススメです。

目次

  1. 変数宣言(var,let)
  2. 型(Int,String...)
  3. 文字列操作
  4. 配列(Array)
  5. 辞書(Dictionary)
  6. タプル(Tuple)
  7. 繰り返し(for,while)
  8. 条件分岐(if,switch)
  9. 関数(func)
  10. クラスと構造体(class,struct)
  11. オプショナル型(Optional)
  12. おすすめの勉強法

1. 変数宣言(var,let)

  • 変数の宣言には「var」と「let」がある
    • varは、変数
    • letは、定数
// 型を指定して生成
var varInt:Int      // 宣言:var 変数名:型
varInt = 0          // 代入:変数名=値
var varInt2:Int = 0 // 宣言+代入:var 変数名:型=値
// 型推論(型を指定せずに生成)
var varInt3 = 3     // 値からInt型の変数が生成される

let pi = 3.14
// let pi2:Double   // 宣言のみだとエラー(値の代入も宣言時に必要)
// pi = 3.1415926   // 値を変更するとエラー

2. 型(Int,String...)

  • マイナス値がない「UInt」型がある
    • 添字などで使うと安全性が高まる
  • 型指定なしの「Any」型がある
名前 説明
Int 整数型
UInt 符号なし整数型
Float 単精度少数型
Double 倍精度少数型
Bool 論理型
Charecter 文字型
String 文字列型
Any 型指定なし
AnyObject 型指定なし(Classに限定)

3. 文字列操作

  • Stringの文字列は「+」で結合できる
    • var str:String = "Hello"+"World"
  • String内に処理の実行結果を代入する時は「\(処理)」で出来る
    • var str:String = "1+1=\(1+1)"

4. 配列(Array)

// 型を指定
var varArray:[String]
// 型推論(型を指定せずに生成)
var varArray2 = ["var0", "var1", "var2"]
// 空の配列
var varArray3 = [String]()
var varArray4:[String] = Array() 
// 要素の追加
varArray2.append("var3")

5. 辞書(Dictionary)

// 型を指定
var varDict:Dictionary<String, String>
// 型推論(型を指定せずに生成)
var varDict2 = ["key0":"var0", "key1":"var1", "key2":"var2"]
// 空の辞書
var varDict3 =  Dictionary<String, String>()
var varDict4:Dictionary<String, String> = Dictionary()
// 要素の追加
varDict2["key3"] = "var3"
// 値の取得
if let value = varDict2["key3"] {
    print(value)
}
// キーと値の取得例
for (dictName, dictValue) in varDict2 {
    print("キーは\(dictName)で、値は\(dictValue)です。")
}

6. タプル(Tuple)

  • タプルは複数の値をグループとして扱える(「x,y,width,height」や「name,age」とかをグループにしたい時に便利)
  • タプル内の値は、異なる型の値を設定出来る
// 値を設定
let http404Error = (404, "Not Found")
// 値を取得
print("The status code is \(http404Error.0)")

// 他の変数に設定、取得
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 変数名をつけて設定、取得
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")

7. 繰り返し(for,while)

var roopArray = ["var0","var1","var2"]
// for-in ループ
for i in 0..<roopArray.count {
    print(roopArray[i])
}
// 条件文付きforループ
for var i = 0; i < roopArray.count; i++ {
    print(roopArray[i])
}
// 代入forループ
for value in roopArray {
    print("\(value)")
}
// while
var roopIndex = 0
while roopIndex < roopArray.count {
    print(roopArray[roopIndex])
    roopIndex++
}

8. 条件分岐(if,switch)

  • ifの条件式に()は、書かなくてもOK
  • switchは、breakを書かなくてもbreakする
  • switchでも、条件が複数指定できる
  • switchでも、細かい条件も指定できる
var varStr:String = "Hello World"
// if
if varStr != "" {
    print(varStr)
}
// if条件分岐+代入
var varStr2:String?
if let varStr3 = varStr2 {
    print(varStr3) // varStr2がnilじゃない場合、varStr3にvarStr2を代入
} else {
    print("varStr2 is nil")
}

// if条件分岐+代入(三項演算子) その2
var varInt:Int = 1
var varStr3:String = (1 == varInt) ? "1" : ""

// switch
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress": // 複数条件
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"): // 細かい条件
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

9. 関数(func)

  • 戻り値が複数設定出来る
// 関数を呼び出す
// func 関数名(引数: 型) -> 戻り値
func square(num: Int) -> Int {
    return num * num
}
// 関数を使う
print("計算結果は\(square(2))です")
// 引数が複数の場合、引数を「,」区切りで宣言
func sum(num1: Int, num2: Int) -> Int {
    return num1 + num2
}
// 戻りが複数の場合、戻り値を「,」区切りで宣言
func tax(num: Float) -> (five: Float, eight: Float) {
    return (five: num*1.05, eight: num*1.08)
}
var taxPrice = tax(100.0)
// 複数の戻りを取り出す場合、「.」で指定する
print("消費税5%だと、\(taxPrice.five)円です")
print("消費税8%だと、\(taxPrice.eight)円です")

10. クラスと構造体(class,struct)

  • クラスと構造体は、変数や関数等をまとめたプログラム
  • クラスと構造体の違いは、「型」として性質(セマンティクス)が異なる
    • クラス(class):参照型(コピーした時に、コピー元の値を変更するとコピー先も同じものを参照しているので値が変わる)
    • 構造体(struct):値型(コピーしてもそれぞれにインスタンスされる)

クラス(class)

  • 本格的に作る場合に向いている
    • 継承を使える
    • ARCが使える
  • インスタンスのライフサイクルが長いもに向いている
  • 例)UIColor,UIImage等(殆どがクラス)
class RectClass {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rectC = RectClass()
rectC.x = 0;
rectC.width = 100;
print("最大幅は\(rectC.maxX())です");

構造体(struct)

  • 軽く作る場合に向いている
  • プロパティ数が少ないもに向いている
  • 例)CGRext,CGPoint,CGSize,Array等
struct RectStruct {
    // 変数
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0
    
    // 関数
    func maxX() -> Float {
        return x+width
    }
    func maxY() -> Float {
        return y+height
    }
}

var rect0 = RectStruct()
var rect1 = RectStruct(x:0.0, y:0.0, width:100.0,height:200.0)
print("最大幅は\(rect1.maxX())です");

11. オプショナル型(Optional)

  • オプショナル型
    • 「?」をつける
    • nilになる可能性があることを示している
    • ラップされた状態
  • 非オプショナル型
    • 「!」をつける
    • nilになる可能性がないことを示している
    • アンラップされた状態
  • オプショナル連鎖
    • オプショナル型のメソッドや変数にアクセスする時は、オプショナルでアクセスする必要がある
// NSURLクラスでの例
NSURL.h
var scheme: String? { get }
var host: String? { get }
var path: String? { get }
var query: String? { get }

MyClass
var url = NSURL(string: "http://www.apple.com")!
var host: String? = url.host // ラップ
var host2: String = url.host! // アンラップ
// var host3: String = url.query // クエリーを設定していないので実行時エラーになる(宣言元にラップ状態の明示が必要)
var host3: String? = url.query
// host.hasPrefix("www") // オプショナル連鎖(?が必要)
host?.hasPrefix("www")

12. おすすめの勉強法

  1. Swift - Overview - Apple Developerをざっくり読む
  2. The Swift Programing Languageを読む(Swift言語ブログが日本語で解説されていて分かりやすい)
    • A Swift Tour: Playground fileからSampleCodeをダウンロード(1.The Swift Programing Languageがplaygroundで閲覧出来る)
    • 気になる点をplaygroundで書く
  3. Using Swift with Cocoa and Objective-Cを読む(CocoaObjective-Cswiftの違いが書かれている)
  4. オススメの書籍

文法もわかりやすく、rssアプリを作成出来ます。基礎の入門書としてオススメです。

他に参考になる資料(2015.12追記)

その他

  • IBM Swift Sandbox(swiftがウェブプラザで動かせるサイト。マックを持ってないけど軽く触ってみたい人にオススメ。)

Web系 システム 開発 セキュリティ 備忘録

f:id:Amarron:20141124135656j:plain

概要

ウェブアプリケーションのセキュリティの基礎や入門的な部分をざっくり書き出しました。
ログイン機能を使う場合やインフラよりのセキュリティは書いていません。

  1. 脆弱性の原因と攻撃パターン
  2. 脆弱性一覧
  3. 入力処理の脆弱性
  4. 表示処理の脆弱性
  5. SQL脆弱性
  6. まとめ

1. 脆弱性の原因と攻撃パターン

脆弱性が生まれる理由

  1. バグによるもの
  2. チェック機能不足によるもの
    • ディレクトリ・トラバーサル等

能動的攻撃と受動的攻撃

  • 能動的攻撃:攻撃者がwebサーバーに対して直接攻撃すること(SQLインジェクション等)
  • 受動的攻撃:攻撃者がwebサーバーに対して直接攻撃せず、Webサイトの利用者に罠を仕掛ける
    • 単純な受動的攻撃:ユーザーが怪しいサイト閲覧してウィルスに感染
    • 正規サイトを悪用する受動的攻撃:攻撃者がwebサーバーに対して不正操作をして、ユーザーが正規サイトに訪れるとウィルスに感染
    • サイトをまたがった受動的攻撃:①ユーザーが罠サイトを閲覧②ユーザーが正規サイトに攻撃のリクエスト送信③正規サイトからJSなどの仕掛けを含むレスポンスが返却

2. 脆弱性一覧

  • 脆弱性には出力に起因するものと処理に起因するものがある
  • 出力に起因する脆弱性には「インジェクション」という単語がつくものが多い
    • インジェクションとは、「"」「'」等の終端マークを混入させ処理や出力の構造を変化させる行為
# 処理の流れ
クライアントブラウザ
   ↓ HTTPリクエスト
サーバー(入力値検証→処理→出力)
   ↓ HTTPレスポンス
クライアントブラウザ

機能と脆弱性の対応

処理 出力 対応アプリ 発生する脆弱性の種類 起因する脆弱性
処理 表示/HTML ブラウザ クロスサイト・スクリプティング
HTTPヘッダ・インジェクション
出力
処理 DB/SQL RDB SQLインジェクション 出力
処理 外部コマンド シェル OSコマンド・インジェクション 出力
処理 メール メール メールヘッダ・インジェクション 出力
処理 ファイル ファイル ディレクトリ・トラバーサル 処理
重要な処理 - - クロスサイト・リクエストフォージェリ 処理
認証 - - セッションフィクセーション 処理
許可 - - 認可不備 処理

インジェクション系脆弱性

脆弱性 インターフェース 悪用手口 データの終端
クロスサイト・スクリプティング HTML JS等の注入 < " など
HTTPヘッダ・インジェクション HTTP HTTPレスポンスヘッダの注入 改行
SQLインジェクション SQL SQL命令文注入 ' など
OSコマンド・インジェクション シェルスクリプト コマンドの注入 ; | など
メールヘッダ・インジェクション sendmailコマンド メールヘッダ、本文の注入・改変 改行

3. 入力処理の脆弱性

  • Webアプリケーションの入力には、HTTPリクエストとしてわたらせるパラメータ(GET,POST,cookieなど)がある
  • 入力処理には次の3つの検証・変換が必要
    1. 文字エンコーディングの妥当性検証
    2. 文字エンコーディングの変換(必要な場合のみ)
    3. パラメータ文字列の妥当性検証(アプリケーションの仕様に基づいた検証)
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力| ←ここ
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ

文字エンコーディングの変換

言語 自動変換 スクリプト記述
PHP pjp.iniなど mb_convert_encoding
Perl - Encode::decode
Java setCharacterEncoding Stringクラス
ASP.NET web.config -

4. 表示処理の脆弱性

  • 表示処理が原因で発生する問題には次の2つがある
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力|
-----
  ↓ HTTPレスポンス
クライアントブラウザ ←ここ

クロスサイト・スクリプティング

  • クロスサイトスクリプティングとは、ウェブページの部分をユーザからの入力をそのままエコーバック(おうむ返し)することによって生成しているアプリケーションのセキュリティ上の不備を利用して、サイト間を横断して悪意のあるスクリプトを注入する攻撃のこと
  • クロスサイト・スクリプティングではユーザーに対して次のような被害がある
    • 攻撃者のスクリプトでクッキー値を盗まれる
    • 攻撃者のスクリプトでWebアプリケーションの機能を悪用される
    • 偽の入力フォームでのフィッシングで個人情報が盗まれる

クロスサイト・スクリプティング対策

  • 必須対策(個別):HTMLの要素内容について[<]や[&]をエスケープする、属性値は「"」で囲い[<]や[&]をエスケープする
  • 必須対策(共通):HTTPレスポンスに文字エンコーディングを明示する
  • 保険的対策:入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効

5. SQL脆弱性

  • SQL脆弱性にはSQLインジェクションがある
  • SQLインジェクションとは、SQLの呼び出しに不備がある場合発生する脆弱性SQLが開発者の意図しない形にに改変され実行されること
  • SQLインジェクション脆弱性がある場合、次のような影響を受ける可能性がある
    • データベース内の全ての情報が盗まれる
    • データベースの内容が書き換えられる
    • 承認を回避される(IDとパスワードを用いずにログインされる)
    • その他、データーバースサーバー上のファイルの読み書き、プログラム実行などが行われる
クライアントブラウザ
  ↓ HTTPリクエスト
-----
|入力|
|処理|
|出力| ←ここ
-----
  ↓ HTTPレスポンス
クライアントブラウザ

脆弱性が生まれる原因

文字列リテラル

  • SQLの標準では、文字列リテラルは「'」で囲む
  • 「'」を使いはみ出した部分で脆弱性が生まれる
# 文字列リテラルの例(BOOKSテーブルから著者がO'Reillyのデータを取得)
SELECT * FROM BOOKS WHERE AUTHOR = 'O'Reilly'
'O'が文字列リテラルで、Reilly'が文字れるリテラルからはみ出した部分
(「SELECT * FROM BOOKS WHERE AUTHOR = 'O';DELETE FROM BOOKS;」みたいな感じで悪用できる)

数値リテラル

  • 文字列リテラル同様、「;」で区切りそのあとに意図しない形のSQLを実行できる
# 数値リテラルの例
SELECT * FROM BOOKS WHERE MONEY <= 20000;DELETE FROM BOOKS
20000が数値リテラル、それ以降が脆弱性を含む意図しない形のSQL

対策

  • プレースホルダSQL分の「?」に値が代入されるやつ)でSQLを組み立てる
  • アプリケーション側でSQLを組み立てる際にリテラルを正しく構成するなどSQL文を変更されないようにする

6. まとめ

  • 入力値のインジェクション対策はかならず行う(「"」「'」「;」等を含んだ入力値でテストする)
  • HTMLの要素内容について[<]や[&]をエスケープする
  • HTTPレスポンスに文字エンコーディングを明示する
  • 入力値検証、クッキーにHttpOnly属性を付与、TRACEメソッドを無効
  • おすすめ書籍

セキュリティの事がひと通り書いてあり、サイトを作成する前に考慮すべき考え事がわかりやすく書いてあります。

iOS SNS シェア 実装方法

概要

SNS等のシェア機能の実装方法の説明です。 実装内容はシェアボタン押下時にアクションシートを表示し、シェアしたい内容を選びシェアするような実装内容です。
Webページを表示しシェアしていますが、それ以外でも同じような実装方法でシェア可能です。

シェア先は次の6個!(mixiは登記簿謄本が必要な為今回は対象外としました)

実装結果イメージ(「safari」と「URLをコピー」はおまけ)

f:id:Amarron:20140828232150p:plain

この記事では、「UIApplication sharedApplication」で実装しています。
iOS6移行はUIActivityViewControllerで実装するのが楽だと思うので、iOS6移行は他の記事を参考にする方がお勧めです!

前提条件

TWITTERFACEBOOKiOS6以上)

Xcodeのライブラリーに「Social.framework」を追加する必要があります。
(TARGETS->MyApp->Build Phases->Link Binary With Libraries)

Google+EverNote

Google+EverNoteは、SDKやサービスの登録が必要となります。
EverNoteはサービス登録が必要になるので、少し時間と手間がかかります)

簡単な概要は次の通りです。

Google+

名称 Google+ Platform
URL https://developers.google.com/+/
必要なライブラリ Security.framework,SystemConfiguration.framework
手順 1. APIs Console プロジェクトを作成する
2. SDK をダウンロードして、サンプル アプリを実行する
3. SDK のダウンロードとインストール
4. プログラム修正
参考URL スタートガイド(iOS)
Googleでログイン
インタラクティブな投稿の作成
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。

EverNote

名称 EverNote Developers
URL https://dev.evernote.com/intl/jp/
必要なライブラリ Security.framework
手順 1.サンドボックスのアカウント登録(アプリごとで共通のアカウントを利用する)
2. Evernote API キー取得(アプリごと別々に取得)
3. SDK のダウンロードとインストール
4.プログラム修正・サンドボックス上のEverNoteでテスト
5.サンドボックスからプロダクションに変更依頼(アクティベーション
6.プログラム修正(プロダクションに変更(ホストを「BootstrapServerBaseURLStringSandbox」→「BootstrapServerBaseURLStringUS」))
参考URL スタートガイド
サンドボックス
アクティベーション(プロダクションAPIキーの有効化)
よくある質問
備考 開発用と製品用でIDが異なる。使えないhtmlタグがある。
アクティベーションの申請時にはgoogleアカウントは使用できないので個人名・個人用メールアドレスで依頼
アクティベーションの承認待ち時間は約2〜3日

プログラム

流れ

  1. アクションシートの生成
  2. アクションシートの表示(シェアボタン押下時)
  3. アクションシートの選択した内容でシェア

サンプルコード

#import <Social/Social.h>
#import "EvernoteSession.h"
#import "EvernoteUserStore.h"
#import "EvernoteNoteStore.h"
#import "GTLPlusConstants.h"
#import "GPPShare.h"

@implementation MainController
    UIActionSheet *_actionsheet;
    UIWebView *_webView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // 1. アクションシートの生成
    _actionsheet = [[UIActionSheet alloc] initWithTitle:@"共有"
                                               delegate:self
                                      cancelButtonTitle:@"キャンセル"
                                 destructiveButtonTitle:nil
                                      otherButtonTitles:@"LINE", @"Twitter", @"Facebook", @"Google+", @"メール", @"Safari", @"Evernote", @"URLをコピー", nil];
    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.co.jp"]]];

}

#pragma mark - IBAction
// 2. アクションシートの表示(シェアボタン押下時)
- (IBAction)shareAction:(id)sender
{
    if([[UIDevice currentDevice].model isEqualToString:@"iPad"]){
        [_actionsheet showFromBarButtonItem:sender animated:YES];
    } else {
        [_actionsheet showInView:self.view];
    }
}

#pragma mark UIActionSheetDelegate
// 3. アクションシートの選択した内容でシェア
-(void)actionSheet:(UIActionSheet*)actionSheet
clickedButtonAtIndex:(NSInteger)buttonIndex {
    NSURL *url = [NSURL URLWithString:[_webView stringByEvaluatingJavaScriptFromString:@"document.URL"]];
    NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; 

    if (0 == buttonIndex) { // LINE
        NSString *contentURL = [self _escapeURL:[url absoluteString]];
        NSString *contentTitleText = [self _escapeURL:title];
        NSURL *lineURL = [NSURL URLWithString:[NSString stringWithFormat: @"http://line.naver.jp/R/msg/text/%@?%@", contentURL, contentTitleText]];
        [[UIApplication sharedApplication] openURL:lineURL];
           
        
    } else if (1 == buttonIndex) { // TWITTER
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];
        
    } else if (2 == buttonIndex) { // FACEBOOK
        SLComposeViewController *postVC = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
        [postVC setInitialText:title];
        [postVC addURL:url];
        [self presentViewController:postVC animated:YES completion:nil];

    } else if (3 == buttonIndex) { // Google+
        id<GPPShareBuilder> shareBuilder = [[GPPShare sharedInstance] shareDialog];
        [shareBuilder setPrefillText:title];
        [shareBuilder setURLToShare:url];
        [shareBuilder open];
        
    } else if (4 == buttonIndex) { // メール
        NSString *conSbjText = [self _escapeURL:title];
        NSString *conBodyText = [self _escapeURL:[url absoluteString]];
        NSString *mail = [NSString stringWithFormat:@"mailto:%@?Subject=%@&body=%@", @"", conSbjText, conBodyText];
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mail]];
        
    } else if (5 == buttonIndex) { // Safari
        [[UIApplication sharedApplication] openURL:url];
        
    } else if (6 == buttonIndex) { // EverNote
        EvernoteSession *session = [EvernoteSession sharedSession];
        [session authenticateWithViewController:self completionHandler:^(NSError *error) {
            if (!error && session.isAuthenticated){
                // We're authenticated!
                EvernoteUserStore *userStore = [EvernoteUserStore userStore];
                [userStore getUserWithSuccess:^(EDAMUser *user) {
                    // success
                    NSLog(@"Authenticated as %@", [user username]);
                    
                    EDAMNote *note = [[EDAMNote alloc] init];
                    
                    note.title = title;
                    
                    NSString *conDes = [_webView stringByEvaluatingJavaScriptFromString:
                                          @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].name=='description') return l[i].content}};a();"];
                    NSString *conImg = [_webView stringByEvaluatingJavaScriptFromString:
                                        @"function a(){l=document.getElementsByTagName('meta');for(i=0;i<=l.length;i++){if(l[i].getAttribute('property')=='og:image') return l[i].content}};a();"];
                    
                    NSString *enNote = [NSString stringWithFormat:@"<p>%@</p><a href='%@'></a>%@<img src='%@' />",
                                        conDes,
                                        [self _escapeXML:[url absoluteString]],
                                        [self _escapeXML:[url absoluteString]],
                                        conImg];
                    note.content = [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\"><en-note>%@</en-note>", enNote];
                    
                    EvernoteNoteStore *store = [[EvernoteNoteStore alloc] initWithSession:[EvernoteSession sharedSession]];
                    [store createNote:note success:^(EDAMNote *note) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteに保存しました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    } failure:^(NSError *error) {
                        [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
                    }];
                } failure:^(NSError *error) {
                    [[[UIAlertView alloc] initWithTitle:@"Evernoteの保存に失敗しました。" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];

                } ];
            }
        }];
    } else if (7 == buttonIndex) { // コピー
        [[UIPasteboard generalPasteboard] setValue:[url absoluteString] forPasteboardType:@"public.text"];
        [[[UIAlertView alloc] initWithTitle:@"URLをコピーしました。" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

#pragma mark - private methods
- (NSString *)_escapeURL:(NSString *) url
{
    return (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                         (__bridge CFStringRef)url,
                                                                         NULL,
                                                                         (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                         kCFStringEncodingUTF8 );

}
- (NSString *)_escapeXML:(NSString *) xml
{
    xml = [xml stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"\""withString:@"&quot;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"'"withString:@"&apos;"];
    xml = [xml stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"];
    xml = [xml stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"];
    return xml;
}

エンコードについて

Line、メール、EverNoteは、直接URLをシェア出来ません。
シェアするためには、htmlのエスケープが必要になります。
urlのエスケープ(line,mail)→16進表記(文字参照
xmlのエスケープ(EverNote)→実態参照

文字 実体参照 10進表記(文字参照 16進表記(文字参照
"&" &amp; &#38; &#x26;
""" &quot; &#34; &#x22;
"<" &lt; &#60; &#x3C;
">" &gt; &#62; &#x3E;
" " &nbsp; &#160; &#xA0;