スパムはあっちいけ!

HTMLソースの難読化


情報収集ロボットの振る舞いは?

ちょっとこれも究極のソリューションだ。当然スパマはいちいちホームページを見て、ターゲットとする掲示板を探すわけではない。それには「ロボット」を使い、ロボットがリンクを辿って、ターゲットとなる掲示板CGIを見つけるわけだ。

で「ロボット」は掲示板らしいURLへの参照、たとえばこんなのだが、

<a href="/cgi-bin/mybbs.cgi">BBS</a>

を見つけた時に「へ、へ〜掲示板だな、これ」と(ロボットだから思わないが..)思って、そのリンクURLを叩いてみる。これが掲示板だったら、

<form action="/cgi-bin/mybbs.cgi" method="POST">
<input type=hidden name=mode value="regist">
<table cellpadding=1 cellspacing=1>
<tr>
  <td><b>おなまえ</b></td>
  <td><input type=text name=name size="20"></td>
</tr>
<tr>
  <td><b>Eメール</b></td>
  <td><input type=text name=email size="20"></td>
</tr>
<tr>
  <td><b>題  名</b></td>
  <td>
    <input type=text name=sub size="25" value="">
    <input type=submit value="送信する"><input type=reset value="リセット">
  </td>
</tr>
<tr>
  <td colspan=2>
    <b>メッセージ</b><br>
    <textarea name=comment cols="55" rows="7" wrap=soft></textarea>
  </td>
</tr>
<tr>
  <td><b>URL</b></td>
  <td><input type=text name=url size="45" value="http://"></td>
</tr>
<tr>
  <td><b>削除キー</b></td>
  <td>
    <input type=password name=pwd size=8 maxlength=8 value="">
    <small>(記事の削除用。英数字で8文字以内)</small>
  </td>
</tr>
</table>
</form>

といった格好の入力フォームを見つけ出して、<input> タグなどの name 属性を拾い出して、リクエストを構築するわけである。

逆に言えば、初歩的な防衛策として対策掲示板などに書かれるノウハウの1つに、

掲示板へのリンクは、JavaScript などを使って直接解析のしにくいようにしてやる!

というのがあるわけだ。たとえば、

を実現するのに、

<script language="javascript">
<!-- 
document.write( "<" + /* 防御 */ "a" + /*防御*/ " " + //
"href" + /* 防御 */ "=" + /* 防御 */ "\"/cg" + /* 防御 */ "i-b" + //
"in/" + /* 防御 */ "my" + /* 防御 */ "b" + /* 防御 */ "b" + /* 防御 */ "s" + //
".c" + /* 防御 */ "gi\"" + /* 防御 */ ">B" + /* 防御 */ "B" + /* 防御 */ "S<" +//
"/" + /* 防御 */ "a><br>\n" ); 
//-->
</script>

とか書いちゃう、という奴だ。

まあ、これはこれで重要なテクニックである。しかし、賢いロボットならば、これを解析して「<a href="/cgi-bin/mybbs.cgi>BBS</a>」を構築する、というのは、例えば Perl レベルで考えた時、面倒っちいが不可能なことではない。たとえば、次のコードはこういう妨害をはねのけて、正しいCGIのURLを出力してしまう。

sub dejs {
    my($text) = @_;
    if( $text =~ m|<script[^>]*>(.*)</script>|is ) {
	$text = $1;
	if( $text =~ m|document.write\(([^)]*)\);|s ) {
	    $text = $1;
	    $text =~ s|/\*[^\*]*\*/||gs;
	    $text =~ s|//[^\n]*||gs;
	    $text =~ s|["]\s*\+\s*["]||gs;
	    if( $text =~ m|bbs|i ) {
		$text =~ s/\\//gs;
		if( $text =~ m|href=["]([^"]*)["]|i ) {
		   return "found:" . $1;
                }
	    } else {
		return'';
	    }
	}
    } else {
	return '';
    }
}

...要するに正規表現っていうのは強力なわけだ。まあ、ここらへん頑張って複雑化しても、「限度」というものもある。じゃあ、このアイデアをもっと強力なアイデアに置き換えたらどうなんだろうか???

難読化のソリューション

