前言

首先说明一下,博主本人是不玩 EmbyServer 的,更多的是之前围绕 115、123云盘做一些302技术探究的时候涉略过,说起来也是一年前的事情了。目前家里的主力影音媒体服务用的是 Plex,对当前最新的 Emby 周边生态了解有限,所以,如果内容上面有什么不符或者出入的地方,还请大家多多担待,欢迎批评指正。

本篇更多的是和大家一起探讨下如何从技术角度避免类似于 Token 泄漏事情的发生,相关的知识也可以用在其他类似的 Web 服务搭建上,内容大多基于一些个人主观看法和经验,如有纰漏还请海涵。

如果你只想了解 EmbyServer Token 泄漏的相关解决方案,可以直接看「安全 EmbyServer Token 方案」。如果你想顺便了解下我的服务器日常维护和 Docker 的使用技巧,可以继续往下阅读。

声明: 本人不提供任何 有偿/无偿的 EmbyServer 搭建服务的技术指导

从 0day 说起

0day: 特指已被 "攻" 方掌握,但是还没有被厂家发现和修复的漏洞。

知己知彼,百战不殆,我们先从攻方的角度出发,聊一聊当一个 0day 被发现的时候,攻击方如何利用 0day 去实施侵入。现在假设我们手里已经掌握了一个 EmbyServer 存在某个路径下任意代码执行的高危漏洞,我们该如何利用这个 0day 去大面积的侵入 EmbyServer 呢?

首先也是最重要的,就是选定目标,也就是寻找那些安装了 EmbyServer 并且可以外部访问的服务器,这主要为两种情况:

  • 针对特定目标

    我知道了你的服务器上面装了 EmbyServer 且服务器 IP 和服务端口我都知道(比如我就是你的用户或单纯就是搞你)。这种情况下,从避免被选为目标的角度来言,防方是没有办法做什么的。

  • 针对不特定目标

    我只是恰好发现了一个 0day,我谁也不针对,像大海捞针一样找,找到一个装了 EmbyServer 的服务器,我就 "攻" 一个。这种情况下,防方是可以通过一系列配置,来减少被选中为目标的可能性。

「针对不特定目标」的场景下,我们先了解寻找服务是否在某台服务器上的一般作法

  • 端口扫描

    最简单,也是最常用的手段,比如 EmbyServer ,默认端口是 8096。我们可以直接扫描某个 IP 段,寻找所有开放了 TCP/8096 端口的主机,大概率都是架设了 EmbyServer 的主机,然后可以用我们的 0day 一一尝试侵入。

  • 特征扫描

    特征扫描常见的一般又分为两种

    • 路由扫描: 这种有建站经历的朋友应该比较熟悉,经常可以在后台看到一些 404 请求,访问一些不存在的路由地址。这种一般就是有人在测试某个路由是否存在,比如说我发现的 EmbyServer 任意代码执行的漏洞发生在 /user/avatar/upload/xxxxx/,我也不管这个服务器有没有搭建 EmbyServer,我直接挨个网站去请求这个地址进行利用,能命中的就成功拿下,没有命中就换下一个。或者是访问某个 Emby 特有的路由,确定 EmbyServer 是否搭建。

    • 结合搜索引擎进行搜索: 可能有些朋友不太了解,我们常用的搜索引擎比如 Google、Baidu 等都是支持高级语法搜索的。比如对于 Google来说,site: xxxx 可以实现特定网站内的搜索,filetype: xxx 可以查找特定的文件类型, intitle/inurl: xxxx 可以搜索包含某些关键字的标题或网址。我们可以找到 EmbyServer 的某些特征(比如我们发现了只要搭建了EmbyServer 一定会存在某个页面,含有: Emby xxx的字样),我们就可以结合搜索引擎进行搜索过滤,也能得到一批搭建了 EmbyServer 的主机列表。

了解了一般扫描器的运行原理,我们就可以作出一些针对性的措施,来避免主机服务被探测到。

针对端口扫描: 除非必要,否则不要暴露任何端口到外网,如果你是 Web 服务,建议统一由 Nginx 等反向代理服务进行内网转发,而不是直接暴露 Web 服务端口到外网。

针对路由扫描: 不要有确定的路由,大部分被广泛使用的高风险/高权限服务,基本都内置这个功能。比如 1Panel、宝塔,他们都会要求你使用一个随机的安全路径做为入口,听从官方的建议,不要随便关闭这个功能。

