Linux socket函数,linux unix socket

  Linux socket函数,linux unix socket

  网络安全编程

  本书到此为止,我们一直在关注如何使用windows sockets编写程序,无论是客户端程序还是服务器程序。但是,我们不考虑安全编程来对抗外部威胁,这些威胁可能来自互联网或我们局域网中某些别有用心的人。在本章中,我们将介绍以下内容:

  inetd守护进程如何与TCP包装器概念合作来提供客户检查?

  TCP包装器的概念是如何工作的

  当我们完成本章时,我们将理解TCP包装器的概念是如何工作的,以及如何将它应用到我们自己管理或编写的服务器上。

  定义安全性

  《韦氏大学词典》对安全性有多种定义。我们对以下两个定义感兴趣:

  被危险控制的性质或状态。

  针对危险采取的措施

  在讨论网络安全的时候,第一点就是要远离各种潜在的威胁。第二,我们必须确保我们的系统资源不被非法检查,以某种方式破坏,并受到其他种类的攻击。我们写的文章,不公之于众,是不可能从我们的系统中流失的。

  这项任务的复杂性及其程序的边界使得不可能在一章中解决所有的问题。但是,网络安全的某些方面直接适用于windows sockets编程,我们不应该忽视现有的危险。我们在本章中提到的一些简单方法可以帮助我们防止攻击。我们将知道弱点在哪里,以及我们应该如何处理它们。

  识别朋友或敌人

  第17章,“传递证书和文件描述符”,将向我们展示,我们可以通过使用证书来识别一个具有高可信度的本地用户。但是,当我们服务器的这个用户是远程用户时,这样的可信度就不容易达到了。

  在本章中,我们将根据对等网络地址来识别资源的用户。但是,这不是一种保护技术,因为对等地址在特定条件下会被欺骗。然而,他确实提供了一个简单的初级保护来抵御攻击。

  有了IP地址后,我们可以检查客户的注册主机和域名。这提供了额外的筛选级别,这需要在攻击者部分进行更多的描述。

  解析主机和域名允许我们的服务器应用以下类型的访问策略:

  允许访问指定的主机

  允许访问指定的域名

  对指定主机的访问被拒绝。

  拒绝访问指定的域名

  拒绝访问无法解析为名字的IP地址。

  以下部分将讨论这些不同的策略。

  通过主机名或域名加强安全性。

  当一个客户机连接到我们的服务器时,我们可能还记得ACCEPT函数调用服务器来接受客户机的windows套接字地址。如下面的代码片段所示:

  结构sockaddr _ in adr _ clnt/* AF_INET */

  int len _ inet/*长度*/

  int c;/*客户端套接字*/。

  len _ inet=sizeof adr _ clnt

  c=接受(s

  (struct sockaddr *) adr_clnt,

  len _ inet);

  数据服务器通过recvfrom函数获取该客户的地址。如下面的代码所示:

  int z;

  结构sockaddr _ in adr _ clnt/* AF_INET */

  int len _ inet/*长度*/

  int s;/*套接字*/

  char dgram[512];/*接收缓冲区*/

  len _ inet=sizeof adr _ clnt

  z=recvfrom(s,/* Socket */

  dgram,/*接收缓冲器*/

  dgram的大小,/*最大接收缓冲区大小*/

  0,/*标志:没有选项*/

  (struct sockaddr *) adr_clnt,/* Addr */

  len _ inet);/*地址长度,输入输出*/

  在获得任何服务器类型的客户端地址后,我们可以应用第9章“主机名和网络名查询”中的技术,并使用gethostbyaddr函数。以下代码片段显示了客户端IP地址如何解析为主机名:

  结构sockaddr _ in adr _ clnt/* AF_INET */

  struct hostent * hp/*主机条目指针*/

  hp=gethostbyaddr(

  (char *) adr_clnt.sin_addr,

  adr_clnt.sin_addr的大小

  ADR _ clnt . sin _ family);

  如果(!惠普)

  fprintf(logf,错误:%s/n ,

  HST error(h _ errno));

  其他

  fprintf(logf, %s/n ,

  HP-h _ name);

  在获得服务器hp- h_name中的主机名之后,它可以使用设计者想要的允许或拒绝策略。

  由IP地址标识

  虽然使用系统主机名或域名工作很方便,但这种识别方式存在安全问题。当服务器收到IP地址时,它必须使用另一个网络进程(由gethostbyaddr初始化)将其解析为主机名。我们很容易想到一个攻击者,他可以将自己的IP地址设置为自己的私有名称服务器。出于这个原因,有时我们更愿意只根据IP地址来做出安全决策。

  另一个折中的策略是只根据IP地址来确定访问权限,但是我们应该同时在我们的服务器日志记录中记录IP地址和解析的域名。这样,在查看历史日志时,不仅提供了一些便利,也提供了一个很好的安全策略。当莫名其妙的事情发生时,我们可以同时检查IP地址和解析的域名。

  使用IP地址作为安全策略对服务器管理提出了额外的要求。如果客户端更改其IP地址,系统管理员必须更新IP地址,尽管他的主机名和域名没有更改。

  使用IP地址安全策略时,系统管理员面临另一个挑战。也许他会遇到不知道自己IP地址的用户。不过,也许我们可以从他们那里得到主机名和域名信息。使用这些信息,我们可以使用nslookup(1)命令来确定该客户的IP地址,然后配置该地址。

  最后,一些客户可能会在每次启动机器时使用不同的IP地址,例如DHCP。他们的IP地址是由系统管理员确定的IP地址范围内的一个已更改的IP地址。在这种情况下,我们的最佳解决方案是联系他们的系统管理员,以获得更多关于他们的IP地址更改范围的内容的信息。在这些情况下,我们通常在网络ID级别限制访问。

  安全inetd服务器

  到目前为止,我们的讨论集中在我们想要实现安全策略的每个服务器中的定制代码上。这有许多缺点:

  管理安全策略的代码必须嵌入到提供给客户的每台服务器中。

  每台服务器都必须全程测试,以确认其准确性并防御攻击。

  多个接入点将暴露多个漏洞。

  前两点说明了一切。最后一点可以通过超市关门时锁很多门的问题来说明。当一些门必须上锁时,很容易忽略其中一扇门。另外,或许更有可能的是,他们中的一个人有一个弱点会被打破。由于这些原因,有必要将安全策略集中到一个模块中。

  集中式网络策略

  在前一章中,我们看到了inetd守护进程如何简化服务器的设计。inetd守护进程提供了监听客户机请求所需的所有服务器代码,并且只在必要时才启动服务器。inetd守护进程提供了另一种便利:它允许安装集中式网络安全模块。

  如果我们使用较新版本的Linux,比如RH6,我们已经有了一个可以被inetd调用的TCP包装器。要验证这一点,您可以使用grep查找telnetd实体,如下所示:

  # grep telnet /etc/inetd.conf

  telnet流TCP nowait root/usr/sbin/tcpd in . telnetd

  #

  Y

  从这个例子的输出中,我们可以看到,当一个telnet请求到达时,inetd调用可执行文件/usr/sbin/tcpd程序。如果我们同时使用grep检测ftp,会发现/usr/sbin/tcpd也会被这个程序调用。那么tcpd程序是做什么的呢?

  需要强调的是,tcpd程序在inetd和服务器(比如telnetd)之间插入自身。这是以透明的方式完成的,因为它不会在windows套接字上生成任何输入或输出。Tcpd程序简单地应用它的网络安全策略,如果它允许访问,它将调用相应的服务器。

  在前面的grep输入示例中,字符串in.telnet被用作tcpd的命令名(他的argv[0]的值)。这告诉tcpd如果允许访问,应该调用哪个服务器。如果由于某种原因访问被拒绝,这样的尝试将被记录,并且windows套接字将被关闭(当tcpd退出时)而不调用服务器。

  理解TCP包装概念

  下图向我们展示了tcpd程序的作用,它与inetd和最终服务器的交互。

  让我们回顾一下远程客户端连接到我们的in.telnetd服务器的过程:

  1客户端使用telnet client命令向我们机器的telnet守护进程发送一个连接请求。

  我们的Linux主机使用的是inetd,配置为23号telnet到端口监视器。他接受第一个连接请求。

  3 /etc/inetd.conf配置文件指示我们的inetd服务器派生一个新进程。并且父进程返回以继续监听连接请求。

  4步骤3中的子进程调用exec命令来执行/usr/sbin/tcpd TCP包装程序。

  5 tcpd程序将决定是否应该为客户端指定某个访问权限。这是由调用的windows套接字地址和/etc/hosts.deny和/etc/hosts.allow配置文件的组合决定的。

  6如果访问被拒绝,tcpd将简单地结束(这将关闭文件单元0、1和2,它们是windows套接字文件描述符)。

  7如果允许访问,要启动的可执行文件由tcpd的argv[0]值决定。在本例中,in.telnetd程序。指定的可执行文件路径名/usr/sbin/in.telnetd将被传递给exec函数进行加载和执行。

  8服务器现在在tcpd之前使用相同的进程ID运行,而不是tcpd。服务器现在正在windows套接字(文件单元0、1、2)中执行输入和输出操作。

  第七步非常重要,这是tcpd内部的exec函数启动服务器进程的地方。这维护了inetd和(子)服务器进程之间的重要父/子关系。当使用wait标志时,inetd守护程序会在检测到当前子进程结束时启动下一个服务器。只有当服务器是inetd的父进程的直接子进程时,这才会起作用。以下几点可能有助于我们理解:

  例如,在本例中,inetd守护进程的ID是124。

  2 inetd守护进程调用fork来启动子进程。在本例中,子流程ID现在是1243。

  3 inetd子进程(PID 1243)现在调用exec启动/usr/sbin/tcpd。

  4注意,tcpd现在运行的是PID 1243(我们可以回忆一下,exec使用相同的进程资源来启动一个新程序,而忽略了调用exec的原始程序)。

  5当允许访问时,tcpd实际上再次调用exec。这将启动新的服务器,在本例中为/usr /usr/sbin/in.telnetd

  6注意,server /usr/sbin/in.telnetd仍然是1243,因为exec没有创建新的进程。

  7服务器in.telnetd实际退出(PID 1243结束)。

  8父进程inetd(PID 1243)接收一个SIGCHLD信号,表示其子进程ID 1243已经结束。这导致inetd调用wait来确定哪个子进程完成了。

  从这几个步骤可以看出tcpd打包程序有多精致。这个程序永远不会在windows套接字上执行I/O操作——这将破坏正在使用的协议(telnet或其他)。

  确认访问

  现在我们可能还有两个问题:

  TCP wrapper如何确定它要保证的服务(telnet,ftp)?

  2他如何确定谁是客户?

  现在,我们将在下一节简要描述每个解决方案。

  确定服务

  Tcpd程序可以通过调用getsockname函数来确定它所保护的服务。还记得这个功能吗?他不仅返回客户端连接到的windows套接字地址,还指示服务的端口。在前面的示例中,端口号是23(telnet服务)。

  确认客户身份。

  因为tcpd程序不调用accept函数(由inetd完成),所以它必须确定客户是谁。也许正如我们猜测的那样,这是由getpeername函数完成的。我们可能还记得,这个函数获取远程客户端的地址和端口号,它的工作方式类似于getsockname。

  确定数据报客户端身份。

  识别数据报客户端有一些麻烦。敏锐的读者可能在前一章就想到了这个问题,因为数据报不使用accept函数。您也不能在数据报windows套接字上使用getpeername函数,因为第一个数据报实际上是由不同的客户端发送的。客户端地址由recvfrom函数调用返回。但是,tcpd程序可以在不实际读取服务器数据的情况下确定客户端ID吗?

  原来tcpd可以作弊。客户端地址和端口号可以通过使用MSG_PEEK标记选项的recvfrom函数调用来确定。如下面的示例代码所示:

  int z;

  结构sockaddr _ in adr _ clnt/* AF_INET */

  int len _ inet/*长度*/

  int s;/*套接字*/

  char dgram[512];/*接收缓冲区*/

  len _ inet=sizeof adr _ clnt

  z=recvfrom(s,/* Socket */

  dgram,/*接收缓冲器*/

  dgram的大小,/*最大接收缓冲区大小*/

  MSG_PEEK,/* Flags:查看数据*/

  (struct sockaddr *) adr_clnt,/* Addr */

  len _ inet);/*地址长度,输入输出*/

  这里我们需要注意MSG_PEEK标签选项。该选项将指导内核以与平常相似的方式执行recvfrom调用,只是不会从读队列中删除数据。如果允许访问,这将允许tcpd程序监视服务器接下来将读取的数据报。

  注意,数据本身在这里并不重要。MSG_PEEK所做的是,它将返回客户端的IP地址(在本例中,这将放在adr_clnt中)。包装器确定数据报是否应该由adr_clnt变量处理。

  到目前为止,我们已经了解了TCP封装概念背后的理论。下面,我们将看到这个想法以一个示例程序的具体形式来演示。

  包装和服务器程序

  本节将提供一个简单的数据报服务器和一个相应的TCP包装器。这个包装器实现了一个非常简单的安全策略。

  检测服务器和包装日志代码

  与服务器包装器共享一些日志功能。日志功能代码如下所示:

  /*

  * log.c

  *

  *日志记录功能:

  */

  #包含stdio.h

  #包含stdlib.h

  #包含字符串. h

  #包含stdarg.h

  #包括unistd.h

  #包含错误号h

  静态文件* logf=NULL/*日志文件*/

  /*

  *打开日志文件进行追加:

  *

  *退货:

  * 0成功

  * -1失败

  */

  int log_open(常量字符*路径名)

  {

  logf=fopen(路径名, a );

  返回logf?0 : -1;

  }

  /*

  *将信息记录到文件中:

  */

  void log(const char *格式,)

  {

  va _ list ap

  如果(!logf)

  返回;/*没有打开日志文件*/

  fprintf(logf,[PID %ld],(long)getpid());

  va_start(ap,格式);

  vfprintf(logf,format,AP);

  va _ end(AP);

  fflush(logf);

  }

  /*

  *关闭日志文件:

  */

  作废日志_关闭(作废)

  {

  如果(登录)

  fclose(logf);

  logf=NULL

  }

  /*

  *此函数将错误报告给

  *日志文件并调用exit(1)

  */

  无效保释(const char *on_what)

  {

  if(logf)/* log是否打开?*/

  {

  if(errno) /* Error?*/

  日志( %s:,strerror(errno));

  日志( %s/n ,on _ what);/*日志消息*/

  log _ close();

  }

  出口(1);

  }

  参考程序包含的文件代码如下:

  /*

  * log.h

  * log.c externs:

  */

  extern int log _ open(const char * pathname);

  extern void log(const char *格式,);

  外部无效log _ close(void);

  extern void ball(const char * on _ what);

  检测数据报服务器代码

  这里将演示一个程序,它将处理第一个数据报,然后轮询更多的数据报。如果在8秒内没有数据报到达,服务器将超时。inetd守护进程不会启动另一个服务器,直到它被通知服务器已完成(/etc/inetd.conf记录实体必须使用等待标识符)。

  以下是数据报服务程序使用的代码:

  /*

  * dgramisrvr.c

  *

  inetd数据报服务器示例:

  */

  #包含stdio.h

  #包含stdlib.h

  #包括unistd.h

  #包含stdarg.h

  #包含错误号h

  #包含字符串. h

  #包含sys/types.h

  #包含时间. h

  #包含sys/socket.h

  #包含netinet/in.h

  #包括arpa/inet.h

  #包含 log.h

  # define log path /tmp/dgramisrvr . log

  int main(int argc,char **argv)

  {

  int z;

  int s;/*套接字*/

  int alen/*地址长度*/

  结构sockaddr _ in adr _ clnt/*客户端*/

  char dgram[512];/*接收缓冲器*/

  char dt fmt[512];/*日期/时间结果*/

  时间_ t td/*当前时间和日期*/

  struct tm dtv/*日期时间值*/

  fd _ set rx _ set/*输入请求. set */

  结构timeval tmout/*超时值*/

  /*

  *打开一个日志文件进行追加:

  */

  if(log_open(LOGPATH)==-1)

  出口(1);/*没有日志文件!*/

  日志( dgramisrvr已启动. n’);

  /*

  *其他初始化:

  */

  s=0;/*我们的插座在标准输入端*/

  FD _ ZERO(rx _ set);/*初始化*/

  FD_SET(s,rx _ SET);/*通知fd=0 */

  /*

  *现在等待传入的数据报:

  */

  for(;)

  {

  /*

  *博客,直到数据报到达:

  */

  alen=sizeof adr _ clnt

  z=recvfrom(s,/* Socket */

  dgram,/*接收缓冲器*/

  dgram的大小,/*最大接收大小*/

  (struct sockaddr *) adr_clnt,

  alen);/*地址长度,输入输出*/

  如果(z 0)

  保释( recv from(2));

  dgram[z]=0;/*空终止dgram */

  /*

  *记录请求:

  */

  日志(从%s端口%d/n获得请求“%s”,

  dgram,

  inet_ntoa(adr_clnt.sin_addr),

  ntohs(ADR _ clnt。sin _ port));

  /*

  *获取当前日期和时间:

  */

  时间(TD);/*当前时间日期*/

  DTV=*当地时间(TD);

  /*

  *格式化新的日期和时间字符串,

  *基于输入格式字符串:

  */

  strftime(dtfmt,/*格式化结果*/

  dtfmt的大小,/*最大尺寸*/

  dgram,/*日期/时间格式*/

  DTV);/*输入值*/

  /*

  *将格式化的结果发送回

  *客户端程序:

  */

  z=sendto(s,/* Socket */

  dtfmt,/*数据报结果*/

  strlen(dtfmt),/* length */

  0,/*标志:没有选项*/

  (struct sockaddr *) adr_clnt,

  alen);

  如果(z 0)

  保释(发送到(2));

  /*

  *等待下一个数据包或超时:

  *

  *这很容易通过

  *使用选择(2).

  */

  做

  {

  /*建立超时=8.0秒*/

  TM out了。TV _ sec=8;/* 8秒*/

  TM out了。TV _ usec=0;/* 0 usec */

  /*等待读取事件或超时*/

  z=select(s 1,rx_set,NULL,NULL,TM out);

  } while(z==-1 errno==ENITR);

  /*

  *如果选择(2)返回错误,则退出

  *或者如果超时:

  */

  如果(z=0)

  打破;

  }

  /*

  *关闭插座并退出:

  */

  如果(z==-1)

  log(%s:select(2) /n ,strerror(errno));

  其他

  日志(超时:服务器正在退出. n’);

  关闭;

  log _ close();

  返回0;

  }

  服务器代码只是简单的组织为一个主()程序。

  服务器代码显示了一个用户数据报协议(用户数据报协议)服务器如何轮询,并且读取更多的数据报直到发生超时。还有其他的方法来实现超时,但是这也许是其中最简单的一个了。

  检测简单的三氯苯酚包装程序

  现在到了介绍我们将会用到的简单的三氯苯酚包装程序的源代码了。其程序代码如下:

  /*

  *包装器。c

  *

  *简单包装示例:

  */

  #包含标准视频

  #包括unistd.h

  #包含标准库

  #包含错误号h

  #包含字符串。h

  #包含sys/types.h

  #包含sys/socket.h

  #包含netinet/in.h

  #包括arpa/inet.h

  #包含log.h

  #定义日志路径/tmp/wrapper。“日志”

  int main(int argc,char **argv,char **envp)

  {

  int z;

  结构sockaddr _ in adr _ clnt/*客户端*/

  int alen/*地址长度*/

  char dgram[512];/*接收缓冲器*/

  char * str _ addr/*地址的字符串形式*/

  /*

  *我们必须记录被拒绝的尝试:

  */

  if(log_open(LOGPATH)==-1)

  出口(1);/*无法打开日志文件!*/

  日志(包装程序已启动. n’);

  /*

  *使用消息_偷看查看数据报:

  */

  alen=sizeof adr _ clnt/*长度*/

  z=recvfrom(0,/*标准输入上的插座*/

  dgram,/*接收缓冲器*/

  dgram的大小,/*最大接收大小*/

  MSG_PEEK,/* Flags:Peek */

  (struct sockaddr *) adr_clnt,

  alen);/*地址长度,输入输出*/

  如果(z 0)

  保释(从(2),偷看客户

  地址。);

  /*

  *将互联网协议(Internet Protocol)地址转换为字符串形式:

  */

  str _ addr=inet _ ntoa(ADR _ clnt。sin _ addr);

  if(strcmp(str_addr, 127.7.7.7 )!=0)

  {

  /*

  *不是我们的特殊地址127.7.7.7:

  */

  日志(地址%s端口%d被拒绝 n ,

  str_addr,nto hs(ADR _ clnt。sin _ port));

  /*

  *我们现在必须阅读这个包

  *放弃数据报的消息_偷看选项:

  */

  z=recvfrom(0,/* Socket */

  dgram,/*接收缓冲器*/

  数据报的尺寸,/*最大化学和容积控制系统尺寸*/

  0,/*没有标志*/

  (struct sockaddr *) adr_clnt,

  alen);

  如果(z 0)

  保释( recvfrom(2)、吃dgram’);

  出口(1);

  }

  /*

  *接受此数据报请求,以及

  *启动服务器:

  */

  日志(地址%s端口%d已被接受 n ,

  str_addr,nto hs(ADR _ clnt。sin _ port));

  /*

  * inetd已从提供了argv[0]

  *配置文件/etc/inetd.conf:我们有

  *用这个来表示服务器的

  *此示例的完整路径名我们

  *只需传递任何其他参数和

  *环境保持原样。

  */

  log(Starting %s/n ,argv[0]);

  log _ close();/*不再需要这个*/

  z=execve(argv[0],argv,envp);

  /*

  *如果控制返回,则执行(2)

  *由于某种原因失败:

  */

  log _ open(日志路径);/* Re日志*/

  保释(‘exec ve),启动服务器);

  返回1;

  }

  上面所提供的包装程序代码实现了下面的一些简单的安全策略:

  如果客户端为127.7.7.7,他的请求可以到达数据报服务器。并没有在客户端的端口上进行限制。

  如果客户端地址是其他的互联网协议(互联网协议)地址,请求被拒绝并记入日志。

  在这里需要注意的一点是,数据报包装程序并不会使用getpeername函数来确定数据报地址。相反,他必须使用消息_偷看标记位来调用接收函数函数。消息_偷看标记位返回客户端地址并存入地址结构adr_clnt中,而并不会真正的由这个套接口的输入队列中移除数据报。

  简介客户端程序

  在这里提供了一个以前的客户端程序的修改版本。这个客户端参数需要两个命令行参数:服务器地址以及要使用的客户端地址。这个额外客户端互联网协议(互联网协议)地址的指定可以允许我们使用我们的三氯苯酚包装器程序执行更多的试验。修改后的数据报客户端程序如下:

  /*

  * dgramcln2.c:

  *

  *修改后的数据报客户端:

  */

  #包含标准视频

  #包含标准库

  #包括unistd.h

  #包含错误号h

  #包含字符串。h

  #包含时间。h

  #包含sys/types.h

  #包含sys/socket.h

  #包含netinet/in.h

  #包括arpa/inet.h

  /*

  *此函数报告错误并

  *出口回到外壳:

  */

  静态无效保释(char *on_what)

  {

  如果(错误号)

  {

  fputs(strerror(errno),stderr);

  fputs(:,stderr);

  }

  fputs(on_what,stderr);

  fputc(/n ,stderr);

  出口(1);

  }

  int main(int argc,char **argv)

  {

  int z;

  char * Srvr _ addr=NULL/* Srvr addr */

  char * clnt _ addr=NULL/* Clnt地址*/

  结构sockaddr _ in adr _ srvr/*服务器*/

  结构sockaddr _ in adr _ clnt/*客户端*/

  美国预托证券中的struct sockaddr _ in/* AF_INET */

  int alen/*套接字地址长度*/

  int s;/*套接字*/

  char dgram[512];/*接收缓冲区*/

  /*

  *坚持两个命令行参数

  *(无端口号):

  *

  * dgramcln2服务器地址客户端地址

  */

  如果(argc!=3)

  {

  fputs(用法:dgramclnt server_ipaddr

  client_ipaddr /n ,stderr);

  返回1;

  }

  srvr _ addr=argv[1];/*第一个参数是srv */

  clnt _ addr=argv[2];/*第二个参数是clnt */

  /*

  *创建服务器套接字地址:

  */

  memset( adr_srvr,0,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==in addr _ NONE)

  保释(’错误的服务器地址。);

  /*

  *创建用户数据报协议(User Datagram Protocol)套接字:

  */

  s=socket(AF_INET,SOCK_DGRAM,0);

  如果(s==-1)

  bail( socket());

  /*

  *创建特定的客户端地址:

  */

  memset( adr_clnt,0,adr_clnt的大小);

  adr _ clnt.sin _ family=AF _ INET

  ADR _ clnt。sin _ port=0;/*任何端口*/

  ADR _ clnt。sin _ addr。s _ addr=inet _ addr(clnt _ addr);

  if(ADR _ clnt。sin _ addr。s _ addr==in addr _ NONE)

  保释("错误的客户地址。");

  /*

  *绑定具体的客户端地址:

  */

  z=bind(s,(struct sockaddr *) adr_clnt,sizeof ADR _ clnt);

  如果(z==-1)

  保释("客户地址的绑定(2)");

  /*

  *进入输入客户端循环:

  */

  for(;)

  {

  /*

  *提示用户输入日期格式字符串:

  */

  FP uts(/n输入格式字符串: ,stdout);

  如果(!fgets(dgram,sizeof dgram,stdin))

  打破;/* EOF */

  z=strlen(dgram);

  if(z 0 dgram[ - z]==/n )

  dgram[z]=0;/*踩出新行*/

  /*

  *向服务器发送格式字符串:

  */

  z=sendto(s,/* Socket */

  dgram,/*要发送的数据报*/

  strlen(dgram),/* dgram长度*/

  0,/*标志:没有选项*/

  (struct sockaddr *) adr_srvr,

  sizeof ADR _ srvr);

  如果(z 0)

  保释( send to(2));

  /*

  *等待响应:

  *

  *注意:如果

  *包装器确定我们缺少访问权限(否

  *响应将到达)。

  */

  alen=sizeof adr

  z=recvfrom(s,/* Socket */

  dgram,/*接收缓冲器*/

  sizeof dgram,/*最大接收大小*/

  0,/*标记无选项*/

  (struct sockaddr *) adr,

  alen);/*地址长度,输入输出*/

  如果(z 0)

  保释( recfrom(2));

  dgram[z]=0;/*空终止*/

  /*

  *报告结果:

  */

  printf(来自%s端口%u的结果:

  /n/t%s/n ,

  inet_ntoa(adr.sin_addr),

  (无符号)ntohs(adr.sin_port),

  dgram);

  }

  /*

  *关闭插座并退出:

  */

  关闭;

  putchar(/n );

  返回0;

  }

  注意,如果包装程序认为它不能访问服务器,dgramcln2程序将被挂起。这是因为recvfrom函数没有从服务器获得答案。当这种情况发生时,我们需要中断我们的程序。

  安装并测试包装器。

  现在我们可以检查我们需要的所有代码。要开始我们的包装实验,首先我们需要这些代码:

  市场管理局

  $ make

  gcc-c-D _ GNU _ SOURCE-Wall-Wreturn-type dgramisrvr . c

  gcc-c-D _ GNU _ SOURCE-Wall-w return-type log . c

  gcc dgramisrvr . o log . o-o dgramisrvr

  gcc-c-D _ GNU _ SOURCE-Wall-Wreturn-type包装器

  gcc包装器. o log.o -o包装器

  大调音阶的第7音

  gcc-c-D _ GNU _ SOURCE-Wall-w return-type dgramcln 2 . c

  gcc dgramcln2.o -o dgramcln2

  $

  通常我们需要root权限来安装服务器。但是,对于简单的测试,我们可以在没有root权限的情况下做这个实验。要在/tmp目录中创建我们的文件,我们可以使用以下命令:

  $进行安装

  RM-f/tmp/wrapper . log/tmp/dgramisrvr . log

  RM-f/tmp/inetd . conf/tmp/wrapper/tmp/dgramisrvr

  cp dgramisrvr包装器/tmp/。

  chmod 500/tmp/wrapper/tmp/dgramisrvr

  chmod 600 /tmp/inetd.conf

  /usr/sbin/inetd /tmp/inetd.conf

  make命令将一系列文件放在/tmp目录中,包括一个简单的/tmp/inetd.conf文件。其内容如下:

  $ cat /tmp/inetd.conf

  9090 dgram udp等待学生1 /tmp/wrapper /tmp/dgramisrvr

  $

  也许我们想检查其他文件是否正确安装在/tmp目录中:

  $ ls -Itr /tmp tail

  -rw-r-1 STD nt1 class 1 29454 2月19日22:59 xprnKg3RSc

  -rw-r-r-1 root root 11 Feb 21 23:18 1pq . 0002621 c

  -r-x-1 STD nt1 class 1 14202 Feb 22 22:48包装

  2月22日22:48 inetd.conf

  -r-x-1 STD nt1 class 1 15237 2月22日22:48 dgramisrvr

  $

  从输出中,我们可以看到TCP包装器已经安装,dgramisrvr可执行文件也安装在/tmp目录中。

  监控日志文件

  如果我们使用X Window会话,建议使用以下命令启动终端会话:

  $ /tmp/wrapper.log

  $ tail -f /tmp/wrapper.log

  这将创建并监视包装日志文件。我们将看到,当包装程序将记录写入日志文件时,窗口将被更新。

  在新启动的窗口中,执行以下命令:

  $ /tmp/dgraimsrvr.log

  $ tail -f /tmp/dgramisrvr.log

  这将创建并监控服务器日志文件。

  如果不使用X Window直播,可以使用多个终端会话达到同样的目的。

  启动我们的inetd守护进程。

  在启动客户端之前,我们必须准备好inetd守护进程。这个守护进程将在我们自己的用户ID下运行,没有任何特殊的root权限。但是,我们必须小心地为命令行指定正确的配置文件,如下所示:

  $/usr/sbin/inetd/tmp/inetd . conf

  $

  该命令行参数将告诉我们的inetd守护程序使用我们提供的/tmp/inetd.conf配置文件,而不是/etc/inetd.conf。该程序将自动在后台运行,我们可以使用以下命令检查它:

  $ ps -ef grep inetd

  根313 1 0 Feb15?00:00:00 inetd

  学生1 12763 1 0 23:04?00:00:00/usr/sbin/inetd/tmp/inetd . conf

  学生1 12765 11739 0 23:08 pts/3 00:00:00 grep inetd

  $

  测试包装

  监视完日志后,现在我们可以启动客户机并试验一些内容。首先,我们测试包装程序的一些可接受的内容:

  $ ./dgramcln2

  输入格式字符串:%A %B %D

  来自127.7.7.7端口9090的结果:

  1999年11月11日星期二

  输入格式字符串:

  这将启动客户端程序,并将windows套接字中的客户端绑定到包装程序可接受的IP地址127.7.7.7。包装日志如下所示:

  $ tail -f /tmp/wrapper.log

  [PID 1279]包装程序已启动。

  [PID 1279]地址127.7.7.7端口1027被接受。

  [PID 1279]开始/tmp/dgramisrvr

  表示日志进程ID为1279,而请求来自127.7.7.7,端口号为1027。因为请求是可接受的,所以服务器/tmp/dgramisrvr执行该请求。

  服务器的日志输出类似于以下内容:

  $ tail -f /tmp/dgramisrvr.log

  [PID 1279] dgramisrvr已启动。

  [PID 1279]从127.7.7.7端口1027获得请求“%A %B %D”

  [PID 1279]超时:服务器正在退出。

  注意,服务器的进程ID与包装器的ID相同(包装器进程由服务器使用execve启动)。日志表明服务器启动并处理了请求。的最后一条记录表明服务器超时。

  拒绝请求

  关闭我们的客户端程序,并使用新地址启动它,如下所示:

  $ ./dgramcln2

  输入格式字符串:%D (%B %A)

  我们将会看到我们的客户端程序并不会有任何响应。他会挂起,因为wrapper程序拒绝这个请求到在服务器。我们可以中断退出。

  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

  [PID 1289] wrapper started.

  [PID 1289] Address 127.13.13.13 port 1027 rejected.

  我们将会看到下一个数据报请求是由一个新的wrapper进程ID 1289来处理的。最后一行日志记录表明地址127.13.13.13被拒绝。客户端程序挂起,因为wrapper程序丢弃了数据报,从而阻止其被服务器处理。然后wrapper程序退出。

  测试服务器超时

  要测试服务器的轮询能力,我们必须快速的输入两个日期格式请求(在8秒以内)。如下面的一个会话例子:

  $ ./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

  $

  如果我们足够快,服务器可以在单一的服务器进程内同时处理这两个请求。要检测是否如此,我们可以查看服务器日志:

  $ 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.

  最后四行日志记录表明服务器进程ID 1294 可以在超时之前处理两个数据请求。

  御载演示程序

  要御载演示程序,可以执行下面的命令:

  $ 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目标会移除/tmp目录下的所有文件,并且试着显示我们inetd守护进程的ID。在所显示的例子输出中,守护进程的ID为12763。这可以由kill命令来结束:

  $ kill 12763

  $

  数据报弱点

  在我们为数据报服务器所设计的包装器中有一个弱点。我们是否看出了这个问题呢?提示:他与服务器轮询有关。

  正 如在前面所显示的数据报服务器的轮询有一个可以使用包装器概念进行攻击的弱点。当没有服务器进程运行时,包装器程序总是可以在服务器读取数据报之前进行筛 选。然而,如果服务器等待更多的数据报,并且只在超时时退出,包装器程序就会被用来筛选这些额外的数据报。这个过程可以总结如下:

  1 一个数据报到达,通知。

  2 inetd守护进程启动wrapper程序。

  3 wrapper程序允许这个数据报,调用exec来启动数据报服务器。

  4 数据报服务顺读取并处理数据报。

  5 服务器等待其他的数据报。

  6 如果一个数据报到达,重复4。

  7 否则,服务器超时并退出。

  8 重复1。

  当服务器继续运行时,进程重复4。这就在步骤3留下了安全检测。如果我们足够快,我们就可以使用前面所提供的例子程序进行演示。

  为了更安全,我们可以有下面的一些选择:

  使用nowait风格的数据报服务器(这些服务器处理一个数据并退出)。这会强制包装器程序仔细检查所有的请求。

  在我们的数据报服务器中使用自定义的代码在接受处理之前测试每一个数据报。

  一个折中的办法就是缩短超时间隔,但是这还会留下许多漏洞。

  因 为这些原因,如果一个同样服务的TCP版本存在,许多站点就会选择禁止数据报服务。如果必须提供数据报服务,安全站点并不会允许他们的数据报服务器循环。 这要求改变服务器源代码,或是编写一个自定义的服务器程序。相对应的,服务器程序本身会检测每一个客户端请求的访问权限,而不依赖于TCP包装器的概念。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: