shadowsocks / go-shadowsocks2

Modern Shadowsocks in Go
Apache License 2.0
4.44k stars 1.39k forks source link

Avoid leaking server's internal network (maybe using ACL) #180

Open yilunzhang opened 4 years ago

yilunzhang commented 4 years ago

Currently it seems that there is no way to prevent client accessing server's internal network from the server side. This is a security risk if you just want to share your ss server with someone but nothing else. What makes it even worse is that some popular SS manager using go-shadowsocks2 (e.g. outline) makes it really easy to share your credential with other people.

shadowsocks-libev and shadowsocks-rust solve this by using block local ACL control, e.g.: https://github.com/shadowsocks/shadowsocks-libev#security-tips

It would be useful if there is at least some way to prevent accessing local address in go-shadowsocks2, either using a heavy solution like ACL, or a simpler solution.

riobard commented 4 years ago

You can use OS firewall (iptables or nftables on Linux) to enforce access control of the go-ss2 process.

yilunzhang commented 4 years ago

That's doable but it makes the go implementation loses two big advantages IMO:

  1. Single binary shipment
  2. Platform/OS independence

That's why I think the best solution is to implement it in go natively.

riobard commented 4 years ago

It's hard to imagine that a server running go-ss2 does NOT have a working firewall. So I don't think the lack of internal ACL hurts deployment. Additionally, to properly run go-ss2 as a daemon, you will need to use OS-dependent supervisor anyway, so the second point is also moot.

I'd like to avoid duplicating capabilities already provided by the OS. Repeated code brings more potential bugs and security pitfalls. We should always prefer OS native solution first before doing custom stuff.

yilunzhang commented 4 years ago

It's hard to imagine that a server running go-ss2 does NOT have a working firewall.

I'm not sure if that's true:

  1. Major cloud providers (e.g. AWS, GCP, Azure) rely on cloud firewall by default. They work well, but cannot restrict access to localhost for go-ss2. Cheaper cloud providers (e.g. DigitalOcean) does not have any firewall enabled by default at all.
  2. We are talking about go-ss2 user group. Lots of them, if not most, are just people who want access to some sites and are not security experts. Most of them might not even know such risks if you don't write it explicitly and give them a simple solution.
  3. Some, if not most, go-ss2 deployment solution does not consider or realized this problem at all. Let's take Outline for example, its deployment scripts does not enable OS level firewall at all, and explicitly ask you to turn off cloud level firewall as well. I'm not saying they are doing it right, but it's indeed not trivial to have a single deployment script that handles all OS level firewall for all OS correctly.

I'd like to avoid duplicating capabilities already provided by the OS.

I agree with you in general, but not in security related fields.

In security, it is not enough that someone can do it right. What we want ideally is that anyone can hardly do it wrong. Most users of security related libraries are not security experts that know all the potential risks and how to avoid them. Only if we make security related stuff hard to misuse can we increase the actual security of their systems.

Some examples: Google's tink is built for this purpose: A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse. Also, NaCl and libsodium, although not built entirely for this purpose, also follow this principle in their design.

In the current design, whoever has the go-ss2 password can use its SOCKS proxy or tunnel feature to access all local port of the machine, which was only possible with login access before. The current version makes it hard to do it right: you need to realize this problem, when it's going to happen (share with other people), know how to fix it (use OS level firewall), and do it right (set up a quite complex firewall rule).

If we following the above principle, I think it's necessary to provide an easy way to disable local access, maybe as simple as a flag (I would even suggest we disable local access by default). OS systemd daemon is not related to security, so the argument does not apply.

riobard commented 4 years ago

We're arguing for different things.

All modern server OS have builtin firewall. Whether to use it is up to you. You do not need to install anything extra, so it does not hurt the single-binary deployment model of go-ss2.

If you'd like an easy way to enhance security for non-expert users, it's better to spend the effort to write a proper deployment script, or OS-native packaging. There are vast number of knobs to enhance security besides ACL, like limiting process privileges, file system restrictions, resource limits and accounting, to name a few. Most of those settings can not, and should not, be implemented by a user-space application.

The majority of go-ss2 server deployment happens on modern Linux with its many powerful and battle-tested defense mechanisms, yet you're arguing that we just duplicate a tiny subset of those defense with questionable usefulness and ongoing maintenance cost.

Sounds like a losing battle to me.

yilunzhang commented 4 years ago

I'm not talking from the deployment's perspective: I'm not deploying for other user, so I can only control my deployment, which does not have such problem. I'm talking from the security design's perspective, considering the security of users where I have no control of their deployment.

If you'd like an easy way to enhance security for non-expert users, it's better to spend the effort to write a proper deployment script, or OS-native packaging.

Let's talk about the fact: ss has been around for years, where is this script now? Just look at the current situation, people are just going to take the software as it is and use it.

The majority of go-ss2 server deployment happens on modern Linux with its many powerful and battle-tested defense mechanisms, yet you're arguing that we just duplicate a tiny subset of those defense with questionable usefulness and ongoing maintenance cost.