当然了,自己也可以利用反向代理服务,给一些不具有此功能的服务添加一个随机入口路由。不过这样做也不是没有缺点的,1个是 SEO 不友好,还有就是可能会导致某些工具/周边服务失效。

针对搜索引擎的收录,简单点的就是设置 Robots 文件,给大家列几个常用的 Demo。

robots.txt: 全称是“网络爬虫排除标准”(Robots Exclusion Protocol),必需放在根路径下。里面的内容是告诉搜索引擎,网站上有哪些页面或目录不希望它们去访问和索引。

需要注意的是,这是一个 "君子协议",搜索引擎会不会遵守,不好说,不过有总好过没有。

  • 允许抓取所有内容

    User-agent: *
    Disallow:
  • 禁止抓取所有内容

    User-agent: *
    Disallow: /
  • 禁止抓取特定目录

    User-agent: *
    Disallow: /admin/
    Disallow: /tmp/
    Disallow: /search/

Docker 并不意味着绝对安全

前文主要从避免被选定为目标的角度,讲了下 0day 攻击发起前,防方的措施。下面讲讲,如何通过合理的服务搭建编排,减少 0day 侵入后能够造成的破坏。(关于数据备份之类比较常规的部分,就默认大伙有这个意识,不展开讲了)

首先需要知道的是,你直接搭建在服务器上的服务,如果造到高危 0day 攻击,恰巧你又没做好权限控制,是有可能导致你整个服务器沦陷的,我们姑且定义为最高级别的损失,因为会导致你所有数据丢失、所有服务不可用。

如果可能的话,将不同的服务部署在不同的服务器上,并做好隔离是最好的。但是绝大多数个人用户并不具有这样的条件,大家往往都是在一台服务器上搭一堆服务,下文我们主要针对这种情况进行探讨。

将一台主机的不同服务进行隔离,最好的办法就是将不同的服务放进 Docker 里面,这样即使一个服务(如 Emby)遭遇了高危 0day 攻击,其造成的损失也被降维了。从“整台服务器沦陷”降低到了“单个容器内的数据泄露或服务不可用”。不过 Docker 并不意味着绝对安全,不合理的配置,一样有整个服务器沦陷的风险。

--privileged 的高度谨慎

很多对 Docker 不是很熟悉的朋友,喜欢照着网上的 Docker 命令 /compose 文件照抄,甚至有的朋友觉得懒省事喜欢给每个容器都加上 --privileged=true ,觉得这样部署起来不容易出问题。这里我要说明一下,--privileged 的本质其实就是 为了信任而设计的“后门”,它会导致 Docker 所有的安全机制失效。如果攻方拿到了容器的 root 权限,几乎可以百分百的逃逸到主机。最简单的作法就是,直接在容器内把主机分区/目录 mount 到容器内的一个目录,然后直接创建或修改宿主机上具有 root 权限的帐号。

所以谨慎对待所有要求 --privileged 权限的容器,确保知道自己在做什么。

慎用 host 网络模式

如果说使用了 --privileged 代表整个「服务器(文件/权限/网络)」在容器面前透明,那么使用了 host 模式,就代表整个「服务器网络」在该容器面前透明。想像这样一个场景,本来你的 MySQL 数据库运行在 Docker 里面,并且仅供本地网络访问,不与外界交互,本来它是绝对安全的。但是你的 EmbyServer 使用了 host 模式,现在攻方通过 0day 拿到了你 EmbyServer 容器的权限,就获得了你整个宿主机的网络命名空间,它就可以从 EmbyServer 容器内部,尝试爆破 MySQL 数据库的密码。

而 Docker 默认情况下的桥接(bridge)模式,每个容器有自己独属的网络命名空间,互相之间无法访问(手动连接不同容器除外)。这样,就算一台容器发生了危险,损失也不会扩散到其他容器和服务。

避开危险目录的数据卷挂载

用 Docker 的朋友,多少都知道使用 --volume 就是将宿主机的目录映射到容器内,从而实现容器数据的持久化。大部人并不会把高危目录映射到容器内。但是,我发现很多人使用的项目,为了实现诸如镜像自动更新之类的功能,特别热衷于获取 Docker 容器的 "遥控器"。

如果你在你的容器中发现诸如 /var/run/docker.sock:/var/run/docker.sock 的映射,请一定要谨慎对待。它实际上相当于你把 Docker 的"遥控器" 交给了该容器。危险程度上和直接使用 --privileged 并没有什么区别,逃逸到宿主机也是分分钟的事情。

当然,有些管理 Docker 容器的容器,这个映射是它必要的组件,没有这个映射,它也实现不了管理容器的功能。

