diff options
-rw-r--r-- | src/aes.rs | 87 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | tests/lib.rs | 42 |
3 files changed, 130 insertions, 0 deletions
@@ -1,5 +1,6 @@ use std; use std::collections::{HashMap, HashSet}; +use std::collections::hash_map::Entry::{Occupied, Vacant}; use openssl; @@ -139,6 +140,92 @@ pub fn crack_padded_aes_128_ecb<F> (f: &F) -> Vec<u8> where F: Fn(&[u8]) -> Vec< return unpad_pkcs7(&plaintext[..]).to_vec(); } +pub fn crack_querystring_aes_128_ecb<F> (encrypter: F) -> (String, Vec<Vec<u8>>) where F: Fn(&str) -> Vec<u8> { + // find blocks that correspond to "uid=10&role=user" or "role=user&uid=10" + let find_uid_role_blocks = || { + let mut map = HashMap::new(); + for c in 32..127 { + let email_bytes: Vec<u8> = std::iter::repeat(c).take(9).collect(); + let email = std::str::from_utf8(&email_bytes[..]).unwrap(); + let ciphertext = encrypter(email); + match map.entry(ciphertext[..16].to_vec()) { + Occupied(mut o) => { *o.get_mut() += 1; }, + Vacant(v) => { v.insert(1); }, + } + match map.entry(ciphertext[16..32].to_vec()) { + Occupied(mut o) => { *o.get_mut() += 1; }, + Vacant(v) => { v.insert(1); }, + } + } + + let mut most_common_blocks = vec![]; + for (k, v) in map { + most_common_blocks.push((k, v)); + if most_common_blocks.len() > 2 { + let (idx, _) = most_common_blocks + .iter() + .enumerate() + .fold( + (0, (vec![], 10000)), + |(aidx, (ablock, acount)), (idx, &(ref block, count))| { + if count < acount { + (idx, (block.clone(), count)) + } + else { + (aidx, (ablock.clone(), acount)) + } + } + ); + most_common_blocks.swap_remove(idx); + } + } + + if let [(ref block1, i1), (ref block2, i2)] = &most_common_blocks[..] { + return (block1.clone(), block2.clone()); + } + else { + panic!("couldn't find most common blocks"); + } + }; + + // encrypt: + // email=..........admin<pcks7 padding>...............&uid=10&role=user + let calculate_admin_block = |block1, block2| { + for _ in 0..1000 { + let email = "blorg@bar.admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b..............."; + let ciphertext = encrypter(email); + if &ciphertext[48..64] == block1 || &ciphertext[48..64] == block2 { + return ciphertext[16..32].to_vec(); + } + } + panic!("couldn't find a ciphertext with the correct role/uid block"); + }; + + // find all possible encryptions with email=............ and then replace + // the last block with the padded admin block above + let calculate_possible_admin_ciphertexts = |admin_block: Vec<u8>| { + let email = "blorg@bar.com"; + let mut possibles = vec![]; + while possibles.len() < 6 { + let ciphertext = encrypter(email); + let modified_ciphertext = ciphertext + .iter() + .take(32) + .chain(admin_block.iter()) + .map(|x| *x) + .collect(); + if !possibles.iter().any(|possible| possible == &modified_ciphertext) { + possibles.push(modified_ciphertext); + } + } + return (String::from_str(email), possibles); + }; + + let (block1, block2) = find_uid_role_blocks(); + let admin_block = calculate_admin_block(block1, block2); + return calculate_possible_admin_ciphertexts(admin_block); +} + fn count_duplicate_blocks (input: &[u8], block_size: usize) -> usize { let mut set = HashSet::new(); let mut dups = 0; @@ -16,6 +16,7 @@ pub use aes::encrypt_aes_128_cbc; pub use aes::find_aes_128_ecb_encrypted_string; pub use aes::detect_ecb_cbc; pub use aes::crack_padded_aes_128_ecb; +pub use aes::crack_querystring_aes_128_ecb; pub use base64::to_base64; pub use http::parse_query_string; pub use http::create_query_string; diff --git a/tests/lib.rs b/tests/lib.rs index 577eeb4..629b7c2 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -2,6 +2,8 @@ extern crate matasano; extern crate "rustc-serialize" as serialize; extern crate rand; +use std::borrow::ToOwned; +use std::collections::HashMap; use std::io::prelude::*; use std::fs::File; @@ -210,3 +212,43 @@ fn problem_12 () { let got = matasano::crack_padded_aes_128_ecb(&random_encrypter); assert_eq!(got, padding); } + +#[test] +fn problem_13 () { + fn profile_for (email: &str) -> String { + let mut params = HashMap::new(); + params.insert("email", email); + params.insert("uid", "10"); + params.insert("role", "user"); + return matasano::create_query_string(params); + } + + let key = random_aes_128_key(); + let encrypter = |email: &str| -> Vec<u8> { + matasano::encrypt_aes_128_ecb(profile_for(email).as_bytes(), &key[..]) + }; + let decrypter = |ciphertext: &[u8]| -> Option<HashMap<String, String>> { + let plaintext = matasano::decrypt_aes_128_ecb(ciphertext, &key[..]); + let plaintext_str = std::str::from_utf8(&plaintext[..]).unwrap(); + if let Some(params) = matasano::parse_query_string(plaintext_str) { + return Some( + params + .into_iter() + .map(|(k, v)| (String::from_str(k), String::from_str(v))) + .collect() + ); + } + else { + return None; + } + }; + + let (email, ciphertexts) = matasano::crack_querystring_aes_128_ecb(encrypter); + let mut expected = HashMap::new(); + expected.insert(String::from_str("email"), email); + expected.insert(String::from_str("uid"), String::from_str("10")); + expected.insert(String::from_str("role"), String::from_str("admin")); + assert!(ciphertexts.iter().any(|ciphertext| { + decrypter(ciphertext).map(|params| params == expected).unwrap_or(false) + })); +} |