This commit is contained in:
2022-05-24 15:00:21 +02:00
parent 32a093d982
commit e8b40afaa8
13 changed files with 665 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# TD Sécurité Web. #
## Exo (Quizz) ##
* Comment échappe-t-on les caractères spéciaux en sql?
## Exo (Little Bobby Table) ##
* Allez lire le célèbre cartoon [exploits of a mum](https://xkcd.com/327/)
* Quel est le problème?
* Donnez un exemple de base de données et de requêtes nécessaires pour recréer ce qui est évoqué dans ce cartoon.
**corection**
Voir [explain xkcd](https://www.explainxkcd.com/wiki/index.php/327:_Exploits_of_a_Mom)?
## Exo (Pour aller plus loin) ##
Implémentez une base de donnée et un fichier d'inscription d'étudiant mal protégé pour réaliser concrètement l'attaque par inscription de *little bobby table*.
+160
View File
@@ -0,0 +1,160 @@
# Introduction à la sécurité des applis web en PHP.
Le tp utilise une application de mini-blog. Le code PHP est
volontairement simplifié et suffisamment laxiste pour illustrer les
failles de sécurité abordées en cours.
Le site utilise un base de données sql. Vous pouvez directement
l'importer (répertoire `extra`). Les paramètres pour l'accès à la base
de données sont dans `lib/common.php`.
Avant de démarrer, consultez les pages existantes, créez un compte et
authentifiez-vous.
#### Formulaires et html
##### Modification de contenu
1. Postez un commentaire sur un article.
2. Modifiez votre commentaire. Modifiez le commentaire d'un autre
utilisateur.
<details><summary>Aide</summary>
Regardez l'url !
</details>
3. Sécurisez l'écriture du formulaire `comment_create.php` afin qu'on
ne puisse pas accéder à un commentaire d'un autre utilisateur.
<details><summary>Aide</summary>
Dans la requête de modification d'un commentaire, ajoutez à la
clause WHERE la vérification du user.
</details>
4. Le client peut-il modifier le formulaire, en changeant par exemple
l'id du commentaire à modifier ? comment ?
<details><summary>Aide</summary>
Il suffit d'écrire son propre formulaire, avec la possibilité de
saisir le champ id du commentaire !
</details>
5. Pour se prémunir contre cela, on va, dans le formulaire, rajouter un
code d'authentification (hash avec une clé secrète) du champ
`id_comment` pour être sur que les données proviennent du formulaire
de l'application. Uilisez la fonction
[hash_mac](http://php.net/manual/fr/function.hash-hmac.php).
Rajoutez, dans le formulaire, un hash que vous pourrez comparé au
hash re-calculé après l'envoie des données.
<details><summary>Aide</summary>
<div>
générez un hash avec une clé secrète :
```php
$hashCode = hash_hmac("sha256",$_REQUEST['id_comment'],"Ma clé");
echo '<input name="cle" type="hidden" value="'.$hashcode .'" >';
```
Au moment du traitement du formulaire, quand on reçoit le champ
`id_comment`, on recalcule et compare le hash avec celui reçu.
</div>
</details>
##### XSS
1. Ajoutez un commentaire qui masque tout ce qui suit son affichage
(uniquement de l'html svp !). Revenez à un fonctionnement normal.
<details><summary>Aide</summary>
Pensez à fermer des balises, et à en ouvrir une qui cachera tout le
reste au moyen d'une propriété css.
</details>
2. Ajoutez un commentaire qui modifie le titre `h1` de la page de
l'article.
<details><summary>Aide</summary>
<div>
Le javascript permet de modifier le contenu HTML d'un élément :
```js
document.querySelector("h1").innerHTML = "hey !!!!";
```
</div>
</details>
3. Empêchez la saisie de balises HTML (les supprimer) pour éviter les
attaques ci-dessus. Est-ce suffisant ?
4. Echappez (protéger) les textes affichés dans le html
(htmlspecialchars).
5. Créez un compte en donnant une url de façon à exploiter une faille
XSS sur la page `article_view.php`.
<details><summary>Aide</summary>
<div>
```js
javascript:alert(/XSS !/);
```
</div>
</details>
6. Proposez une url vers la page `user_login.php` qui affiche dans un
popup les login/mots de passe saisies dans le formulaire (cela
pourrait être plus dangereux, en les envoyant sur un serveur par
exemple ...)
<details><summary>Aide</summary>
La valeur de la variable login est écrite dans le formulaire, dans
l'attribut value du champ texte correspondant.
Il donc tout à fait possible d'injecter du code javascript via un
attribut html, par exemple onchange ...
</details>
##### CSRF
Les vunérabilités de type CSRF (Cross-Site Request Forgery) consiste à
forger de fausses requêtes à partir d'url authentifiées et à pousser le
client à exécuter des actions sans le savoir.
Le client authentifié exécute à son insu une requête (suppression par
exemple) par un formulaire dissimuler ou une balise image contrôlée par
du javascript.
1. Se rendre sur la page `csrf.html` (soyez authentifié). Que s'est-il
passé ?
2. Passer les données en POST résoudra le problème précédent.
Malheureusement, on pourra quand même créer une attaque avec du
javascript. (vous verrez l'année prochaine qu'on peut faire une
requête http à partir de javasscript.
3. Résoudre le problème en utilisant un token unique dans le
formulaire.
#### Injection SQL {#injection-sql .alert}
1. Sur la page `user_login.php`, connectez-vous sans mot de passe,
juste avec un login valide.
2. connectez-vous sans mot de passe ni login.
3. "Sécurisez" avec la méthode `quote` de PDO.
4. Sur la page `comment_create.php`, malgré l'échappement SQL,
prouvez que l'on peut toujours modifier les commentaires d'autrui.
<details><summary>Aide</summary>
Regerdez la clause WHERE dans la requête d'update d'un
commentaire.
</details>
5. Utilisez le [filtrage](http://fr.php.net/manual/fr/book.filter.php)
en entrée des données.
6. Sécurisez la page `comment_create.php` avec des requêtes
préparées.
#### Sessions
1. Utilisez une faille XSS pour afficher le contenu du cookie. Volez
alors la session associée. (Il suffit d'écrire depuis un autre
navigateur la valeur du cookie récupéré)
+38
View File
@@ -0,0 +1,38 @@
<?php
require_once 'lib/common.php';
session_start();
$db = initDatabase();
$articles = $db->query("SELECT * FROM article")->fetchAll(PDO::FETCH_OBJ);
?>
<?php
include './templates/header.php';
?>
<body container>
<h1>Liste des articles</h1>
<?php
if (!empty($_SESSION['user'])) {
echo "<p>Bonjour, " . $_SESSION['user']->name . ".</p>";
}
?>
<ul>
<?php
foreach ($articles as $article) {
echo '<li><a href="article_view.php?id=' . $article->id .'">'
. $article->title . "</a></li>\n";
}
?>
</ul>
<?php
include './templates/footer.php';
?>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<?php
require_once 'lib/common.php';
session_start();
if (empty($_GET['id'])) {
header('Location: article_list.php');
exit();
}
$db = initDatabase();
$article = $db->query("SELECT * FROM article WHERE id=" . $_GET['id'])
->fetch(PDO::FETCH_OBJ);
$article->comments = $db->query("SELECT c.*, u.login, u.url "
. "FROM comment c JOIN user u ON c.id_user=u.id"
. " WHERE id_article=" . $_GET['id'])
->fetchAll(PDO::FETCH_OBJ);
?>
<?php
include 'templates/header.php';
?>
<body container>
<h1>Article</h1>
<?php
echo '<div id="article">'
. '<h3>'. $article->title .'</h3>'
. '<div id="content">' . $article->content . '</div>';
echo '<h5 class="_bb1">Commentaires</h5>';
if (empty($article->comments)) {
echo '<p>Aucun</p>';
} else {
foreach ($article->comments as $comment) {
echo '<section class="alert-box">';
echo "<b class='_ts2'>".$comment->title."</b>"
. (isset($_SESSION['user']->id) && $comment->id_user == $_SESSION['user']->id ?
' <a href="comment_create.php?id_article=' . $comment->id_article
. '&amp;id_comment=' . $comment->id . '" title="'. $comment->title
. '">Modifier ce commentaire</a>' :
'')
. '<p class="_ts2">' . $comment->content ."</p>"
. "<p><span class='tag-box -warning'><a href=\"$comment->url\">$comment->login</a></p>";
echo "</section>";
}
}
echo "</div>";
if (empty($_SESSION['user'])) {
echo '<p>Il faut être identifié pour poster un commentaire.</p>';
} else {
if ($article->closed) {
echo "<p>Article fermé, non modifiable.</p>";
} else {
echo '<p> <a href="comment_create.php?id_article='. $article->id
.'">Ajouter un commentaire</a> avec votre compte : ' . $_SESSION['user']->name
.' </p>';
}
}
?>
<p> <a href="article_list.php">Retour à la liste des articles</a> </p>
<?php
include './templates/footer.php';
?>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
<?php
require_once 'lib/common.php';
session_start();
$db = initDatabase();
if (empty($_REQUEST['id_article'])) {
header('Location: article_list.php');
exit();
}
if (!empty($_GET['title']) && !empty($_GET['content'])) {
$title = $_GET['title'];
$content = $_GET['content'];
if (empty($_GET['id_comment'])) { // nouveau ou modif ?
$sql = "INSERT INTO comment (id_article, title, content, id_user) "
."VALUES (".$_GET['id_article'].", '$title', '$content', ".$_SESSION['user']->id.")";
} else {
$sql = "UPDATE comment SET title='$title', content='$content', id_user=". $_SESSION['user']->id
." WHERE id = " . $_GET['id_comment'];
}
if ($db->query($sql)) {
header('Location: article_view.php?id=' . $_GET['id_article']);
exit();
} else {
die("Erreur : $sql");
}
}
?>
<?php
include './templates/header.php';
?>
<body container>
<h1>Ajouter/modifier un commentaire</h1>
<form action="" method="get">
<fieldset>
<?php if (!empty($_REQUEST['id_comment'])) {
echo '<input name="id_comment" type="hidden" value="' . $_REQUEST['id_comment'] ."\" />\n";
} ?>
<input name="id_article" type="hidden" value="<?php echo $_REQUEST['id_article']; ?>" />
<div><label> Titre <input name="title" type="text" value="" size="60" /></label></div>
<div> <label> Texte <textarea name="content" cols="60" rows="6"></textarea></label></div>
<button type="submit" name="ok" value="1">Ajouter ce commentaire</button>
</fieldset>
</form>
<?php
include './templates/footer.php';
?>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<html>
<head>
<title>Sécurité PHP</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<img src="./comment_create.php?id_article=2&title=CSRF&content=Triche&ok=1" alt="image"/>
</body>
</html>
+136
View File
@@ -0,0 +1,136 @@
-- phpMyAdmin SQL Dump
-- version 4.7.0
-- https://www.phpmyadmin.net/
--
-- Host: localhost
-- Generation Time: May 29, 2017 at 06:15 AM
-- Server version: 10.1.22-MariaDB
-- PHP Version: 7.1.4
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `securite`
--
-- --------------------------------------------------------
--
-- Table structure for table `article`
--
CREATE TABLE `article` (
`id` int(11) NOT NULL,
`title` text COLLATE utf8mb4_unicode_ci,
`closed` decimal(10,0) DEFAULT NULL,
`content` text COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Dumping data for table `article`
--
INSERT INTO `article` (`id`, `title`, `closed`, `content`) VALUES
(1, 'Premier', '1', 'Premier message, sans commentaire.'),
(2, 'Second', '0', 'Second message, avec des commentaires.');
-- --------------------------------------------------------
--
-- Table structure for table `comment`
--
CREATE TABLE `comment` (
`id` int(11) NOT NULL,
`id_article` decimal(10,0) DEFAULT NULL,
`title` text COLLATE utf8mb4_unicode_ci,
`id_user` decimal(10,0) DEFAULT NULL,
`content` text COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Dumping data for table `comment`
--
INSERT INTO `comment` (`id`, `id_article`, `title`, `id_user`, `content`) VALUES
(1, '2', 'Super', '2', 'Article très intéressant !'),
(2, '2', 'Un commentaire', '1', 'Pour tester !'),
(3, '2', 'Un deuxième commentaire', '1', 'ça casse pas trois pattes à un canard !');
-- --------------------------------------------------------
--
-- Table structure for table `user`
--
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`login` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`name` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password` text COLLATE utf8mb4_unicode_ci,
`url` text COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Dumping data for table `user`
--
INSERT INTO `user` (`id`, `login`, `name`, `password`, `url`) VALUES
(1, 'francois', 'François', 'mdp', 'http://fr.php.net'),
(2, 'denis', 'Denis', 'moi', 'http://lemonde.fr');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `article`
--
ALTER TABLE `article`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `comment`
--
ALTER TABLE `comment`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `user`
--
ALTER TABLE `user`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `login` (`login`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `article`
--
ALTER TABLE `article`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
--
-- AUTO_INCREMENT for table `comment`
--
ALTER TABLE `comment`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23;
--
-- AUTO_INCREMENT for table `user`
--
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+18
View File
@@ -0,0 +1,18 @@
<?php
function initDatabase() {
$dir = dirname(__FILE__);
try {
$db = new PDO (
'mysql:host=localhost;dbname=securite',
'',
'');
//
//$db = new PDO('sqlite:' . $dir .'/database.sq3');
// $db = new PDO('mysql:host=localhost;dbname=Base', 'login', 'mdp');
} catch (PDOException $e) {
die('DB error: ' . $e->getMessage());
}
return $db;
}
+9
View File
@@ -0,0 +1,9 @@
<hr>
<p class="pull-left">
<a href="./article_list.php"><i class="fa fa-home fa-2x"></i></a>
</p>
<p class="pull-right">
<a href="#" class="btn btn-normal"><i class="fa fa-arrow-circle-o-up fa-2x"></i></a>
</p>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<title>Sécurité PHP</title>
<link rel="stylesheet" href="http://www.iut-fbleau.fr/css/tacit.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
+51
View File
@@ -0,0 +1,51 @@
<?php
require_once 'lib/common.php';
session_start();
if (!empty($_POST['name']) && !empty($_POST['login']) && !empty($_POST['password'])) {
$db = initDatabase();
$sql = "INSERT INTO user (name, login, password, url) "
."VALUES ('".$_POST['name']."', '".$_POST['login']."', '".$_POST['password']
."', '" . $_POST['url'] . "')";
try{
if ($db->query($sql)){
header('Location: user_login.php');
exit();
}else{
die("Erreur !!!!");
}
} catch(PDOException $e) {
echo "Erreur: ".$e;
}
}
?>
<?php
include './templates/header.php';
?>
<body container>
<h1>Création de compte</h1>
<form action="" method="POST">
<fieldset>
<div>
<label>Nom <br> <input name="name" type="text" value="" /> </label>
</div>
<div><label> Site perso <br> <input name="url" type="text" value="" /> </label></div>
<div><label> Login <br> <input name="login" type="text" value="" /> </laebl></div>
<div><label> Mot de passe <br> <input name="password" type="password" value="" /> </label></div>
<button type="submit" name="ok" value="1">Créer ce compte</button>
</fieldset>
</form>
<p> <a href="article_list.php">Retour à la liste des articles</a> </p>
<?php
include './templates/footer.php';
?>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
<?php
require_once 'lib/common.php';
session_start();
if (!empty($_REQUEST['login']) && !empty($_REQUEST['password'])) {
$db = initDatabase();
$sql = "SELECT * FROM user "
."WHERE login='".$_POST['login']."' AND password='".$_POST['password']."'";
$user = $db->query($sql)->fetch(PDO::FETCH_OBJ);
if ($user) {
$_SESSION['user'] = $user;
header('Location: article_list.php');
exit();
}
}
?>
<?php
include 'templates/header.php';
?>
<body container>
<h1>Authentification et injection SQL</h1>
<form action="" method="POST">
<fieldset>
<div>
<label> Login : <input name="login" type="text" value="<?php if (isset($_REQUEST['login'])) { echo $_REQUEST['login']; } ?>" /> </label></div>
<div><label> Mot de passe : <input name="password" type="password" value="" /> </label></div>
<button type="submit" name="ok" value="1">S'authentifier</button>
</fieldset>
</form>
<?php
include './templates/footer.php';
?>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
[ Une liste de ressources partagée par Théo Debauvais ]
XSS:
explication:
https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A7-Cross-Site_Scripting_(XSS)
https://www.acunetix.com/websitesecurity/xss/
défense:
site pour s'entrainer (que des DOM-XSS et client-side XSS): https://xss.pwnfunction.com/
Injection SQL:
explication:
explication:
https://owasp.org/www-project-top-ten/OWASP_Top_Ten_2017/Top_10-2017_A1-Injection
https://owasp.org/www-community/attacks/SQL_Injection
https://www.w3schools.com/sql/sql_injection.asp
défense:
https://www.php.net/manual/en/mysqli.real-escape-string.php ou une fonction similaire en fonction du language que vous utilisez
Preparez vos requests, ou echappez les entrée comme ci-dessus
Programme pour tester automatiquement : http://sqlmap.org/
Local File Inclusion (LFI) / Remote File Inclusion (RFI):
https://en.wikipedia.org/wiki/File_inclusion_vulnerability
CSRF:
explication:
https://portswigger.net/web-security/csrf
https://owasp.org/www-community/attacks/csrf
défense:
https://thisinterestsme.com/php-csrf-protection/ , surtout le principe utilisé
OS injection:
explication:
https://portswigger.net/web-security/os-command-injection
https://owasp.org/www-community/attacks/Command_Injection
défense:
Pourquoi il ne faut pas essayer de cacher des fichiers sur un serveur http:
https://github.com/OJ/gobuster
https://tools.kali.org/web-applications/dirbuster
https://tools.kali.org/web-applications/dirb
Liste de vulnérabilitées (PortSwigger est une référence avec OWASP, ils développent https://tools.kali.org/web-applications/burpsuite, download=https://portswigger.net/burp):
https://portswigger.net/web-security/all-materials
Liste des failles web les plus connues (référence):
https://owasp.org/www-project-top-ten/
Applications vulnérables pour s'entrainer:
http://www.dvwa.co.uk/
http://itsecgames.com/
Site connus pour apprendre et s'entrainer (très axé sur le pentest, moins sur la défense):
https://www.root-me.org/
https://www.hackthebox.eu/
https://tryhackme.com/
Lectures de Stanford sur la sécurité web (très récent: commence en septembre 2019; très bon et explique plein d'autres failles et sujets sur la sécurité web):
https://www.youtube.com/watch?v=5JJrJGZ_LjM&list=PLMF2PpA06Sb26oYT-dfNOt3Y4wwoLAho0