Principle of Least Privilege

- 19 min read - Text Only

In the InfoSec space, professionals have to draw a line of what they can trust and where they cannot, they then address and secure what they cannot trust.

confused
"Trust" can mean a lot of things, but within security "trust" should be rigidly defined. There are many models to define this, from access control lists (ACL), role based access control (RBAC), attribute based access control (ABAC), to ... the list goes on and on.

Within this post, I will reference AWS for examples, AWS permits an ABAC model. However, it is possible to emulate an RBAC model within AWS's ABAC model.

As for Principle of Least Privilege, the idea is that instead of trusting a resource, client, or user with everything, only trust it with exactly what it needs to function.

For example, instead of trusting a user and password with full access to all resources in the cloud account, a security engineer would

  1. By default deny all access to all resources
  2. Specify policies which:
    1. Enumerate resources or resources with wildcards which access is granted
    2. Specify actions or scopes which may apply to resources afforded
    3. Optionally constrain actions or resources with conditions
  3. Link these policies to network, compute, storage, and identities

At the end with the principle of least privilege applied, the application would have no privileges for resources it has no requirement for.

Example Application

As an example throughout this post, let's consider an application that users upload images to for forum avatars. The application would then upload the processed images to an object store with public access, and track the resources and who they belong to in a database. At the users request, these images may also be deleted or replaced. It may also automatically use Gravatar to find a default image to use for a forum user.

It would look something like this in the AWS space.

                         ┌──────────┐
                         │          │
                         │ Database │
                         │          │
                         └──────────┘
                              ▲
                              │
                              │
┌────────────────┐      ┌─────┴───────┐         ┌──────────────────┐
│                │      │             │         │                  │
│ Load Balancer  ├─────►│ Application ├────────►│ Images S3 Bucket │
│                │      │             │         │                  │
└────────────────┘      └─────────────┤         └──────────────────┘
                                      │
                                      │         ┌──────────────────┐
                                      │         │                  │
                                      └────────►│ DB Credentials   │
                                                │                  │
                                                └──────────────────┘
think
There's a lot going on here, you might think. You could just set up a Virtual Private Server (VPS), log in, install mysql, install your application, add a folder to store the files, and it all cost $5 a month. This will cost... probably around $50-80.
The difference here is, you're offloading a lot of concerns by using this type of deployment model. The application can update without losing customer data. The database gets backups and can be upgraded without taking the application down. SSH isn't exposed to the world. Customer files are replicated across many machines. The application server can have more produced at a moments notice due to customer demand.
talk-w-bubble
laptop
A hobby or personal setup is different, cheaper for a reason. You take on more burdens at the cost of time, but in exchange your deployment is cheaper and perhaps more fragile if it goes down.

We have a public load balancer, an application, a database, an AWS resource for saving images, and some credentials.

Let's also assume that the application and database are in a Virtual Private Cloud (VPC) together, which can be thought of like as an internal network for resources, appliances, and services. While the load balancer is also on the VPC, it has a public facing IP address and therefore can be contacted from outside the VPC.

Details like network routing, gateway, NAT, etc. for VPC setup are omitted in this post but are necessary for a proper deployment. This subject is hard to get right in practice and the result is you're stuck with it for months or years, so a proper educational resource is advised to learn more.
ssssh

To access the database, the application must present credentials, which are stored separately from the application. So, before the application can connect, it would get the credentials from Secrets Manager, or Simple Systems Manager Parameter Store. In this case, Secrets Manager is used. Thus the application first retrieves the secret from Secrets Manager using its Principal to authenticate with the service, and then presents those credentials to the database.

notes
The combination of the instance identity and the instance profile form the Principal. Instances within AWS can prove their identity using a meta data service. The same service also provides instance profiles which are linked to a single Identity and Access Management (IAM) role.