ACL has been implemented in other ss implementations (libev and rust) and are compatible. So I consider it as a "semi" standard feature.

Sounds like a losing battle to me.

Well, losing or winning, it's your call since this is your repo. I'm just giving suggestions from my perspective.

riobard commented 4 years ago

The fact is that the libev implementation has been packaged as Debian .deb for a long time, and most non-expert users actually install it via apt anyway. If you specifically mean go-ss2, it's true there's never been any effort to provide packaging or deploy script, because my original intention is to provide it as a proof of concept to test AEAD ciphers during the designing phase of SIP007, as well as making sure cross-implementation compatibility of the SS protocol, and partly because the original ss-go implementation is unmaintained and hard to improve due to design pitfalls.

I've clearly explained the downside of custom ACL and offered a more robust and universal alternative. The fact that other SS implementations have ACL does not evaluate the technical merits of better approaches. There's also been CVE report of ACL bugs in libev implementation if I recall correctly.

Outline, Clash, and many other projects use this code base as library, so even if we implement ACL here, downstream will have to re-implement it again because they have different code paths in their server processes. Contrary that to using native OS firewall, none of the repeated work needs to happen, because the same config can be reused across all server implementations.

Look, this is open source, not any one person's repo. If you want to improve it, either convince me or send pull requests! You seem to be running an open source project as well. Presumably you know how the process works.

yilunzhang commented 4 years ago

I definitely agree the system firewall is more complete, powerful, and well tested, but it is not available in all deployments. For example, one of the projects I'm working on belongs to this category: it is a transparent and light weighted middle layer that does not make assumption or modify the system, not does it have root access. The service launcher has the same restriction and cannot modify system firewall. I'm currently finding a list of "safe" (at least restrict internal net access) software that can be run as the official use cases or services. That's why I was inspecting whether go-ss2 has builtin ACL list or other similar mechanism that can make life so much easier.

But please don't take it wrong, I never meant "hey I need this function, please implement it". I just want to see whether you happen to think it's a useful feature and have the plan to implement it in the near future. If you want to support similar scenario, having a built in access control might be a good option. If not, it's also not a problem, the current version is probably good enough for people's own usage.

If you want to improve it, either convince me or send pull requests!

They are the same thing IMO. I assume you are not going to merge a PR without being convinced it's needed right? :)

riobard commented 4 years ago

For example, one of the projects I'm working on belongs to this category: it is a transparent and light weighted middle layer that does not make assumption or modify the system, not does it have root access. The service launcher has the same restriction and cannot modify system firewall.

This is intriguing. What kind of environment is this? So you cannot use any native firewall in this scenario, but somehow you want to limit access? The closest I could think of is something like a container, but I'm not familiar with networking inside a container.

If you have a convincing reason to implement custom ACL, I'm more than happy to merge a PR :P

yilunzhang commented 4 years ago

It's basically a tunnel with pure Go implementation (https://github.com/nknorg/tuna/tree/dev) and a launcher also implemented in Go (not open sourced yet, but it's basically a Go version of supervisor). It's running in all different OS/platforms, e.g. Linux, Mac, Windows, some are cloud nodes, some are home computers, some are home NAS devices, and some are even mips routers. Most users don't know how to set up native firewall properly, not to mention all possible OS.

The closest I could think of is something like a container, but I'm not familiar with networking inside a container.

Container is what we are using for some services now, but it's far from ideal. AFAIK, to prevent container accessing other services (listening at 0.0.0.0) on host machine while not affecting container's access to Internet, one would still need to partially rely on host's firewall. Not to mention a few other issues docker brings:

  1. Require root access by default on Linux, unless installed differently for non-root user.
  2. Behaves differently on Mac and Windows. For example, host.docker.internal can be used on Mac and Windows to access host machine (it's designed for debugging purpose, but it cannot be turned off πŸ˜‚).
  3. It's bringing some overhead to some light weight devices (e.g. mips router).

A pure Go solution would be much better at handling all these different conditions correctly.

riobard commented 4 years ago

So Tuna is like Tor but paid? What's your current mechanism for a Tuna node to filter/reject 3rd-party traffic? Assuming in this use case you'd like to deny access to 127.0.0.0/8 on the Tuna node, isn't it mandatory for the Tuna node itself to setup some firewall rules? Does the Tuna node run with root privileges?

Sorry but I have lots of questions πŸ˜‚

yilunzhang commented 4 years ago

Haha not a problem, happy to answer the questions.

So Tuna is like Tor but paid?

It's more like a general paid tunnel, not specific to an application or protocol (like tor). For example, user can use or provide http proxy, socks proxy, or even real time game streaming, and reverse proxy in reverse mode.

What's your current mechanism for a Tuna node to filter/reject 3rd-party traffic?

Tuna has its own communication and payment protocol. The payment part is realtime using a payment channel. If a third party software implements tuna communication protocol, including payment, then it's a valid tuna implementation. But if he implements the communication protocol but not the payment part, or a modified payment protocol that pays less, then the service provider side will find out by validating payment amount (per interval and per bandwidth usage) and then cut out the connection.

Assuming in this use case you'd like to deny access to 127.0.0.0/8 on the Tuna node, isn't it mandatory for the Tuna node itself to setup some firewall rules?

Tuna is running as a separate tunnel, not including the service software itself one wants to provide or use. For service provider side, user starts both tuna and the server side of the software as it is, and tells tuna what service it needs to publish and provide, identified by service name and ports. In this case:

So it's best that services which want to disable localhost access can have a way to disable it itself.

Does the Tuna node run with root privileges?

It's not required to run node with root.

riobard commented 4 years ago

So Tuna is basically a reverse proxy fronting other services (like Shadowsocks or HTTP), as in the following diagram:

SS client --[SS proto] --> Tuna client ---[Tuna proto]--> Tuna server --[SS proto]--> SS server

And since Tuna server does not launch/control SS server, it cannot do anything to restrict access of SS. You mentioned a not-yet-open-sourced service launcher above. Presumably it's the launcher's job to launch and supervise actual services (e.g. SS servers), and the launcher might not have sufficient privileges to control OS firewalls, therefore the only recourse is for the actual services to "behave themselves".

If my understanding above is close enough to reality, I do see the necessity of ACL in this particular setup.

However I cannot resist to ask: isn't the situation too fragile to be remotely secure? Basically you're requiring every service running behind the Tuna tunnel to be individually secure. Without access to proper OS-level isolation (both resources and firewalls), a single exploit in any of the services will compromise the whole node. Your said above that

I'm currently finding a list of "safe" (at least restrict internal net access) software that can be run as the official use cases or services.

implies that you cannot really afford to run arbitrary services but rather a manually vetted/sanctioned few. That would seriously limit the usefulness of the whole Tuna idea.

Or I could be totally wrong and missed the picture of how your supervisor is working? πŸ˜„

yilunzhang commented 4 years ago

So Tuna is basically a reverse proxy fronting other services (like Shadowsocks or HTTP), as in the following diagram:

Yes that's basically right.

However I cannot resist to ask: isn't the situation too fragile to be remotely secure? Basically you're requiring every service running behind the Tuna tunnel to be individually secure. Without access to proper OS-level isolation (both resources and firewalls), a single exploit in any of the services will compromise the whole node. Your said above that

It's hard to guarantee the security because we cannot control what services users will use tuna for under what environment, but at least we want to give users some guidelines, such as:

But since that's just recommendations, we can't stop users from grabbing the software and run it with the example services. We want to at least makes the example services as safe as possible by default.

implies that you cannot really afford to run arbitrary services but rather a manually vetted/sanctioned few. That would seriously limit the usefulness of the whole Tuna idea.

There might be some misunderstanding here. Tuna is a standalone service as it is, but to bootstrap the system and makes it easy to understand and user for people that first see it, we want to give some example services that are useful, can easily get started, and don't have severe security issues so people can immediately try to use it or provide services without bothering thinking what services might be available. That's the list I'm talking about earlier.

The service launcher I mentioned earlier is not so important in this context. It just makes life easier for our own service provider nodes and nodes that people want us to help manage. But in our vision most people will run tuna on their own since it's a lot more flexible.

riobard commented 4 years ago

Now I understand. So at the most rudimentary level, you want users to start using Tuna with e.g. SS right away, without considering extra configurations. But you do need to provide proper isolations or at least proper deployment strategies to encourage users to run services securely (via eg. virtualization/cgroups/process isolations).

Effectively we're back to the original issue: is it better to add a half-assed ACL, or to add a proper deployment script? Given the cross-platform nature of your use case, it looks like ACL is simpler (only one place to config vs one script per OS family) but with weaker guarantee (you have to trust SS that its ACL is effective without flaws).

yilunzhang commented 4 years ago

It's not quite feasible or scalable for tuna to set up OS level restriction for every service because it requires tuna to know what the service is, how to start it, what access it needs, etc. This will turn tuna from a transparent tunnel into something that only works with a list of services.

Ideally tuna don't need to care about the service details and should work with any service. Some might need localhost access, some might need local network access, some need local file access, and some need public network access.

So the most generic solution might be that, the service itself can handle access control, either with a builtin mechanism, or with the help from OS (manually or with a deployment script). Specifically to go-ss2, either user starts it with an argument that restrict local access (builtin ACL), or user starts it together with some OS level firewall rule.

Given the cross-platform nature of your use case, it looks like ACL is simpler (only one place to config vs one script per OS family) but with weaker guarantee (you have to trust SS that its ACL is effective without flaws).

Yeah I agree. I guess it's not feasible or appropriate to ship many OS dependent start scripts with go-ss2 by default. If so, I think the builtin ACL is suitable for most users, while OS firewall is better for advanced users who know exactly what they want and how to do it.

riobard commented 4 years ago

I guess it's not feasible or appropriate to ship many OS dependent start scripts with go-ss2 by default.

I do not intend to, but as a counter example there's this popular https://github.com/DNSCrypt/dnscrypt-proxy with builtin Go code to properly configure systemd service file…