Invariant Properties

  • rss
  • Home

Proactive Database Defenses Using Triggers

Bear Giles | January 15, 2017

I’m sure I’ve discussed this a number of years ago but a question came up after the recent Boulder Linux User Group meeting and I decided this would be a good time to revisit it.

The question is how do you protected sensitive information from illicit insertion or modification when the attacker has full SQL access as the website user?

Important: I am focused on approaches we can use in the database itself, not our application, since the former will protect our data even if an attacker has full access to the database. These approaches are invisible to our database frameworks, e.g., JPA, once we have created the tables.

An Approach Without Triggers

At a minimum we can ensure that the database was properly configured with multiple users:

app_owner – owns the schema and tables. Often does not have INSERT/UPDATE/DELETE (or even SELECT) privileges on the tables.

app_user – owns the data but cannot modify the schema, tables, etc.

We can make this much more secure by splitting app_user into two users, app_reader and app_writer. The former user only has SELECT privileges on the tables. This is the only account used by user-facing code. The app_writer user adds INSERT/UPDATE/DELETE privileges and is only used by the methods that actually need to modify the data. Data is typically read so much more often that it is written that it often makes sense to view an application as actually two (or more) separate but related applications. In fact they may be – you can improve security by handling any data manipulation via microservices only visible to the application.

There is a big downside to this – modern database frameworks, e.g., JPA or Hibernate, make heavy use of caching to improve performance. You need to ensure that the the cache is properly updated in the app_reader cache whenever the corresponding record(s) are updated in the app_writer account.

Security Defense

This is highly database specific – does the database maintain logs that show when a user attempts to perform a non-permitted action? If so you can watch the logs on the app_reader account. Any attempt to insert or update data is a strong indication of an attacker.

Triggers Based On Related Information

A 3NF (or higher) database requires that each column be independent. In practice we often perform partial denormalization for performance reasons, e.g., adding a column for the day of the week in addition to the full date. We can easily compute the former from the latter but it takes time and can’t be indexed.

There’s a risk that a bug or intruder will introduce inconsistencies. One common solution is to use an INSERT OR UPDATE trigger that calculates the value at the time the data is inserted into the database. E.g.,

  1. CREATE FUNCTION calculate_day_of_week() ....
  2.  
  3. CREATE TABLE date_with_dow (
  4.     date text,
  5.     dow  text
  6. );
  7.  
  8. CREATE FUNCTION set_day_of_week() RETURNS trigger AS $$
  9.     BEGIN
  10.         NEW.date = OLD.date;
  11.         NEW.dow = calculate_day_of_week(OLD.date);
  12.         RETURN NEW;
  13.     END;
  14. $$ LANGUAGE plpgsql;
  15.  
  16. CREATE TRIGGER set_day_of_week BEFORE INSERT OR UPDATE ON date_with_dow
  17.    FOR EACH ROW EXECUTE PROCEDURE set_day_of_week();
CREATE FUNCTION calculate_day_of_week() ....

CREATE TABLE date_with_dow (
    date text,
    dow  text
);

CREATE FUNCTION set_day_of_week() RETURNS trigger AS $$
    BEGIN
        NEW.date = OLD.date;
        NEW.dow = calculate_day_of_week(OLD.date);
        RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER set_day_of_week BEFORE INSERT OR UPDATE ON date_with_dow
   FOR EACH ROW EXECUTE PROCEDURE set_day_of_week();

This ensures that the day of week is properly set. A software bug, or attacker, can try specifying an invalid value but they’ll fail.

Of course we don’t really care (much) if the day of the week is incorrect. However there are other times when we care a great deal, e.g., cached attributes from digital certificates. If someone can insert a certificate with mismatched cached values, esp. if it they can replace an existing table entry, then they can do a lot of damage if the code doesn’t assume that the database could be corrupted and thus perform its own validation checks on everything it gets back. (First rule of security: never trust anything.) Even with tests we’ll only know that the data has been corrupted, not when and not how broadly.

Security Defense

Developers are information packrats. Can we learn anything from the provided day of week value?

Yes. It’s a huge red flag if the provided value doesn’t match the calculated value (modulo planned exceptions, e.g., passing null or a sentinel value to indicate that the application is deferring to the database). It’s easy to add a quick test:

  1. CREATE FUNCTION calculate_day_of_week() ....
  2.  
  3. -- user-defined function that can do anything from adding an entry into
  4. -- a table to sending out an email, SMS, etc., alert
  5. CREATE FUNCTION security_alert() ....
  6.  
  7. CREATE TABLE date_with_dow (
  8.     date text,
  9.     dow  text
  10. );
  11.  
  12. CREATE FUNCTION set_day_of_week() RETURNS rigger AS $$
  13.     DECLARE
  14.         calculated_dow text;
  15.     BEGIN
  16.         NEW.date = OLD.date;
  17.         NEW.dow = calculate_day_of_week(OLD.date);
  18.         IF (NEW.dow  OLD.date) THEN
  19.             security_alert("bad dow value!");
  20.             RETURN null;
  21.         END IF;
  22.         RETURN NEW;
  23.     END;
  24. $$ LANGUAGE plpgsql;
  25.  
  26. CREATE TRIGGER set_day_of_week BEFORE INSERT OR UPDATE ON date_with_dow
  27.     FOR EACH ROW EXECUTE PROCEDURE set_day_of_week();
