酷!學園
技術討論區 => 程式討論版 => PHP程式設計討論區 => 主題作者是: ricky 於 2007-08-21 16:31
-
目前為了避免自動灌水機器人
各個網站的防治方法不外乎使用圖片文字去防堵
不過這種方式除了傷眼睛,對系統而言還得耗費額外的資源去產生動態的圖片
並且紀錄session
相對而言,想要搞破壞的人只要拼命的對你的sever發出request
Server勢必會被這些session以及圖形產生器給搞死
對使用者而言,除了麻煩還是麻煩
那是否有更好的解決方法呢
這幾天上下班的途中突然有了靈感 :D
於是就做出了這個東西
首先我們說明一下原理
目前灌水機器人或是想寫個灌水機器人的"人"
幾乎都是分析HTML的內容去找出特定的input表單
然後在針對這些tag去做post塞資料的動作
所以我們在server端產生一個驗證的字串
透過javascript指定給一個hidden欄位
我們再用這個hidden欄位去做檢查的動作
可是有人會開始質疑,去分析javascript的內容把特定欄位的值抓出來
不就形同破解了
所以為了避免這種情況,我們動用了一個暴力的手法
(當初這種手段是用在病毒上將自己加密來躲過使用特徵碼偵測方式的防毒軟體)
所以嘍,在這裡做了兩個動作
1.將填入驗證值的那段javascript給加密
2.將解密段的程式變數採用不故定長度,而且隨機的名稱,避免被人直接分析文字內容
透過上面這兩個步驟,想單純使用文字分析的方式就破功了
想破解這段,勢必得弄個javascript的直譯器才行
這工程太浩大了,應該沒人這麼閒吧
下一篇就是程式碼實做的部份嘍
待續...
-
這邊我寫好了一個php的class
robotaway.inc
class RobotAway
{
/*
$VerifyKey 指定給Client端的驗證碼
$EncodeKey 加密用的key
$ElementID input表單中hidden欄位的id值
$Prefix 驗證函數開頭名稱
$TimeOut 驗證碼時效(分鐘)
$CheckFunctionName 檢驗函數的名稱,這邊也導入了隨機命名,增加混亂度
*/
var $VerifyKey;
var $EncodeKey;
var $ElementID;
var $Prefix;
var $TimeOut;
var $CheckFunctionName;
/*用來產生隨機名稱的函數*/
private function GenerateRandomFunctionName()
{
return '_'.substr(md5(time().microtime().rand()),0,rand(10,32));
}
/*
用來將Javascript var變數定義存在一個Array裡
將順序打亂在輸出成一個String
*/
private function GenerateVarDefineString($VarArray)
{
$VarStrings='';
$VarArrayCount=count($VarArray);
for($i=0;$i<=$VarArrayCount;$i++)
{
$RandSwap1=rand(0,$VarArrayCount-1);
$RandSwap2=rand(0,$VarArrayCount-1);
$Tmp=$VarArray[$RandSwap1];
$VarArray[$RandSwap1]=$VarArray[$RandSwap2];
$VarArray[$RandSwap2]=$Tmp;
}
foreach($VarArray as $Index => $VarString)
$VarStrings.=$VarString;
return $VarStrings;
}
/*
在Javascript中加入混淆用註解的函數
*/
private function ConfuseComment()
{
$ConfuseStringPatten="0123456789abcdef_+-/,'\";= ";
$ConfuseString='';
$Size=rand(5,10);
for($i=0;$i<$Size;$i++) $ConfuseString.=$ConfuseStringPatten{rand(0,strlen($ConfuseStringPatten)-1)};
return "/*$ConfuseString*/";
}
/*
用來將變數打亂然後加上混淆註解的函數
例如
$this->('1234567');
程式輸出結果會變成
[垃圾註解]'12'+'345'+'67'[垃圾註解]
*/
private function Confuse($String)
{
if(strlen($String)<=5) return $this->ConfuseComment()."'$String'".$this->ConfuseComment();
$ConfusedString='';
$StringLen=strlen($String);
for($i=0;;)
{
$RndStrLen=rand(5,10);
if($RndStrLen>=strlen($String)-$i-1) $RndStrLen=strlen($String)-$i;
$ConfusedString.=$this-> ConfuseComment()."'".substr($String,$i,$RndStrLen)."'".$this-> ConfuseComment()."+";
$i+=$RndStrLen;
if($i>=strlen($String)-1) break;
}
return $this->ConfuseComment()."$ConfusedString''".$this->ConfuseComment();
}
/*建構函數*/
function __construct($EncodeKey,$ElementID='RACheck',$Timeout=10,$Prefix='RA')
{
$this->ElementID=$ElementID;
$this->Prefix=$Prefix;
$this->GenerateVerifyKey();
$this->TimeOut=$TimeOut;;
}
/*
用來檢驗Client Post送過來的hidden欄位的驗證碼
驗證碼的格式為時間戳記加上兩個md5組成的字串
8byte的時間戳記
32byte ID值
32byte 驗證值
驗證值則是ID跟加密的Key兩個的md5
所以我們只要檢查這個驗證碼是否合法來判別輸入的驗證碼是否正確
就不需額外使用session來存驗證碼了
*/
function Verify($Key)
{
$Time=substr($Key,0,8);
if(($this->TimeOut)&&(hexdec($Time)+$this->TimeOut*60<time())) return false;
$ID=substr($Key,8,32);
if($ID!=md5($Time.$this->EncodeKey)) return false;
$CheckValue=substr($Key,40);
$Result=md5($ID.$this->EncodeKey);
return ($Result==$CheckValue);
}
/*
這邊是用來產生驗證碼
ID就是一個亂數產生的識別碼
*/
private function GenerateVerifyKey()
{
$Time=sprintf("%x",time());
$ID=md5($Time.$this->EncodeKey);
$this->VerifyKey=$Time.$ID.md5($ID.$this->EncodeKey);
}
/*
這邊是一個XOR的加密函數
用來將產生的Javascript給加密起來
*/
private function XorEncode($Str,$Key)
{
$KeyLen=strlen($Key);
$EncStr='';
for($i=0;$i<strlen($Str);$i++)
{
$BinStr=hexdec(bin2hex($Str{$i}));
$BinKey=hexdec(bin2hex($Key{$i%$KeyLen}));
$EncStr.=pack('C',$BinStr^$BinKey);
}
return urlencode($EncStr);
}
/*
這裡就是整個程式的核心
用來產生隨機的Javascript
*/
function GenerateJS()
{
$VarName='_'.substr(md5(time().microtime().rand()),0,rand(10,32));
$FunctionName='_'.substr(md5(time().microtime().rand()),0,rand(10,32));
$EvalDataEncodeKey=sha1(time().microtime().rand());
$EncodedFunction='_'.substr(md5(time().microtime().rand()),0,rand(10,32));
$EvalDataEncodeKeyName='_'.substr(md5(time().microtime().rand()),0,rand(10,32));
/*
$JS 是用來取得驗證碼的核心Javascript 函數
由於驗證碼是一個64bytes的字串值
這邊我們把他打散並且加密起來
最後產生的Javscript會像這樣
var _df12688fab12='xxx';
_df12688fab12+='xxx';
_df12688fab12+='xxx';
其中變數名稱以及打散的數量是不固定的
*/
$JS="var $VarName='';";
for($i=0;;)
{
$RndStrLen=rand(1,10);
if($RndStrLen>=strlen($this->VerifyKey)-$i-1) $RndStrLen=strlen($this->VerifyKey)-$i;
$JS.="$VarName+='".substr($this->VerifyKey,$i,$RndStrLen)."';";
$i+=$RndStrLen;
if($i>=strlen($this->VerifyKey)-1) break;
}
/*
這邊的EvalData就是經過xor加密過後的Javascript函數嘍
$EvalDataEncodeKey 就是解密的Key
*/
$EvalData=$this->XorEncode("var $FunctionName".'=function(){'.$JS."return $VarName;};",$EvalDataEncodeKey);
/*
這邊是將Javascript中的var變數宣告加入一個php的array中
透過Confuse這個函數去打亂
最後再組回一段字串成為Javascript輸出
*/
array_push($RandomJSVarDefineList,"var $EncodedFunction=decodeURIComponent(".$this->Confuse($EvalData).");");
array_push($RandomJSVarDefineList,"var $EvalDataEncodeKeyName=".$this->Confuse($EvalDataEncodeKey).";");
array_push($RandomJSVarDefineList,"var $EvalDataName=".$this->Confuse('').";");
//Javascript Start
/*
以下是Javascript的部份
為了方便閱讀所以把特別排版成這樣
實際輸出時把跳行字元給拿掉節省空間
為了讓大家了解他在做什麼
我幫Javascript加上註解
使用時請把註解給拿掉,節省空間
*/
return str_replace("\n",'',"
function ".$this->CheckFunctionName."()
{
".$this->GenerateVarDefineString($RandomJSVarDefineList)."
for(i=0;i<$EncodedFunction.length;i++)
$EvalDataName+=String.fromCharCode($EvalDataEncodeKeyName.charCodeAt(i%$EvalDataEncodeKeyName.length)^$EncodedFunction.charCodeAt(i));
eval($EvalDataName);
document.getElementById('".$this->ElementID."').value=$AssignFunctionName();
}
");
//Javascript 結束
}
}
?>
剛剛發覺一個漏洞
如果有人把驗證碼記下來,還是可以拼命灌
所以剛剛修改了一下
加上了一個TimeOut預設是10分鐘
超過10分鐘後這個驗證碼就會失效了
修改版本
加入了註解混淆,以及隨機變數宣告的功能
目的就是讓整個Javascript更亂,更難以閱讀
達到混淆的目的
-
ricky 好樣的
真是不錯的方案!
-
實際使用範例
<?php
require_once('./robotaway.inc');
$EncodeKey='自己指定一個Key的內容吧隨便取都可以,越長越好';
$RA=new RobotAway($EncodeKey,'CheckKey');
if(isset($_POST['CheckKey']))
{
//驗證Clinet送過來的這個Checkey值是否合法
$OK=$RA->Verify($_POST['CheckKey'])?'Valid':'Invalid';
}
?>
<html>
<head>
<script>
<?=$RA->GenerateJS()?>
</script>
</head>
<body>
CheckStatus:<?=$OK?>
<form action='' method=post>
<input type=hidden name="CheckKey" id="CheckKey" value=''><br>
<!--
在執行submit之前呼叫RA_Check();將驗證碼指定給CheckKey這個hidden欄位
-->
<input type=button value=" verify " onclick="<?=$RA->CheckFunction()?>;submit();">
</form>
</body>
</html>
如果是一個正常的User在操作,並不會感覺到需要額外輸入任何東西
可是一但灌水機器人介入時
要分析就很麻煩了
-
我把程式打包好了
線上Demo (http://www.ez2.us/~ricky/RobotAway/)
裡面就有下載點嘍
-
你示範的方法似乎是在單一頁submit使用
那如果是POST到另外一頁的話呢?
-
你示範的方法似乎是在單一頁submit使用
那如果是POST到另外一頁的話呢?
應該是在接收端利用 verify 就可以了...
-
你示範的方法似乎是在單一頁submit使用
那如果是POST到另外一頁的話呢?
要POST到哪一頁其實都沒差
只要呼叫Verify去檢驗post的Key就行了
由於沒有使用session來存放key
我採用了md5這種單向演算法
原理是這樣
$Time=sprintf("%x",time());
$ID=md5($Time.$this->EncodeKey);
$this->VerifyKey=$Time.$ID.md5($ID.$this->EncodeKey);
EncodeKey是一個你要永久保存的"密碼"(別給人知道)
Time是一個時間戳記,用來判別這個驗證碼是否還在時效內
由於md5是一種單向的加密演算法
加上別人沒有你的EncodeKey,就無法由驗證碼(VerifyKey)推得
你只需要在你要驗證的那個網頁
$EncodeKey中帶入之前您保留的EncodeKey就行了
至於第二的參數由於你只要檢驗而不是要產生Javascript就可以不需帶入
$RA=new RobotAway($EncodeKey);
-
嗯..那用 crypt 也可以有同樣效果
-
code 好像是 PHP5 以上才能用?
如果要改成 PHP4 可以用的話要怎麼修改啊 @@
-
code 好像是 PHP5 以上才能用?
如果要改成 PHP4 可以用的話要怎麼修改啊 @@
??...ricky 寫的應該是 php4 的語法,應該不會有 php5 才能用的問題,你可以先試試看唷...^_^...
-
code 好像是 PHP5 以上才能用?
如果要改成 PHP4 可以用的話要怎麼修改啊 @@
??...ricky 寫的應該是 php4 的語法,應該不會有 php5 才能用的問題,你可以先試試看唷...^_^...
試了幾次都蠻奇怪的,都直接出現錯誤
單純引入 robotaway.inc 就出現了錯誤 @@
我是直接下載 rciky 兄 DEMO 網站中的檔案下載使用
rw.inc.php 沒有任何修改
Index.php 中單純地
<?php
require_once('rw.inc.php');
?>
出現錯誤
Parse error: syntax error, unexpected T_STRING, expecting T_OLD_FUNCTION or T_FUNCTION or T_VAR or '}' in /home/rocotw/public_html/Robotaway/rw.inc.php on line 53
-
哈的確是php5 only
不過也沒有真正用到php5的功能啦
想說既然php4停止支援了
就順應潮流改用php5
如果真的要在php4上跑
改code也不難啦
把class 中的private,public拿掉
__construct 改成RobotAway
這樣就ok了
-
XD... ricky 我只有看到你的變數是用 var 我就以為你是寫 php4 的..:p..
沒想到後面還有用到 private 阿....
-
很有創意的方法
但剛想到一個問題
即此機器人只能防止tag分析型的機器人
如果我是用一般的桌面開發軟體例如delphi就無效了
可以先寫好一個ie模擬器
然後依此程式自動上網取得網頁回來
再自動填值送出就破解了
不過還是一個很不錯的idea^^
-
很有創意的方法
但剛想到一個問題
即此機器人只能防止tag分析型的機器人
如果我是用一般的桌面開發軟體例如delphi就無效了
可以先寫好一個ie模擬器
然後依此程式自動上網取得網頁回來
再自動填值送出就破解了
不過還是一個很不錯的idea^^
最根本的問題還是在,Client端必須透過Javscript的engine去取得Key
既然Client必須去執行Javascript那就有很多方法可以去"惡整"用軟體跑的人嘍
像是用Javascript抓取滑鼠,鍵盤的Input行為,馬上就可以找出這是用程式還是實際的人
如果要用程式去抓取螢幕中"submit"按鈕的位置,不就又回到圖片辨識的老問題
是否有必要做到這麼的"嚴苛",簡化User的操作才是目的不是嗎??
能夠檔的住95%的惡意攻擊就足夠了,剩下的5%再來專案處理。
如果攻擊者真的有心,就算用了一堆傷眼圖片,還有人海戰術可以用
對岸啥不多,就是閒到可以請一堆人用人工去猜密碼
-
最根本的問題還是在,Client端必須透過Javscript的engine去取得Key
既然Client必須去執行Javascript那就有很多方法可以去"惡整"用軟體跑的人嘍
像是用Javascript抓取滑鼠,鍵盤的Input行為,馬上就可以找出這是用程式還是實際的人
如果要用程式去抓取螢幕中"submit"按鈕的位置,不就又回到圖片辨識的老問題
是否有必要做到這麼的"嚴苛",簡化User的操作才是目的不是嗎??
能夠檔的住95%的惡意攻擊就足夠了,剩下的5%再來專案處理。
如果攻擊者真的有心,就算用了一堆傷眼圖片,還有人海戰術可以用
對岸啥不多,就是閒到可以請一堆人用人工去猜密碼
[/quote]
哈哈
我已經看破了
現在都是直接鎖大陸的ip了
省得一堆麻煩
-
今天發現一個問題,這個程式無法支援Opera瀏覽器
在這種瀏覽器下驗證都無法通過
只能先暫時將它拿掉了
-
最根本的問題還是在,Client端必須透過Javscript的engine去取得Key
既然Client必須去執行Javascript那就有很多方法可以去"惡整"用軟體跑的人嘍
像是用Javascript抓取滑鼠,鍵盤的Input行為,馬上就可以找出這是用程式還是實際的人
如果要用程式去抓取螢幕中"submit"按鈕的位置,不就又回到圖片辨識的老問題
是否有必要做到這麼的"嚴苛",簡化User的操作才是目的不是嗎??
能夠檔的住95%的惡意攻擊就足夠了,剩下的5%再來專案處理。
如果攻擊者真的有心,就算用了一堆傷眼圖片,還有人海戰術可以用
對岸啥不多,就是閒到可以請一堆人用人工去猜密碼
哈哈
我已經看破了
現在都是直接鎖大陸的ip了
省得一堆麻煩
[/quote]
請問大大,您如何直接鎖大陸的ip呢?
可以教教嗎?謝謝!
-
我想到一個方法去解決也實作了公司的電腦財產管理和一些訂單和問卷調查的程式,不知觀念是否正確
1. 所有和資料庫有關的部份都檢查$_SESSION['ID']是否有設定,並且每支程式都檢查$_SESSION['RIGHT']有沒權限執行
2. $_SESSION['ID']和$_SESSION['RIGHT']是在index.php中由使用者輸入並不是寫死在程式中
我的想法是以此擋住機器人和想盜取程式中連資料庫的帳號和密碼,不知這樣是否有盲點? 觀念是否正確?
-
我想到一個方法去解決也實作了公司的電腦財產管理和一些訂單和問卷調查的程式,不知觀念是否正確
1. 所有和資料庫有關的部份都檢查$_SESSION['ID']是否有設定,並且每支程式都檢查$_SESSION['RIGHT']有沒權限執行
2. $_SESSION['ID']和$_SESSION['RIGHT']是在index.php中由使用者輸入並不是寫死在程式中
我的想法是以此擋住機器人和想盜取程式中連資料庫的帳號和密碼,不知這樣是否有盲點? 觀念是否正確?
原作應該不是這個意思。你的做法大概是ACL吧?
許多blog有匿名留言的功能,為了防止機器人灌水大量貼廣告留言,通常會設計captcha的方法來做驗證,假設機器人沒有人眼的能力來辨識圖形裡的文數字,就可以達成攔阻的效果。(不過目前許多這類的機器人都有能力做圖形裡面文數字的辨認了)Ricky大的做法是假設這類機器人沒有內建javascript引擎,所以設計了必須有內建javascript引擎才容易破解的方法。
阻擋機器人是一個永遠的課題,因為他會隨著技術進步。前一陣子在John Resig的blog文章中看到,有人用GreaseMonkey,加上HTML5 的Canvas來讀取圖片,然後配合用javascript寫的類神經網路演算法的程式來破Captcha呢。
-
大部份都是大陸ip比較多直接鎖了就可以了,不然反而麻煩
-
感谢大大的办法。
我这边(日本)怎么都是从欧洲来的讨厌机器人呢?
-
用VB6 + WebBrowser控制件就可以灌水了
除非...輸出Form時,不產生或是隨機產生id,name等屬性
因為要接收,所以至少要有name屬性
所以,建議所有要使用的大大
採用隨機產生Name屬性的方式
否則可以很輕易的抓到DOM
-
用VB6 + WebBrowser控制件就可以灌水了
除非...輸出Form時,不產生或是隨機產生id,name等屬性
因為要接收,所以至少要有name屬性
所以,建議所有要使用的大大
採用隨機產生Name屬性的方式
否則可以很輕易的抓到DOM
不支援其它 Browser ,不跨平台...只能用某一家的東西而且很貴
--
好的機器人就算是隨機產生Name也沒用啊!!
-
用VB6 + WebBrowser控制件就可以灌水了
除非...輸出Form時,不產生或是隨機產生id,name等屬性
因為要接收,所以至少要有name屬性
所以,建議所有要使用的大大
採用隨機產生Name屬性的方式
否則可以很輕易的抓到DOM
這是個概念性的東西,看個人如何去發揮嘍。
例如搭配ajax,採用javascript的變數去儲存,這個方法就完全失效了。
我也有遇過使用螢幕比對 (http://groups.csail.mit.edu/uid/sikuli/)的方式去控制滑鼠點選。(當然這個防灌水方法也就失效了)
不過加個簡單的手腳,讓submit按鈕位置隨機擺放,大小也隨機,那圖形比對就會破功了。 ;D
-
這是個概念性的東西,看個人如何去發揮嘍。
例如搭配ajax,採用javascript的變數去儲存,這個方法就完全失效了。
我也有遇過使用螢幕比對 (http://groups.csail.mit.edu/uid/sikuli/)的方式去控制滑鼠點選。(當然這個防灌水方法也就失效了)
不過加個簡單的手腳,讓submit按鈕位置隨機擺放,大小也隨機,那圖形比對就會破功了。 ;D
沒研究過機器人
如果用逆向思考的方式
在表單中隨機插入數組input
然後該input區塊用CSS隱藏
讓正常使用者不會去編輯到該區塊來判斷是否為機器人的方式是否可行呢?
-
沒研究過機器人
如果用逆向思考的方式
在表單中隨機插入數組input
然後該input區塊用CSS隱藏
讓正常使用者不會去編輯到該區塊來判斷是否為機器人的方式是否可行呢?
應該說機器人是看原始碼的....所以 css 無效
-
你這么做比記錄session消耗的資源還要多,不可取,你甚至不如將需要記錄到session內的驗證碼存放到txt文件內直接讀取文件內容來的快速呢.
-
我都是直接做一道圖片驗證和兩道問題.... 很多 bot 無法回答那兩道問題就解決了