ここでちょいと思いつくのは、いわゆる「HTML難読化」である。要するにページ全体を暗号化して、JavaScript によってクライアントサイドで解読させるという技だ。勿論この「HTML難読化」は「スパム防御のため」に開発されたようなものじゃなくて、

  1. 苦心して書いた CSS や HTML のノウハウを、他人に盗まれたくないデザイナー
  2. 情報流出を防ぎたいセキュリティマニア

など、別方面のニーズから研究されてきたものだ。とはいえ、今まではこういう難読化を本気で欲しがるユーザってのはかなりの少数派だった。だから、「HTML難読化ツール」ってのはあるが、フリーのものなんてどうやらないみたいだ。そして、スパム防御のために「リンクやフォームの入っている一部分だけ難読化できたらイイよね...」というニーズは全然想定外だったりするのは仕方がないな。

まあ、ここで「暗号化」と呼ばずにわざわざ「難読化」と言うのは、実質上暗号アルゴリズムを使って内容を「暗号化」するんだが、人間は全然内容が判らなくてもブラウザは解読できなきゃ話にならない。言い替えると「解読に必要な情報はすべてブラウザに渡される」わけだから、たとえば頑張って解読すれば、解読できてしまうわけだ...だから「キーが判らないと読めない暗号化」ではなくて、「難読化」と言うわけである。

そこで筆者は、「部分難読化をサポートするCGI」などというものを開発してしまった。要するに「ファイルアップロードでHTMLをCGIに渡せば、必要な部分を難読化して返してくれる」CGIだ。だから、特に「難読化ソフト」をインストールして...などということはまったく不要で、単にこのページの以下の部分にあるフォームから、あなたが難読化したいページを送ってやればいいわけだ。返ってくるのは「すでに難読化されたHTML」であり、表示はそのままだが、中身を見てれば、


