[轉(zhuǎn)]來(lái)聊聊條件競(jìng)爭(zhēng)
條件競(jìng)爭(zhēng),無(wú)非就是多個(gè)線程(action)同時(shí)訪問(wèn)一個(gè)數(shù)據(jù)(data)沒(méi)有進(jìn)行加鎖操作,而產(chǎn)生非預(yù)期結(jié)果的行為。
開(kāi)發(fā)者在進(jìn)行代碼開(kāi)發(fā)時(shí)常常傾向于認(rèn)為代碼會(huì)以線性的方式執(zhí)行,但他們忽視了并行服務(wù)器會(huì)并發(fā)執(zhí)行多個(gè)線程,這就會(huì)導(dǎo)致意想不到的結(jié)果。
存在以下條件的事務(wù),可能存在條件競(jìng)爭(zhēng):
-
并發(fā)。至少得存在兩個(gè)并發(fā)執(zhí)行流。
-
共享對(duì)象。即多個(gè)并行任務(wù)訪問(wèn)同一個(gè)數(shù)據(jù)。
-
改變數(shù)據(jù)(寫(xiě)操作)。至少有一個(gè)進(jìn)程對(duì)數(shù)據(jù)進(jìn)行了改變。如果所有進(jìn)程執(zhí)行的都是讀操作,那就不會(huì)有條件競(jìng)爭(zhēng)錯(cuò)誤了。
舉個(gè)最簡(jiǎn)單的條件競(jìng)爭(zhēng)例子:
$cnt=file_get_contents('count.txt');//count.txt 初始值為0$cnt+=1;
echo "這個(gè)頁(yè)面已經(jīng)被訪問(wèn)".$cnt."次了";file_put_contents('count.txt',$cnt);
在這個(gè)代碼里,php使用file_get_contents讀取count.txt文本,將文本的值進(jìn)行“+1”操作并進(jìn)行輸出。最后將這個(gè)“+1”的值寫(xiě)回這個(gè)文本中。
我們?cè)谠L問(wèn)這個(gè)頁(yè)面的時(shí)候,頁(yè)面就可以統(tǒng)計(jì)這個(gè)網(wǎng)頁(yè)被訪問(wèn)的次數(shù)了。
我們有些時(shí)候會(huì)為了某些目的,去寫(xiě)一個(gè)腳本瘋狂訪問(wèn)這個(gè)頁(yè)面。但是當(dāng)我們?nèi)プ屇_本替我們?cè)L問(wèn)1000次的時(shí)候,可能最后輸出也就幾百次(甚至幾次)......
怎么回事呢?條件競(jìng)爭(zhēng)了唄。
可能,在線程0執(zhí)行到代碼第七行的時(shí)候($cnt=2),同時(shí)有300個(gè)進(jìn)程也在嘗試寫(xiě)訪問(wèn)“count.txt”這個(gè)文檔,它們的$cnt可能不盡相同,也可能都相同。存入的值也就五花八門(mén)了。
在存入時(shí),可能會(huì)出現(xiàn)互相爭(zhēng)搶資源的情況,
比如0號(hào)線程存入了個(gè)1,
同時(shí)4號(hào)線程存入了個(gè)3,
19號(hào)線程執(zhí)行的稍微慢些,存入了個(gè)1......
或者
在2號(hào)線程對(duì)文件寫(xiě)入時(shí),文件的值剛被刪除,59號(hào)線程就讀取到了這個(gè)空文件的值......然后一切又從零開(kāi)始了
來(lái)看看真實(shí)場(chǎng)景下出現(xiàn)的條件競(jìng)爭(zhēng)吧
header("Content-Type:text/html;charset=utf-8");$filename = $_FILES['file']['name'];$ext = substr($filename,strrpos($filename,'.') + 1); #后綴
$path = 'uploads/' . $filename;$tmp = $_FILES['file']['tmp_name'];if(move_uploaded_file($tmp, $path)){if(!preg_match('/php/i', $ext)){ #判斷后綴是否為phpecho 'upload success,file in '.$path;}else{unlink($path); #已經(jīng)上傳后判斷若是PHP則刪除die("can't upload php file!");}}else{die('upload error');}
這道題,曾是一道17年的CTF題,用戶上傳文件到服務(wù)器上,如果檢測(cè)到已經(jīng)上傳成功的文件擴(kuò)展名是.php,那就unlink(刪除)它。
在執(zhí)行完move_uploaded_file之后,執(zhí)行unlink之前,此時(shí)這個(gè)php文件是已經(jīng)保存到了web服務(wù)器上的,并且我們能夠訪問(wèn)。
就這樣,我們弄出了這樣的一個(gè)php腳本,并打算把它上傳到服務(wù)器上......
$content='<?php system($_GET["c"]);?>';file_put_contents('test.php',$content);
然后一頭一遍又一遍的往這個(gè)接口去上傳這個(gè)腳本,另一頭去嘗試訪問(wèn)服務(wù)器上這個(gè)腳本的名稱(假設(shè)上傳成功了的話)和訪問(wèn)這個(gè)腳本所生成的一句話木馬文件(test.php),如果訪問(wèn)后兩個(gè)腳本服務(wù)端都返回腳本存在,那......
咱們就卡bug成功了唄
總之,我個(gè)人認(rèn)為,條件競(jìng)爭(zhēng)就是在卡bug。
黑客們?cè)谫€,服務(wù)器是否會(huì)有充分的時(shí)間來(lái)處理每一條指令。會(huì)不會(huì)被我們鉆到空子
正如我在烤盤(pán)飯打工的時(shí)候,當(dāng)前臺(tái)同時(shí)取餐過(guò)多時(shí),我們可能會(huì)因?yàn)闆](méi)有充分的反應(yīng)時(shí)間,導(dǎo)致忘記回收號(hào)牌,出錯(cuò)餐,牌子和夾子對(duì)不上等等問(wèn)題。
這,就是條件競(jìng)爭(zhēng)。
條件競(jìng)爭(zhēng)如何防御?
-
給服務(wù)器足夠的時(shí)間處理這些請(qǐng)求。(不妨強(qiáng)迫客戶端慢一點(diǎn)請(qǐng)求借口......)
-
加鎖,在向數(shù)據(jù)庫(kù)做寫(xiě)入操作的同時(shí),給這行數(shù)據(jù)表加寫(xiě)鎖(別人都不能讀取和寫(xiě)入這行數(shù)據(jù))
-
調(diào)優(yōu)代碼邏輯,對(duì)不穩(wěn)登的文件,一定要檢查好再存儲(chǔ)在服務(wù)器上。
反正,應(yīng)對(duì)條件競(jìng)爭(zhēng),我們能做的,核心就是:
慢慢來(lái),慢慢處理
“慢慢來(lái)”,不僅僅只對(duì)條件競(jìng)爭(zhēng)生效,對(duì)感情一樣有效。
祝愿(000 0000 01 10)有(1011 001 01 10)情(1011 001)人(1001 00 10)終(1010 0000 0 10)成(1100 10 111)眷(1101 00 01 10)屬(000 0000 111 011)(說(shuō)誰(shuí)倆誰(shuí)倆心里清楚)
