Login to the page using the php password. Password protection. Put a password on the page

Put a password on the page

This article does not pretend to be any revelations; all these things are quite obvious and widely known. But having recently received several questions about restricting access to web pages, I decided to bring the answers to them together.

So, our task is to set a password to access a certain page. Let's start with the most primitive method, so to speak, of protection - a few lines of JavaScript. The code is something like

Var pass = prompt("Enter the Password:", ""); if (pass == null) window.location = "bad.html"; else if (pass.toLowerCase() == "password") window.location = "ok.html"; else window.location = "bad..js"> do not fundamentally change anything.

At a higher level there is a similar system implemented in Java.

Below is the simplified source code.

Import java.applet.*; import java.awt.*; import java.net.*; public class Password extends Applet ( TextField login, password; String Login = "login"; String Password = "Password"; public Password() ( ) public void init() ( Panel panel = new Panel(); panel.setLayout(new GridLayout(2,2)); login = new TextField(20); panel.add(new Label:)); panel.add(new) Label("Password:")); panel.add(password); add(new Button("Ok")); public boolean action(Event evt, Object obj) ( if(evt.target instanceof Button) ( String s; if(login.getText().equals(Login) && password.getText().equals(Password)) ( s = "http://www.webclub.ru/materials/ pagepsw/ok. html"; ) else ( s = "http://www.webclub.ru/materials/pagepsw/bad.html"; ) try ( getAppletContext().showDocument (new URL(s)); ) catch(Exception e) ( password.setText(e.toString()); ) return true; return false;

By including this applet in a page, you can get something like this:

Password check

It can be made smarter, create a separate page for each user, force it to read data from a file, etc. The fundamental drawback is that once a person has landed on the page he is looking for, no one can prevent him from remembering this URL, so this is a one-time tool. Of course, you can hide the page inside a frame so that the URL does not appear in the address bar, but you understand who this protection is from. Again, the applet goes entirely to the client and is, in principle, completely available for research.

The solution based on the use of CGI does not have the last drawback. A simple Perl script looks something like this:

#!/usr/bin/perl use CGI qw(:standard); $query = new CGI; $ok = "ok.html"; $address = "bad.html"; $login = "login"; $password = "password"; $l = $query->param("login"); $p = $query->param("password"); if(($p eq $password) && ($l eq $login)) ( $address = $ok; ) print $query->redirect($address);

Usage example:

Password check

To deal with the first drawback, you can dynamically generate a new page based on the one hidden somewhere inside, without giving out the URL.

Modified code:

#!/usr/bin/perl use CGI qw(:standard); $query = new CGI; $ok = "ok.html"; $address = "bad.html"; $docroot = $ENV("DOCUMENT_ROOT"); $localpath = "/materials/pagepsw/"; $login = "login"; $password = "password"; $l = $query->param("login"); $p = $query->param("password"); if(($p eq $password) && ($l eq $login)) ( $address = $ok; ) print $query->header(); open(FL, $docroot.$localpath.$address); while() ( # Here you can also modify the html code on the fly # Why? Well, you never know... :) print $_; )close(FL);

Usage example:

Password check

As you can see, the file URL is no longer displayed, although at the cost of SSI, if something similar was present (however, this can be caught during output and processed manually). But even here there remains the theoretical possibility of guessing the URL, and we must not forget that all sorts of pictures included in the pages can do a disservice - when using relative paths, of course.

Finally, the most reliable way to set an access password is to use server tools - it’s not for nothing that people did them, after all. I’ll focus on two - Apache as the most popular and IIS as also popular :)

With IIS everything is quite simple - protection is carried out using NTFS, which, of course, somewhat limits the capabilities of non-server administrators. The idea is as follows: the user IUSR_xxxx, under whose account all site visitors work by default, is denied access to the desired file/directory. After that, only those users for whom this is explicitly specified in Properties->Security will have access to these files. It is clear that it is much more convenient to combine them into groups. There are a couple of subtleties here. Firstly, the specified users must be given the Logon locally right (Policies->User Rights in User Manager). Secondly, if you do not select the WWW service Basic authentication (Clear Text) in the WWW settings, only Internet Explorer users will be allowed in "A.

In Apache, everything is done a little differently. Protection is set at the directory level. The corresponding directives can be placed both in the general configuration file (in the section) and in the .htaccess files. The set of directives in both cases is the same, and for most people renting space for a website/page on someone else’s server, the second method is much more relevant. So, you create a .htaccess file in the directory to which you plan to restrict access, and then insert the following directives into it (I’ll list the main ones):

AuthType type of control- Basic is usually used.

AuthName Name- specifies the name of the area in which user names and passwords are valid. This is the same name that the browser shows in the password dialog. By setting one such name for different directories, you can save users the time of entering an extra password.

AuthGroupFile Name- specifies the name of the file in which the names of groups and their members are stored. Its format:
group1: member1 member2 ...
group2: member3 member4 ...

AuthUserFile Name- specifies the name of the file with passwords. By and large, to generate it you need to use the htpasswd utility from the Apache distribution. But at least for some versions of the server, this format is like this:
user1: passwordhash1
user2: passwordhash2

Passwordhash can be easily obtained using the standard Perl function:
$hash=crypt($pass,$salt);
where $pass is the password, $salt is a two-character string involved in generating the hash.

So it is quite possible to automate the process of adding new users, changing passwords through html forms, etc.

require user user1 user2 and require group user1 user2 allow you to specify which users and groups will have access to a given directory.

require valid-user allows access to all users specified in the system password file.

... , where method i defines an HTTP method. For example, it limits the use of nested directives to cases of using the GET and POST methods (usually this is more than enough). Directives can be nested: require, order, allow and deny.

Another couple of useful directives are deny and allow - denying and allowing access, respectively. Apply something like this:
deny from all
allow from 192.168

By default, all deny are executed first, then all allow, so allow from all will allow access to all users, regardless of any deny. The order can be changed with the order directive: order allow, deny.

deny from all goes well with the second method of protecting pages via CGI; it is this directive that is best for covering all sorts of passwords for guest books, etc.

By the way, here, in passing, independent error handling is demonstrated: in this case, code 403, Forbidden. The beloved 404, Not Found, and 401, Unauthorized are processed similarly. To do this, just add the ErrorDocument directive to .htaccess url code:
ErrorDocument 404 /cgi-bin/bad.pl
ErrorDocument 403 /cgi-bin/badaccess.pl
ErrorDocument 401 /cgi-bin/badaccess.pl

All the script does is generate an error message using the REQUEST_URI environment variable, so you can just point to some suitable page instead.

For the final example, we use a .htaccess file with the following content:

AuthType Basic AuthName Test AuthGroupFile /.../pagepsw/deny/tgroup AuthUserFile /.../pagepsw/deny/tuser require group test

There is only one line in the tgroup file - test: login test, in the tuser file - encrypted passwords for login (password) and test (test). Please note that when you access this page again, the browser understands that it has just accessed this area and does not bother the user with an unnecessary password request.

This is, in brief, the minimum set of information necessary to protect web pages. As practice shows, you should more or less trust only solutions based on the tools provided by the server (and then until another hole is discovered in the server), so if possible, it is better to choose them.


One of the main axioms of information security states that “the comfort of a system is inversely proportional to its security.” This means that when choosing a security system, it is necessary to find the optimal balance between the complexity of the security and the convenience of the user experience.


On the other hand, the development and implementation of protection requires a certain amount of effort and money. Therefore, it is necessary to take a reasonable approach to designing protection. Simply put, there is no need to create a complex and expensive security system if nothing valuable is stored on the site. Not a single attacker will try to break your home page, the business card website of a small company, or the website of a kindergarten.


In the previous lesson, we looked at authorization using a Web server (Basic authorization). This is probably the easiest and safest way to restrict access to resources. However, maintaining such a mechanism is quite labor-intensive, especially with a large number of users with different rights. Additionally, not all servers allow HTTP authentication.


A more popular alternative is password protection. Its meaning is that the server stores lists of users and their corresponding logins, passwords and access rights. When accessing the site for the first time, the user enters a login/password and gains access to the resources. The server “remembers” the user and does not ask for a password until the next session is opened (browser restart).

The organization of password protection falls entirely on the shoulders of the programmer. The developer must ensure the security of storing user lists, checking logins/passwords, saving security contexts and reusing them. The security context here is understood as a set of parameters that uniquely identify the user (at a minimum, this is a login, password and session identifier).

Consider an example of implementing a simple password protection. Let's create a file logon.php

When you click on the “login” button, the form data will be sent to the server, where the script will check the entered login and password and if they are equal to “admin” and “megaPass”, respectively, it will display a message that login is allowed. If the login or password is incorrect, the user will see a warning message.

The first drawback of this script: data transmission using the GET method. When using this method, the data is transmitted directly in the address, and therefore visible even to the naked eye. For example, if you entered the correct username and password, you will see in the address bar

Http://localhost/logon.php?login=admin&passwd=megaPass

The second drawback: usernames and passwords are hardcoded directly into the code. This means that to add new users you will need to constantly change the file code. Just imagine how big the file will be if you have at least a thousand registered users on your site... User lists are best stored in a separate file, or better yet, in a database, because... It is much easier for an attacker to steal a file than to extract something from a database.

The third drawback: the user's login results are not remembered, i.e. Just refresh the page and you will be asked to enter your password again.

So, let's look at ways to eliminate these shortcomings.

The easiest way to hide transmitted data is to use the POST transmission method. In this case, the browser itself (hidden from the user) will transfer all the necessary data to the server and it can only be intercepted by special programs from the arsenal of IT specialists, programmers or hackers.

As mentioned above, user lists must be stored separately. Let's create a table in the test database:

CREATE TABLE `smplusers` (
`user_id` int(11) NOT NULL auto_increment,
`user_name` varchar(50) NOT NULL,
`user_login` varchar(50) NOT NULL,
`user_password` varchar(50) NOT NULL,
`reg_date` datetime NOT NULL,
PRIMARY KEY (`user_id`)
)

and add several user records to it:

INSERT INTO smplUsers
(user_name, user_login, user_password, reg_date)
values
("Ivanov I.I.", "ivanov-i-i", "pass1", NOW()),
("Petrov P.P.", "petrovich", "pass2", NOW()),
("Sidorov S.S.", "sidorov", "pass3", NOW())

Please note that the INSERT operator allows several records to be added to the table at once, and the data is listed in blocks separated by commas.

Now let's change logon.php so that the username and password are correctly checked directly in the database:







Now the login and password are transmitted secretly, and it is very easy to change the credentials by editing the table in the database. The last step remains - to teach the server to remember the fact of registration. The easiest way to do this is using the session mechanism. Let's make the necessary changes:



...

The server will now remember each user who successfully logged in, and will display a welcome message the next time the page is refreshed.

This script is just an example of organizing password protection, albeit a fully functional one. You can turn it into a practically valuable sample by adding input data verification, encryption functions, password recovery, session end, etc.

I decided to describe ways to protect part of the site with a password. The topic is actually quite large, so for the first time I will limit myself to php+mysql authorization.

The very first question that usually arises is how to close the directory with administration scripts with a password. In this case, no frills are needed - one or more administrators have the same rights, and personalities rarely change. The easiest way in this situation is to use standard server authorization - put the .htaccess and .htpasswd files and write the necessary parameters in them. A lot has already been written about this, so I won’t say anything particularly new.

I'll add two things. The first is where to put the .htpasswd file. Experimentally, I found out that if, for example, the path to a document with an error message (ErrorDocument) is written relative to the DocumentRoot system variable. But the path to the password file (UserFile) is written relative to ServerRoot. As far as I understand, you cannot put .htpasswd above ServerRoot - "../" is not perceived. All this is done so that you can place a file with passwords, for example, one level above the root directory of the site, so that there is no access to the file from the network at all.

The second is that the script can find out who is opening it and the password: the $PHP_AUTH_USER and $PHP_AUTH_PW variables.

The main disadvantage of this method is that the server cannot block password guessing (after several unsuccessful login attempts, the user is asked to wait an hour or two, and during this time calls from his IP address are ignored). This is written in the official Apache documentation.

Another drawback is the need to rewrite files with passwords when deleting a user or introducing a new one. But if this happens infrequently, this method is quite sufficient, and you won’t have to worry about writing an authorization mechanism.

Automation of authorization

This is necessary not only to simplify work with a large number of users and their high turnover. If you need to keep additional information about users, or you need flexible differentiation of rights, it is better to transfer authorization to the database.

Each page of a closed territory includes a file with the following code:

$result = mysql_query(" SELECT * FROM person WHERE login="". preg_replace("/[^w_-]/","",$PHP_AUTH_USER). "" AND pass="". md5($PHP_AUTH_PW). " ""); if (@mysql_num_rows($result)!=1) ( header("WWW-Authenticate: Basic realm="User area""); header("HTTP/1.0 401 Unauthorized"); print("To log into the user area of ​​the site , you must enter your username and password."); exit(); ); $user_row = mysql_fetch_array($result);

In the first line, all characters except letters, numbers, dashes and underscores are removed from the login. The number of rows received is then checked and only if it is one row is access granted. In other cases, the user will see a window in the browser prompting you to enter a login and password. If the user logged in successfully, we have all the information about him in the $user_row array.

Of course, the example I gave has a number of significant shortcomings. Do not rewrite it one-to-one, so as not to fall victim to password guessing attempts, because
1. there is no protection against selection here
2. if the user table is large, when guessing the password, an attacker will most likely overwhelm the database

And the last method for today is storing encrypted data in cookies.

There is a script for logging in, the rest include code that only allows you to continue actions in a closed area - if the cookies expire or he logs out of there, you will have to return to the login page.

The input script checks the login and password and issues two cookies. In the first - the login, in order to immediately identify the user (in the database, the login field is, of course, unique or even key). The second cookie contains a hash of the login time and password (for completeness of secrecy, I add the letter “Y” to these lines - then it is almost impossible to find the hash :).

All other programs include code that does the following. Makes a request to the database - selects the line with the received login. From this line it takes the “log_time” field and the password and makes a hash from them, as described above. Compares it with what it received, and if they match, issues a new hash cookie, again, from the password, time and letter "Y" and makes a query to the database "UPDATE user SET log_time='...' WHERE login='$ cookie_login'".

if (isset($HTTP_COOKIE_VARS[$cookie_login]) && isset($HTTP_COOKIE_VARS[$cookie_code])) ( $login = $HTTP_COOKIE_VARS[$cookie_login]; $code = $HTTP_COOKIE_VARS[$cookie_code]; $result = mysql_query("SELECT date_format(log_date,"%Y%m%d%H%i%s") as log_date1,pass,uid FROM user WHERE email="$login" AND log_date>"DATE_SUB(NOW(),INTERVAL 15 MINUTE)"" ); if (!mysql_error() && @mysql_num_rows($result)==1) ( $log_time0 = time(); $log_time1 = date("YmdHis", $log_time0); $log_time2 = date("Y-m-d H:i :s", $log_time0); $current_user = mysql_fetch_array($result); if (md5($current_user["pass"].$current_user["log_date1"].$md5letter) == $code) ( mysql_query("UPDATE user SET log_date="$log_time2" WHERE uid=".$current_user["uid"]); setcookie($cookie_code, md5($current_user["pass"].$log_time1.$md5letter), time()+900, $site_path); $auth = true; else unset($current_user);

Again, there is no protection here from selection and attack on the server (by the way, here you can write the user’s IP address instead of the letter “Y” - so that, for example, an office neighbor cannot take a file with a cookie and log in from his computer).

Password for the page. Part 2. Recruitment blocking

When I posted this issue last time, they kicked me on the spot, saying that such a block could derail the server.

But first, about rebound blocking. Banalities, but still. A ten-character password consisting of Latin letters and numbers means there are a lot of options. If you guess a password 1,000,000 times per second, it will take several thousand years. But since it’s difficult to remember such gobbledygook, we often make passwords out of meaningful words. A few years ago, it turned out that most passwords can be guessed using a dictionary of 10,000 words. At one time, a worm (a virus like that) appeared on the network, which climbed Unix servers, using their security holes, and picked up passwords for privileged users using... the Unix system spelling dictionary. There was no need to carry anything!

Each user, until he has entered the correct login and password, is considered an evil hacker. What do we deal with when the user enters something incorrectly?
forgetfulness (for this, decent websites have a “forgot password” form to send the same password to the email entered in the system settings)
pampering (“because I don’t care”)
selecting a password using a dictionary (the probability of a successful selection is high, so you need to close it, especially if the site is of a commercial nature)
DoS attack (in order not to overload the server, you need to minimize the actions that the script will perform in this case)

I thought for a long time about how I could cause an overload on the server if the protection mechanism is based on files. It turned out to be easy (how much it will cost is another question). So, let’s say the server won’t be able to handle it if the script tries to open files for writing 1000 times a second and write data to them. Since after 5 unsuccessful attempts to log in, the user will immediately be denied access (without any data being written to a file), it is necessary to find 200 unique IPs, from which to contact five times. It's possible. We hang an html banner with five tags in the banner scroller:

The user instantly makes five requests; the server writes to the file five times (by the way, in some browsers, a window for entering your login and password may pop up). You can make an HTML page with five such pictures, and insert the page itself via an iframe onto the site you are visiting (via an iframe - so that the referer field will not be found. It is unlikely that the support service of a free hosting will deal with such things as digging through log files in search of referrers) . The examples that I gave are, of course, far-fetched, but the very fact that one can take advantage of such a flaw in the system has been proven. By the way, something similar has already happened.

But still, I’ll give you this method - I wrote it in vain, or what? By the way, it can be used without much fear for a limited number of addresses (for example, for a company’s local network) by placing a .htaccess file in the directory with the following content:

order deny,allow
deny from all
allow from xxx.xxx.xxx

And here is the program code:

$errors = 0; $fn = "ignore/". preg_replace("[^d.]", "", $REMOTE_ADDR. ".". $HTTP_FORWARDED_FOR); if (is_file($fn)) ( if (filectime($fn)< time()-3600) unlink($fn); else $errors = fread(fopen($fn, "r"), 2); }; if ($errors>5) ( print ("Access is closed. Please come back in an hour."); exit(); ); // here the connection with the database server is established. so as not to touch in vain if the user is immediately “beaten”. $result = mysql_query("SELECT * FROM user WHERE login="". preg_replace("/[^w_-]/", "", $PHP_AUTH_USER). "" AND pass="". md5($PHP_AUTH_PW). " ""); if (@mysql_num_rows($result)!=1) ( header("WWW-Authenticate: Basic realm="secret area""); header("HTTP/1.0 401 Unauthorized"); print ("Authorization required"); fwrite (fopen($fn, "w"), ++$errors); $current_user = mysql_fetch_array($result); mysql_free_result($result); However, it’s a sin to work with files if there is a database. Joke. For failed authorizations, we create a table: CREATE TABLE unauth (username VARCHAR(64) NOT NULL, pass VARCHAR(64) NOT NULL, ip VARCHAR(255), logintime TIMESTAMP) And instead of accessing files, we work with the database. $errors = @mysql_result(mysql_query("SELECT count(username) as false FROM unauth WHERE logintime>DATE_SUB(NOW(),INTERVAL 1 HOUR) AND ip="$REMOTE_ADDR""),0); if (mysql_error()) die(mysql_error()); if ($errors>5) ( print ("Access is closed. Please come back in an hour."); exit(); ); $result = mysql_query("SELECT * FROM user WHERE login="". preg_replace("/[^w_-]/", "", $PHP_AUTH_USER). "" AND pass="". md5($PHP_AUTH_PW). " ""); if (@mysql_num_rows($result)!=1) ( header("WWW-Authenticate: Basic realm="secret area""); header("HTTP/1.0 401 Unauthorized"); print ("Authorization required"); mysql_query ("INSERT INTO unauth (username, pass, ip) VALUES ("$PHP_AUTH_USER", "$PHP_AUTH_PW", "$REMOTE_ADDR $HTTP_X_FORWARDED_FOR")"); $current_user = mysql_fetch_array($result); mysql_free_result($result);

Whether to store old records for statistics or not is a business decision. If anything, they can be deleted by running the following request before authorization:

DELETE FROM unauth WHERE logintime