<HTML><HEAD><TITLE>スパムはあっちいけ!「難読化サンプル」</TITLE>
<META http-equiv=Content-Type content="text/html; charset=utf-8">
<META http-equiv=Content-Script-Type content=text/javascript>
<META http-equiv=Content-Style-Type content=text/css>
<META content=K.Sugiura name=author>
<META content="スパムはあっちいけ!「難読化サンプル」" name=description>
<!-- 内容が暗号化されるので、キーワードはちゃんと書いておかなきゃ!//-->
<META content="ホームページ製作,BBS,スパム対策" name=keywords>
<META content="ホームページ製作,BBS,スパム対策" name=robots>
</HEAD>
<BODY bgColor="#f0f0f0">
<!-- 暗号化ライブラリ //-->
<script langage="JavaScript">
<!--
var a1690=-1;var p0015=8;var o2120=16;var g6584=0;var i0294="";var 
l6875=15;var s4334=Math.ceil(o2120*4.0/3.0);var o3810=1;var k7602=31;var 
s0642=o2120*p0015;var k8460=63;var d6867="";var p0067=6;var f6921=64;var 
q7010=65;var v9745=0;var k2090=document;var m8748=2;
<!-- これが暗号キー(コメントアウトされている) //-->
/*var w0906="Gar1TLFWu4eg9Sc6QM3kdbKxq5wV8ARPv0HfjzC2sNYmnXOlpUoi/hEJtZ.IByD7";*/
<!-- 以下の3行+空行をファイルの先頭に移動し、ファイルを
拡張子 .asis で保存し、適切な .htaccess を書けば、ソースに
暗号キーがない状態を実現できる。 //-->
/*insert this 3 lines and \n(blank line)to the top of the file,
and save this file as***.asis to the directory set .htaccess to handle 
asis
Status:200 OK
Content-Type:text/html
Set-Cookie:w0906=Gar1TLFWu4eg9Sc6QM3kdbKxq5wV8ARPv0HfjzC2sNYmnXOlpUoi/hEJtZ.IB
yD7
*/
<!-- 暗号解読ルーチン //-->
o7554();var n6539=new Object();var h4765=new Array();var p2069=new Array
();for(s3138=v9745;s3138<p0015;s3138++){ p2069[s3138]=g6584;}
for(s2670=g6584;s2670<f6921;s2670++){v=w0906.substring
(s2670,s2670+o3810);n6539[v]=n2587(s2670);}
function o7554(){if((typeof w0906)!="undefined")return;var 
u9510=k2090.cookie;var b6293=u9510.indexOf("w0906");if(b6293 !=a1690){var 
q1732=b6293+"w0906=".length;u9510=u9510.substring(q1732);var 
i3570=u9510.indexOf(";");if(i3570==a1690){i3570=u9510.length;}}
w0906=u9510.substring(0,i3570);}
function n2587(k2272){var o2302,q1732;var w7666=i0294;for
(o2302=v9745;o2302<p0067;o2302++){q1732=k2272%
m8748;w7666=d6867+q1732+w7666;k2272=Math.floor(k2272/m8748);}
return w7666;}
<!-- などなど、中略 //-->
<!-- 解読ルーチンの一部は更に暗号化されている //-->
var t0955=a1768("SoGiqkLJAJ0EqxU0AJb.3pSoGiq3hhAJ4tqxU04iQluvS3vO
gEzsqkNgqKL0qk8v9pqkAXq3NXq3XXq3aXq3SXqQgKh0eKh0SKh0cKh0cFh0cpVKT
ERjXgqKL0q38O9Ez0cpqxU08xN0cELyqk4UAJbiRvqkXYwCLsqkNg3rzhA2A/FvcU
U0PFTIRjX0qKL06TX0qQqKT2gfSNqkz0PFLURCTZqQPKTo8xAh8ELOqkLJAJ0ERvq
kzYwCLsqkNg3r8O9Ez0epqxU08xN0eELyqkLJAJ0ERvq3XYwCLsqkNg3FL0qKTvqQ
PFTE8x0UAEBHe3GiGHtz4GG1bNeE0.3/00qKL0gKLBqQuFLjqkLJAJ0ERjXuqKL0q
QeKLBqKzNuFLmqx40wCLJ8v8WSowFLjqxbU8F005FTUApAJ0ERjXuqKL0qkb0PFTN
AQA2A/Ffzm9xAJRW5YgMU.3p3FL0qKTNAx5JAasZwiLJApRW5YgMU0PFTNAx5JAas
Zwp9xAJRW5YeMU.3/00qKL0eQAx5JAasZwiLJAJ0EwHj8qQPFThRjXu6TX0qKL06T
X0qQqKT2gfSNqk00PFLURCTtqQPKTE8x0UAEBX4rBCS3z.qQcFNYqK00cjXuuELBq
k5URG8xAluHjv9puO43QGSKjtwGRjXugKLBq3S05FTo8xAh8pRjXueKLBqKzNuEXp
8FNJ8v8F0j82apwFLjqk4UAJbiRv3/vhqxU0exbEAJQwgMU.3p3rzhA2A/FH/8qxU
0exbEApAasNWWNg3rzhA2A/FHj8qQPFThRjX0qKL06TX0qKL04pgfSNqk50PFLURC
TEqxh09QAJAtA2N0SCNYqK00cjXuSpqxU0S2Lt8xAluHjv9puO4Q4GGhwk5sRjXuS
ELyPxU0wQSCMZwvpvS3zl4o/OgfSNSvV2zswFMZRjXuSELBqKjJ5p8kzU8382w1hN
wkA28kj24p8xLsPJyZwWNg31Li8xAtFvSCMZWFTPPFTJRjX0qKL06G3iU.3ohhAJ4
twK0.3oAU8p8W40PFT2SrBHS3vOgEjYVQeK00cCTi41d/9oy0e0BU8p8xAtFHj8RC
TB3EL0qKL0qQ");
eval(t0955);//-->
</script>
<!-- HTMLテキスト先頭は暗号化されていない //-->
<div align=center><table width="60%" class=nav><tr>
<td align=left width="25%"><a href="/~sug/homep/spam/spam7.htm#sec4">戻る
</a></td>
<td align=center width="50%"><a href="/~sug/index.shtml">トップ</a>>
<a href="/~sug/homep/index.htm">HP製作</a>>
<a href="/~sug/homep/spam/index.htm">SPAM</a></td>
<td align=right width="25%">  </td>
</tr></table></div>
<H2>スパムはあっちいけ!</H2>
<h3>難読化サンプル</h3>

<p>これは「<a href="/~sug/homep/spam/spam7.htm">HTMLソースの難読化</a>」用
のサンプルファイルである。例えば、以下のようなリンクがあるとする。</p>

<p>目次</p>
<ul class=linkat>
<!-- 暗号化指定部分 //-->
<script langage="JavaScript"><!--
k2090.write(k4274( "oFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3GW
EHdM56opPKVeQ8N3B.tKGlWYfgzJVZsb23IUisw1N6v.zyqawpci9fO6IFWDFAj3v
HKCqPpQsbdrZTMuLQjlqaQ/M//MnQM68MB2au63ZNeVQ/QitZBA4K5FVBG.spBsU7
Gl6mo6fh65vT23lzN.cStevXlKFZ5CRvu3n6UXMIGDsrGoFTsQnGQDBvyAGd6ujEo
3vG0z8wlRAoKkzaMqArs.z3G2EHd5AStEPKkeQ8N3B.UKGl6YfffhSPYT2omXNscT
IeviaPqHehPEuKhlSpZ7Hzv4pGJjqqTKmBBUJLLGWgNE5NG/VJt8gKrVDHLnQQWG/
U4kQWroqJVGClATqlMQ6ejiNZGlAmHm2hVRvjlpmhH.uTNYvylA3uezOzuBMOmNms
r0puvGJjqqTKmBBUJLLGWgNE5NG/VJt8gKrVDHLnQQWG/U4kQW3oq7gGjl8TqlMQ6
ejiNZGlAYimjiIwYz2xO/NsRLZcvDnAwgsHEruH0etjMY18tfGoFTsQnGQDBvyAGd
6ujEo3vG0z8wlRAoKkzaMqArs.z3G2/Hd5AS.EPKkeQRN3BYUEGOPmolvh4V0L23m
XNtcSZev77VQYPhLTu8zYsDI7AnLevAY5zmzUlJ08f.nBsUqX0AGT/BqjKFvT/rwO
ATcqQ4SUpa9Jd59QjR/0zoQuS3X0dZGjkEtJm8Cg8aSlGq6r0m9ZGo4HIUsx4wuL6
zFv0NDxtYvyl8wmqCHMuwLlZnMIGDt1GEJKvi/rPhBGl5Gq6qthU0v" ));
//--></script>
<!-- 暗号化終わり //-->

</ul>

<p>スパムフィルターのダウンロードは次のリンクから。</p>

<ul class=linkat>
<!-- 暗号化指定部分 //-->
<script langage="JavaScript"><!--
k2090.write(k4274( "oFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3Gz
sjdM7comRQXgG8W30HZGpfUUhICUDJ1d1t0Bs/BsNDGCkYiL2xgPYd2vgh0ZcRZFv
IlP3NgEcruqzYNsg7a/srGoFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3
GWCHd5A6.jRK1eQ9m3v.UoGlWsYf0ilwHblQYjsY8PsYvEh//xhrV/EGO5a865.h0
av" ));
//--></script>
<!-- 暗号化終わり //-->
</ul>

<!-- 略 //-->
</ul>
<br><br>
<HR>
<div align=center><table width="60%" class=nav><tr>
<td align=left width="25%"><a href="/~sug/homep/spam/spam7.htm#sec4">戻る
</a></td>
<td align=center width="50%"><a href="/~sug/index.shtml">トップ</a>>
<a href="/~sug/homep/index.htm">HP製作</a>>
<a href="/~sug/homep/spam/index.htm">SPAM</a></td>
<td align=right width="25%">  </td>
</tr></table></div>
<BR>
<div class=copyr>copyright by K.Sugiura, 1996-2006</div>
</body></html>

となっていて、難読化の最初の部分、

<script langage="JavaScript"><!--
k2090.write(k4274( "oFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3GW
EHdM56opPKVeQ8N3B.tKGlWYfgzJVZsb23IUisw1N6v.zyqawpci9fO6IFWDFAj3v
HKCqPpQsbdrZTMuLQjlqaQ/M//MnQM68MB2au63ZNeVQ/QitZBA4K5FVBG.spBsU7
Gl6mo6fh65vT23lzN.cStevXlKFZ5CRvu3n6UXMIGDsrGoFTsQnGQDBvyAGd6ujEo
3vG0z8wlRAoKkzaMqArs.z3G2EHd5AStEPKkeQ8N3B.UKGl6YfffhSPYT2omXNscT
IeviaPqHehPEuKhlSpZ7Hzv4pGJjqqTKmBBUJLLGWgNE5NG/VJt8gKrVDHLnQQWG/
U4kQWroqJVGClATqlMQ6ejiNZGlAmHm2hVRvjlpmhH.uTNYvylA3uezOzuBMOmNms
r0puvGJjqqTKmBBUJLLGWgNE5NG/VJt8gKrVDHLnQQWG/U4kQW3oq7gGjl8TqlMQ6
ejiNZGlAYimjiIwYz2xO/NsRLZcvDnAwgsHEruH0etjMY18tfGoFTsQnGQDBvyAGd
6ujEo3vG0z8wlRAoKkzaMqArs.z3G2/Hd5AS.EPKkeQRN3BYUEGOPmolvh4V0L23m
XNtcSZev77VQYPhLTu8zYsDI7AnLevAY5zmzUlJ08f.nBsUqX0AGT/BqjKFvT/rwO
ATcqQ4SUpa9Jd59QjR/0zoQuS3X0dZGjkEtJm8Cg8aSlGq6r0m9ZGo4HIUsx4wuL6
zFv0NDxtYvyl8wmqCHMuwLlZnMIGDt1GEJKvi/rPhBGl5Gq6qthU0v" ));
//--></script>

は、

<li><a href="/~sug/homep/spam/spam1.htm">攻撃の「傾向と対策」</a>
<li><a href="/~sug/homep/spam/spam2.htm">スパムフィルターCGIの設定</a>
<li><a href="/~sug/homep/spam/spam3.htm">スパムフィルターCGIの詳細</a>
<li><a href="/~sug/homep/spam/spam4.htm">月に代わってお仕置よ!</a>
<li><a href="/~sug/homep/spam/spam5.htm">統計的スパム判定法?</a>
<li><a href="/~sug/homep/spam/spam6.htm">ベイジアン・フィルターによるスパム判定</a>
<li><a href="/~sug/homep/spam/spam7.htm">HTMLソースの難読化</a>

2番目の部分

<script langage="JavaScript"><!--
k2090.write(k4274( "oFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3Gz
sjdM7comRQXgG8W30HZGpfUUhICUDJ1d1t0Bs/BsNDGCkYiL2xgPYd2vgh0ZcRZFv
IlP3NgEcruqzYNsg7a/srGoFTsQnGQDBvyAGd6ujEo3vG0z8wlRAoKkzaMqArs.z3
GWCHd5A6.jRK1eQ9m3v.UoGlWsYf0ilwHblQYjsY8PsYvEh//xhrV/EGO5a865.h0
av" ));
//--></script>

<li><a href="/~sug/homep/spam/spam-filter-1.0.zip">フィルターCGIのダウンロード</a>
<li><a href="/~sug/homep/spam/spam2.htm">設定解説ページ</a>

というテキストに、JavaScript によってクライアントサイドで解読される。これを人間あるいはロボットが見て...内容が判るわけないじゃん! ブラウザがこれを解読できるから、そりゃ人間が頑張って解読しようとすればこれ不可能ではないのだが...

function n2587(k2272){var o2302,q1732;var w7666=i0294;for
(o2302=v9745;o2302<p0067;o2302++){q1732=k2272%
m8748;w7666=d6867+q1732+w7666;k2272=Math.floor(k2272/m8748);}
return w7666;}

というような「プログラマの悪夢」(こーゆーの筆者も見たことある...苦笑)に出てくるような代物である。ちなみに JavaScript の変数名なんかも、難読化するたびに変わるし、中盤にある難読化されたコード

var t0955=a1768("SoGiqkLJAJ0EqxU0AJb.3pSoGiq3hhAJ4tqxU04iQluvS3vO
gEzsqkNgqKL0qk8v9pqkAXq3NXq3XXq3aXq3SXqQgKh0eKh0SKh0cKh0cFh0cpVKT
ERjXgqKL0q38O9Ez0cpqxU08xN0cELyqk4UAJbiRvqkXYwCLsqkNg3rzhA2A/FvcU
U0PFTIRjX0qKL06TX0qQqKT2gfSNqkz0PFLURCTZqQPKTo8xAh8ELOqkLJAJ0ERvq
kzYwCLsqkNg3r8O9Ez0epqxU08xN0eELyqkLJAJ0ERvq3XYwCLsqkNg3FL0qKTvqQ
<-- 略 //-->
twK0.3oAU8p8W40PFT2SrBHS3vOgEjYVQeK00cCTi41d/9oy0e0BU8p8xAtFHj8RC
TB3EL0qKL0qQ");
eval(t0955);

は、変数t0955 に代入されており、eval() されている...ということは、「JavaScript のコードでもキモの部分」を更に難読化している、ということである。あまり易しい手段では解読できないと思う。

まあ今の例は別に隠す必要もないようなものだが、この部分難読化を掲示板へのリンクとか、掲示板CGIで使うスキンの中の「<form>〜</form>」の部分だけ適用してやることができるわけである。まあ、掲示板CGIの場合、CGIがスキンの内部に値を設定してやるわけだから、全面難読化しちゃったら投稿ができるわけがない。それに、やはり結構ロジックが重いのもあって、全文難読化したら、ブラウザが降参するかもしれない....結構そこらへん微妙なんだな。

だから、こういう「部分難読化サービス」ってのは、面白いサービスになる可能性があるのでは?というんで開発したわけである。具体的なやり方なんかは次の節で解説しよう。

で、一応このシステムの「やり方」について注意しておく。この難読化システムはキーをクッキーに保存している。だからもし、クッキーが失われたらこれは難読化ではなくて暗号解読になってしまう。言い替えると、「ローカルディスクへファイル保存」したら、クッキーを一緒に保存するわけがないので、暗号キーが失われることになる。これは「ローカルファイルに保存されたくない!」という用途を実現できてしまうな。まあ、一応CGIのレスポンスにはコメントのかたちで暗号キーが埋め込まれていたりするので、そのコメントを外せばローカルディスクでも解読できる。ここらへんも次節でゆっくり説明していく。

設置手順

  1. 日本語を含む場合、エンコーディングは UTF-8 でなくてはならない。これは解読の JavaScript の制限による。他の Shift_JIS とか EUC_JP とかは勘弁してね。それに合わせて
    <META http-equiv=Content-Type content="text/html; charset=utf-8">
    
    のように変更しておくとよいだろう。
  2. もし、一部分だけを難読化する場合には、難読化したい部分を、
    <!--Please NANDOKU -->
    <a href="cgi-bin/bbs.cgi">BBS</a>
    <!--End NANDOKU -->
    
    のように、コメント形式で囲んでおく。この「<!--Please NANDOKU -->」が難読化の開始をCGIに伝え、「<!--End NANDOKU -->」が難読化部分の終了をCGIに伝える。
  3. 次のフォームから「ファイルアップロード」の手順に従って、ファイル名を指定してCGIに処理させる。当然ラジオボタンの「BODY全体の難読化」or「コメント制御の部分難読化」はその通りだが、「変数名を変動させる」or「変数名を固定する」のオプションは、要するに「JavaScriptコード部分を別立ての JavaScriptファイルにしたい!」という要望を簡単にかなえるために、本来解読を難しくするために「CGI起動のたびに変数名をランダムに変えて」いるのを抑止するオプションだ。「変数名を固定する」を選択すれば、固定の変数名を使うので、複数のファイルを難読化しても、JavaScriptロジック部分だけを別立てファイルにしてやって、共有することができるようになる。ただし、これは難読度を下げ、ロボットによる解析を若干易しくする可能性があるので、注意してほしい。だからデフォルトでは「変数名は変動する」。
コメント制御の部分難読化 BODY全体の難読化
変数名を変動させる 変数名を固定する

  1. そうすると、見た目はアップロードした通りの内容のHTMLが返る。うまく解読ルーチンが動作しない場合には、もう一度やってみるとイイことが多い(ブラウザに負担かかってるな....)。
  2. しかし、これはもう既に難読化がなされたHTMLである。内容を「ソース表示」で確認すると良かろう。
  3. そして、このファイルをローカルディスクに保存する。
  4. しかし、このローカルディスクに保存したファイルは、ブラウザではもう既に読めなくなっている....ローカルディスクからブラウザで読み込んで確認すると良い。これはクッキーから暗号化キーを取得できないからである。
  5. もし、スパムロボット対策のため、単にHTMLテキストが読めないことだけを目的とするのならば、
    この位置にあるコメントアウトが暗号キー。変数名(r0043)はその都度変わるので、
    アテにしないでね。
    /* var r0043 = "qU54v/Kub2G7tEmpM3WSohV1gNn0jJxHe6C9YsDR8rlkBaQwAz.PIdXZiOcFTyfL"; */
    
    var r0043 = "qU54v/Kub2G7tEmpM3WSohV1gNn0jJxHe6C9YsDR8rlkBaQwAz.PIdXZiOcFTyfL";
    /* insert this 3 lines and \n(blank line) to the top of the file,
       and save this file as ***.asis to the directory set .htaccess to handle asis
    Status: 200 OK
    Content-Type: text/html
    Set-Cookie: r0043=qU54v/Kub2G7tEmpM3WSohV1gNn0jJxHe6C9YsDR8rlkBaQwAz.PIdXZiOcFTyfL
    */
    
    の要領で、暗号キーのコメントを外す。そうすると、暗号化キーがHTMLに埋め込まれている状態になって、ローカルディスクからもブラウザでテキスト内容を表示できるようになる。まあ、掲示板CGIのスキンとして使うケースでは「暗号キー」を埋め込んだかたちでやるのが良いだろう。

しかし、「ローカルディスクに保存したら読めなくなる文書」ということを目的として、HTMLを公開するのならば、ここから次のようにする。以下の説明は Apache の場合だけの話のなので、後は知らないよ。

  1. 先ほどの部分で、さっきはコメントアウトを外した「var sugkey=」の行を削除する。これは暗号解読の危険性を低めるためである。
  2. そして、その次のブロックにあるように、「Status:」〜「Set-Cookie:」の3行を、ファイルの先頭に移動する。これはコピーではなくて、元々あった位置からは消えていることが重要である。
  3. そして、先頭3行の後に空行を一行追加する。だから、ファイルの先頭は次のようになっているはずだ。今したことは、要するに「クッキーをセットするHTTPレスポンスヘッダ」を、対象とするファイルに直接埋め込んだわけである。で、こういうファイルを「そのまま(asis)」扱う特別な仕掛けが Apache にはあるんである。
    Status: 200 OK
    Content-Type: text/html
    Set-Cookie: r0043=qU54v/Kub2G7tEmpM3WSohV1gNn0jJxHe6C9YsDR8rlkBaQwAz.PIdXZiOcFTyfL
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
    "http://www.w3.org/TR/REC-html40/loose.dtd">
    <html>
    <head>
    
  4. そして、このファイルを、拡張子を .asis にして保存する。
  5. 「ローカルファイルとして保存できない公開ファイル」たちを公開するディレクトリを決めて作成する。
  6. そのディレクトリに、次の内容の .htaccess をアップロードする。これは .asis の拡張子を持ったファイルに対して、asis ハンドラ(ファイル内にHTTPのレスポンスヘッダが埋め込まれているので、それを使え!)を使わせるための指定と、.html の拡張子を持ったファイルに対するアクセスを .asis の同名ファイルにこっそりすり替えるための指定である。
    AddHandler send-as-is .asis
    RewriteEngine On
    RewriteRule ^(.*)\.html$  /~sug/homep/spam/test/$1.asis [L]
    
  7. そのディレクトリに保存した .asis の拡張子を持った、暗号化されたHTMLファイルをアップロードする。
  8. そのディレクトリに対しては、「.html」の拡張子でも「.asis」の拡張子でも、どちらの名前でも同じファイルがアクセスされるようになる。両方でアクセスしてみて、正しく表示されることを確認する。
  9. また、そのファイルをローカルディスクに保存してみて、読めないことを確認しよう。

応用編〜難読化HTMLサーバー

しかし、「ファイルに保存したら読めなくなるHTML」は、JavaScriptをキッチリ理解しているハッカーの手にかかったら、「! そりゃクッキーを見てやればいいだけだ!」となって、もう一度あなたが公開したサーバにアクセスし直して、クッキー内容を確認して、「var キー変数 = "クッキー内容"」にセットしてブラウザで表示させるかもしれない。まあ、そのケースでも、あなたがアップロードしたファイルが削除されたり、もう一度さっきの難読化CGIを適用してクッキー値を変更したりしたら、マジの暗号解読になってしまうので、専門研究者にだって決して解読は容易ではなくなる。

ということは...クッキーに保存された暗号キーがアクセスごとに変動するようなものであれば、「ダウンロードしたその時」にクッキー値をメモっておくくらいのことをしない限り、マジに保存ファイルを読みなおすことができなくなる...なんて仕掛けも実現できないわけではないのだ。

「変動するクッキー値」を実現するには、やはりCGIの助けが必要だ。この難読化CGIは「url=URL」というオプションを与えてやれば、LWPを使って指定ファイルを取得して、暗号化して返す仕様が含まれている。だから、これは、

<a href="http://www.nurs.or.jp/~sug/cgi-bin/encrypt.cgi?url=http://hogehoge.com/hoge.htm">
保存できないHTML!</a>

のようにすれば、アクセスのたびごとに違った暗号化キーで暗号化するように動作するわけだ。

しかし、プロキシサーバとして悪用されるのを防ぐ(かなり重過ぎと思うけど...)ために、GETメソッド以外には対応せず、url オプションの「?」以下は削除する。また、筆者は間借り環境なので、これは実験用途以外には使わないようにしてくれ。実際にはこのモードの場合でも「暗号キーのコメントへの埋め込み」はそのままなので、ホントの暗号化にはなっていないぞ。もし、実用に関するご相談があれば、筆者の方で対応する。

そういう使い方

まあ、実用...で考えたら、特定ディレクトリについて .htaccess で RewriteRule を定義して、「***.htmlにアクセスしてるつもりなのに encrypt.cgi を経由して難読化されて返ってくる...」なんてことも可能である。そういう意味じゃ強力だな、これ。

あと応用として考えられるのは、このCGIをApacehの出力フィルターにしてしまう、という荒技か....そのうち研究してみようか?

難読化CGIオプション一覧

http://www.nurs.or.jp/~sug/cgi-bin/encrypt.cgi のオプションの一覧をまとめておく。

url
ターゲットとなるファイルを値のURLからGETメソッドを使って取得する。ただし、プロキシCGIとして使われるのを防ぐために、拡張子は「.htmlか.htm」でなければエラーにするし、クエリはあっても無視する。対象ファイルのエンコーディングは UTF-8 でないと、日本語は化ける。また、当然、対象ファイルがHTMLであることを期待しているので、正しくないHTMLの場合、動作しないこともある。
text
テキストを直に指定する。テキストは UTF-8 でエンコードされた HTML でなくてはならない。
style
「whole」か「partial」を値として取る。デフォルトは whole である。whole の場合、HTML のボディパート以降すべてを難読化し、partial の場合は「<!--Please NANDOKU --><!--End NANDOKU -->」で挟まれた部分だけを難読化の対象とする。
fixvar
値は「true」か「false」。false がデフォルトである。JavaScript の暗号解読部だけを別立ての JavaScript ファイルにして、複数の難読HTMLの間で共有させたいケースでは、変数名がファイルごとに違っていたらできない。そのため、変数名を「変動させずに固定する」オプションである。勿論、この値は暗号キーの値とは独立である。

注記

少しだけ暗号ロジックを改善しました!(2006.5/10) あと、最近ちょっと気になることとして、

  1. 次バージョンの Java(Java SE6 コードネームは「Mustang」) で、JavaScript エンジンが標準装備される...

ということがある。mozilla で開発されている Rhino という JavaScript エンジンがあるのだが、これを標準で装備しちゃおうという方向に向かっているわけだ...ちょいとマズいな。この難読化アプローチが強い理由とは、

JavaScriptエンジンの相対的入手困難性(及びノウハウの不在)

にあるわけで、それこそ「Java プログラムから JavaScript を実行する」というのが、技術力がサルなスパマでも「楽勝!」ということにでもなってしまえば、「自前で書いたロボットに JavaScript エンジンを搭載して、前処理で JavaScript を解釈&実行させる」というのが簡単に出来るようになる。そりゃ「JavaScriptエンジンを搭載したブラウザ」が解釈できるんだから、「JavaScriptエンジンを搭載したロボット」にそれが出来ないわけはない....ふう、技術の進歩は「困った」ものだ。

とはいえ、今ある人と協力して、「スパマを完全に排除する秘策」を練っているところである。そのうち公開できるんでは?とも思うので、期待してくれ。





copyright by K.Sugiura, 1996-2006