Niezalogowany [ logowanie ]
Subskrybuj kanał ATOM Kanał ATOM    Subskrybuj kanał ATOM dla tagu sql Kanał ATOM (tag: sql)

Autor wpisu: Michał Środek, dodany: 13.02.2010 22:33, tagi: php, sql

Ostatnio pracowałem nad pewnym projektem wraz z innym(„troszkę“ mniej doświadczonym) programistą. Starałem się przymykać oko na wiele jego błędów(niepotrzebne zmienne, brak obiektowości itp.) jednak jeden był niewybaczalny — brak filtracji danych przychodzących w zapytaniach SQL. Dzisiaj postaram się wytłumaczyć dlaczego to jest tak bardzo ważne pokazując jak haker w prosty sposób może wykraść loginy i hasła użytkowników ze słabo zabezpieczonej witryny.

Stwórzmy przykładową bazę danych „hack“ aby pokazać jak to wygląda od drugiej strony.

CREATE TABLE articles(
id int(10) AUTO_INCREMENT,
title varchar(80) NOT NULL,
content text NOT NULL,
author int(10),
category int(10),
PRIMARY KEY(id)
);
 
CREATE TABLE users(
id int(10) AUTO_INCREMENT,
login varchar(40) NOT NULL,
email varchar(60) NOT NULL,
password varchar(32) NOT NULL,
PRIMARY KEY(id)
);

Wypełnijmy je danymi:

mysql> select * from articles;
+----+--------------+----------+--------+----------+
| id | title        | content  | author | category |
+----+--------------+----------+--------+----------+
|  1 | Artykul nr 1 | tresc... |      1 |        2 |
|  2 | Artykul nr 2 | tresc... |      3 |        1 |
|  3 | Artykul nr 3 | tresc... |      2 |        1 |
|  4 | Artykul nr 4 | tresc... |      3 |        2 |
+----+--------------+----------+--------+----------+
mysql> select * from users;
+----+--------+--------------------+----------------------------------+
| id | login  | email              | password                         |
+----+--------+--------------------+----------------------------------+
|  1 | Michal | michal@example.com | 1660fe5c81c4ce64a2611494c439e1ba |
|  2 | Magda  | romek@example.com  | 9d0250b24620c2056516e5d2d79eed4a |
|  3 | Romek  | romek@example.com  | 944278ab01f435bfc369fa038130f25b |
+----+--------+--------------------+----------------------------------+

Czas na kod PHP odpowiedzialny za pobieranie artykułów. Pomijam filtrowanie zmiennych przychodzących. Wygląda to mniej więcej tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$mysqli = new mysqli('localhost', 'root', '', 'hack');
 
if(isset($_GET['id']))
  $result = $mysqli->query('SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id='.$_GET['id']);
else
  $result = $mysqli->query('SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id');
 
while($row = $result->fetch_array())
{
  echo '<h2>'.$row['title'].'</h2>';
  echo '<strong>Author: '.$row['author'].'</strong>';
  echo '<p>'.$row['content'].'</p>';
}
?>

Wystarczy mała nieuwaga aby skrypt był dziurawy jak ser szwajcarski. Co więcej, dziurę taką da się bardzo łatwo wykryć — wystarczy apostrof lub cudzysłów.

http://localhost/hack/sql/?id=1'

Naszym oczom ukaże się błąd:

Fatal error: Call to a member function fetch_array() on a non-object in /home/hellson/public_html/hack/sql/index.php on line 27 

Jest to informacja, że użytkownik może wpływać na treść zapytania SQL. Dlaczego tak się dzieje? PHP bezmyslnie umieszcza zmienną $_GET[’id’] do zapytania SQL które teraz wygląda tak:

SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id=1'

Jest to niepoprawne zapytanie więc php zwraca błąd metody fetch_array(). Tym samym jest to informacja, że haker może spreparować własne zapytania, które zwrócą mu loginy i hasła. Oczywiście w sytuacji gdy nie zna on struktury bazy danych jest to troszkę utrudnione lecz wciąż możliwe. Aby uświadomić o jakie aspekty należy zadbać warto dowiedzieć się jak to robi przeciętny włamywacz.

Od strony hakera

Pierwszym krokiem jest sprawdzenie czy rzeczywiście możemy wpływać na zapytanie. Najlepiej jest dopisać nic nie znaczące „OR 1=1″

