# Integrity (crypto)

##ENG  
[PL](#pl-version)

In the task we get a service:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

As can be seen we can either login or register.  
Loggin in as admin will give us the flag, so this is the ultimate goal.  
We can see that we can't register as `admin` because this is explicitly
checked.

Once we register we get as a result login token in the form `IV | AES_CBC(md5(login) | login)`.

Since we can modify freely the IV, we can force the decoding of md5 checksum
to any value we want.  
This is because in CBC block crypto the first decrypted block is `IV xor
AES_decrypt(ciphertext[0])` and we know the value of
`AES_decrypt(ciphertext[0])` because it is md5 of the login we provided.  
So in order to force k-th decoded byte to value `X` we simply need to modify
k-th byte of IV to `IV[k] ^ md5(login)[k] ^ X`.

As a result we can easily fool the md5 checksum and we can provide IV such
that the decoded md5 checksum will be a checksum of `pad("admin")`.

Now we need somehow to obtain ciphertext which will decode to `pad("admin")`.  
This is again quite simple, since we're dealing with block crypto and the
login token does not contain any information about the length of the provided
input (unlike in real HMAC).  
It means that we can remove some ciphertext blocks from the end and the
decryption will still work just fine, assuming the padding is correct.

So if we decide to encrypt login in a form of `pad("admin")+16_random_bytes`,
the server will send us ciphertext of `pad(pad("admin")+16_random_bytes)`.  
And of course this translates to 3 ciphertext blocks in a form of
`pad("admin")+16_random_bytes+PKCS_padding`.  
But as said before, we can simply remove the last 2 blocks of ask server to
use only the first one, which decodes to `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

And this gives us `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`

##PL version

W zadaniu dostajemy dostęp do serwisu:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

Jak łatwo zauważyć możemy się zalogować lub zarejetrować.  
Logowanie jako admin pozwoli uzyskać flagę i to jest naszym finalnym celem.  
Możemy zauważyć, że nie możemy zarejestrować się jako `admin` ponieważ jest to
wyraźnie sprawdzane.

Po rejestracji dostajemy token logowania w postaci `IV | AES_CBC(md5(login) | login)`.

Ponieważ możemy dowolnie zmieniać IV, możemy wymusić dowolne dekodowanie
wartości md5.  
Wynika to bezpośrednio z tego jak działa szyfr blokowy w trybie CBC - pierwszy
dekodowany blok to `IV xor AES_decrypt(ciphertext[0])` a znamy wartość
`AES_decrypt(ciphertext[0])`bo to md5 wyliczone z wprowadzonych przez nas
danych.  
Więc żeby wymusić dekodowanie k-tego bajtu do wartości `X` musimy jedynie
zmienić k-ty bajt IV na `IV[k] ^ md5(login)[k] ^ X`

W efekcie możemy w prosty sposób oszukać checksume md5 i podać takie IV, że
odkodowane md5 będzie zgodne z checksumą dla `pad("admin")`.

Teraz potrzebujemy uzyskać ciphertext który zdekoduje się do `pad("admin")`.  
To znów jest dość proste, ponieważ mamy do czynienia z szyfrem blokowym a
token logowania nie zawiera nigdzie informacji o długości wprowadzonych danych
(w przeciwieństwie do prawdziwego HMAC).  
To oznacza, że możemy usunąć kilka bloków ciphertextu z końca a deszyfrowanie
nadal przebiegnie pomyślnie, o ile padding się zgadza.

Jeśli więc zdecydujemy się zaszyfrować login w postaci
`pad("admin")+16_random_bytes` serwer odeśle nam ciphertext dla
`pad(pad("admin")+16_random_bytes)`  
A to oczywiście oznacza że dostaniemy 3 bloki ciphertextu w postaci
`pad("admin")+16_random_bytes+PKCS_padding`.  
Jak wspomnieliśmy wcześniej, możemy teraz usunąć 2 ostatnie bloki i poprosić
serwer o deszyfrowanie tylko pierwszego, który dekoduje się do `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

A to daje nam `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`  

Original writeup (https://github.com/p4-team/ctf/tree/master/2017-03-18-0ctf-
quals/integrity).# Integrity (crypto)

##ENG  
[PL](#pl-version)

In the task we get a service:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

As can be seen we can either login or register.  
Loggin in as admin will give us the flag, so this is the ultimate goal.  
We can see that we can't register as `admin` because this is explicitly
checked.

Once we register we get as a result login token in the form `IV | AES_CBC(md5(login) | login)`.

Since we can modify freely the IV, we can force the decoding of md5 checksum
to any value we want.  
This is because in CBC block crypto the first decrypted block is `IV xor
AES_decrypt(ciphertext[0])` and we know the value of
`AES_decrypt(ciphertext[0])` because it is md5 of the login we provided.  
So in order to force k-th decoded byte to value `X` we simply need to modify
k-th byte of IV to `IV[k] ^ md5(login)[k] ^ X`.

As a result we can easily fool the md5 checksum and we can provide IV such
that the decoded md5 checksum will be a checksum of `pad("admin")`.

Now we need somehow to obtain ciphertext which will decode to `pad("admin")`.  
This is again quite simple, since we're dealing with block crypto and the
login token does not contain any information about the length of the provided
input (unlike in real HMAC).  
It means that we can remove some ciphertext blocks from the end and the
decryption will still work just fine, assuming the padding is correct.

So if we decide to encrypt login in a form of `pad("admin")+16_random_bytes`,
the server will send us ciphertext of `pad(pad("admin")+16_random_bytes)`.  
And of course this translates to 3 ciphertext blocks in a form of
`pad("admin")+16_random_bytes+PKCS_padding`.  
But as said before, we can simply remove the last 2 blocks of ask server to
use only the first one, which decodes to `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

And this gives us `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`

##PL version

W zadaniu dostajemy dostęp do serwisu:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

Jak łatwo zauważyć możemy się zalogować lub zarejetrować.  
Logowanie jako admin pozwoli uzyskać flagę i to jest naszym finalnym celem.  
Możemy zauważyć, że nie możemy zarejestrować się jako `admin` ponieważ jest to
wyraźnie sprawdzane.

Po rejestracji dostajemy token logowania w postaci `IV | AES_CBC(md5(login) | login)`.

Ponieważ możemy dowolnie zmieniać IV, możemy wymusić dowolne dekodowanie
wartości md5.  
Wynika to bezpośrednio z tego jak działa szyfr blokowy w trybie CBC - pierwszy
dekodowany blok to `IV xor AES_decrypt(ciphertext[0])` a znamy wartość
`AES_decrypt(ciphertext[0])`bo to md5 wyliczone z wprowadzonych przez nas
danych.  
Więc żeby wymusić dekodowanie k-tego bajtu do wartości `X` musimy jedynie
zmienić k-ty bajt IV na `IV[k] ^ md5(login)[k] ^ X`

W efekcie możemy w prosty sposób oszukać checksume md5 i podać takie IV, że
odkodowane md5 będzie zgodne z checksumą dla `pad("admin")`.

Teraz potrzebujemy uzyskać ciphertext który zdekoduje się do `pad("admin")`.  
To znów jest dość proste, ponieważ mamy do czynienia z szyfrem blokowym a
token logowania nie zawiera nigdzie informacji o długości wprowadzonych danych
(w przeciwieństwie do prawdziwego HMAC).  
To oznacza, że możemy usunąć kilka bloków ciphertextu z końca a deszyfrowanie
nadal przebiegnie pomyślnie, o ile padding się zgadza.

Jeśli więc zdecydujemy się zaszyfrować login w postaci
`pad("admin")+16_random_bytes` serwer odeśle nam ciphertext dla
`pad(pad("admin")+16_random_bytes)`  
A to oczywiście oznacza że dostaniemy 3 bloki ciphertextu w postaci
`pad("admin")+16_random_bytes+PKCS_padding`.  
Jak wspomnieliśmy wcześniej, możemy teraz usunąć 2 ostatnie bloki i poprosić
serwer o deszyfrowanie tylko pierwszego, który dekoduje się do `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

A to daje nam `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`  

Original writeup (https://github.com/p4-team/ctf/tree/master/2017-03-18-0ctf-
quals/integrity).# Integrity (crypto)

##ENG  
[PL](#pl-version)

In the task we get a service:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

As can be seen we can either login or register.  
Loggin in as admin will give us the flag, so this is the ultimate goal.  
We can see that we can't register as `admin` because this is explicitly
checked.

Once we register we get as a result login token in the form `IV | AES_CBC(md5(login) | login)`.

Since we can modify freely the IV, we can force the decoding of md5 checksum
to any value we want.  
This is because in CBC block crypto the first decrypted block is `IV xor
AES_decrypt(ciphertext[0])` and we know the value of
`AES_decrypt(ciphertext[0])` because it is md5 of the login we provided.  
So in order to force k-th decoded byte to value `X` we simply need to modify
k-th byte of IV to `IV[k] ^ md5(login)[k] ^ X`.

As a result we can easily fool the md5 checksum and we can provide IV such
that the decoded md5 checksum will be a checksum of `pad("admin")`.

Now we need somehow to obtain ciphertext which will decode to `pad("admin")`.  
This is again quite simple, since we're dealing with block crypto and the
login token does not contain any information about the length of the provided
input (unlike in real HMAC).  
It means that we can remove some ciphertext blocks from the end and the
decryption will still work just fine, assuming the padding is correct.

So if we decide to encrypt login in a form of `pad("admin")+16_random_bytes`,
the server will send us ciphertext of `pad(pad("admin")+16_random_bytes)`.  
And of course this translates to 3 ciphertext blocks in a form of
`pad("admin")+16_random_bytes+PKCS_padding`.  
But as said before, we can simply remove the last 2 blocks of ask server to
use only the first one, which decodes to `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

And this gives us `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`

##PL version

W zadaniu dostajemy dostęp do serwisu:

```python  
#!/usr/bin/python -u

from Crypto.Cipher import AES  
from hashlib import md5  
from Crypto import Random  
from signal import alarm

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)  
unpad = lambda s : s[0:-ord(s[-1])]

class Scheme:  
def __init__(self,key):  
self.key = key

def encrypt(self,raw):  
raw = pad(raw)  
raw = md5(raw).digest() + raw

iv = Random.new().read(BS)  
cipher = AES.new(self.key,AES.MODE_CBC,iv)

return ( iv + cipher.encrypt(raw) ).encode("hex")

def decrypt(self,enc):  
enc = enc.decode("hex")

iv = enc[:BS]  
enc = enc[BS:]

cipher = AES.new(self.key,AES.MODE_CBC,iv)  
blob = cipher.decrypt(enc)

checksum = blob[:BS]  
data = blob[BS:]

if md5(data).digest() == checksum:  
return unpad(data)  
else:  
return

key = Random.new().read(BS)  
scheme = Scheme(key)

flag = open("flag",'r').readline()  
alarm(30)

print "Welcome to 0CTF encryption service!"  
while True:  
print "Please [r]egister or [l]ogin"  
cmd = raw_input()

if not cmd:  
break

if cmd[0]=='r' :  
name = raw_input().strip()

if(len(name) > 32):  
print "username too long!"  
break  
if pad(name) == pad("admin"):  
print "You cannot use this name!"  
break  
else:  
print "Here is your secret:"  
print scheme.encrypt(name)

elif cmd[0]=='l':  
data = raw_input().strip()  
name = scheme.decrypt(data)

if name == "admin":  
print "Welcome admin!"  
print flag  
else:  
print "Welcome %s!" % name  
else:  
print "Unknown cmd!"  
break  
```

Jak łatwo zauważyć możemy się zalogować lub zarejetrować.  
Logowanie jako admin pozwoli uzyskać flagę i to jest naszym finalnym celem.  
Możemy zauważyć, że nie możemy zarejestrować się jako `admin` ponieważ jest to
wyraźnie sprawdzane.

Po rejestracji dostajemy token logowania w postaci `IV | AES_CBC(md5(login) | login)`.

Ponieważ możemy dowolnie zmieniać IV, możemy wymusić dowolne dekodowanie
wartości md5.  
Wynika to bezpośrednio z tego jak działa szyfr blokowy w trybie CBC - pierwszy
dekodowany blok to `IV xor AES_decrypt(ciphertext[0])` a znamy wartość
`AES_decrypt(ciphertext[0])`bo to md5 wyliczone z wprowadzonych przez nas
danych.  
Więc żeby wymusić dekodowanie k-tego bajtu do wartości `X` musimy jedynie
zmienić k-ty bajt IV na `IV[k] ^ md5(login)[k] ^ X`

W efekcie możemy w prosty sposób oszukać checksume md5 i podać takie IV, że
odkodowane md5 będzie zgodne z checksumą dla `pad("admin")`.

Teraz potrzebujemy uzyskać ciphertext który zdekoduje się do `pad("admin")`.  
To znów jest dość proste, ponieważ mamy do czynienia z szyfrem blokowym a
token logowania nie zawiera nigdzie informacji o długości wprowadzonych danych
(w przeciwieństwie do prawdziwego HMAC).  
To oznacza, że możemy usunąć kilka bloków ciphertextu z końca a deszyfrowanie
nadal przebiegnie pomyślnie, o ile padding się zgadza.

Jeśli więc zdecydujemy się zaszyfrować login w postaci
`pad("admin")+16_random_bytes` serwer odeśle nam ciphertext dla
`pad(pad("admin")+16_random_bytes)`  
A to oczywiście oznacza że dostaniemy 3 bloki ciphertextu w postaci
`pad("admin")+16_random_bytes+PKCS_padding`.  
Jak wspomnieliśmy wcześniej, możemy teraz usunąć 2 ostatnie bloki i poprosić
serwer o deszyfrowanie tylko pierwszego, który dekoduje się do `pad("admin")`!

```python  
import hashlib  
from crypto_commons.netcat.netcat_commons import nc, send, receive_until_match

BS = 16  
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

def generate_payload(ct):  
stripped_ct = ct[:-64] # skip last 2 blocks, the PKCS padding and the dummy
block  
target_md5 = hashlib.md5(pad("admin")).digest() # md5 we want to get from
decryption  
source_md5 = hashlib.md5(pad(pad("admin") + ("a" * 16))).digest() # md5 the
server calculated  
original_iv = stripped_ct[:32].decode("hex")  
new_iv = []  
for i in range(len(original_iv)):  
new_iv.append(chr(ord(original_iv[i]) ^ ord(source_md5[i]) ^
ord(target_md5[i])))  
iv = "".join(new_iv).encode("hex")  
payload = iv + stripped_ct[32:]  
return payload

def main():  
s = nc("202.120.7.217", 8221)  
print(receive_until_match(s, ".*ogin"))  
send(s, "r")  
send(s, pad("admin") + ("a" * 16))  
print(receive_until_match(s, ".*secret:"))  
ct = s.recv(9999).split("\n")[1]  
send(s, "l")  
send(s, generate_payload(ct))  
print(s.recv(9999))  
print(s.recv(9999))  
print(s.recv(9999))

main()  
```

A to daje nam `flag{Easy_br0ken_scheme_cann0t_keep_y0ur_integrity}`  

Original writeup (https://github.com/p4-team/ctf/tree/master/2017-03-18-0ctf-
quals/integrity).# Web

## Swaggy  
Category: Web  
Difficulty: Easy

Description:  
This API documentation has all the swag

### Solve  
![image](Swaggy/1.png)

There is a weak password here, `admin, admin`

![image](Swaggy/2.png)

After log in, we test the api and get the flag.

flag{e04f962d0529a4289a685112bf1dcdd3}

\---

## Confidentiality  
Category: Web  
Difficulty: Easy

Description:  
My school was trying to teach people about the CIA triad so they made all
these dumb example applications... as if they know anything about information
security. Can you prove these aren't so secure?

### Solution  
![image](Confidentiality/1.png)

It is a command injection.

\---

## Integrity  
Category: Web  
Difficulty: Medium

Description:  
My school was trying to teach people about the CIA triad so they made all
these dumb example applications... as if they know anything about information
security.

Supposedly they learned their lesson and tried to make this one more secure.
Can you prove it is still vulnerable?

### Solution  
![image](Integrity/1.png)

Command injection bypass filter `\n`

I have used MaxHackbar addon for this challenge.After installing this
addon/extension,the steps is as under:  
1) You need to click on Load URL first to load the URL.  
2) You need to type ```file=\ncat flag.txt``` as your post data to get the
contents of the flag.txt file. And hence you will get the flag.

command injection cheatsheet: https://hackersonlineclub.com/command-injection-
cheatsheet/

Note : Addon/Extension used here is available on
https://addons.mozilla.org/en-US/firefox/ .  
\---

## OPA Secrets  
Category: Web  
Difficulty: Hard

Description:  
OPA! Check out our new secret management service

### Solution  
This is a code audit challenge.  
After we look around the website, we find source code of the website.

![image](OPA_Secrets/1.png)

The flag is hidden in the secret. We may use the secret id to get the flag.

![image](OPA_Secrets/2.png)

The getValue function will return the value of secrete.

![image](OPA_Secrets/3.png)

The get secret function will return value without check user session id.

![image](OPA_Secrets/4.png)

We make up the post and change the secret id to get the flag

***flag{589882d62d1c899d8b85db1af2076b39}***  

Original writeup (https://github.com/todd-tao/CTF-
WriteUps/tree/main/H%40cktivityCon_2021_CTF/Web).