使用PHP-curl獲取驗證碼並模擬登入教務系統
首先說一下這篇文章的需求,當我們在一些沒有提供驗證介面的系統中,需要驗證使用者身份的時候,就可能需要使用者登入當前系統,從而確定該使用者是當前系統的合法使用者,校園的教務系統就是一個典型的例子,我們通過學生自己登入學校的教務系統從而確定該使用者為在校生。
但是,現如今各式各樣的系統為了安全起見,通常都會設定驗證碼防止惡意攻擊,這裡就以本校的為例簡單介紹一下如何使用PHP-curl請求登入驗證碼並模擬登入教務系統。
首先上圖:
如上圖,正常的登入介面,包括使用者名稱,密碼以及驗證碼的表單,模擬提交表單不難,主要是這裡的驗證碼,因為我們是自己重新寫了一個模擬登入頁面,則需要將教務系統的驗證碼提取到我們自己的頁面中來。
首先我們分析一下請求的過程:
首先是直接請求教務系統網址,如果是第一次請求,則在響應中則會設定cookie,如果不是第一次請求,則在這次請求中就會攜帶上cookie,我們模擬一下包含cookie的和沒有包含cookie的請求如下:
非第一次請求(請求中帶有cookie)的請求和響應頭部資訊如下:
第一次請求(請求中帶有cookie)的請求和響應頭部資訊如下:
很明顯的能從中看到區別,在第一次請求中沒有Cookie欄位,同時在對應的響應中包含Set-Cookie:ASP.NET_SessionId=de42wg55nry5w12gzb3xdz45; path=/
欄位。
也就是說在第一次請求頁面完成之後本地都會有cookie存在,這個cookie是非常有用的。我們需要知道第一次頁面的請求和驗證碼圖片的請求不是同一次請求,驗證碼的請求實際上是拿到了cookie之後再次去請求了一次,如下圖:
這次請求使用了第一次請求拿到的cookie,cookie的作用就在於識別當前使用者,同時在伺服器後端儲存對應客戶端的驗證碼的值,讓客戶端和驗證碼對應起來,所以我們的主要工作就是操作cookie。
php的curl擴充套件很好的集成了模擬客戶端瀏覽器的功能,這裡再簡單的紹一下,比如我現在要直接去請求教務系統官網(其實只是為了第一次獲取到cookie值),我可以根據瀏覽器中的請求引數來設定php-curl的引數,如下:
瀏覽器中的請求引數如下:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Host:222.24.19.201
Pragma:no-cache
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
使用curl請求如下:
function getCookie(){
$ch = curl_init();
$url = "http://222.24.19.201/"; //此處修改為教務系統登陸頁URL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_ACCEPT_ENCODING, "gzip, deflate, sdch");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$contents = curl_exec($ch);
curl_close($ch);
//為了匹配出來cookie的值
preg_match('/Set-Cookie: ASP.NET_SessionId=(.*);/', $contents, $str);
Log::write("getCookie:".$str[1]);
return $str[1];
}
關於php-curl的引數自行搜尋,引數挺多的,後續再介紹另外一種方式,這裡我們先看一下這個cookie的值,原本php-curl有一個使用檔案儲存cookie的引數CURLOPT_COOKIEFILE
,但是牽扯到檔案的IO可能會降低效率,所以我這裡採用正則匹配直接獲取cookie的值了。
這裡就又有新的問題了,使用cookie檔案有對應的引數,但是直接使用cookie的值沒有對應的引數,所以我們就需要手動設定請求頭,也就是上面說的另外一種設定請求引數的方式,如下是在我拿到我自己頁面提交的使用者名稱,密碼,驗證碼之後去請求教務系統的程式碼實現:
function CheckId($sid,$passwd,$captcha)
{
$cookie = "ASP.NET_SessionId=".$_SESSION["cookie"];
$HTTP_REQUEST_HEADER = array(
"Host: 222.24.19.201",
"Connection: keep-alive",
"Cookie: ".$cookie,
"Pragma: no-cache",
"Cache-Control: no-cache",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Origin: http://222.24.19.201",
"Upgrade-Insecure-Requests: 1",
"User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
"Content-Type: application/x-www-form-urlencoded",
"Referer: http://222.24.19.201/",
"Accept-Encoding: gzip, deflate",
"Accept-Language: zh-CN,zh;q=0.8"
);
$ch = curl_init();
$url = "http://222.24.19.201/default2.aspx"; //此處修改為教務系統免驗證碼登陸頁URL
$post_data = "__VIEWSTATE=dDwtNTE2MjI4MTQ7Oz61IGQDPAm6cyppI%2BuTzQcI8sEH6Q%3D%3D&txtUserName=".$sid."&Textbox1=".$passwd."&TextBox2=".$passwd."&txtSecretCode=".$captcha."&RadioButtonList1=%D1%A7%C9%FA&Button1=&lbLanguage=&hidPdrs=&hidsc=";
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $HTTP_REQUEST_HEADER);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
// curl_setopt($ch, CURLOPT_COOKIEJAR, $cookfile); // 連線斷開後儲存cookie
// curl_setopt($ch, CURLOPT_COOKIEFILE, $cookfile); // cookie 寫入檔案
curl_setopt($ch, CURLOPT_COOKIESESSION, 1);
//以下為SSL設定
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
//抓取URL並把它傳遞給瀏覽器
$res = curl_exec($ch);
$httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
//關閉cURL資源,並且釋放系統資源
curl_close($ch);
if ($httpCode == 302) {
return true;
}else if ($httpCode == 200){
return false;
}
return false;
}
可以在上面的程式碼中看到,教務系統傳給瀏覽器的cookie我在後臺儲存在session中,然後為了設定請求頭引數,使用了陣列的方式,然後通過curl_setopt($ch, CURLOPT_HTTPHEADER, $HTTP_REQUEST_HEADER);
將所有的請求頭部引數直接通過陣列的方式進行設定。在隨後的引數設定中也能看到,兩行註釋的位置就是之前使用檔案儲存cookie的方式,設定了資料夾和儲存cookie的檔案。
隨後,我們是通過響應的狀態碼來判斷是否成功登入的,如下是成功登入的響應:
General
Request URL:http://222.24.19.201/default2.aspx
Request Method:POST
Status Code:302 Found
Remote Address:222.24.19.201:80
Referrer Policy:no-referrer-when-downgrade
Response Headers
Cache-Control:no-cache, no-store
Content-Length:142
Content-Type:text/html; charset=gb2312
Date:Tue, 16 May 2017 14:42:32 GMT
Expires:-1
Location:/xs_main.aspx?xh=04142051
P3P:CP=CAO PSA OUR
Pragma:no-cache
Pragma:no-cache
Server:Microsoft-IIS/6.0
X-AspNet-Version:1.1.4322
X-Powered-By:ASP.NET
也就是說當跳轉到登入成功的主頁的時候,我們就能判斷使用者成功登入了,也就是所謂的狀態碼是302的時候。
還有一個比較重要的部分就是使用者提交的表單內容:
Form Data:
__VIEWSTATE:dDwtNTE2MjI4MTQ7Oz61IGQDPAm6cyppI+uTzQcI8sEH6Q==
txtUserName:04142051
Textbox1:************
TextBox2:************
txtSecretCode:txn1
RadioButtonList1:(unable to decode value)
Button1:
lbLanguage:
hidPdrs:
hidsc:
source如下:
__VIEWSTATE=dDwtNTE2MjI4MTQ7Oz61IGQDPAm6cyppI%2BuTzQcI8sEH6Q%3D%3D&txtUserName=04142051&Textbox1=*****密碼******&TextBox2=*****密碼******&txtSecretCode=txn1&RadioButtonList1=%D1%A7%C9%FA&Button1=&lbLanguage=&hidPdrs=&hidsc=
上面就是POST請求操作的所有資料的字串。
總結一下,需要注意這麼幾個點:
首先,就是cookie的問題,我們需要先從教務系統獲取本次請求的cookie並儲存起來,方便後續請求使用;其次,是請求頭部的設定問題,請求頭部使用的陣列是直接使用冒號將請求欄位和請求欄位的值連起來作為陣列的一個元素,而不是使用鍵值對陣列的方式。這是我在使用的時候遇上需要注意的問題,其他的像資料校驗以及免登入等就看自己的邏輯實現了。