Merge pull request #5907 from x-f1v3/java/hardcoded-shiro-key

Java: CWE-798: Query to detect hard-coded SHIRO key
This commit is contained in:
Chris Smowton
2021-09-30 17:58:12 +01:00
committed by GitHub
17 changed files with 510 additions and 5 deletions

View File

@@ -0,0 +1,3 @@
lgtm,codescanning
* The query "Hard-coded credential in API call" (`java/hardcoded-credential-api-call`) can now detect a hard-coded Apache Shiro cipher key.
* The query "Hard-coded credential in API call" (`java/hardcoded-credential-api-call`) now detects hard-coded credentials that are Base64 encoded or decoded before use.

View File

@@ -309,6 +309,8 @@ private predicate summaryModelCsv(string row) {
"java.util;Base64$Decoder;false;decode;(ByteBuffer);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;decode;(String);;Argument[0];ReturnValue;taint",
"java.util;Base64$Decoder;false;wrap;(InputStream);;Argument[0];ReturnValue;taint",
"cn.hutool.core.codec;Base64;true;decode;;;Argument[0];ReturnValue;taint",
"org.apache.shiro.codec;Base64;false;decode;(String);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;Encoder;true;encode;(Object);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;Decoder;true;decode;(Object);;Argument[0];ReturnValue;taint",
"org.apache.commons.codec;BinaryEncoder;true;encode;(byte[]);;Argument[0];ReturnValue;taint",

View File

@@ -27,9 +27,30 @@ class HardcodedCredentialApiCallConfiguration extends DataFlow::Configuration {
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.asExpr().getType() instanceof TypeString and
exists(MethodAccess ma | ma.getMethod().hasName(["getBytes", "toCharArray"]) |
node2.asExpr() = ma and
ma.getQualifier() = node1.asExpr()
(
exists(MethodAccess ma | ma.getMethod().hasName(["getBytes", "toCharArray"]) |
node2.asExpr() = ma and
ma.getQualifier() = node1.asExpr()
)
or
// These base64 routines are usually taint propagators, and this is not a general
// TaintTracking::Configuration, so we must specifically include them here
// as a common transform applied to a constant before passing to a remote API.
exists(MethodAccess ma |
ma.getMethod()
.hasQualifiedName([
"java.util", "cn.hutool.core.codec", "org.apache.shiro.codec",
"apache.commons.codec.binary", "org.springframework.util"
], ["Base64$Encoder", "Base64$Decoder", "Base64", "Base64Utils"],
[
"encode", "encodeToString", "decode", "decodeBase64", "encodeBase64",
"encodeBase64Chunked", "encodeBase64String", "encodeBase64URLSafe",
"encodeBase64URLSafeString"
])
|
node1.asExpr() = ma.getArgument(0) and
node2.asExpr() = ma
)
)
}

View File

@@ -513,5 +513,6 @@ private predicate otherApiCallableCredentialParam(string s) {
s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);1" or
s = "com.azure.identity.UsernamePasswordCredentialBuilder;username(String);0" or
s = "com.azure.identity.UsernamePasswordCredentialBuilder;password(String);0" or
s = "com.azure.identity.ClientSecretCredentialBuilder;clientSecret(String);0"
s = "com.azure.identity.ClientSecretCredentialBuilder;clientSecret(String);0" or
s = "org.apache.shiro.mgt.AbstractRememberMeManager;setCipherKey(byte[]);0"
}

View File

@@ -26,6 +26,9 @@ edges
| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String |
| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String |
| HardcodedAzureCredentials.java:63:3:63:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | HardcodedAzureCredentials.java:43:14:43:38 | parameter this [clientSecret] : String |
| HardcodedShiroKey.java:9:46:9:54 | "TEST123" : String | HardcodedShiroKey.java:9:46:9:65 | getBytes(...) |
| HardcodedShiroKey.java:18:61:18:86 | "4AvVhmFLUs0KTA3Kprsdag==" : String | HardcodedShiroKey.java:18:46:18:87 | decode(...) |
| HardcodedShiroKey.java:26:83:26:108 | "6ZmI6I2j5Y+R5aSn5ZOlAA==" : String | HardcodedShiroKey.java:26:46:26:109 | decode(...) |
| Test.java:9:16:9:22 | "admin" : String | Test.java:12:13:12:15 | usr : String |
| Test.java:9:16:9:22 | "admin" : String | Test.java:15:36:15:38 | usr |
| Test.java:9:16:9:22 | "admin" : String | Test.java:17:39:17:41 | usr |
@@ -76,6 +79,12 @@ nodes
| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | semmle.label | new HardcodedAzureCredentials(...) [clientSecret] : String |
| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | semmle.label | new HardcodedAzureCredentials(...) [username] : String |
| HardcodedAzureCredentials.java:63:3:63:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | semmle.label | new HardcodedAzureCredentials(...) [clientSecret] : String |
| HardcodedShiroKey.java:9:46:9:54 | "TEST123" : String | semmle.label | "TEST123" : String |
| HardcodedShiroKey.java:9:46:9:65 | getBytes(...) | semmle.label | getBytes(...) |
| HardcodedShiroKey.java:18:46:18:87 | decode(...) | semmle.label | decode(...) |
| HardcodedShiroKey.java:18:61:18:86 | "4AvVhmFLUs0KTA3Kprsdag==" : String | semmle.label | "4AvVhmFLUs0KTA3Kprsdag==" : String |
| HardcodedShiroKey.java:26:46:26:109 | decode(...) | semmle.label | decode(...) |
| HardcodedShiroKey.java:26:83:26:108 | "6ZmI6I2j5Y+R5aSn5ZOlAA==" : String | semmle.label | "6ZmI6I2j5Y+R5aSn5ZOlAA==" : String |
| Test.java:9:16:9:22 | "admin" : String | semmle.label | "admin" : String |
| Test.java:10:17:10:24 | "123456" : String | semmle.label | "123456" : String |
| Test.java:12:13:12:15 | usr : String | semmle.label | usr : String |
@@ -110,6 +119,9 @@ subpaths
| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" | HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | HardcodedAzureCredentials.java:18:13:18:20 | username | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:18:13:18:20 | username | sensitive API call |
| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" | HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | sensitive API call |
| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" | HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:46:17:46:28 | clientSecret | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:46:17:46:28 | clientSecret | sensitive API call |
| HardcodedShiroKey.java:9:46:9:54 | "TEST123" | HardcodedShiroKey.java:9:46:9:54 | "TEST123" : String | HardcodedShiroKey.java:9:46:9:65 | getBytes(...) | Hard-coded value flows to $@. | HardcodedShiroKey.java:9:46:9:65 | getBytes(...) | sensitive API call |
| HardcodedShiroKey.java:18:61:18:86 | "4AvVhmFLUs0KTA3Kprsdag==" | HardcodedShiroKey.java:18:61:18:86 | "4AvVhmFLUs0KTA3Kprsdag==" : String | HardcodedShiroKey.java:18:46:18:87 | decode(...) | Hard-coded value flows to $@. | HardcodedShiroKey.java:18:46:18:87 | decode(...) | sensitive API call |
| HardcodedShiroKey.java:26:83:26:108 | "6ZmI6I2j5Y+R5aSn5ZOlAA==" | HardcodedShiroKey.java:26:83:26:108 | "6ZmI6I2j5Y+R5aSn5ZOlAA==" : String | HardcodedShiroKey.java:26:46:26:109 | decode(...) | Hard-coded value flows to $@. | HardcodedShiroKey.java:26:46:26:109 | decode(...) | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:15:36:15:38 | usr | Hard-coded value flows to $@. | Test.java:15:36:15:38 | usr | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:17:39:17:41 | usr | Hard-coded value flows to $@. | Test.java:17:39:17:41 | usr | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:18:39:18:41 | usr | Hard-coded value flows to $@. | Test.java:18:39:18:41 | usr | sensitive API call |

View File

@@ -0,0 +1,40 @@
import org.apache.shiro.web.mgt.CookieRememberMeManager;
public class HardcodedShiroKey {
//BAD: hard-coded shiro key
public void testHardcodedShiroKey(String input) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCipherKey("TEST123".getBytes());
}
//BAD: hard-coded shiro key encoded by java.util.Base64
public void testHardcodedbase64ShiroKey1(String input) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
cookieRememberMeManager.setCipherKey(decoder.decode("4AvVhmFLUs0KTA3Kprsdag=="));
}
//BAD: hard-coded shiro key encoded by org.apache.shiro.codec.Base64
public void testHardcodedbase64ShiroKey2(String input) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCipherKey(org.apache.shiro.codec.Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
}
//GOOD: random shiro key
public void testRandomShiroKey(String input) {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
}
}