CREATE FUNCTION calculate_day_of_week() ....

-- user-defined function that can do anything from adding an entry into
-- a table to sending out an email, SMS, etc., alert
CREATE FUNCTION security_alert() ....

CREATE TABLE date_with_dow (
    date text,
    dow  text
);

CREATE FUNCTION set_day_of_week() RETURNS rigger AS $$
    DECLARE
        calculated_dow text;
    BEGIN
        NEW.date = OLD.date;
        NEW.dow = calculate_day_of_week(OLD.date);
        IF (NEW.dow  OLD.date) THEN
            security_alert("bad dow value!");
            RETURN null;
        END IF;
        RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER set_day_of_week BEFORE INSERT OR UPDATE ON date_with_dow
    FOR EACH ROW EXECUTE PROCEDURE set_day_of_week();

Sidenote: check out your database documentation for more ideas. For instance many applications use @PrePersist annotations to autofill creationDate and lastUpdateDate. It’s easy to do this via a trigger – and using a trigger ensures that the data updated even if an attacker does it via SQL injection or direct access. More impressively you can write audit information to a separate table, perhaps even in a separate schema that the app_user only has INSERT privileges for in order to prevent an attacker from learning what the system has learned about them, much less altering or deleting that information.

I’ve written triggers that generate XML representations of the OLD and NEW values and write them to an audit table together with date, etc. On INSERT the OLD data is null, on DELETE the NEW data is null. Using XML allows us to use a common audit table (table name is just a field) and potentially allows you to add transaction id, etc.

It is then easy to use a bit of simple XML diff code to see exactly what changed when by reviewing the audit table.

Resources:

  • PostgreSQL
  • MySQL
  • Oracle

Triggers Based On Secrets

What about tables where there’s no “related” columns? Can we use a trigger to detect an illicit attempt to INSERT or UPDATE a record?

Yes!

In this case we want to add an extra column to the table. It can be anything – the sole purpose is to create a way to pass a validation token to the trigger.

What are validation tokens?

A validation token can be anything you want. A few examples are:

A constant – this is the easiest but will be powerful as long as you can keep it secret. An example is ’42’. An obvious variant is the sum of several of the other columns of the table. This value should not be written to the database or it will be exposed to anyone with SELECT privileges.

A time-based value – your webserver and database will have closely synced clocks so you can use a time-based protocol such as Time-based One-time Password (TOTP) Algorithm. If both the database and application servers use NTP you can keep the window as small as a few seconds. Just remember to include one tick on either side when validating the token – NTP keeps the clocks synchronized but there can still be a very small skew plus network latency to consider.

Note: TOTP requires a shared secret and is independent of the contents of the INSERT or UPDATE statement.

You can save a time-based value but it is meaningless without a timestamp – and some algorithms can be cracked if you have a series of values and the starting time.

An HMAC value – most people will be familiar with standard cryptographic hashes such as MD-5 or SHA-1 (both considered cracked) or SHA-256. They’re powerful tools – but in part because everyone will compute the same values given the same input.

In our case we want an HMAC – it is a cryptographically strong message digest that also requires an encryption key. An attacker cannot generate their own HMAC but anyone with the corresponding digital certificate can verify one. An HMAC value requires something in the message to be processed and it needs to be intrinsic to the value of the record. For instance a digital certificate, a PDF document, even a hashed password. Don’t use it to hash the primary key or any value that can be readily reused.

You can freely save an HMAC value.

Subsequent validation

We would like to know that values haven’t been corrupted, e.g., by an attacker knowledgeable enough to disable the trigger, insert bad values, and then restore the trigger. The last step is important since we can / should run periodic scans to ensure all security-related features like these database triggers are still in place. Can we use these techniques to validate the records after the fact?

Constant value: no.

Time-base value: only if we record a timestamp as well, and if we do then we have to assume that the secret has been compromised. So… no.

HMAC value: yes.

Backups and Restorations

Backups and restorations have the same problems as subsequent validations. You can’t allow any magic values to be backed up (or an attacker could learn it by stealing the backup media) and you can’t allow the time-based values plus timestamps to be backed up (or an attacker could learn the shared secret by stealing the backup media). That means you would need to disable the trigger when restoring data to the database and you can’t verify that it’s properly validated afterwards. Remember: you can’t trust backup media!

