{-# LANGUAGE OverloadedStrings #-}
module Challenge26
(
Profile, mkProfile, decryptProfile
, isAdmin
, fakeAdmin
) where
import Bytes ( HasBytes(..), Bytes, xorb )
import AES ( encryptCTR, decryptCTR )
import Data.Maybe ( fromJust, mapMaybe )
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import qualified Network.URL as URLThis is even easier than Challenge 16, where we did CBC bitflipping.
newtype Profile = Profile { encryptedProfile :: Bytes }
deriving (Eq,Show)We can decrypt the profile. Unlike the CBC case, there is no padding to verify.
decryptProfile :: (HasBytes key, HasBytes nonce)
=> key -> nonce -> Profile -> Maybe Bytes
decryptProfile key nonce = decryptCTR key nonce . encryptedProfileWe create the profile in the same way: sanitize the supplied user data then concatenate and encrypt.
mkProfile :: (HasBytes key, HasBytes nonce, HasBytes userdata)
=> key -> nonce -> userdata -> Maybe Profile
mkProfile key nonce userdata = do
sanitized <- sanitize (toBytes userdata)
fmap Profile $ encryptCTR key nonce $
B.concat ["comment1=cooking%20MCs&userdata=",sanitized
,"&comment2=%20like%20a%20pound%20of%20bacon"]
where
sanitize str | BC.any (\ch -> ch == '&' || ch == '=') str = Nothing
| otherwise = Just strWe once again use the url library
to look for key-value pair admin=true.
isAdmin :: (HasBytes key, HasBytes nonce)
=> key -> nonce -> Profile -> Bool
isAdmin key nonce profile = maybe False (=="true") $ do
ps <- decryptProfile key nonce profile
url <- URL.importURL ("/?" ++ fromBytes ps)
lookup "admin" (URL.url_params url)Creating a fake admin profile with CTR bitflipping is even easier than with CBC, since we can just apply our XOR to the block we want to edit, not to the previous one! Other than that, the idea is the same.
fakeAdmin :: (Bytes -> Maybe Profile) -> Profile
fakeAdmin mkProfile =Our legitimate user profile has user data which is just &admin=true,
with the & and = replaced by an arbitrary character, say A.
let evilUserData = "&admin=true"
legitUserData = BC.map (\c -> if BC.elem c "&=" then 'A' else c)
evilUserData
legitProfileData = encryptedProfile $ fromJust $ mkProfile legitUserDataWe know the prefix text (we have to in this case, since there's no blocksize to help us figure it out). We don't want to alter the prefix or suffix at all, so we'll XOR them against zeros.
prefixLen = B.length "comment1=cooking%20MCs&userdata="
suffixLen = numBytes legitProfileData - prefixLen - numBytes evilUserData
bitmask = B.replicate prefixLen 0 <>
xorb evilUserData legitUserData <>
B.replicate suffixLen 0The manipulated profile is then just the bitmask XORed against the legitimate one.
in Profile (legitProfileData `xorb` bitmask)