View File

@@ -1 +1 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java:${testdir}/../../../../../stubs/shiro-core-1.4.0

View File

@@ -0,0 +1,36 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.shiro.codec;
public class Base64 {
static final int CHUNK_SIZE = 76;
static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
private static final int BASELENGTH = 255;
private static final int LOOKUPLENGTH = 64;
private static final int EIGHTBIT = 8;
private static final int SIXTEENBIT = 16;
private static final int TWENTYFOURBITGROUP = 24;
private static final int FOURBYTE = 4;
private static final int SIGN = -128;
private static final byte PAD = 61;
private static final byte[] base64Alphabet = new byte[255];
private static final byte[] lookUpBase64Alphabet = new byte[64];
public Base64() {
}
public static byte[] decode(String base64Encoded) {
byte[] bytes = new byte[1024];
return decode(bytes);
}
public static byte[] decode(byte[] base64Data) {
return base64Data;
}
}

View File

@@ -0,0 +1,28 @@
package org.apache.shiro.crypto;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
public abstract class AbstractSymmetricCipherService extends JcaCipherService {
protected AbstractSymmetricCipherService(String algorithmName) {
super(algorithmName);
}
public Key generateNewKey() {
return this.generateNewKey(this.getKeySize());
}
public Key generateNewKey(int keyBitSize) {
KeyGenerator kg;
try {
kg = KeyGenerator.getInstance(this.getAlgorithmName());
} catch (NoSuchAlgorithmException var5) {
String msg = "Unable to acquire " + this.getAlgorithmName() + " algorithm. This is required to function.";
throw new IllegalStateException(msg, var5);
}
kg.init(keyBitSize);
return kg.generateKey();
}
}

