Request Forgeries and ColdFusion - Security Series #9

It's been a while since I posed an entry, and it has been even longer since I posted for my on-going security series. So today I would like to get back to it by looking at Request Forgeries.

What is a Request Forgery?

A request forgery, also sometimes called a Cross-Site (or On-Site) Request Forgery(XSRF), is an attack that is perpetrated against the user of a site who has authenticated access to that site. The user is unwittingly tricked into performing actions on a site through hidden code displayed to them and, therefore, executed in their browser.

For a request forgery to work, the user must still have an active, authenticated session for the site that the action will be performed against.

That was confusing as hell, can we have an example?

Yes. Our website (http://www.easilypwnd.com) has a administrators only section for maintaining the website. Bob, one of our administrators is logged into the site and is making routine changes. While he is logged in, he receives an email from Kay in Accounting (but it is actually a spoofed email from 1337 H@x0r MattOverdrive) with a stupid joke. Bob reads the email, chuckles politely at the lame joke and then deletes it, and goes back to administering the website.

Everything seems OK until the phone starts ringing. The company website is down.

What happened?

The email Bob received from Kay(MattOverdrive) contained the following image tag:


<img src="http://www.easilypwnd.com/admin/deletePage.cfm?pageid=1" />

Huh, that's a weird image name. It almost looks like a ColdFusion "action page" (1998 called, they want their code back).

When Bob viewed this email, in his email client or web browser, it tried to load the image. So the browser made the request to deletePage.cfm looking for the image, it also passed the parameter for page 1 (probably the site home page). Since Bob is the administrator, and since he was logged in, the page executed the request without issue.

This type of attack could just as easily been carried out if MattOverdrive had submitted the code to a blog comment or forum thread that Bob viewed. The key is that Bob views it while he is logged in to the easilypwnd.com site. And even if Bob wasn't logged into the site, he would not see an error. At most he would see a broken image icon.

So what can a request forgery be used for

Good question. The answer is, just about anything. A request forgery can be used to send almost any type of request to any page on a site. In fact, it doesn't even need to be a public facing page, it could be an intranet page that the hacker knows about. As long as the victim has access to the page, and the request is sent properly, it will be executed. So a site that is vulnerable to request forgeries can be exploited on any action that the administrator (or other authenticated user) can perform. Like deleting users, modifying pages, performing transactions, transferring funds, etc. So, as you can see, this is very dangerous.

So what can we do about it?

That's always the big question isn't it. It seems like there is not much we can do. I mean, the browser request is legitimate. The victim site sees a legit request coming from a legit user, with a legit session token, and a legit IP address. All this legitimacy would make us think it is a legit request.

The first thought a lot of people might have is to pass some sort of a "secret" parameter only known to the administrator or authenticated user. In some cases, this may work, but that parameter could be compromised, especially if it is static.

Well, what about checking CGI.HTTP_REFERER? If it's not correct, then we know it did not come from the right web form or page, right? That's a good thought, but there are two issues with that. The first is that the HTTP_REFER can be spoofed. The other, and more important, is that in an SSL request, the HTTP_REFER variable is omitted.

OK, how about if we switch to a POST request instead of a GET request. That would stop the request forgery, wouldn't it? Well, actually, yes. It would stop the request forgery example I displayed above, but it would not stop this one:


<form name="rfForm" action="deletePage.cfm" method="post">
<input type="hidden" name="pageid" value="1" />
</form>

<script type="text/javascript">

rfForm.post();
</script>

Well geez, what can we do about it? Will anything work?

A dynamic variable could be used, but how would we pass it and have it verified on the other end? There are actually a couple of ways we can use dynamic, temporal "key" variables to verify that the request came from a legitimate source.

So let's say that we had a dynamically generated form that looks like this:


<form action="deletePage.cfm" method="post">
<input type="hidden" name="pageid" value="#queryPages.pageid#" />
<input type="submit" name="btnSubmit" value="Delete Page #queryPages.pageid#" />
</form>

Well, this form posts to the same vulnerable page that we were exploiting earlier. So how can we secure the action page to remove that vulnerability? Well, we can do this to the form:


<cfset sr = createObject("java","java.security.SecureRandom") />
<cfset key = sr.nextLong() />

<cfset session.formkey.delPageForm = key />

<form action="deletePage.cfm" method="post">
<input type="hidden" name="pageid" value="#queryPages.pageid#" />
<input type="submit" name="btnSubmit" value="Delete Page #queryPages.pageid#" />
<input type="hidden" name="key" value="#Hash(key, "SHA-256")#" />
</form>

And then in the action page you would have the follow code BEFORE you do any processing:


<cfif StructKeyExists(form, "key") AND form.key EQ Hash(session.formkey.delPageForm, "SHA-256")>
<cfset structDelete(session.formkey, "delPageForm") />
<cfelse>
<cflocation url="/" />
</cfif>

So this code creates a UUID and saves that UUID to the session scope under a key that is unique to this form.The CRSF token needs to be random and unique. A UUID is not a great choice. In the example above I am now using a Long Int fromt he Java SecureRandom class. It then passes a hash of that key to the processing page. The processing page receives that hash and compares it to a hash of the key stored in the session variable, if they match, the key is deleted and the processing is allowed to continue, if they do not match, then the request is invalid and the user is redirected.

The complexity of this can be increased as you, the developer, sees fit. If you want the key to be a combination of the userid, jsessionid, a UUID, and the last 4 of the users SSN, then go for it. Just make sure you hash it and then rehash it on the other end. The passed hash value can be any combination of unique identifiers, as long as it can be reconstructed on the other end and as long as it is not constructed entirely of values that would be available to the hacker. For example, a bad hash would be comprised of the current date and the username.

I'm sure I missed something about Request Forgeries in this post, but at the moment, I cannot think of what. I'm sure that the method outlined above is not the only way to protect against request forgeries, so if you know of another way, please add it to the comments.

Comments
Ben Nadel's Gravatar Last month at the NYCFUG, Jim Harris came to give a presentation on securing web sites. One of the things he discussed was very similar to this approach. But, rather than move things into the session, I believe he used a COOKIE value to store the "referring form" key. I can't remember off-hand how the key was passed, but I think he had a unique reference for every form in his application (rather than using a UUID).

Of course, as you say, with a "Static" value, the likelihood of compromise goes up. And, I don't want to misquote Jim Harris.

All in all, I like what you are doing here. I used to be opposed to anything that tied SESSION or COOKIE to a given screen, but I think something like this can be easily done without impacting the user experience too much (if they use multiple tabs, etc.).
# Posted By Ben Nadel | 8/27/08 1:55 PM
Andrea's Gravatar Ben,

using a cookie is not very safe, because if the user - who must be logged on in the target application for the XSRF to work - happens to already have received a valid cookie for the request that the attacker wants to perform, that cookie will be automatically submitted by the browser even if the request has been originated from the attacker's site, so you'll believe the request is legit.

Andrea
# Posted By Andrea | 8/27/08 5:22 PM
Andrea's Gravatar @Ben again... probably I'm not understanding what you mean with a static unique reference for every form in the application, but maybe it is worth clarifying one aspect of the XSRF.

If an application has a unique, fixed reference for a given request (e.g. a form), should the attacker be able to create a valid account that gives him access to the target form (with his own account) he'll be able to read the token and copy it into his forged request. From there on, he can have any user that visits his site unwittingly perform the action on his own behalf. Certain actions might be out of the attacker's reach because he doesn't have access to the form at all unless he is an admin, but all the common user actions would be vulnerable.

The whole protection resides in the fact that the token is impossible to know by the attacker - without hijacking the session, in which case he wouldn't need to resort to a XSRF at all.

Producing a random token with CreateUUID, storing it in the session and adding it to the form as a hidden field achieves just this, because UUIDs are rather impossible to guess, and only the person logged in with that particular session will perform requests with that particular token.

With this in mind, also hashing the token is not essential: what is important is the fact that the token is always only included in requests prepared by the real site for the legitimate user.
# Posted By Andrea | 8/27/08 5:57 PM
Andrea's Gravatar A last consideration: there is something to be said about GET requests, too.

Because in many cases they are not prepared in two steps (first set up a form, then process the submission), you will not be able to set up an UUID in the first and expect to find it returned by the second.

Allthough we all know that GET requests should not be used to alter any data, there are many cases in which this is done nevertheless, and plenty of smooth Ajax transactions are carried on using GETS.

Replace all such GETs with POSTs if you can - and if you can't, you'll have to create one, random session-wide token at login time, store it in the session as usual, and accept *any* GET request that carries this particular token in one form or the other (and which the current user is authorised to request, of course). You *may* 'consume' this token and replace it with a new one for any subsequent request, but this would raise the risk of false positive alerts, probably to unacceptable levels: as soon as the user opens an internal link in a new tab or window, the old tab/window is almost sure to loose her legitimacy because she is still holding the original token.

Note that this same risk is associated with creating a new UUID each time a form is requested: and this is the reason why in fact I don't: I create a single token for the whole session at login time, and any request made by that user with that token (in an hidden field for POSTs or in some other part of the request for GETs), is accepted until he logs out or the session times out. (I believe, however, but still need to work on it, that this practice has disadvantages because if I also have an XSS flaw I increase the risk of the token being exposed.).

The final last thing to consider is how you can make your GET request use the token. We've already seen that cookies are not an option. In all my Ajax requests, I add a custom HTTP header to the request, to reduce the visibility of the token. In the others the only possibility I can see is in the URL. I'd be very interested in reading any ideas for an alternative solution.
# Posted By Andrea | 8/27/08 6:28 PM
Jason Dean's Gravatar @Ben - Thanks for the comment. I understand what you are saying about tying forms or specific pages to the session or cookie scopes. I don't know if it would make it any smoother, but this might be a place to consider using a session facade. That way , if you decide to change your implementation, you don't need to change the API for the facade. Just a thought. I am no expert on facades, but I think this is a case where it might be useful.
# Posted By Jason Dean | 8/27/08 8:42 PM
Jason Dean's Gravatar @Andrea - I am going to start charging you per word ;) I think my bandwidth bill is going up.

Thanks for the comments. Ajax security is something I need to explore more indepthly before I comment on it. It is on my list of things to learn more about. One of the things I want to explore is security implementation differences between making Ajax requests directly to the service layer versus making them through the controller(Mach-II, MG, ColdBox, etc).
# Posted By Jason Dean | 8/27/08 8:47 PM
Andrea's Gravatar I know! Sorry for that. I do read my comments before posting them, and usually I manage to shorten them by a sentence or two. But while doing so I also realise that I was forgetting something that ought to be said...

It's hopeless.
# Posted By Andrea | 8/28/08 2:33 AM
Ben Nadel's Gravatar @Andrea,

You make good points. I hadn't thought about it that way, but now I see what you are saying about the dangers of static form references. I could very well be wrong about what Jim was saying (he seemed to know his stuff!).
# Posted By Ben Nadel | 8/28/08 7:37 AM
Chris Luksha's Gravatar Hi Jason,

Just an FYI - tried to use this w/ a little copy and paste and was rudely scolded by CF
<cfif StructKeyExists(form, "key") AND form.key = Hash(session.formkey.delPageForm, "SHA-256")> is incorrect :)

it should be form.key EQ not form.key =
# Posted By Chris Luksha | 1/29/09 2:08 PM
Jason Dean's Gravatar @Chris, thanks. Fixed
# Posted By Jason Dean | 1/29/09 3:05 PM
Chris Luksha's Gravatar Should the keys persists or reset during form submissions.....

I am combining your salting and hashing techniques together with this form submission technique. I am finding an anomaly.

First off I have the form set up as you state above:
<cfset key = CreateUUID() />
<cfset session.formkey.delPageForm = key />
<input type="hidden" name="key" value="#Hash(key, "SHA-256")#" />

For the sake of being able to test I set the field to text instead of hidden so I can see it's value.

When I load the form I get the following values:
session key: 245EECFD-AA19-34A6-0CDB3E4C36E11751
form field called key: 2B950389DA513956E72C3DF0B7AE27C081A24C1806FAB7060969DC66AD86490D

Then I submit the form deliberately placing a two distinct passwords in my password confirmation set. I get an error stating: The passwords you entered do not match This is exactly what I expected.

Now I look at my form again and the form.key is still equal to 2B950389DA513956E72C3DF0B7AE27C081A24C1806FAB7060969DC66AD86490D

However the session key is now 24620DF4-F1DF-076E-558C47D67875FE97
So the next time I submit my form - no processing will ever happen.

It took me a while but I finally realized that the wonderful little setting preservedata="yes" was on and so my form.key never ever changed.

I just thought I would bring this up for anyone who tries this in the future - it works great - but in order to persist your data - you will have to cfparam all the form variables yourself EXCEPT the form.key :)
# Posted By Chris Luksha | 1/29/09 3:05 PM
Jason Dean's Gravatar @Chris, great observation. I had not considered this case. I am wondering. Could you instead, cfparam the session key instead of all of your form fields. That way you can continue using preservedata.

I have not tried this, but perhaps there is a way to do it. Cause losing preservedata when you are otherwise able to use it, would suck.
# Posted By Jason Dean | 1/29/09 3:26 PM
Chris Luksha's Gravatar I will give it a try and let you know. That is a good idea for sure. Right away I can't see any drawback to doing it that way - but we will see :)
# Posted By Chris Luksha | 1/29/09 5:19 PM
Chris Luksha's Gravatar after a bit of stupidity - I realized I needed to clear my existing session variable - duh! But after clearing that - Voila. So my code now looks like :

<cfset key = CreateUUID() />
<cfparam name="session.formkey.newBusiness" default="#key#" />

<cfinput type="hidden" name="key" value="#Hash(key, "SHA-256")#">

Then on the other end it is the same as it already was ...
<cfif StructKeyExists(form, "key") AND form.key EQ Hash(session.formkey.newBusiness, "SHA-256")>

So there you have it - if you want to use the preservedata="yes" you certainly need to param your session.formkey variable. However - remember to erase it once you have put it to use :)

Thanks for the oh so simple solution.
Chris
# Posted By Chris Luksha | 1/29/09 6:09 PM
Jason Dean's Gravatar A couple of things:

1. Your param could have CreateUUID() in the default=""

2. I never added it to this post, but I have since realized that in the processing code, we should also be doing a structKeyExists() on session.formkey.newBusiness before we compare it to form.key

Thanks for looking into that Chris, very helpful.
# Posted By Jason Dean | 1/29/09 6:23 PM
Chris Luksha's Gravatar Interesting Jason - I never bothered to put the uuid in the param as I was just following along with the code I cut and pasted
<cfset key = CreateUUID() />
<cfset session.formkey.delPageForm = key />
But hey -why not save some typing huh :) I like it.

I like the idea of the structkeyexists() The only trouble I see is that session.formkey is not defined when we do the test at the top of the page - unless we create it in the onSessionStart() function. I think you may have mentioned that in another post. But if someone doesn't want to define it there and have it hanging out - they can just do a simple isDefined('session.formkey.delPageForm') if they want.

I think I like the idea of setting in the onSessionStart and then dealing with structkeyexists() - this allows us to more easily document the most common features we are using. Now I'm just babbling so - thanks and have a great night!
Chris
# Posted By Chris Luksha | 1/29/09 6:44 PM
Saul's Gravatar This helped me a lot, so thanks.

You are missing an output tag around the "key" form field, but then it works nicely.

This helps me with a noob flash application I am fighting with. I have a small flash game of a slot machine embedded in a CF page in a directory secured by application.cfc and session variables enabled. The only way I have found to pass back data from Flash to CF is with

dataOut = new LoadVars();
dataOut.credits = credits;
//Using send ( ); method to send the data
dataOut.send("http://mywebsite.com/secure/slots.cfm";, "_self", "POST");

This works fine but the variables are presented back to CF as a form.var . I think I can use your technique to pass a hashed key into my swf when it loads and back out to my CF page when the user is "cashing out", which will stop the form handler part of my CF page being abused.
# Posted By Saul | 1/31/09 6:52 PM
Saul's Gravatar Sorry, in code I mean

<input type="hidden" name="key" value="<cfoutput>#Hash(key, "SHA-256")#</cfoutput>" />
# Posted By Saul | 1/31/09 6:56 PM
brett's Gravatar I'm just learning about the need to have security in place as mentioned in the blog post. What I find interesting is that this is something that CF developers need to manually code in every form. Is it just me or shouldn't something like this automatically happen in the CF application whenever a FORM tag is processed / posted?
# Posted By brett | 8/8/10 2:01 PM
Jason Dean's Gravatar @brett

Most languages and environments do not do this automatically. There are some frameworks (like Java's Seam, for example) that will help make it easier, but they still usually do not do it automatically.

Making this process easier is something I have proposed for ColdFusion 10, which you can read about here: http://www.12robots.com/index.cfm/2010/7/12/My-10-...

But is still would not happen automatically.

Doing things like this automatically is asking for incompatibilities, work-arounds, and troubleshooting difficulties. And while it may work for 50%-60% of the applications that need it, for the others it would be a headache that would be disabled and complained about.

There are different ways of handling these tokens, and for Adobe to assume that their way is best and make it automatic would be folly.

Thanks for the comment. It would be nice if security was easier, but just like most things, it can be more complicated than it seems.
# Posted By Jason Dean | 8/8/10 4:17 PM
brett's Gravatar @Jason. Fair point. I'm in desperate need of applying this to one of my apps but before I do I'd like to be sure I can test the before/after to make sure it works. When you built this, did you have any type of testing app or URL so you could unit test the before and after to confirm it's working?
# Posted By brett | 8/9/10 11:35 PM
brett's Gravatar @Jason, a couple more questions if you don't mind :)

After trying to test this out, I realized I needed to enable sessions variables in my application file... so I added:

   <cfapplication name="myapp"
      sessionmanagement="Yes"
      sessiontimeout=#CreateTimeSpan(0,0,45,0)#>

Strangely, I'm now getting the following error:
Element FORMKEY.DELPAGEFORM is undefined in SESSION

Also, Coldfusion is adding data to the end of the URL which I need to remove. http://mysite.com/?CFID=10502&CFTOKEN=16015021...

Any suggestions?
# Posted By brett | 8/10/10 12:06 AM
Saul's Gravatar In the end I used a simplified solution drawing on the thoughts discussed above. This fails (as in stops form submission) with user opening multiple tabs with forms, but if you can live with that ...

In every form I set a session variable.

<cfset session.formName="frmSignup">

and in the form handler

<cfif not IsDefined("session.formName") or session.formname neq "frmSignup">
   <!--- if no form session variable or the wrong form name set create error message --->
   <cfsavecontent variable="msg">
   <H2>New user registration - Error in form submission</H2>
   <P>Either you are have tried to submit the form twice or are accessing this page incorrectly. Please click on the menu links to try again.</P>
   </cfsavecontent>
<cfabort>
</cfif>

<!--- delete the session formname session variable to prevent duplicate submissions --->   
<cfset StructDelete(Session, "formName")>

<!--- form variables have been submitted from the form on the website
so go ahead and process --->
# Posted By Saul | 8/10/10 4:31 AM
Jason Dean's Gravatar @brett

First, I will address the tokens on the URL string. Unfortunately, when you enable session management ColdFusion feels that it is being helpful by appending the session tokens onto the URL string to help persist the session from request to request. This really would not be a problem if it wasn't a security threat.

ColdFusion only does this when you do a <cflocation /> and the only way to make it not do that is to add the attribute addToken="false" to every <cflocation> in your application. I hope they change it in the future to allow us to disable this by default in the cfadmin.
# Posted By Jason Dean | 8/10/10 8:15 AM
Jason Dean's Gravatar @brett

It seems as though your CSRF token is never getting set into the session scope. How are you putting it in there? Somewhere in your form generation page or in your unit test set up you should have something like this:

<cfset token = createUUID() />
<cfset session.formkey.delpageform = token />

If you changed the name of the token in this area then you also need to change the name of the token you are looking for in your form handler script.

As for your question on unit testing, I don't see why you could not set up a unit test for this. You might even make it multiple tests by making the code that checks the token its own function so that you can test that. Then perhaps you might build some integration tests that use <cfhttp> to actually send a form submission to the handler page and then check for an exception (which you would need to throw) or some other returned output that would let you know it failed.

I hope this helps.
# Posted By Jason Dean | 8/10/10 8:20 AM
Jason Dean's Gravatar @saul,

I think your XSRF logic is flawed and could be exploited, though the window of opportunity for that is small, it is still there.

The purpose of XSRF countermeasures is to verify that the form POST is coming from a legitimate source. Since you are not modifying the form POST, you are not doing that. What you are doing is marking the point at which they arrived at the form and then when the form is processed you remove that mark. Technically, it would not matter if they processing happened with a legitimate request.

Take this example.

1. Your user browses to the form and the session token is set.
2. The user browses away fromt he form for some reason (because users are like that)
3. The user is later tricked into browsing to a site that performs a request forgery against that form handler.

In step 3 the token you set would still be present in the session (provided the session has not timed out) and the attack would work.

Like I said, the window of opportunity it reduced by your script, but it is not completely mitigated.

I'm curious why you did not just add the token and stick it in the POST?
# Posted By Jason Dean | 8/10/10 8:26 AM
Brett's Gravatar @Jason, this is incredibly helpful and fascinating stuff. Couple follow-up questions.

You define "session.formkey.delPageForm" which is specific to a form. I think the goal with this is to allow users to have multiple tabs open and not break which they would if you just used a conflicting "key" variable. Was that the intent? And so that the form key resets on every load?

Lastly where are all these session variables stored? If an app grows to several CF servers, I'm wondering if this method breaks?

thanks
# Posted By Brett | 8/10/10 8:03 PM
Jason Dean's Gravatar This method would allow for a user to have multiple forms open in multiple tabs, but it would break i they tried to open the SAME for in multiple tabs. If you wanted them to be able to do that, then you'd have to have a more dynamic key. Like

<cfset keyNum = "delPageForm" & randRange(0,1000) />
<cfset session.formKey[keyNum] = createUUID() />

And then pass it as such:

<input type="hidden" name="#keyNum#" value="#session.formKey[keyNum]#" />

Then in the form handler you'd need to check for a form key that matched the pattern you are expecting for XSRF tokens and check to see if the token exists.

NOTE: I did not test any of the code above. It is just thinking and typing. But I think it would work.

As for your second quesiton, Yes, just like any situation where you are dealing with the session scope, if you move to multiple servers where you cannot control which server the user will hit and you do not have decent session replication, then this methodology would fail.

But presumably, you would have some sort of server-side storage in place for storing user data, so instead of storing the keys/tokens in the session scope, you would store them somewhere else. Like the DB using client variables, for example.
# Posted By Jason Dean | 8/10/10 8:23 PM
Jason Dean's Gravatar @saul,

I thought more about your method and I realized that it would actually be quite a bit easier to exploit than I originally thought.

If the user can be tricked into visiting the form page itself first (probably via an iframe) then the token would be automatically set. And since your form handler does not require the token to be passed, the hacker could also load the handler page in an iframe and pass the other params to perform any attack that he wants.

Does that make sense? It's hard to explain.
# Posted By Jason Dean | 8/10/10 8:27 PM
brett's Gravatar So far it's looking good.

On my form page I have:
<!--- CSRF Prevention --->
<cfset key = CreateUUID() />
<cfset session.formkey.signin = key />
<cfoutput>
<input type="hidden" name="key" id="key" value="#Hash(key, "SHA-256")#" />
</cfoutput>

On My Authorization Page, which validates the login credentials, and then submits the SignIn form if validated:
<!--- CSRF Protection --->
<cfif StructKeyExists(form, "key") AND form.key EQ Hash(session.formkey.signin, "SHA-256")>
<!--- <cfset structDelete(session.formkey, "signin") /> --->
<cfelse>
<cfthrow message = "key is not defined">
</cfif>


--- For some reason I get the following error:
Element FORMKEY.SIGNIN is undefined in SESSION

Interestingly this only happens if the first login attempt (server hit is bad) and you try on a subsequent attempt.
If you submit the form with valid login credentials on the first attempt, than this error does not get logged.

After looking at the session variables, for some reason they are updating on every request, perhaps there's something in my app that is refreshing the page on the ajax call.
# Posted By brett | 8/10/10 9:29 PM
Jason Dean's Gravatar @brett,

I'm confused. In your comment you said "On My Authorization Page, which validates the login credentials, and then submits the SignIn form if validated..." and then you listed the handler code.

Your form should already be "submitted" before it reaches that code. The user should have already clicked "submit". It should nto happen AFTER the validation of credentials.

Am I misunderstanding what you are doing?
# Posted By Jason Dean | 8/10/10 10:57 PM
Pinoy Tambyan's Gravatar <a href="http://www.lambingan.live//"><strong>... Tambayan</strong></a> Live Pinoy Tambayan is another type of filipino TV shows because there are different terms for each of them so Pinoy Tambayan is also a famous Keyword and Pinoy Tambayan shows are also watched and loved by the people of Phillippines.
# Posted By Pinoy Tambyan | 4/23/18 10:21 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner