Potential security vulnerability: Remote JSON-LD contexts may be used to bypass restrictions when arbitrary objects are allowed to be created

Assumption: Remote instance software adhere to the origin based security model and expand JSON-LD with support for fetching remote @context entries (not all do JSON-LD obviously, but in an ideal world (as expected by the spec anyway) they would).

Therefore, if a user can create an actor-like object with private keys in their control, they can impersonate any other actor on their instance.

There are several ways this can be done that can be checked for and blocked by the C2S server implementation, but what about remote contexts?

Sure you can validate them like the other variants of this security issue, but then the remote context could change from under you and turn previously-innocent fields into actor public keys.

Even if you cache the context and never update it ever, will remote instances?

What’s the solution to this hole beyond (I do not expect any of these to happen, btw)

  • dropping JSON-LD (or just support for unknown remote contexts, which would break federation with Pleroma and it’s forks) ecosystem wide
  • or somehow undoing the origin based security model

Perhaps:

  • asking all “dumb” implementations to separate their actors by subdomains
  • modifying objects and proxying all their contexts over the server’s cache
4 Likes

Interesting observation! I don't know the answer, and I didn't really take JSON-LD processing into account when working on FEP-fe34.

or somehow undoing the origin based security model

Replacing origin based security with actor based security doesn't seem to help. Where origin based security requires servers to reject public keys with a local origin, the actor based security requires servers to reject public keys controlled by other local actors. In both cases C2S server needs to reliably identify public keys and verification methods, and duck typing is the only method that is available to us.

dropping JSON-LD

This actually might happen.

@protocol

Origins host resources, but resources don’t always represent origins

The so-called “origin-based security model” as assumed here is provably false, as you have just demonstrated. The only way to reconcile this origin assumption with the actor model is if every origin has exactly 1 actor representing that origin, or if every agent gets their own origin. It is simply incorrect to assume that any actor resource on an origin is allowed to represent that entire origin (or that any key resource on that origin is allowed to represent that entire origin, which is even more incorrect).

This doesn’t have anything to do with JSON-LD, either – the fundamental error is in assigning a representative relationship between a key/actor and an origin simply by virtue of having an http(s) identifier within that origin’s authority. You must recognize that any resource being published on an origin might in fact have user-generated content. (If I am currently renting an apartment room, am I allowed to authorize things on my landlord’s behalf?)

JSON-LD isn’t relevant; any information needs to be trusted regardless

This doesn’t logically follow.

With regards to JSON-LD contexts, the processing of contexts only matters when you want to establish that we mean the same things when we use the same terms. For example, https://w3id.org/security/v1 defines the term CryptographicKey to be shorthand for https://w3id.org/security#Key. But for that same concept of a https://w3id.org/security#Key, you might call it a Key instead. The JSON-LD context definition allows us to recognize that what you call a Key and what I call a CryptographicKey are actually the same thing: a https://w3id.org/security#Key.

If we were to do away with JSON-LD contexts altogether, what you are effectively describing is a situation where user-generated content is not allowed to make statements as defined by the Security Vocabulary that a certain resource is a cryptographic key, or that a certain resource has public key material corresponding to some literal string, or so on – basically disallowing statements of the form ?s sec:publicKeyPem ?keymaterial. I don’t see why you would do this, since you should be allowed to assert such descriptive statements without it affecting anyone else except by whatever knowledge they import from you… and why should they trust arbitrary statements? (If we were to go further and do away with JSON-LD altogether, you would have to ban certain JSON property keys from being used as agreed upon by some central registry.)

How to solve the problem

Remote contexts shouldn’t pose an issue in the abstract sense, but I understand that JSON-LD contexts are currently mishandled in a variety of ways by a variety of current fediverse instance software.

With respect to the context changing, this can happen but generally should not. If it breaks your security assumptions, then your C2S server should disallow remote contexts… but there isn’t really justification for this idea, because as mentioned earlier, you can inline a context and then change the inlined context later, too. You could also not use any additional context at all. So the solution you’d want to pursue is not “drop JSON-LD”, but “disallow publishing statements using sec:publicKeyPem” (again, as brought up earlier). You’d need to check for these statements on any Create or Update, in theory.

Ultimately, the error is in the initial assumption that a key resource on an origin is authorized to represent that entire origin. Nothing good can come from that. This doesn’t fully “undo” the origin based security model, but it does mean you have to reconsider the incorrect parts of it. Like, really consider the assumptions you’re making here. How committed are you to the idea of “origins” as a security boundary? Would you expect everyone to have their own FQDN instead of allowing multi-tenancy? Do you want to disallow split storage models (where alice.social.example might have objects stored on cdn.social.example)? I’d go so far as to say that the primary value of the origin model is that it allows for authentication via TLS. If that doesn’t apply to your use case, then there isn’t much point to it.

1 Like

because as mentioned earlier, you can inline a context and then change the inlined context later, too.

Changing inline contexts can be validated during the processing of the Create or Update activities, that’s not an issue here. The issue is a remote context that may first present an innocent term definition, but changes after validation to be malicious.

A malicious remote context may even go so far as to change the contents of the file served to different IP addresses to evade this detection even more.

Ultimately, the error is in the initial assumption that a key resource on an origin is authorized to represent that entire origin. Nothing good can come from that.
[…]
Do you want to disallow split storage models (where alice.social.example might have objects stored on cdn.social.example)? I’d go so far as to say that the primary value of the origin model is that it allows for authentication via TLS. If that doesn’t apply to your use case, then there isn’t much point to it.

I agree here, and on my own software I plan to validate a bit more thoroughly than the origin, but the most important part of the question was actually in the title, being “federate with the current network?”, as in, remote software I have no control over that is actively federating right now, as we speak, and validating origins instead of doing any further checks. What is a C2S server supposed to do to protect itself under this context?

1 Like

I’m not sure if you mean something special by “C2S server” (beyond a server implementing the server side of the C2S API). The C2S API/protocol does not depend on federation. In other words, a server might only support C2S and not support S2S federation.

The authz/authn for C2S is not standardized, but typically OAuth 2 is used. That doesn’t use the actor public/private key or HTTP signatures, so I’m not clear about that (based on looking at the tests you referenced) or how it relates to JSON-LD context loading.

Can you give a more concrete example for this vulnerability? Thanks.

1 Like

C2S by itself isn’t actually relevant here. Any way for a user to place arbitrary ActivityPub objects in an instance they don’t own will encounter this issue. C2S just happened to be my own use case here.

1 Like

Shouldn’t that be part of a CVE? If indeed such possibility exists, there should at least be a way to document how to circumvent it, and most probably a addendum to the protocol to remove this vulnerability.

1 Like

What is to be done if everyone else makes the same mistake, you mean? I think the consequences mostly fall on the naive remote side rather than on your side, because the naive remote will be tricked into accepting certain HTTP messages, leading to an inconsistent state on their side. If you want to avoid the issue on their behalf, then you should never allow arbitrary untrusted user-generated content on your web origin. This applies regardless of whether AP C2S is used; the error is enabled just the same by a file upload over HTTP, FTP, SFTP, WebDAV, etc… but there are limits to how accommodating you can be of other people’s mistakes.

@how FEP-fe34 already contains requirements that prevent this vulnerability. Implementation of those requirements in an LD-unaware application is relatively straightforward: recursively scan every submitted activity and reject it when something that looks like a public key is found. I do this in my C2S server apps.

If a simple solution for LD-aware applications exists, I can describe it in the FEP. Otherwise I will just say that JSON-LD shouldn't be used.

remote software I have no control over that is actively federating right now, as we speak, and validating origins instead of doing any further checks

@kopper What further checks do you think they should perform? Not everyone validates origins, some validate ownership. However, as I mentioned in my previous comment, there doesn't seem to be any difference between those two approaches.

@protocol

1 Like

Maybe advise that a key on an origin does not automatically mean that the key represents the entire origin? In other words, check that the key has a https://w3id.org/security#controller or https://w3id.org/security#owner, then check if that object resource links back to the key via the appropriate relation (either the older https://w3id.org/security#publicKey, or the newer https://w3id.org/security#authentication, https://w3id.org/security#assertionMethod, etc. as appropriate for the use case). If you do this, the origin doesn’t matter anymore except perhaps when validating claims of attribution (as in http://joinmastodon.org/ns#attributionDomains) without proof (which would be verified by sec:assertionMethod)

I'm updating the FEP with a clarification on how public keys are identified and a warning for JSON-LD consumers:

https://codeberg.org/fediverse/fep/pulls/662/files

It also explains why same-origin policy is recommended for keys and not same-owner.

@protocol

Clients would be permitted to create public keys if the same-owner policy was used instead of the same-origin policy. However, servers would still need to identify all public keys and verify their owners in order to prevent impersonation of other local actors. The same-origin policy is recommended here for simplicity.

But the “same-origin policy” cited here is simple to the point of being incorrect.

When the rest of the web refers to “same-origin”, they mean “resources on the same web origin are generally safe to load”, not “any/all keys on an origin always represent the origin itself”. The former is a content security policy to prevent loading resources outside of “your” control from different origins or inline sources, where “you” are the web origin as authenticated by TLS. The latter is an incorrect assumption about who owns the resource, which doesn’t reflect the reality of the situation.

To put it more directly: this “same-origin policy” is incompatible with arbitrary user-generated content. It presupposes that all content on an origin is attributed to, owned by, or controlled by the same entity which is the single controller of that origin. In other words, “same-origin” is just “same-owner” if you ignore the actual owner and assume it’s always the origin. It can only ever work if origin == owner == a single entity, which is not the case when origins host users who generate arbitrary content. The workaround being advocated here is to not only disallow arbitrary user-generated content for your own origin, but to foolishly hope that everyone else does the same. This will never be true in an open world system like the web, where anyone can say anything about anything.

The text says that the same-owner policy is roughly equivalent to the same-origin policy when signatures are used. Both are incompatible with arbitrary user-generated content, but the latter is simpler, therefore it is preferable.

So it is not clear what you're objecting to. Simple doesn't mean incorrect. And if you don't like how ActivityPub depends on servers/origins, create your own protocol where objects don't have HTTP IDs and stop harassing people.

Because literally every thread where this topic is discussed is flooded with your meaningless repetitive comments.

This is not true. In “same-origin” as described, any key on the origin is valid for signing messages. But in “same-owner”, only a key verifiably associated with the owner in both directions is valid for signing messages.

Additionally, “same-owner” is NOT incompatible with arbitrary user-generated content. If the owner signs for a message that claims to be their message, then it is verified. If a message is attributed to an actor without proof, then it is unverified. Messages can also be verified with a prior trust statement by the actor (like how Mastodon’s toot:attributionDomains specifies domains for which author bylines can be trusted for preview cards).

The ironic thing is that “same-owner” isn’t even as complicated as it’s being portrayed here. On the sending side, all you have to do is use one of the actor’s keys (which is trivial because in most current fedi implementations, keys are custodial), and on the receiving side, you just check the owner/controller of the key points to the actor and vice versa (no notion of an origin needed, just explicit follow-your-nose links that go in both directions).

Please re-read my post, because I address this misconception in detail. The part that’s incorrect is assuming that origin == owner == single entity. ActivityPub over HTTPS depends on HTTPS origins, yes, but there is no justification for assuming that an origin’s resources are always controlled by a single entity which owns all resources on the origin – which is demonstrably not the case when you have any sort of multi-tenancy, i.e. more than one user.

If you assume that all resources are controlled by the origin, your assumption is incorrect – full stop. You are ignoring the actual controller. This ignorance is harmful because it creates exactly the security flaws you caution about, and the proposed “solution” to limit which claims are allowable is never going to work in an open world system. It is an unavoidable fact that you cannot control the behavior of every single web server in existence, and it is likewise an unavoidable fact that any remote web server which doesn’t follow your recommendation can mislead you due to your insufficient security checks:

I am raising the same issue consistently every time this topic is brought up because the issue has not been addressed. That isn’t “meaningless”, and it certainly isn’t “harassing people”. The recommendations being given promote fragility and insecurity wherever they may be adopted. It simply isn’t how the web works. The web is a place where anyone can say anything about anything. There is no universal set of assumptions that can be applied to everyone publishing information on the web. By pretending otherwise, you open yourself to entirely avoidable issues when you encounter a website doing something you didn’t expect – a thing that is perfectly normal: having more than one user on an origin.

This thread started with the problem being identified as such:

If you wish to continue to promote this false conception of “same-origin”, then the least you could do is similarly recommend that each origin be controlled by only one entity, i.e. give each user their own web origin. This doesn’t entirely solve the problem, but it does mitigate it for anyone concerned about naive remotes falsely treating unverified resources as verified.

@kopper could you formulate a short and descriptive title for this topic?

1 Like

I’ve updated the title, I hope it’s descriptive enough

1 Like

@laurens would you like to make a primer out of this, so that ActivityPub implementers do not miss this security issue and avoid it in their implementations?

if you would like to avoid the security issue, then do not follow the recommendations that create the security issue.

consumers SHOULD verify owner/controller of keys in both directions, and ideally also verify that they are applicable to the specific purpose (authentication, assertionMethod, keyAgreement, capabilityInvocation, capabilityDelegation)

publishers SHOULD consider using separate web origins to represent trust boundaries. in the absence of attribution, or in case of naive or ignorant consumers ignoring attribution, statements made might be assumed to represent the entire web origin by default. if you wish to accommodate such cases, do not host arbitrary content. (but ultimately, it is the responsibility of consumers to not make invalid or incorrect assumptions)

i reiterate that the assumption of “all key resources on an origin are representative of the origin” is incorrect, in exactly the same way that html or plaintext statements on org.example/~alice/foo are not representative of org.example – they are authored by org.example/~alice specifically, and they are only hosted by org.example unless otherwise claimed or proven

1 Like

I’d like to emphasize that this issue only affects “generic” ActivityPub servers. Only few such implementations exist, and all of them are research projects.

A warning has already been added to the FEP (the section Signatures).

1 Like

The reason why a server is assumed to be an authority for all its users is stated in the section “Origin” of the FEP:

Different origins are considered potentially hostile and are isolated from each other to varying degrees. Actors sharing an origin are assumed to trust each other because all their interactions are mediated by a single piece of software operated by a single person or an organization.

This assumption extends to “generic” ActivityPub servers as well.

A server might not be interested in impersonating actors. But it can do that, and other servers have no way of knowing how it works, because from their perspective, it is a black box that responds to requests. Therefore, a server is considered a trusted entity, and actors are not considered to be autonomous. Given these assumptions, any object served at origin is valid (the section Fetching from an origin). This includes public keys.

This assumption doesn’t extend to nomadic ActivityPub applications, which are described in another document (FEP-ef61).

Note that the FEP doesn’t claim that the proposed security model is the only possible model and the only correct model. The goal is to design internally consistent model that works in existing Fediverse.