针对这种情况,我只是说我希望你知道 /var/run/docker.sock:/var/run/docker.sock 意味着什么,避免将其给一些不太靠谱的容器使用。尤其是你还将使用了它的容器暴露在公网上 =_=#

我常用的 Docker 配置

我当前服务器的情况是,整台服务器没有任何直接运行在服务器层面的服务。也不使用任何 "特权" 容器,所有服务均部署在 Docker 中。为了方便管理,所有 Docker 容器均采用 Docker Compose 管理。除 Nginx(同样运行在 Docker 中)暴露 80/443端口外,所有 Web 服务不对外暴露端口,数据库禁止外部连接。

核心的是两个Docker 网络,一个是Nginx: 作为所有 Web 服务的出入口,另一个是MySQL: 为服务连接数据库提供网络通道。

  • Nginx 网络

    services:
      nginx:
        image: 'xxxx/nginx:latest'
        container_name: nginx
        ports:
          - "443:443"
          - "80:80"
        restart: always
        volumes:
          - /xxxx/nginx/data:/xxxxx
          - /xxxx/nginx/ssl:/xxxxxx
        environment:
          - TZ=Asia/Shanghai
        networks:
          - nginx
    networks:
      nginx:
        name: nginx
  • MySQL 网络

    services:
      mysql:
        image: xxxx/mysql:latest
        container_name: mysql
        environment:
          - TZ=Asia/Shanghai
        env_file:
          - ./env/mysql.env
        restart: always
        volumes:
          - /xxxxx/mysql/conf:/etc/mysql/conf.d
          - /xxxxx/mysql/data:/var/lib/mysql
          - /xxxxx/mysql/log:/var/log/mysql
        networks:
          - mysql
    networks:
      mysql:
        name: mysql

具体使用上,所有需要数据库的服务加入 MySQL 网络,通过内网接入 MySQL。同理,所有需要接入外网的 Web 服务,统一加入 Nginx 网络,由 Nginx 使用内网地址进行转发。(当然这样会导致各个 Web 服务在同一个网络,但这也是单服务器多 Web 服务的场景下,我能想到的相对来说比较好的方案)

举例来说,一个同时使用的 Web / MySQL 的服务可以这么写

version: "3.5"
services:
  webUI:
    image: xxxx/webUI:latest
    container_name: webUI
    restart: always
    volumes:
      - /xxxx/webUI:/xxxxxx
    networks:
      - mysql
      - nginx
networks:
  mysql:
    external: true
  nginx:
    external: true

然后 Nginx 通过内网进行代理,WebUI 内部使用 mysql:3306 进行数据库连接。

nginx-docker.png

以上是我日常使用的 Docker 服务搭建方式,好处是可以环境端口层面的服务嗅探,Nginx 也可以统一给所有 Web 服务设置 Robots.txt。

如果在单机上你有更好的部署方案,或者发现了本方案中我没有考虑到的安全风险,还请不吝赐教。

安全 EmbyServer Token 方案

前面几个章节,主要从服务搭建和部署等角度讲了提高服务安全性的措施。

下面讲讲针对 EmbyServer 这种自身不具有 Token 权限管理的服务,如何做到权限隔离,既能把 Token 给任意项目使用,又不担心 Token 泄漏了,对 EmbyServer 本身产生影响。

这里的核心思想是加一层中间层做响应拦截,当然这种中间层不仅局限于 EmbyServer,任何有同样需求的服务都可以这么做。整体实现起来也并不复杂,感兴趣的朋友可以自行尝试。(如果一直没人做的话,我倒是可以简单写一个

首先你需要了解的是 EmbyServer 的 API 文档: Emby Swagger UI,方便后续根据自己的需求,定制权限隔离方案。

1. 简单的方案

直接使用 Nginx 做拦截,想省事的可以直接禁用用户访问 UserService 相关的路由(具体可以根据自己的需求来决定颗粒度),只允许特定 IP 访问此路由(比如我们自己)。

Nginx 方案有一个前提,就是你没有直接开放 EmbyServer 端口到外部,所有的请求都是通过 Nginx 走内网转发,保证入口只有 Nginx )

2. 进一步的方案

新建一个 TokenManager 服务,其本身拥有对 EmbyServer 的所有权限,我们保证 TokenManager 绝对安全。

