zoukankan      html  css  js  c++  java
  • 极路由安全设计分析姐妹篇

    开篇想说两件事情:

    一、非常感谢Freebuf大牛们,在其提供的网站上找到了HiWiFi固件、其中9003版本squashfs文件系统上的lua代码没有经过预编译处理,这对我们基于源码分析极路由提供了可能。地址

    二、经过修炼发现HiWiFi固件解压问题,其实可以使用Windows操作系统下面的开源软件7zip解压。

    那么,本期的重点是分析HiWiFi lua源码安全设计部分。

    0×01 分析思路

    一、了解OpenWRT Web认证过程。

    二、了解HiWiFi web认证过程和HiWiFi Cloud 之间的通讯认证分析。

    0×02 分析过程

    测试工具:源代码阅读使用luaEdit。连接openwrt查看目录软件winscp ,因为有些文件是认证后才产生的。


    一、了解OpenWRT Web认证过程。

      1.1、搭建OpenWRT虚拟运行环境

      1.2、网络分析结合lua源代码分析,了解其认证过程。

    搭建OpenWRT虚拟机运行环境,下载x86架构的openWRT.vmdk文件,就强调一点如果你只使用一块无线网卡搭建环境,会失败,因为OpenWRT需要两个不同的网络LAN、WAN。

    OpenWRT主要是通过内建的web服务器uHttpd配合lua脚本语言实现B/S互交。

    通过抓包软件抓取交互数据包,如下:

    POST http://192.168.1.10/cgi-bin/luci HTTP/1.1
    Host: 192.168.1.10
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://192.168.1.10/cgi-bin/luci
    Connection: keep-alive
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 29
    username=root&password=123qwe
    GET http://192.168.1.10/cgi-bin/luci/;stok=f10e9261c036d0c97db82c5eee568b34?status=1&_=0.4118332080369933 HTTP/1.1
    Host: 192.168.1.10
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Referer: http://192.168.1.10/cgi-bin/luci
    Cookie: sysauth=ae896241b40cf93dbf079c08acaeffcd
    Connection: keep-alive

    通过web界面提供的账号和密码,返回用户在web端保持通讯的token.

    stok=f10e9261c036d0c97db82c5eee568b34

    和客户端认证的cookie。

    Cookie: sysauth=ae896241b40cf93dbf079c08acaeffcd 时间产生cookies.

    然后使用winscp登录到OpenWRT上查看文件,会发现整个登录调用文件流程如下:

    通过ccache.lua处理,发现服务器端变化:/tmp/luci-sessions 产生f10e9261c036d0c97db82c5eee568b34 一个文件(session)

    该文件存储一个预编译lua脚本,主要是映射的登录用户名和token的对应关系。

    Json数据形式(保存的数据库形式):

    return { ["secret"] = "bb8d42ed6c097b6f16ea698b22f7b0e1",
    ["token"] = "f10e9261c036d0c97db82c5eee568b34",
    ["user"] = "root" }
    /usr/lib/lua/luci/ccache.lua

    涉及到加密算法 16进制输出。encoded = encoded .. ("%2X" % string.byte(name, i))

    由于源码太多没全部写出来,大家可以参考相关的bin文件中的代码。

    然后查看:/usr/lib/lua/luci/dispatcher.lua 是怎么处理登录的:发现authenticator.htmlauth 函数。

    function authenticator.htmlauth(validator, accs, default)
        local user = luci.http.formvalue("username")
        local pass = luci.http.formvalue("password")
     
        if user and validator(user, pass) then
             return user
        end
     
        require("luci.i18n")
        require("luci.template")
        context.path = {}
        luci.template.render("sysauth", {duser=default, fuser=user})
        return false
     
    end

    validator{},主要是通过对提交的密码做MD5对比校验,如果匹配则返回真。

    Sysauth具体怎么产生的呢?

    通过查询/usr/lib/lua/luci/sauth.lua

    关键代码:

    sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60
     
    local function _checkid(id)
        return not not (id and #id == 32 and id:match("^[a-fA-F0-9]+$"))
    end
     
     
    --- Write session data to a session file.
    -- @param id Session identifier
    -- @param data    Session data table
    function write(id, data)
        if not sane() then
             prepare()
        end
     
        assert(_checkid(id), "Security Exception: Session ID is invalid!")
        assert(type(data) == "table", "Security Exception: Session data invalid!")
     
        data.atime = luci.sys.uptime()
     
        _write(id, luci.util.get_bytecode(data))
    end

    从上面代码可以看出session生成和时间有关,要想破解这个需要通过系统时间遍历。有点难度呀。

    当然在极路由上 我发现在这之前会有一个函数调用   authenticator{}; 但是在lua代码中没有找到具体的函数编写内容,后来通过luaedit工具做全文内容检索发现。在libauth.so文件中。这个在最后章节讲解。

    小结:可以看出,只有认证通过了才会在openwrt上产生服务器端token,保存在uhttpd服务器的本地文件中。整个过程基本上无法伪造,登录验证体系比较安全,当然也有漏洞,包括遍历密码尝试等。

    二、了解HiWiFi web认证过程和HiWiFi Cloud 之间的通讯认证分析。

    我申请了极路由的root权限通过winscp登录查看其目录新产生的文件。本来想逆向lua最新的源代码unlua 和luadec 两个开源的反编译工具都无法通过,所以只好看H5661-9003版本软件。

    GET /cgi-bin/turbo/;stok=7523cc581c1bccca1db1ea1866c90b95/api/system/check_network_connect?_=1440172033296 HTTP/1.1
    Host: 192.168.199.1
    Connection: keep-alive
    Accept: application/json, text/javascript, */*; q=0.01
    X-Requested-With: XMLHttpRequest
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.103 Safari/537.36
    Referer: http://192.168.199.1/cgi-bin/turbo/admin_web
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
    Cookie: sysauth=d4b90df6bee107655b1672c244dd8b75; is_mobile=0

    整个过程和openwrt类似,但是做了一些改动。把路径改变成:/cgi-bin/turbo/admin_web,然后推送ajax 脚本检测相关内容。

    改进部分:

    (1)通过云端参与密钥生成。

    LOCAL_KEY='oLKmbg1g'
    APP_LOGIN_AUTH_FILE='/etc/app/appcloudkey'
     
    putkey() {
      echo "$1" | grep -q -E "^[a-z0-9_]+$"
      if [ $(echo $?) != 0 ]; then
        return 1
      fi
      touch "${APP_LOGIN_AUTH_FILE}"
      echo "$1" >"${APP_LOGIN_AUTH_FILE}"
      return 0
    }
     
    validate() {
      userkey="$1"
      randkey="$2"
      echo "${userkey}" | grep -q -E "^[a-f0-9]{32}$"
      if [ $(echo $?) != 0 ]; then
        echo false
        return 1
      fi
      echo "${randkey}" | grep -q -E "^[a-f0-9]{8,}$"
      if [ $(echo $?) != 0 ]; then
        echo false
        return 1
      fi
      touch "${APP_LOGIN_AUTH_FILE}"
      key=$(cat "$APP_LOGIN_AUTH_FILE")
      echo "${key}" | grep -q -E "^[a-z0-9]+$"
      if [ $(echo $?) != 0 ]; then
        echo false
        return 1
      fi
      sign=$(echo -n "$LOCAL_KEY""${key}""${randkey}"|md5sum|awk '{print $1}')
      if [ ${userkey} == "${sign}" ] 2>/dev/null; then
        echo true
        return 0
      fi
      echo false
      return 1
    }

    (2)增加登录重试次数限定(10次)。通过校验/tmp/loginerrnum 存储的次数

    local loginlock_time = 10
    function up_loginlock()
    local num = fs.readfile("/tmp/loginerrnum") or 0
    num=num+1
    fs.writefile("/tmp/loginerrnum", num)
    end
    function unset_loginlock()
    fs.writefile("/tmp/loginerrnum", 0)
    end
    function get_loginlock()
    local num = fs.readfile("/tmp/loginerrnum") or 0
    return num
    end
     
    function authenticator.jsonauth(validator, accs, default)
    if user and validator(user, pass) and user~="root" then
    luci.util.unset_loginlock()
    return user
    end
    context.path = {}
    local json_msg = '{"code":"99999","msg":"not auth."}'
    luci.http.write(json_msg)
    return false
    end

    (3)对openapi调用设置沙盒权限,以及更为严格的token获取方式。

    在 /usr/lib/lua/luci/dispatcher.lua 中

    authenticator{};没找到实现的方法。

    通过查询目录发现usr/lib/libauth.so 可能存在认证信息。然后把它丢到IDA中。选择MIPS little endian

    先通过cache_load_token_v3读取本地存储token。如果没有那么申请token

    https://auth.hiwifi.com/tokenv2?app=%s&checksum=%s&name=%s&cnonce=%d&nonce=%s
    checksum(校验和)、name(设备MAC地址)、
    cnonce(本地产生的随机数) Cnonce  时间和加盐处理

    nonce是通过云平台自动产生的,算法只有云自己知道。

    这里要找的就是checksum值。

    查找tw_get_uuid,通过查找发现在tw.so文件中

    当然你也可以使用python语言调用so库,来测试其加密算法。

    UUID是通过设备mac地址,外加一个常量123456789123 等再加上复杂的算法生成,那么由于每台设备的uuid并不相同,所以即使得到对方的MAC地址,也无法通过伪造请求来进行利用。这种多因素校验的机制,极大的保障了云平台用户的安全。

    沙盒部分,其实就是现在通过openapi登录取得密钥的访问目录限制,防止二次开发人员开发恶意程序,然后数以百万路由器。

    0×03 安全设计总结

    可以看出,极路由从openwrt->hiwifi 9003->hiwifi 9008(目前极1s用的最多的版本),整个固件的软件安全部分设计越来越复杂。

    变态的安全设计也是符合业务需求:

    (1)基本安全的保障:开原版lua源代码可见,必须在发布设备前要做预编译处理。核心的算法和认证库放到so文件中。

    (2)由于要做路由器软件market,那么,把云的key验证机制结合进来后,即使你破解了本地算法,云算法你不了解也白搭。

    最后,为这种信息安全的工匠精神点个赞。

  • 相关阅读:
    std thread
    windows更新包发布地址
    How to set up logging level for Spark application in IntelliJ IDEA?
    spark 错误 How to set heap size in spark within the Eclipse environment?
    hadoop 常用命令
    windows 安装hadoop 3.2.1
    windows JAVA_HOME 路径有空格,执行软连接
    day01MyBatisPlus条件构造器(04)
    day01MyBatisPlus的CRUD 接口(03)
    day01MyBatisPlus入门(02)
  • 原文地址:https://www.cnblogs.com/k1two2/p/4760794.html
Copyright © 2011-2022 走看看