This post describes what Bambu has implemented in their Authorization Control scheme, with an emphasis on the cryptographic system involved. Cryptography is what ultimately defines what you can and cannot do with hardware and software you have access to, and there is a lot of wiggle room here to achieve 3rd-party interoperability if you can break through Bambu’s attempts to obscure their software.
Forward Link to heading
I’m intentionally avoiding a detailed description of HOW I observed most of this since Bambu seems to be in the mood to threaten legally-dubious lawsuits recently for just USING their software in a way they don’t like. Besides, if I did then they might decide to go change things to make analysis harder in future versions.
The content of this post is primarily about the Linux libbambu_networking.so v02.06.00.50 of the network plugin that is being distributed at the time of writing (SHA256: 6dd1491ef3038e5ed76a330e0ccbe9ae083635b7b9f2a1ba824a14c771b53fb6), though much of it is probably the same on other systems.
This post contains my own ideas and opinions, and nothing herein should be considered reflective of the official positions or opinions of any company or group that I am affiliated with- past, present, or future.
General References Link to heading
My research began with these references, which serve as a pretty good base for starting to understand how the control scheme works if you’re interested in doing similar research:
- Bambu’s official security white paper: https://cdn1.bambulab.com/trust-center/file/bambulab-security-whitepaper-en.pdf (primarily sections 2.3.4 and 3.1)
- https://github.com/coelacant1/Bambu-Lab-Cloud-API (contains a fair amount of wrong/outdated/incomplete info at the time of publishing this post)
- https://github.com/ClusterM/open-bamboo-networking/blob/master/NETWORK_PLUGIN.md
Under the Banner of Security Link to heading
(This section is historical context and an analysis of Bambu’s justification for the changes; skip it if you just want to get to the technical content)
In 2025 Bambu Labs announced changes to how their 3D printers would be allowed to interact with 3rd-party products and software, which had historically been a staple benefit of buying their printers. Their initial announcement seemed to almost entirely preclude 3rd-party local access, and after immense community backlash they “clarified” (or backpedaled, if you’re cynical) that the existing local access methods would still be available via a separate Developer Mode. But critically, Developer Mode would disable all official cloud features and integration. In early 2025 this change began rolling out in new beta firmware updates and Bambu Studio releases. Probably the most annoying effect of this change that most users have experienced is that you can no longer use OrcaSlicer to send prints directly to your printer (evenlocally) if your printer is also cloud-connected.
Bambu claimed that these changes were necessary for security. In the current version of the FAQ in that 2025 blog, Bambu gives this explanation (emphasis added):
This security update is necessary to enhance the overall security of your printer. By ensuring that all interactions with the hardware—such as moving axes, heating components, or performing other critical actions—are verified and secure, we can minimize risks and prevent potentially dangerous situations. Additionally, over the past year, we’ve detected an increase in the number of requests made to our cloud services through unofficial channels. These incidents have included significant abnormal traffic patterns and, in some cases, targeted DDoS attacks that have impacted service availability. Our monitoring systems have detected peaks of up to 30 million unauthorized requests per day, creating unnecessary strain on our infrastructure.
As I read it, they call out two reasons for these changes: preventing dangerous commands from being sent to the printer, and because their cloud servers are receiving too many “unauthorized” and/or “unofficial” requests.
The fact that they differentiate between “unauthorized” requests and “unofficial” channels is telling; I assume that “unofficial” in this context means “coming from unofficial 3rd-party software.” But they can’t just lump that traffic into the actually-problematic “unauthorized” traffic because their servers do require users’ authorization tokens to be present to process 3rd-party requests related to printer control. They just didn’t like that they were supporting legitimate user traffic that was originating from software outside their own ecosystem.
However, the cloud traffic argument alone doesn’t justify cutting off local 3rd-pary access to cloud-connected printers. So they also threw in a vague concern that “unverified” interactions are dangerous and need to be prevented. THIS is the justification for blocking 3rd-party local access, conveniently lumped in with the unrelated cloud-specific concerns.
In my opinion, of the concerns listed only intentional DDoS is a legitimate concern that offers some justification for restricting cloud access - but it only applies to cloud services, not local interactions. It has virtually no bearing on 3rd-party local access to cloud-connected printers, unless they are doing something stupid like mirroring all local traffic back to their cloud servers.
“Preventing dangerous commands” is a legitimate concern, but the solution is already in place: require the access code displayed on the printer screen to send commands to the printer. You can further harden that if needed (make the code longer or more complex, for example) but that’s a perfectly serviceable security measure that doesn’t need changing, if implemented correctly. Properly validating commands sent through the cloud (i.e., user input) is another basic best practice that they apparently beefed up when they noticed they had some holes there. These are real security solutions.
Lock-Out to Lock-In Link to heading
In early early 2025 they began rolling out the new “Authorization Control” features for cloud-connected printers via optional firmware updates. Users of pre-existing devices were (and currently still are) free to continue to use older firmware versions to maintain the same cloud-and-local access that always existed. But they miss out on new features, and this is not an option for newer printers released after the change. I strongly suspect that Bambu will drop cloud support for these old firmwares at some point in the future to force users into the new paradigm.
In a nutshell, this change separated printer commands into two categories: I’ll call them “safe” and “privileged” (the whitepaper calls the latter “critical operations”). Most or all commands that only read data back from the printer are considered “safe” and still accessible to 3rd party software, same as they were prior. But commands that control the printer or change settings are privileged, and no longer accessible to 3rd-party software. This change tightens Bambu’s control over users, locking out 3rd-party integration in order to lock users more into Bambu’s tools and cloud infrastructure.
For the rest of this post, unless otherwise specified, “command” refers to privileged commands. Similarly, I’m only discussing printer that are cloud-connected, not in LAN-Only or Developer Mode.
Bootstrapping the Chain of Trust Link to heading
How is this lockout achieved? Bambu printers now require that privileged commands be signed with a Bambu key. Identifying which keys belong to Bambu is done with certificates. When a printer powers on, it initially trusts no keys for signing. To add one, a Bambu application must supply a chain of certificates that are signed by Bambu in a specific manner. I’ll call that process “bootstrapping.” The technical specifics of that process are detailed later in the Signing Messages section, while a high-level overview is provided here.
The printer (presumably) has a copy of the top-level certificate (a root CA) embedded in its firmware. When it receives a certificate chain from an application, it validates the first cert against the embedded root CA. Then it validates the second cert against the first, and the third against the second. Each cert signs the cert below it, until the final “leaf” cert (linked to the command signing key) is reached. If every cert in that chain is properly signed all the way back to the embedded CA cert, the command signing key associated with the leaf cert becomes trusted.
Different Bambu applications (like Bambu Studio networking plugins on Windows, Linux, and MacOs, Bambu Connect, and Bambu Handy) all use a different key, and each gets its own cert signed by Bambu. Printers can have multiple active trusted certs, so that they will trust commands sent/signed by different Bambu applications.
You can freely ask your printer for a list of leaf certs it currently trusts for command signing. Here’s some python code to do that, just fill in your printer details in the top three variables:
#!/usr/bin/env python3
import json, ssl, time
try:
import paho.mqtt.client as mqtt
except ImportError:
raise SystemExit("paho-mqtt is required: pip install paho-mqtt")
PRINTER_IP = "192.168.1.100"
ACCESS_CODE = ""
SERIAL = ""
def query_certs(host, code, serial, timeout=8.0):
out = {}
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
c.username_pw_set("bblp", code)
c.tls_set_context(ctx)
def on_connect(c, *_):
c.subscribe(f"device/{serial}/report")
c.publish(f"device/{serial}/request", json.dumps({"security": {
"command": "app_cert_list", "sequence_id": "qcl",
"timestamp": int(time.time()), "type": "app"}}))
def on_message(_c, _ud, msg):
sec = json.loads(msg.payload).get("security", {})
if sec.get("command") == "app_cert_list":
out.update(result=sec.get("result"), cert_ids=sec.get("cert_ids", []))
c.on_connect = on_connect
c.on_message = on_message
c.connect(host, 8883, keepalive=30)
c.loop_start()
deadline = time.time() + timeout
while time.time() < deadline and not out:
time.sleep(0.1)
c.loop_stop()
c.disconnect()
return out
r = query_certs(PRINTER_IP, ACCESS_CODE, SERIAL)
ids = r.get("cert_ids", [])
print(f"result={r.get('result')} certs_trusted={len(ids)}")
for cid in ids:
print(f" {cid}")
The Network Plugin Link to heading
After bootstrapping is complete, Bambu Studio needs to sign privileged MQTT commands send to the printer using the associated private key. But Bambu REALLY doesn’t want anyone else to be able to get ahold of a valid private key, because that could be used to enable 3rd-party software interoperability for printer control- exactly the thing they are really trying to lock out with these changes.
So Bambu uses a closed-source networking plugin to help with this process. This plugin’s code is heavily obfuscated and contains extensive anti-debug features to try to prevent anyone from extracting the keys it uses. It basically tries to acts like a software version of a HSM, hiding key material from the open-source Bambu Studio process behind heavy software obfuscation.
Analyzing the obfuscated plugin directly would be no easy task, since it actively works to prevent analysis. Bambu’s whitepaper explicitly mentions the use of virtual machines, obfuscation, and anti-debug features (sections 3.1.2-4), among others. The Windows version of the plugin apparently uses commercial VMProtect software, and I’m sure the Linux/Mac ones use something similar. Despite all that, we can get an idea of how the current plugin works by analyzing the Bambu Connect code that got dumped shortly after the authorization changes were announced. We can then compare that to some intercepted network traffic and get a pretty good idea of what’s going on.
The Previous Leak Link to heading
Shortly after Bambu announced these changes, the private key and associated code used by Bambu Connect got dumped. From what I understand, Bambu reworked the code for Connect to make it much harder to dump after that, but the old dump still gives us useful insight into the process to obtain the private key, at least as it existed then.
This code contains several hardcoded cryptographic fields of note:
freis the cloud server’s certificatej_contains the private key and cert for the application, plus a certificate chain (for “bootstrapping”) that leads back to a “BBL CA” root certificate, and a CRL. The private key is what ultimately “proves” that network requests made by the application application are legit.Areis an app identifier, comprised of the application cert’sSubjectfieldCNvalue (GLOF3813734089-524a37c80000), plus the seemingly-random 16-characterc6a6a274a47b3281hex string.
Here’s a visual breakdown of the certs and their relationships:
CN=BBL CA (C=CN, O=BBL Technologies Co., Ltd)
└─ offline root — NOT present in the code
│ signs
▼
┌───────────────────────────────────────────────────┐
│ application_root.bambulab.com (top cert in chain) │ CA:TRUE, pathlen:2
│ serial …F50F0988 │ 2024-05-29 → 2034-05-27
│ SKI/AKI anchor for everything below │
└───────────────────────────────────────────────────┘
│ signs │ signs
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────────────────┐
│ service.bambulab.com │ │ GLOF3813734089.bambulab.com │ CA:TRUE, pathlen:1
│ (in 'fre' variable) │ │ (intermediate CA in chain) │ AKI → root SKI
│ serial …F50F0992 │ │ serial …F50F0994 │ SKI C2:C9:D1:2D…
│ CA:FALSE (leaf) │ │ 2024-08-02 → 2034-07-31 │
│ 2024-07-26 → 2034-07-24 │ └──────────────────────────────────────┘
│ AKI → root SKI │ │ signs │ signs
└─────────────────────────┘ ▼ ▼
┌─────────────────────────┐ ┌────────────────────┐
│ GLOF…-524a37c80000 │ │ CRL │
│ (j_.cert) │ │ issuer = GLOF… │
│ priv key = j_.privateKey│ │ AKI C2:C9:D1:2D… │
│ serial EE3C…BA2C │ │ revokes 2 serials │
│ CA:FALSE (client ID) │ └────────────────────┘
│ 2024-12-11 → 2025-12-12 │
│ AKI → intermediate SKI │
└─────────────────────────┘
The j_ variable alone is notable because it shows the private key is embedded in the application itself. However, in addition to that, the application also has code to obtain a fresh cert and private key from the server. Probably to handle cases when a key gets leaked and needs to be revoked, but a new plugin with the new key isn’t ready for distribution yet, or not everyone has had time to upgraded to the new one yet. The presence of a non-empty hardcoded Cert Revocation List also supports this kind of scheme.
The Fresh Key Process Link to heading
On startup, the old code generates a random 32-bye AES session key. It uses that key to AES-GCM encrypt the string GLOF3813734089-524a37c80000c6a6a274a47b3281 (contained in variable Are). This results in a typical Nonce||Ciphertext||Tag binary blob.
The AES session key is then encrypted using the server’s public key (the fre variable), ensuring that it cannot be extracted from network traffic even if TLS is decrypted.
The binary blob and encrypted session key are injected into a request URL that is then sent to the server. The request also contains a couple x-bbl-* headers that identify the application’s cert and prove that the requestor actually possesses the correct private key by signing a current timestamp with it.
(Inferred) If the headers check out, the server decrypts the AES session key using the private key associated with the fre cert, then decrypts the binary blob using that session key. If the binary blob decrypts to the expected cert identifier and semi-secret string combo, the server sends back a fresh cert and private key for the application.
The returned private key is encrypted using the same random session key that was sent to the server. This ensures only the application can decrypt it, and that session key disappears forever when the program closes. The encrypted key blob has the same Nonce||Ciphertext||Tag AES-GCM format that the encrypted secret-string did.
The fresh decrypted private key can then be used to sign MQTT messages. But why bother with this process when the private key is already embedded in the code? Probably to speed up key revocation. After a key is compromised, pushing out a new network plugin with an updated key can take a while (especially since Bambu has to go ask whatever company is providing the obfuscation software to generate a new one). With this scheme they can push an updated cert chain, private key, and (most importantly) CRL that revokes the leaked key to cloud-connected applications right away, while the updated networking libraries get sorted.
Since bootstrapping is performed by the application (not pushed to the printer directly from the cloud) this also means that many LAN-Only printers will get the updated CRL, as long as the computer that the Bambu application is running on is able to connect to the Internet.
The Current Cert API Link to heading
Now let’s explore how that old code compares to what we can see in the current network traffic generated by Bambu Studio with the network plugin.
The Request Link to heading
On startup, Bambu Studio generates a request like this, presumably asking the plugin to fill in the app-id and C variables just like the old Bambu Connect code did:
https://api.bambulab.com/v1/iot-service/api/user/applications/<app-id>/cert?aes256=<C>&ver=1
This request contains two variables: an app-id and a ciphertext C.
- App-id is a Base64URL-encoded string representing 71 bytes, which appear to be random and/or encrypted.
- C is a Base64URL-encoded string representing 256 random/encrypted bytes, or 2048 bits.
Semantically, this request represents the application requesting a certificate and supplying an AES256 key, just like the old code did.
The Response Link to heading
The server sends back a JSON response that looks like this:
{
"message": "success",
"code": 0,
"error": null,
"cert": "[3 PEM certificates]",
"crl": [
"[An empty Certificate Revocation List in PEM format]"
],
"key": "[some base64 data]"
}
Semantically, this response provides a RSA2048 certificate and key, plus bootstrapping chain and CRL. If you interact with your printer through Bambu Studio after this request is made, and then run the previous python script to check which certs your printer currently trusts, you will find that the first cert returned in the cert field here is trusted by the printer.
So it stands to reason that the key field contains the associated RSA2048 private key used to do the message signing, but of course it appears to be encrypted.
Analyzing the Ciphertexts Link to heading
As expected based on the old leaked code, the aes256 request parameter value is 256 bytes long (2048 bits), consistent with encryption under the server’s public key. That way only the Bambu server can decrypt it, using a private key that never leaves the server. That’s a pretty standard way of sending a shared key to another party without exposing it to eavesdroppers, the plugin just needs to have the server’s public key embedded in it.
Now, what’s in the response key ciphertext? If you capture several of these messages and compare them, you’ll find that they actually have some visible structure. The first 28 bytes appear random, but then there’s always the same 4 bytes next: A0 02 00 00. If you read this as a little-endian 4-byte integer, it decodes to decimal 672. 672 is exactly the number of bytes remaining in the binary blob, so this 4-byte chunk is a length value.
What about the 28-byte header? Well, AES256-GCM typically requires a 12-byte Nonce (similar to an IV) for encryption and decryption. And the encryption process also generates a 16-byte tag that can be used to verify the ciphertext is valid before attempting to decrypt it. 12+16=28, which would explain the header.
What about the ciphertext itself? The bytes appear to be uniform random, as expected. But the length is somewhat telling. A RSA2048 private key in a standard DER encoding takes up roughly 1200 bytes, almost double the 672-byte ciphertext length. It could be compressed, but that adds additional complexity that you typically want to avoid when the decryption has to be done by a heavily-obfuscated VM core.
There are a few different standard algorithms that you can use to perform the RSA signing process. One of them is based on the Chinese Remainder Theorem. I won’t discuss how that works, but the import part is that the algorithm uses 5 different values related to the RSA private key, and (for a RSA2048 key) each of those values is 128 bytes in size. 128 x 5 = 640, just 32-bytes shy of the 672 ciphertext block.
AES-GCM is a length-preserving cipher, so there’s definitely another 32 bytes of data unaccounted for (if it is indeed the CRT values). This could be explained by some combination of headers, trailers, field delimiters, length prefixes, content hashes, magic bytes, or some custom nonsense. My guess is it’s either an integrity hash of the plaintext or a certificate identifier.
Finally, what role does the app-id play in all this? If the plugin is using the same process as the old leaked Bambu Connect code, it’s an AES-GCM encrypted blob that contains the cert CN (GLOF3813734089-xxxxxxxxxxxx), concatenated with a secret fixed 16-character hex string. That plaintext would have length 27 + 16 = 43 bytes. Add the 12-byte nonce and the 16-byte tag and you get 71 bytes. Exactly the size of the app-id after Base64-URL decoding it. This just serves to prevent you from generating your own request to the server with your own AES key, since you need to know that secret 16-character hex string.
Asking Nicely Link to heading
Assuming the cryptography on this side of the request hasn’t changed (and everything we can observe black-box points to it being the same), then in theory you could generate your own requests to ask the server to give you the private key, which it would encrypt with whatever 32-byte key you put in the aes256 parameter. But to do so you would first need to know what that secret 16-character hex string is for the target key. And that secret is likely locked away pretty tight inside the plugin obfuscation. Guessing it through brute-force isn’t an option; assuming it’s actually random, that’s 16^16 = 18 quintillion possible values to get through. You would have to send 3 billion network requests every second for 100 years to have a 50% chance of guessing it.
That said, while the request data looks identical, the response returned by the server has changed a bit. It’s no longer Nonce||Ciphertext||Tag, it’s Header||Length||Ciphertext, where Header is likely Nonce||Tag. This appears to be fundamentally the same cryptographic response, just rearranged a bit. So there’s nothing that obviously indicates the decryption process has changed, but there’s always the possibility that Bambu added some additional obfuscation steps in there somewhere.
Finding the Cracks Link to heading
On the whole, this looks like a pretty strong cryptographic scheme from the outside. There’s no way to extract the private key from network traffic, or generate your own requests, assuming they used standard library implementations for all the crypto operations and an actually-random 16-char hex secret.
But what about the plugin itself? That’s where their “security” model starts to break down. In fact, they even acknowledge this as a known limitation in their whitepaper at the end of section 3.1.2:
We are well aware that even assets protected by virtual machines are not 100% secure against experienced attackers … we use virtual machine protection as a key component to significantly raise the attack barrier, which in turn reduces the risk of being compromised.
The other sections for different obfuscation techniques similarly acknowledge that their various “security through obscurity” (my words, not theirs) efforts merely “reduce the risk” of compromise (their words), and that skilled people can break through all that to access the protected assets.
So where exactly would the weaknesses be in this scheme, assuming my analysis is correct? Based on that paper and what can be observed in network traffic, that plugin is likely designed to protect key material, primarily the private key that signs printer commands. As an extension of that goal, it must also necessarily protect the linchpins of the cert API request: the ephemeral AES session key, and the secret 16-character hex string.
An RSA private key is much bigger than an AES256 key, so I suspect they keep that encrypted server response sitting in memory and do just-in-time decryption of the private key every time it’s used; that would minimize the time it’s exposed in memory, and is consistent with the “decrypted only when needed” language in section 3.1.5.
If I was one of those sufficiently skilled and motivated people, here’s where I would go looking for key material, roughly in order of priority:
Decryption Time Link to heading
If you manage to identify and hook into the decryption routine, you can just let the plugin do all the de-obfuscation for you. It has to properly decrypt the private key from the server response at some point, and you might be able to dump it right as it finishes the decryption.
Signature Time Link to heading
Every time Bambu Studio sends a privileged command to a printer, the network plugin has to sign it. If you identify and hook that signing operation right when it uses the private key, you’d get it directly.
aes256 Parameter Link to heading
The client encrypts the session AES key using the server’s public key. If you can identify where this encryption happens, you can grab the plaintext key right before it gets encrypted.
This not only lets you decrypt the private key in the server response (which you’d have to also capture from network traffic at the same time), but also probably lets you decrypt the app-id field and get the secret hex-string. Then you could generate your own requests to the server with whatever AES key you want and easily decrypt the response.
app-id Value Link to heading
The secret hex string for the key is either hardcoded into the plugin, or derived from the certificate CN at runtime. Either way, if you extracted that (either statically or by dumping it at runtime), you could try to generate a request to the server using your own AES key. If the scheme hasn’t changed since the Bambu Connect leak, you get a response and decrypt the private key. But if it has changed, you hit a dead-end and have to go back and try the aes256 parameter approach to figure out what the new request format is.
AES Session Key Link to heading
The network plugin generates this once at startup, and then uses it a few times: to generate the aes256 parameter, to encrypt the app-id value, and to decrypt the server response (possibly multiple times). If you dump it at any of these times, you can decrypt the captured server response and get the private key.
Server Public Key Link to heading
The network plugin encrypts the AES session key using the public RSA key contained in a hardcoded certificate. If you could trick the plugin into using a different RSA key for that step, you could easily decrypt the AES key from the network traffic. Then decrypt the captured server response.
Static Analysis Link to heading
Regardless of what obfuscation methods the plugin uses, at the end of the day it still has to do everything in x86 assembly (or amd64 or ARM64 or whatever). With enough dedication and patience you could always do a full static analysis of the raw plugin bytes and extract the key embedded in it. But I suspect that’s the kind of effort typically reserved for a PhD thesis; I wouldn’t bother with it.
Signing Messages Link to heading
If you did get access to the private key, how would you actually use it? The basic approach has already been documented briefly here and in some more detail here (Ctrl+F for RSA_SHA256):
{
"header": {
"cert_id": "<cert_serial>CN=<issuer_CN>",
"payload_len": 1234,
"sign_alg": "RSA_SHA256",
"sign_string": "<base64_signature>",
"sign_ver": "v1.0"
},
"print": { … }
}
But after analyzing the network traffic for the current Bambu Studio and network plugin, I noticed some important steps were missing. If you power-cycle your printer and then run the Python script (near the top of this post) to see what certs it trust, it won’t return any. Each app has to “bootstrap” its own cert before the printer will accept it. This is accomplished using the app_cert_install command published on device/<serial>/request MQTT topic:
{
"security": {
"app_cert": "<full 3-cert PEM chain>",
"command": "app_cert_install",
"crl": "<CRL PEM>",
"sequence_id": "<digits>"
}
}
app_certis just a copy of thecertfield from the cert API response; it contains this app’s cert chain, including the leaf cert corresponding to the private key.crlis similarly just the copiedcrldata from the same server responsesequence_idis whatever you want, just has to increment with each subsequent request you send.
Sending that command (assuming all the contents validate correctly) causes the printer to trust that leaf cert for signing. If things don’t validate correctly the printer will publish an error code on the response topic. Since this request doesn’t have to be signed, you can publish this message yourself to your printer using the data from an intercepted cert API response, then use the provided Python script to verify that it was accepted by your printer.
The CRL Catch Link to heading
The cert for the private key is good for ~1.5 years (may vary by cert and circumstances), so in theory if you extracted it you could send all the 3rd-party signed messages you wanted for a long time before you had to go back and extract an updated key+cert. Unfortunately, the CRL returned by the server is currently only good for 1 month. And it’s signed by a key that only exists on Bambu servers, so generating your own is a non-starter.
But… the printer doesn’t actually check the expiration on the CRL! You can send an expired one and it’s happy to accept it. Which seems almost too good to be true; why would Bambu even bother spending resources making the servers return these every time someone starts Bambu Studio, and make the printers require them, if we can just replay old ones? If a key gets leaked and Bambu starts adding it to CRL responses, what good does that do if we can just send over an old empty CRL at the start of our 3rd-party session?
I suspect the answer is that the printers cache non-empty CRL revocations in volatile memory just like they cache trusted certs. You don’t have to send this bootstrap command before every request, but all of Bambu’s applications do so when they initiate a connection to your printer (rather than checking to see if it was already bootstraped). So if you’re trying to use a publicly-leaked key to sign 3rd-party commands to send to your non-dev-mode printer, it’s still going to get a fresh CRL any time an Internet-connected Bambu application talks to it. And if I’m correct, that will immediately lock out your 3rd-party code again.
Which is exactly what Bambu is trying to accomplish. They don’t care about accepting expired CRLs because security isn’t their real goal here; forcing you to choose between native cloud features and 3rd-party control is their real goal.
Practical Security Implications Link to heading
Security is clearly not Bambu’s actual goal here. So what attack vectors are they leaving open by building this 3rd-party lockout solution instead of an actual security solution? Turns out, not much, because the printer security scheme was already pretty solid before these changes.
The Access Code Gate Link to heading
You could completely rip out the new MQTT signing requirement and the security model for the local network would barely change, because you still have to have the printer’s current Access Code to talk to the printer at all. MQTT has authentication built into it, and Bambu printers have been using it for years. The access code is the password for that authentication.
Bambu tries to make a big deal about users’ “network security” being hard for most users to control, with potential for bad actors to be on the network. That’s a completely legitimate concern, especially for printers on company networks. But the existing Access Code authentication implementation already addresses that very well. A random 8-digit code has 100,000,000 possible values. At 1 request a second (conservative) it would take 3 years to test every code. Now, I suspect you might be able to squeeze 10 responses per second out of a printer, which brings that down to about 4 months to guess all codes for one printer. Which is admittedly a bit more practical for an attacker who is absolutely determined to mess with your printer.
But newer firmware and/or printer models add letters into that code. Assuming a properly random selection process, case-insensitivity, and dropping the ambiguous 0/O pair, that’s 34^8 = 1,785,793,904,896 possible combinations. Almost 18,000 times more. That 4-month hacker stretch goal becomes a 6,000 year impossible task. Bambu’s “security” changes are adding absolutely no security enhancements to this threat model.
To be fair, this model might add some security for commands sent through the cloud, rather than directly to the printer over the local network. Bambu implies that they’ve had issues with malicious commands being sent through their cloud before, and requiring signing for that route might make those attacks harder. But I didn’t look very closely at the cloud route for printer commands, and this argument still reeks of wanting to avoid putting in the work to properly sanitize and validate input server-side. But even if they do think this is valid approach for that, it provides no reason to block 3rd-party commands sent over the local network.
The Real Hacker Threat Link to heading
What happens if a top-tier hacker decides they want to send malicious commands to someone’s printer? These are the “skilled” people that Bambu mentions in their whitepaper. These kinds of people buy and sells exploits for millions of dollars. For these “skilled” people, extracting the private key from an obfuscated program is probably a relaxing downtime activity compared to their normal hacking efforts. I can almost guarantee that some of all of those private keys have already been extracted by some of those kinds of people, because good hackers love hacking things just to prove they can.
So while I maintain that a properly-implemented access code scheme is perfectly acceptable security against local network intrusions, remember that no matter what security Bambu claims it is adding with these changes, those protections (by their own admission) are not going to do anything to stop the most dangerous attackers- the ones who might actually have some motivation to cause physical harm or destruction against specific targets, rather than just wanting to steal your bank password or install crypto miners.
Profit Over Principles Link to heading
In summary, Bambu made a bunch of changes that have every appearance of being intentionally designed to do nothing more than lock out 3rd-party access, while throwing around flimsy security arguments to justify them. Their own whitepaper boasts about the security-through-obscurity mechanisms they built to reduce the risk of a vague “compromise.” They even had the audacity to include a line encouraging 3rd-party developers who are interested in a partnership with this new scheme to contact them (end of section 2.3.4), apparently with no real intention of actually partnering; OrcaSlicer tried and got told to use Bambu Connect like everyone else. Have you tried using Bambu Connect? I did once, just to see. It sucks.
But hey, at least Bambu’s cloud servers, already sitting safely behind Cloudflare, maybe aren’t getting getting hit with DDoS attempts anymore.
Solutions Link to heading
So what could Bambu do to fix all this nonsense, properly enhance security, and make all their users happy at the same time? Here are a few of my ideas:
Restore Local Control Link to heading
Bambu doesn’t want to pay to service 3rd-party requests through their cloud? Fine. Keep all the signing requirements in place for the cloud MQTT broker, but drop it for the local MQTT server hosted by the printer. Or at least make it a toggle button option, similar to Dev Mode. Then everyone can go back to using OrcaSlicer locally while still having the option to use Bambu Handy when needed.
That makes 99% of power users happy, and the remaining 1% might be satisfied enough with using TailScale to send remote print jobs via OrcaSlicer that they will no longer feel motivated to rip that private key out of the network plugin.
User-Managed Certs Link to heading
Add an option in the printer screen to enable user-controlled certs. Include whatever security warning Bambu wants, just like Dev Mode. Users add/change the cert from the USB or SD card, just like offline firmware updates. The printer then trusts messages signed with either this cert or the official ones pushed via the bootstrap process. Turn the toggle off and the cert gets wiped.
If Bambu insists on pretending like the signing requirement adds meaningful security, then this is the obvious way to merge that security with secure local 3rd-party software control.
If Bambu wanted to make this really secure (insofar as they claim their new model adds security), they could let users push a custom CA to the printer, which would work just like the BBL CA one that is presumably embedded in firmware. Then users could give each 3rd-party app its own cert+key signed under that CA, and use the exact same bootstrap process to register it with the printer.