”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 授权陷阱:Keycloak 隐藏了什么?

授权陷阱:Keycloak 隐藏了什么?

发布于2024-08-31
浏览:276

Author: Valerii Filatov

User authorization and registration are important parts of any application, not only for users but also for security. What pitfalls does the source code of a popular open-source identity management solution hide? How do they affect the application?

Authorization pitfalls: what does Keycloak cloak?

If you've ever implemented authorization for web apps, you probably know all the frustrating issues that can arise. I'm no exception, either.

Once, I implemented the messenger-based authorization in the frontend part of one project. It seemed like the world's easiest task but turned out to be the opposite: deadlines were looming, code was tripping over messenger APIs, and people around me were yelling.

After this case, my colleague showed me a cool tool that would streamline the implementation of authorization in our future projects. This project was Keycloak, an open-source Java solution to enable single sign-on (SSO) with identity and access management aimed at modern apps and services.

As I use the solution myself, I find it interesting to get into the source code and use the PVS-Studio static analyzer to look for the bugs hidden there.

He-he, classic NullPointerException

Someone knocked on the door. The Junior Dev tried to open a door and crashed. "NullPointerException!" concluded the Senior Dev.

Errors related to checking for null are encountered in almost every project we've checked before. So, let's start with this old but gold error.

Fragment N1

private void checkRevocationUsingOCSP(X509Certificate[] certs) 
  throws GeneralSecurityException {
    ....    
    if (rs == null) {
      if (_ocspFailOpen)
        logger.warnf(....);
      else
        throw new GeneralSecurityException(....);
    }

    if (rs.getRevocationStatus() == 
      OCSPProvider.RevocationStatus.UNKNOWN) { // 



Warning:

V6008 Potential null dereference of 'rs'.

This code snippet has a null check, but we need to understand what's going on inside. If the _oscpFailOpen variable is true, the program won't throw an exception. It'll only save the log about it and continue execution—it receives a NullPointerException in the following if, since the rs variable has already been used inside if.

It might seem like the program would just throw another exception if there weren't the NullPointerException. But that's not the case because the _oscpFailOpen variable is true, and the program should continue execution. No fate, it tripped over the null pointer and fell into the exception.

Fragment N2

public void writeDateAttributeValue(XMLGregorianCalendar attributeValue) 
  throws ProcessingException {
    ....
    StaxUtil.writeAttribute(
      writer, 
      "xsi", 
      JBossSAMLURIConstants.XSI_NSURI.get(), 
      "type", 
      "xs:"   attributeValue.getXMLSchemaType().getLocalPart()   // 



Warning*:*

V6060 The 'attributeValue' reference was utilized before it was verified against null.

"Better late than never," this phrase is good enough for many cases, but unfortunately, not for the null check. In the above code snippet, the attributeValue object is used before it's checked for existence, which leads to the NullPointerException.

Note. If you want to check out other examples of such bugs, we've put together a list for you!

Arguments?

I don't know how it has happened, but Keycloak has errors related to the number of arguments in the format string functions. It isn't an illustrative statistic—just a fun fact, though.

Fragment N3

protected void process() {
  ....
  if (f == null) {
    ....
  } else {
    ....
    if (isListType(f.getType()) && t instanceof ParameterizedType) {
      t = ((ParameterizedType) t).getActualTypeArguments()[0];
      if (!isBasicType(t) && t instanceof Class) {
        ....
        out.printf(", where value is:\n", ts);              // 



Warning:

V6046 Incorrect format. A different number of format items is expected. Arguments not used: 1.

In the above fragment, when the printf function is called, we pass a format string and a value to be substituted into the string. There's only one problem: there's simply no place in the string to substitute arguments.

It's pretty interesting that a dev not only copied and pasted both the code fragment from if to else if, but a dev also copied and pasted the error inside else if.

In the next snippet, we have the opposite case: developers have spared arguments.

Fragment N4

public String toString() {
  return String.format(
    "AuthenticationSessionAuthNoteUpdateEvent 
      [ authSessionId=%s,
        tabId=%s,
        clientUUID=%s,
        authNotesFragment=%s ]", 
    authSessionId, 
    clientUUID, 
    authNotesFragment);      // 



Warning:

V6046 Incorrect format. A different number of format items is expected. Missing arguments: 4.

Although devs passed the authSessionId, clientUUID and authNotesFragment arguments to the function, the fourth tabld argument is a bit missed.

In this case, the String.format method will throw an IllegalFormatException, which can be a nasty surprise.

Unclean code

The following PVS-Studio warnings aren't related to code health errors. These warnings are more about how high-quality the code is. I just pointed out that these aren't firm errors.

"What's the point of looking at them?" you may think. First, I believe that code should be clean and neat. It's not a matter of tastes: clean code is not only about the visual experience but also about code readability. Imho, it's important for any project, especially Open Source. Secondly, I'd like to show that the PVS-Studio static analyzer helps not only fix errors in code but also makes it beautiful and clean.

Not enough variables, My Lord!

For some reason, the following code fragment looks in my mind like an evil plan of someone who has a bad attitude to use variables: "Let's adopt variables and leave them waiting in horror for the garbage collector to gobble them up..."

Fragment N5

 private void onUserRemoved(RealmModel realm, String userId) {
    int num = em.createNamedQuery("deleteClientSessionsByUser")
      .setParameter("userId", userId).executeUpdate();             // 



Warning:

V6021 The value is assigned to the 'num' variable but is not used.

From the point of program operation, nothing terrible happens in this code snippet. But it's still not clear why developers wrote that.

The num variable contains the number of set parameters that the executeUpdate method returns. So, I thought that the method might have had a check for changes. Even if I rewind in time, I'll only find that calls to the method aren't written to a variable but accept the current state a little later.

The result is a useless assignment to the variable—just like in the next fragment.

Fragment N6

private void verifyCodeVerifier(String codeVerifier, String codeChallenge, 
  String codeChallengeMethod) throws ClientPolicyException {
    ....
    String codeVerifierEncoded = codeVerifier;

    try {
      if (codeChallengeMethod != null &&
        codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) {

        codeVerifierEncoded = generateS256CodeChallenge(codeVerifier);

        } else {
          codeVerifierEncoded = codeVerifier; // 



Warning:

V6026 This value is already assigned to the 'codeVerifierEncoded' variable.

If you look at the code, you can see that before this, the codeVerifierEncoded variable has already assigned the same value in the else branch. A developer just did redundant action: and useless, and overcomplicating.

The next code fragment just amuses me.

Fragment N7

private static Type[] extractTypeVariables(Map typeVarMap, 
  Type[] types){
    for (int j = 0; j 



Warning:

V6005 The variable 'types[j]' is assigned to itself.

It looks just like the previous snippet, but honestly, I'm totally lost on what this code is trying to do.

At first, I thought we were facing a nested loop here, and the author had just mixed up the variables i and j. But eventually I realized that there's only one loop here.

I also thought the assignment appeared when developers were refactoring the code, which might have been even more complicated before. In the end, I found that the function was originally created this way (commit).

So sweet copy-paste

Copy-paste errors are quite common. I'm sure you've seen them even in your own code.

Keycloak has some traces of the copy-paste use, too.

Fragment 8

public class IDFedLSInputResolver implements LSResourceResolver {
  ....

  static {
    ....
    schemaLocations.put("saml-schema-metadata-2.0.xsd", 
      "schema/saml/v2/saml-schema-metadata-2.0.xsd");
    schemaLocations.put("saml-schema-x500-2.0.xsd", 
      "schema/saml/v2/saml-schema-x500-2.0.xsd");
    schemaLocations.put("saml-schema-xacml-2.0.xsd", 
      "schema/saml/v2/saml-schema-xacml-2.0.xsd");

    schemaLocations.put("saml-schema-xacml-2.0.xsd", 
      "schema/saml/v2/saml-schema-xacml-2.0.xsd");          // 



Warning*:*

V6033 An item with the same key '"saml-schema-xacml-2.0.xsd"' has already been added.

Honestly, even though I knew there was a typo in the source code, I had a hard time finding it right away in the code.

If you notice, in the schemaLocations.put method calls, the passed arguments are quite similar. So, I assume that the dev who wrote this code simply copied a string as a template and then just changed values. The problem is that during copy-pasting, one line that repeats the previous one has crept into the project.

Such "typos" can either lead to serious consequences or have no effect at all. This copy-pasting example has been in the project since November 21, 2017 (commit), and I don't think it causes any serious problems.

We're including this error in the article to remind developers to be careful when copying and pasting code fragments and to keep an eye on any changes in code. Want to read more about it? Here's the article about the copy-paste typos.

Unreachable code

The headline gives us a little clue as to what kind of warning awaits us in the following snippet. I suggest you use your detective skills to spot the flaw yourself.

Fragment N9

public void executeOnEvent(ClientPolicyContext context) 
  throws ClientPolicyException {
    switch (context.getEvent()) {
      case REGISTER:
      case UPDATE:
        ....
      case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST:
        ....
        executeOnAuthorizationRequest(ropcContext.getParams());
        return;
      default:
        return;
    }
}

It's not that easy to detect, isn't it? I'm not gloating over you, I just give you a chance to roughly access the situation. I show it because a small flaw makes it harder for the developer to find the error without examining the rest of the code. To find out what error is lurking in this code snippet, we need to look at what is cloaked in the executeOnAuthorizationRequest method:

private void executeOnAuthorizationRequest(MultivaluedMap params) throws ClientPolicyException {
    ....
    throw new ClientPolicyException(....);
}

Yes, this method throws an exception. That is, all the code written after calling this method will be unreachable—the PVS-Studio analyzer detected it.

public void executeOnEvent(ClientPolicyContext context) 
  throws ClientPolicyException {
    switch (context.getEvent()) {
      case REGISTER:
      case UPDATE:
        ....
      case RESOURCE_OWNER_PASSWORD_CREDENTIALS_REQUEST:
        ....
        executeOnAuthorizationRequest(ropcContext.getParams());
        return;       // 



Warning:

V6019 Unreachable code detected. It is possible that an error is present.

Even if this flaw is quite small, a similar case could lead to more serious consequences. I can only note here that a static analyzer will help you avoid such unpleasant things.

I dictate CONDITION

Now, let's look at conditional statements and cases when they execute their code.

Fragment N10

public boolean validatePassword(AuthenticationFlowContext context, 
  UserModel user, MultivaluedMap inputData, 
  boolean clearUser) {

    ....

    if (password == null || password.isEmpty()) {
      return badPasswordHandler(context, user, clearUser,true);
    }

    ....

    if (password != null && !password.isEmpty() &&    // 



Warning:

V6007 Expression 'password != null' is always true.

V6007 Expression '!password.isEmpty()' is always true.

Here are two warnings in one line! What does the analyzer warn us about? The first conditional statement checks that the password is non-null and non-empty. If the opposite occurs, the function is no longer executed. In the line the analyzer highlighted, both of these checks are repeated, so the conditions are always true.

On the one hand, it's better to check than not to check. On the other, such duplicates may lead to missing the part of the condition that may be equal to false—it can really affect the program operation.

Let's repeat the exercise.

Fragment N11

public KeycloakUriBuilder schemeSpecificPart(String ssp)
  throws IllegalArgumentException {
    if (ssp == null) 
      throw new IllegalArgumentException(....);
    ....
    if (ssp != null) // 



Warning:

V6007 Expression 'ssp != null' is always true.

In general, the case is the same. If the ssp variable is null, the program throws an exception. So, the condition below isn't only true all the time but is also redundant because the corresponding code block will always be executed.

The condition in the next fragment is also redundant.

Fragment N12

protected String changeSessionId(HttpScope session) {
  if (!deployment.turnOffChangeSessionIdOnLogin()) 
    return session.getID();     // 



Warning:

V6004 The 'then' statement is equivalent to the 'else' statement.

In this method, the same code is executed under different seasons, the moon phases, and, most importantly, under different conditions. So, again, the condition is redundant here.

Digging into the code, I found the code snippet that is like two drops of water similar to the one above:

protected String changeSessionId(HttpSession session) {
  if (!deployment.turnOffChangeSessionIdOnLogin()) 
    return ChangeSessionId.changeSessionId(exchange, false);
  else return session.getId();
}

As you can see, when the condition is executed, the method that changes the session ID is called.

So, we can make two guesses: either devs just copied the code and changed the condition result, or the first condition still should have updated the session, and this error goes way beyond the "sloppy" code.

But we mustn't live by redundant conditions alone*!*

Fragment N13

static String getParameter(String source, String messageIfNotFound) {
  Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);

  while (matcher.find()) {
    return matcher.group(1).replaceAll("'", ""); // 



Warning:

V6037 An unconditional 'return' within a loop.

I have a feeling this while *was raised by *ifs. This code may have some hidden intentions, but the analyzer and I see the same thing here: a loop that always performs one iteration.

The code author might have wanted a different behavioral outcome. Even if they don't want, we'll find this code a bit harder to understand than if there is just a conditional operator.

String comparison

In the following snippet, a developer suggests everything is so easy. But it turns out it's not.

Fragment N14

public boolean equals(Object o) {
  if (this == o) return true;
  if (o == null || getClass() != o.getClass()) return false;

  Key key = (Key) o;

  if (action != key.action) return false;         // 



Warning:

V6013 Strings 'action' and 'key.action' are compared by reference. Possibly an equality comparison was intended.

Comparing strings implies that we compare their contents. We actually compare object references. This also applies to arrays and collections, not only strings. I think it's clear that in certain cases, code operations can lead to unexpected consequences. Most importantly, it's pretty easy to fix such an error:

public boolean equals(Object o) {
  ....
  if (!action.equals(key.action)) return false;
  ....
}

The equals method returns a comparison exactly to the contents of two strings. Victory!

I'll draw your attention to the fact that the static analyzer has detected the error, which a developer would most likely have missed when reviewing the code. You can read about this and other reasons for using static analysis in this article.

Double-checked locking

Double-checked locking is a parallel design pattern used to reduce the overhead of acquiring a lock. We first check the lock condition without synchronization. If it's encountered, the thread tries to acquire the lock.

If we streamline it, this pattern helps get the lock only when it's actually needed.

I think you've already guessed that there are bugs in the implementation of this template as I've started talking about it. Actually, they are.

Fragment N15

public class WelcomeResource {
  private AtomicBoolean shouldBootstrap;

  ....

  private boolean shouldBootstrap() {
    if (shouldBootstrap == null) {
      synchronized (this) {
        if (shouldBootstrap == null) {
          shouldBootstrap = new AtomicBoolean(....);
        }
      }
    }
    return shouldBootstrap.get();
  }

Warning:

V6082 Unsafe double-checked locking. The field should be declared as volatile.

The analyzer warns that the shouldBootstrap field doesn't have the volatile modifier. What does it affect? In such code, it's likely that different threads use an object until they're fully initialized.

This fact doesn't seem to be that significant, does it? In the next example, the compiler may change the action order with non-volatile fields.

Fragment N16

public class DefaultFreeMarkerProviderFactory 
  implements FreeMarkerProviderFactory {

    private DefaultFreeMarkerProvider provider;   // ();
            }
            kcSanitizeMethod = new KeycloakSanitizerMethod();
            provider = new DefaultFreeMarkerProvider(cache, kcSanitizeMethod);
          }
        }
      }
      return provider;
    }
}

Warning:

V6082 Unsafe double-checked locking. The field should be declared as volatile.

Why don't developers fix these code fragments if the bug is so dangerous? The error is not only dangerous but also sneaky. Everything works as it should most of the time. There are lots of different reasons why bad behavior can occur, due to, let's say, used JVM or the thread scheduler operations. So, it can be quite hard to reproduce the error conditions.

You can read more about such errors and their reasons in the article.

Conclusion

At the end of this article, I'd like to point out that I used the analyzer a little out of order. I just wanted to entertain you and show you the bugs in the project. However, to fix errors and prevent them, it's better to use the static analyzer regularly while writing code. You can read more about it here.

However, the analyzer helped us spot various errors related both to the program operation and insufficient code cleanliness (I still think it's important, and you'll hardly change my mind). Errors are not the end of the world if you spot and fix them in time. Static analysis helps with this. Try PVS-Studio and use it to check your project for free.

版本声明 本文转载于:https://dev.to/anogneva/authorization-pitfalls-what-does-keycloak-cloak-cf2?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何在 Go 中跟踪 HTTP POST 请求的进度?
    如何在 Go 中跟踪 HTTP POST 请求的进度?
    Go 中跟踪 HTTP POST 请求的进度通过 POST 请求发送大文件和图像时,开发者经常面临跟踪上传进度的挑战。本问题探讨了一种可靠的方法来监控 Go 应用程序中此类请求的进度。该问题建议手动打开 TCP 连接并分块发送 HTTP 请求。但是,此方法可能会遇到 HTTPS 站点的限制,并且不被...
    编程 发布于2024-11-06
  • 如何在 Java 中获取文件夹中的文件名列表?
    如何在 Java 中获取文件夹中的文件名列表?
    使用 Java 获取文件夹中的文件名获取目录中文件名列表的任务是各种环境中的常见需求编程场景。要在 Java 中实现此目的,有一种简单的方法,即利用 File 类。代码方法:首先,使用所需的目录路径实例化 File 对象:File folder = new File("your/path&...
    编程 发布于2024-11-06
  • 角管:综合指南
    角管:综合指南
    Angular 中的 Pipes 是简单的函数,用于在不修改底层数据的情况下转换模板中的数据。管道接收一个值,对其进行处理,然后返回格式化或转换后的输出。它们通常用于格式化日期、数字、字符串,甚至数组或对象。 它们允许您直接在视图中以更具可读性或相关性的格式格式化和显示数据,而无需更改底层数据模型。...
    编程 发布于2024-11-06
  • Tailwind CSS 和深色模式
    Tailwind CSS 和深色模式
    在本文中,我们将探讨如何在 Tailwind CSS 中实现深色模式。深色模式已成为流行的设计趋势,因为它可以在低光环境下提供更好的用户体验并减轻眼睛疲劳。 Tailwind 可以通过其内置实用程序轻松支持暗模式。 1. Tailwind 中的深色模式如何工作 Tailwind 提供...
    编程 发布于2024-11-06
  • 如何使用 CakePHP 的 Find 方法执行 JOIN 查询?
    如何使用 CakePHP 的 Find 方法执行 JOIN 查询?
    CakePHP Find 方法与 JOINCakePHP find 方法提供了一种从数据库检索数据的强大方法,包括连接表。本文演示了使用 CakePHP 的 find 方法执行 JOIN 查询的两种方法。方法 1:利用模型关系此方法涉及定义模型之间的关系并使用可遏制的行为。考虑以下模型关系:clas...
    编程 发布于2024-11-06
  • 如何在 Python 中重用生成器而不重新计算或存储结果?
    如何在 Python 中重用生成器而不重新计算或存储结果?
    通过重置在 Python 中重用生成器在 Python 中,生成器是用于迭代元素序列的强大工具。但是,一旦迭代开始,生成器就无法倒回。如果您需要多次重用生成器,这可能会带来挑战。重用生成器的一个策略是再次重新运行生成器函数。这将从头开始重新启动生成过程。然而,如果生成器函数的计算成本很高,则这种方法...
    编程 发布于2024-11-06
  • 面向 JavaScript 开发人员的热门 S 代码扩展
    面向 JavaScript 开发人员的热门 S 代码扩展
    JavaScript 正在快速发展,围绕它的工具生态系统也在快速发展。 作为开发人员,您希望使您的工作流程尽可能高效和流畅。这就是 Visual Studio Code (VS Code) 的用武之地。 我精心挑选了 5 个 VS Code 扩展,它们将显着增强您的 JavaScript 开发体验。...
    编程 发布于2024-11-06
  • 如何使用 HTML 输出标签来显示计算结果。
    如何使用 HTML 输出标签来显示计算结果。
    欢迎回来!我希望每个人都度过愉快的周末。今天,让我们回到 HTML 标签并重点关注 标签。 标签是什么? 标签用于显示计算结果。它是一个内联元素,可以放置在 、 或其他内联元素内。它通常用于显示计算结果或实时显示变量值。 阅读完整文章,实时观看并获取代码。 ...
    编程 发布于2024-11-06
  • Java:理解变量、数据类型和输入/输出
    Java:理解变量、数据类型和输入/输出
    介绍: Java 是世界上最流行、最通用的编程语言之一,它被用于从 Web 应用程序到移动应用程序的所有领域。如果您要开始 Java 之旅,了解基础知识至关重要。在本指南中,我们将深入探讨三个基本概念——变量、数据类型和输入/输出操作——它们构成了任何 Java 程序的支柱。在读完...
    编程 发布于2024-11-06
  • 如何根据 Div 的高度保持其纵横比?
    如何根据 Div 的高度保持其纵横比?
    根据高度维护 Div 的长宽比在网页设计中,控制元素的长宽比对于响应式布局至关重要。本题探讨了如何保持 div 的宽度占其高度的百分比,确保元素的形状保持一致,无论其高度如何变化。传统方法是使用 padding-top 来设置 div 的高度一个元素,而 padding-left 可以用作对象宽度的...
    编程 发布于2024-11-06
  • 在 Flet 中处理 DatePicker
    在 Flet 中处理 DatePicker
    我需要执行 DatePicker 的项目。 Veamos el ejemplo que proporciona la documentación oficial de Flet. import datetime import flet as ft def main(page: ft.Page): ...
    编程 发布于2024-11-06
  • 如何调整图像大小以适合圆形 SVG 蒙版?
    如何调整图像大小以适合圆形 SVG 蒙版?
    调整图像大小以适合圆形 SVG 路径尝试使用 SVG 路径从图像中剪切圆形部分时,这一点很重要以确保正确对齐。如果图像不太适合,可能是由于 SVG 蒙版的大小或位置不正确。这里有一种实现所需结果的替代方法:使用增强SVG 蒙版:此方法使用 SVG 蒙版创建一个圆孔,在其中显示图像:<svg w...
    编程 发布于2024-11-06
  • 技术面试问题 - 部分打字稿
    技术面试问题 - 部分打字稿
    Introduction Hello, hello!! :D Hope you’re all doing well! How we’re really feeling: I’m back with the second part of this series. ? In this...
    编程 发布于2024-11-06
  • 如何在 Laravel Eloquent 中为每个唯一的“seller_id”选择具有最大“created_at”的行?
    如何在 Laravel Eloquent 中为每个唯一的“seller_id”选择具有最大“created_at”的行?
    Laravel Eloquent: Select Rows with Maximum Created_at在 Laravel Eloquent 中,你可能会遇到需要选择所有具有最大值的行的场景表中每个唯一的 seller_id 的created_at 值。以下是实现此目的的方法:使用原始 SQL 查...
    编程 发布于2024-11-06
  • ReactJS 中的延迟加载:开发人员指南
    ReactJS 中的延迟加载:开发人员指南
    延迟加载是 ReactJS 中一项强大的技术,它允许组件或元素仅在需要时才加载,从而增强了 Web 应用程序的性能。在本文中,我们将探讨延迟加载的概念、它的好处,以及如何使用内置的 React.lazy() 和 React.Suspense 特征。   什么是延迟加载? 延迟加载是W...
    编程 发布于2024-11-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3