Linux就這個範兒 第14章 身在江湖

weixin_34162629發表於2016-03-27

Linux就這個範兒 第14章 身在江湖

 

“有人的地方就有江湖”,如今的計算機世界就像一個“江湖”。且不說冠希哥有多麼無奈,把微博當QQ的局長有多麼失敗,就說如此平凡的你我什麼時候就成了任人擺佈的羔羊也一點都不奇怪。計算機的安全問題一直讓我們深受其害。可以說,使用過計算機的人就沒有不被病毒、蠕蟲甚至木馬侵害過的。輕一點的少幾個檔案或做個勤快的“肉雞”,嚴重的可能別人知道你銀行密碼的速度比你老婆還快。這隻能感嘆由於利益的驅動讓黑客們的技術進步得實在是太快。
還好,有江湖的地方同樣也有“仗劍的俠士”,能夠“遇見不平一聲吼,該出手時就出手”,比如各種各樣的防毒軟體、網路安全軟體等。其實每一種作業系統自身也是“仗劍的俠士”,只是“武功”的高低不同罷了。如果這麼比喻的話,可能馬上會有人想到Windows,認為它的“武功”很挫。因為在計算機世界的這個“江湖”中,受害最多的就是Windows所“庇護”的那些使用者們。但是這是一個非常錯誤的認識,Windows在“江湖”上應該屬於一等一的高手,人家可是通過了美國C2級安全認證的。Windows的安全問題之所以被人們廣為詬病,就是因為受它“庇護”的人太多了。有點忙不過來不是?而且由子可攻擊的“肉”多,自然攻擊它的狼也多,即便Windows是藏獒也對付不了。畢竟好狗鬥不過群狼啊!

安全等級 從高到低:A->B->C2(Windows\SQL Server)->C1->D


14.1 C2級安全認證
既然提到了美國C2級安全認證,那我們就來看看它到底處在一個什麼位置上。
話說美國國防部為電腦保安的不同級別制訂了4個準則,從高到低的順序是A、B、C、D,每一個級別還細分了若干子級。顯然C2是處於中等偏下的水準,看來Windows真不咋的。但是彆著急,我們們接著往下看。
作為A、B這兩個最高安全等級是應用於國防的,我們一般人玩不起它,也不是作業系統自己能夠搞定的。比如A級設定中,一個磁碟驅動器從生產廠房直至其安裝到計算機上都要被嚴格跟蹤。作為我們小老百姓去實施這樣的安全防護措施不累麼?
那麼再看C級,本身被劃分為C1和C2兩個子級。
作為C1級的系統也要求硬體有一定的安全措施,不過只是要求給機箱加個鎖。作為普通人,只要你能給你家大門安上一把鎖就算滿足這個標準了(應該都能滿足)。對於軟體的要求是在使用之前必須登入,並且具有完全的訪問控制能力,允許系統管理員為一些程式或資料設立訪問許可權。但是C1級不要求控制進入系統的使用者的訪問級別,這就允許使用者可以把系統的資料任意移走。
C2則對C1進行了加強,引入了受控訪問。這一特性不僅以使用者許可權為基礎,還進一步限制了使用者執行某些系統指令。授權分級使系統管理員能夠給使用者分組,授予它們訪問某些程式的許可權或訪問分級目錄。另一方面,使用者許可權以個人為單位來授權使用者對某一程式所在目錄的訪問。如果其他程式和資料也在同一目錄下,那麼使用者也將自動得到訪問這些資訊的許可權。C2級別系統還採用了系統審計。審計特性跟蹤所有的“安全事件”,如登入(成功和失敗的)、系統管理員所作的操作等。可以說C2安全級別是普通使用者能夠負擔得起且不會給使用上造成太大麻煩的最高安全等級。Windows能夠達到這個高度,作為一個面向個人電腦的作業系統已經是最高的了,因為更高的就不是軟體能做到的了。其實被人們普遍認為“安全”的Linux也不過如此了,有些地方可能還不如Windows。
可以說我們現在使用的系統幾乎都是執行在C1級別這個樣子,最主要的原因就是我們從來不想受到系統的束縛,直接拿管理員賬號來用,這才導致要藉助第三方軟體來維持你計算機的安全。
研究Windows顯然不是本書的目的所在,只是順便幫它清洗了一下冤屈。我們還是迴歸正題,研究一下Linux的安全問題。Linux也是身在計算機這個“江湖”的,但是到目前為止有關Linux安全問題的報導卻寥寥無幾。這並不是說Linux真的很安全,畢竟針對Linux的病毒不是沒有。只能說給Linux寫病毒不賺錢,所以幹這種事情的人少。但這也並不是說Linux就一點“武功”都不會,要不然也沒法在這個“江湖”上立足。本章的內容就跟大家分享一下Linux所依仗著的那些“武功”路數,是如何讓它能夠在這充滿了“血雨腥風”、“你死我活”的“江湖”中搶到了一個不小的山頭的。

 

14.2 Linux的安全問題
電腦保安領域的先驅——Robert Morris曾經說過:“確保電腦保安的三條黃金定律是:不要擁有計算機,不要開啟計算機,不要使用計算機。”按照這個說法,世界上就沒有安全的計算機。即使是普遍被認為“安全”的Linux系統,在這方面也存在嚴重的不足之處。事實就是這樣的,不管有多麼殘酷,您都得照單收著,還得享受著,要不您能咋辦?
下面我準備從三個方面來闡述一下Linux系統所面臨的安全問題,還都挺恐怖的!Linux可能會說自己很冤,可是沒有辦法,畢竟這種事兒就發生在你身上了。
14.2.1 羆客入侵
對於個人使用的Linux系統其實是不必太在意這個問題的。因為你就是那麼一小撮兒,而且很可能是被黑客們不太“看得起”的苦逼程式猿。這等於在你身上沒啥油水可撈,所以他們是不會打你的主意的。但是作為企業使用者就不同,這背後會涉及巨大的商業利益在裡面,沒有不心動的黑客。而企業又是Linux系統應用的大戶,所以就成了重災區。
那麼黑客們是如何入侵企業的Linux主機呢?一些類似於密碼暴力破解的方法就沒必要說了,因為即便現在最傻的系統管理員也知道把密碼設定得長一點,所以是基本無用的。當然,那些五花八門的黑客祕籍一類的書上花費90%篇幅所介紹的什麼監聽啊、埠掃描啊、等等的一些伎倆,都是騙你銀子的,也基本無用。因為現在大家都在使用ssh -類的遠端控制工具,監聽、掃描有什麼用呢?我要說的是基於“漏洞”的入侵方式。這種方式一直都是黑客們行之有效,屢試不爽的高招。
要說這些“漏洞”是怎麼被黑客們發現的呢?三個字——試,分析。經過不斷地嘗試和分析,在目前任何一臺執行著的Linux系統主機上都能找出安全漏洞來。我完全沒有誇大的意思,這是事實情況。原因就在於不管什麼系統都是人設計和開發出來的,你見過沒犯過錯誤的人嗎?“漏洞”就是開發人員所犯下的錯誤,而且是非常難於被察覺的錯誤。你還千萬別不服氣,認為自己十數年的開發工作所積累下來的優良經驗,不可能犯下能夠產生嚴重安全問題的錯誤。
一個最典型的故事就發生在Jon Bently的身邊,並在他的《程式設計珠璣》一書中描述過。他說:在多年的時間裡先後讓上百位專業程式設計師實現二分查詢法,而且每次都是在給出演算法的基礎描述後,很慷慨地讓他們用兩個小時的時間去實現它,而且允許他們使用自己所擅長的高階語言(包括虛擬碼)。令人驚訝地是,大約只有10%的專業程式設計師正確地實現了二分查詢法。Donald Knuth也說過:儘管第一次二分查詢演算法早在1946年就被發表,但第一個沒有Bug的二分查詢演算法卻是十二年後才被發表出來
可以說,如果一個簡單的二分查詢演算法沒有被正確實現的話,就很有可能產生一個嚴重危及安全的漏洞。不信,那就先看一下程式碼1,我用C來實現。
程式碼1:

static int binary_search( int target, int array[], int size )
{
Int l=0, r=size -1;
Int h=0;
if(size<=0)
return -1;
while(1<=r) {
h=(1+r) /2if (target>array[h] )
1=h+1else if(target<array[h] )
r=h - 1;
else
return target;
}
return -1;
}


這段程式碼看起來非常嚴謹,但是卻有著非常嚴重的問題,你看出來了嗎?最關鍵的是:

h=(1+r) /2


因為h是int型別,在32位或64位的機器上,它最大能表示的數是2147483647,如果l與r的和大於這個數會出現什麼問題呢?加法溢位,會得到一個負數。如果用一個負數作為陣列的下標會出現啥情況?有人說:訪問越界,系統崩潰。要我說這還是很優美的方式呢。如果傳遞給它的陣列是在堆中分配①的,系統就會崩潰;但是如果是在棧中分配②的,你的堆疊中的內容就可能洩露了,雖然大多是情況下是無關緊要的,但誰敢保證“有心人”的努力會白費呢?正確的寫法應該是:

h=1+((r-1)/2);


如果使用Java語言,可以是:

h=(1+r) >>>1;


當然,很多人會認為這種錯誤即便發生了,也不會造成太大損失,畢竟黑客要想從這個漏洞中獲取點什麼有用的資訊甚至入侵到系統中,還是需要付出很多時間代價去剖析或者擁有極大的運氣。但是不要忘記,大多數能夠被黑客有效利用併成功入侵系統的漏洞,基本上都是這種看似“無關緊要”的錯誤引發的,比如緩衝區溢位、SQL程式碼注入等。而且,你不能認為黑客們就沒有中500萬的運氣,也不能低估他們因為利益的驅動而嚴生耐心的程度。

①使用malloc等庫函式分配的。 TCmalloc庫 谷歌
②區域性變數。

我給出這個例子要說明的是,很多嚴重的安全漏洞並不是因為你的粗心大意帶來的。因為像上述程式碼這樣簡單的例子都要全體計算機工作者們經過12年的實踐才能發現。而作為Linux這樣一個複雜的軟體系統,又有哪些隱患至今還是不為人知的呢?而且一個很實際的問題就是,隨著Linux系統的廣泛應用,其所暴露出來的安全漏洞也越來越多了。

 

 