When users use this example service, the application will store the original and modified contents in the object store S3. It may also need to delete objects. So it will need permissions for that too. Finally, it may also ask Gravatar, an external service, for a default avatar to use once it has some identifying data from the user.

Principal

Now, I mentioned a keyword a few times: Principal. Like Capitol and Capital, the word principal has no relation to principle.

Key Security Concepts | Microsoft Docs
A principal represents the identity and role of a user and acts on the user's behalf.
What is really a Principal in .NET - Stack Overflow
When authorizing access to a resource or the ability to run some code, it is not sufficient merely to know which user is authorizing the action, but under what role they are authorizing it. Think of this as being roughly equivalent to when you elevate a shell: the shell is now running under a different principal (with more privileges), even though the identity of the principal is still the same (your user account). The IIdentity type is focused around issues of authentication, i.e. establishing that an identity known to the system actually belongs to an agent that wants to act under that identity. This is a necessary prerequisite for authorizing anything, but is not the whole story.
My interpretation: a Principal is a structure that combines an identity with a discrete role or permission-enabling descriptor trusted by the relying party.
talk-w-bubble

In AWS: a role may have multiple policies. A policy has multiple statements. A statement Allows or Denies actions upon resources and may be subject to additional conditions that constrain the effect of the statement and or policy.

Applications deployed in AWS typically use the machine's identity which is tied to a single role at the management layer. Whereas applications outside of AWS typically rely on an AWS user. An AWS User can be assigned to groups, and have roles attached to the user. An AWS User Group can have multiple roles associated. There may be delegating ways to not use users, but I have not personally done this exercise.

Now, you may be thinking, I access stuff all the time, as a user, without specifying what roles or permissions I have. AWS's Identity and Access Management (IAM) style does not require the application to select the role it uses. Instead AWS IAM creates a Principal just in time with the identity which contains all roles tied to the identity. But to actually authorize the request, all applicable roles in the principal are explored, and evaluated, and a determination is made to allow or deny the action upon the resource.

faceplant
Just my opinion, but while this provides some UX benefits to the developer, it sounds like a permanent performance nightmare to the cloud infrastructure, services, and request lifecycle.

The behavior of how Principals are formed differs wildly across public clouds. For example, Google Cloud requires declaring scopes up front when requesting an OAuth bearer token. The JWT bearer token that comes back becomes the principal, identifying who or what is executing a call to a resource, and for what permissions and constraints it has when accessing resources.

Service Privileges

Back to AWS, what would the policy look like for this application? Remember, this application's needs are

  1. Save or Delete image assets in an S3 bucket, this maps to s3:PubObject and s3:DeleteObject
  2. Image assets uploaded should be made public, this maps to s3:PutObjectAcl
  3. Retrieve the credentials and connection details for the database which is secretsmanager:GetSecretValue

Therefore:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::example-images-bucket/*"
        },
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "arn:aws:secretsmanager:us-east-1:555555555555:secret:database-secret"
        }
    ]
}

Once this policy exists, a role can be created with this single policy. Then the application instance can be provisioned with this role attached.

The above example doesn't truly demonstrate the "Attribute based access control", but it does show how least privilege can be enforced. Conditions like: Allow buckets * with tag team=developers and environment=production would make this more attribute-like.

excited
AWS has multiple types of policies here, such as resource-based, identity-based, and session policies. This post focuses on identity-based policies.

Network Privileges

While the application is now restrained as far as AWS's resources go, this isn't the end of the story. The network is wide open between all networked resources!

Suppose, for whatever reason, the adversary gained access to the database through a clever chain of exploits that permitted arbitrary shell commands to execute on the database (through use of the application).
watching-you
hiss
The adversary could then hop to and compromise any other applications or networked resources within the same VPC, or contact the outside world through the NAT gateway. This is not desirable.

The application and database appliance may accept connections from any networked resource in the virtual private cloud (VPC). Unless security is configured to be deny by default.

To make these resources fit the principle of least privilege, we should constrain their network access! Specifically the application and database network access.

This is performed with Security Groups in AWS. And in fact, there's a really neat feature in Security Groups, they can reference one another!

In this example, suppose the following security groups are created

  • sg-🍐 - The Load Balancer, with rules such that
    • Allow TCP port 80 from *
    • Allow TCP port 443 from *
    Which in summary means anything can access this resource over TCP with ports 80 and 443.
  • sg-🍋 - Access to the application, as a reference, no rules are set.
    Which on its own does nothing, but when applied to a resource is a tag that other rules can reference.
  • sg-🍊 - The Application, a rule is set such that
    • Allow TCP port 80 from sg-🍋
    • Allow TCP port 443 to *
    Which in summary means that anything with the sg-🍋 security group is permitted to contact this resource over port 80. No other resources, IPs, etc. have been permitted to contact it. The application must contact S3 over HTTPS, as well as Gravatar. So HTTPS (port 443) access is granted to all addresses.
  • sg-🍏 - Access to the database, as a reference, no rules are set.
    Which on its own does nothing, but when applied to a resource is a tag that other rules can reference.
  • sg-🍎 - The Database, a rule is set such that
    • Allow TCP port 3306 from sg-🍏
    • Deny * to *
    Which in summary means anything with the sg-🍏 security group is permitted to contact this resource over port 3306 (mysql). No other resources, IPs, etc. have been permitted to contact it. It cannot contact any resources.
yuck
While the complete list of AWS IPs could be programmatically enumerated, filtered and included for services like S3, there are 1462 CIDRs matching "us-east-1" in this list at the time of writing. The complete list of us-east-1 for cloudfront alone require a quota increase for security group rules.
If AWS released global security groups that were maintained at all times with the appropriate region and service CIDRs, with no maintenance burden on the account holder... The world would be a better place.
frustrated2

Visually, the security groups are applied as follows

  • The load balancer has sg-🍐 and sg-🍋
  • The application has sg-🍏 and sg-🍊
  • The database has sg-🍎


 ┌───────────────┐
 │               │ * sg-🍐
 │ Load Balancer │ * sg-🍋
 │               │
 └───────┬───────┘
         │
         ▼
  ┌─────────────┐
  │             │  * sg-🍏
  │ Application │  * sg-🍊
  │             │
  └──────┬──────┘
         │
         ▼
    ┌──────────┐
    │          │
    │ Database │   * sg-🍎
    │          │
    └──────────┘

While you could assign a load balancer a specific IP address and in the application's security group sg-🍊 include that IP address as allowed to contact the application over port 80, this method can be fragile over time and can make migrations difficult. What if the load balancer appliance was at end of life and all services needed to be migrated to the next generation of load balancers without downtime? It would be tedious and error prone to repeat this process with manual IP copying and pasting on each security group. While it would be easy to just add sg-🍋 to the new load balancer and swap the DNS pointer.

Here we see an example of attribute based access control (ABAC). The security group sg-🍋 is an attribute of the load balancer. Given sg-🍊 permits connections from resources with this attribute, the access control model is in this instance attribute based.

Conclusion

The principle of least privilege is used in the infosec industry to rigidly define trust for applications, networks, and identities. There are many models to define trust, here we see that attribute based models afford a powerful and flexible approach to defining trust. While Amazon has many resources on the matter, their Principal method works for them but may not work for you. If you plan to implement your own system for authorizing applications (don’t in production), I recommend looking at Google’s documentation on mimicking theirs. That is, a step to acquire an attestation of authorization, which includes authenticating, and using the attestation with a service or relying party.

If you want to find a solution that you can buy, search for "authorization as a service", you'll find service starting with "auth" and they'll describe several competing services and their capabilities. Though the fact they list OAuth 3.0 support and OAuth specifically says OAuth 3 does not exist causes me to doubt the authenticity of its marketing.
shrug
angel
Maybe some day I'll have a product in this space.