summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/aes.rs87
-rw-r--r--src/lib.rs1
-rw-r--r--tests/lib.rs42
3 files changed, 130 insertions, 0 deletions
diff --git a/src/aes.rs b/src/aes.rs
index 67dee34..48f70b3 100644
--- a/src/aes.rs
+++ b/src/aes.rs
@@ -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;
diff --git a/src/lib.rs b/src/lib.rs
index ddb53f5..582a8e8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
+ }));
+}