上一章起了個頭,這一章咱們親身做一下這個API的基礎結構。
我們給它叫做“老趙API系統”。
首先,我們要做的這個API系統是私有的,不開源的,不分發給其它人一起用(當然你非要大力推廣,也隨便你)。
其次,我故意遺漏了一個小小的點子,這個點子我自己用,我也是怕我這個辦法泄露后會有安全問題。
就當是拋磚引玉吧。
先說要注意的幾點:
0、不要使用默認首頁
1、不使用SESSION和COOKIE
2、每次訪問都需要驗證用戶名密碼
3、任何單個文件都不允許能正常運行
4、任何單個文件都不允許能單獨實現任何功能
5、未驗證成功身份時不輸出任何錯誤提示
相信很多高人一看這幾條就會知道我要怎么做。也會有一部分人會嗤之以鼻說太初級。
管他呢,反正這是我自己摸索出來的,就是黑客黑我也只能在操作系統方面下手,在我這套系統里他永遠不可能有成功的訪問請求。
下面詳細說:
0、不要使用默認首頁。
如果你的環境里首頁設置了很多,比如index.php,index.html,default.html,default.php之類的,那么你API的接口文件一定不能用這些,比如你可以用iLaoZhao_XX_api.php,這誰猜得到?
1、不使用SESSION和COOKIE。
COOKIE是用明文存在于用戶端的,很容易查看和修改,這個大家都清楚。而PHP的SESSION也依賴于COOKIE來存儲一個SESSION_ID。并且我們的API要每次訪問就返回一次數據,很多訪問方法并不會存儲COOKIE,這會導致SESSION失效。
2、每次訪問都驗證用戶名密碼。
現在流行那個啥叫Token的方式,我懶得研究,倒不如每次都驗證用戶名和密碼,這樣等密碼泄露后只要我改一個密碼,他那邊立馬就不能用了。都不用等他那邊信息過期。當然我們不能明文傳遞這些東西,需要做一定加密處理。
3、任何單個文件都不允許能正常運行。
資源文件夾不能有任何可執行文件。并且盡量多分幾個文件,所有的文件相互依賴,并且把它們互相組織起來的文件又是另一個單獨文件,這樣即便別人知道了我們的文件路徑和文件名,當訪問這個文件時,也無法正常運行。
4、任何單個文件都不允許能單獨實現任何功能。
某個文件要實現功能,必須依賴其它文件內的函數或類,并且當前文件不能知道這些函數和類具體在哪個文件里實現的。這樣即便這個文件的源代碼泄露,對方也不知道里面的依賴關系,不會連累到其它文件。
5、未驗證成功身份時不輸出任何錯誤提示。
在用戶名密碼出錯時,系統不要輸出任何有用的信息,或者輸出一個假的404信息,這樣別人在試探我們的系統時,根本不知道是哪一步出錯了,而我們的客戶端可以根據里面的暗語來判斷狀態。
我做一個小例子來說明一下,這個小例子只是拋磚引玉,并不是我最終的代碼樣子。
文件樹結構:
老趙API文件樹結構
根目錄下只有一個文件夾,整個API系統都通過一個接口文件”DaYeLaiWanA.php”來調用。
怎么樣,不知道文件結構的人根本猜不對你有哪些文件,也就不能訪問任何文件。
下面的代碼沒有使用太先進的語法,比如類和對象啊命名空間啊啥的,新版本PHP添了好多特性,但我比較傾向于不去使用它們,我要盡量的讓代碼對環境沒有要求,在盡可能多的版本環境下都能運行。
畢竟我們要的是安全,而不是讓所有人能讀懂我們的代碼。
我們要盡可能的不使用教科書上的通用的寫法,盡可能搞一套自己的寫法。
下面是DaYeLaiWanA.php的代碼:
<?php
//接口文件,單獨調用這個文件沒任何作用,參數必須得對,差一個字母這個文件都運行不出個毛線來。
//而參數是你自己定的,可隨意加密修改。
//當然你還可以把這個路徑也放到變量里,然后藏起來。
include "./iLaoZhao_funs/postget.php"; //沒有依賴,但也不實現任何具體功能
include "./iLaoZhao_funs/login.php"; //依賴pdo,dbfuns,postget和接口文件
include "./iLaoZhao_funs/curl.php"; //沒有依賴,但也不實現任何具體功能
include "./iLaoZhao_funs/dbfuns.php"; //依賴pdo和接口文件
//上面這些文件里涉及到安全的功能都有依賴,并且他們不知道要用的函數在哪
//全靠這個接口文件把它們組合到一起
//并且上面的函數里也會用到接口文件的函數(就是下面那個)
//這些因素缺一不可,只能由接口文件來整合他們
//上面這些文件和其它的API文件,都無法單獨運行。不是缺這就是缺那。
//下面這倆函數是故意沒單獨放文件里的,為的就是其它文件缺少這個接口文件時在調用這個函數時會出錯。
function output2Die($msg, $code = -1, $data = ''){
$dieMsg['code'] = $code;
$dieMsg['msg'] = $msg;
$dieMsg['data'] = $data;
echo json_encode($dieMsg);
die();
}
function isPhone($phonenumber)
{
return preg_match("/^1[3-9]d{9}$/", $phonenumber);
}
$area = POST("area");//要使用的API種類
$class = POST("class");//要使用的API小分類
$fun = POST("fun");//要使用的API功能
//清理POST
unset($_POST['area']);
unset($_POST['class']);
unset($_POST['fun']);
if ($class !== FALSE && $fun !== FALSE){
if ($area == false){
$area = "public";
}
//生成API文件全名,這里沒加密,你可以自己變動這里。
$incFile = dirname(__FILE__)."/{$area}/{$class}/{$fun}.php";
if (file_exists($incFile)){ //檢查API文件是否存在,其實不檢查也行。
include "./config/pdodb.php"; //創建數據庫連接,依賴接口文件
include $incFile;//將單獨的API文件導入進來
}else{
//我因為調試的原因,顯示了錯誤信息,要求高的可以去掉。
output2Die("無效的請求", -1);
}
}else{
output2Die("無效的請求", -2);
}
文件我寫了注釋,方便大家看,實際在使用時不能有這些注釋,這些注釋只能方便別人破解我們的系統,并且這個接口文件盡可能進行代碼混淆加密。
然后是一個獲得當前用戶信息的小例子API,這個API文件存在了
/iLaoZhao_api/public/login/usrMsg.php里面。在調用時大概URL是這樣:
https://你的域名/iLaoZhao_api/DaYeLaiWanA.php
POST數據為:{
key:"用戶名密碼時間加密字符串運算后的結果",
class:"login",
fun:"usrMsg",
userName:"該用戶的用戶名"
}
API代碼內容usrMsg.php為:
<?php
//示例文件,得到當前用戶的所有信息
//文件依賴:接口文件、數據庫連接文件、dbfuns文件、login文件
//單獨訪問這個文件根本不能運行
//依賴關系完全靠接口文件處理
//這個API讀取post參數中的userName
checkUserNamePassWord(); //檢查用戶名密碼,不對真接就終止運行了,對了的話就可以運行下面的代碼
$dbArr['userName'] = POST("userName");
if($dbArr['userName'] !== false){ //這一步沒必要,能驗證過上面那個函數,這個參數肯定存在,但我就是寫了,就是玩兒
//一定要用PDO的綁定功能訪問數據庫,這種方式能避免SQL注入。
$sql = "select * from tb_users where users_userName = :userName";
$rec = db_query($sql,$dbArr);
if($rec['count'] > 0){
output2Die("成功。",1,$rec);
}else{
output2Die("找不到該用戶。",-1); //要求高的,不要輸出錯誤信息。
}
}
output2Die("失敗",-2); //要求高的,不要輸出錯誤信息。
這里面的checkUserNamePassWord()函數(在iLaoZhao_funs文件夾內的login.php文件里)也比較關鍵:
<?php
/**
* 判斷用戶名密碼的函數
此函數不允許返回東西
調用的時候直接調用
用戶正常自然沒反應
用戶不正常,這個函數直接結束程序運行。
*/
function checkUserNamePassWord(){
$keyStr = "這里是加密字符串,這個串只有你自己知道是什么。";
$key = POST("key"); //這個key是客戶端把用戶名密碼時間加密字符串運算后的結果
//用當前日期當第二密鑰,這個作用是每天的密鑰都不一樣。
//即便被攔截了,也算不出規律來,當然源碼泄露了就不行了。
//不能太精確,因為客戶端與服務器端的時間不可能完全一樣。
$keyStr_Today = date('Ymd',time());
//訪問數據庫,一定要用PDO的這個參數方式。
//這個方式在代入數據與直接寫在SQL語句里不一樣。
//sql注入在這種使用方式下完全不會生效。
$sqlPar['username'] = POST("userName"); //這個沒加密的必要,這個在輸入框里就能看到,而且也會在很多地方顯示。
if($sqlPar['username'] === false || $key === false){
//非法調用
}
//不要在sql里直接比對用戶名密碼
//而且傳過來的數據也沒有密碼
//要取出該用戶的記錄,然后計算后與傳過來的加密身份信息對比
$sql = "select * from tb_users where userName=:username"; //你可以在這limit 1
$rec = db_query($sql, $sqlPar);
if($rec['count'] > 0){
$keyInDB = md5(md5($keyStr).md5($rec['rows'][0]['userName']).md5($keyStr_Today).md5($rec['rows'][0]['passWord']));
//上面這句是加密方式,把加密字符串、用戶名、當前日期、密碼的MD5連接起來并再計算一次MD5
//當然你還可以再加點別的東西,比如字符串反轉,以及其它變量之類的。
//上面這句計算出來的和$key中的一樣時,說明密碼是對的。
if($key == $keyInDB){
//用戶名密碼對
//此時我們啥也不做
//或者你想設置某些變量也行
}else{
//否則
die(); //結束程序運行,
}
}
}
代碼里都有注釋,應該能看清楚。而且還有其它的自定義函數我就不放代碼了。
這樣我們在擴充API時只需要在對應路徑下寫PHP代碼文件就行,而且很多函數可以直接使用,不用include任何文件。
而且這個系統內所有的文件在單獨訪問時都會出錯(可以設置PHP不顯示錯誤信息),而接口文件在參數不正確的情況下也不會有什么具體運行結果。
也沒有默認的首頁文檔可以訪問。
文件路徑和文件名都沒規律,在網上都查不到參考。
那么想黑掉這套API只有兩個辦法:
0、黑掉服務器操作系統,再從文件系統入手。
1、破解客戶端代碼或者攔截訪問數據,找到訪問規律。
這兩點暫時無能為力解決。
**邊寫文檔邊寫代碼,頭有點亂,可能有遺漏的東西,不知道大家能不能看懂。
**此API系統還有很多地方可以進一步加密處理,但我個人感覺沒必要了。已經夠可以了。
**此代碼沒有使用太先進的語法,比如類啊命名空間啊啥的,新版本PHP添了好多特性,但我比較傾向于不去使用它們,我要盡量的讓代碼對環境沒有要求,在盡可能多的版本環境下都能運行。
畢竟我們要的是安全,而不是讓所有人能讀懂我們的代碼。
有啥忘了的以后再補充吧。
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。