14.2.2 “病毒氾濫”
如果嚴格說Linux病毒氾濫是很冤枉的,只是我們將病毒、木馬、流氓軟體等統統稱為了病毒罷了。在我看來,它們不但是計算機病毒,而且還是這個社會的毒瘤。因為現在的病毒製造者根本就不是什麼喜歡惡作劇的電腦神童,其真實的本質是趨炎附勢的財奴。不但危害著企業的利益,還侵害著我們這些兢兢業業、艱苦奮鬥的房奴。
就Linux本身而言,從科學層面定義的計算機病毒是很難在Linux系統中長期生存下去的。從歷史上看,1996年秋誕生了Linux上的第一個病毒Staog,後來在1997年初又誕生了
第二個病毒Bliss,它們兩者之所以能夠成為病毒是因為當時Linux核心存在某種缺陷,而當缺陷被修正了之後,它們就隨之消亡了。直到現在,十幾年過去了,沒有發生任何實質性的Linux病毒大流行的情況出現,更談不上“氾濫成災”。可以說Linux的病毒基本就是人們茶餘飯後的談資罷了,因為一個成功的Linux病毒必須是手法高超且具有非凡創意的軟體作品,這留給真正的天才們去創造吧,那些財奴們才懶得做這樣的事情呢。
但是Linux上的惡意軟體卻有愈發瘋狂的趨勢。雖然Linux是以開放原始碼著稱,但是普通使用者有幾個能有那麼大把的時間和能力,每個軟體都從原始碼安裝,且能夠對每一個常用軟體的程式碼都瞭如指掌呢?這就給某些財奴們創造了機會,而且有時候還能冒充正義的化身,高喊著支援開源軟體運動,而在背後卻實行著他們不可告人的勾當。使得很多無辜的開發者成為他們替罪的羔羊,很多善良的人們淪為他們賺取大把鈔票的工具。隨著Android這
樣的基於Linux平臺的移動作業系統的普及,以及Google等這樣極度崇尚所謂“美式自由”的公司允許人們任意開設應用商店的境況出現,使得這種現象開始有愈演愈烈的趨勢。
可是,很多問題並不是財奴們單方面就能掀起這麼大風浪的。畢竟中國的一句古話是:“一個巴掌拍不響”。很多Linux使用者也有自身的問題。總是不愛受到限制,喜歡使用root賬號,這使得病毒、木馬、流氓軟體有了在Linux下生根的更廣闊的空間和養分。我僅能在這本書中卑微地呼籲一下:親們,善待Linux,慎用root賬號,保護你自身的權益!

 

14.2.3拒絕服務攻擊
拒絕服務攻擊也叫DoS (Denial Of Service)攻擊,這跟曾經十分流行的作業系統沒有半毛錢關係。這是一種非常缺德地侵犯計算機系統安全的行為。雖然遭到這種攻擊的使用者不會爆出什麼“豔照門”來引爆大眾的眼球,但損失依然會很慘重。之所以說DoS很缺德,是因為它如果針對你個人,你將無法正常使用你的計算機,除非斷開網路,但是沒有網路我們還怎麼活啊?要是它攻擊某些商業性伺服器,那麼不單提供服務的系統會癱瘓,所有需要得到服務的使用者也將失去服務。然而實行這種攻擊的“人”卻得不到什麼實際的好處。我之所以將人字加上引號,就是因為這種“損人不利己”的行為應該已經超出人類的價值觀了,雖然有些情況是出於某種所謂“商業”目的。
其實以現在個人計算機的運算能力,加之Linux系統優秀的TCP/IP協議棧實現,普通的DoS攻擊並不會帶來什麼特別大的威脅。面對強大的商業伺服器系統,DoS自然也不在話下了。這是Linux的一種優勢。但是,當前“黑客”(依然要用引號)們已經變換了策略,採取了更為恐怖的DDoS( Distributed Denial of Service),翻譯過來是分散式拒絕服務攻擊。這種攻擊的危害相當可怕,讓很多DoS攻擊源同時向某個計算機發起攻擊,猶如洪水猛獸一般勢不可擋,而且甚至與被攻擊計算機處於同一網路的其他無辜計算機也會受累。國內最為著名的發生於2009年5月19日的“暴風門”事件就是一起規模空前的DDoS攻擊,直接導致了中國電信的大面積網路癱瘓,面積覆蓋大半個中國。足可見DDoS的可怕之處。
面對這種型別的攻擊,可以說任何作業系統都是無能為力的。因為這涉及了一個電腦科學歷史性的“巨大Bug”- TCP協議的機制問題。從前面的章節中你可以瞭解到,TCP協議是一種面向連線的協議。兩臺計算機通過TCP協議通訊時,這兩臺主機要負責維護這個連線。這必然會消耗一定的計算資源和作業系統資源,比如檔案描述符。作為服務端的計算機往往需要同時維護多個TCP連線,但是這個數量是有限制的,因為檔案描述符會佔用記憶體,縱使可以放開描述符的限制也會遇到記憶體上的限制,這個你沒法放開。DoS或DDoS攻擊就利用了這個“漏洞”,把計算機系統的全部資源都消耗乾淨而讓它無法繼續工作。當然,這還是很普通的手法。更厲害的還有TCP Sync Flood、UDP Flood等,本書就不一一介紹了。總之是防不勝防。
雖然DoS或DDoS從原理上講是目前網際網路技術無法解決的,但是Linux的開發者們,包括其他作業系統的開發者們,依然在試圖改進一些TCP/IP協議的實現演算法,來提高系統本身對抗這種攻擊的能力。根據我的經驗,Linux在這方面不如Windows做得好。在我的從業經驗中,Windows 2003的服努器在遇到這類攻擊時的存活時間往往比Linux伺服器要長。而且有些版本的Linux核心在遇到這種攻擊後,會出現“Kernel panic”,整個系統徹底崩潰。Windows 2003卻從來沒遇到過。換句話說,在攻擊結束之後,Linux伺服器需要重啟才能繼
續工作,而Windows 2003伺服器能夠立即開始工作。這就相當於Linux系統在遇到類似問題時,往往需要更長的系統修復時間,使得整套系統的可靠性下降。雖然Linux有免費的優
勢,但是在遇到這種問題的時候,對於某些使用者來說,Windows 2003會更划算。
雖然這是一本介紹Linux的書,但是在這裡卻一直讚揚Windows很強。並不是我有什麼
叛逆的心態,只是不愛護短罷了。同時也是想讓大家瞭解Linux在安全方面相較於其他系統還是有不足的地方的,不要去迷信。


14.2.4小結
“聰明莫過帝王,伶俐莫過江湖。養家不可治氣,治氣不必養家。心有波濤面含春色,此為江湖。江湖子弟,拿得起來放得下,更應守江湖道義。奈何,今日江湖已面目全非。尤其我行英才,掰塊饅頭扔臉上讓幹嘛都行,饒塊醬豆腐造反都能商量。給十塊錢能把他父親小名兒寫電視塔上。寂寞風前獨悲立,江湖幾入還。”這是“非著名相聲演員”郭德綱先生對其所從事行業的感嘆,我們IT這個行當又何嘗不是如此呢?
就像我在開頭所說的,雖然本節所列舉的這些安全問題並不完全是Linux自身不足造成的,但是它就發生在Linux身上了,不能完全說不管我事兒。同時我也想通過這一節的介紹讓大家能夠了解到:電腦保安問題依然是任重而道遠的,諸君仍需努力。
好了,目前Linux系統上非常棘手的安全問題已經呈現給大家了,而且是一直需要去解決的問題。但是Linux系統依然被看作是安全的,這是因為它已經解決了很多問題。那麼接下來的內容,就是要介紹那些Linux已經實現了的且被廣泛應用的安全機制。Linux也正是因為有了這些安全機制,才被人們認可了它的安全性。當然,討論這些機制是否存在漏洞則超出了本書的範圍,我也不是這方面的“磚家”,真拍不動它。如果你能夠通過本書的介紹發現這些現有的安全機制存在某些漏洞,那就發揚一下開源精神告訴大家吧!

 

 

14.3 PAM使用者認證機制
日新月異的計算機技術發展了這麼多年,使用者認證方式也發生了翻天覆地的變化。比如:密碼驗證、指紋認證、RFID認證、虹膜認證等,踴躍而至,層出不窮。可是不管採用什麼樣的認證方法,都有一個不可迴避的問題,那就是你得實現它!


14.3.1 什麼是PAM(可拔插認證模組)
Linux作為一個能夠同時提供多種服務的作業系統,是不能只提供一個繞不過去的login命令就能保障其不被非法登入的。
類似sshd、ftpd這樣的應用也能讓使用者登入系統並做一些有實質性的工作,也需認證使用者身份。
所有能夠讓使用者登入系統的應用都要單獨去實現一套使用者認證方法很有重複造輪子的嫌疑。而且人在編寫軟體時想不出錯是很難的,重複地去編寫這麼多的使用者認證程式碼想保證每一個都不出錯更是難上加難。即便一個非常簡單的軟體錯誤,都有可能被黑客們拿來利用併入侵到系統中。更何況在遮麼重要的使用者認證上出現點什麼差錯呢?而且一旦發現某種方法或演算法有問題,要修改起來可就麻煩大了。因為好多應用可能都使用了這種方法或演算法,
而它們又不是一個人或一個公司做的。但凡遺漏了誰或誰懶惰了一點,對整個系統的安全性
都可能會帶來致命的威脅。
為了解決多個應用的使用者認證問題,Linux引入了一套名為PAM的使用者認證機制,它的全稱是Pluggable Authentication Modules,翻譯過來是就是“可拔插認證模組”。它將使用者認證功能從應用中獨立出來,單獨進行模組化設計,統一實現和維護,並提供了一套標準API,以便各應用程式能夠方便地使用它們所提供的各種功能。特別的,這種使用者認證機制,對於其上層的使用者(包括應用程式和終端使用者)是透明的。
PAM誕生自1995年,最先由SUN提出並應用於Solaris 2.3上。在這之後,經過廣大開發人員的不懈努力,各版本的UNIX系統陸續提供了對PAM的支援,包括FreeBSD和Linux。其中專門針對Linux實現的PAM,通常被稱為Linux-PAM。這些不同的PAM,除了具體的實現不同外,框架和標準API都是相同的。所以本書並沒有特別指明要介紹的足Linux-PAM,因為這些知識具有普適性。

 

 

14.3.2 PAM的體系結構
PAM為了提供足夠高的通用性、靈活性和可配置性,採用了外掛機制,這樣就可以針對不同的應用靈活組織不同的外掛來提供“隨需應變”的使用者認證機制。這也是其“可拔插”之名的由來。
為了實現“可拔插”性,又要兼顧易用性,PAM採用了分層的體系結構:讓各認證模組從應用中獨立出來,然後通過PAM API作為兩者聯絡的紐帶,應用程式可根據實際功能需要,靈活地在其中“插入”所需類別的認證功能模組。所以,PAM可以被劃分為三層:應用層、介面層和認證模組層。這種設計思想還有一個特別時髦的名字,叫“高內聚,低耦合”。如果你還搞不清楚啥是“高內聚,低耦合”,PAM就是一個非常典型的例子。具體可
見圖14.1所示。

 

 

 