View File

@@ -0,0 +1,9 @@
package org.apache.shiro.crypto;
public class AesCipherService extends DefaultBlockCipherService {
private static final String ALGORITHM_NAME = "AES";
public AesCipherService() {
super("AES");
}
}

View File

@@ -0,0 +1,12 @@
package org.apache.shiro.crypto;
import java.io.InputStream;
import java.io.OutputStream;
public interface CipherService {
void decrypt(InputStream var1, OutputStream var2, byte[] var3) throws Exception;
void encrypt(InputStream var1, OutputStream var2, byte[] var3) throws Exception;
}

View File

@@ -0,0 +1,120 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.shiro.crypto;
public class DefaultBlockCipherService extends AbstractSymmetricCipherService {
private static final int DEFAULT_BLOCK_SIZE = 0;
private static final String TRANSFORMATION_STRING_DELIMITER = "/";
private static final int DEFAULT_STREAMING_BLOCK_SIZE = 8;
private String modeName;
private int blockSize;
private String paddingSchemeName;
private String streamingModeName;
private int streamingBlockSize;
private String streamingPaddingSchemeName;
private String transformationString;
private String streamingTransformationString;
public DefaultBlockCipherService(String algorithmName) {
super(algorithmName);
this.modeName = OperationMode.CBC.name();
this.paddingSchemeName = PaddingScheme.PKCS5.getTransformationName();
this.blockSize = 0;
this.streamingModeName = OperationMode.CBC.name();
this.streamingPaddingSchemeName = PaddingScheme.PKCS5.getTransformationName();
this.streamingBlockSize = 8;
}
public String getModeName() {
return null;
}
public void setModeName(String modeName) {
}
public void setMode(OperationMode mode) {
}
public String getPaddingSchemeName() {
return null;
}
public void setPaddingSchemeName(String paddingSchemeName) {
}
public void setPaddingScheme(PaddingScheme paddingScheme) {
}
public int getBlockSize() {
return 1;
}
public void setBlockSize(int blockSize) {
}
public String getStreamingModeName() {
return null;
}
private boolean isModeStreamingCompatible(String modeName) {
return false;
}
public void setStreamingModeName(String streamingModeName) {
}
public void setStreamingMode(OperationMode mode) {
}
public String getStreamingPaddingSchemeName() {
return null;
}
public void setStreamingPaddingSchemeName(String streamingPaddingSchemeName) {
}
public void setStreamingPaddingScheme(PaddingScheme scheme) {
}
public int getStreamingBlockSize() {
return 1;
}
public void setStreamingBlockSize(int streamingBlockSize) {
}
protected String getTransformationString(boolean streaming) {
return null;
}
private String buildTransformationString() {
return null;
}
private String buildStreamingTransformationString() {
return null;
}
private String buildTransformationString(String modeName, String paddingSchemeName, int blockSize) {
return null;
}
private boolean isModeInitializationVectorCompatible(String modeName) {
return false;
}
protected boolean isGenerateInitializationVectors(boolean streaming) {
return false;
}
protected byte[] generateInitializationVector(boolean streaming) {
return null;
}
}

View File

@@ -0,0 +1,113 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.shiro.crypto;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public abstract class JcaCipherService implements CipherService {
private static final int DEFAULT_KEY_SIZE = 128;
private static final int DEFAULT_STREAMING_BUFFER_SIZE = 512;
private static final int BITS_PER_BYTE = 8;
private static final String RANDOM_NUM_GENERATOR_ALGORITHM_NAME = "SHA1PRNG";
private String algorithmName;
private int keySize;
private int streamingBufferSize;
private boolean generateInitializationVectors;
private int initializationVectorSize;
protected JcaCipherService(String algorithmName) {
this.algorithmName = algorithmName;
this.keySize = 128;
this.initializationVectorSize = 128;
this.streamingBufferSize = 512;
this.generateInitializationVectors = true;
}
public String getAlgorithmName() {
return null;
}
public int getKeySize() {
return 1;
}
public void setKeySize(int keySize) {
}
public boolean isGenerateInitializationVectors() {
return false;
}
public void setGenerateInitializationVectors(boolean generateInitializationVectors) {
}
public int getInitializationVectorSize() {
return 1;
}
public void setInitializationVectorSize(int initializationVectorSize) throws IllegalArgumentException {
}
protected boolean isGenerateInitializationVectors(boolean streaming) {
return false;
}
public int getStreamingBufferSize() {
return 1;
}
public void setStreamingBufferSize(int streamingBufferSize) {
}
protected String getTransformationString(boolean streaming) {
return null;
}
protected byte[] generateInitializationVector(boolean streaming) {
return null;
}
private byte[] crypt(byte[] bytes, byte[] key, byte[] iv, int mode) throws IllegalArgumentException, Exception {
return null;
}
public void encrypt(InputStream in, OutputStream out, byte[] key) throws Exception {
}
private void encrypt(InputStream in, OutputStream out, byte[] key, byte[] iv, boolean prependIv) throws Exception {
}
public void decrypt(InputStream in, OutputStream out, byte[] key) throws Exception {
}
private void decrypt(InputStream in, OutputStream out, byte[] key, boolean ivPrepended) throws Exception {
}
private void decrypt(InputStream in, OutputStream out, byte[] decryptionKey, byte[] iv) throws Exception {
}
private void crypt(InputStream in, OutputStream out, byte[] keyBytes, byte[] iv, int cryptMode) throws Exception {
}
}

View File

@@ -0,0 +1,19 @@
package org.apache.shiro.crypto;
public enum OperationMode {
CBC,
CCM,
CFB,
CTR,
EAX,
ECB,
GCM,
NONE,
OCB,
OFB,
PCBC;
private OperationMode() {
}
}

View File

@@ -0,0 +1,25 @@
package org.apache.shiro.crypto;
public enum PaddingScheme {
NONE("NoPadding"),
ISO10126("ISO10126Padding"),
OAEP("OAEPPadding"),
OAEPWithMd5AndMgf1("OAEPWithMD5AndMGF1Padding"),
OAEPWithSha1AndMgf1("OAEPWithSHA-1AndMGF1Padding"),
OAEPWithSha256AndMgf1("OAEPWithSHA-256AndMGF1Padding"),
OAEPWithSha384AndMgf1("OAEPWithSHA-384AndMGF1Padding"),
OAEPWithSha512AndMgf1("OAEPWithSHA-512AndMGF1Padding"),
PKCS1("PKCS1Padding"),
PKCS5("PKCS5Padding"),
SSL3("SSL3Padding");
private final String transformationName;
private PaddingScheme(String transformationName) {
this.transformationName = transformationName;
}
public String getTransformationName() {
return this.transformationName;
}
}

View File

@@ -0,0 +1,34 @@
package org.apache.shiro.mgt;
import org.apache.shiro.crypto.AesCipherService;
public abstract class AbstractRememberMeManager {
private byte[] encryptionCipherKey;
private byte[] decryptionCipherKey;
private AesCipherService cipherService;
public AbstractRememberMeManager() {
AesCipherService cipherService = new AesCipherService();
this.cipherService = cipherService;
this.setCipherKey(cipherService.generateNewKey().getEncoded());
}
public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
this.encryptionCipherKey = encryptionCipherKey;
}
public void setDecryptionCipherKey(byte[] decryptionCipherKey) {
this.decryptionCipherKey = decryptionCipherKey;
}
public void setCipherKey(byte[] cipherKey) {
this.setEncryptionCipherKey(cipherKey);
this.setDecryptionCipherKey(cipherKey);
}
}

View File

@@ -0,0 +1,30 @@
package org.apache.shiro.web.mgt;
import org.apache.shiro.mgt.AbstractRememberMeManager;
public class CookieRememberMeManager extends AbstractRememberMeManager {
public CookieRememberMeManager() {
}
public void setCookie() {
}
protected void rememberSerializedIdentity() {
}
private boolean isIdentityRemoved() {
return false;
}
protected byte[] getRememberedSerializedIdentity() {
return null;
}
private String ensurePadding(String base64) {
return null;
}
}