Jaki jest najlepszy sposób zapobiegania przechwytywaniu sesji?


124

W szczególności dotyczy to wykorzystania pliku cookie sesji klienta do identyfikacji sesji na serwerze.

Czy najlepszą odpowiedzią jest użycie szyfrowania SSL / HTTPS dla całej witryny internetowej i masz najlepszą gwarancję, że żaden człowiek w trakcie ataków nie będzie w stanie podsłuchać istniejącego pliku cookie sesji klienta?

A może drugim najlepszym rozwiązaniem jest użycie jakiegoś rodzaju szyfrowania samej wartości sesji, która jest przechowywana w pliku cookie sesji?

Jeśli złośliwy użytkownik ma fizyczny dostęp do maszyny, nadal może zajrzeć do systemu plików, aby pobrać prawidłowy plik cookie sesji i użyć go do przejęcia sesji?

Odpowiedzi:


140

Szyfrowanie wartości sesji będzie miało zerowy skutek. Plik cookie sesji jest już dowolną wartością, a jego zaszyfrowanie spowoduje po prostu wygenerowanie kolejnej arbitralnej wartości, którą można wykryć.

Jedynym prawdziwym rozwiązaniem jest HTTPS. Jeśli nie chcesz używać protokołu SSL w całej witrynie (być może masz problemy z wydajnością), możesz uciec tylko z SSL chroniącym wrażliwe obszary. Aby to zrobić, najpierw upewnij się, że Twoja strona logowania to HTTPS. Kiedy użytkownik się loguje, ustaw bezpieczny plik cookie (co oznacza, że ​​przeglądarka będzie przesyłać go tylko przez łącze SSL) oprócz zwykłego pliku cookie sesji. Następnie, gdy użytkownik odwiedzi jeden z „wrażliwych” obszarów, przekieruj go do HTTPS i sprawdź, czy istnieje ten bezpieczny plik cookie. Prawdziwy użytkownik to będzie miał, porywacz sesji nie.

EDYCJA : Ta odpowiedź została pierwotnie napisana w 2008 roku. Teraz jest rok 2016 i nie ma powodu, aby nie mieć SSL w całej witrynie. Koniec z HTTP w postaci zwykłego tekstu!


28
HTTPS zapobiegnie tylko wąchaniu. Ale jeśli masz XSS lub identyfikatory sesji można łatwo odgadnąć, lub jesteś podatny na utrwalanie sesji, lub twój magazyn identyfikatorów sesji jest słaby (wstrzyknięcie SQL?), SSL nie będzie wcale lepszy.
Calimo

5
@Josh Jeśli złośliwy użytkownik ma fizyczny dostęp do maszyny, nadal może zajrzeć do systemu plików, aby pobrać prawidłowy plik cookie sesji i użyć go do przejęcia sesji?
Pacerier,

72
Jeśli złośliwy użytkownik ma fizyczny dostęp do systemu plików, nie musi przejmować sesji.
Josh Hinman

25
@Josh. Nieprawda, czasami użytkownik ma ograniczony czasowo fizyczny dostęp do systemu plików. Wyobraź sobie laptopa, który został odblokowany przez kolegę pędzącego do toalety, teraz wszystko, co muszę zrobić, to przejść do tego laptopa, zainstalować wtyczkę EditThisCookie, pobrać jego pliki cookie z plus.google.com za pomocą funkcji eksportu EditThisCookie i mam teraz jego konto . Czas potrzebny: 18 sekund.
Pacerier

1
Jestem prawie pewien, że Google będzie miał
wbudowaną

42

SSL pomaga tylko w podsłuchiwaniu ataków. Jeśli osoba atakująca ma dostęp do Twojego komputera, zakładam, że może również skopiować Twój bezpieczny plik cookie.

Przynajmniej upewnij się, że stare ciasteczka po jakimś czasie stracą na wartości. Nawet udany atak przechwytujący zostanie udaremniony, gdy plik cookie przestanie działać. Jeśli użytkownik ma plik cookie z sesji, która zalogowała się ponad miesiąc temu, poproś o ponowne wprowadzenie hasła. Upewnij się, że za każdym razem, gdy użytkownik kliknie łącze „wyloguj się” w Twojej witrynie, nie będzie można już nigdy użyć starego UUID sesji.

Nie jestem pewien, czy ten pomysł zadziała, ale oto idzie: Dodaj numer seryjny do pliku cookie sesji, może ciąg podobny do tego:

SessionUUID, numer seryjny, bieżąca data / godzina

Zaszyfruj ten ciąg i użyj go jako pliku cookie sesji. Regularnie zmieniaj numer seryjny - może wtedy, gdy plik cookie ma 5 minut, a następnie ponownie wystaw plik cookie. Jeśli chcesz, możesz nawet ponownie wydać go przy każdym wyświetleniu strony. Po stronie serwera zachowaj ostatni numer seryjny, który wydałeś dla tej sesji. Jeśli ktoś kiedykolwiek wyśle ​​plik cookie z nieprawidłowym numerem seryjnym, oznacza to, że osoba atakująca może używać pliku cookie, który przechwycił wcześniej, więc unieważnij identyfikator UUID sesji i poproś użytkownika o ponowne wprowadzenie hasła, a następnie ponowne wysłanie nowego pliku cookie.

Pamiętaj, że Twój użytkownik może mieć więcej niż jeden komputer, więc może mieć więcej niż jedną aktywną sesję. Nie rób niczego, co zmusza ich do ponownego logowania za każdym razem, gdy przełączają się między komputerami.


Wiem, że to stary post, ale chciałem tylko dodać, że jeśli atakujący miałby przejąć sesję w oknie wyznaczonym przez „numer seryjny”, nie miałoby to na niego wpływu.
zmiażdżyć

@crush, ale wtedy atakujący zostałby zablokowany po wyznaczonym oknie, prawda? Więc przynajmniej okno ataku jest mniejsze (e).
Johanneke,

2
Jedynym problemem jest to, że jeśli użytkownik opuści Twoją witrynę na 5 minut, będzie musiał ponownie się zalogować
elipoultorak

21

Czy rozważałeś przeczytanie książki o bezpieczeństwie PHP? Wysoce rekomendowane.

Odniosłem duży sukces z następującą metodą dla witryn bez certyfikatu SSL.

  1. Nie zezwalaj na wiele sesji na tym samym koncie, upewniając się, że nie sprawdzasz tego wyłącznie na podstawie adresu IP. Raczej sprawdzaj za pomocą tokena wygenerowanego przy logowaniu, który jest przechowywany wraz z sesją użytkownika w bazie danych, a także adres IP, HTTP_USER_AGENT i tak dalej

  2. Korzystanie z hiperłączy opartych na relacjach Generuje łącze (np. Http://example.com/secure.php?token=2349df98sdf98a9asdf8fas98df8 ) Do łącza jest dołączany losowo wygenerowany ciąg znaków MD5 x-BYTE (preferowany rozmiar), po przekierowaniu strony wygenerowany losowo token odpowiada żądanej stronie.

    • Po ponownym załadowaniu wykonywanych jest kilka testów.
    • Źródłowy adres IP
    • HTTP_USER_AGENT
    • Token sesji
    • dostajesz punkt.
  3. Plik cookie uwierzytelniania sesji o krótkim czasie życia. jak napisano powyżej, dobrym pomysłem jest plik cookie zawierający bezpieczny ciąg znaków, który jest jednym z bezpośrednich odniesień do ważności sesji. Spraw, aby wygasał co x minut, ponownie wystawiaj ten token i ponownie zsynchronizuj sesję z nowymi danymi. Jeśli jakiekolwiek niezgodności w danych są niezgodne, wyloguj użytkownika lub poproś o ponowne uwierzytelnienie sesji.

W żadnym wypadku nie jestem ekspertem w tej dziedzinie, miałem trochę doświadczenia w tym konkretnym temacie, mam nadzieję, że część z tego pomoże komukolwiek.


4
Czy są jakieś książki, które byś polecił?
Rikki,

1
Zwłaszcza biorąc pod uwagę, że OP nie mówi nic o PHP, powiedziałbym, że lepiej jest spojrzeć tylko na ogólną książkę o bezpieczeństwie (zwłaszcza, że ​​bezpieczeństwo między różnymi językami różni się tylko szczegółami implementacji, ale koncepcje pozostają takie same).
matts 1

w 2017 nawet facebook / gmail itp. pozwala na wiele sesji na tym samym koncie (szczególnie na urządzeniach mobilnych), chyba że nienawidzisz swoich użytkowników, numer 1 jest nieco przestarzały.
cowbert

20
// Collect this information on every request
$aip = $_SERVER['REMOTE_ADDR'];
$bip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$agent = $_SERVER['HTTP_USER_AGENT'];
session_start();

// Do this each time the user successfully logs in.
$_SESSION['ident'] = hash("sha256", $aip . $bip . $agent);

// Do this every time the client makes a request to the server, after authenticating
$ident = hash("sha256", $aip . $bip . $agent);
if ($ident != $_SESSION['ident'])
{
    end_session();
    header("Location: login.php");
    // add some fancy pants GET/POST var headers for login.php, that lets you
    // know in the login page to notify the user of why they're being challenged
    // for login again, etc.
}

Służy to przechwytywaniu „kontekstowych” informacji o sesji użytkownika, czyli informacji, które nie powinny zmieniać się w trakcie trwania pojedynczej sesji. Użytkownik nie będzie jednocześnie przebywał przy komputerze w USA i Chinach, prawda? Jeśli więc adres IP zmienia się nagle w tej samej sesji, co silnie implikuje próbę przejęcia sesji, możesz zabezpieczyć sesję, kończąc sesję i zmuszając użytkownika do ponownego uwierzytelnienia. To udaremnia próbę włamania, atakujący jest również zmuszony do zalogowania się zamiast uzyskania dostępu do sesji. Powiadom użytkownika o próbie (trochę się AJAX) i vola, lekko zirytowany + poinformowany użytkownik, a jego sesja / informacje są chronione.

Dorzucamy User Agent i X-FORWARDED-FOR, aby zrobić wszystko, co w naszej mocy, aby uchwycić wyjątkowość sesji dla systemów za serwerami proxy / sieciami. Możesz wtedy wykorzystać więcej informacji, nie krępuj się.

Nie jest w 100%, ale jest cholernie skuteczny.

Możesz zrobić więcej, aby chronić sesje, wygasać je, gdy użytkownik opuści witrynę i wróci, aby zmusić go do ponownego zalogowania. Możesz wykryć, że użytkownik odchodzi i powraca, przechwytując puste HTTP_REFERER (domena została wpisana w pasku adresu URL) lub sprawdzić, czy wartość w HTTP_REFERER jest równa Twojej domenie, czy nie (użytkownik kliknął zewnętrzny / spreparowany link, aby dostać się do Twojego teren).

Wygaś sesje, nie pozwól im pozostać ważne na czas nieokreślony.

Nie polegaj na plikach cookie, można je ukraść, jest to jeden z wektorów ataku na przejmowanie sesji.


3
Wcześniej spotkałem się z sytuacją, w której pewien (dość duży, ale technicznie wsteczny) dostawca usług internetowych zmieniałby od czasu do czasu adres IP przeglądarki użytkownika w oparciu o przekierowywanie połączeń użytkowników. To sprawiło, że czek REMOTE_ADDR zwrócił nam fałszywe negatywy.
goofballLogic

Po co zawracać sobie głowę tworzeniem haszyszu? $_SESSION['ident'] = $aip . $bip . $agent;byłoby równie bezpieczne.
Dan Bray

2
Jeśli masz użytkowników mobilnych, którzy korzystają z Twojej witryny w podróży i przechodzą z jednego punktu dostępu do drugiego, ich adresy IP będą się zmieniać i będą wylogowywać się z konta.
Kareem

A co z serwerami proxy i VPN? Każdy, kto korzysta z proxy TOR, będzie stale wylogowywany ze swojego konta… w rzeczywistości co kilka minut.
Bradley,

Nawet jeśli to przeoczysz, adresy IP mogą być manipulowane przez klienta, więc i tak nie zniechęciłoby to atakującego.
Bradley

9

Wypróbuj protokół Secure Cookie opisany w tym artykule przez Liu, Kovacs, Huang i Gouda:

Jak stwierdzono w dokumencie:

Bezpieczny protokół plików cookie, który działa między klientem a serwerem, musi zapewniać następujące cztery usługi: uwierzytelnianie, poufność, integralność i zapobieganie powtórkom.

Jeśli chodzi o łatwość wdrożenia:

Pod względem wydajności nasz protokół nie obejmuje żadnego wyszukiwania w bazie danych ani kryptografii klucza publicznego. Pod względem możliwości wdrażania nasz protokół można łatwo wdrożyć na istniejącym serwerze WWW i nie wymaga on żadnych zmian w specyfikacji internetowej pliku cookie.

Krótko mówiąc: jest bezpieczny, lekki, u mnie działa po prostu świetnie.


9
Twój link to specyfikacja protokołu - czy masz link do implementacji? Byłoby świetnie - dzięki.
Adam

9

Nie ma sposobu, aby zapobiec przejmowaniu sesji w 100%, ale przy pewnym podejściu możemy skrócić czas przechwytywania sesji przez atakującego.

Metoda zapobiegania przejmowaniu sesji:

1 - zawsze używaj sesji z certyfikatem ssl;

2 - wysyłaj plik cookie sesji tylko z ustawieniem httponly na wartość true (zapobiegaj dostępowi do plików cookie sesji przez javascript)

2 - użyj identyfikatora regeneracji sesji przy logowaniu i wylogowywaniu (uwaga: nie używaj regeneracji sesji przy każdym żądaniu, ponieważ jeśli masz kolejne żądanie Ajax, masz szansę na utworzenie wielu sesji).

3 - ustaw limit czasu sesji

4 - zapisz klienta użytkownika przeglądarki w zmiennej $ _SESSION i porównaj z $ _SERVER ['HTTP_USER_AGENT'] przy każdym żądaniu

5 - ustaw plik cookie tokena i ustaw czas wygaśnięcia tego pliku cookie na 0 (do zamknięcia przeglądarki). Regeneruj wartość cookie dla każdego żądania (w przypadku żądania Ajax nie generuj ponownie pliku cookie tokena). DAWNY:

    //set a token cookie if one not exist
    if(!isset($_COOKIE['user_token'])){
                    //generate a random string for cookie value
        $cookie_token = bin2hex(mcrypt_create_iv('16' , MCRYPT_DEV_URANDOM));

        //set a session variable with that random string
        $_SESSION['user_token'] = $cookie_token;
        //set cookie with rand value
        setcookie('user_token', $cookie_token , 0 , '/' , 'donategame.com' , true , true);
    }

    //set a sesison variable with request of www.example.com
    if(!isset($_SESSION['request'])){
        $_SESSION['request'] = -1;
    }
    //increment $_SESSION['request'] with 1 for each request at www.example.com
    $_SESSION['request']++;

    //verify if $_SESSION['user_token'] it's equal with $_COOKIE['user_token'] only for $_SESSION['request'] > 0
    if($_SESSION['request'] > 0){

        // if it's equal then regenerete value of token cookie if not then destroy_session
        if($_SESSION['user_token'] === $_COOKIE['user_token']){
            $cookie_token = bin2hex(mcrypt_create_iv('16' , MCRYPT_DEV_URANDOM));

            $_SESSION['user_token'] = $cookie_token;

            setcookie('user_token', $cookie_token , 0 , '/' , 'donategame.com' , true , true);
        }else{
            //code for session_destroy
        }

    }

            //prevent session hijaking with browser user agent
    if(!isset($_SESSION['user_agent'])){
        $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
    }

    if($_SESSION['user_agent'] != $_SERVER['HTTP_USER_AGENT']){
      die('session hijaking - user agent');
    }

uwaga: nie generuj ponownie pliku cookie tokena za pomocą żądania Ajax uwaga: powyższy kod jest przykładem. uwaga: jeśli użytkownicy wylogują się, token cookie musi zostać zniszczony, podobnie jak sesja

6 - nie jest dobrym podejściem używanie adresu IP użytkownika do zapobiegania przejmowaniu sesji, ponieważ niektórzy użytkownicy zmieniają adresy IP przy każdym żądaniu. KTÓRE WPŁYWAJĄ NA WAŻNYCH UŻYTKOWNIKÓW

7 - osobiście przechowuję dane sesji w bazie danych, od Ciebie zależy jaką metodę zastosujesz

Jeśli zauważysz błąd w moim podejściu, popraw mnie. Jeśli masz więcej sposobów na zapobieganie hyjakowi podczas sesji, powiedz mi.


Cześć, staram się zapobiec przejmowaniu sesji (w ASP.NET) i rozważyłem wszystkie powyższe kroki u sugerowane. Jest to przybliżone działanie, ale kiedy używam trybu InPrivateBrowsing / incognito przeglądarki, sesja jest przejęta. Czy możesz zasugerować jakąś dodatkową rzecz do dodania w ciągu sessionId?
Vishwanath Mishra

4

Upewnij się, że nie używasz inkrementujących liczb całkowitych jako identyfikatorów sesji. Znacznie lepiej jest użyć identyfikatora GUID lub innego długiego, losowo generowanego ciągu znaków.


3

Istnieje wiele sposobów tworzenia ochrony przed przejęciem sesji, jednak wszystkie z nich albo zmniejszają zadowolenie użytkowników, albo nie są bezpieczne.

  • Czeki IP i / lub X-FORWARDED-FOR. Te działają i są całkiem bezpieczne ... ale wyobraź sobie ból użytkowników. Przychodzą do biura z WiFi, otrzymują nowy adres IP i tracą sesję. Muszę się ponownie zalogować.

  • Agent użytkownika sprawdza. Podobnie jak powyżej, nowa wersja przeglądarki jest niedostępna i tracisz sesję. Dodatkowo są one naprawdę łatwe do „zhakowania”. Wysyłanie fałszywych ciągów znaków UA jest trywialne dla hakerów.

  • token localStorage. Podczas logowania wygeneruj token, zapisz go w pamięci przeglądarki i zapisz w zaszyfrowanym pliku cookie (zaszyfrowanym po stronie serwera). Nie ma to żadnych skutków ubocznych dla użytkownika (localStorage utrzymuje się po uaktualnieniu przeglądarki). To nie jest tak bezpieczne - jak po prostu bezpieczeństwo poprzez zaciemnienie. Dodatkowo możesz dodać logikę (szyfrowanie / deszyfrowanie) do JS, aby jeszcze bardziej ją ukryć.

  • Ponowne wysłanie pliku cookie. To prawdopodobnie właściwy sposób. Sztuczka polega na tym, aby pozwolić na używanie pliku cookie tylko jednemu klientowi na raz. Tak więc, aktywny użytkownik będzie miał ponownie wysyłany plik cookie co godzinę lub krócej. Stary plik cookie jest unieważniany, jeśli zostanie wystawiony nowy. Hacki są nadal możliwe, ale znacznie trudniejsze do zrobienia - albo haker, albo ważny użytkownik zostanie odrzucony.


1

Rozważmy, że podczas fazy logowania klient i serwer mogą uzgodnić tajną wartość soli. Następnie serwer podaje wartość zliczania przy każdej aktualizacji i oczekuje, że klient odpowie hashem (sekretna sól + liczba). Potencjalny porywacz nie ma żadnego sposobu na uzyskanie tej tajnej wartości soli, a zatem nie może wygenerować następnego skrótu.


3
Ale jak chcesz przechowywać tę sól po stronie klienta, aby nikt nie mógł jej ukraść?
Dcortez,

1

ODPOWIEDŹ obiekt sesji nie jest dostępny po stronie klienta, ponieważ jest przechowywany na serwerze WWW. Jednak identyfikator sesji jest przechowywany jako plik cookie i umożliwia serwerowi WWW śledzenie sesji użytkownika.

Aby zapobiec przechwytywaniu sesji przy użyciu identyfikatora sesji, możesz przechowywać zaszyfrowany ciąg znaków wewnątrz obiektu sesji, utworzony przy użyciu kombinacji dwóch atrybutów, adresu zdalnego i portu zdalnego, do których można uzyskać dostęp na serwerze WWW wewnątrz obiektu żądania. Te atrybuty wiążą sesję użytkownika z przeglądarką, w której zalogował się użytkownik.

Jeśli użytkownik loguje się z innej przeglądarki lub trybu incognito w tym samym systemie, adres IP pozostanie taki sam, ale port będzie inny. Dlatego podczas uzyskiwania dostępu do aplikacji serwer WWW przypisuje użytkownikowi inny identyfikator sesji.

Poniżej znajduje się kod, który zaimplementowałem i przetestowałem, kopiując identyfikator sesji z jednej sesji do drugiej. Działa całkiem dobrze. Jeśli istnieje luka, daj mi znać, jak ją zasymulowałeś.

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    HttpSession session = request.getSession();
    String sessionKey = (String) session.getAttribute("sessionkey");
    String remoteAddr = request.getRemoteAddr();
    int remotePort = request.getRemotePort();
    String sha256Hex = DigestUtils.sha256Hex(remoteAddr + remotePort);
    if (sessionKey == null || sessionKey.isEmpty()) {
        session.setAttribute("sessionkey", sha256Hex);
        // save mapping to memory to track which user attempted
        Application.userSessionMap.put(sha256Hex, remoteAddr + remotePort);
    } else if (!sha256Hex.equals(sessionKey)) {
        session.invalidate();
        response.getWriter().append(Application.userSessionMap.get(sessionKey));
        response.getWriter().append(" attempted to hijack session id ").append(request.getRequestedSessionId()); 
        response.getWriter().append("of user ").append(Application.userSessionMap.get(sha256Hex));
        return;
    }
    response.getWriter().append("Valid Session\n");
}

