0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WebViewで基本認証させる際の注意点

Last updated at Posted at 2022-01-13

最初に結論

AndroidアプリのWebViewで基本認証させようと思って検索するとよく見かけるサンプルコードについて、これではコンテンツに埋め込まれている外部サイトのオブジェクト(画像とかロギングツールとか広告オブジェクトとか)へ認証情報を流してしまうかもしれないので、注意が必要。
つまり、**認証情報を提示するホスト/URLを、きちんと制限しましょう。**という至極当然の結論。


よく見るサンプルコード

webView.setWebViewClient(new WebViewClient(){
 @Override
 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
  handler.proceed("ユーザ名", "パスワード");
 }
});

つまり、WebViewClientクラスの「onReceivedHttpAuthRequest」をオーバーライドして、ユーザ名とパスワードを与えてあげればいい。

というもの。

まぁ、確かにそうなんだが、これでは、任意のホストの任意のレルムに認証情報を流してしまうかもしれないので、注意が必要。

webView0.png


実際に試してみる


環境

こんな感じ

webView1.jpg

トップの192.0.2.1のコンテンツ内部に、192.0.2.2のコンテンツ**(広告の画像とか、外部スクリプトとか)**を参照しているような場合。

192.0.2.2 側で故意に認証要求させると認証情報がそちらに漏れるかもしれない。


アプリのコード

protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 // これはトップのhttp://192.0.2.1:90/Auth/test.htmqだけに
 // 関係する認証情報のはず、だと思ってコーディングしていると思う
 String username = "sanaki";
 String password = "password123!";
 //
 WebView webView = (WebView)findViewById(R.id.webView);
 webView.setWebViewClient(new WebViewClient(){
  @Override
  public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
   handler.proceed(username, password);
   Log.i("BasicAuth", "Host=" + host + ",realm=" + realm);
  }
 });
 // JavaScriptを有効化
 webView.getSettings().setJavaScriptEnabled(true); // JavaScriptを有効にする
 //
 webView.loadUrl("http://192.0.2.1:90/Auth/test.htm");
}

こんな感じ。
アプリを起動すると「192.0.2.1:90」にアクセスして、そのコンテンツを表示する。というやつ。

ネットにアクセスするために
AndroidManifest.xmlに
「<uses-permission android:name="android.permission.INTERNET"/>」
と、(検証のためにhttpsにするのは面倒なので)平文のhttpをWebViewで許可するために
「android:usesCleartextTraffic="true"」
は記述している

まぁ、基本認証をしているということは、主に管理系のWebサイトだと思うけど、管理系に広告とかアクセス履歴系のやつとか埋め込んでいるのも、まぁ、たしかに想定としておかしな話かもしれんけどねぇ~


結果(まとめ)

きちんと、192.0.2.2側にも認証情報が洩れている事が、下記から確認できると思う。


結果(ログ)

Logの方には、こんな感じで、ログが出てる。

2022-01-12 15:36:06.175 18740-18740/jp.dip.rocketeer.webviewtest I/BasicAuth: Host=192.0.2.1,realm=192.0.2.1
2022-01-12 15:36:06.379 18740-18740/jp.dip.rocketeer.webviewtest I/BasicAuth: Host=192.0.2.2,realm=192.0.2.2

子フレーム側の192.0.2.2側にも認証情報が洩れているということになるだろう。


結果(192.0.2.1側{親、想定しているホスト側})

192.0.2.1 の方は、こんな感じの通信内容となっている。

RAW 通信データ
C:\>StreamRelay.NET.exe -localport 90 -remoteport 80 -remotehost 127.0.0.1 -logging GET /Auth/test0.htm HTTP/1.1 Host: 192.0.2.1:90 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9

HTTP/1.1 401 Unauthorized ← 192.0.2.1側のこれは想定している認証要求
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
WWW-Authenticate: Basic realm="192.0.2.1"
Date: Wed, 12 Jan 2022 07:11:14 GMT
Content-Length: 6396

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IIS 10.0 エラーの詳細 - 401.2 - Unauthorized</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana,Arial,Helvetica,sans-serif;}
code{margin:0;color:#006600;font-size:1.1em;font-weight:bold;}
.config_source code{font-size:.8em;color:#000000;}
pre{margin:0;font-size:1.4em;word-wrap:break-word;}
ul,ol{margin:10px 0 10px 5px;}
ul.first,ol.first{margin-top:5px;}
fieldset{padding:0 15px 10px 15px;word-break:break-all;}
.summary-container fieldset{padding-bottom:5px;margin-top:4px;}
legend.no-expand-all{padding:2px 15px 4px 10px;margin:0 0 0 -12px;}
legend{color:#333333;;margin:4px 0 8px -12px;_margin-top:0px;
font-weight:bold;font-size:1em;}
a:link,a:visited{color:#007EFF;font-weight:bold;}
a:hover{text-decoration:none;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.4em;margin:10px 0 0 0;color:#CC0000;}
h4{font-size:1.2em;margin:10px 0 5px 0;
}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS",Verdana,sans-serif;
color:#FFF;background-color:#5C87B2;
}#content{margin:0 0 0 2%;position:relative;}
.summary-container,.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
.content-container p{margin:0 0 10px 0;
}#details-left{width:35%;float:left;margin-right:2%;
}#details-right{width:63%;float:left;overflow:hidden;
}#server_version{width:96%;_height:1px;min-height:1px;margin:0 0 5px 0;padding:11px 2% 8px 2%;color:#FFFFFF;
background-color:#5A7FA5;border-bottom:1px solid #C1CFDD;border-top:1px solid #4A6C8E;font-weight:normal;
font-size:1em;color:#FFF;text-align:right;
}#server_version p{margin:5px 0;}
table{margin:4px 0 4px 0;width:100%;border:none;}
td,th{vertical-align:top;padding:3px 0;text-align:left;font-weight:normal;border:none;}
th{width:30%;text-align:right;padding-right:2%;font-weight:bold;}
thead th{background-color:#ebebeb;width:25%;
}#details-right th{width:20%;}
table tr.alt td,table tr.alt th{}
.highlight-code{color:#CC0000;font-weight:bold;font-style:italic;}
.clear{clear:both;}
.preferred{padding:0 5px 2px 5px;font-weight:normal;background:#006633;color:#FFF;font-size:.8em;}
-->
</style>

</head>
<body>
<div id="content">
<div class="content-container">
<h3>HTTP エラー 401.2 - Unauthorized</h3>
<h4>認証ヘッダーが無効なため、このページを表示することができません。</h4>
</div>
<div class="content-container">
<fieldset><h4>可能性のある原因:</h4>
<ul> <li>IIS は認証プロトコル (匿名のものも含む) を選択しません。</li> <li>統合認証のみ有効です。統合認証をサポートしないクライアント ブラウザーが使用されました。</li> <li>統合認証は有効で、Web サーバーに到達する前に認証ヘッダーを変更するプロキシ サーバーをとおして要求が送信されています。</li> <li>Web サーバーは匿名アクセスに対して構成されてなく、必要な認証ヘッダーが受信されませんでした。</li> <li>"configuration/system.webServer/authorization" 構成セクションが明示的にユーザーのアクセスを拒否しています。</li> </ul>
</fieldset>
</div>
<div class="content-container">
<fieldset><h4>対処方法:</h4>
<ul> <li>リソースに対する認証設定を確認した後、その認証方法を使用するリソースを要求します。</li> <li>クライアント ブラウザーが統合認証をサポートしていることを確認します。</li> <li>統合認証が使用される場合、要求がプロキシ サーバーをとおしていないことを確認します。</li> <li>"configuration/system.webServer/authorization" 構成セクションによって、ユーザーが明示的にアクセスを拒否されていないことを確認します。</li> <li>この HTTP 状態コードに対して失敗した要求を追跡するトレース規則を作成します。失敗した要求のトレース規則の作成の詳細については、<a href="http://go.microsoft.com/fwlink/?LinkID=66439&quot;&gt;ここ&lt;/a&gt;をクリックします。&lt;/li> </ul>
</fieldset>
</div>

<div class="content-container">
<fieldset><h4>エラー情報の詳細:</h4>
<div id="details-left">
<table border="0" cellpadding="0" cellspacing="0">
<tr class="alt"><th>モジュール</th><td>&nbsp;&nbsp;&nbsp;IIS Web Core</td></tr>
<tr><th>通知</th><td>&nbsp;&nbsp;&nbsp;AuthenticateRequest</td></tr>
<tr class="alt"><th>ハンドラー</th><td>&nbsp;&nbsp;&nbsp;StaticFile</td></tr>
<tr><th>エラー コード</th><td>&nbsp;&nbsp;&nbsp;0x80070005</td></tr>

</table>
</div>
<div id="details-right">
<table border="0" cellpadding="0" cellspacing="0">
<tr class="alt"><th>要求された URL</th><td>&nbsp;&nbsp;&nbsp;http://192.168.0.11:80/Auth/test0.htm&lt;/td&gt;&lt;/tr>
<tr><th>物理パス</th><td>&nbsp;&nbsp;&nbsp;C:\inetpub\Rocketeer\Auth\test0.htm</td></tr>
<tr class="alt"><th>ログオン方法</th><td>&nbsp;&nbsp;&nbsp;未定義です</td></tr>
<tr><th>ログオン ユーザー</th><td>&nbsp;&nbsp;&nbsp;未定義です</td></tr>

</table>
<div class="clear"></div>
</div>
</fieldset>
</div>

<div class="content-container">
<fieldset><h4>詳細情報:</h4>
このエラーは、Web サーバーに送信された WWW-Authenticate ヘッダーがサーバーの構成でサポートされていないときに発生します。リソースの認証方法を確認し、クライアントが使用している認証方法を確認します。それらの認証方法が異なる場合、このエラーが発生します。クライアントが使用している認証の種類を判断するには、クライアントの認証設定を確認します。
<p><a href="https://go.microsoft.com/fwlink/?LinkID=62293&amp;amp;IIS70Error=401,2,0x80070005,17763&quot;&gt;詳細情報の表示 &raquo;</a></p>
<p>マイクロソフト サポート技術情報の記事:</p>
<ul><li>907273</li><li>253667</li></ul>

</fieldset>
</div>
</div>
</body>
</html>
GET /Auth/test0.htm HTTP/1.1
Host: 192.0.2.1:90
Connection: keep-alive
Authorization: Basic c2FuYWtpOnBhc3N3b3JkMTIzIQ== ← これは想定している認証情報
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Requested-With: jp.dip.rocketeer.webviewtest
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Wed, 12 Jan 2022 06:47:42 GMT
Accept-Ranges: bytes
ETag: "e418764c807d81:0"
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Wed, 12 Jan 2022 07:11:14 GMT
Content-Length: 157

<html>
<head><title>Basic Auth Test</title></head>
<body>This is Test
<hr>
<iframe src="http://192.0.2.2/Auth/testIn.htm&quot;&gt;&lt;/iframe>
</body>
</html>


結果(192.0.2.2側{子、想定していないホスト側})

192.0.2.2 の方は、こんな感じの通信内容となっている。
認証情報が漏れている事が確認できる

RAW 通信データ
C:\>StreamRelay.NET.exe -localport 80 -remoteport 80 -remotehost 192.0.2.1 -logging GET /Auth/testIn.htm HTTP/1.1 Host: 192.0.2.2 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Referer: http://192.0.2.1:90/Auth/test0.htm Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9

HTTP/1.1 401 Unauthorized ← 192.0.2.2側のこちらは想定されていない場面が多いと思う
Content-Type: text/html
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
WWW-Authenticate: Basic realm="192.0.2.2"
Date: Wed, 12 Jan 2022 07:11:14 GMT
Content-Length: 1313

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"/>
<title>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}fieldset{padding:0 15px 10px 15px;}h1{font-size:2.4em;margin:0;color:#FFF;}h2{font-size:1.7em;margin:0;color:#CC0000;}h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;background-color:#555555;}#content{margin:0 0 0 2%;position:relative;}.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>サーバー エラー</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</h2>
<h3>指定した資格情報を使用して、このディレクトリまたはページを表示するアクセス許可がありません。</h
3>
</fieldset></div>
</div>
</body>
</html>
GET /Auth/testIn.htm HTTP/1.1
Host: 192.0.2.2
Connection: keep-alive
Authorization: Basic c2FuYWtpOnBhc3N3b3JkMTIzIQ== ← 想定外のホスト(192.0.2.2)へ認証情報が洩れている
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
X-Requested-With: jp.dip.rocketeer.webviewtest
Referer: http://192.0.2.1:90/Auth/test0.htm
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Wed, 12 Jan 2022 04:31:28 GMT
Accept-Ranges: bytes
ETag: "d424f3436d7d81:0"
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Wed, 12 Jan 2022 07:11:14 GMT
Content-Length: 19

This is ifame page.


まとめ

まぁ、つまり、オーバーライドする際に、hostやrealmで制限する必要があるということ。

例えば、

webView.setWebViewClient(new WebViewClient(){
 String relmStr = "";
 @Override
 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
  // hostやrealmをチェックして、認証情報をチェックする必要がある
  if(host.equals("192.0.2.1") == true){ // 例えば、ホストで制限する
   if(this.relmStr.length() == 0){
    // ここは一番最初の要求
    this.relmStr = realm;
    handler.proceed("ユーザ名", "パスワード"); // ← 想定している範囲内だけに制限することが大切
   }else if(this.relmStr.equals(realm) == true){
    // 2番目からは記憶しているレルムの時だけに制限する
    handler.proceed("ユーザ名", "パスワード"); // ← 想定している範囲内だけに制限することが大切
   }else{
    handler.cancel();  
   }
  }else{
   handler.cancel();  
  }
 }
});

こんな感じ。

webView3.jpg

192.0.2.2側ではちゃんと認証情報を漏らさずに認証エラーとなっている

RAW 通信データ
C:\>StreamRelay.NET.exe -localport 80 -remoteport 80 -remotehost 192.0.2.1 -logging GET /Auth/testIn.htm HTTP/1.1 Host: 192.0.2.2 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86 Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 X-Requested-With: jp.dip.rocketeer.webviewtest Referer: http://192.0.2.1:90/Auth/test0.htm Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 If-None-Match: "d424f3436d7d81:0" If-Modified-Since: Wed, 12 Jan 2022 04:31:28 GMT

HTTP/1.1 401 Unauthorized
Content-Type: text/html
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
WWW-Authenticate: Basic realm="192.0.2.2"
Date: Thu, 13 Jan 2022 00:46:32 GMT
Content-Length: 1313

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-s
trict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"/>
<title>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</title>
<style type="text/css">
<!--body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;
}fieldset{padding:0 15px 10px 15px;}h1{font-size:2.4em;margin:0;color:#FFF;}h2{font-size:1.7em;margin:0;color:#CC0000;}h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;background-color:#555555;}#content{margin:0 0 0 2%;position:relative;}.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}-->
</style>
</head>
<body>
<div id="header"><h1>サーバー エラー</h1></div>
<div id="content">
<div class="content-container"><fieldset>
<h2>401 - 権限がありません: 資格情報が無効であるため、アクセスが拒否されました。</h2>
<h3>指定した資格情報を使用して、このディレクトリまたはページを表示するアクセス許可がありません。</h
3>
</fieldset></div>
</div>
</body>
</html>

ここから通信が途切れて、最終的に認証エラーとなっている(認証情報は漏れなくなった)

これでも、悪意あるコンテンツと、レンタルサーバを共有しているような場合は、突破されるので、さらなる制限が必要だよ。
(第一引数のWebView viewを使ってポートとか、URLのパスとかに基づいてさらに制限をかける)


リンク

JSSECの「Androidアプリのセキュア設計・セキュアコーディングガイド」は読んでおこう。


以上

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?