如何在连接到 Cisco VPN 时允许本地 LAN 访问?

如何在连接到 Cisco VPN 时允许本地 LAN 访问?

连接到 Cisco VPN 时如何维持本地 LAN 访问?

当使用 Cisco VPN 连接时,服务器能够指示客户端阻止本地 LAN 访问。

假设无法关闭此服务器端选项,那么如何在连接 Cisco VPN 客户端时允许本地 LAN 访问?


我曾经认为这只是添加路由以更高的指标捕获 LAN 流量的问题,例如:

  Network 
Destination      Netmask        Gateway       Interface  Metric
   10.0.0.0  255.255.0.0       10.0.0.3        10.0.0.3      20  <--Local LAN
   10.0.0.0  255.255.0.0  192.168.199.1  192.168.199.12       1  <--VPN Link

尝试删除该10.0.x.x -> 192.168.199.12路线没有任何效果:

>route delete 10.0.0.0
>route delete 10.0.0.0 mask 255.255.0.0
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1 if 192.168.199.12
>route delete 10.0.0.0 mask 255.255.0.0 192.168.199.1 if 0x3

尽管它可能仍然只是一个路由问题,但添加或删除路由的尝试会失败。

思科 VPN 客户端驱动程序在网络堆栈中的哪个级别上执行什么操作以覆盖本地管理员管理其机器的能力?

Cisco VPN 客户端不可能使用魔法。它仍然是我电脑上运行的软件。它使用什么机制来干扰我机器的网络?当 IP/ICMP 数据包到达网络时会发生什么?网络堆栈中的哪个位置会吞噬数据包?

也可以看看


编辑:我还没尝试过的事情:

>route delete 10.0.*

更新:由于思科已经放弃了他们的旧客户端,转而采用 AnyConnect(基于 HTTP SSL 的 VPN),这个尚未解决的问题只能作为历史遗留问题了。

接下来,我们可以尝试解决他们的新客户也遇到了同样的问题

答案1

Anyconnect 的问题在于,它首先修改路由表,然后照看它并在您手动修改时修复它。我找到了一种解决方法。适用于版本 3.1.00495、3.1.05152、3.1.05170,以及 3.1 系列中的其他任何版本。可能适用于其他版本,至少类似的想法应该可行,前提是代码不会被重写。幸运的是,思科已将保姆“婴儿醒了”调用放入共享库中。因此,我们的想法是通过 LD_PRELOAD 阻止 vpnagentd 的操作。

  1. 首先我们创建一个文件hack.c

    #include <sys/socket.h>
    #include <linux/netlink.h>
    
    int _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv()
    {
      int fd=50;          // max fd to try
      char buf[8192];
      struct sockaddr_nl sa;
      socklen_t len = sizeof(sa);
    
      while (fd) {
         if (!getsockname(fd, (struct sockaddr *)&sa, &len)) {
            if (sa.nl_family == AF_NETLINK) {
               ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
            }
         }
         fd--;
      }
      return 0;
    }
    

注意:此代码仅适用于 Linux。要将此解决方案应用于 macOS 计算机,请参阅macOS 适配版本

  1. 然后像这样编译它:

    gcc -o libhack.so -shared -fPIC hack.c
    
  2. 安装libhack.so到Cisco库路径:

    sudo cp libhack.so  /opt/cisco/anyconnect/lib/
    
  3. 关闭代理:

    /etc/init.d/vpnagentd stop
    
  4. 确保它确实处于关闭状态

    ps auxw | grep vpnagentd
    

    如果没有,kill -9只是为了确定一下。

  5. 如果有/etc/init.d/vpnagentd,则通过添加调用底层可执行文件的位置来修复它,LD_PRELOAD=/opt/cisco/anyconnect/lib/libhack.so 使其看起来像这样:

    LD_PRELOAD=/opt/cisco/anyconnect/lib/libhack.so /opt/cisco/anyconnect/bin/vpnagentd
    

    更现代的 AnyConnect 安装不再使用/etc/init.d/vpnagentd/lib/systemd/system/vpnagentd.service在这种情况下,您会需要类似以下内容:

    sudo mv /opt/cisco/anyconnect/bin/vpnagentd /opt/cisco/anyconnect/bin/vpnagentd.orig &&
    { echo '#!/bin/bash' &&
      echo "LD_PRELOAD=$HOME/vpn/libhack.so exec /opt/cisco/anyconnect/bin/vpnagentd.orig"
    } | sudo tee /opt/cisco/anyconnect/bin/vpnagentd &&
    sudo chmod +x /opt/cisco/anyconnect/bin/vpnagentd
    
  6. 现在启动代理:

    /etc/init.d/vpnagentd start
    
  7. 修复 iptables,因为 AnyConnect 会干扰它们:

    iptables-save | grep -v DROP | iptables-restore
    

    您可能希望在这里做一些更高级的事情,以仅允许访问某些 LAN 主机。

  8. 现在按照您的需要修复路线,例如:

    route add -net 192.168.1.0 netmask 255.255.255.0 dev wlan0
    
  9. 检查它们是否真的存在:

    路线-n

此 hack 的先前简化版本提供了一个仅执行“返回 0;”的函数 - 该发帖者指出“到目前为止,我观察到的唯一副作用是 vpnagentd 使用了 100% 的 CPU(如 top 报告所示),但总体 CPU 只有 3% 的用户和 20% 的系统,并且系统响应完美。我对其进行了跟踪,它似乎在空闲时循环执行两个选择,并快速从两个选择中返回,但它从未读取或写入 - 我想我使用 LD_PRELOAD 剪切出的调用应该是读取。可能有一种更简洁的方法可以做到这一点,但到目前为止对我来说已经足够好了。如果有人有更好的解决方案,请分享。”

这个简单黑客的问题是它导致单个 CPU 核心一直处于 100% 状态,从而有效地将硬件 CPU 线程数减少一个 - 无论您的 vpn 连接是否处于活动状态。我注意到代码正在执行的选择是在 netlink 套接字上,当路由表更改时,它会发送 vpnagentd 数据。vpnagentd 不断注意到 netlink 套接字上有一条新消息并调用 routeCallBackHandler 来处理它,但由于这个简单的黑客不会清除新消息,它只会不断被调用。上面提供的新代码会刷新 netlink 数据,因此导致 100% CPU 的无限循环不会发生。

如果某些操作不起作用,请gdb -p $(pidof vpnagentd)在连接后执行以下操作:

b socket
c
bt

并查看您处于哪个调用中。然后只需猜测您要剪掉哪一个,将其添加到hack.c并重新编译。

答案2

这非常复杂,但如果您使用 VMWare Player 或类似程序创建一个最小 VM,并在其中运行 Cisco AnyConnect VPN 客户端,则可能可以使用 VMWare 虚拟网络适配器根据需要设置路由,或者只是使用 VM 通过 Cisco SSL VPN 访问所需的任何资源并将文件“拖放”到/从您的实际机器。

答案3

对于那些希望在使用 Cisco AnyConnect SSL VPN 时保持对路由表控制的用户,请查看OpenConnect。它既支持 Cisco AnyConnect SSL VPN,又不会试图破坏或“保护”路由表条目。@Vadzim提到了这一点以上评论

在尝试了所有方法(除了修补 AnyConnect 安全移动客户端)后,我成功地在 Windows 上用以下方法替换了它:OpenConnect 图形用户界面。这使我能够保持与本地资源的连接(并更新路由表)。

我在 Windows 上使用 OpenConnect,但它也支持 Linux、BSD 和 macOS(以及其他平台)根据项目页面

答案4

谢谢萨沙·帕切夫对于上面的精彩破解。

vpnagentd还会通过覆盖对 所做的更改来干扰解析器/etc/resolv.conf。我最终通过赢得与它的竞争解决了这个问题:

#!/bin/bash

dnsfix() {
    [ -f /etc/resolv.conf.vpnbackup ] || echo "Not connected?" >&2 || return 0 # do nothing in case of failure
    while ! diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup #>/dev/null
    do
         cat /etc/resolv.conf.vpnbackup >/etc/resolv.conf
    done
    chattr +i /etc/resolv.conf
    diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null 
}

while ! dnsfix
do
    echo "Retrying..."
    chattr -i /etc/resolv.conf
done

chattr -i /etc/resolv.conf断开连接时不要忘记。

我正在尝试通过拦截回调来解决它,就像上面的路由方法一样,但还找不到相应的回调或方法。

更新 1/2:Astrace透露vpnagentd正在使用inotifyAPI 来监控解析器文件更改。从那时起,事情就变得糟糕了。以下是额外的 hack:

int _ZN18CFileSystemWatcher11AddNewWatchESsj(void *string, unsigned int integer)
{
  return 0;
}

当然,这有点过头了,因为它会禁用全部代理的文件监视。但似乎工作正常。

下面的 vpn 客户端包装器脚本集成了所有功能(已更新以包含此附加黑客功能)。chattr不再使用/需要。

更新 3:修复了脚本中的用户名/密码设置。它现在使用vpn.conf具有下述格式的文件(以及仅限 root 的权限)。

#!/bin/bash

# Change this as needed
CONF="/etc/vpnc/vpn.conf"
# vpn.conf format
#gateway <IP>
#username <username>
#password <password>
#delete_routes <"route spec"...> eg. "default gw 0.0.0.0 dev cscotun0"
#add_routes <"route spec"...> eg. "-net 192.168.10.0 netmask 255.255.255.0 dev cscotun0" "-host 10.10.10.1 dev cscotun0"

ANYCONNECT="/opt/cisco/anyconnect"

usage() {
    echo "Usage: $0 {connect|disconnect|state|stats|hack}"
    exit 1
}

CMD="$1"
[ -z "$CMD" ] && usage

ID=`id -u`

VPNC="$ANYCONNECT/bin/vpn"

dnsfix() {
    [ -f /etc/resolv.conf.vpnbackup ] || echo "Not connected?" >&2 || return 0 # do nothing in case of failure
    while ! diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null
    do
         cat /etc/resolv.conf.vpnbackup >/etc/resolv.conf
    done
#    chattr +i /etc/resolv.conf
    diff -q /etc/resolv.conf /etc/resolv.conf.vpnbackup >/dev/null 
}

case "$CMD" in
    "connect")
        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        HOST=`grep ^gateway $CONF | awk '{print $2}'`
        USER=`grep ^user $CONF | awk '{print $2}'`
        PASS=`grep ^password $CONF | awk '{print $2}'`
        OLDIFS=$IFS
        IFS='"'
        DEL_ROUTES=(`sed -n '/^delete_routes/{s/delete_routes[ \t\"]*//;s/\"[ \t]*\"/\"/g;p}' $CONF`)
        ADD_ROUTES=(`sed -n '/^add_routes/{s/add_routes[ \t\"]*//;s/\"[ \t]*\"/\"/g;p}' $CONF`)
        IFS=$OLDIFS

        /usr/bin/expect <<EOF
set vpn_client "$VPNC";
set ip "$HOST";
set user "$USER";
set pass "$PASS";
set timeout 5
spawn \$vpn_client connect \$ip
match_max 100000
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    ">> The VPN client is not connected." { exit 0};
    ">> state: Disconnecting" { exit 0};
    "Connect Anyway?"
}
sleep .1
send -- "y\r"
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    "Username:"
}
sleep .1
send -- "\$user\r"
expect { 
    timeout {
        puts "timeout error\n"
        spawn killall \$vpn_client
        exit 1
    }
    "Password: "
}
send -- "\$pass\r";
expect eof
EOF
        sleep 2
        # iptables
        iptables-save | grep -v DROP | iptables-restore

        # routes
        for ROUTE in "${DEL_ROUTES[@]}"
        do
#            echo route del $ROUTE
            route del $ROUTE
        done
        for ROUTE in "${ADD_ROUTES[@]}"
        do
#            echo route add $ROUTE
            route add $ROUTE
        done

        # dns
        while ! dnsfix
        do
            echo "Try again..."
#            chattr -i /etc/resolv.conf
        done

        echo "done."
        ;;
    "disconnect")
#        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        # dns
#        chattr -i /etc/resolv.conf

        $VPNC disconnect
        ;;
    "state"|"stats")
        $VPNC $CMD
        ;;
    "hack")
        [ $ID -ne 0 ] && echo "Needs root." && exit 1
        /etc/init.d/vpnagentd stop
        sleep 1
        killall -9 vpnagentd 2>/dev/null
        cat - >/tmp/hack.c <<EOF
#include <sys/socket.h>
#include <linux/netlink.h>

int _ZN27CInterfaceRouteMonitorLinux20routeCallbackHandlerEv()
{
  int fd=50;          // max fd to try
  char buf[8192];
  struct sockaddr_nl sa;
  socklen_t len = sizeof(sa);

  while (fd) {
     if (!getsockname(fd, (struct sockaddr *)&sa, &len)) {
        if (sa.nl_family == AF_NETLINK) {
           ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
        }
     }
     fd--;
  }
  return 0;
}

int _ZN18CFileSystemWatcher11AddNewWatchESsj(void *string, unsigned int integer)
{
  return 0;
}
EOF
        gcc -o /tmp/libhack.so -shared -fPIC /tmp/hack.c
        mv /tmp/libhack.so $ANYCONNECT
        sed -i "s+^\([ \t]*\)$ANYCONNECT/bin/vpnagentd+\1LD_PRELOAD=$ANYCONNECT/lib/libhack.so $ANYCONNECT/bin/vpnagentd+" /etc/init.d/vpnagentd
        rm -f /tmp/hack.c
        /etc/init.d/vpnagentd start
        echo "done."
        ;;
    *)
        usage
        ;;
esac

相关内容