所有需要 Token 的第三方服务,由 TokenManager 统一代理请求,管理生成 「二级Token」,并做好权限隔离。需要利用 Token 的第三方服务,使用 TokenManager 生成的 「二级Token」, 向 TokenManager 发起请求。TokenManager 则根据配置,按需进行响应或拦截,并在响应的结果中过滤掉敏感信息。

  • 权限隔离

    如图所示,即使第三方服务泄漏了 BBBBB、CCCCC、DDDDD 等「二级Token」,攻方使用其去向 EmbyServer 请求是没用的。

  • 响应拦截/参数改写/响应过滤

    emy-token-stru.png

    如图所示,攻方即使拿着第三方服务泄漏的「二级Token」,直接向 TokenManager 构造非法请求也是没用的,会被 TokenManager 拦截/过滤系统给过滤掉,保证整体服务的安全。同样的,这套方案也可以和「简单方案」中的 Nginx 配置并存,二者并不冲突。

3. 一点补充

可能有朋友打算直接照着这个架构用 Vibe Coding 的方式去写这么一套系统,我个人是不太推荐的,因为这个「TokenManage」如果有问题的话,整个防线就都漏了。如果你非要写的话,那么我的建议是,明确告诉 AI,唯一的真实 Token AAAAA,只能在后端配置,前端无法设置,且前端没有任何地方可以读取到这个 Token。包括你自己要改,也只能去后端改。然后 TokenManager 本身不具有创建/更改管理员权限用户的功能,只能你自己登录 EmbyServer 去修改。这样的话,可以最大程度减少 Vibe Coding 带来的权限失控风险。

当然,如果是成熟开发者的话,原本就不需要我的任何建议,TokenManager 的本身功能可以设计的更加激进。

后记&QA

有朋友私下联系我说,最近又发生数起 EmbyServer 黑库事件,想问我的想法

我能否公开目前已经发现存在问题的第三方服务名称

怎么说呢,一来,我对 EmbyServer 的生态了解有限,知道的服务本就不多,二来眼瞅着 Vibe Coding 越来越流行,有问题的服务只会越来越多,简单的公布几个对现状和后续并没有多大帮助,还会放松对未公开的但存在问题的项目的警戒心。还有就是,很多服务本身就是小范围内分享的,人家作者压根就不希望有人提及,我也实在没必要做这个坏人。我更多的建议还是聚焦自己,做好服务防护。

我对脚本小子的态度和看法

脚本小子一直都有,不过每个人的出发点不太一样,细分起来大体分为这么三类

  1. 出于经济/恩怨纠纷,肆意报复的。这种我不是当事人,与我无瓜,不评价、不参和。

  2. 出于炫技,或着满足自己虚荣心的。这种一般恶意不大,不会对服务做一些破坏性的修改,有的可能还主动联系你,告诉你漏洞所在,这种其实还好,能主动告诉你漏洞位置的,说声谢谢不为过。

  3. 没有任何原因,单纯恶意破坏、敲诈勒索的。这种就是人讨狗嫌了,十分没有必要。至少在我印象中(15年前吧,早不接触这些东西了,对现在的情况不太了解,可能有出入),这种人在脚本小子圈也是不受欢迎的。

关于 Vide Coding 的看法

AI 编程大势所趋,不以人的意志力为转移。辅助编程还行,纯 Vibe Coding ,个人感觉现阶段的 AI 表现还是有点不太行的。至于拿着 Vibe Coding 的项目去卖钱,我持中立态度,一个愿打一个愿挨,人家作者也是花费很多心血辛辛苦苦调出来的,赚点外快无所厚非,而且很多想法也很有意思,执行力也强。

不过我还是觉得,使用 Vibe Coding 项目的时候,对数据安全保持敏感,对多余的权限要求,保持警惕。

对于 Vibe Coding 作者,我的看法是分项目吧,有些比较敏感的项目,还是把安全放在第一位,功能实现放在后面比较好。

当然了以上只是我的个人想法,称不上建议,实际按照自己的想法来做就好。

Other

端口扫描、碰撞每天、每时、每刻都在发生,倒也不用太过担心。不过呢,我们倒是可以部署一些 "蜜罐",反过来收集别人碰撞的时候使用的弱密码,假以时日,就有了一批质量还可以的弱密码库。当然了我并不是鼓励大家使用这些弱密码库去做什么,而是知道常常用来碰撞的弱密码有哪些,可以针对性的改进自己使用的密码,避免碰撞成功的可能。

我自己搭建的 "蜜罐" 目前已经收集了 100w+ 条帐号/密码组合,感兴许的朋友可以动手搭建一下,看到每天库库上涨的数据也是蛮有意思的。