Spoofing CGI Variables - Security Series #11

So after a long interruption, I am restarting my security series. I really enjoy blogging about security topics and want to get back to it.

What is a CGI variable?

CGI Environment Variables are a set of variables created by the web server and the browser to communication information about the transaction taking place between the two.

Some CGI Environment Variables may look familiar to you:

  • REMOTE_HOST
  • HTTP_REFERER
  • HTTP_USER_AGENT
  • SCRIPT_NAME
  • PATH_INFO

Using CGI Variables

I know I have done this, and I am sure many others have done this or still do it.

Hypothetically, I have a page that I want to secure. It's a simple page for deleting blog entries or something. I don't want to go to a lot of trouble building some elaborate security system or anything, I just want to make sure that the page can only be called by a certain other page from a certain IP address.

The page I am trying to secure is called deleteEntry.cfm (it could just as easily be entryService.cfc with a deleteEntry() method) and I only want it to work if it is called from myAdminPage.cfm and has a entryID passed in. So I might have some code that looks like this.


    <cfif
        cgi.HTTP_REFERER EQ "http://www.12robots.com/myAdminPage.cfm"
        AND StructKeyExists(form, "entryid")
        AND IsNumeric(form.entryid)
        AND cgi.remote_host EQ "71.244.78.2">

        
        <!--- Go a head and delete --->
        <cfset deleteEntry(entryID) />
    <cfelse>
        <!--- abort request --->
        <cfthrow message="Unathorized Access" />
        <cfabort />
    </cfif>

Some might think that this is pretty well locked down. It can only be accessed from a specific page called from a specific IP address. I used to think this was pretty good too.

Spoofing CGI Variables

As you may have guessed from the title of this post, we are going to talk about spoofing CGI environment variables. As it turns out, it's really not very hard. Looking at our example above, if my deletePage.cfm (or deletePage() method) was really secured this way, it would be a trivial matter for a hacker to write a remote page that could delete any entry of his/her choosing.

HackmyBlog.cfm:


    <cfhttp method="post" url="http://www.12robots.com/deletePage.cfm" result="myVar">
        <cfhttpparam type="header" name="HTTP_REFERER" value="http://www.12robots.com/myAdminPage.cfm">
        <cfhttpparam type="header" name="REMOTE_HOST" value="71.244.78.2">
        <cfhttpparam type="formfield" name="entryid" value="1">
    </cfhttp>

The hacker could run this CFM file from anywhere, even his/her local machine, and it would call the delete page with all of the parameters and form fields needed to delete the entry with entryid = 1.

Now consider:


    <cfloop from="1" to="100" index="pwnd">
        <cfhttp method="post" url="http://www.12robots.com/test.cfc" result="myVar">
            <cfhttpparam type="url" name="method" value="test">
            <cfhttpparam type="header" name="HTTP_REFERER" value="http://www.12robots.com/myAdminPage.cfm">
            <cfhttpparam type="header" name="REMOTE_HOST" value="71.244.78.2">
            <cfhttpparam type="formfield" name="entryid" value="#pwnd#">
        </cfhttp>
    </cfloop>

Conclusion

Because CGI variables can be manipulated so easily, they simply cannot be trusted to provide any type of information that governs access control or other security for your site. This is a situation where proper session management and access control combined with Request Forgery protection will offer the real protection your site needs.

Comments
Gary F's Gravatar Good post, Jason. What happens to the genuine "remote_host" IP? Does that remain in the CGI scope or is it totally overwritten? I thought the web server would look at the incomming TCP packet to establish the IP rather than to trust the client browser? Or is CF more fallable because it's willing to look at the HTTP header from the browser to get the IP?
# Posted By Gary F | 12/10/08 2:01 AM
Jura Khrapunov's Gravatar Nice post, it raised some questions in terms of security of our applications. However after a couple of tests it seems that not all CGI variables affected by this behavior. For example CGI.REMOTE_ADDR cannot be changed by just providing fake data in the request header - apparently it comes from the different source than just CGI header.
# Posted By Jura Khrapunov | 12/10/08 4:51 AM
Jason Dean's Gravatar @Gary, as far as I know, the genuine remote_host is never created, it simply relies on the input from the browser. That apparently does not apply to all CGI environment variables as Jura points out.

@Jura, Good Catch and thank you for taking this further. I looked a little more into this after I saw your comment. Since cgi.remote_addr is created by the web server, it cannot be overridden so easily by our httpparams. In my reading, it looks like the only way to spoof an IP address would be by actually modifying the TCP/IP packets that are sent, and I am not sure how difficult that would be. Someday I may need to look into that. Personally, I would still be skeptical of cgi.remote_addr for really secure systems.

Thank you both for your comments.
# Posted By Jason Dean | 12/10/08 8:25 AM
Gary F's Gravatar I had always wondered why CF bothers to provide two cgi vars for the client IP as they always appear identical. I'm really pleased to learn what the difference is and it's rather significant as far as application security goes. Adobe should point out the difference in the docs.

I must go and change some var names now... :-) Jura, thank you for the enlightenment.
# Posted By Gary F | 12/10/08 1:15 PM
Ben Nadel's Gravatar When you spoof a header value that starts with "http_", remove the "http_" prefix. So, when spoofing referer, pass back the header "referer", not "http_referer". I learned that from Charlie Arehart.
# Posted By Ben Nadel | 12/11/08 8:13 AM
Jason Dean's Gravatar @Ben, But why? I'm curious if there is a technical reason for that. The way that I did it seems to work the way I intended it to.
# Posted By Jason Dean | 12/11/08 8:36 AM
Ben Nadel's Gravatar @Jason,

You are supposed to use the "official" names for the CGI / Header values as defined by the w3c:

http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html...

I have definitely run into cases when not following this rule caused things not to work (especially with screen scraping and use of the referrer).
# Posted By Ben Nadel | 12/11/08 8:42 AM
Jura Khrapunov's Gravatar Actually, by using standards compliant headers you can spoof not only ColdFusion-based scriptsbut also other standards compliant applications. For example PHP.
# Posted By Jura Khrapunov | 12/11/08 8:47 AM
Jason Dean's Gravatar @Ben, @Jura Very cool. Thanks for the comments and information. That makes sense.
# Posted By Jason Dean | 12/11/08 8:56 AM
Ben Nadel's Gravatar @Jason,

I have pounded my head against the wall for many hours because of this! I think the most confusing thing is that if you use the HTTP_ prefix and then CFDump out the CGI scope, it *looks* like the value is coming through correctly. But, I think that's just ColdFusion doing some shenanigans and is not how outside parties will see the values.
# Posted By Ben Nadel | 12/11/08 9:01 AM
Sum's Gravatar Is it possible to spoof auth_user or remote_user?
# Posted By Sum | 10/26/10 11:09 AM
Jason Dean's Gravatar If it is data provided by the client (which I believe both of those are) then any value can be passed.
# Posted By Jason Dean | 10/29/10 1:00 PM
Henry Ho's Gravatar Just tried on CF10 using IIS, remote_host & remote_addr are not spoofed. So this has been fixed?

However, the reason I came across this post because I'm getting some IP spoof but not sure how the hacker did it.
# Posted By Henry Ho | 4/22/14 3:06 PM
Jason Dean's Gravatar @Henry,

There is nothing that can be fixed. The reason spoofing is possible is because there is a disconnect between client and server between requests.

If the client sends a different remote_host than the one it is actually requesting from, the server has no way of knowing that, so it can't do anything to compensate for it.

I can't imagine how you would know that either. Unless the request is coming from an IP over which you have control and know *for certain* is not sending the request, then how could you know the IP is being spoofed?

Oh, and you can't spoof remote_host or remote_addr using the methods I outlined above. That information does not come from the client software, it comes from the IP packet itself. For someone to spoof their remote IP they would need to change the IP packet, not the content (the request).
# Posted By Jason Dean | 4/22/14 5:54 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner