まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2015/01

NHK が最近また流行りだしたワンクリック詐欺サイトのニュースを流していました:
スマホに「消せないメッセージ」 注意を


(2015/02/06 追記)
NHK のニュースページが消えてしまい、リンク切れになっていたので、同様のニュースを紹介しているページのリンクを貼っておきます:
【注意!】スマホに消せない画面を表示し金を奪うサイトが続出!
(2015/02/06 追記終わり)


ニュースの内容によると、どうもこれは
 ・(アプリとかではなく)特定のウェブページを
 ・スマホを使って
 ・閲覧した瞬間に
起こる仕組みがある、というものでした。

試しに見てボタンをクリックしたら、ではなく、見た瞬間に発生するらしいです。なので詐欺とわかっていても何が起こるかわからないので試してみるのも怖い、、、、ですよね。

せっかくなので(笑)、勇気を出して比較的安全と思われる方法で何をやっているのかを調べてみました。

このニュースで報じられていたサイトは、どうやら http://XXXXX-avnavi.net/ というサイトです(XXXXX 部分は僕の判断で伏せ字にしてます)。 結論から言うと、このサイトは PC のブラウザでアクセスすれば、単に JavaScript の alert が繰り返し表示されるだけの迷惑ページで済むのですが、最初は何が起こるかわからないので PC ブラウザも使いません。Linux 環境から基本の wget でソースコードだけいただきます:
(トップページの HTML ソースコードだけいただいて X.html という名前で保存)
# wget http://XXXXX-avnavi.net/ -O X.html

取り出した内容を vi で表示します。何よりも驚いたのは丁寧でわかりやすいコメントが大量に書かれていました。おかげで何をやっているのか、とても理解しやすかったです(笑)。この姿勢に限っては真似してもいいと思う。

さてその内容ですが、まず最初にこんな JavaScript コードが実行されてました(コメントもそのまま):
<script>
// History API が使えるブラウザかどうかをチェック
if( window.history && window.history.pushState ){
  //. ブラウザ履歴に1つ追加
  history.pushState( "nohb", null, "" );
  $(window).on( "popstate", function(event){
    //. このページで「戻る」を実行
    if( !event.originalEvent.state ){
      //. もう一度履歴を操作して終了
      history.pushState( "nohb", null, "" );
      return;
    }
  });
}
</script>

非常に丁寧なコメントですよね~(苦笑)。実を言うとこの時点で「おや?」と気付くこともあったのですが、その内容は後述します。

肝心な内容は JavaScript で HTML5 の History API が利用できるブラウザかどうかをチェックした上で、popState/pushState を操作して「戻る」を実行しても戻れないように(要するに「戻る」の無効化)操作しています。このページを見て、怖くなって「戻る」を押しても戻れないように予め設定されてしまうのです。用意周到ですね。。

ちなみにここのテクニックに関しては以前に僕のブログエントリでも紹介しています。興味ある方はそちらを参照ください:
HTML5 の pushState/popState でヒストリバックを無効にする


そして、ページを戻れなくした上で行っているのがこちらのコードです。赤字部分は僕が加えた説明コメントです:
<script type="text/javascript">
client_id1 = getCookie('id'); //. クッキーから(別途設定した) ID を取得
console.log("in template"); //. この2行要らないと思う
console.log(client_id1);
var HogeTimer = setInterval("hogemoge()",1000); //. 1秒毎に以下を実行
function hogemoge(){
(この一行があまりにも長くて見にくかったので改行しています。
 問い合わせ先電話番号 NNNNNNNNNNNNN はこちらで伏せ字にしました)

alert('【御登録完了】\nお申込み承諾致しました。\n 動画再生準備完了中。\n\n 【18禁アダルト動画サイト】\n 【365日間の視聴期間】\n\n ★只今お客様還元祭★\n ★キャンペーン期間中で割引適用中★\n 99800円\n 会員ID:' +client_id1 +'\n問い合わせ先\nNNNNNNNNNNNNN\n ※キャンペーン期間内にご精算下さい※\n\n ※誤作動登録の場合※\n →24時間以内に登録削除(自動処理)へご連絡ください。\n\n ※24時間経過後※\n→誤作動登録の場合でもご精算頂きます。');
//. ↑怖いメッセージを表示した後に、、 location.href = "tel:NNNNNNNNNNNNN"; //. スマホの場合、ここで電話をかけようとする

//. 終了しても1秒後にメッセージ表示から繰り返される } </script>

まず別途設定したクッキーを使って、お客様番号っぽい情報を作り、それを取り出します(お客様番号でも何でもありません)。 その直後の console.log 2行は気持ちは分かるけど消しておいた方がいいと思う(苦笑)。

そして JavaScript の setInterval を使って、1000ミリ秒(1秒)毎に実行する処理を指定しています。ここでいう「1秒毎」は、この処理が実行されて、最後まで実行して、そこから1秒たったらまた初めから実行、です。要は1秒の間をとりながら延々と繰り返される処理、ということになります。ここまではよくあるブラクラと同じ原理です。

気になる肝心の処理内容ですが、(1)怖いメッセージを表示して(「OK」ボタンだけ表示されるので押すしかない)、(2)「OK」を押すと(スマホでは)特定の番号に国際電話をかけようとする です。実際には「電話をかけますか?」という確認メッセージが表示されて、かけるかどうかを選択できるのですが、書けずに終了しても、1秒後にまた (1) へ戻るだけです。ちなみに電話をかけても (1)へ戻ります。

「怖い」と感じて、前のページに戻ろうとしても前述のように「戻る」は無効化されています。普通にホームボタンを押してブラウザを閉じてしまうことはできますが、(別の目的で)ブラウザを使おうとすると、履歴が残っているのでまたこの画面に戻ってしまいます。パニックにさせて、「電話をかけるしかないのか?」と思わせようとしてるんですかね。ちなみに電話をかける実験まではしてないので、どういう内容かはわかりません。 (^^;


もしも、間違ってこのページを PC ブラウザで開いてしまった場合は、CTRL+ALT+DEL キーを押してタスクマネージャーを出すなどして、ブラウザごと終了してしまいましょう。

スマホでこのページを見ちゃった場合はこちらのページを参考に履歴ごと消してしまってください:
http://did2memo.net/2013/11/15/iphone-endless-pop-up-message-page/



サイトの仕組みの説明は以上です。この下は気付いた雑感を2つほど。

まず、このページの HTML / JavaScript は非常に読みやすいです、メンテナンスしやすそう(苦笑)。ある意味で優秀なエンジニアが関わってるんだろうなあ、と思いました。デバッグ用と思われる console.log が残っているのはご愛嬌ですが、JavaScript 以外の HTML でも例えばこんな感じの記述がされています:
2015013101


いやあ、わかりやすい(笑)。 <div> でちゃんとパーツ化されている上に HTML コメントが非常に適切で、実際の画面を見なくてもどういうページを作ろうとしているのか想像できます。感心してる場合じゃないけど、これ作った人とは話が合いそう。。 (^^;

そしてもう1つ気になったこと。それは上記で紹介した僕のブログエントリ
HTML5 の pushState/popState でヒストリバックを無効にする

で紹介している JavaScript の内容と、このサイトで実際に使われている JavaScript の内容が変数やコメントの使い方のレベルで非常に似ている、というか似すぎている!? あれ?もしかして参照してくれた!? ということに気付いてしまいました。 良い子はこういう使い方に応用しないでね。 d(o^ )

#念のため補足しておくと、僕が紹介した無効化は問い合わせフォームなどで 入力→確認→送信 みたいなページ遷移をする時に 送信後のページから確認ページに戻られると色々不都合があるので、そういった挙動をさせなくするための手段として紹介したつもりでした。


というわけで、メンテナンス性も含めた色んな意味で意外と完成度の高い詐欺サイトでした。




 

Android の開発環境、多くの人は Windows か MacOS X 上で構築すると思いますが、天邪鬼なので Linux(CentOS) で構築してみました。ちょっとコツが必要だったので、その備忘録です:

【環境】
OS: CentOS 6.6(64bit)
  (yum で X Window System, Desktop, JDK 1.7, firefox 導入済み) 
Eclipse: Eclipse LUNA(4.4.1)
  IDE for Java Developers を http://www.eclipse.org/ からダウンロード 

↑普通に CentOS 上で Eclipse + Java の開発環境を用意します。ここから先を説明します。


まずは Android SDK Manager をダウンロードして用意します:
http://developer.android.com/sdk/

↑ここから "SDK Tools Only" と書かれた箇所の Linux 用 Package の最新版をダウンロードします。ここでは android-sdk-r24.0.2-linux.tgz というファイルを /tmp にダウンロードしたと仮定します。

これを展開します:
# cd /usr/local/src
# tar xzvf /tmp/android-sdk-r24.0.2-linux.tgz

Path に tools ディレクトリを追加します:
# vi /etc/bashrc
  :
  :
export PATH=${PATH}:/usr/local/src/android-sdk-linux/tools
(↑この一行を最後に追加して保存)

Eclipse にプラグインを追加します。Eclipse を起動し、メニューから Help - Install New Software を選択して、"http://dl.google.com/android/eclipse/" を追加して、全ツールをインストールします。


全ツールインストール後に Eclipse の再起動が促され、再起動すると Android SDK コンポーネントのインストールが促されます:
2015012701


"Open Preferences" をクリックするとプリファレンスが開きます。ここで Android の SDK Location 上記フォルダを指定して、Apply をクリックします。
2015012702


ここで SDK Manager 経由でのビルドツールのインストールが促されます。"Open SDK Manager" をクリックします:
2015012703


Android SDK Manager が開きます:
2015012704


利用可能な項目のうち、ビルド対象にするバージョンを選択し、チェックを入れてインストールします。この辺りは下記の参考サイトを参照ください。


で、ここからが今回自分で体験して分かった内容です。実はこれだけだと Eclipse 内でエミュレーターの起動ができませんでした。adb や ddms の起動もできません。

僕の場合はライブラリが足りないことが原因でした。以下を実行して解決しました:
# yum install ld-linux.so.2
# yum install libstdc++.so.6
# yum install libz.so.1

2015012705



(参考)http://android.keicode.com/devenv/install-sdk-linux.php


 

Tomcat や Jetty などの Java ウェブアプリケーションでエラーが発生した時に表示されるエラーページを独自のものにカスタマイズする方法の紹介です。もちろんエラーが発生しないことが望ましいのですが、万が一エラーが発生してしまった場合に、標準のエラーメッセージが出てしまうと、どのアプリケーションサーバーを使っているのかが分かってしまいます。その結果、そのサーバーのセキュリティホールを狙われる可能性もないわけではありません。あとエラーページにユーモアを交えるような目的でも有用だと思います。

ではその手順の紹介です。 まずアプリケーションの web.xml の最後に以下の青字の情報を追加します:
  :
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <error-page>
    <error-code>404</error-code>
    <location>/error/404.html</location>
  </error-page>
  <error-page>
    <error-code>500</error-code>
    <location>/error/500.jsp</location>
  </error-page>
</web-app>

この例ではステータスコードが 404(ページが見つからないエラー)の時と、500(サーバー内部エラー)の時用のページをカスタマイズする前提で、その2つのページに関する情報を追加しています。他のエラー(認証など)についてもカスタマイズする場合は同様に追加してください。この例ではページが見つからない場合は /error/404.html に、Java コード等での内部エラー発生時には /error/500.jsp にそれぞれ飛ばすような指定をしています。もちろん静的な HTML ページでもいいのですが、今回は 500 エラーの時には JSP にして動的に作成してみます。

そしてこれらのページを作っていきます。/error/404.html は 404 エラー、つまり URL が間違っていることになるので、こんな感じの内容で:
<html>
<head>
<title>404</title>
</head>
<body>
<h1>見つからないよ~</h1>
</body>
</html>

一方、/error/500.jsp は 500 エラー、つまり Java の内部エラーなので、スタックトレースも表示するような内容にしてみます。試しにこんな感じにしました。また isErrorPage を true にしています:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ page import="javax.servlet.http.*" %>
<%@ page import="javax.servlet.*" %>
<% request.setCharacterEncoding("utf-8"); %>
<%@ page isErrorPage="true"%>

<html>
<head>
<title>500</title>
</head>
<body>
<h1>Internal な Server の Error だよ~</h1>

<% exception.printStackTrace(new java.io.PrintWriter(out)); %>


</body>
</html>

最後に、わざと 500 エラーを発生させるための JSP ページを作成します:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ page import="javax.servlet.http.*" %>
<%@ page import="javax.servlet.*" %>
<% request.setCharacterEncoding("utf-8"); %>

<%
String[] a = null;
for( int i = 0; i < a.length; i ++ ){ //. ここでぬるぽエラー
}
%>

<html>
<head>
<title>index</title>
</head>
<body>
<h1>Index</h1>

<% exception.printStackTrace(new java.io.PrintWriter(out)); %>


</body>
</html>

この状態で動かしてみます。まず存在しないページの URL を叩くとこんな感じのエラーになります:
2015012706


次に上記で作成したわざと NullPointer エラーがでるページにアクセスするとこんな感じに:
2015012707


できた!
 

WebView は、「ネイティブアプリに内蔵して使うブラウザ」です。

HTML5 をベースにするなどして、ネイティブアプリっぽい見栄えになったウェブアプリケーションであっても、利用するには URL を打ち込むか、ブックマークから選択するなどして(つまり、あくまでウェブページとして)利用することになります。

でも、そのようなウェブアプリは簡単にネイティブ化する方法があります。それが上述の WebView を使う方法です。WebView 自体はネイティブアプリケーションに組み込んで使う部品ですが、例えば「全画面 WebView だけを表示するネイティブアプリ」を作れば、(メニューの表示項目は何にするか、JavaScript を有効にするか無効にするか、最初に表示するページの URL はどうするか・・・などの設定項目はありますが)それはウェブブラウザでウェブページを見ているのと大きく変わりません。モバイルUIに対応したウェブページを比較的簡単にネイティブアプリ化する方法、といえます。

実際に自分も Android アプリ開発の中で使ってみる機会があったのですが、そこで想定外のことに1つ躓きました。それがタイトルにある「何も考えずに Android の WebView を使うと viewport が無視される」ということです。

これがバグ扱いなのか、何らかの理由があっての仕様なのかもよくわかりませんが、ともあれ何故か viewport は無視されてしまいます。

そしてそれが原因で、本来モバイルUIで表示されるべきページが、WebView を使って見た時だけモバイル端末と判断されず、PC 用の UI で表示されてしまう、という現象が起こってしまいます。

で、これを回避するための方法がこちらです。色々設定してますが、今回の肝は青字部分の2つ:
(1) 読み込み時にページ横幅を画面幅に無理やり合わせる
(2) ワイドビューポートへの対応
public class MainActivity extends Activity{
  public WebView webView = null;

  protected void onCreate(Bundle savedInstanceState){
      :
      :
    super.onCreate( savedInstanceState );
    setContentView( R.layout.activity_main );

    webView = ( WebView )findViewById( R.id.webView1 );

    //. JavaScript 有効
    webView.getSettings().setJavaScriptEnabled( true );

    //. リンクタップ時に標準ブラウザを使わない
    webView.setWebViewClient( new WebViewClient() );

    //. (1) 読み込み時にページ横幅を画面幅に無理やり合わせる
    webView.getSettings().setLoadWithOverviewMode( true );

    //. (2) ワイドビューポートへの対応
    webView.getSettings().setUseWideViewPort( true );

      :
      :
    //. 初期URLを指定してロード
    webView.loadUrl( "http://neppi.co/" );
  }

  :
  :
}

これで viewport を読んでいる時と同様に画面幅を強制的に変更して、本来の(モバイルUIの)ページに対応させることができました。

なお、WebView に関しては Android 4.3 以下の(WebKit ベースの)WebView については Google による脆弱性サポートが提供されなくなる、というニュースがありました(4.4 以上の Chrome ベースの WebView に対してのみサポート継続):
Google No Longer Provides Patches for WebView Jelly Bean and Prior

4.3 以下ってまだ結構多いはず。そして WebView を使ってるアプリってどのくらいあるんだろ?? その意味でも少し気になるニュースではあります。







 

ウェブサーバー(HTTP サーバー)を使う場合、なんらかのアクセスログを残すことになります。例えば Apache HTTPD の場合、デフォルトでアクセスログは /var/log/httpd/access_log に残ります。そのフォーマットは /etc/httpd/conf/httpd.conf 内で設定/カスタマイズできます。
2015012200


デフォルトの httpd.conf では、以下の様なログフォーマットが指定されています:
LogFormat "%h %l %u %t \"%r\" %>s %b common

暗号のような記述ですが、左から順にこのような意味があります:
 %h : 接続元のリモートホスト名(IPアドレス)
 %l : クライアント識別子(取得できない場合は - )
 %u : 認証時のユーザー名(取得できない場合は - )
 %t : 時刻
 %r : リクエストの最初の行の値
 %s : レスポンスステータス
 %b : 送信バイト数(0バイトの場合は - )

このログフォーマットであれば、以下の様なログが取得できることになります:
192.168.1.101 - - [20/Jan/2015:11:32:29 +0900] "GET / HTTP/1.1" 200 -


最初の値がアクセス元のホストになります。つまりこれで(プロクシが使われている場合はプロクシのアドレスになりますが)どこからのアクセスがあったのか、という情報が分かります。


ところが、このアクセス元ホストが分からなくなる場合があります。それは HTTP サーバーがロードバランサ経由で使われていた場合です。サーバーの負荷軽減などの目的で、サーバーを複数台構成にしてロードバランサ経由で利用する、というケースは珍しくないと思います(クラウド環境によっては1台構成でもロードバランサ経由になることもあります)。ただその場合、この設定だとアクセス元ホストは必ずロードバランサの IP アドレスになってしまい、結果として常に同じホストからのアクセスがあったとログに記録されることになります。

これを避けるには httpd.conf をカスタマイズする必要があります。具体的にはこんな感じに:
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b common


%h の代わりに %{X-Forwarded-For}i という記述に変えました。この意味ですが、まず %{XXX}i という記述は「リクエストヘッダに含まれるヘッダー名 XXX の値」です。つまりこの記述は「リクエストヘッダに含まれる X-Forwarded-For ヘッダの値」をログに書き出すよう指示していることになります。

そしてリクエストヘッダの X-Forwarded-For ですが、これはプロクシやロードバランサなどのキャッシングサーバーに接続するクライアントの送信元 IP アドレスです。つまりキャッシングサーバーから見た時の %h の値がこのヘッダ値に入ってくるので、その値を取り出してログに記録すればよい、ということになるわけです。 そこで上記のような設定をすることでロードバランサ経由であっても、元のクライアントの IP アドレスを記録できるようになるのでした。

なお、この方法は HTTP プロトコルに限って有効です。HTTPS では暗号化によって HTTP ヘッダに送信元 IP アドレスが記録できるかどうか、ロードバランサによって変わってきてしまいます。お使いのロードバランサが HTTPS をサポートしているか、サポートしていない場合の対応方法などはお使いのクラウド業者に問い合わせる必要があると思っています。



 

このページのトップヘ