Użyłem algorytmu SHA-2 do hashowania wartości, korzystając z przykładu podanego w SHA-256 Hashing at baeldung

Czekam na twoje komentarze.


@zaph Przepraszam, ale nie dodałem odpowiedzi, aby zyskać reputację. Nie chodzi też o szyfrowanie SHA-2. Ale fakt, że byłem w stanie wykryć użycie pliku cookie z identyfikatorem sesji innego użytkownika w sesji innego użytkownika, tj. Przejęcie sesji, jak w pierwotnym pytaniu. Przetestowałem moje rozwiązanie i stwierdziłem, że działa. Ale ja nie jestem ekspertem w tej dziedzinie, więc chciałbym, aby społeczność sprawdzała, czy moja odpowiedź jest wystarczająco dobra. Może zobaczysz, co robi mój fragment kodu i zdecydujesz, czy odpowiada na pytanie. Dzięki!
Jzf,

@zaph Dziękuję za wskazanie błędu. Zaktualizowałem moją odpowiedź zgodnie z twoją sugestią. Usuń swój głos negatywny, jeśli uważasz, że odpowiedź jest pomocna.
Jzf

0

Aby zmniejszyć ryzyko, możesz również powiązać pierwotny adres IP z sesją. W ten sposób osoba atakująca musi znajdować się w tej samej sieci prywatnej, aby móc korzystać z sesji.

Sprawdzanie nagłówków odnośników również może być opcją, ale łatwiej je sfałszować.


7
nie, nie możesz użyć pierwotnego adresu IP, ponieważ może się on zmienić - albo przez dynamiczne adresy IP, zmienione, gdy użytkownik jest tymczasowo odłączony, albo przez (niejawne lub jawne) użycie farmy proxy. Ponadto porywacze sesji pochodzący od tego samego dostawcy usług internetowych mogą używać tego samego serwera proxy i adresu IP co legalny użytkownik ...
Olaf Kock

1
PHP-Nuke ma dobrą stronę o ich podejściu do sesji i szczegółowo omawiają,
Sembiance

4
Zmiana adresów IP jest bardzo powszechna w przypadku dostawców internetu mobilnego. Dzięki Vodafone moje IP zmienia się wraz z KAŻDĄ prośbą.
Blaise,

1
Trochę starego postu, ale żeby to rozwinąć. Jak wspomniano, adresy IP są złym pomysłem. Jak wspomniano, użytkownicy mobilni mają tendencję do przemieszczania się po adresach IP. Niektórzy dostawcy usług internetowych w przeszłości również korzystali z adresów IP w roamingu (np. AOL), jednak obecnie podejście to staje się coraz bardziej popularne z powodu braku adresów IPv4. Do takich dostawców usług internetowych należą Plus net i BT
Peter

-15

Ochrona przez:

$ip=$_SERVER['REMOTE_ADDER'];
$_SESSEION['ip']=$ip;

12
Nie rozumiem, co tu robisz.
samayo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.