從圖14.1中可以看出,PAM API處在中間位置,負責應用與各模組之間的通訊。當應用程式呼叫PAM API時,相關的API實現程式碼會按照“配置檔案”的規定,載入並呼叫相應的模組所提供的功能來執行具體的認證操作。這樣,只要修改“配置檔案”的相關項就可以改變具體應用的認證方式。而且,也可以根據實際需要,任意新增新昀認證模組來實現特殊的認證方式。
PAM為了提供更為細粒度的認證控制,給模組劃分了四種型別,分別代表四種不同的認證任務。它們是:auth、account、session和password。auth型別的模組用來執行實際的認證工作,比如:提示使用者輸入密碼或判斷使用者是否為root等;account型別的模組負責對使用者的各種屬性進行檢查,比如:某個使用者的密碼是否到期、root使用者是否允許在這個終端登入等;session型別的模組用於執行使用者登入前、退出後的一些操作,以及對每個會話進行跟蹤和記錄,比如:給新使用者初始化home目錄、記錄使用者登入的時間等。password型別的模組實現了使用者密碼的細粒度管理,比如:設定密碼的有效期、允許重複輸入的次數等。
這四類模組可以堆疊使用。也就是說,一個認證動作可以同時使用多個相同型別的模組共同去完成。這就像“陪審團”去判斷一個使用者是否“有罪”一樣,只要達到相應的“比例”就可以作出結論。而且每個具體的模組也不會僅限於一種型別,它們的“身份”也會隨時變化。具體應該怎樣,一切由配置檔案說了算。
作為開發人員,不管是要編寫需要認證功能的應用,還是準備給PAM編寫某種型別的模組,都需要與PAM API打交道。
PAM API所提供的一些介面會與PAM的四類模組有一定的對應關係。例如:用於認證使用者的pam_authenticate()介面和用於管理認證憑證的pam_setcred()介面對應著auth型別的模組;用於確認使用者是否有權登入系統的pam_acct_mgmt()按口對應著account型別的模組;用於開啟和關閉使用者對話的pam_open_session()和pam_close_session()介面對應著session型別的模組;用於修改使用者口令的pam_chauthtok()介面對應著password型別的模組。
PAM API所提供的另外一些介面就不再與具體的模組相對應了,它們的作用是提供一些管理性功能或實現應用與模組之間的通訊。管理性的介面有:pam_start()、pam_end()、pam_get_item()和pam_set_item()等。實現應用與模組之間的通訊介面有:pam_putenv()、pam_getenv()和pam_getenvlist()等。
至於PAM API該如何使用,我們稍後再做介紹。
如果是做為Linux系統管理員,要針對某些特殊的應用制定特別的使用者認證方案或新增新的PAM模組,就需要與配置檔案打交道了。
PAM的配置檔案通常是pam.conf檔案或pam.d目錄,它們都會儲存在/etc目錄下。具體是使用pam.conf還是pam.d目錄,不同的發行版可能不同,但是它們一般不會同時出現。毋庸置疑,在使用pam.conf檔案的時候,所有配置資訊都會儲存在這個檔案中;使用pam.d檔案的時候,每個應用會有一個與它對應的配置檔案,對應方式就是具體的配置檔名會與對應的應用名相同。如果pam.conf有機會與pam.d目錄同時出現的話,pam.conf是會被無情地拋棄的。

 

14.3.3 配置PAM
不管採用何種形式,配置檔案的格式都差不多,由很多條記錄組成。一般地,一條記錄就是一行,並且使用空白字元(包括Tab)將其劃分成多個欄位。各欄位的定義按照從左至右順序為:應用名、模組型別、控制標記、模組路徑和模組引數。但是由於pam.d目錄的形式會給每個應用提供一個單獨配置檔案,這就使得“應用名”這個欄位沒有存在的必要了,所以你會發現這些配置檔案的內容會較pam.conf少一列。此外,如果覺得一條記錄佔一行會影響可讀性,也可以使用斜槓“\”續行(我反倒認為這樣更不易讀)。程式碼2是一個典型
的pam.conf檔案的內容片段,程式碼3是pam.d目錄下針對sshd的配置檔案內容。通過這兩個例子,你可以比較一下它們的差別。
程式碼2:

#%PAM-1 . 0
login auth [user_unknown=ignore success=ok ignore deault=bad]\
pam_securetty . so
login auth include system-auth
login account required pam_nologin . so  /lib64/security 或/lib/security
login account include system-auth
login password include system-auth
login session required pam_loginuid . so 
login session optional pam_console . so
login session required pam_namespace . so
login session optational pam_keyinit . so force revoke
login session include sys tem-auth
login -session optaional pam_ck_connector . so
應用    模組型別   控制標記      模組路徑                       模組引數
...... 
other auth required pam_deny . so
other account required pam_deny . so
other password required pam_deny . so
other session required pam_deny . so
......

 


程式碼3:

#%PAM-1 . 0
auth required pam_sepermit . so
auth include password-auth
account required pam_nologin . so
account include password-auth
password include password-auth
# pam_selinux . os close should be the first session rule
session required pam_selinux . so close
session required pam_loginuid . so
# pam_selinux.os open should only be followed by
# sessions to be executed in the user context
session required pam_selinux. so open evn_params 
Session optional pam_keyinit . so force revoke
Session include password-auth

從這兩個例子中你能容易辨別的就是模組型別欄位了,具體內容與我之前介紹的模組型別是一致的。你會發現,每個應用都會配置多個相同型別的模組,這就是模組的堆疊使用。具體到執行階段,同等型別的模組會被按照其出現的順序來執行。
程式碼2的應用名欄位中有一個other應用。實際上並沒有這個名稱的應用,也不要把你的應用這樣命名。因為other代表了所有未明確指定配置項的應用,在這個例子中要求對這些應用都要拒絕認證。即便使用pam.d目錄的形式也支援這樣的設定,只要檔名是other就行
那麼“控制標記”欄位該如何解釋呢?你們會發現,我所提供的這兩個例子,有的複雜、有的簡單,但是不管怎樣,都是不知所云。從其內容上看,有些是在描述具體模組的重要程度的,有些則不是。事實上也差不多是這樣。
對於規定模組重要程度的控制標記一共有四個,他們是:
● required
表示該模組的認證成功是用屍通過認證的必要條件。也就是說,只要有一個被
標明為required的模組認證失敗,使用者就一定不會通過認證。但是,即便這類模組
認證失敗,PAM也不會立即將錯誤訊息返回給應用,而是繼續將其他模組都呼叫
完畢之後才返回。這樣做的目的就是為了麻痺“敵人”,讓他們搞不清楚到底是哪
裡認證失敗的。所以,required是最常用的控制標記。
● requisite
與required差不多,但是隻要用它標記的模組認證失敗,就會立即返回給應用。
具體的返回值與第一個認證失敗的模組有關。一般被requisite標記的模組多數用來
判定當前使用者所處的環境,如果環境不夠安全,即便是合法的使用者也不會通過驗證。
這是最嚴苛的要求,一般很少使用。但是requisite能夠降低黑客利用不安全媒介獲
得輸入密碼的機會。
● sufficient
表示該模組驗證成功是使用者通過認證的充分條件。只要這個模組驗證成功了,
就代表沒有必要繼續去認證這個使用者了。那麼相應的行為就是隻要被sufficient標
記的模組一旦認證成功,就會立即返回給應用,報告成功;但是需要注意的是
sufficient的優先順序低於required,那麼如果有required失敗,剛最終的結果也是失
敗的;當sufficient認證失敗時,相當於optional。
● optional
這表示即便該模組認證失敗,使用者也可能通過認證,除非別無它選。換句話說,
它僅供參考。而實際應用中,optional所標記的模組只是顯示些資訊,根本不去做
什麼認證工作。
為了更進一步地說明這四個“控制標記”,可以參考一下圖14.2所示的PAM使用者認證的基本流程。

 

這四個是說明模組重要性的,那麼其他的呢?include是包含的意思嗎?猜對了,就是包含的意思。include要求當前配置檔案包含另外一個配置檔案,所以它所描述的模組並非真正的模組,而是與當前配置檔案具有相同路徑的另外一個配置檔案。在我們的例子中是system-auth和password-auth這兩個配置檔案。它們的內容與pam.d目錄下的配置檔案很像,也是沒有“應用名”這個欄位,即便實際上使用的是pam.conf。因為在被具體應用的記錄所引用時,它們就是要修飾這個應用,顯然“應用名”欄位不但多餘還礙事兒。需要注意的是,include屬於控制標記,只會對“模組型別”欄位所標記的一類模組起作用。即便system-auth可能包含了全部四種型別的模組,也只有與當前記錄所對應的那一類模組的那些記錄被包含了進來。我們舉了例子,就是程式碼3的第三行,內容是:
auth include password-auth
假設password-path醌置檔案的內容是:

#%PAM-1 .0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env . so
auth sufficient pam_Unix. so nullok try_first_pass
auth requisite pam_succeed_if .so uid >= 500 quiet
auth required pam_deny . so
account required pam_Unix . so
account suf ficient pam_localuser . So
......
Password requisite pam_cracklib . so try_first_pass retry=3 type=
password sufficient pam_Unix.so sha512 shadow nullok try_first_pass
use_authok
......

那麼它實際包含的內容就是:

auth required pam_env . so
auth sufficient pam_Unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny . s o

目前所介紹的這些“控制標記”都還是很簡單的。但是還有一個非常複雜的,那就是在程式碼2中所見到的:

[user_unknown=ignore success=ok ignore=ignore default=bad]

這是什麼含義呢?好像是對一些返回狀態的處理方式!沒錯,這次你又猜對了,就是描述了對模組具體的返狀態的處理方式。類似user unknown、success、ignored等這些內容就是某個模組的返回狀態。那麼相應的ignore、ok、bad等就是對這些返回狀態的處理方式標記,或者說是“動作”。PAM所支援的所有動作見表14-1所示:

 

14-1 PAM所支援的動作列表

標  記

說  明

ignore

忽略這個返回狀態,不會對驗證結果產生影響

bad

表示這個返回狀態代表驗證失敗,餘下的模組呼叫也永遠失敗

die

bad相同,但是應該立即返回到應用,不再繼續呼叫餘下模組

ok

表示這個返回狀態應該被看作是驗證成功,繼續呼叫餘下模組

done

ok相同,但是應該立即返回到應用,不再繼續呼叫餘下模組

N(一個整數)

ok相同,但是要跳過N個模組才能繼續

reset

清除所有的返回狀態,繼續呼叫餘下模組

 

顯而易見的是,利用這些標記可以更加細膩地控制各模組驗證結果所佔有的權重。而且完全可以用它們描述出之前所介紹的那四種有關模組重要性的“控制標記”。重新描述一下它們就是:

required [ success =ok new_authtok_reqd=ok ignore=ignore def ault=bad ]
requisite [success=ok new_authtok_reqd=ok ignore=ignore defalut=die]
sufficient [success=done new_authtok_reqd=done default=ignore]
optional [ success=ok new_authtok_reqd=ok def ault=ignore ]

對於模組能有哪些返回狀態是沒有嚴格要求的。比較常見的可參考表14-2中所列的那些。當然,這些也只是最常見的。如果你想了解更多的返回狀態,或者需要開發PAM模組,可以參考/usr/include/security/_pam_types.h檔案中所定義的內容。這個檔案中定義了很多巨集,幾乎每一個都代表返回狀態。只是它們的命名會與應用在配置檔案中的有一些差別,但還是
有規律推斷出來的。不相信的話你就試試嘛!

 14-2帶見的模組返回狀態

狀  態

說  明

success

認證成功

auth_ err

認證失敗

new_authtok_reqd

需要新的認證令牌。有三種情況會返回這個狀態:一是根據安全策略需要更改密碼;二是密碼為空:三是密碼已經過期

ignore

忽略底層account型別模組的返回狀態,而且無論是否被requiredsufficientoptional修飾。出於安全性考慮,這個返回狀態通常要被忽略掉,這樣其他模組的返回狀態就會被參考

user unknown

未知使用者

try_again

已經通過了密碼服務的初步檢測

default

所有沒有註明的返回狀態

 

 可能到這裡你會猛然發現,控制標記的“語法”構成需要很多空白字元來進行分割,難

道不會被當作“欄位”給弄錯嗎?答案自然是不會,因為使用了中括號“[]”。要是你忘記
了中括號,肯定就會出錯!
哦,還有一個控制標記需要說明一下,那就是substack。這個標記跟include有些類似,但是done和die這兩個動作不會立即返回,而是要繼續執行完substack中的所有剩餘模組。相應地,reset動作也只能影響到substack中模組的返回狀態。你完全可以將substack看作是使用者以證階段的“子過程”。
配置檔案中餘下的兩個欄位是:“模組路徑”和“模組引數”,應該不難理解的。對於模
塊路徑,可以有兩種方式表達:一是使用絕對路徑,也就是從根“/”開始的路徑;二是隻給出一個檔名,PAM會在模組的預設路徑中去搜尋。PAM模組的預設搜尋路徑是/lib/security,如果是64位系統就是/lib64/security。需要注意的是,本書所給出的這個預設搜尋路徑只是最常見的,因為PAM在編譯的時候可以特別指定。這就意味著如果你遇到了一個比較蛋疼的Linux發行版,可能就不是這樣了。至於模組引數就與具體模組有關了。而且模組引數欄位處於最後面,這使得它不會因為擁有空白字元而被劃分成若干獨立的欄位。換句話說就是隨意使用空白,用不用中括號“[]”就看您心情了。
某些特殊的模組和其引數配合起來可以獲得非常神奇的效果。比如pam_succeed_ if.so
這個模組。它可以是滿足全部四種型別模組的功能需求。它的引數很多時候就是一個條件表示式,比如:user!=root、user_ id >=500等。如果“表示式”的計算結果為真,那麼pam_succeed_ ifso模組會返回success狀態,表示通過認證。如果有效利用,完全可以使得PAM的配置檔案像一個擁有選擇分支結構的微型語言。實際上也正是如此。
至於PAM所提供的那些模組,本書就不做詳細的介紹了。芻需要其配置PAM的時候,可以檢視Linux的聯機幫助文件。每一個PAM模組,都有詳細的文件資料。
似乎到了這裡,如何配置PAM算是說明白了,PAM的整個工作機制也開始清晰起來。但是你可能還會有一些疑問,那就是為什麼一個簡單的確認使用者名稱和密碼是否正確的過程,要搞得這麼複雜這裡面的箇中原因顯然是幾句話說不清楚的。其中最主要的原因恐怕就是PAM並不是提供一種方法,而是提供了一種機制。它允許使用者利用這種機制,實現多種多樣的使用者認證方法。
就比如:我有一臺在網際網路上提供HTTP服務的Linux主機,HTTP服務是提供給所有人的,但是管理這臺主機的就只能是我本人。雖然利用ssh可以很有效地保證我與主機之間的通訊安全,但是我所使用的使用者名稱和密碼可能早就被買賣了好多次。那麼一個比較安全的
做法是,只允許通過我自己的電腦才能使用ssh連線這臺主機。這時只需要改變一下PAM的配置,只允許我這臺電腦才能通過使用者認證就可以了。當我的HTTP服務開始盈利了,並且足夠養活一個團隊了,我也忙得不可開交,急需要僱傭一個專門的人來幫我維護。那麼也只需要改變一下PAM的配置,就可以讓另外一臺電腦通過認證了。當然,我是不希望粑root賬號給我的僱員的,那麼就限制他的電腦不能用root賬號吧。
顯然在實際的應用過程中,會有更為複雜的需求。如果PAM只是提供了一種方法,可能所有Linux管理員會很高興,但是更高興的恐怕是黑客們了。只要在自己的環境中找到那麼一個漏洞,可就變成了放之四海而皆準的“真理”了。顯然這不是任何人期望看到的結果。
最後,你可能還是存在著那麼一點點疑慮,或在暗自抱怨著我或出版社的不細心,總是
有一些筆誤或印刷錯誤。為什麼呢?因為在程式碼2有這樣的一行內容:

login -session optaional pam_ck_connector.so

哈哈,歐耶!看到這一點說明你非常細心,但是我也真沒有粗心。因為模組型別欄位裡的那個橫線“一”既不是筆誤也不是印刷錯誤,它是真實存在的。它要求這個模組的認證過程不記入日誌。與此相對應地那些不帶有橫線“一”的模組,就都要記入日誌。不過具體有沒有日誌可記就要取決於具體的模組是否有日誌要寫。事實情況是大多數模組預設情況下不寫日誌,但是使用debug選項基本都能強迫它們去寫。PAM的使用者認證日誌記錄在/var/log/secure(許可權600)檔案中。只有root使用者能夠檢視。也可以通過監控這個檔案的變化,來研究PAM的工作過程

 

14.3.4密碼對映
既然PAM提供了一套機制,允許使用多種方法的組合進行使用者身份確認,那麼就不可避免地遇到一個問題——不同的方法有不同的密碼體系。為這個問題開脫的最好理由就是“安全”。因為黑客要攻破這樣的系統,就必須攻破所有的密碼體系。但是一旦採用了多種密碼體系,就避免不了在進行一次認證的過程中要求使用者多次輸入不同的密碼。顯然如果記性不太好將會遇到很大的麻煩。但是統一密碼體系統一密碼,就會削弱系統來之不易的安全性。PAM的密碼對映機制提供了兩全其美的解決方案。
密碼對映機制引入了“主密碼”和“副密碼”這兩個概念。主密碼加密其他密碼體系的密碼而形成副密碼,並且將這些經過加密的副密碼存放在一個使用者能訪問的地方。主密碼一旦通過認證,就可以利用它來解密副密碼而獲得相應的密碼繼續認證。這個過程就是“密碼對映”。如果密碼對映出現錯誤,或者對映不存在,那麼需要密碼的相關模組就會提示使用者輸入密碼。主密碼一般被儲存在PAM API層(P508密碼的儲存),並在需要時提供給堆疊的各個認證模組。出於安全考慮,密碼要在pam_authenticate函式返回之前清除,而且主密碼必須足夠強壯。一般地,主密碼應該比較長,使用大小寫字母、數字和特殊符號混合在一起構成。密碼如何加密和如何儲存則完全取決於PAM的具體實現。
為了實現密碼對映,所有型別的認證模組應該支援以下四個對映選項:
● user_first_pass
該選項表示模組執行時不提示使用者輸入密碼,而將該模組之前提示使用者輸入的
主密碼作為它們的公共密碼進行驗證。如果使用者沒能通過主密碼的認證,則該模組
不提示使用者輸入密碼。這個選項一般用於希望強制用同一個密碼通過多個模組認證
的時候。
● try_first_pass
該選項與user_first_pass類似,但是當使用者的主密碼沒能通過認證時,則會提
示使用者輸入密碼。
● use_mapped_pass
該選項要求使用密碼對映機制得到模組的密碼。也就是說,該模組被定叉後不
會提示使用者輸入密碼,而是用對映密碼,也就是使用經過主密碼解密副密碼得到的
密碼來進行認證。即便在此之前使用者沒能通過主密碼的認證,該模組也不會提示使用者輸
入密碼。
● try_mapped_pass
該選項與use_mapped_pass的差別就如同try_first_pass與user_frst_pass的差別
一樣。如果主密碼不正確,模組就會提示使用者輸入密碼。
需要說明的是,當某個模組的密碼被改變之後,PAM會儲存所有新舊密碼,並且能夠讓某些相關模組訪問到它們。這使得其他模組能夠利用此資訊更新加密的口令而不必強制使用者再次輸入密碼。
密碼對映機制是PAM比較複雜的機制,可以顯著提高系統的安全性同時又保障了系統的易用性。當前大多數企業級的Linux發行版並不預設提供利用這種機制(PAM)的使用者認證方案,唯一的應用可能就是我在前面給出的那個password-auth檔案的例子了。主要是因為“安全”這個問題對於不同的企業是有不同的要求和實施策略,伺服器供應商很難做到面面俱到。為了能夠讓一個Linux系統達到足夠強健的安全性而又不失去易用性,作為系統的管理員,可能需要付出更多的汗水和經驗值去保障。

 

14.4 應用PAM API
通過前面的介紹,你應該對PAM的整套運作機制有所瞭解了。如果你是一名程式設計師,或許會很急切地想知道如何在自己的程式中整合PAM未進行必要的使用者認證,抑或是想了解一下諸如login、sshd這樣的程式是如何利用PAM來完成使用者認證的,乃至如何向PAM中新增自己的模組。這一節將為您揭曉答案。


14.4.1 開發概述
PAM所能提供的功能在前面已經說過了,就是四類模組的功能。與Linux系統其他的API 一樣,PAM API也使用一個共享庫(libpam.so檔案)來提供所有介面,同時也會提供幾個與之相對應的C標頭檔案
當你要開發一個使用PAM的應用時,應該包含這樣的程式碼:
#include<security/pam_appl.h>
在編譯你的程式時,還應該使用類似這樣的命令:

gcc—o application… -lpam

當你要給PAM開發一個模組時,應該包含這樣的程式碼:

#include<S ecurity/pam_modules. h>

在編譯你的模組時,應該使用類似這樣的命令:

gcc - fPIC—C pam_module.c
gcc - shared—o pam_module. so pam_module.o- lpam

雖然開發應用和開發模組的時候所使用的標頭檔案不同,但是都要求連線同一個庫:
libpam.so。就是它提供了PAM API的全部介面,也是整個PAM系統的核心。


14.4.2 PAM事務 
應用程式認證使用者身份的過程和使用者認證通過之後對其行為進行跟蹤的一整套流程被
稱之為PAM事務,以下我會簡稱為“事務”。
事務擁有一個名稱,一般與應用程式名稱相同。當然,如果你想別出心裁的話,可以起個花哨的名字,但是你得想辦法告訴系統管理員你起的是什麼名字。因為在配置檔案中“應用名”欄位實際上標明的是事務名。
一個事務只能認證一個使用者。也就是說,如果你的程式需要認證多個使用者,就必須建立多個事務。多個事務可以並行執行,只要你的程式是那樣設計的就行。就比如類似sshd、ftpd這樣的服務程式,很多時候要處理多個使用者的同時登入,那麼它們就一定要開啟多個事務。具體為什麼是這樣,慢慢地就能理解。
建立事務的介面是pam_start(),它的完整定義是:

int pam_start(const char*servlce_name.  事務名/應用名
const char *user.
const struct pam—conv *pam_conversation,
pam_handle_t **pamh)

第一個引數service_ name就是事務名,第二個引數user是要認證的使用者名稱。第一個引數必須給定,而第二個引數則是可選的(可以為NULL)。應用程式只要呼叫了pam_start()介面,PAM一API就會讀取配置檔案,如果事務名沒有給定,這個時候就無法找到合適的配置項了。而第二個引數使用者名稱為什麼不需要給定,後面再說。
第三個引數則很重要,這是一個結構體,有如下定義:

struct pam_message {
int msg_style ;
char *msg;
};
struct pam_response t
char *resp;
int resp_retcode;
};
struct pam_conv {
int(*conv) (int, const struct pam_message**, struct pam_response**,
void*)
void *appdata_ptr;
};

