Думаю, многие сталкивались с тем, что иногда необходимо защитить ваш скрипт от копирования, и вы использовали разные Ioncube, PHPLockit, но многим неудобно каждый раз кодировать. Понимаю, что в Ioncube есть такая штука, благодаря которой можно генерировать ключи безопасности и т.д., но многим невыгодно покупать или же пользоваться чужими услугами, а то мало ли, все бывает.

Некоторым хочется кодировать не весь код, а лишь его часть, и для этого многие используют такой «массив» для защиты:

$_SERVER['HTTP_HOST']

Но его легко обойти:

$_SERVER['HTTP_HOST']='разрешенный домен';

Поэтому есть решение данной проблемы, и её может решить любой человек с начальным знанием PHP, MySQL.

В итоге у нас получится:
  • Скрипт будет закодирован путем обфускатора, который посылает API запрос к сайту, где API ищет доменное имя и ключ в базе данных; если он есть, то скрипт будет работать, если нет, то он будет переадресовывать основному сайту (пример будет таким: domain.com/api.php?domain=mysite1.ru&key=4024B-C0876-4FF0C-9A298-80EFA);
  • Также у нас будет скрипт api.php, который будет отвечать за работу проверки лицензии и т.д.;
  • Также хочу выделить лицензионный ключ, который мы будем получать. Лицензионный ключ — это md5 хэш домена, который будет проверятся через api, а в базе данных будет записан лишь сам домен и его статус.

Решение


1. Выдача лицензий и проверка действительности скрипта через api:

<?php

$config = array(

	'host' => 'localhost',
	'user' => 'root',
	'pass' => '',
	'base' => 'lic'
	
);

$db = new mysqli($config['host'], $config['user'], $config['pass'], $config['base']);
if($db->connect_errno) {
    exit('Ошибка: Не удалось подключиться к базе данных!');
}
$db->set_charset("utf8");

class Main {
	public 	function keygen($domain){
		$key[0] = strtoupper(md5($domain));
		$key[1] = substr($key[0], 0, 5);
		$key[2] = substr($key[0], 5, 5);
		$key[3] = substr($key[0], 10, 5);
		$key[4] = substr($key[0], 15, 5);
		$key[5] = substr($key[0], 20, 5);
		
		return $key[1].'-'.$key[2].'-'.$key[3].'-'.$key[4].'-'.$key[5];
	}

	public function getLicInfo($domain){
		global $db, $config;
		
		$sql = "SELECT * FROM `licenses` WHERE `domain` = '{$domain}' LIMIT 1";
		$result = $db->query($sql);

		if($result->num_rows == 1){
			return $result->fetch_assoc();
		}
		return false;
	}
}

$main = new Main;

$domain = "".$_GET['domain']."";
$key = strtoupper($_GET['key']);



if($lic = $main->getLicInfo($domain)){
	if($lic['license_status']){
		$twokey = $main->keygen($lic['license_domain']);
		if($twokey == $key){
			$a = 'OK_'.$lic['license_domain'];
		} else {
			$a = 'ERROR_wrongkey';
		}
	} else {
		$a = 'ERROR_nolicense';
	}
} else {
	$a = 'ERROR_nolicense';
}

echo $a;
exit();
?>

Вот сам код api.php. Тут я хотел бы обратить ваше внимание на следующий код:

class Main {
	public 	function keygen($domain){
		$key[0] = strtoupper(md5($domain));
		$key[1] = substr($key[0], 0, 5);
		$key[2] = substr($key[0], 5, 5);
		$key[3] = substr($key[0], 10, 5);
		$key[4] = substr($key[0], 15, 5);
		$key[5] = substr($key[0], 20, 5);
		
		return $key[1].'-'.$key[2].'-'.$key[3].'-'.$key[4].'-'.$key[5];
	}

Данный класс создает ключ домена путем использования md5 хэш.

2.Проверка доменного имена на наличие в базе данных
В 1 пункте мы с вами обозревали код api.php, который отвечает за работу скрипта. Хочу выделить код, который я уже выделял:

	public function getLicInfo($domain){
		global $db, $config;
		
		$sql = "SELECT * FROM `licenses` WHERE `domain` = '{$domain}' LIMIT 1";
		$result = $db->query($sql);

		if($result->num_rows == 1){
			return $result->fetch_assoc();
		}
		return false;
	}
}


Данный класс проверяет доменное имя на наличие в базе данных, и если все удачно, то скрипт работает; если нет, то он переадресует на основной сайт.

Вот мы с вами закончили обозревать код api.php, который отвечает за основную работу проверки лицензии, но теперь стоит вопрос: «А как его реализовать в самом скрипте?»

Делается оно благодаря следующему коду:

	public function __destruct() {
		$request = file_get_contents("http://domain.com/api.php?domain=". $_SERVER['HTTP_HOST'] ."&key=".$this->config->lic_key);
		$status = explode("_", $request);
		
		if($status[0] != "OK" && "".$_SERVER['HTTP_HOST']."" != $status[1]){
			header("Location: http://domain.com/");
		}
	}

Этот код отправляет запрос в API и если имеется в базе данных, и если доменное имя есть в базе данных, то скрипт работает, если нет, то не работает. Такая же ситуация, если код неправильный, для этого в api.php существует следующий «отрезок» кода:

if($lic = $main->getLicInfo($domain)){
	if($lic['license_status']){
		$twokey = $main->keygen($lic['license_domain']);
		if($twokey == $key){
			$a = 'OK_'.$lic['license_domain'];
		} else {
			$a = 'ERROR_wrongkey';
		}
	} else {
		$a = 'ERROR_nolicense';
	}
} else {
	$a = 'ERROR_nolicense';
}

echo $a;
exit();

Думаю, тут понятно: если все правильно, то «выходит» сообщение ok_myssite.com и это удовлетворяет, то скрипт продолжает работу, а если введен неверный ключ или доменное имя, то «выходит» следующие сообщения (смотря где есть ошибка):

ERROR_nolicense

или же

ERROR_wrongkey

Думаю, все. Также жду конструктивных комментариев, и благодарю pixxxel за его статью «Защита PHP скриптов от копирования — это возможно?»

Комментарии (9)


  1. edogs
    20.07.2015 12:47
    +8

    Бессмысленная гора кода.

    Сначала Вы говорите о легком способе обойти

    $_SERVER['HTTP_HOST']
    Но его легко обойти:
    $_SERVER['HTTP_HOST']='разрешенный домен';


    Но при этом в упор не видите такой же легкий способ обойти Ваш способ
    if($status[0] != «OK» && "".$_SERVER['HTTP_HOST']."" != $status[1]){
    header(«Location: domain.com»);
    }
    его же тоже легко обойти
    if($status[0] != «OK» && "".$_SERVER['HTTP_HOST']."" != $status[1]){
    //header(«Location: domain.com»);
    }


    p.s.: обфускация в плане эффективности равноприменима и к первому варианту и ко второму.


    1. crmMaster
      20.07.2015 15:11
      -6

      Не палите контору. Чем больше таких вот писателей-лицензиатов, тем проще потом простым пацанам нуллить подобные поделки.


  1. maximw
    20.07.2015 12:49
    +3

    Прорисываю в /etc/hosts
    127.0.0.1 domain.com
    Делаю небольшой скрипт api.php, который выдает нужный md5.
    Profit.


  1. OnYourLips
    20.07.2015 12:58
    +13

    На самом деле решение давно есть и оно простое: SaaS.
    Ну а автору такого ужасного кода я советую основательно засесть за www.phptherightway.com


  1. kentastik
    20.07.2015 15:31

    я бы еще со стороны сервера проверял откуда приходит запрос, потом сверял IP запроса и IP домена, чтобы под одной лицензией не сидело 300 сайтов.


  1. garex
    20.07.2015 15:42

    Ещё проще — удалить метод __destruct ))


  1. Ugputu
    20.07.2015 15:56
    +5

    А потом «они» жалуются, — «нас взломали и утянули базу». А все потому, что человек который пишет защиту, понятия не имеет что такое защита. И допускает банальные ошибки которые приводят к SQL Injection.

    Смею допустить что автор и слыхом не слыхивал о filter_input.


  1. Ugputu
    20.07.2015 17:17
    -1

    Можно же делать через JS запрос к АПИ и смотреть реферер? Не?


    1. Finesse
      21.07.2015 04:29

      Речь идёт о проверке лицензии на сервере.

      Referer — один из HTTP заголовков, его легко подделать.