The exception is HMAC tokens. They can be safely backed up and restored even if the triggers are in place.

Security Defense

You can add a token column to any table. As always it’s a balance between security and convenience and the less powerful techniques may be Good Enough for your needs. But for highly sensitive records, esp. those that are inserted or updated relatively infrequently, an HMAC token may be a good investment.

Implementation-wise: on the application side you can write a @PrePersist method that handles the creation of the TOTP or HMAC token. It’s a standard calculation and the biggest issue, as always, is key management. On the database side you’ll need to have a crypto library that supports whatever token method you choose.

Shadow Tables

Finally, there are two concerns with the last approach. First, it requires you to have crypto libraries available in your database. That may not be the case. Second if a value is inserted it’s impossible know for sure that it was your application that inserted it.

There’s a solution to this which is entirely app-side. It might not give you immediate notification of a problem but it still gives you some strong protection when you read the data.

You start as above – add a @PrePersist method that calculates an HMAC code. Only now you edit the domain bean so that the HMAC column uses a @SecondaryTable instead of the main table. (I think you can even specify a different schema if you want even higher security.) From the data perspective this is just two tables with a 1:1 relationship but from the source code perspective it’s still a single object.

Putting this into a separate table, if not a separate schema as well, means that a casual attacker will not know that it is there. They might succeed in inserting or modifying data but not realize that the changes will be detected even if audit triggers are disabled.

The final step is adding a @PostLoad method that verifies the HMAC code. If it’s good you can have confidence the data hasn’t been corrupted. If it’s incorrect or missing you know there’s a problem and you shouldn’t trust it.

For advanced users the developer won’t even know that the extra data is present – you can do a lot with AOP and some teams are organized so that the developers write unsecured code and a security team – which is focused entirely on security, not features – is responsible for adding security entirely through AOP code interwoven into the existing code. But that’s a topic for a future blog….

Comments
No Comments »
Categories
CEU, PostgreSQL, security
Comments rss Comments rss
Trackback Trackback

Encrypt your usernames and email addresses!

Bear Giles | June 13, 2016

The possibility of a massive twitter password breach brings up a basic requirement in our world. I discussed encrypting personally identifiable information (PII) last August but didn’t mention login information specifically.

Encrypt your usernames and email addresses.

Use a unique, random salt/IV for each user.

This won’t stop a dedicated attacker with resources but it will slow them down and that buys time for you to discover the breach and for your users to change their passwords. Maybe not much time, maybe a week instead of a few days until a majority of passwords is cracked, but that could make a big difference for a lot of users.

How do I find a user if their username and email is encrypted?

This is easy to answer – also store a hash of each user’s username and email. Think HashMap – not unique identifier – it’s okay if there are a few users in each ‘bin’. In fact this is desirable since it makes the job of the attacker a little bit harder. You can use the last few bytes of a SHA hash.

You can efficiently determine the correct user – compute the password hash for each potential match using their unique hash salt. If there’s no matches you know it’s an invalid username and/or password. If there is you can encrypt the provided username or password, again using the unique salt (possibly different from the password hash salt) and see if there’s a match.

(Note: if you compare ciphertext you must make sure it does not contain the random salt/IV. Otherwise you’ll need to decrypt the values and compare the plaintext.)

Can I store a hash of the username or email address instead?

Good idea! The answer is maybe. It depends on your data model. That brings us to the next point…

Any other ideas to improve my security?

Think microservices. Even if you have a monolithic application you probably have it broken apart into separate components internally – e.g., an e-commerce site probably has user management and authentication, inventory, shopping cart, fulfillment, payment, etc. There’s no reason these components need to share the same database – they can use separate schemas and database accounts.

You can take that a step further and use different physical databases for critical information. E.g., the user authentication information can (and arguably should) be stored on a different server than anything user-facing.

If you do this then you can store a hash of the username and email address in your authentication tables and database. Again this will only slow down a dedicated attacker (they’ll have likely usernames and email addresses from prior attacks) but it buys you, and your users, time.

Another good idea is to periodically reencrypt everything with a new key. An attacker might not be able to obtain a copy of the database and the encryption key at the same time so this gives an extra measure of security. You might be tempted to use multiple encryption keys, e.g., one identified by the month they registered, but I doubt the improved security is worth the increased complexity. IMHO it’s much better to use a single key that you rotate on a regular basis.

I’m small fry – who would target me? Why should I bother with this?

You might be “small fry” but many of your users will use the same password on other sites. Attackers know this and that “small fry” often have lax security out of ignorance or the misguided belief that they’ll never be the target of an attack.

Comments
No Comments »
Categories
CEU, security
Comments rss Comments rss
Trackback Trackback

Introduction to Kerberos – PostgreSQL (part 2)

Bear Giles | April 21, 2016

The the first part of this introduction I discussed setting up the Kerberos infrastructure and creating users. In this part I will demonstrate how to use it to connect to a PostgreSQL server.

Note: GSSAPI is a generic security interface that can use different protocols on the backend. Kerberos is one of many protocols that can be used with GSSAPI. Using it allows developers to write flexible code while still allowing the system administrator to have the final word on which authentication method(s) will be accepted. When given a choice you should enable GSSAPI instead of just Kerberos.

Configuring the Server

The default servicename for PostgreSQL is ‘postgres’. There is one notable exception – working with Active Directory requires a servicename of ‘POSTGRES’ and that requires recompiling the server. The full principal for a PostgreSQL server is ‘postgres/fqdn@realm’ although in a some situations you’ll need to specify postgres@fqdn.

We start by creating a keytab file for the server:

  1. $ kadmin
  2. kadmin% ank -randkey postgres/db.invariantproperties.com
  3. kadmin% ktadd -k krb5.keytab postgres/db.invariantproperties.com
$ kadmin
kadmin% ank -randkey postgres/db.invariantproperties.com
kadmin% ktadd -k krb5.keytab postgres/db.invariantproperties.com

Note that the value of the fully-qualified domain name (fqdn) is critical. The server and client must both recognize it as the name of the server – the server uses it to identify which keytab entry to use as it comes up and the client uses it when it constructs its query to the KDC. Ideally the name will be fully supported by DNS – including reverse lookups – but it’s often enough to put entries into the /etc/hosts files if you’re only working with a few systems.

Once you have your keytab file you should copy it to /etc/postgresql/9.4/main/krb5.keytab and change the file’s ownership. You should also make sure that the file can only be read by the server.

  1. $ sudo chown postgres:postgres krb5.keytabe
  2. $ sudo chmod 0600 krb5.keytab
$ sudo chown postgres:postgres krb5.keytabe
$ sudo chmod 0600 krb5.keytab

We now need to tell the server the location of the keytab file and to listen to all network interfaces. Kerberos can only be used on TCP connections.

/etc/postgresql/9.4/main/postgresql.conf

  1. ...
  2. krb_server_keyfile = '/etc/postgresql/9.4/main/krb5.keytab'
  3. listen_addresses = '*'
...
krb_server_keyfile = '/etc/postgresql/9.4/main/krb5.keytab'
listen_addresses = '*'

We must also tell the server which databases can be accessed via Kerberos. Security note: in production we never want to use a wildcard (‘all’) for both database and user on a single line.

/etc/postgresql/9.4/main/pg_hba.conf

  1. # TYPE  DATABASE        USER            ADDRESS                 METHOD       OPTIONS
  2. host    all             all             52.34.69.195/32         gss          include_realm=1 map=gss krb_realm=INVARIANTPROPERTIES.COM
# TYPE  DATABASE        USER            ADDRESS                 METHOD       OPTIONS
host    all             all             52.34.69.195/32         gss          include_realm=1 map=gss krb_realm=INVARIANTPROPERTIES.COM

We will always want to retain the Kerberos realm so we don’t confuse ‘bob@FOO.COM’ with ‘bob@BAZ.COM’ but this requires us to use the pg_ident mapping file. The name of the mapping is arbitrary – I’m using ‘gss’ for convenience.

In this case we’re being even stricter and requiring that the Kerberos domain match ‘INVARIANTPROPERTIES.COM’ specifically. This isn’t always possible but you can repeat the line if you only need to support a few realms.

We must add entries to the server’s identity lookup file. The easiest approach is to add authorized users directly:

/etc/postgresql/9.4/main/pg_ident.conf

  1. # MAPNAME    SYSTEM-USERNAME                    PG-USERNAME
  2. gss          bgiles@INVARIANTPROPERTIES.COM     bgiles
# MAPNAME    SYSTEM-USERNAME                    PG-USERNAME
gss          bgiles@INVARIANTPROPERTIES.COM     bgiles

This is not a good long-term solution since you must manually restart the database, or at least reload the configuration files, every time you need to add or remove an authorized user. It’s much better to qualify the username, e.g., with ‘/postgres’, and then use regular expression matching in the pg_ident file.

/etc/postgresql/9.4/main/pg_ident.conf

  1. # MAPNAME    SYSTEM-USERNAME                                    PG-USERNAME
  2. gss           /^([^/]+)\/postgres@INVARIANTPROPERTIES\.COM$     \1
# MAPNAME    SYSTEM-USERNAME                                    PG-USERNAME
gss           /^([^/]+)\/postgres@INVARIANTPROPERTIES\.COM$     \1

Important: do not share Kerberos credentials. Either map multiple Kerberos users to the same PostgreSQL identity or map them to different PostgreSQL identities and use the standard grants and roles to control access within the database.

We’re now ready to restart the PostgreSQL server.

  1. $ sudo service postgresql restart
$ sudo service postgresql restart

Example