結構體pam_conv的conv欄位是一個函式指標,一般在C中就是用來進行回撥的,在這裡也不例外。按照PAM的術語,conv指向的函式叫conversation function,翻譯過來就是“對話函式”。
既然叫對話函式,那麼是誰跟誰對話呢?模組與應用對話。模組和應用對什麼話呢?最簡單的就是問使用者要密碼。因為密碼這東西是肯定不能明文儲存的,只能等到用的時候叫使用者自己去輸入。當然,模組和應用還有一些別的對話內容,比如模組的一些警告資訊、提示訊息等。所以,對話函式由誰提供,由誰來呼叫,應該不用我明示了吧?
對話函式的前三個引數很重要。第一個是訊息的數量,第二個是具體的訊息陣列(也可以叫“問題陣列”),第三個是對具體訊息的響應(也可以叫“答案陣列”)。有多少個“問題”,就得有多少個“答案”,否則就會發生錯誤。需要注意的是,“問題陣列”的記憶體由PAMAPI來分配,而“答案陣列”的記憶體則需要應用程式來分配,但是PAM API會負責回收這些記憶體。更需要注意的是,“問題陣列”是值傳遞,而“答案陣列”是引用傳遞。換句話說,“問題陣列”是pam_message揩針的陣列,而“答案陣列”則是pam_response的陣列。如果還是不理解,就看我在後面給出的程式碼吧。
對話函式的第四個引數屬於輔助資料,它實際上就是pam_conv結構體的appdata_ptr欄位的內容。在使用C++程式設計的時候,可以將this指標傳遞給它。根據實際需要,傳遞什麼都可以,只要你認為是合理的,能用得著的就行。
對於代表“問題”的pam_message結構體,第一個欄位msg_style用來標明這是一個什麼型別的“問題”,而msg欄位則包含具體的問題。一般有四種問題,分別用PAM.PROMPT_ECHO_OFF、PAM_PROMPT_ECHO_ ON、PAM_ERROR_ MSG和PAM TEXT_ INFO這四個巨集來代表。PAM PROMPT_ ECHO OFF型別的“問題”要求使用者回答問題的時候不能有回顯,最典型的就是要求使用者輸入密碼,那麼這類問題的msg欄位的內容可能就是“Password:”;PAM—PROMPT_ ECHO_ ON型別的“問題”要求使用者回答問題的時候可以回顯,比如要求輸入使用者名稱的時候:PAM ERROR MSG和PAM TEXT- INFO型別的“問題”可以不用回笞,只需要簡單地顯示msg欄位的內容即可,但是“答案陣列”也得給這類問題留出位置。作為應用程式,很多時候可以將msg的內容掉包,也就是不直接顯示給使用者,而是採用另外一些更明確的訊息來提示使用者。具體怎麼用,應用程式的作者說了算。
對於代表“答案”的pam_response結構體只有resp欄位有用,resp_retcode欄位給個0就行了。resp欄位的內容就是具體的答案了,比如需要輸入密碼,那麼這個欄位的內容就得是密碼。而且一定不能給它賦值一個常量,因為PAM API會試圖去回收它的記憶體。如果你賦值了常量,就會報錯。
pam_start()介面的前三個引數就介紹完了。第三個引數一定不能是NULL,而且必須提供“對話函式”,因為PAM API並沒有預設提供。至於第四個引數,實際上是一個返回引數,會返回事務控制程式碼。事務控制程式碼能夠唯一標識一個事務,不要直接打它的注意,否則PAM會很生氣,後果很嚴重。
當pam_start()介面返回了PAM SUCCESS,就代表成功地建立了一個PAM事務。如果是別的值,那就檢查一下是否有引數傳遞錯了,如果不是,那就悲哀吧,您沒有權利執行使用者認證操作。
使用者退出系統之後,應該關閉事務。具體的是執行pam_end()介面。完整定義如下:

int pam_end (pam_handle_t *pamh, int pam_status);

第一個引數不用多說,就是事務控制程式碼。第二個引數則是呼叫pam_end()之前的那個PAM API介面的返回值。這個引數很重要,它要根據這個具體的返回值做一些清理操作,所以給定不正確的結果就不好說了,很有可能是災難性的。
注意,pam_end()最好是在使用者退出後呼叫。如果在這之前呼叫,會導致使用者喪失所有已經獲得的許可權,並被系統拒之門外當然,對於涉及系統安全的應用程式,一旦發現使用者有不軌行為,那也就不用客氣了。這也是一種十分必要的安全策略,


14.4.3事務屬性
前面說了,pam_start()介面的第二個引數,也就是使用者名稱不需要給定。而且十分重要的第三個引數也不需要給定。這是為什麼呢?這就要引出事務屬性的概念了。
一個事務擁很多屬性。事務名稱、使用者名稱、對話函式等這些都是事務的屬性。除此之外,還有遠端使用者名稱、遠端主機名等。設定和獲取事務屬性值,可以通過pam_set_item()和pam_get_item()這兩個介面完成。它們的完整定義是:
intpam_se t_item(pam_handle_t *pamh, int item_type, const void *item);
intpam_get_item(const pam_handle_t *pamh,int item_type,const void **item);
一共三個引數。第一個引數是事務控制程式碼,如果不給定就不知道該處理誰了;第二個引數是具體要設定或獲取的屬性標誌,第三個引數就是具體的屬性值了。屬性值的型別會根據屬性的不同而不同,表14-3列出了一些常用的屬性,沒有列出的,可以通過聯機幫助man來檢視。

 

14-3常用的事務屬性表

屬性標識

說  明

PAM_SERVICE

事務名稱

PAM_USER

要認誑的使用者名稱

PAM_TTY

當前使用者所使用的終端裝置名,字首肯定是/dev。如果是圖形使用者介面,那麼這個值應該是環境變數DISPLAY的值

 

 

PAM_RHOST

如果是遠端登入認證,那麼這個屬性就是遠端主機名

PAM_RUSER

如果是遠端登入認證,那麼這個屬性一般是登入客戶端的名稱。與PAM_RHOST屬性構成PAM_RUSER@PAM_RHOST來標識認證使用者的來源。也可以根據這兩個屬性來判斷使用者是否合法

PAM_CONV

模組與應用的對話函式

PAM_AUTHTOK

這個屬性就是使用者密碼,只有在更改密碼的時候有效,被看作是新的密碼

PAM_OLDAUTHTOK

這個屬性是使用者過期的密碼,只有在更改密碼的時候有效,被看作舊密碼

 
可以這樣說,建立完事務之後,就應該設定相應的屬性,否則就有可能通不過認證。設定和獲取事務屬性的操作很少會發生錯誤,除非傳錯了引數,但是這在很多時候編譯都是通不過的。

 


14.4.4使用者認證
當必要的事務屬性都設定好之後,就開始了具體的身份認證了。這需要呼叫pam_authenticate0介面來完成。它的完整定義如下:

intpam_authenticate (pam_handle_t *pamh, int flags 位掩碼); 

這個介面相當簡單,就是事務控制程式碼和認證標誌兩個引數。而認證標誌也只有兩個,分別是PAM SILENT相PAM DISALLOW_ NULL_AUTHTOK。前者是要求“不聲不響”地認證,而後者是要求密碼不得為空。實際上這個認證標誌是個位掩碼,也就是說可以通過C語言的位或“l”操作聯合使用。如果這兩種行為都不是你期望的,那麼直接給個0就行了。
pam_authenticate()介面被呼叫後,PAM API就會按照配置檔案的規定去呼叫那些auth型別的模組來認證使用者。只有返回PAM SUCCESS才表明使用者通過了認證。認證失敗分為兩種情況:一種是因為使用者認證資訊不正確導致的失敗,這是最普遍的情況;二是由於程式本身bug導致事務有問題或認證模組由於某些原因不能工作引起。這兩種情況需要區分對待。
pam_authenticate()介面只是執行初步的認證。換句話說,即便這一步的認證通過了,也不代表使用者就有權利使用系統。
首先要明確的一個事情是,PAM對於處理遠端登入的使用者提供一種被稱之為“臨時使用者”的概念。這是一個什麼概念暱?對於一個遠端登入的使用者,當通過pam_authenticate()介面認證之後,可能會獲得一個新的使用者名稱,而這個使用者名稱會與系統中現有的某個使用者相對應。或者反過來說,遠端登入的用盧名可能系統中並不存在,但是PAM會通過某種機制將它對映到系統中的一個使用者上面。這個被對映到的系統中的真實使用者,就是臨時使用者。如果採用了這種機制,那麼pam_authenticate()介面會修改事務的PAM_ USER屬性,使它的值是當前系統中的一個真實使用者名稱。所以當發現登入使用者名稱與真實使用者名稱不相同時,就需要通過getpwnam()系統呼叫來獲得這個真實使用者的資訊,來給這個登入使用者進行授權。有關getpwam()系統呼叫的詳細資訊,可以通過聯機幫助man獲得。
另外一個事情是,使用者的密碼可能已經過期,或者是根據某些策略被禁止登入。這些事
情將由account型別的模組負責處理,具體到介面是pam_acc_mgmt()。它的完整定義如下:  getpasswordname-》/etc/passwd-》系統呼叫

int pam_acc_mgmt (pam_handle_t *pamh, int flags);

它的兩個引數與pam_authenticate()介面相同,就不再複述。需要注意的是它的返回值。如果返回了PAM_SUCCESS,則表明這個使用者沒有任何問題,可以放行了;如果返回了PAM _NEW_AUTHTOK_REQD,就標明這個使用者的密碼已經過期,需要修改該密碼:其他的返回值都表示這個使用者有問題,絕對不能放行。
當發現一個使用者的密碼已經過期,那麼可以有兩種策略:一是禁止登景;二是提示修改密碼。具體選擇何種策略,應該根據具體應用的需求決定。因為禁止登入這個策略還可以通過修改配置檔案做到,所以較為常見的是預設策略是提示修改密碼。修改密碼的操作由password型別的模組負責處理,具體到介面是pam_chauthtok(),完整定義如下:

int pam_chauthtok(pam_handle_t *pamh. int flags);

同樣地,這個介面有兩個引數,flags也有兩個標誌。PAM_SILENT標誌的行為與前面兩個介面是一致的。另外一個標誌是PAM_CHANGE_ EXPIRED_AUTHTOK,宣告只是修改一下已經過期的密碼,如果不給定這個標誌,就會要求使用者輸入舊密碼和新密碼。這兩個標誌同樣可以使用C語言的位或“l”操作聯合使用。
出於安全方面考慮,在進行使用者認證的時候,適當調整一下當前程式的優先順序,這樣可
以獲得更高的安全保證和更快的認證速度。至於如何調整當前程式的優先順序,可以檢視聯機
幫助中有關setpriority()系統呼叫的詳細介紹。


