国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Linux Socket學(xué)習(xí)(十六)
這本書直到這個地方,我們一直在注意如何編寫使用套接口的程序,而不論其是客戶端還是服務(wù)器程序。但是我們卻并沒有級出安全編程的考慮來對抗外在的威脅,這些威脅可以是來自Internet或是我們局域網(wǎng)內(nèi)部的一些別有用心的人。在這一章,我們將會介紹以下內(nèi)容:
inetd守護(hù)進(jìn)程如何與TCP包裝器概念配合來提供客戶的檢查
TCP包裝器概念是如何工作的
當(dāng)我們結(jié)束這一章,我們將會理解TCP包裝器概念是如何工作的,并且會了解到如何將其應(yīng)用到我們管理或是自已編寫的服務(wù)器上。
定義安全
Merriam Webster的學(xué)院字典用各各方式定義了安全。而我們感興趣的是下面的兩種定義方式:
當(dāng)受危險控制的質(zhì)量或狀態(tài)
對抗危險的采取的方法
當(dāng)討論網(wǎng)絡(luò)安全時,第一點(diǎn)是指:我們希望遠(yuǎn)離各種潛在的威脅。第二點(diǎn)是,我們必須保證我們的系統(tǒng)資源不被非法檢查,以某種方式的破壞,以及其他的各種攻擊。我們編寫的文章在沒有公開的情況下不能從我們的系統(tǒng)未經(jīng)授權(quán)的丟失。
這個任務(wù)的復(fù)雜性以及其程序的邊界使得其不可能在一章中解決全部的問題。然而,網(wǎng)絡(luò)安全的某些方面直接適用于套接口編程,而我們不應(yīng)忽略所存在的危險。在這一章我們所提到的一些簡單的方法可以幫助我們防止攻擊。我們將會了解弱點(diǎn)存在的地方,而我們應(yīng)怎么樣來處理。
標(biāo)識朋友或敵人
第十七章,"傳遞證書與文件描述符",將會向我們展示我們可以通過使用證書來我們服務(wù)器的一個本地用戶標(biāo)識一個高可信度。然而,當(dāng)我們服務(wù)器的這個用戶是一個遠(yuǎn)程用戶時,這樣的可信度并不容易達(dá)到。
在這一章,我們將會依據(jù)對等網(wǎng)絡(luò)地址來標(biāo)識一個資源的用戶。然而,這并不是一個防護(hù)技術(shù),因?yàn)閷Φ鹊牡刂吩谝欢ǖ臈l件可以被欺騙。但是,他確實(shí)提供了一個簡單的預(yù)防攻擊的初級防護(hù)。
在我們擁了IP地址以后,我們可以查看客戶的注冊主機(jī)與域名。這提供了一個篩選的額外級別,而這在攻擊者部分需要更多的描述。
解析主機(jī)與域名允許我們的服務(wù)器應(yīng)用下列類型的訪問策略:
允許訪問指定主機(jī)
允許訪問指定域名
拒絕訪問指定主機(jī)
拒絕訪問指定域名
拒絕訪問不能解析為名字的IP地址
下面的章節(jié)將會討論這些不同的策略。
通過主機(jī)名或是域名加強(qiáng)安全
當(dāng)一個客戶連接到我們的服務(wù)器時,我們也許會回憶起服務(wù)器由accep函數(shù)調(diào)用來接受客戶端套接口地址。如下面的代碼片段所示:
struct sockaddr_in adr_clnt;/* AF_INET */
int len_inet;                /* length */
int c;                 /* Client socket */
. . .
len_inet = sizeof adr_clnt;
c = accept(s,
(struct sockaddr *)&adr_clnt,
&len_inet);
數(shù)據(jù)報服務(wù)器由recvfrom函數(shù)得到這個客戶的地址。如下面的代碼所示:
int z;
struct sockaddr_in adr_clnt;/* AF_INET */
int len_inet;                /* length */
int s;                         /* Socket */
char dgram[512];       /* Recv buffer */
len_inet = sizeof adr_clnt;
z = recvfrom(s,                 /* Socket */
dgram,           /* Receiving buffer */
sizeof dgram,   /* Max recv buf size */
0,               /* Flags: no options */
(struct sockaddr *)&adr_clnt,/* Addr */
&len_inet);    /* Addr len, in & out */
在任何一種服務(wù)器類型中得到了客戶端地址以后,我們可以應(yīng)用第九章中的技術(shù),"主機(jī)名與網(wǎng)絡(luò)名查詢",使用gethostbyaddr函數(shù)。下面的代碼片段顯示了一個客戶端IP地址如何解析為一個主機(jī)名:
struct sockaddr_in adr_clnt;/* AF_INET */
struct hostent *hp; /* Host entry ptr */
hp = gethostbyaddr(
(char *)&adr_clnt.sin_addr,
size of adr_clnt.sin_addr,
adr_clnt.sin_family);
if ( !hp )
fprintf(logf," Error: %s\n",
hstrerror(h_errno));
else
fprintf(logf," %s\n",
hp->h_name);
服務(wù)器在hp->h_name中得到主機(jī)名以后,他就可以就用設(shè)計者所希望的允許或是拒絕策略。
通過IP地址標(biāo)識
盡管使用系統(tǒng)主機(jī)名或是域名進(jìn)行工作很方便,但是這種標(biāo)識方法卻存在安全問題。當(dāng)服務(wù)器接收到IP地址時,他必須使用另外一個網(wǎng)絡(luò)進(jìn)程(通過gethostbyaddr進(jìn)行初始化)來將其解析為一個主機(jī)名。我們可以很容易想到一個攻擊者可以其IP地址謊設(shè)置為他的私有名字服務(wù)器。因這這個原因,有時我們更喜歡只依據(jù)IP地址來進(jìn)行安全決定。
另一個折中的策略就是只依據(jù)IP地址確定訪問權(quán)限,但是在我們的服務(wù)器日志記錄中要同時記錄IP地址與所解析的域名。這樣在查看歷史日志時除了提供了一定的方便也提供了很好的安全策略。當(dāng)出現(xiàn)一些莫名的事情時,我們可以同時查看IP地址與所解析的域名。
使用IP地址作為安全策略為服務(wù)器管理提出了額外的要求。如果客戶改變了其IP地址,那么系統(tǒng)管理員必須更新IP地址,盡管他的主機(jī)名與域名并沒有改變。
當(dāng)使用IP地址安全策略時,對于系統(tǒng)管理員還有另一個挑戰(zhàn),也許他會遇到并不知道自己IP地址的用戶。然而,也許我們可以從他們那里得到主機(jī)名與域名的信息。使用這些信息,我們可以使用nslookup(1)命令來確定這個客戶的IP地址,然后配置這個地址。
最后,一些客戶每次啟動他們的機(jī)器時也許會使用不同的IP地址--例如,使用DHCP。他們的IP地址是由他們的系統(tǒng)管理員所確定的IP地址范圍內(nèi)一個變化的IP地址。在這種情況下我們最好的解決辦法就是與他們的系統(tǒng)管理員聯(lián)系,從而得到更多的關(guān)于他們的IP地址變化范圍內(nèi)容的信息。在這些情況下,我們通常會在網(wǎng)絡(luò)ID級限制訪問。
安全化inetd服務(wù)器
到目前為止,我們的討論圍繞著在我們要實(shí)現(xiàn)安全策略的每一臺服務(wù)器中的自定義代碼。這有許多缺點(diǎn):
管理安全策略的代碼必須嵌入到提供給客戶的每一臺服務(wù)器中
每臺服務(wù)器必須全程測試來確認(rèn)其精度并且防御攻擊
多個訪問點(diǎn)就會暴露多個弱點(diǎn)
前兩點(diǎn)很好的解釋了他們自身。最后一點(diǎn)可以通過一個超市關(guān)門時要鎖許多門的問題來演示。當(dāng)一些門必須鎖住時,就會很容易忽略其中的一個。另外,也許更有可能的是其中一個有會被攻破的弱點(diǎn)。正是由于這些原因,就需要將一個安全策略集中到一個模塊中。
集中網(wǎng)絡(luò)策略
在上一章中,我們看到inetd守護(hù)進(jìn)程如何使得服務(wù)器的設(shè)計更為簡單。inetd守護(hù)進(jìn)程提供了監(jiān)聽客戶端請求所必須的所有服務(wù)器代碼,并且只在必須的時候才會啟動服務(wù)器。inetd守護(hù)進(jìn)程提供了另外的一級方便:他允許安裝一個集中的網(wǎng)絡(luò)安全模塊。
如果我們正在使用Linux較新的版本,例如RH6,我們就已經(jīng)有了一個可以由inetd來調(diào)用的TCP包裝程序。要驗(yàn)證這一點(diǎn),可以用grep查找telnetd實(shí)體,如下所示:
# grep telnet /etc/inetd.conf
telnet stream tcp nowait root /usr/sbin/tcpd in.telnetd
#
Y
由這個例子輸出,我們可以看到當(dāng)一個telnet請求到達(dá)時,inetd調(diào)用可執(zhí)行的/usr/sbin/tcpd程序。如果我們同時用grep檢測ftp,我們就會發(fā)現(xiàn)/usr/sbin/tcpd也會為這個程序而調(diào)用。那么tcpd程序是做什么的呢?
應(yīng)該強(qiáng)調(diào)的一點(diǎn)就是tcpd程序在inetd與服務(wù)器(例如telnetd)之間插入其自身。這是以透明的方式來完成的,因?yàn)樗⒉粫谔捉涌谏袭a(chǎn)生任何輸入或是輸出。tcpd程序只是簡單的應(yīng)用其網(wǎng)絡(luò)安全策略,如果允許訪問就會調(diào)用相應(yīng)的服務(wù)器。
從前面的grep輸入的例子,字符串in.telnet作為tcpd的命令名(他的argv[0]的值)。這會通知tcpd如果允許訪問應(yīng)調(diào)用哪個服務(wù)器。如果因?yàn)槟承┰蚓芙^訪問,這樣的嘗試將會被記入日志,而套接口將會關(guān)閉(當(dāng)tcpd退出時),而不會調(diào)用服務(wù)器。
理解TCP包裝器概念
下圖向我們顯示了tcpd程序的角色,以及他與inetd的交互和最后的服務(wù)器。
下面我們來回顧一下遠(yuǎn)程客戶端連接到我們的in.telnetd服務(wù)器的過程:
1 客戶端使用telnet客戶端命令向我們機(jī)器的telnet守護(hù)進(jìn)程發(fā)送一個連接請求。
2 我們的Linux主機(jī)使用inetd,他被配置為在23號端口監(jiān)聽telnet請求。他接受第一步的連接請求。
3 /etc/inetd.conf配置文件指導(dǎo)我們的inetd服務(wù)器fork一個新進(jìn)程。而父進(jìn)程返回繼續(xù)監(jiān)聽連接請求。
4 第三步的子進(jìn)程調(diào)用exec命令來執(zhí)行/usr/sbin/tcpd TCP包裝器程序。
5 tcpd程序?qū)Q定是否應(yīng)為客戶端指定一定的訪問權(quán)限。這是由所調(diào)用的套接口地址與/etc/hosts.deny和/etc/hosts.allow配置文件的組合來決定的。
6 如果拒絕訪問,tcpd只會簡單的結(jié)束(這會使得文件單元0,1,2關(guān)閉,而他們是套接口文件描述符)。
7 如果允許訪問,要啟動的可執(zhí)行文件是由tcpd的argv[0]值來確定的。在這個例子中為in.telnetd程序。這指定的可執(zhí)行路徑名,/usr/sbin/in.telnetd,他將會傳遞給exec函數(shù)來載入并執(zhí)行。
8 服務(wù)器現(xiàn)在以tcpd之前所有的相同的進(jìn)程ID代替tcpd運(yùn)行。服務(wù)器現(xiàn)在在套接口(文件單元0,1,2)執(zhí)行輸入與輸出操作。
第七步是很重要的,這是由tcpd內(nèi)部通過exec函數(shù)啟動服務(wù)器進(jìn)程的地方。這在inetd與(子)服務(wù)器進(jìn)程之間維護(hù)重要的父/子關(guān)系。當(dāng)使用了wait標(biāo)記時,inetd守護(hù)進(jìn)程就會在檢測到當(dāng)前子進(jìn)程結(jié)束時啟動下一個服務(wù)器。這只有當(dāng)服務(wù)器是inetd父進(jìn)程的直接子進(jìn)程時才會正常工作。下面的幾點(diǎn)也許會有助于我們來理解:
1 例如在這個例子中inetd守護(hù)進(jìn)程的ID為124。
2 inetd守護(hù)進(jìn)程調(diào)用fork來啟動一個子進(jìn)程。在這個例子中子進(jìn)程ID現(xiàn)在為1243。
3 inetd子進(jìn)程(PID 1243)現(xiàn)在調(diào)用exec來啟動/usr/sbin/tcpd。
4 注意到tcpd現(xiàn)在以PID 1243來運(yùn)行(我們可以回想一下exec使用相同的進(jìn)程資源來啟動一個新程序,而忽略調(diào)用exec的原始程序)。
5 當(dāng)允許訪問時,tcpd實(shí)際會再次調(diào)用exec。這會啟動新服務(wù)器,在這個例子中為/usr/sbin/in.telnetd。
6 注意到服務(wù)器/usr/sbin/in.telnetd仍然是1243,因?yàn)閑xec并沒有創(chuàng)建一個新進(jìn)程。
7 服務(wù)器in.telnetd實(shí)際退出(PID 1243結(jié)束)。
8 父進(jìn)程inetd(PID 1243)接收到一個SIGCHLD信號來表明其子進(jìn)程ID 1243已經(jīng)結(jié)束。這會使得inetd調(diào)用wait來確定哪一個子進(jìn)程結(jié)束了。
從這些步驟我們可以看出tcpd包裝程序是多么的精巧。這個程序絕不會在套接口上執(zhí)行I/O操作--這會打亂正使用的協(xié)議(telnet或是其他)。
確定訪問
現(xiàn)在我們也許仍有兩個問題:
1 TCP包裝程序如何確定他要保證的服務(wù)(telnet,ftp)?
2 他如何確定客戶端是誰?
現(xiàn)在,我們將會在下面的部分簡要的描述各個解決方案。
確定服務(wù)
tcpd程序可以通過調(diào)用getsockname函數(shù)來確定他所保護(hù)的服務(wù)。還記得這個函數(shù)?他不僅返回客戶端所連接到的套接口地址,而且還會指明服務(wù)的端口。在前面的例子中,端口號為23(telnet服務(wù))。
確定客戶端標(biāo)識
因?yàn)閠cpd程序并不執(zhí)行accept函數(shù)調(diào)用(這是由inetd來完成的),他必須確定客戶端是誰。也許正如我們所猜想的,這是由getpeername函數(shù)來完成的。我們也許還會回憶起這個函數(shù)取得遠(yuǎn)程客戶端的地址和端口號,其工作方式與getsockname相類似。
確定數(shù)據(jù)報客戶端標(biāo)識
確定一個數(shù)據(jù)報客戶端的標(biāo)識有一些麻煩。敏銳的讀者也許在前面的章節(jié)中已經(jīng)想到了這個問題,因?yàn)閿?shù)據(jù)報并不使用accept函數(shù)。也不可以在數(shù)據(jù)報套接口上使用getpeername函數(shù),因?yàn)榈谝粋€數(shù)據(jù)報實(shí)際上是由不同的客戶端發(fā)來的??蛻舳说刂肥怯蓃ecvfrom函數(shù)調(diào)用返回的。然而,tcpd程序可以在不實(shí)際讀取服務(wù)器數(shù)據(jù)的情況下確定客戶端標(biāo)識嗎?
結(jié)果證明tcpd可以欺騙??蛻舳说刂放c端口號可以由使用MSG_PEEK標(biāo)記選項(xiàng)的recvfrom函數(shù)調(diào)用來確定。如下面的示例代碼所示:
int z;
struct sockaddr_in adr_clnt;/* AF_INET */
int len_inet;                /* length */
int s;                      /* Socket */
char dgram[512];         /* Recv buffer */
len_inet = sizeof adr_clnt;
z = recvfrom(s,                  /* Socket */
dgram,          /* Receiving buffer */
sizeof dgram,    /* Max recv buf size */
MSG_PEEK,      /* Flags: Peek at data */
(struct sockaddr *)&adr_clnt,/* Addr */
&len_inet);    /* Addr len, in & out */
在這里我們需要注意MSG_PEEK標(biāo)記選項(xiàng)。這個選項(xiàng)會引導(dǎo)內(nèi)核以類似通常的方式來執(zhí)行recvfrom調(diào)用,所不同的是數(shù)據(jù)并不會由讀隊(duì)列中移除。如果允許訪問,這會允許tcpd程序監(jiān)視服務(wù)器接下來所要讀取的數(shù)據(jù)報。
注意,數(shù)據(jù)本身在這里并不重要。MSG_PEEK操作所完成的是他會返回客戶端的IP地址(在這個例子中,這會放置在adr_clnt中)。包裝程序會由adr_clnt變量決定數(shù)據(jù)報是否應(yīng)被處理。
到現(xiàn)在為止,我們已經(jīng)理解了TCP包裝概念后面的理論。下面,我們將會看到這樣的思想以例子程序的具體形式進(jìn)行演示。
安裝包裝器與服務(wù)器程序
這一節(jié)將會提供一個簡單的數(shù)據(jù)報服務(wù)器與一個相對應(yīng)的TCP包裝程序。這個包裝程序?qū)崿F(xiàn)了一個非常簡單的安全策略。
檢測服務(wù)器與包裝器日志代碼
服務(wù)器與包裝程序共享一些日志函數(shù)。日志函數(shù)代碼在下面給出:
/*
* log.c
*
* Logging Functions:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
static FILE *logf = NULL;    /* Log File */
/*
* Open log file for append:
*
* Returns:
* 0 Success
* -1 Failed
*/
int log_open(const char *pathname)
{
logf = fopen(pathname,"a");
return logf ? 0 : -1;
}
/*
* Log information to a file:
*/
void log(const char *format,...)
{
va_list ap;
if(!logf)
return ;    /* No log file open */
fprintf(logf,"[PID %ld] ",(long)getpid());
va_start(ap,format);
vfprintf(logf,format,ap);
va_end(ap);
fflush(logf);
}
/*
* Close the log file:
*/
void log_close(void)
{
if(logf)
fclose(logf);
logf = NULL;
}
/*
* This function reports the error to
* the log file and calls exit(1)
*/
void bail(const char *on_what)
{
if(logf)    /* Is log open? */
{
if(errno)    /* Error ? */
log("%s:",strerror(errno));
log("%s\n",on_what);    /* Log msg */
log_close();
}
exit(1);
}
由引用程序所包含的文件代碼如下:
/*
* log.h
* log.c externs:
*/
extern int log_open(const char *pathname);
extern void log(const char *format,...);
extern void log_close(void);
extern void bail(const char *on_what);
檢測數(shù)據(jù)報服務(wù)器代碼
這里將會演示一個程序,這個程序會處理第一個數(shù)據(jù)報,然后會輪詢更多的數(shù)據(jù)報。如果在8秒內(nèi)沒有數(shù)據(jù)報到達(dá),服務(wù)器就會超時退出。inetd守護(hù)進(jìn)程直到被通知服務(wù)器結(jié)束時才會啟動另一個服務(wù)器(/etc/inetd.conf記錄實(shí)體必須使用wait標(biāo)識字)。
下面是數(shù)據(jù)報服務(wù)程序所使用的代碼:
/*
* dgramisrvr.c
*
* Example inetd datagram server:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.h"
#define    LOGPATH "/tmp/dgramisrvr.log"
int main(int argc,char **argv)
{
int z;
int s;        /* Socket */
int alen;    /* Length of address */
struct sockaddr_in adr_clnt;    /* Client */
char dgram[512];    /* Receive buffer */
char dtfmt[512];    /* Date/Time Result */
time_t td;            /* Current Time and Date */
struct tm dtv;        /* Date time values */
fd_set rx_set;        /* Incoming req. set */
struct timeval tmout;    /* Timeout value */
/*
* Open a log file for append:
*/
if(log_open(LOGPATH) == -1)
exit(1);        /* No log file ! */
log("dgramisrvr started. \n");
/*
* Other initialization:
*/
s = 0;    /* Our socket is on std input */
FD_ZERO(&rx_set);    /* Initialize */
FD_SET(s,&rx_set);    /* Notice fd=0 */
/*
* Now wait for incoming datagrams:
*/
for(;;)
{
/*
* Blog until a datagram arrives:
*/
alen = sizeof adr_clnt;
z = recvfrom(s,        /* Socket */
dgram,        /* Receiving buffer */
sizeof dgram,    /* Max recv size */
(struct sockaddr *)&adr_clnt,
&alen);        /* Addr len,in&out*/
if(z<0)
bail("recvfrom(2)");
dgram[z]=0;    /* NULL terminate dgram */
/*
* Log the request:
*/
log("Got request '%s' from %s port %d\n",
dgram,
inet_ntoa(adr_clnt.sin_addr),
ntohs(adr_clnt.sin_port));
/*
* Get the current date and time:
*/
time(&td);    /* Current time & date */
dtv = *localtime(&td);
/*
* Formate a new date and time string,
* based upon the input format string:
*/
strftime(dtfmt,        /* Formatted result */
sizeof dtfmt,    /* Max size */
dgram,            /* date/time format */
&dtv);            /* Input values */
/*
* Send the formatted result back to the
* client program:
*/
z = sendto(s,            /* Socket */
dtfmt,            /* datagram result */
strlen(dtfmt),    /* length */
0,                /* Flags:no options */
(struct sockaddr *)&adr_clnt,
alen);
if(z<0)
bail("sendto(2)");
/*
* Wait for next packet or timeout:
*
* This is easily accomplished with the
* use of select(2).
*/
do
{
/* Establish Timeout = 8.0 secs */
tmout.tv_sec = 8;    /* 8 seconds */
tmout.tv_usec = 0;    /* + 0 usec */
/* Wait for read event or timeout */
z = select(s+1,&rx_set,NULL,NULL,&tmout);
}while(z==-1 && errno == ENITR);
/*
* Exit if select(2) return an error
* or if it idictes a timeout:
*/
if(z<=0)
break;
}
/*
* Close the socket and exit:
*/
if(z == -1)
log("%s:select(2) \n",strerror(errno));
else
log("Time out:server exiting.\n");
close(s);
log_close();
return 0;
}
服務(wù)器代碼只是簡單的組織為一個main()程序。
服務(wù)器代碼顯示了一個UDP服務(wù)器如何輪詢,并且讀取更多的數(shù)據(jù)報直到發(fā)生超時。還有其他的方法來實(shí)現(xiàn)超時,但是這也許是其中最簡單的一個了。
檢測簡單的TCP包裝程序
現(xiàn)在到了介紹我們將會用到的簡單的TCP包裝程序的源代碼了。其程序代碼如下:
/*
* wrapper.c
*
* Simple wrappher example:
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.h"
#define LOGPATH "/tmp/wrapper.log"
int main(int argc,char **argv,char **envp)
{
int z;
struct sockaddr_in adr_clnt;    /* Client */
int alen;        /* Address length */
char dgram[512];    /* Receive buffer */
char *str_addr;        /* String form of addr */
/*
* We must log denied attempts:
*/
if(log_open(LOGPATH) == -1)
exit(1);    /* Can't open log file! */
log("wrapper started.\n");
/*
* Peek at datagram using MSG_PEEK:
*/
alen = sizeof adr_clnt;    /* length */
z = recvfrom(0,        /* Socket on std input */
dgram,        /* Receiving buffer */
sizeof dgram,    /* Max recv size */
MSG_PEEK,        /* Flags:Peek */
(struct sockaddr *)&adr_clnt,
&alen);            /* Addr len, in & out */
if(z<0)
bail("recvfrom(2),peeking at client"
"address.");
/*
* Covert IP address to string form:
*/
str_addr = inet_ntoa(adr_clnt.sin_addr);
if(strcmp(str_addr,"127.7.7.7") != 0)
{
/*
* Not our special 127.7.7.7 address:
*/
log("Address %s port %d rejected.\n",
str_addr,ntohs(adr_clnt.sin_port));
/*
* We must read this packet now without
* the MSG_PEEK option to discard dgram:
*/
z = recvfrom(0,        /* Socket */
dgram,        /* Receiving buffer */
sizeof dgram,    /* Max rcv size */
0,            /* No flags */
(struct sockaddr *)&adr_clnt,
&alen);
if(z<0)
bail("recvfrom(2),eating dgram");
exit(1);
}
/*
* Accpet this dgram request,and
* launch the server:
*/
log("Address %s port %d accepted.\n",
str_addr,ntohs(adr_clnt.sin_port));
/*
* inetd has provied argv[0] from the
* config file /etc/inetd.conf:we have
* used this to indicate the server's
* full pathname for this exampel.we
* simply pass any other arguments and
* environment as is.
*/
log("Starting '%s'\n",argv[0]);
log_close();        /* No longer need this */
z = execve(argv[0],argv,envp);
/*
* If control returns,the execve(2)
* failed for some reason:
*/
log_open(LOGPATH);        /* Re log */
bail("execve(2),starting server");
return 1;
}
上面所提供的包裝程序代碼實(shí)現(xiàn)了下面的一些簡單的安全策略:
如果客戶端為127.7.7.7,他的請求可以到達(dá)數(shù)據(jù)報服務(wù)器。并沒有在客戶端的端口上進(jìn)行限制。
如果客戶端地址是其他的IP地址,請求被拒絕并記入日志。
在這里需要注意的一點(diǎn)是,數(shù)據(jù)報包裝程序并不會使用getpeername函數(shù)來確定數(shù)據(jù)報地址。相反,他必須使用MSG_PEEK標(biāo)記位來調(diào)用recvfrom函數(shù)。MSG_PEEK標(biāo)記位返回客戶端地址并存入地址結(jié)構(gòu)adr_clnt中,而并不會真正的由這個套接口的輸入隊(duì)列中移除數(shù)據(jù)報。
簡介客戶端程序
在這里提供了一個以前的客戶端程序的修改版本。這個客戶端參數(shù)需要兩個命令行參數(shù):服務(wù)器地址以及要使用的客戶端地址。這個額外客戶端IP地址的指定可以允許我們使用我們的TCP包裝器程序執(zhí)行更多的試驗(yàn)。修改后的數(shù)據(jù)報客戶端程序如下:
/*
* dgramcln2.c:
*
* Modified datagram client:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(char *on_what)
{
if(errno)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
}
fputs(on_what,stderr);
fputc('\n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
char *srvr_addr = NULL;        /* Srvr addr */
char *clnt_addr = NULL;        /* Clnt addr */
struct sockaddr_in adr_srvr;    /* Server */
struct sockaddr_in adr_clnt;    /* Client */
struct sockaddr_in adr;            /* AF_INET */
int alen;        /* Socket addr lenth */
int s;            /* Socket */
char dgram[512];    /* Recv buffer */
/*
* Insist on two command-line arguments
* (without port numbers):
*
* dgramcln2 <server_addr> <client_addr>
*/
if(argc != 3)
{
fputs("Usage: dgramclnt <server_ipaddr> "
"<client_ipaddr>\n",stderr);
return 1;
}
srvr_addr = argv[1];    /* 1st arg is srv */
clnt_addr = argv[2];    /* 2nd arg is clnt */
/*
* Create a server socket addrss:
*/
memset(&adr_srvr,0,sizeof adr_srvr);
adr_srvr.sin_family = AF_INET;
adr_srvr.sin_port = htons(9090);
adr_srvr.sin_addr.s_addr = inet_addr(srvr_addr);
if(adr_srvr.sin_addr.s_addr == INADDR_NONE)
bail("bad server address.");
/*
* Create a UDP socket:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* Create the specific client address:
*/
memset(&adr_clnt,0,sizeof adr_clnt);
adr_clnt.sin_family = AF_INET;
adr_clnt.sin_port = 0;    /* Any port */
adr_clnt.sin_addr.s_addr = inet_addr(clnt_addr);
if(adr_clnt.sin_addr.s_addr == INADDR_NONE)
bail("bad client address.");
/*
* Bind the specific client address:
*/
z = bind(s,(struct sockaddr *)&adr_clnt,sizeof adr_clnt);
if(z==-1)
bail("bind(2) of client address");
/*
* Enter input client loop:
*/
for(;;)
{
/*
* Prompt user for a date formate string:
*/
fputs("\nEnter format string: ",stdout);
if(!fgets(dgram,sizeof dgram,stdin))
break;        /* EOF */
z = strlen(dgram);
if(z>0 && dgram[--z] == '\n')
dgram[z] = 0;    /* Stomp out newline */
/*
* Send format string to server:
*/
z = sendto(s,    /* Socket */
dgram,    /* datagram to send */
strlen(dgram),    /* dgram length */
0,        /* Flags:no options */
(struct sockaddr *)&adr_srvr,
sizeof adr_srvr);
if(z<0)
bail("sendto(2)");
/*
* Wait for a response:
*
* NOTE: Control will hang here if the
* wrapper decides we lack access (no
* response will arrive).
*/
alen = sizeof adr;
z = recvfrom(s,        /* Socket */
dgram,        /* Receiving buffer */
sizeof dgram,    /* Max recv size */
0,            /* Flags no options */
(struct sockaddr *)&adr,
&alen);        /* Addr len, in & out */
if(z<0)
bail("recfrom(2)");
dgram[z]=0;        /* NULL terminate */
/*
* Report Result:
*/
printf("Result from %s port %u :"
"\n\t'%s'\n",
inet_ntoa(adr.sin_addr),
(unsigned)ntohs(adr.sin_port),
dgram);
}
/*
* Close the socket and exit:
*/
close(s);
putchar('\n');
return 0;
}
注意,如果包裝器程序認(rèn)為不可以訪問服務(wù)器,那么dgramcln2程序就會被掛起。這是因?yàn)閞ecvfrom函數(shù)并不會從服務(wù)器得到應(yīng)答。當(dāng)出現(xiàn)這種情況時,我們需要中斷我們的程序。
安裝與測試包裝器
現(xiàn)在我們可以檢測我們要用到的所有代碼。要開始我們的包裝器實(shí)驗(yàn),首先我們需要這些代碼:
AMF
$ make
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type           dgramisrvr.c
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type           log.c
gcc dgramisrvr.o log.o -o dgramisrvr
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type           wrapper.c
gcc wrapper.o log.o -o wrapper
TE
gcc -c -D_GNU_SOURCE -Wall -Wreturn-type           dgramcln2.c
gcc dgramcln2.o -o dgramcln2
$
通常我們需要root權(quán)限才可以安裝服務(wù)器。然而為了簡單的測試,我們可以不用root權(quán)限來進(jìn)行這個實(shí)驗(yàn)。要在/tmp目錄下創(chuàng)建我們的文件,我們可以用下面的命令:
$ make install
rm -f /tmp/wrapper.log /tmp/dgramisrvr.log
rm -f /tmp/inetd.conf /tmp/wrapper /tmp/dgramisrvr
cp dgramisrvr wrapper /tmp/.
chmod 500 /tmp/wrapper /tmp/dgramisrvr
chmod 600 /tmp/inetd.conf
/usr/sbin/inetd /tmp/inetd.conf
make命令在/tmp目錄下放置了一系列的文件,包括一個簡單的/tmp/inetd.conf文件。其內(nèi)容如下:
$ cat /tmp/inetd.conf
9090 dgram udp wait studnt1 /tmp/wrapper /tmp/dgramisrvr
$
也許我們希望再檢測一下其他的文件是否正確的安裝到了/tmp目錄:
$ ls -Itr /tmp | tail
-rw-r-----   1 stdnt1 class1 29454 Feb 19 22:59 xprnKg3RSc
-rw-r--r--   1 root   root       11 Feb 21 23:18 1pq.0002621c
-r-x------   1 stdnt1 class1 14202 Feb 22 22:48 wrapper
-rw-------   1 stdnt1 class1    53 Feb 22 22:48 inetd.conf
-r-x------   1 stdnt1 class1 15237 Feb 22 22:48 dgramisrvr
$
從輸出中,我們可以看到TCP包裝程序wrapper已經(jīng)被安裝了,而dgramisrvr可執(zhí)行程序也安裝到了/tmp目錄。
監(jiān)視日志文件
如果我們正使用X Window會話,那么推薦啟動一個終端會話,其命令如下:
$ >/tmp/wrapper.log
$ tail -f /tmp/wrapper.log
這會創(chuàng)建并監(jiān)視包裝器日志文件。我們將會看到當(dāng)包裝器程序向日志文件中寫入記錄時窗口會進(jìn)行更新。
在一個新啟動的窗口,執(zhí)行下面命令:
$ >/tmp/dgraimsrvr.log
$ tail -f /tmp/dgramisrvr.log
這會創(chuàng)建并監(jiān)視服務(wù)器日志文件。
如果我們并沒有使用X Window會活,我們可以使用多個終端會話來達(dá)到同樣的目的。
啟動我們的inetd守護(hù)進(jìn)程
在我們啟動我們的客戶端之前,我們必須準(zhǔn)備好我們的inetd守護(hù)進(jìn)程。這個守護(hù)進(jìn)程會運(yùn)行在我們自己的用戶ID下,而不需要任何特殊的root權(quán)限。然而,我們必須小心的命令行為其指定正確的配置文件,如下所示:
$ /usr/sbin/inetd /tmp/inetd.conf
$
這個命令行參數(shù)會通知我們的inetd守護(hù)進(jìn)程使用我們所提供的/tmp/inetd.conf配置文件,而不是/etc/inetd.conf。這個程序會自動的進(jìn)入后臺運(yùn)行,而我們可以用下面的命令來查看:
$ ps -ef | grep inetd
root       313     1 0 Feb15 ?    00:00:00 inetd
studnt1 12763      1 0 23:04 ?    00:00:00 /usr/sbin/inetd /tmp/inetd.conf
studnt1 12765 11739 0 23:08 pts/3 00:00:00 grep inetd
$
測試包裝器
完成了日志的監(jiān)視之后,現(xiàn)在我們就可以啟動客戶端并且試驗(yàn)一些內(nèi)容了。首先,我們測試一些包裝器程序可以接受的內(nèi)容:
$ ./dgramcln2 127.0.0.1 127.7.7.7
Enter format string: %A %B %D
Result from 127.7.7.7 port 9090 :
'Tuesday November 11/09/99'
Enter format string:
這會啟動客戶端程序,并且將套接口的客戶端綁定到IP地址 127.7.7.7,而這正是wrapper程序所可以接受的。包裝器日志如下:
$ tail -f /tmp/wrapper.log
[PID 1279] wrapper started.
[PID 1279] Address 127.7.7.7 port 1027 accepted.
[PID 1279] Starting '/tmp/dgramisrvr'
日志表明進(jìn)程ID為1279,而請求來自127.7.7.7,端口號為1027。因?yàn)檎埱罂梢越邮?,服?wù)器/tmp/dgramisrvr來執(zhí)行請求。
而服務(wù)器的日志輸出類似下面的內(nèi)容:
$ tail -f /tmp/dgramisrvr.log
[PID 1279] dgramisrvr started.
[PID 1279] Got request '%A %B %D from 127.7.7.7 port 1027
[PID 1279] Timed out: server exiting.
注意,服務(wù)器的進(jìn)程ID與包裝器的相同(包裝器進(jìn)程是由服務(wù)器使用execve啟動的)。日志記錄表明服務(wù)器啟動并處理請求。最后的記錄表明服務(wù)器超時退出。
拒絕請求
關(guān)閉我們的客戶端程序,并使用一個新的地址啟動,如下所示:
$ ./dgramcln2 127.0.0.1 127.13.13.13
Enter format string: %D (%B %A)
我們將會看到我們的客戶端程序并不會有任何響應(yīng)。他會掛起,因?yàn)閣rapper程序拒絕這個請求到在服務(wù)器。我們可以中斷退出。
wrapper日志文件內(nèi)容如下:
$ tail -f /tmp/wrapper.log
[PID 1279] wrapper started.
[PID 1279] Address 127.7.7.7 port 1027 accepted.
[PID 1279] Starting '/tmp/dgramisrvr'
[PID 1289] wrapper started.
[PID 1289] Address 127.13.13.13 port 1027 rejected.
我們將會看到下一個數(shù)據(jù)報請求是由一個新的wrapper進(jìn)程ID 1289來處理的。最后一行日志記錄表明地址127.13.13.13被拒絕。客戶端程序掛起,因?yàn)閣rapper程序丟棄了數(shù)據(jù)報,從而阻止其被服務(wù)器處理。然后wrapper程序退出。
測試服務(wù)器超時
要測試服務(wù)器的輪詢能力,我們必須快速的輸入兩個日期格式請求(在8秒以內(nèi))。如下面的一個會話例子:
$ ./dgramcln2 127.0.0.1 127.7.7.7
Enter format string: %x
Result from 127.7.7.7 port 9090 :
'11/09/99'
Enter format string: %x %X
Result from 127.7.7.7 port 9090 :
'11/09/99 19:11:32'
Enter format string: CTRL+D
$
如果我們足夠快,服務(wù)器可以在單一的服務(wù)器進(jìn)程內(nèi)同時處理這兩個請求。要檢測是否如此,我們可以查看服務(wù)器日志:
$ tail -f /tmp/dgramisrvr.log
[PID 1279] dgramisrvr started.
[PID 1279] Got request '%A %B %D' from 127.7.7.7 port 1027
[PID 1279] Timed out: server exiting.
[PID 1294] dgramisrvr started.
[PID 1294] Got request '%x' from 127.7.7.7 port 1027
[PID 1294] Got request '%x %X' from 127.7.7.7 port 1027
[PID 1294] Timed out: server exiting.
最后四行日志記錄表明服務(wù)器進(jìn)程ID 1294 可以在超時之前處理兩個數(shù)據(jù)請求。
御載演示程序
要御載演示程序,可以執(zhí)行下面的命令:
$ make clobber
rm -f *.o core a.out
rm -f /tmp/wrapper.log /tmp/dgramisrvr.log
rm -f /tmp/inetd.conf /tmp/wrapper /tmp/dgramisrvr
rm -f dgramisrvr wrapper dgramcln2
studnt1 12763   1  0 23:04 ?    00:00:00 /usr/sbin/inetd /tmp/inetd.conf
If you see your inetd process running above, you may want to kill it now.
$
Makefile所提供的clobber目標(biāo)會移除/tmp目錄下的所有文件,并且試著顯示我們inetd守護(hù)進(jìn)程的ID。在所顯示的例子輸出中,守護(hù)進(jìn)程的ID為12763。這可以由kill命令來結(jié)束:
$ kill 12763
$
數(shù)據(jù)報弱點(diǎn)
在我們?yōu)閿?shù)據(jù)報服務(wù)器所設(shè)計的包裝器中有一個弱點(diǎn)。我們是否看出了這個問題呢?提示:他與服務(wù)器輪詢有關(guān)。
正如在前面所顯示的數(shù)據(jù)報服務(wù)器的輪詢有一個可以使用包裝器概念進(jìn)行攻擊的弱點(diǎn)。當(dāng)沒有服務(wù)器進(jìn)程運(yùn)行時,包裝器程序總是可以在服務(wù)器讀取數(shù)據(jù)報之前進(jìn)行篩選。然而,如果服務(wù)器等待更多的數(shù)據(jù)報,并且只在超時時退出,包裝器程序就會被用來篩選這些額外的數(shù)據(jù)報。這個過程可以總結(jié)如下:
1 一個數(shù)據(jù)報到達(dá),通知。
2 inetd守護(hù)進(jìn)程啟動wrapper程序。
3 wrapper程序允許這個數(shù)據(jù)報,調(diào)用exec來啟動數(shù)據(jù)報服務(wù)器。
4 數(shù)據(jù)報服務(wù)順讀取并處理數(shù)據(jù)報。
5 服務(wù)器等待其他的數(shù)據(jù)報。
6 如果一個數(shù)據(jù)報到達(dá),重復(fù)4。
7 否則,服務(wù)器超時并退出。
8 重復(fù)1。
當(dāng)服務(wù)器繼續(xù)運(yùn)行時,進(jìn)程重復(fù)4。這就在步驟3留下了安全檢測。如果我們足夠快,我們就可以使用前面所提供的例子程序進(jìn)行演示。
為了更安全,我們可以有下面的一些選擇:
使用nowait風(fēng)格的數(shù)據(jù)報服務(wù)器(這些服務(wù)器處理一個數(shù)據(jù)并退出)。這會強(qiáng)制包裝器程序仔細(xì)檢查所有的請求。
在我們的數(shù)據(jù)報服務(wù)器中使用自定義的代碼在接受處理之前測試每一個數(shù)據(jù)報。
一個折中的辦法就是縮短超時間隔,但是這還會留下許多漏洞。
因?yàn)檫@些原因,如果一個同樣服務(wù)的TCP版本存在,許多站點(diǎn)就會選擇禁止數(shù)據(jù)報服務(wù)。如果必須提供數(shù)據(jù)報服務(wù),安全站點(diǎn)并不會允許他們的數(shù)據(jù)報服務(wù)器循環(huán)。這要求改變服務(wù)器源代碼,或是編寫一個自定義的服務(wù)器程序。相對應(yīng)的,服務(wù)器程序本身會檢測每一個客戶端請求的訪問權(quán)限,而不依賴于TCP包裝器的概念。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux Socket學(xué)習(xí)(八)
Ubuntu下建立tftp服務(wù)器_FTP服務(wù)器_開發(fā)學(xué)院
Linux網(wǎng)絡(luò)服務(wù)配置文件詳解
什么是xinetd?的相關(guān)文章推薦 - - JavaEye專欄頻道
BusyBox自帶的FTP服務(wù)器
inetd.conf文件
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服