最近、かなり人気のあるいくつかのリソースで使用されているボット保護に対処する必要がありました。
一見、保護はjavascriptを介したCookieの通常の設定であるように思われ、15分で対処できます。 実際、少し調べてから、どこで何が行われ、どのパラメーターがどこに渡されるかが明らかになりました。小さな関数をjavascriptからphpに書き換えるだけで、問題は解決しません。
しかし、それはそれほど単純ではありませんでした。 そして、最終的には保護が破られましたが、15分もかかりませんでした。保護の原理自体は新しく、私にとって非常に興味深いものでした。
だから、まず最初に。
表面検査
保護は次のように機能します。
index.phpサイトのメインページのスクリプトは、訪問者のIPアドレスから計算されたハッシュがパラメーターの1つに含まれるCookieを想定しています。
Cookieが送信されない場合、index.phpは、必要なパラメーターを計算するjavascriptコードを含む別のページに訪問者をリダイレクトし、Cookieに書き込み、メインページに戻ります。
通常のphpボットがCURLを介してGETおよびPOST要求を実行してそのような保護を通過できるようにするには、ハッシュ計算をjavascriptからphpに書き換えてから、必要なcookieを要求ヘッダーに追加する必要があります。
検死
より詳細に。
Firefoxを起動し、javascriptを無効にし、Firebugを有効にします。
メインページのindex.phpをリクエストし、リクエストおよびレスポンスヘッダーを確認します。
リクエスト:
ゲット
http://example.com
このリクエストのヘッダーは、私たちには関係ありません。
また、応答ヘッダーは次のとおりです。
ステータス:302一時的に移動
接続キープアライブ
コンテンツタイプテキスト/ html
日付XXX GMT
場所 http://example.com/govalidateyourself#98765:1234:11.22.33.44:/index.php
サーバーYTS / 1.20.0
転送エンコードチャンク
その後、Firefoxはヘッダーで指定された場所に自動的に移動し、次の応答ヘッダーを受信します。
Accept-Rangesバイト
接続キープアライブ
コンテンツタイプテキスト/ html; 文字セット= utf-8
日付XXX GMT
最終変更日YYY GMT
サーバーYTS / 1.20.0
Set-Cookie addr = 1234:11.22.33.44; パス= /
転送エンコードチャンク
11.22.33.44は私のIPアドレスであり、1234は計算ロジックが不明な数値です。
ページ自体には、jsコードへのリンクが含まれています
http://example2.com/validator/va.js
碑文「No javascript」。
jsがなければ、彼らは私たちにそれ以上先へ進ませません。
すべての応答要求が記録されたら、javascriptをオンにし、Cookieをクリアして、もう一度やり直します。
ここで、検証ページのリクエスト後に何が起こるかに興味があります。
今回はサイトのメインページが読み込まれます。最後のリクエストのタイトルは次のとおりです。
テキスト/ html、application / xhtml + xml、application / xmlを受け入れる; q = 0.9、* / *; q = 0.8
Accept-Encoding gzip、deflate
Accept-Language ru-ru、ru; q = 0.8、en-us; q = 0.5、en; q = 0.3
接続キープアライブ
Cookie addr = 5678:11.22.33.44; 尿= aabbccdd; v = 1
ホストexample.com
リファラー http://example.com/govalidateyourself
Firefoxのユーザーエージェント
今回の最後のサーバー応答からの定数1234は5678に変更され、IPアドレスは同じままでした。 どうやらこれは、サーバーによって割り当てられ、Cookieに保存されている要求IDです。 さて、あなたはそれを保存し、リクエスト中に変更せずにクッキーに書き込む必要があります。
しかし、urine = aabbccddパラメーターはすでに興味深いものです。 サーバーから送信されたのではないので、それは私たちから受信したことを意味し、これがva.jsの仕事であることがわかります。
中身を確認する時間です。 一見したところ、完全な沼地、それは入らない方が良い:
if(document.cookie==""){document.write("Cookies error")}else{function poo(a,b){var c=a.length,d=b^c,e=0,f;while(c>=4){f=a.charCodeAt(e)&255|(a.charCodeAt(++e)&255)<<8|(a.charCodeAt(++e)&255)<<16|(a.charCodeAt(++e)&255)<<24;f=(f&65535)*1540483477+(((f>>>16)*1540483477&65535)<<16);f^=f>>>24;f=(f&65535)*1540483477+(((f>>>16)*1540483477&65535)<<16);d=(d&65535)*1540483477+(((d>>>16)*1540483477&65535)<<16)^f;c-=4;++e}switch(c){case 3:d^=(a.charCodeAt(e+2)&255)<<16;case 2:d^=(a.charCodeAt(e+1)&255)<<8;case 1:d^=a.charCodeAt(e)&255;d=(d&65535)*1540483477+(((d>>>16)*1540483477&65535)<<16)}d^=d>>>13;d=(d&65535)*1540483477+(((d>>>16)*1540483477&65535)<<16);d^=d>>>15;return d>>>0}function coo(a){var b=a+"=";var c=document.cookie.split(";");for(var d=0;d<c.length;d++){var e=c[d];while(e.charAt(0)==" ")e=e.substring(1,e.length);if(e.indexOf(b)==0)return e.substring(b.length,e.length)}return null}var dt=new Date,expiryTime=dt.setTime(dt.getTime()+1000e5);var dt2=new Date,expiryTime=dt2.setTime(dt2.getTime()+2e4);var addr=window.location.hash.split(":")[2];var a=poo(addr,47).toString(16);for(var i=0,z="";i<8-a.length;i++)z+="0";a=z+a;a=a.substring(6)+a.substring(4,6)+a.substring(2,4)+a.substring(0,2);var refurl=window.location.hash.split(":")[3];document.cookie="urine="+a+"; expires="+dt.toGMTString()+"; path=/";if(!coo("v")){document.cookie="v=1; expires="+dt2.toGMTString()+"; path=/";setTimeout("window.location = refurl",300)}else if(coo("v")<3){var c=coo("v");c++;document.cookie="v="+c+"; expires="+dt2.toGMTString()+"; path=/";setTimeout("window.location = refurl",300)}else if(coo("v")>=3){document.write("Too many redirects from: "+document.referrer)}}
しかし、少しの忍耐、そしてフォーマット後にすべてが読みやすく、非常に理解しやすいように見えます。
2つの関数coo()とpoo()、および必要なCookieを書き込み、index.phpに送り返すコードがあります。
関数co()は特に重要ではありません。指定されたパラメーターの値をcookieから受け取り、単純な正規表現でphpに簡単に書き込まれます。
そして、ここにpoo()関数があり、これは尿パラメーターを考慮します:
function poo( a, b ) { var c = a.length, d = b^c, e = 0, f; while( c >= 4 ) { f = a.charCodeAt( e ) & 255 | ( a.charCodeAt( ++e ) & 255 ) << 8 | ( a.charCodeAt( ++e ) & 255 ) << 16 | ( a.charCodeAt( ++e ) & 255 ) << 24; f = ( f & 65535 ) * 1540483477 + ( ( ( f >>> 16 ) * 1540483477 & 65535 ) << 16 ); f ^= f >>> 24; f = ( f & 65535 ) * 1540483477 + ( ( ( f >>> 16 ) * 1540483477 & 65535 ) << 16 ); d = ( d & 65535 ) * 1540483477 + ( ( ( d >>> 16 ) * 1540483477 & 65535 ) << 16 )^f; c -= 4; ++e } switch( c ) { case 3: d ^= ( a.charCodeAt( e + 2 ) & 255 ) << 16; case 2: d ^= ( a.charCodeAt( e + 1 ) & 255 ) << 8; case 1: d ^= a.charCodeAt( e ) & 255; d = ( d & 65535 ) * 1540483477 + ( ( ( d >>> 16 ) * 1540483477 & 65535 ) << 16 ) } d ^= d >>> 13; d = ( d & 65535 ) * 1540483477 + ( ( ( d >>> 16 ) * 1540483477 & 65535 ) << 16 ); d ^= d >>> 15; return d >>> 0 }
呼び出し中に、次のパラメーターがそれに転送されます。
var a = poo( addr, 47 ).toString( 16 );
a-これは、尿パラメーターの既製の値です(8文字未満の場合にのみゼロが埋め込まれます)。
addrはIPアドレス11.22.33.44です。
47は定数です。
これですべてが明らかになりました。
この保護を突破するphpボットは、次のアルゴリズムに従って動作するはずです。
1. GETリクエストを行う
http://example.com/index.php
応答ヘッダーを受信するオプションを設定します。
curl_setopt( $ch, CURLOPT_HEADER, 1 );
そして同時に、リダイレクトの場合に自動遷移をオンにします:
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 );
この場合、curlは新しい場所自体に移動するため、2番目のリクエストをプログラムする必要はありません。 そして、両方の応答のヘッダーを取得します。最初のヘッダーはLocationで、2番目のヘッダーはリクエストIDを含む最初のCookieです。
2.ヘッダーを解析し、リクエストIDとIPアドレスを取得します(別のトリックを使用する場合、すぐにはわかりませんが、ここではお勧めします-非常に便利です)。
尿パラメーターを読み取り、Cookieに書き込み、index.phpに新しいGETリクエストを送信します。 保護が通過しました。
Cookieは次のように記述されます。
$headers = array( "Cookie: " . $cookie_str,
だから、最後のタッチが残っている-尿の計算。
すくい
poo()関数をphpに書き換えるだけです。
始めるには、少しグーグルで、phpにないjs関数と演算子のペアの類似物を書きます。
これですべての準備が整い、poo()を書き換えることができます。
保存、実行、中断-jsとphpのバージョンの結果が一致しません。
元気?
jsとphpにコードを追加して、計算の各行の後に結果を表示し、何が起こっているのかを見てください。
単純な算術php演算子は、javascriptとは異なり、大きな数値ではうまく機能しないことがわかりました。
たとえば、式
( 18220025198660 & 65535 ) * 1540483477 + ( ( ( 18220025198660 >>> 16 ) * 1540483477 & 65535 ) << 16 );
javascriptの場合は221886241596
36に等しく、phpの場合も同様です
( 18220025198660 & 65535 ) * 1540483477 + ( ( ( zeroFill( 18220025198660, 16 ) ) * 1540483477 & 65535 ) << 16 )
わずかに異なる数221886241596
00に等しくなります
いくつかの同様の式が連続して計算されると、エラーが累積し、まったく異なる結果が得られます。 一部の式では、phpはデフォルトで結果がint型であると想定し、最大値を40億に制限します(32ビットシステム)。
Perlには、多数の同様の問題があります。
PHPで正確な計算を行うには、BC Mathライブラリの関数を使用する必要があります。 これに加えて、float型にキャストを追加する必要があります。
試行錯誤の結果、javascriptと同じ結果が得られるコードが得られます。 しかし、これには追加の時間と労力が必要です。
コードは最適ではありません。明確にするために、計算は段階的に実行されます。
zeroFill()関数の場合、最初に追加します。
$a = floatval( $a );
おわりに
私のボットは仕事を終えており、ここで説明する保護を自分の目的に使用できます。 たとえば、計算を行うコードを動的に変更するなど、修正すると、このようなハッキングはさらに難しくなります。 そして、誰もあなたを真剣に受け止めたくないなら、この保護は十分でしょう。
一般的に、ボットに対する最良の保護はキャプチャです。 最もトリッキーなjavascriptでさえ、
Mechanize Perlモジュールのようなものを使用してボットによって実行できます。