Cyber Lab
Spring 2024 - Week 5
https://l.acmcyber.com/s24-w5-lab
π£ Announcements
Security by Design
What is it?
What is my goal?
Security via Types
Types aren't just how data is laid out in memory; they describe what a value means
By designing your types carefully, you can stop a whole range of invalid (and insecure) usage
Example Vulnerability
Chunked decryption with AES-GCM cipher in Node.js:
const cipher: Cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
const chunks: Buffer[] = [];
for (const chunk of data) {
chunks.push(cipher.update(chunk));
}
chunks.push(cipher.final()); // crucial line
const decrypted: Buffer = Buffer.concat(chunks);
If you leave out the call to cipher.final(), your code won't break, but it'll be vulnerable to data forgery
Fixing it with types
What the types look like right now:
interface Cipher {
// decrypts a chunk of data
update(data: Buffer): Buffer;
// verifies the auth tag and returns leftover data
final(): Buffer;
}
Problem: There is nothing in the typesystem saying that final has to be called in order to use the data
// opaque wrapper around a buffer
class UnvalidatedData { private inner: Buffer; }
class Cipher {
// decrypts a chunk of data
update(data: Buffer): UnvalidatedData {
// wrap the decrypted data so that it's unusable
return new UnvalidatedData(this.oldUpdate(data));
}
// verifies the auth tag, appends remaining data, and concats data
final(chunks: UnvalidatedData[]): Buffer {
chunks.push(this.oldFinal());
// use some private method to unwrap the unvalidated data
return Buffer.concat(chunks.map(x => x.inner));
}
}
Now the data is unusable without calling final, forcing security in a way that's intuitive for the user
Takeaways
Security via types:
Avoiding Invalid Values
Instead of allowing arbitrary values to be constructed then validating later, you should validate during construction
Allowing the creation of incomplete/invalid values means allowing the developer to forget to complete/validate it later
Example
Consider the following logic for saving a user in an ORM (suppose this is in a web server):
const user = User.create({username: blah, password: blah});
if (user.isValid()) {
user.save();
} else {
throw new Error("invalid user");
}
You need to remember to validate the user every single time you create one
Just put the validation in User.create instead
Especially Egregious Example
If you have an unusable "zero value", don't have one to begin with!
const user = new User();
user.username = blah;
user.password = blah;
user.age = blah;
Worsened by languages like C++ and Go which are designed around expecting a zero/default value to be meaningful when they frequently aren't
Topic Speedrun
Things I didn't cover but are nevertheless important:
Conclusion
Questions?
Thanks for coming! β€οΈ