14.4.5認證憑證
當一個使用者通過了認證之後,就應該給他/她建立一個認證憑證了。所謂認證憑證,就像身份證一樣可以確認使用者的身份。當使用者在執行某些操作需要再次進行身份認證時,可以
直接拿這個認證憑證給系統,說我是合法公民,我要行使我的權利。如果這個憑證具有絕對
的說服力,那麼Linux系統肯定是會放行的。
實際上,在Linux系統中使用者的UID和GID就是一種認證憑證。而且這種憑證是要取決於執行這個程式的使用者本身所具備的認證憑證。換句話說,如果一個較低階別的使用者執行了具有使用者認證功能的程式,那麼這個程式能夠認證的使用者級別只能更低。為了能夠讓級別較高的使用者通過認證,往往這類程式需要root賬號來執行。比如login命令、su命令等,它們都擁有root許可權

/bin/su

/bin/login
建立、管理和刪除使用者憑證的PAM API介面只有一個,就是pam_setcred()。它的完整
定義如下:

int pam_setcred(pam_handle_t *pamh, int flags);

這個介面的flags引數有5個標誌。雖然可以使用位或“l”操作聯合使用,但是除了PAM_SILENT與其他四個能這樣做之外,其他的是會產生衝突的。至於為什麼,表14-4說
明瞭這個問題。

 

14-4常用的事務屬性表

標  志

說  明

PAM_ESTABLISH_CRED  credit信用

為使用者初始化認證憑證

PAM_DELETE_CRED

刪除使用者的認證憑證

PAM_REINITIALIZE_CRED

完全重新初始化使用者認證憑證

PAM_REFRESH_CRED

延長現有使用者憑證的有效期

 

 在已經給使用者建立了認證憑證之後,如果要對使用者的許可權進行修改,可是使用PAM_REINITIALIZE_CRED標誌來重新初始化使用者憑證;當需要延長使用者憑證的有效期時,可以使用PAM_REFRESH_CRED 標誌,最典型的sudo命令就是這樣做的;如果使用者退出,在關閉事務之前,應該刪除認證憑證。
認證憑證管理功能由auth型別的模組提供。執行成功會返回PAM_SUCCESS,
你可能會有一些疑問,那就是在Linux中完全可以使用UID和GID作為認證憑證來行使使用者權利,似乎單獨去建立一個新的使用者憑證有些多此一舉。這可以通過PAM的體系結構來理解。因為PAM是完全模組化的高度可配置的,而且目前所介紹的這些介面很多都與具體型別的模組相對應。也就是說,作為應用程式的開發者,根本不知道呼叫某個介面之後PAM會去做什麼,包括pam_setcred()。這樣的話,在開發程式時就不能做任何假定。那麼
最合理的解決方案就是不要去問為什麼,按照規則來就行了。雖然在Linux下使用pam_setcred()來建立一個認證憑證看起來有些多餘,但是誰能保證某些auth型別的模組不
會拒絕一些特殊的使用者呢?

 


14.4.6 PAM會話
當建立成功認證憑證之後,就要去建立一個PAM會話來初始化使用者環境和跟蹤記錄使用者之後的行為。PAM會話屬於事務的一部分,在使用者退出之後,要在刪除認證憑證和關閉事務之前關閉會話。
建立和關閉PAM會話的介面是pam_open_session()和pam_close_session(),它們的完整定義如下:

int pam_open_session (pam_handle_t *pamh, int flags);
int pam_close_session (pam_handle_t *pamh, int flags);

它們的flags引數只有一個標誌可以選擇,就是PAM_SILENT。
從表面上看,PAM會話很多時候也是沒什麼用的。按照PAM的設計,PAM會話用於初始化使用者環境和記錄使用者行為。由於建立PAM會話是在一個使用者已經通過認證之後要做的事情,即便不建立這個會話,應用程式自己也可以做到PAM所規定的那些事情。其實PAM會話與前面介紹的認證憑證是類似。作為應用程式的開發者是不能做任何假定的,那就按照標準執行好了。

 

14.4.7使用者認證流程
應用程式用來進行使用者認證的PAM API介面基本上算是介紹得差不多了,如果你想了解更為詳細內容,可以去查詢Linux的聯機幫助。
現在可以總結一下使用者認證的基本流程,大體上可以劃分為如下這幾個步驟:
1. 建立PAM事務;
2. 設定事務屬性;
3. 與使用者互動提示輸入使用者名稱和密碼;
4. 確認使用者的有效性和密碼是否過期;
5. 如果密碼過期,提示使用者修改密碼;
6. 建立認證憑證;
7. 建立PAM會話;
8. 執行認證後操作:
9. 關閉PAM會話;
10. 刪除認證憑證;
11. 關閉PAM事務。
各介面呼叫的基本流程圖見圖14.3所示:

 

根據這個流程圖,我們可以編寫一段簡單的使用PAM進行使用者認證的程式碼,見程式碼4所示。程式碼可能有點長,但還是比較容易讀懂的。
程式碼4:

#include<security/pam_appl. h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pwd.h>
#include<unistd.h>
#define MAX_UN_SIZE 256
pam_handle_t *pamh=NULL;
char tusername =NULL;
struct passwd *pwd=NULL.
static int
conv_func(int n, const struct pam_message **msg, struct pam_response
**resp, void *data)
{
/*
*這就是對話函式,與使用者進行認證互動。
*/
struct pam_response *aresp=calloc (n, sizeof (struct pam_response)};
for (int i=0; i<n; ++i) {
switch (msg [i]- >msg_style) {
case PAM_PROMPT_ECHO_OFF:
/*要求使用者輸入密碼,getpass系統呼叫可以做到。*/
aresp [i]. resp=strdup (getpass (msg [i] ->msg));
aresp [i]. resp_retcode=0;
break;
default:
/*其他情況我們忽略掉*/
break;
)
)
*resp=aresp;
return PAM_SUCCESS;
}
static char*
get_username()
{
/*
*這個函式用於獲取使用者名稱,模擬login命令。
*/
static char un [MAX_UN_SIZE];
char *p=NULL;
int ch=0;
do{
printf("Please input user name:”);
for (p=un; (ch=getchar() ) != ‘\n’; ) {
if (p<un+sizeof (un) - 1) {
*p++=Ch;
}
)
*p=’\0’;
) while (p==un);
return un;
}
static int
auth(pam_handle_t *pamh)
(
/*
*使用者的認證過程在這裡完成,直到認證通過才會退出。
*/
const char *tmpl_user=NULL;
const void *item=NULL;
int pam_err=0;
while (1){
pam_err=pam_authenticate (pamh, PAM_SILENT);
switch (pam_err) {
case PAM_SUCCESS:
/*判斷是否有臨時使用者*/
pam_err=pam_get_item (pamh. PAM_USER, &item);
if (pam_err==PAM_SUCCESS) {
tmpl_user= (const char*)item;
if (strcmp (username, tmpl_user) !=0)
pwd=getpwnam(tmpl_user);
}
pam_err=pam_acct_mgmt (pamh, PAM_SILENT);
if (pam_err==PAM_ NEW_AUTHTOK_REQD) {
/*提示使用者修改密碼*/
pam_err=pam_chauthtok (pamh,
PAM_SILENT | PAM_CHANGE_EXPIRED_AUTHTOK);
}
break;
case PAM_MAXTRIES:
/*達到認證次教的上限,宣告失敗。*/
break;
efault:
continue:
}
break;
}
return pam_err;
}
int main (int argc, char *argv[ ] )
{
struct pam_conv pamc={conv_func, NULL);
int pam_err=0;
/*建立一個事務*/
pam_err = pam_start ( "login" . NULL, NULL, &pamh ) ;
if (pam_err != PAM_SUCCESS ) {
printf ( " pam_start ( ) fail\n " ) ;
return -1;
}
/*設定事務屬性*/
username=get_username();
pam_err=pam_set item(pamh, PAM_USER, username);
pam_err=pam_set_item(pamh, PAM_CONV. &pamc );
/*獲取使用者的基本許可權資訊,用於通過認證後的授權。*pwd=getpwnam (username);
pam_err=auth (pamh);
if (pam_err!=PAM_SUCCESS){
printf( "auth() fail\n");
pam_end (parnh, pam_err);
return -1;
}
/*判斷使用者是否存在*if (NULL==pwd){
printf(*Invalid user\n");
pam_end (pamh, pam_err);
return -1;
}
/*初始化使用者組的許可權,用於建立認證憑證*/
initgroups( username, pwd- >pw_gid);
pam_err=pam_setcred (pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
if (pam_err !=PAM_SUCCESS){
printf( "pam_setcred fail\n*);
pam_end( pamh, pam_err);
return -1;
}
pam_err=pam_open_session (pamh, PAM_SILENT);
if (pam_err !=PAM—SUCCESS){
printf(*pam_open_session()fail \n*);
pam_err=pam_setcred(pamh, PAM_DELETE_CRED|PAM_SILENT);
pam_end( pamh. pam_err);
return -1;
}
setuid (pwd->pw_uid);
/*
這個地方可以新增一些通過認證的操作
*/
pam_err=pam_close_session (pamh, PAM_SILENT);
pam_err = pam_setcred(pamh, PAM_DELETE_CREDI PAM_SILENT) ;
pam_end ( pamh , pam_err ) ;
return 0;
}

 

 

14.4.8模組開發
PAM模組的開發從表面上看要比應用開發容易得多。但是那也只是表面上的。先不說PAM機制本身是否存在漏洞,作為它的模組本身必須擁有更高的安全標準。因為PAM在執行認證的過程中要呼叫很多個模組,只要其中一個有問題,其他模組的努力都等於白費。所以要把一個安全可靠的認證模組寫好,是一件非常不容易的事情。遺憾的是,本書只能教會您那些表面的東西,就是一個PAM模組是怎麼構成的。至於它內部的那些具體實現和注意事項,還需要大家去努力學習,很多是需要不斷地積累的。
那麼PAM模組是如何構成的呢?非常簡單,就是普通的共享庫只要能夠匯出固定名稱的兒個介面就行了。之前就說過,PAM API中有幾個介面是與模組相對應的,就是:pam_setcred()、pam_authenticate()、pam_acct_mgmt()、pam_chauthtok()、pam_open_session()和pam_close_session()。那麼只要有一個共享庫實現了與這些介面相對應的一些介面就能夠成為PAM的模組。注意,只要實現一些就行,不用全部,一個、兩個都可。也正是因為這樣,雖然PAM規定了模組有四種型別,但是實現一個模組的時候卻不用去嚴格區分它的型別。也就是說,你實現了屬於什麼型別的介面這個模組就“屬於”什麼型別,你實現了全部四種型別的介面,你的模組就是四種型別都支援的模組。從這點上看,說一個模組是支援什麼型別的應該比較準確。
這些介面的標準定義如下:

int pam_sm_authentica te(pam_handle_t*pamh,int flags,int argc,char **argv);
int pam_sm_setcred (pam_handle_t*pamh, int flags, int argc, char **argv);
int pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, int argc. char **argv);
int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc. char **argv);
int paIn—sm_open_session (pam_handle_t *pamh,int flags,int argc,char **argv);
int pam_sm_close一session (pam_handle_t *pamh, int flags, int argc, char **argv);

從這些標準定義中,你可能有了驚奇的發現,就是模組要實現的介面名稱與對應PAM API的介面名稱只是在中間增加了“sm”,而且前面的兩個引數也都一樣。至於後面的那兩個引數也應該比較熟悉,與C語言的main()函式引數相同,就是用來處理命令列選項的。因為模組也是支援選項的。
具體的程式碼本書就不給了。對這部分內容有興趣的大家可以去參考Linux PAM原始碼包中那些模組的實現程式碼。任何一個都非常有代表性。


14.4.9密碼的儲存
通過前面這些文字的介紹,你應該對PAM的整套機制和如何使用它有了較為深入的瞭解。但是作為一個使用者認證機制,與使用者互動最多的內容恐怕就是密碼了。但是PAM如何知道使用者輸入的密碼是有效的呢?答案是很明顯的,就是PAM事先知道使用者的正確密碼。但是密碼可是一個非常敏感的東西,誰都不希望自己的密碼被別人知道,即便是系統管理員也不行。要解決這個問題,PAM就只能加密密碼,而且這個加密過程必須是不可逆的。換句話說,PAM不能從加密的密碼獲得密碼本身的內容,而且包括其他任何程式或人。這樣的演算法顯然很多,MD5、SHAI等都能做到。那麼這個密碼是怎麼儲存的呢?
對於密碼是如何儲存的這個問題,在PAM機制中我們無法找到一個準確的答案。為什麼呢?這個取決於auth型別的模組。不同的auth型別模組對於密碼上的處理是不盡相同的。拿pam_unix.so這個模組舉例,它採用了UNIX原始的影子(/etc/shadow)檔案來儲存密碼。

Apr  4 22:17:12 localhost su: pam_unix(su-l:session): session opened for user root by steven(uid=0)
Apr  4 22:26:55 localhost su: pam_unix(su-l:session): session closed for user root
Apr  4 22:26:55 localhost sshd[15314]: pam_unix(sshd:session): session closed for user steven
Apr  6 17:17:38 localhost sshd[23765]: pam_unix(sshd:session): session opened for user steven by (uid=0)
Apr  6 20:12:10 localhost sshd[23765]: pam_unix(sshd:session): session closed for user steven
Apr  7 08:36:54 localhost sshd[31311]: pam_unix(sshd:session): session opened for user steven by (uid=0)
Apr  7 08:37:00 localhost su: pam_unix(su-l:session): session opened for user root by steven(uid=0)
[root@_centos ~]# grep -i pam_unix /var/log/secure
Apr  7 08:36:54 localhost sshd[31311]  31311是指程式ID
ps aux|grep 31311
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     31311  0.0  0.3  97904  3800 ?        Ss   08:36   0:00 sshd: steven [priv]

既然說到了影子檔案,又由於Linux的預設密碼儲存機制也是使用影子檔案,我就借這
個機會細說一下什麼是影子檔案。
話說在早期的Unix實現申(那個時候還沒有Linux啥事兒),使用者密碼儲存在/etc/passwd(看它的名字就是幹這個的)檔案中。由於多種原因,/etc/passwd必須讓所有使用者讀取。雖然密碼是採用不可逆加密演算法加密的,但是對於這種演算法只要肯花時間依然是可以破解的(MD5現在都被破解了,還是個中國人乾的)。這就給密碼的安全性帶來了威脅。為了進一步加強密碼的安全性,後來的Unix System V系統中開始使用/etc/shadow來儲存密碼,這就是影子檔案。而且這個檔案只允許root賬號讀取。
儲存在影子檔案中的密碼依然使用不可逆加密演算法加密,開始的時候使用的是DES(加密演算法標準)的各種變體。但是隨著計算機處理速度的顯著提高,暴力破解DES加密密碼帶來的威脅也與日劇增,不得不改變策略。於是又引入了一些新的演算法,比如MD5和Blowfish。
這個時候問題就來啦,到底應該使用何種加密演算法呢?如果統一升級採用一種公認的可靠演算法,那麼老的影子檔案怎麼辦?畢竟不能讓所有使用者都去重新更改密碼(UNIX系統可以連續執行很多年),這個成本很大。這個時候就要採用一種這樣的辦法,老使用者使用老的加密演算法,新使用者使用新的加密演算法。這孰需要改變影子檔案的格式。下面的內容就是我的一臺計算中影子檔案的一段內容:

jagen: $6$nd8XAZBf $azSIbwUFtvQz....:15561:0:99999:7:::

這個影子檔案使用冒號“:”將一行文字分割成多個欄位,我們只關心開頭的兩個欄位就行了。第一個欄位是使用者名稱,第二個欄位就是加密後的密碼。如果口令的第一個字元不是“$”,那麼此口令使用的是基於DES演算法加密的;如果第一個字元是“$”,那麼直到下一個“$”間的字元表明所使用的加密演算法,而處於第二和第三個“$”之間的字元為加擾( salt)字元,隨機生成的,用於改變演算法的加密方式。
可以看得出,第一個字元是“$”,表明使用的肯定不是DES加密演算法。在這個例子中,演算法識別符號是“6”,通過檢視crypt的聯機幫助得知使用的是SHA-512演算法。要檢視當前系統預設採用何種密碼加密演算法,可以檢視/etc/login.defs檔案。
使用影子檔案只是Linux系統儲存使用者密碼的一種策略,通過修改PAM的配置檔案可以改變這種策略,比如使用NIS、LDAP等將使用者密碼集中儲存起來。
最後需要注意的一個問題是,影子檔案只有root賬號才能訪問。而pam_Unix.so模組正
是使用影子檔案來讀取密碼的。那麼,如果在PAM中給某個應用配置需要使用它來進行密碼校驗,那麼這個應用程式必須以root許可權來執行,否則將永遠會認證失敗

 

14.5安全增強系統
在大多數使用者的眼裡,每次登入Linux系統都必須輸入密碼,而且除了root之外,一個普通使用者不能隨意讀寫其他使用者的檔案,也不能更改系統設定,應該足夠安全了。當了解到PAM那樣細緻入微的使用者認證機制之後,更加堅定了對Linux系統安全性的信心,似乎從此以後就可以高枕無憂了。且慢,有關安全性酌討論我們才剛剛開始,危機依舊四伏,而且問題很多時候就恰恰出現在PAM那看似細緻入微的機制中。
什麼?PAM機制不安全?並不是,只是因為PAM是使用者認證機制,而不是使用者授權機制。真正的使用者授權機制在Linux的核心,而且這套機制存在問題。


14.5.1 主動訪問控制DAC    
Linux核心的使用者授權機制也叫訪問控制機制,在第2章中我們已經討論過使用者身份問題和檔案管理方式就是受控於這種機制。Linux系統中的使用者主要分為root(系統管理員)和普通使用者兩種(root和非root),並將他/她們劃分到不同的群組中。而這兩種使用者是否能夠訪問系統中的某個檔案則與該檔案的rwx許可權屬性有關。如果某個程式想要訪問這個檔案,Linux核心會根據該程式的擁有者的UID和所屬群組的GID與該檔案UID和GID的rwx許可權屬性進行對比來決定是否允許操作。
Linux這種控制檔案訪問的機制有一個非常好聽的名字——主動訪問控制,英文的叫法是Discretionary(自由的) Access Control,簡稱DAC。名字雖然好聽,但是缺陷可一點都不少:
● root擁有特權
特權這種事兒,在任何時候都是十分可怕的。root在DAC中不受任何限制,如果
某個擁有root許可權的程式被一小撮別有用心的人所控制,那麼他就可以利用這個程式
在你的系統上千任何事情,訪問任何路徑和檔案,而且你還不知道。   chown  xx:xx  程式路徑      兩個目的 DAC和限制普通程式的資源使用 root不受ulimit約束
● SUID程式的許可權升級
Linux為了實現類似讓使用者能夠修改自己密碼的操作(影子檔案只能由root存取),
只能給某些程式開啟一個後門,這個後門就是SUID。這可是特權的直接產物,因為有
特權就會有人通過特權走捷徑。雖然設計良好的帶有SUID的程式都會整合PAM,但是
萬一在實現上有什麼閃失的話,依然會被一小撮別有用心的人所利用,並且很容易就能
將許可權提升到root,那麼他就可以利用這樣的程式在你的系統上千任何事情,而且你還
是什麼都不知道。
● 使用者可以利用程式來更改檔案的存取許可權
如果某個年輕的Linux使用者為了自己的方便,將某個目錄的許可權設定為777,那麼
該目錄就可以被所有人任意訪問。這可是一件非常危險的事情,別人想在這個目錄下幹
什麼就幹什麼,你依然是什麼都不知道。
其實缺陷還有很多,但是已經無需多舉了,這已經足夠讓所有人倒吸一口涼氣了。而且對於這些缺陷更為要命的是,防火牆、入侵監測系統等完全無能為力,是不是該擦擦背後的冷汗了呢?
那麼Linux是否在面對安全問題時就完全繳械投降了呢?沒有,因為還有MAC。千萬不要以為這是“喬幫主”的發明,也跟網路卡的實體地址無關,它是……

 


14.5.2 強制訪問控制MAC
與主動訪問控制相對應的是強制訪問控制,英文的叫法是Mandatory Access Control,簡稱MAC。
MAC可以規避DAC的所有問題。因為MAC針對特定的程式與特定的檔案進行關聯性地訪問控制。即便你是root,在使用不同的程式的時候,你所能訪問的檔案也只是那個程式所能訪問的檔案。換句話說,MAC控制的主體是程式而不是使用者,MAC規定了一個程式能夠訪問哪些檔案,而跟使用這個程式的使用者無關。
但是,一個Linux系統中的程式數量比它的使用者數量多個幾十倍是一點都不奇怪的,那麼每個程式都使用MAC來控制似乎不太現實。Linux系統採用了一個折中的方案——DAC與MAC結合使用。
對於大多數與安全無關的程式就使用DAC來控制好了。對於那些安全攸關的程式,比如passwd、httpd等則採用MAC來控制,只能讓它們讀取/etc/shadow檔案或訪問/var/www目錄,即便它們存在某些問題能夠被那些別有用心的人所利用,又能掀起多大的風浪呢?而且即便是使用MAC控制的程式,也首先要進行一次DAC的控制。這樣,整個系統的訪問控制能力不但細膩得多,安全得多,也容易使用了。
那麼Linux是怎麼實現MAC的,我們又該怎麼使用呢?不用著急,答案馬上揭曉。就像DAC那樣,MAC也是由Linux核心來實現的,比較經典的有SELinux、AppArmor和Grsecurity,LIDS,其中SELinux已經包含在CentOS/RHEL/Fedora Linux、Debian/Ubuntu、Suse、Slackware等很多發行版中,我們就以SELinux來認識一下訪問控制是如何提供強健的安全保證和防禦未知攻擊的吧。


14.5.3 SELinux-Linux的MAC實現
作為Linux新老使用者如果你說不認識它,那我就會懷疑你之前用的是Linux嗎?因為這
個東西在Linux下紮根已經有10年(到2013年)的時間了,而且所處的位置也是十分顯耀的根目錄下。但是也不管你是Linux新使用者還是老使用者,不知道SELinux是MAC也完全不奇怪,甚至老使用者們根據過往的經驗還會十分討厭SELinux。那麼從現在起,我就讓新使用者瞭解它,老使用者喜歡它。


1. SELinux到底是什麼?
首先,SELinux是一種基於策略的MAC安全系統,是以Linux核心可載入模組的形式提供的。
其次,SELinux的全稱是Security Enhanced Linux,翻譯過來就是安全強化Linux,是Linux上最傑出的新型安全子系統。
另外,SELinux的來頭也著實不小。是由芙國國家安全域性(NSA,The National Security Agency)和SCC(Secure Computing Corporation)開發,所以它的能力是不容小覷的。
最後,SELinux是在2.6版本的Linux核心中開始被引用。對於目前可用的各種Linux安全模組來說,SELinux是功能最全面,而且是測試最充分的,是在超過20年的MAC研究基礎上建立起來的。自從Linux擁有了SELinux,大型企業和政府部門才真正地開始考慮採用Linux作為其主要的作業系統。可以說SELinux是Linux的一個十分顯耀的賣點。這也說明但凡能叫得響的Linux發行版,基本上都會整合SELinux。


2. 開啟SELinux
非常遺憾的一件事情是,搜遍Google和百度,看到的都是如何關閉SELinux。或許它真的給你帶來了不小的麻煩,但是如果你是一個十分謹慎的人,那麼就從現在起,開啟SELinux吧。
但是當我準備提筆的時候,卻突然發現不知道該如何下手了。因為不同的Linux發行版開啟SELinux的方式有很大不同,而且即便是相同的發行版的不同版本也會有很大變化。這的確是一個非常難辦的事情。就衝著達一點,我認為大部分使用者都迫切地要關閉SELinux並不是盲目的行為,因為它真的很“討人厭”。但是話分兩頭說,SELinux在配置的時候雖然有些煩人,但是安全性那可是槓槓地,從日益惡化的安全域性面上看,還真的很難找出不開啟SELinux的理由。
那麼所幸我就找一個比較通用的Linux發行版做為例子吧,如果你選擇了其他的發行版,那就問問Google和百度吧,雖然都是告訴你如何關閉SELinux的,但是反向操作不就是開啟了嗎?哦,差點忘記說了,我選擇了CentOS 6.x作為參考例子。選擇它的原因是因為這玩意兒本身就是RHEL,多數企業用的都是它。而且在後面所講解的所有內容,也都是基於它的。
好了,廢話不多說了。開啟/etc/sysconfig/selinux這個檔案,你可能會看到下而的內容:

# This file controls the state of SELinux on the system.
# SEIINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these two values:
# targeted - Targeted processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE= targeted

知果不是這樣,可能你並沒有關閉SELINUX,看看是不是跟下面的差不多:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enf orcing
# SELINUXTYPE= can take one of these two values:
# targeted - Targeted processes are protected,
# mls - Multi Level Security protection.
SELINUXTYPE= targeted

發現什麼地方有變化了嗎?就是將SELINUX=disable變更成SELINUX=enforcing了。
disable很好理解,就是關閉嘛!那麼enforcing對於SELinux意味著什麼呢?它代表了一種工作模式,強制SELinux使用你所定義的所有安全策略。那麼與enforcing模式相對應的是permissive模式。如果SELinux工作在permissive模式之下,則並不代表系統已經真正受到SELinux的保護了,因為它只要求顯示一些相關的資訊狀態,餘下的就什麼都不做了。
開啟和關閉SELinux都要重新啟動系統,因為這個需要核心本身被重新載入。如果你不確定SELinux是否已經成功開啟了,可以使用setatus命令來確認,在我的系統下,會得到這樣的結果:

setatus
SELinux status: enabled
SELinuxfs mount /selinux
Current mode : enforcing
Mode from config file: enforcing
Policy version: 24
Policy from config file: targeted

雖然開啟或關閉SELinux要重啟系統,但是在enforcing和permlsslve模式之間進行切換就很方便了。只需要使用setenforce命令就可以,給它一個引數0或1。0就代表permlsslve模式,那麼1就代表enforcing模式啦。如果要檢視當前SELinux工作在什麼模式下,可以使用getenforce命令來獲得。
需要注意的是,一旦你開啟了SELinux,而且設定它工作在enforcing模式之下,很可能會有悲劇發生,你有一堆服務無法順利啟動。最主要的提示資訊是/lib目錄下的某些檔案沒有讀取許可權而導致啟動失敗。一旦發生這種悲劇,可以將SELinux設定為permissive模式,然後使用“restorecon -Rv/”命令。如果有人很倒黴或者出於好奇將SELINUXTYPE=targeted設定成了SELINUXTPE=mls,那可能會是更大的悲劇,因為你都無法登泵系統也無法關機。如果嘗試斷電的話,就更加悲劇了,單使用者模式你都進不去了。這個時候怎麼辦呢?動用核心啟動引數吧,我們之前介紹過了。在GRUB的互動介面中指定核心引數enforcing=0就行。啟動之後就是喜劇了:)
如果你在讀完本節的內容之後,還是覺得SELinux有些累贅,不妨將它設定為permissive模式,這樣當你的系統受到安全威脅的時候可以使用dmesg命令來查有關資訊。當然,這個模式並不是真的讓你這麼幹的,它存在的目的是讓你能夠除錯一下你所設定的策略是否合理。
至於後面那個SELINUXTYPE=targeted或者SELINUXTYPE=mls是怎麼回事,我們後面再做詳細探討。


3. 安全上下文
在進一步講述SELinux之前,我們先來了解一下它的最基本的概念——安全環境,或者說是安全上下文( context)。
安全上下文實際上是一個標籤,如果系統中開啟了SELinux,那麼系統中所有的檔案、目錄、程式、套接字、乃至使用者本身就會帶有這個標籤。這個標籤由:使用者、角色、域,型別、敏感度和類別這五個部分構成,並使用冒號“:”分割。這五個部分並不總是全部出現,具體的要根據你選擇了何種策略。使周“id—Z”命令可以檢視當前使用者的安全上下文,在我的系統中是這個樣子的:

unconf ined_u: unconfined_r: uncon fined_t:s0 -S0: c0. c1023

id:--context(-Z)works only on an selinux-enabled kernel


要檢視程式的安全上下文,可以使用“ps—Z”命令,至於檔案或目錄的安全上下文,可以使用“Is -Z”命令。
安全上下文中的第一個部分是使用者。它與Linux本身的使用者是完全不同的概念。它們可以共存,而且很多時候還會被人們弄混。因為一個SELinux的使用者會與Linux本身的使用者使用相同的文字表達(登入名)。但是不管怎樣,都要記住它們兩個完全不是一回事兒。使用su命令並不能改變SELinux的使用者。
每一個Linux本身的使用者可以和一個SELinux的使用者相對應,也就是說當他/她通過合途徑登入了一個Linux系統,那麼同時就會分配給他/她一個SELinux使用者。可以通過“semanage login一1”命令來檢視這種對應關係,在我的系統中是這個樣子:

Login Name SELinux User MLS/MCS Range
_default_ unconf ined_u s0-s0 : c0 . c1023
Root unconf ined_u s0-s0 : c0 . c1023
sys tem_u system_u s0s0 : c0 . c1023

安全上下文中的第二個部分是角色。角色規定了使用者能幹什麼,這跟我們現實社會中的
角色很像,比如:有些人是醫生、有些人是教師、有些人是官員……一個人如果有了社會角色,那麼他/她就會得到某些別的角色無法獲得的資源,比如:醫生可以給病人下處方、教師可以授予學生學位,官員可以決定你是不是醫生或是教師……
每一個SELinux使用者都會有它對應的角色,而且還可以對應多個角色,現實社會是這樣的,比如:一個醫生也可以是老師,去培養新一代的醫生。


4. 身份
在SELinux中,身份的概念與傳統的Linux UID是完全不同的,它們可以共存。但是在很多時候會被人們弄混,因為一個SELinux的身份會跟標準的Linux登入名使用相同的文字表達(大多數情況都是這樣),但是不管怎樣都要記住,它們兩個完全不是一回事兒。在SELinux中身份是安全上下文的一部分,它會決定什麼可以被執行。執行su命令是不會改變SELinux的身份的。


5. SELinux的策略
一開始我們就說SELinux是一種基於策略的MAC安全系統,那麼它都提供了什麼策略呢?回答這個問題是很難的,因為SELinux的策略可以有千百種,我卻完全不知道你到底喜歡哪一種。
但是對於SELINUXTYPE這個配置項留給我們的選擇並不多,因為Linux發行商們已經為我們編制好了一些策略套件,拿來直接使用或做一些小幅度的改動就可以放心地使用了。而這個SELINUXTYPE配置項就是用於選擇策略套件的。可是話又說回來,既然已經編制好了,幹嘛還要給我們選擇呢?只能說安全這玩意兒,真是眾口難調啊!
對於CentOS 6.x,它提供了三個策略套件,分別是targeted、mls和mmlmum,也分別代表了不同的側重點。targeted是最常用的策略套件,也是CentOS 6.x預設的它側重的是對網路服務的嚴格限制,用來在沒有嚴重影響使用者體驗的情況下儘可能多的保護關鍵程式,在CentOS 4只有15個已經定義好的targets存在,包括httpd、named、dhcpd、和mysqld,而後來的CentOS版本受保護的物件越來越多,targets數量猛增,現已超過了200個。在保護安全的同時targeted策略也期望能達到這麼一種境界,大多數使用者沒有感覺到SELinux在執行而它卻在默默起著保護作用。至於其他沒有被SELinux保護的行為仍舊只能要靠古老穩定的UNIX安全系統來保障了SELinux還允許使用者使用多策略,我們可以使用mls套件來實現。它能做到不需要對安全策略進行破壞性地改變就能使MLS生效,我們需要在檔案/etc/sysconfig/selinux中把SELINUXTYPE=targeted這行改成SELINUXTYPE=mls,使用chcon或semanage命令重新標籤上下文,然後重啟系統。如果有的使用者只想使用一個“小”的SELinux在記憶體不多的虛擬機器上執行,他可以採用targeted策略慢慢刪除不需要的軟體包,但是這樣白手起家地開始工作遲早有一天會讓他洩氣的。所以針對這種需求,SELinux推出了和targeted策略一樣的mlnlmum軟體包,先只要求安裝base policy軟體包,然後包含所有targeted策略的selinux-policy-minimum rpm包進行後安裝再載入到核心裡。


14.6結束語
除了通過使用者認證機制和訪問控制機制提高Linux系統的安全性外,把入侵檢測系統(LIDS)整合到Linux核心中,可以進一步加強Linux核心的安全性。
另外,“亡羊補牢,為時不晚”,syslogd被用來處理系統日誌。雖然這已經是事後諸葛亮,但是它是系統安全與管理的一個重要方面,幫助我們排查錯誤原因防止類似的問題再次發生。syslogd記錄的日誌一般在/var/log/下,當然也有儲存在另外的伺服器上的。它可以記錄who、when、where和what等要素,這樣你就可以知道系統什麼時候重新引導過、軟硬體錯誤、系統執行的服務資訊等。
有時日誌還會包含一些敏感資料,例如本地啟用了哪些服務,使用者賬號和配置資料,在這種情況下需要考慮加密儲存與傳輸資料。

 

 

 3A 授權(核心實現,可載入核心模組,DAC,MAC)->認證(PAM程式 使用者態實現  共享庫檔案 libpam.so)-》審計(syslogd服務 使用者態實現)

 

 

 

 

f

相關文章