http://localhost/hack/sql/?id=1 OR 1=1
SELECT a.*, u.login FROM articles a, users u WHERE a.author=u.id AND a.id=1 OR 1=1

Naszym oczom powinna ukazać się pełna lista artykułów zamiast tylko jednego lub jeśli na stronie moduł pobiera tylko pierwszy rekord otrzymamy inny niż jest sprecyzowany w zmiennej id(oczywiście należy wziąć pod uwagę sortowanie). Kolejnym krokiem jest wprowadzenie własnego zapytania. Najczęściej używa się UNION ponieważ jest to najprostsza technika. UNION połączy 2 zapytania tylko wtedy gdy będą one posiadać taką samą ilość kolumn. Warto więc najpierw to sprawdzić metodą prób i błędów pobierając kolejno 2 NULL-e, 3 NULL-e itd. W naszym przykładzie wystarczy sześć.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: cojack, dodany: 19.01.2010 20:57, tagi: sql

Nadszedł czas na pierwszą implementację tej struktury w sql, oczywiście nie jest to czego byśmy oczekiwali ponieważ ma pewne braki które mogły by się wydawać dla większego użytku wprost odwrotnie proporcjonalne do zastosowania role based access control. Otóż jest trochę uproszczona struktura, dlaczego? Mieliśmy w pracy z kolegą burzę mózgów na ten temat gdyż go bardzo zainteresował i doszedł do wniosku iż konstruowanie kolosa może mieć negatywny wpływ na jego interfejs, i prawdę powiedziawszy po głębszym zastanowieniu się oraz doświadczeniu wyniesionym z używania phpbb (taki skrypt forum) doszedłem do wniosku iż ma chłop rację. Więc na pierwszy ogień skonstruowaliśmy taką oto strukturę rbac’a, ale moje ego nie pozwala mi na zakończenie prac nad tym i będę kontynuował rozwój do takiego stopnia że stwierdzę iż większego kolosa się zbudować nie da ponieważ zawiera już wszystko.

Struktura SQL Role Based Access Control

Także do dzieła, struktura bazy danych:

BEGIN;
 
DROP TABLE IF EXISTS "rbac_user" CASCADE;
CREATE TABLE "rbac_user"
(
   "idUser" SERIAL PRIMARY KEY,
   "userName" VARCHAR( 255 ) NOT NULL
);
 
DROP TABLE IF EXISTS "rbac_group" CASCADE;
CREATE TABLE "rbac_group"
(
   "idGroup" SERIAL PRIMARY KEY,
   "groupName" VARCHAR( 255 ) UNIQUE NOT NULL
);
 
DROP TABLE IF EXISTS "rbac_user_to_group";
CREATE TABLE "rbac_user_to_group"
(
   "idUser" INT REFERENCES "rbac_user"( "idUser" ) NOT NULL,
   "idGroup" INT REFERENCES "rbac_group"( "idGroup" ) NOT NULL,
   PRIMARY KEY( "idUser", "idGroup" )
);
 
DROP TABLE IF EXISTS "rbac_module" CASCADE;
CREATE TABLE "rbac_module"
(
   "idModule" SERIAL PRIMARY KEY,
   "moduleName" VARCHAR( 255 ) UNIQUE NOT NULL
);
 
DROP TABLE IF EXISTS "rbac_action" CASCADE;
CREATE TABLE "rbac_action"
(
   "idAction" SERIAL PRIMARY KEY,
   "idModule" INT REFERENCES "rbac_module"( "idModule" ) NOT NULL,
   "actionName" VARCHAR( 255 ) NOT NULL,
   "inherited" BOOLEAN DEFAULT FALSE NOT NULL,
   UNIQUE( "actionName", "idModule" )
);
 
DROP TABLE IF EXISTS "rbac_privilages";
CREATE TABLE "rbac_privilages"
(
   "idGroup" INT REFERENCES "rbac_group"( "idGroup" ) NOT NULL,
   "idModule" INT REFERENCES "rbac_module"( "idModule" ) NOT NULL,
   "idAction" INT REFERENCES "rbac_action"( "idAction" ) NOT NULL,
   "allow" BOOLEAN DEFAULT FALSE NOT NULL,
   PRIMARY KEY( "idGroup", "idModule", "idAction" )
);
 
COMMIT;

Użyłem transakcji, w posgresql można ;) W MySQL też, pod warunkiem że typ tabeli to InnoDB. Ale nie teraz o tym rzecz. Co jest co?

rbac_user – tabela przechowująca użytkowników naszego systemu rbac_group – tabela przechowująca grupy w naszym systemie rbac_user_to_group – tabela wiążąca użytkowników z grupami, relacja typu many-to-many (wiele do wielu) rbac_module – tabela przechowująca nazwy naszych modułów, można to rozumieć jako controllery naszej aplikacji rbac_action – tabela przechowująca nazwy naszych akcji, trzeba to rozumieć jako metody w naszych controllerach, relacja typu many-to-one (wiele do jednego, to samo co jeden do wielu [one-to-many]) rbac_privilages – a to jest tabela wiążąca grupę użytkowników, moduł i akcję.

Demo Role Based Access Control

Teraz jak to ustrojstwo wypełnić danymi? Bardzo proszę:

BEGIN;
 
INSERT INTO "rbac_user" ( "userName" ) VALUES ( 'przemek' ), ( 'maciej' ), ( 'andrzej' ), ( 'grzesiek' );
INSERT INTO "rbac_group" ( "groupName" ) VALUES ( 'admin' ), ( 'user' ), ( 'moderator' );
INSERT INTO "rbac_user_to_group" VALUES ( 1, 1 ), ( 2, 3 ), ( 3, 2 ), ( 4, 2 );
INSERT INTO "rbac_module" ( "moduleName" ) VALUES ( 'news' ), ( 'articles' ), ( 'download' ), ( 'music' );
INSERT INTO "rbac_action" ( "idModule", "actionName", "inherited" ) VALUES
   ( 1, 'index', FALSE ),
   ( 1, 'edit', FALSE ),
   ( 1, 'add', FALSE ),
   ( 1, 'delete', FALSE ),
   ( 1, 'block', FALSE ),
   ( 2, 'index', TRUE ),
   ( 2, 'edit', TRUE ),
   ( 2, 'add', TRUE ),
   ( 2, 'delete', TRUE ),
   ( 2, 'block', TRUE ),
   ( 3, 'index', TRUE ),
   ( 3, 'edit', FALSE ),
   ( 3, 'add', TRUE ),
   ( 3, 'delete', FALSE ),
   ( 3, 'block', FALSE ),
   ( 3, 'download', TRUE ),   
   ( 4, 'index', TRUE ),
   ( 4, 'edit', TRUE ),
   ( 4, 'add', FALSE ),
   ( 4, 'delete', FALSE ),
   ( 4, 'block', FALSE ),
   ( 4, 'download', FALSE ),
   ( 4, 'play', TRUE );
 
INSERT INTO "rbac_privilages" VALUES 
   ( 1, 1, 1, TRUE ),
   ( 1, 1, 2, TRUE ),
   ( 1, 1, 3, TRUE ),
   ( 1, 1, 4, TRUE ),
   ( 1, 1, 5, TRUE ),
   ( 2, 1, 1, TRUE ),
   ( 2, 1, 2, FALSE ),
   ( 2, 1, 3, TRUE ),
   ( 2, 1, 4, FALSE ),
   ( 2, 1, 5, FALSE ),
   ( 3, 1, 1, TRUE ),
   ( 3, 1, 2, TRUE ),
   ( 3, 1, 3, TRUE ),
   ( 3, 1, 4, TRUE ),
   ( 3, 1, 5, FALSE ),
   ( 1, 2, 6, TRUE ),
   ( 1, 2, 7, TRUE ),
   ( 1, 2, 8, TRUE ),
   ( 1, 2, 9, TRUE ),
   ( 1, 2, 10, TRUE ),
   ( 2, 2, 6, TRUE ),
   ( 2, 2, 7, FALSE ),
   ( 2, 2, 8, TRUE ),
   ( 2, 2, 9, FALSE ),
   ( 2, 2, 10, FALSE ),
   ( 3, 2, 6, TRUE ),
   ( 3, 2, 7, TRUE ),
   ( 3, 2, 8, TRUE ),
   ( 3, 2, 9, TRUE ),
   ( 3, 2, 10, FALSE ),
   ( 1, 3, 11, TRUE ),
   ( 1, 3, 12, TRUE ),
   ( 1, 3, 13, TRUE ),
   ( 1, 3, 14, TRUE ),
   ( 1, 3, 15, TRUE ),
   ( 1, 3, 16, TRUE ),
   ( 2, 3, 11, TRUE ),
   ( 2, 3, 12, FALSE ),
   ( 2, 3, 13, TRUE ),
   ( 2, 3, 14, FALSE ),
   ( 2, 3, 15, FALSE ),
   ( 2, 3, 16, TRUE ),
   ( 3, 3, 11, TRUE ),
   ( 3, 3, 12, TRUE ),
   ( 3, 3, 13, TRUE ),
   ( 3, 3, 14, FALSE ),
   ( 3, 3, 15, TRUE ),
   ( 3, 3, 16, TRUE ),
   ( 1, 4, 17, TRUE ),
   ( 1, 4, 18, TRUE ),
   ( 1, 4, 19, TRUE ),
   ( 1, 4, 20, TRUE ),
   ( 1, 4, 21, TRUE ),
   ( 1, 4, 22, TRUE ),
   ( 1, 4, 23, TRUE ),
   ( 2, 4, 17, TRUE ),
   ( 2, 4, 18, FALSE ),
   ( 2, 4, 19, TRUE ),
   ( 2, 4, 20, FALSE ),
   ( 2, 4, 21, FALSE ),
   ( 2, 4, 22, TRUE ),
   ( 2, 4, 23, TRUE ),
   ( 3, 4, 17, TRUE ),
   ( 3, 4, 18, TRUE ),
   ( 3, 4, 19, TRUE ),
   ( 3, 4, 20, FALSE ),
   ( 3, 4, 21, TRUE ),
   ( 3, 4, 22, TRUE ),
   ( 3, 4, 23, TRUE );
 
COMMIT;

Znowu transakcję, jakoś tak szybciej działa to ;) Jak pewnie zdążyliście zauważyć tabela rbac_action posiada kolumnę “inherited” która w późniejszym etapie mojej implementacji tego paradygmatu będzie potrzebna do tego by w hierarchii definiować czy dana akcja ma być dziedziczona dla pod grup. Taki ficzer.

Teraz tak, żeby to działało co jest wyżej przedstawione na gotowca, czyli copy-paste wymagana jest baza danych postgresql > 8.2. Dlaczego? A otóż dlatego że:

DROP "tabela" IF EXISTS;

jest zaimplementowane od tej versji. Oj brakowało tego brakowało… Nie wiem jak z wierszami podawanymi po przecinku, gdyż w 8.1 to nie działa (mowa tutaj o klauzuli INSERT).

Sprawdzanie uprawnień

Pozostało nam już tylko jak to pobrać, dla przykładu nie tworzyłem żadnych testów wydajnościowych, chodź przyznam że łączymy parę tabel ale utworzone indeksy są w pełni wykorzystywane, przykład jak sprawdzić uprawnienia:

SELECT 
  "allow" 
FROM 
  "rbac_privilages" p 
LEFT JOIN 
  "rbac_module" m 
ON 
  ( p."idModule" = m."idModule" ) 
LEFT JOIN 
  "rbac_action" a 
ON 
  ( p."idAction" = a."idAction" ) 
LEFT JOIN 
  "rbac_user_to_group" u2g 
ON 
  ( p."idGroup" = u2g."idGroup" ) 
WHERE 
  u2g."idUser" = 2 
AND 
  m."moduleName" = 'music' 
AND 
  a."actionName" = 'index';

Możemy sobie z tego ładny widoczek utworzyć, ale może o tym kiedy indziej. A teraz co wykazało explain?

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: stormfly, dodany: 03.01.2010 17:44, tagi: sql

Jednym z elementów rozbudowanego systemu CRM, nad którym pracuje był wybór typu projektu z podziałem na główny typ oraz podtyp. Można to zrealizować poprzez stworzenie dwóch tabel (typy i podtypy połączone relacją) lub jednej (wykorzystując kolumnę, która będzie przechowywać...

Autor wpisu: cojack, dodany: 30.12.2009 01:15, tagi: sql

Tym razem nie będzie nic o implementacji pewnej struktury drzewiastej w php. Natomiast zajmiemy się dziś sql. A dokładniej PostgreSQL, od dłuższego czasu nie mogłem ogarnąć modułu w postgresql, który nazywa się ltree, jak sama cząstkowa nazwy wskazuje i łatwo się domyślić że chodzi tutaj o reprezentację i strukturę drzewa. Co najlepsze, przeprowadzone benchmarki przez jego twórców wykazują na największy w śród tymczasowych ogólnie dostępnych wzorców tego typu struktury czas dostępu do danych.

Trochę historii W 2002 roku, dwóch programistów: Teodor Sigaev oraz Oleg Bartunov, wypuściło na światło dzienne moduł ltree (kiedy zaczęli, brak danych). Podeszli do tego dość ogólnego problemu z wielkim dystansem, za to teraz mamy potężne narzędzie całkowicie za darmo. Następne wydania miały na celu wyeliminowanie błędów zgłoszonych przez użytkowników oraz rozszerzyć możliwości o nowe możliwości w indeksie GIST. Więcej na temat samych wydań jak zwykle w paczce do pobrania z ich strony na dole.

Z czym to się je? Ltree jak już wyżej wspomniałem jest modułem do PosgreSQL’a mający za zadanie przechowywanie danych w sposób przedstawiający strukturę drzewiastą. Implementacja jak najbardziej wydajna. A więc zacznijmy.

W zależności od dystrybucji z jakiej korzystamy, możemy mieć lub nie dodatkową paczkę dla postgresql’a która powinna się nazywać mniej więcej tak: postgresql-contrib

Po zainstalowaniu tych dodatków mamy masę innych rzeczy, z których nie koniecznie będziemy korzystać. Ale nas interesuje ltree, ścieżka ltree.sql u mnie to:

/usr/share/postgresql/8.4/contrib/

a żeby wyszukać gdzie my to mamy:

locate ltree.sql

po wcześniejszym updatedb.

powinniśmy mieć tam plik ltree.sql, musimy ten plik wrzucić do naszej bazy danych. No to jedziemy:

psql nazwaDB < /usr/share/postgresql/8.4/contrib/ltree.sql

powinniśmy mieć wszystko ładnie wrzucone do bazy danych. Teraz zostało nam już tylko utworzenie tabeli i ogień.

Przykładowa tabela z naszym drzewem.

Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...

Autor wpisu: stormfly, dodany: 06.12.2009 15:06, tagi: sql

CREATE INDEX - przyspieszenie zapytań Pora sobie odpowiedzieć na pytanie kiedy i w jaki sposób warto zastosować index by przyspieszyć nasze zapytania. Mamy kilka typów indeksów, ale używać będziemy domyślnego, czyli B-tree. Nie jest to moje widzi misie, ale te pozostałe są po prostu...

Autor wpisu: eRIZ, dodany: 11.11.2009 13:40, tagi: php, sql

PHP (zresztą nie tylko ten język) jako server-side do nauczenia trudny nie jest. Po jakim czasie seito twierdzi, że potrafi pisać całkiem niezłe skrypty, CMS, czy aplikacje. Ale nawet i wielu sensei często miewa problemy, gdy chodzi o mocno obciążone serwisy, w których stopniowo zaczynają pojawiać się wąskie gardła - strony wczytują się coraz wolniej, aż do momentu, gdy baza danych odmawia posłuszeństwa z powodu przeciążenia.

Na domiar złego, użytkownicy odświeżają wielokrotnie te same strony - albo z powodu wielu prób otwarcia, albo w celu aktualizacji często zmieniających się treści. I tak mamy pewien procent żądań, które tylko niepotrzebnie obciążają serwer, nieraz będące błędnym kołem - skrypt oczekuje na połączenie z bazą, a zniecierpliwiony gość albo opuści stronę, albo będzie wciskał CTRL+R do oporu…

Owszem, można postawić farmę serwerów z load-balancingiem/proxy-cache, ale nieraz jest to strzelaniem do muchy z armaty, poza tym - w przypadku mniejszych serwisów jest to poza ich zasięgiem finansowym. Jednak są sposoby… ;]

Autor wpisu: JoShiMa, dodany: 08.11.2009 22:22, tagi: php, sql

Skoro już wyjaśniłam na czym polega konstrukcja drzewa typu nested set, pora pokazać w jaki sposób, za pomocą niezbyt skomplikowanych zapytań, można wyciągnąć różne informacje. Na początek zajmiemy się zapytaniami, które pozwolą nam wczytać strukturę drzewa. Wczytanie drzewa Żeby wczytać całe drzewo nie trzeba wielkiej filozofii, wystarczy wykonać jedno zapytanie. To zagadnienie jest jednak trywialne bez [...]
Wszystkie wpisy należą do ich twórców. PHP.pl nie ponosi odpowiedzialności za treść wpisów.