The following is an example dialogue as I attempt to log into the server.

  1. # try to log in without any Kerberos tickets
  2. bgiles@snowflake:~$ psql -h kpg
  3. psql: GSSAPI continuation error: Unspecified GSS failure.  Minor code may provide more information
  4. GSSAPI continuation error: No Kerberos credentials available
  5.  
  6. # log in as regular user, try to connect. The error is a little confusing but we do not get access.
  7. bgiles@snowflake:~$ kinit bgiles
  8. Password for bgiles@INVARIANTPROPERTIES.COM:
  9.  
  10. bgiles@snowflake:~$ psql -h kpg
  11. psql: duplicate GSS authentication request
  12.  
  13. # log in as database user, try to connect.
  14. bgiles@snowflake:~$ kinit bgiles/postgres
  15. Password for bgiles/postgres@INVARIANTPROPERTIES.COM:
  16.  
  17. bgiles@snowflake:~$ psql -h kpg
  18. psql (9.4.7, server 9.4.6)
  19. SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
  20. Type "help" for help.
  21.  
  22. bgiles=#
# try to log in without any Kerberos tickets
bgiles@snowflake:~$ psql -h kpg
psql: GSSAPI continuation error: Unspecified GSS failure.  Minor code may provide more information
GSSAPI continuation error: No Kerberos credentials available

# log in as regular user, try to connect. The error is a little confusing but we do not get access.
bgiles@snowflake:~$ kinit bgiles
Password for bgiles@INVARIANTPROPERTIES.COM: 

bgiles@snowflake:~$ psql -h kpg
psql: duplicate GSS authentication request

# log in as database user, try to connect.
bgiles@snowflake:~$ kinit bgiles/postgres
Password for bgiles/postgres@INVARIANTPROPERTIES.COM: 

bgiles@snowflake:~$ psql -h kpg
psql (9.4.7, server 9.4.6)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

bgiles=# 

Establishing a JDBC Connection

Connecting to the database via the CLI demonstrates that we have properly set up Kerberos authentication but in practice we will want to access the database programmatically. That means JDBC connections.

Is it possible?

The answer is yes and no. I’ll cover it in further detail in the next part but it’s easy to establish the connection but there’s an unexplained problem when mapping the GSS/Kerberos identity to the PostgreSQL identity. Investigation continues…

PostgreSQL currently supports Kerberos authentication if you use a simple Kerberos principal (user) and password as your connection properties. It does not support a compound Kerberos principal as I discussed above, nor does it support the use of keytab files.

I plan to submit a patch to support keytabs in the next few weeks. I don’t know how long review will take and of course it’s unlikely to be applied retroactively. Write me if you want a copy.

What About Other Databases?

Other databases also support Kerberos.

Oracle: Oracle has supported Kerberos for a long time with an optional security extension. In 2013 Oracle made it free to use for all versions of its database. I haven’t had a chance to see if I can use it with Oracle 11 XE for Linux. It was cut at about the same time but it can be tricky to set up Oracle XE on Linux. (I had it working on Ubuntu 14.04 LTS but then a routine package update broke it.)

Microsoft SQLServer: SQLServer has also supported Kerberos for a long time, at least in its Active Directory wolf’s clothing. I would expect MSSQL XE to also support AD/Kerberos.

MySQL/MariaDB: MySQL does not support Kerberos. MariaDB does via a plugin but I haven’t been able to bring my MariaDB server up to verify this.

Cassandra: Cassandra supports Kerberos. See Datastax.

MongoDB: MongoDB supports Kerberos.

Neo4j: No information.

Apache Hive: Hive supports Kerberos.

H2/Derby/embedded databases: They do not appear to support Kerberos but I can’t say this with any certainty.

(Note: if you’re not familiar with Oracle XE and MSSQL XE they’re the ‘first hit is free’ versions of the respective databases. You can use them on your website or application as long as the server is limited to a single CPU and you do not have more than 10 GB (iirc) of data. They’re usually one rev back from the commercial product. A Personal Use license lets you use the latest versions of the software but there are extremely tight restrictions – it’s basically only useful when learning how to use the respective database.)

Next Time

Next time I will discuss establishing low-level connections using Kerberos authentication.

Comments
No Comments »
Categories
CEU, linux, PostgreSQL, security
Comments rss Comments rss
Trackback Trackback

Introduction to Kerberos (part 1)

Bear Giles | April 17, 2016

Old protocols never die.

Kerberos is a three party protocol that provides strong mutual authentication between clients and servers. It is designed for academic and enterprise organizations where there is a single source of truth regarding identify, authentication and authorization. Think universities with faculty, students, and staff, or businesses with employees with different responsibilities. Changes must be propagated immediately – it is not acceptable for there to be a lag such as we see with X.509 digital certificates using periodically updated CRL lists. (It is possible, but expensive, to use OCSP to verify a certificate on each use.)

Strong mutual authentication is when both parties to a transaction have high confidence in the identity of the other party. A good example is when you log onto your bank’s website. The bank authenticates the user with username, password, and second factor like the user accepting the “personal image”. The user authenticates the bank by checking the HTTPS icon in the corner and the recognized “personal image”. Many of us consider the latter somewhat broken since the system will accept a certificate from ANY certificate authority preloaded into the browser. I have nothing against the Moldovian postal service but I don’t want to have to trust it in order to check my bank balance. I have nothing against it but I have no reason to trust it either. With Kerberos there’s a personal relationship – it is our employer, our university, etc., so there’s a much higher level of trust.

Kerberos came out of the Athena project at MIT in the 1980s and was “embraced and extended” by the Borg, I mean by Microsoft, in Windows 2000 as the basis of Active Directory. The “client” was traditionally a person but it is increasingly software as well.

There are three open source Kerberos implementations widely available to Linux users: MIT, Heimdal, and Shishi. MIT Kerberos 5 is the reference implementation and somewhat more tuned to enterprise users, Heimdal is an implementation made outside of the US, and I know nothing about Shishi. The fact that Heimdal was developed outside of the US was important while the US banned export under ITAR regulations (since it includes strong encryption) and may become important again and we refight the crypto wars.

I will be discussing MIT Kerberos since I’m a little more familiar with it.

The Three-Headed Hellhound

Kerberos is named after the three-headed dog that guards the gate of Hell. This reflects the three parties in a Kerberos session:

The Client

The client may be a person (student, employee) or enterprise software. The client does not log into a remote system. Instead the client logs into a local agent and that agent performs all necessary negotiation with the KDC and servers. In the protocol this is the “ticket granting ticket (TGT)”.

As mentioned earlier the client may also be enterprise software.

The Server

The server can be any software requiring authentication. Traditional servers include telnet (ktelnet) allowing remote access to a shell and NFS (allowing remote access to a filesystem). In the enterprise environment we often see kerberized web services.

The Key Distribution Center (KDC)

The key distribution center (KDC) is the trusted third party. In early implementations the KDC was literally responsible for generating symmetric keys to be used by the client and server, hence the name. I don’t know if that’s still true. The client and server are free to renegotiate their session keys at any time.

All of this occurs within a Kerberos realm. This is an arbitrary string but by convention is the domain name in all caps, e.g., INVARIANTPROPERTIES.COM. This ensures that realms will be unique (modulo bad players). Users within a realm are identified by principals. An individual may have more than one principal but no principal should be used by more than one individual. Principals have the format username@REALM or username/role@REALM, e.g., bgiles/admin@INVARIANTPROPERTIES.COM. Servers have a similar format: servicename/fqdn@REALM.

KDC installation on Debian and Ubuntu

Today we will discuss the installation and configuration of an MIT Kerberos5 KDC on an AWS EC2 instance. MIT can use either an internal database or LDAP for its backing store. The latter will be a better choice if you already use an LDAP database for user management.

I will only discuss the former in this blog entry but might revisit the latter in the future.

Hardware Requirements

The primary hardware requirement for a KDC is security. A compromised KDC will put the entire enterprise at risk. It should be physically secured and run no other services. In production we want at least two servers (one primary and at least one secondary) and they should be in physically separate locations.

It is extremely important that the clocks are synchronized across the all systems within a realm. Historically tickets would be valid for 5 to 15 minutes but with NTP running on all systems this window can be reduced to seconds. Note: this is the window when a ticket can be used for authentication and has no bearing on how long the user can use the service.

A KDC has modest computational requirements. I believe we can start with a micro instance and only upgrade if the need arises. This means the financial cost is extremely modest even if we use multiple KDCs.

We need to open three ports in our firewall. Ports 88 and 750 are required by Kerberos, port 22 for SSH is only required for initial installation and can can be blocked at the AWS Security Group level unless explicitly needed. Subsequent access can eat its own dog food with ktelnet (kerberized telnet), krsh (kerberized rsh), or SSH with Kerberos extensions.

The KDC admin server must also open port 749. Access to this port should be limited.

Debian/Ubuntu Packages

We need to install two packages: krb5-kdc and krb5-admin-server. If you are using LDAP you will want to install krb5-ldap as well. Finally we want to install krb5-doc for documentation.

  1. $ sudo apt-get install krb5-kdc krb5-admin-server krb5-doc
$ sudo apt-get install krb5-kdc krb5-admin-server krb5-doc

During installation we will be asked three things:

  • Name of our realm – this is traditionally our domain name in all-caps.
  • KDC servers – this system
  • KDC admin servers – this system

If you wish you can leave the servers blank at this time and fix the /etc/krb5.conf file below.

Database Initialization

We initialize the internal database with a call to krb5_util. We need to provide our realm name.

  1. $ sudo /usr/sbin/kdb5_util create -r INVARIANTPROPERTIES.COM -s
$ sudo /usr/sbin/kdb5_util create -r INVARIANTPROPERTIES.COM -s

This will take a minute or so to complete.

Defining user rights

We define our users’ rights via an ACL file – /etc/krb5kdc/kadm5.acl. See the man pages for details. One thing to keep in mind is that the user will remain logged in for 7 days by default. Roles with more authority should require reauthentication more frequently.

/etc/krb5dkc/kadm5.acl

  1. */admin@INVARIANTPROPERTIES.COM x  * -maxlife 4h
  2. */root@INVARIANTPROPERTIES.COM  ci *1@INVARIANTPROPERTIE.COM
  3. */root@INVARIANTPROPERTIES.COM  l  *
*/admin@INVARIANTPROPERTIES.COM x  * -maxlife 4h
*/root@INVARIANTPROPERTIES.COM  ci *1@INVARIANTPROPERTIE.COM
*/root@INVARIANTPROPERTIES.COM  l  *

See the man page for kadm5.acl for a description of these entries. The gist is that any principal with ‘admin’ has full administrative rights but must reauthenticate every 4 hours. The superuser on any system can list and update user credentials.

Creating our first administrative user(s)

We must bootstrap our administrative users on the KDC itself. Subsequent user management can be handled remotely. Note that we do NOT need the master password for this – this is why it is critical that access to the KDC be limited.

This shows how to add a user with both regular and administrative rights.

  1. $ sudo kadmin.local
  2. kadmin.local: add_principal bgiles
  3. Enter password for principal "bgiles@INVARIANTPROPERTIES.COM": *******
  4. Re-enter password for principal "bgiles@INVARIANTPROPERTIES.COM": *******
  5. kadmin.local: add_principal bgiles/admin
  6. Enter password for principal "bgiles/admin@INVARIANTPROPERTIES.COM": *******
  7. Re-nter password for principal "bgiles/admin@INVARIANTPROPERTIES.COM": *******
  8. kadmin.local: quit.
$ sudo kadmin.local
kadmin.local: add_principal bgiles
Enter password for principal "bgiles@INVARIANTPROPERTIES.COM": *******
Re-enter password for principal "bgiles@INVARIANTPROPERTIES.COM": *******
kadmin.local: add_principal bgiles/admin
Enter password for principal "bgiles/admin@INVARIANTPROPERTIES.COM": *******
Re-nter password for principal "bgiles/admin@INVARIANTPROPERTIES.COM": *******
kadmin.local: quit.

Registering our servers

If you did not specify the KDC server and KDC admin servers when we installed the packages you can fix this now. Edit the appropriate entry in the /etc/krb5.conf file:

  1. ,,,
  2. [realms]
  3.         INVARIANTPROPERTIES.COM = {
  4.                 kdc = kdc1.invariantproperties.com:88
  5.                 kdc = kdc2.invariantproperties.com:88
  6.                 admin_server = kdc1.invariantproperties.com
  7.                 default_domain = invariantproperties.com
  8.         }
  9. ...
  10. [domain_realm]
  11.         .invariantproperties.com = INVARIANTPROPERTIES.COM
  12.         invariantproperties.com = INVARIANTPROPERTIES.COM
  13. ...
,,,
[realms]
        INVARIANTPROPERTIES.COM = {
                kdc = kdc1.invariantproperties.com:88
                kdc = kdc2.invariantproperties.com:88
                admin_server = kdc1.invariantproperties.com
                default_domain = invariantproperties.com
        }
...
[domain_realm]
        .invariantproperties.com = INVARIANTPROPERTIES.COM
        invariantproperties.com = INVARIANTPROPERTIES.COM
...

This snippet demonstrates how to specify multiple KDC servers. I have not discussed how to perform database replication among multiple KDC servers. This is one task where an LDAP backing store would make much more convenient.

Preparing the Client

We are now ready to set up the client systems so the user can log into Kerberos (that is, get a ticket-granting-ticket (TGT)). I am not going to discuss setting up and accessing kerberized services at this time.

Debian/Ubuntu Packages

We need to install one package: krb5-user. We may also want to install krb5-doc for the rare user who reads the documentation before calling the help desk.

  1. $ sudo apt-get install krb5-user krb5-doc
$ sudo apt-get install krb5-user krb5-doc

You will be asked to specify the Kerberos realm, KDC and KDC admin servers.

Logging in and out

The user can now log into the system using kinit. This command has many options – see the man page for details.

  1. $ kinit
  2. Password for bgiles@INVARIANTPROPERTIES.COM: ******
  3. $ kinit bgiles/admin
  4. Password for bgiles/admin@INVARIANTPROPERTIES.COM ******
$ kinit
Password for bgiles@INVARIANTPROPERTIES.COM: ******
$ kinit bgiles/admin
Password for bgiles/admin@INVARIANTPROPERTIES.COM ******

We can determine our current credentials with klist

  1. $ klist
  2. Ticket cache: FILE:/tmp/krb5cc_1000
  3. Default principal: bgiles/admin@INVARIANTPROPERTIES.COM
  4.  
  5. Valid starting       Expires              Service principal
  6. 04/17/2016 09:40:51  04/17/2016 19:40:51  krbtgt/INVARIANTPROPERTIES.COM@INVARIANTPROPERTIES.COM
  7.     renew until 04/18/2016 09:40:46
$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: bgiles/admin@INVARIANTPROPERTIES.COM

Valid starting       Expires              Service principal
04/17/2016 09:40:51  04/17/2016 19:40:51  krbtgt/INVARIANTPROPERTIES.COM@INVARIANTPROPERTIES.COM
	renew until 04/18/2016 09:40:46

(note that the credentials expire at 10 hours, not 4 hours. I need to look into that…)

Finally we can log out with kdestroy

  1. $ kdestroy
  2. $ klist
  3. klist: Credentials cache file '/tmp/krb5cc_1000' not found
$ kdestroy
$ klist
klist: Credentials cache file '/tmp/krb5cc_1000' not found

You can change your password with kpasswd and change your active principal with kswitch.

Remote administration

Administrators can run kadmin remotely. It is no longer necessary for them to physically log into the KDC and run kadmin.local. They will be prompted to reenter their credentials since this is a sensitive operation.

Keytab files

Finally it can be inconvenient to re-enter your password every time you access a remote service. Keytab files allow you to store a Kerberos principal and encryption key for us in place of a password. You must regenerate the keytab files every time you change your Kerberos password.

Keytabs are maintained with the ktutil command.

For more information see What is a keytab, and how do I use one? from Indiana University.

Next Time

Next time I will discuss setting up kerberized services.

Finally A Cautionary Tale

I almost forgot something important – a server should never ask for a user’s Kerberos credentials. The user always performs local authentication and everything else is negotiated behind the scenes. I mention this because Apache had (and still has?) a Kerberos authentication module that worked by prompting the user for his Kerberos credentials and then logging into the system as that that user on the server in order to obatain resources from other systems. This is entirely contrary to the design goals of Kerberos and it is a huge red flag when it happens – it is possible to get “transferable” tickets that can be used by a server on your behalf.

Comments
No Comments »
Categories
CEU, linux, security
Comments rss Comments rss
Trackback Trackback

Archives

  • August 2018 (1)
  • May 2018 (1)
  • February 2018 (1)
  • November 2017 (4)
  • January 2017 (3)
  • June 2016 (1)
  • May 2016 (1)
  • April 2016 (2)
  • March 2016 (1)
  • February 2016 (3)
  • January 2016 (6)
  • December 2015 (2)
  • November 2015 (3)
  • October 2015 (2)
  • August 2015 (4)
  • July 2015 (2)
  • June 2015 (2)
  • January 2015 (1)
  • December 2014 (6)
  • October 2014 (1)
  • September 2014 (2)
  • August 2014 (1)
  • July 2014 (1)
  • June 2014 (2)
  • May 2014 (2)
  • April 2014 (1)
  • March 2014 (1)
  • February 2014 (3)
  • January 2014 (6)
  • December 2013 (13)
  • November 2013 (6)
  • October 2013 (3)
  • September 2013 (2)
  • August 2013 (5)
  • June 2013 (1)
  • May 2013 (2)
  • March 2013 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • May 2012 (6)
  • January 2012 (2)
  • December 2011 (12)
  • July 2011 (1)
  • June 2011 (2)
  • May 2011 (5)
  • April 2011 (6)
  • March 2011 (4)
  • February 2011 (3)
  • October 2010 (6)
  • September 2010 (8)

Recent Posts

  • Better Ad Blocking Through Pi-Hole and Local Caching
  • The difference between APIs and SPIs
  • Hadoop: User Impersonation with Kerberos Authentication
  • Setting Up Multi-Factor Authentication On Linux Systems
  • Setting Up SSH Identity Forwarding on Jump Hosts

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org

Pages

  • About Me
  • Notebook: Common XML Tasks
  • Notebook: Database/Webapp Security
  • Notebook: Development Tips

Syndication

Java Code Geeks

Know Your Rights

Support Bloggers' Rights
Demand Your dotRIGHTS

Security

  • Dark Reading
  • Krebs On Security Krebs On Security
  • Naked Security Naked Security
  • Schneier on Security Schneier on Security
  • TaoSecurity TaoSecurity

Politics

  • ACLU ACLU
  • EFF EFF

News

  • Ars technica Ars technica
  • Kevin Drum at Mother Jones Kevin Drum at Mother Jones
  • Raw Story Raw Story
  • Tech Dirt Tech Dirt
  • Vice Vice

Spam Blocked

52,509 spam blocked by Akismet
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox