Старый member register php. Создаем невероятную простую систему регистрации на PHP и MySQL

Laravel requires Composer to manage the project dependencies. So before installing Laravel, make sure you have Composer installed on your system. In case you are hearing about Composer for the first time, it"s a dependency management tool for php similar to node"s npm.

To install Composer on your machine, check this post:

Installing Laravel on Windows:

Follow the below steps to install laravel on windows machine. No matter you have xampp/wamp stack, it works for both. On WAMP, make sure to install laravel on "www" folder and on XAMPP, obviously the "htdocs".

STEP-1) Open "htdocs" folder on XAMPP, hold SHIFT key and right click on the folder, and choose "open command window here". Alternatively, you can open command window and change directory to "xampp/htdocs".

STEP-2) Enter the following command.

Composer create-project laravel/laravel my_laravel_site --prefer-dist

Here "my_laravel_site" is the folder name where laravel files will be installed. Change this to your liking.

STEP-3) Now it"s time to be patient as laravel installation is going to take some time.

STEP-4) Once installed, change directory to "my_laravel_site" (cd "my_laravel_site") on the command prompt and enter the below command.

Php artisan serve

STEP-5) This will show a message something like, "Laravel development server started:" along with an url.

STEP-6) Copy and paste the url on the browser. If things go right, you"d see the laravel welcome screen.

STEP-7) Done! You have successfully installed laravel on windows machine and ready to go with.

Setting Application Key:

Laravel requires little configuration after installation. It requires you to set the application key. This is a random string of 32 characters long used for encrypting session and other sensitive data. Usually this will be set automatically when you install laravel via composer or laravel installer.

In case it"s not set, you have to do it manually. First make sure to rename the ".env.example" file to ".env" on your application root. Then open command prompt and change to the laravel project folder. Now run the below command to generate the key.

Php artisan key:generate

Copy this generated key to the APP_KEY variable on ".env" file. Save and you are done.

Installing Specific Laravel Version:

The above given method will make composer to download and install the latest version of laravel. If you want to install earlier versions of laravel on your machine, make sure to include the respective version number on create-project command.

Composer create-project laravel/laravel=5.4 your-project-name --prefer-dist Read Also:

Likewise you can easily install laravel using composer on windows . I hope you find this tutorial useful. Please share it on your social circle if you like it.

Процесс создания системы регистрации – это довольно большой объем работы. Вам нужно написать код, который бы перепроверял валидность email-адресов, высылал email-письма с подтверждением, предлагал возможность восстановить пароль, хранил бы пароли в безопасном месте, проверял формы ввода и многое другое. Даже когда вы все это сделаете, пользователи будут регистрироваться неохотно, так как даже самая минимальная регистрация требует их активности.

В сегодняшнем руководстве мы займемся разработкой простой системы регистрации, с использованием которой вам не понадобятся никакие пароли! В результаты мы получим, систему, которую можно будет без труда изменить или встроить в существующий PHP-сайт. Если вам интересно, продолжайте чтение.

PHP

Теперь мы готовы к тому, чтобы заняться кодом PHP. Основной функционал системы регистрации предоставляется классом User, который вы можете видеть ниже. Класс использует (), представляющую собой минималистскую библиотеку для работы с базами данных. Класс User отвечает за доступ к базам данных, генерирование token-ов для логина и их валидации. Он представляет нам простой интерфейс, который можно без труда включить в систему регистрации на ваших сайтах, основанных на PHP.

User.class.php

// Private ORM instance
private $orm;

/**
* Find a user by a token string. Only valid tokens are taken into
* consideration. A token is valid for 10 minutes after it has been generated.
* @param string $token The token to search for
* @return User
*/

Public static function findByToken($token){

// find it in the database and make sure the timestamp is correct


->where("token", $token)
->where_raw("token_validity > NOW()")
->find_one();

If(!$result){
return false;
}

Return new User($result);
}

/**
* Either login or register a user.
* @return User
*/

Public static function loginOrRegister($email){

// If such a user already exists, return it

If(User::exists($email)){
return new User($email);
}

// Otherwise, create it and return it

Return User::create($email);
}

/**
* Create a new user and save it to the database
* @param string $email The user"s email address
* @return User
*/

Private static function create($email){

// Write a new user to the database and return it

$result = ORM::for_table("reg_users")->create();
$result->email = $email;
$result->save();

Return new User($result);
}

/**
* Check whether such a user exists in the database and return a boolean.
* @param string $email The user"s email address
* @return boolean
*/

Public static function exists($email){

// Does the user exist in the database?
$result = ORM::for_table("reg_users")
->where("email", $email)
->count();

Return $result == 1;
}

/**
* Create a new user object
* @param $param ORM instance, id, email or null
* @return User
*/

Public function __construct($param = null){

If($param instanceof ORM){

// An ORM instance was passed
$this->orm = $param;
}
else if(is_string($param)){

// An email was passed
$this->
->where("email", $param)
->find_one();
}
else{

If(is_numeric($param)){
// A user id was passed as a parameter
$id = $param;
}
else if(isset($_SESSION["loginid"])){

// No user ID was passed, look into the sesion
$id = $_SESSION["loginid"];
}

$this->orm = ORM::for_table("reg_users")
->where("id", $id)
->find_one();
}

/**
* Generates a new SHA1 login token, writes it to the database and returns it.
* @return string
*/

Public function generateToken(){
// generate a token for the logged in user. Save it to the database.

$token = sha1($this->email.time().rand(0, 1000000));

// Save the token to the database,
// and mark it as valid for the next 10 minutes only

$this->orm->set("token", $token);
$this->orm->set_expr("token_validity", "ADDTIME(NOW(),"0:10")");
$this->orm->save();

Return $token;
}

/**
* Login this user
* @return void
*/

Public function login(){

// Mark the user as logged in
$_SESSION["loginid"] = $this->orm->id;

// Update the last_login db field
$this->orm->set_expr("last_login", "NOW()");
$this->orm->save();
}

/**
* Destroy the session and logout the user.
* @return void
*/

Public function logout(){
$_SESSION = array();
unset($_SESSION);
}

/**
* Check whether the user is logged in.
* @return boolean
*/

Public function loggedIn(){
return isset($this->orm->id) && $_SESSION["loginid"] == $this->orm->id;
}

/**
* Check whether the user is an administrator
* @return boolean
*/

Public function isAdmin(){
return $this->rank() == "administrator";
}

/**
* Find the type of user. It can be either admin or regular.
* @return string
*/

Public function rank(){
if($this->orm->rank == 1){
return "administrator";
}

Return "regular";
}

/**
* Magic method for accessing the elements of the private
* $orm instance as properties of the user object
* @param string $key The accessed property"s name
* @return mixed
*/

Public function __get($key){
if(isset($this->orm->$key)){
return $this->orm->$key;
}

Return null;
}
}
Token-ы генерируются при помощи алгоритма , и сохраняются в базу данных. Мы используем из MySQL для установки значения в колонку token_validity, равного 10 минутам. При валидации token, мы сообщаем движку, что нам нужен token, поле token_validity пока еще не истекло. Таким образом мы ограничиваем время, в течение которого token будет валиден.

Обратите внимание на то, что мы используем волшебный метод __get () в конце документа, чтобы получить доступ к свойствам объекта user. Это позволяет нам осуществить доступ к данным, которые хранятся в базе данных в виде свойств: $user->email, $user->token. Для примера давайте посмотрим, как мы можем использовать этот класс в следующем фрагменте кода:


Еще один файл, в котором хранится необходимый функционал, это functions.php. Там у нас есть несколько вспомогательных функций, которые позволяют нам сохранить остальной код более опрятным.

Functions.php

Function send_email($from, $to, $subject, $message){

// Helper function for sending email

$headers = "MIME-Version: 1.0" . "\r\n";
$headers .= "Content-type: text/plain; charset=utf-8" . "\r\n";
$headers .= "From: ".$from . "\r\n";

Return mail($to, $subject, $message, $headers);
}

function get_page_url(){

// Find out the URL of a PHP file

$url = "http".(empty($_SERVER["HTTPS"])?"":"s")."://".$_SERVER["SERVER_NAME"];

If(isset($_SERVER["REQUEST_URI"]) && $_SERVER["REQUEST_URI"] != ""){
$url.= $_SERVER["REQUEST_URI"];
}
else{
$url.= $_SERVER["PATH_INFO"];
}

Return $url;
}

function rate_limit($ip, $limit_hour = 20, $limit_10_min = 10){

// The number of login attempts for the last hour by this IP address

$count_hour = ORM::for_table("reg_login_attempt")
->
->where_raw("ts > SUBTIME(NOW(),"1:00")")
->count();

// The number of login attempts for the last 10 minutes by this IP address

$count_10_min = ORM::for_table("reg_login_attempt")
->where("ip", sprintf("%u", ip2long($ip)))
->where_raw("ts > SUBTIME(NOW(),"0:10")")
->count();

If($count_hour > $limit_hour || $count_10_min > $limit_10_min){
throw new Exception("Too many login attempts!");
}
}

function rate_limit_tick($ip, $email){

// Create a new record in the login attempt table

$login_attempt = ORM::for_table("reg_login_attempt")->create();

$login_attempt->email = $email;
$login_attempt->ip = sprintf("%u", ip2long($ip));

$login_attempt->save();
}

function redirect($url){
header("Location: $url");
exit;
}
Функции rate_limit и rate_limit_tick позволяют нам ограничивать число попыток авторизации на определенный промежуток времени. Попытки авторизации записываются в базу данных reg_login_attempt. Эти функции запускаются при проведении подтверждения формы авторизации, как можно видеть в следующем фрагменте кода.

Нижеприведенный код был взят из index.php, и он отвечает за подтверждение формы авторизации. Он возвращает JSON-ответ, который управляется кодом jQuery, который мы видели в assets/js/script.js.

index.php

If(!empty($_POST) && isset($_SERVER["HTTP_X_REQUESTED_WITH"])){

// Output a JSON header

Header("Content-type: application/json");

// Is the email address valid?

If(!isset($_POST["email"]) || !filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)){
throw new Exception("Please enter a valid email.");
}

// This will throw an exception if the person is above
// the allowed login attempt limits (see functions.php for more):
rate_limit($_SERVER["REMOTE_ADDR"]);

// Record this login attempt
rate_limit_tick($_SERVER["REMOTE_ADDR"], $_POST["email"]);

// Send the message to the user

$message = "";
$email = $_POST["email"];
$subject = "Your Login Link";

If(!User::exists($email)){
$subject = "Thank You For Registering!";
$message = "Thank you for registering at our site!\n\n";
}

// Attempt to login or register the person
$user = User::loginOrRegister($_POST["email"]);

$message.= "You can login from this URL:\n";
$message.= get_page_url()."?tkn=".$user->generateToken()."\n\n";

$message.= "The link is going expire automatically after 10 minutes.";

$result = send_email($fromEmail, $_POST["email"], $subject, $message);

If(!$result){
throw new Exception("There was an error sending your email. Please try again.");
}

Die(json_encode(array(
"message" => "Thank you! We\"ve sent a link to your inbox. Check your spam folder as well."
)));
}
}
catch(Exception $e){

Die(json_encode(array(
"error"=>1,
"message" => $e->getMessage()
)));
}
При успешной авторизации или регистрации, вышеприведенный код отсылает email человеку с ссылкой для авторизации. Token (лексема) становится доступной в качестве $_GET-переменной "tkn" ввиду сгенерированного URL.

index.php

If(isset($_GET["tkn"])){

// Is this a valid login token?
$user = User::findByToken($_GET["tkn"]);

// Yes! Login the user and redirect to the protected page.

$user->login();
redirect("protected.php");
}

// Invalid token. Redirect back to the login form.
redirect("index.php");
}
Запуск $user->login() создаст необходимые переменные для сессии, что позволит пользователю оставаться авторизованным при последующих входах.

Выход из системы реализуется примерно таким же образом:

Index.php

If(isset($_GET["logout"])){

$user = new User();

If($user->loggedIn()){
$user->logout();
}

Redirect("index.php");
}
В конце кода мы снова перенаправляем пользователя на index.php, поэтому параметр?logout=1 в URL исключается.

Нашему файлу index.php также потребуется защита – мы не хотим, чтобы уже авторизованные пользователи видели форму. Для этого мы используем метод $user->loggedIn():

Index.php

$user = new User();

if($user->loggedIn()){
redirect("protected.php");
}
Наконец, давайте посмотрим, как можно защитить страницу вашего сайта, и сделать ее доступной только после авторизации:

protected.php

// To protect any php page on your site, include main.php
// and create a new User object. It"s that simple!

require_once "includes/main.php";

$user = new User();

if(!$user->loggedIn()){
redirect("index.php");
}
После этой проверки вы можете быть уверены в том, что пользователь успешно авторизовался. У вас также будет доступ к данным, которые хранятся в базе данных в качестве свойств объекта $user. Чтобы вывести email пользователя и их ранг, воспользуйтесь следующим кодом:

Echo "Your email: ".$user->email;
echo "Your rank: ".$user->rank();
Здесь rank() – это метод, так как колонка rank в базе данных обычно содержит числа (0 для обычных пользователей и 1 для администраторов), и нам нужно преобразовать это все в названия рангов, что реализуется при помощи данного метода. Чтобы преобразовать обычного пользователя в администратора, просто отредактируйте запись о пользователе в phpmyadmin (либо в любой другой программе по работе с базами данных). Будучи администратором, пользователь не будет наделен какими-то особыми возможностями. Вы сами в праве выбирать, каким правами наделять администраторов.

Готово!

На этом наша простенькая система регистрации готова! Вы можете использовать ее на уже существующем PHP-сайте, либо модернизировать ее, придерживаясь собственных требований.

A tutorial for the very beginner! No matter where you go on the Internet, there"s a staple that you find almost everywhere - user registration. Whether you need your users to register for security or just for an added feature, there is no reason not to do it with this simple tutorial. In this tutorial we will go over the basics of user management, ending up with a simple Member Area that you can implement on your own website.

If you need any extra help or want a shortcut, check out the range of PHP service providers on Envato Studio. These experienced developers can help you with anything from a quick bug fix to developing a whole app from scratch. So just browse the providers, read the reviews and ratings, and pick the right one for you.

Introduction

In this tutorial we are going to go through each step of making a user management system, along with an inter-user private messaging system. We are going to do this using PHP, with a MySQL database for storing all of the user information. This tutorial is aimed at absolute beginners to PHP, so no prior knowledge at all is required - in fact, you may get a little bored if you are an experienced PHP user!

This tutorial is intended as a basic introduction to Sessions, and to using Databases in PHP. Although the end result of this tutorial may not immediately seem useful to you, the skills that you gain from this tutorial will allow you to go on to produce a membership system of your own; suiting your own needs.

Before you begin this tutorial, make sure you have on hand the following information:

  • Database Hostname - this is the server that your database is hosted on, in most situations this will simply be "localhost".
  • Database Name, Database Username, Database Password - before starting this tutorial you should create a MySQL database if you have the ability, or have on hand the information for connecting to an existing database. This information is needed throughout the tutorial.

If you don"t have this information then your hosting provider should be able to provide this to you.

Now that we"ve got the formalities out of the way, let"s get started on the tutorial!

Step 1 - Initial Configuration

Setting up the database

As stated in the Introduction, you need a database to continue past this point in the tutorial. To begin with we are going to make a table in this database to store our user information.

The table that we need will store our user information; for our purposes we will use a simple table, but it would be easy to store more information in extra columns if that is what you need. In our system we need the following four columns:

  • UserID (Primary Key)
  • Username
  • Password
  • EmailAddress

In database terms, a Primary Key is the field which uniquely identifies the row. In this case, UserID will be our Primary Key. As we want this to increment each time a user registers, we will use the special MySQL option - auto_increment .

The SQL query to create our table is included below, and will usually be run in the "SQL" tab of phpMyAdmin.

CREATE TABLE `users` (`UserID` INT(25) NOT NULL AUTO_INCREMENT PRIMARY KEY , `Username` VARCHAR(65) NOT NULL , `Password` VARCHAR(32) NOT NULL , `EmailAddress` VARCHAR(255) NOT NULL);

Creating a Base File

In order to simplify the creation of our project, we are going to make a base file that we can include in each of the files we create. This file will contain the database connection information, along with certain configuration variables that will help us out along the way.

Start by creating a new file: base.php , and enter in it the following code:

Let"s take a look at a few of those lines shall we? There"s a few functions here that we"ve used and not yet explained, so let"s have a look through them quickly and make sense of them -- if you already understand the basics of PHP, you may want to skip past this explanation.

Session_start();

This function starts a session for the new user, and later on in this tutorial we will store information in this session to allow us to recognize users who have already logged in. If a session has already been created, this function will recognize that and carry that session over to the next page.

Mysql_connect($dbhost, $dbuser, $dbpass) or die("MySQL Error: " . mysql_error()); mysql_select_db($dbname) or die("MySQL Error: " . mysql_error());

Each of these functions performs a separate, but linked task. The mysql_connect function connects our script to the database server using the information we gave it above, and the mysql_select_db function then chooses which database to use with the script. If either of the functions fails to complete, the die function will automatically step in and stop the script from processing - leaving any users with the message that there was a MySQL Error.

Step 2 - Back to the Frontend

What Do We Need to Do First?

The most important item on our page is the first line of PHP; this line will include the file that we created above (base.php), and will essentially allow us to access anything from that file in our current file. We will do this with the following line of of PHP code. Create a file named index.php , and place this code at the top.

Begin the HTML Page

The first thing that we are going to do for our frontend is to create a page where users can enter their details to login, or if they are already logged in a page where they can choose what they then wish to do. In this tutorial I am presuming that users have basic knowledge of how HTML/CSS works, and therefore am not going to explain this code in detail; at the moment these elements will be un-styled, but we will be able to change this later when we create our CSS stylesheet.

Using the file that we have just created (index.php), enter the following HTML code below the line of PHP that we have already created.

What Shall We Show Them?

Before we output the rest of the page we have a few questions to ask ourselves:

  1. Is the user already logged in?
  • Yes - we need to show them a page with options for them to choose.
  • No
  • Has the user already submitted their login details?
    • Yes - we need to check their details, and if correct we will log them into the site.
    • No - we continue onto the next question.
  • If both of the above were answered No , we can now assume that we need to display a login form to the user.
  • These questions are in fact, the same questions that we are going to implement into our PHP code. We are going to do this in the form of if statements . Without entering anything into any of your new files, lets take a look at the logic that we are going to use first.

    Looks confusing, doesn"t it? Let"s split it down into smaller sections and go over them one at a time.

    If(!empty($_SESSION["LoggedIn"]) && !empty($_SESSION["Username"])) { // let the user access the main page }

    When a user logs into our website, we are going to store their information in a session - at any point after this we can access that information in a special global PHP array - $_SESSION . We are using the empty function to check if the variable is empty, with the operator ! in front of it. Therefore we are saying:

    If the variable $_SESSION["LoggedIn"] is not empty and $_SESSION["Username"] is not empty, execute this piece of code.

    The next line works in the same fashion, only this time using the $_POST global array. This array contains any data that was sent from the login form that we will create later in this tutorial. The final line will only execute if neither of the previous statements are met; in this case we will display to the user a login form.

    So, now that we understand the logic, let"s get some content in between those sections. In your index.php file, enter the following below what you already have.

    Member Area

    and your email address is .

    Success"; echo "

    We are now redirecting you to the member area.

    "; echo ""; } else { echo "

    Error

    "; echo "

    Sorry, your account could not be found. Please click here to try again.

    "; } } else { ?>

    Member Login

    Thanks for visiting! Please either login below, or click here to register.



    Hopefully, the first and last code blocks won"t confuse you too much. What we really need to get stuck into now is what you"ve all come to this tutorial for - the PHP code. We"re now going to through the second section one line at a time, and I"ll explain what each bit of code here is intended for.

    $username = mysql_real_escape_string($_POST["username"]); $password = md5(mysql_real_escape_string($_POST["password"]));

    There are two functions that need explaining for this. Firstly, mysql_real_escape_string - a very useful function to clean database input. It isn"t a failsafe measure, but this will keep out the majority of the malicious hackers out there by stripping unwanted parts of whatever has been put into our login form. Secondly, md5 . It would be impossible to go into detail here, but this function simply encrypts whatever is passed to it - in this case the user"s password - to prevent prying eyes from reading it.

    $checklogin = mysql_query("SELECT * FROM users WHERE Username = "".$username."" AND Password = "".$password."""); if(mysql_num_rows($checklogin) == 1) { $row = mysql_fetch_array($checklogin); $email = $row["EmailAddress"]; $_SESSION["Username"] = $username; $_SESSION["EmailAddress"] = $email; $_SESSION["LoggedIn"] = 1;

    Here we have the core of our login code; firstly, we run a query on our database. In this query we are searching for everything relating to a member, whose username and password match the values of our $username and $password that the user has provided. On the next line we have an if statement, in which we are checking how many results we have received - if there aren"t any results, this section won"t be processed. But if there is a result, we know that the user does exist, and so we are going to log them in.

    The next two lines are to obtain the user"s email address. We already have this information from the query that we have already run, so we can easily access this information. First, we get an array of the data that has been retrieved from the database - in this case we are using the PHP function mysql_fetch_array . I have then assigned the value of the EmailAddress field to a variable for us to use later.

    Now we set the session. We are storing the user"s username and email address in the session, along with a special value for us to know that they have been logged in using this form. After this is all said and done, they will then be redirect to the Member Area using the META REFRESH in the code.

    So, what does our project currently look like to a user?

    Great! It"s time to move on now, to making sure that people can actually get into your site.

    Let the People Signup

    It"s all well and good having a login form on your site, but now we need to let user"s be able to use it - we need to make a login form. Make a file called register.php and put the following code into it.

    User Management System (Tom Cameron for NetTuts)

    Error"; echo "

    Sorry, that username is taken. Please go back and try again.

    "; } else { $registerquery = mysql_query("INSERT INTO users (Username, Password, EmailAddress) VALUES("".$username."", "".$password."", "".$email."")"); if($registerquery) { echo "

    Success

    "; echo "

    Your account was successfully created. Please click here to login.

    "; } else { echo "

    Error

    "; echo "

    Sorry, your registration failed. Please go back and try again.

    "; } } } else { ?>

    Register

    Please enter your details below to register.




    So, there"s not much new PHP that we haven"t yet learned in that section. Let"s just take a quick look at that SQL query though, and see if we can figure out what it"s doing.

    $registerquery = mysql_query("INSERT INTO users (Username, Password, EmailAddress) VALUES("".$username."", "".$password."", "".$email."")");

    So, here we are adding the user to our database. This time, instead of retrieving data we"re inserting it; so we"re specifying first what columns we are entering data into (don"t forget, our UserID will go up automatically). In the VALUES() area, we"re telling it what to put in each column; in this case our variables that came from the user"s input. So, let"s give it a try; once you"ve made an account on your brand-new registration form, here"s what you"ll see for the Member"s Area.

    Make Sure That They Can Logout

    We"re almost at the end of this section, but there"s one more thing we need before we"re done here - a way for user"s to logout of their accounts. This is very easy to do (fortunately for us); create a new filed named logout.php and enter the following into it.

    In this we are first resetting our the global $_SESSION array, and then we are destroying the session entirely.

    And that"s the end of that section, and the end of the PHP code. Let"s now move onto our final section.

    Step 3 - Get Styled

    I"m not going to explain much in this section - if you don"t understand HTML/CSS I would highly recommend when of the many excellent tutorials on this website to get you started. Create a new file named style.css and enter the following into it; this will style all of the pages that we have created so far.

    * { margin: 0; padding: 0; } body { font-family: Trebuchet MS; } a { color: #000; } a:hover, a:active, a:visited { text-decoration: none; } #main { width: 780px; margin: 0 auto; margin-top: 50px; padding: 10px; border: 1px solid #CCC; background-color: #EEE; } form fieldset { border: 0; } form fieldset p br { clear: left; } label { margin-top: 5px; display: block; width: 100px; padding: 0; float: left; } input { font-family: Trebuchet MS; border: 1px solid #CCC; margin-bottom: 5px; background-color: #FFF; padding: 2px; } input:hover { border: 1px solid #222; background-color: #EEE; }

    Now let"s take a look at a few screenshots of what our final project should look like:

    The login form.

    The member area.

    The registration form.

    And Finally...

    And that"s it! You now have a members area that you can use on your site. I can see a lot of people shaking their heads and shouting at their monitors that that is no use to them - you"re right. But what I hope any beginners to PHP have learned is the basics of how to use a database, and how to use sessions to store information. The vital skills to creating any web application.

    • Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.

    Сегoдня мы рассмотрим эксплуатацию критической 1day-уязвимости в популярной CMS Joomla, которая прогремела на просторах интернета в конце октября. Речь пойдет об уязвимостях с номерами CVE-2016-8869 , CVE-2016-8870 и CVE-2016-9081 . Все три происходят из одного кусочка кода, который пять долгих лет томился в недрах фреймворка в ожидании своего часа, чтобы затем вырваться на свободу и принести с собой хаос, взломанные сайты и слезы ни в чем не повинных пользователей этой Joomla. Лишь самые доблестные и смелые разработчики, чьи глаза красны от света мониторов, а клавиатуры завалены хлебными крошками, смогли бросить вызов разбушевавшейся нечисти и возложить ее голову на алтарь фиксов.

    WARNING

    Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.

    С чего все началось

    6 октября 2016 года Дэмис Пальма (Demis Palma) создал топик на Stack Exchange , в котором поинтересовался: а почему, собственно, в Joomla версии 3.6 существуют два метода регистрации пользователей с одинаковым названием register() ? Первый находится в контроллере UsersControllerRegistration , а второй - в UsersControllerUser . Дэмис хотел узнать, используется ли где-то метод UsersControllerUser::register() , или это лишь эволюционный анахронизм, оставшийся от старой логики. Его беспокоил тот факт, что, даже если этот метод не используется никаким представлением, он может быть вызван при помощи сформированного запроса. На что получил ответ от девелопера под ником itoctopus, подтвердившего: проблема действительно существует. И направил отчет разработчикам Joomla.

    Далее события развивались самым стремительным образом. 18 октября разработчики Joomla принимают репорт Дэмиса, который к тому времени набросал PoC, позволяющий регистрировать пользователя. Он опубликовал заметку на своем сайте , где в общих чертах рассказал о найденной проблеме и мыслях по этому поводу. В этот же день выходит новая версия Joomla 3.6.3, которая все еще содержит уязвимый код.

    После этого Давиде Тампеллини (Davide Tampellini) раскручивает баг до состояния регистрации не простого пользователя, а администратора. И уже 21 октября команде безопасности Joomla прилетает новый кейс. В нем речь уже идет о повышении привилегий . В этот же день на сайте Joomla появляется анонс о том, что во вторник, 25 октября, будет выпущена очередная версия с порядковым номером 3.6.3, которая исправляет критическую уязвимость в ядре системы.

    25 октября Joomla Security Strike Team находит последнюю проблему, которую создает обнаруженный Дэмисом кусок кода. Затем в главную ветку официального репозитория Joomla пушится коммит от 21 октября с неприметным названием Prepare 3.6.4 Stable Release , который фиксит злосчастный баг.

    После этого камин-аута к междусобойчику разработчиков подключаются многочисленные заинтересованные личности - начинают раскручивать уязвимость и готовить сплоиты.

    27 октября исследователь Гарри Робертс (Harry Roberts) выкладывает в репозиторий Xiphos Research готовый эксплоит , который может загружать PHP-файл на сервер с уязвимой CMS.

    Детали

    Что ж, с предысторией покончено, переходим к самому интересному - разбору уязвимости. В качестве подопытной версии я установил Joomla 3.6.3, поэтому все номера строк будут актуальны именно для этой версии. А все пути до файлов, которые ты увидишь далее, будут указываться относительно корня установленной CMS.

    Благодаря находке Дэмиса Пальмы мы знаем, что есть два метода, которые выполняют регистрацию пользователя в системе. Первый используется CMS и находится в файле /components/com_users/controllers/registration.php:108 . Второй (тот, что нам и нужно будет вызвать), обитает в /components/com_users/controllers/user.php:293 . Посмотрим на него поближе.

    286: /** 287: * Method to register a user. 288: * 289: * @return boolean 290: * 291: * @since 1.6 292: */ 293: public function register() 294: { 295: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN")); ... 300: // Get the form data. 301: $data = $this->input->post->get("user", array(), "array"); ... 315: $return = $model->validate($form, $data); 316: 317: // Check for errors. 318: if ($return === false) 319: { ... 345: // Finish the registration. 346: $return = $model->register($data);

    Здесь я оставил только интересные строки. Полную версию уязвимого метода можно посмотреть в репозитории Joomla.

    Разберемся, что происходит при обычной регистрации пользователя: какие данные отправляются и как они обрабатываются. Если регистрация пользователей включена в настройках, то форму можно найти по адресу http://joomla.local/index.php/component/users/?view=registration .


    Легитимный запрос на регистрацию пользователя выглядит как на следующем скриншоте.


    За работу с пользователями отвечает компонент com_users . Обрати внимание на параметр task в запросе. Он имеет формат $controller.$method . Посмотрим на структуру файлов.

    Имена скриптов в папке controllers соответствуют названиям вызываемых контроллеров. Так как в нашем запросе сейчас $controller = "registration" , то вызовется файл registration.php и его метод register() .

    Внимание, вопрос: как передать обработку регистрации в уязвимое место в коде? Ты наверняка уже догадался. Имена уязвимого и настоящего методов совпадают (register), поэтому нам достаточно поменять название вызываемого контроллера. А где у нас находится уязвимый контроллер? Правильно, в файле user.php . Получается $controller = "user" . Собираем все вместе и получаем task = user.register . Теперь запрос на регистрацию обрабатывается нужным нам методом.


    Второе, что нам нужно сделать, - это отправить данные в правильном формате. Тут все просто. Легитимный register() ждет от нас массив под названием jform , в котором мы передаем данные для регистрации - имя, логин, пароль, почту (см. скриншот с запросом).

    • /components/com_users/controllers/registration.php: 124: // Get the user data. 125: $requestData = $this->input->post->get("jform", array(), "array");

    Наш подопечный получает эти данные из массива с именем user .

    • /components/com_users/controllers/user.php: 301: // Get the form data. 302: $data = $this->input->post->get("user", array(), "array");

    Поэтому меняем в запросе имена всех параметров с jfrom на user .

    Третий наш шаг - это нахождение валидного токена CSRF, так как без него никакой регистрации не будет.

    • /components/com_users/controllers/user.php: 296: JSession::checkToken("post") or jexit(JText::_("JINVALID_TOKEN"));

    Он выглядит как хеш MD5, а взять его можно, например, из формы авторизации на сайте /index.php/component/users/?view=login .


    Теперь можно создавать пользователей через нужный метод. Если все получилось, то поздравляю - ты только что проэксплуатировал уязвимость CVE-2016-8870 «отсутствующая проверка разрешений на регистрацию новых пользователей».

    Вот как она выглядит в «рабочем» методе register() из контроллера UsersControllerRegistration:

    • /components/com_users/controllers/registration.php: 113: // If registration is disabled - Redirect to login page. 114: if (JComponentHelper::getParams("com_users")->get("allowUserRegistration") == 0) 115: { 116: $this->setRedirect(JRoute::_("index.php?option=com_users&view=login", false)); 117: 118: return false; 119: }

    А так в уязвимом:

    • /components/com_users/controllers/user.php:

    Ага, никак.

    Чтобы понять вторую, гораздо более серьезную проблему, отправим сформированный нами запрос и проследим, как он выполняется на различных участках кода. Вот кусок, который отвечает за проверку отправленных пользователем данных в рабочем методе:

    Продолжение доступно только участникам

    Вариант 1. Присоединись к сообществу «сайт», чтобы читать все материалы на сайте

    Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score!

    Last updated: Tue, 19 Sep 2006

    session_register

    (PHP 4, PHP 5)session_register -- Register one or more global variables with the current session

    Description

    bool session_register (mixed name [, mixed ...])
    session_register() accepts a variable number of arguments, any of which can be either a string holding the name of a variable or an array consisting of variable names or other arrays. For each name, session_register() registers the global variable with that name in the current session.
    Caution If you want your script to work regardless of register_globals , you need to instead use the $_SESSION array as $_SESSION entries are automatically registered. If your script uses session_register() , it will not work in environments where the PHP directive register_globals is disabled.
    register_globals: important note: Since PHP 4.2.0, the default value for the PHP directive register_globals is off , and it is completely removed as of PHP 6.0.0. The PHP community encourages all to not rely on this directive but instead use other means, such as the superglobals .
    Caution This registers a global variable. If you want to register a session variable from within a function, you need to make sure to make it global using the global keyword or the $GLOBALS array, or use the special session arrays as noted below.
    This function returns TRUE when all of the variables are successfully registered with the session. If session_start() was not called before this function is called, an implicit call to session_start() with no parameters will be made. $_SESSION does not mimic this behavior and requires session_start() before use. You can also create a session variable by simply setting the appropriate member of the $_SESSION or $HTTP_SESSION_VARS (PHP
    Note: It is currently impossible to register resource variables in a session. For example, you cannot create a connection to a database and store the connection id as a session variable and expect the connection to still be valid the next time the session is restored. PHP functions that return a resource are identified by having a return type of resource in their function definition. A list of functions that return resources are available in the