SecureM, Sécurité Web

Aller au contenu | Aller au menu | Aller à la recherche

Virtualisation Linux

Coucou tout le monde :)

Aujourd'hui, je me suis amusé à essayer la virtualisation. Détails de l'aventure.

Virtualisation, késako ?

La virtualisation est une technique (stylééee) qui permet de faire tourner une "machine virtuelle" sur un ordinateur déjà en route. En gros, vous êtes sous votre tout nouveau Windows Seven, mais vous regrettez votre ancien XP parce que lui au moins pouvait faire tourner votre logiciel préféré. Pas de problème, virtualisez-le donc ! Vous aurez alors un Windows XP qui tournera dans... une fenêtre. Sous Windows Seven. Sympa non ?
Bon, ok, l'exemple n'est peut-être pas approprié, mais illustre bien le procédé : on fait tourner un autre (tant qu'à faire) système d'exploitation sur l'ordinateur.

Mon exemple

on, comme vous le savez peut-être, je suis sous Mandriva. Mandriva Linux 2010.0. J'ai en dual boot le Windows Vista fourni avec le portable, dont j'ai créé le double CD de restauration. J'ai décidé d'utiliser VirtualBox pour commencer, parce qu'il est apparemment plus facile d'utilisation, et en Français. Et en effet, après un petit urpmi virtualbox (sans oublier les modules noyau et les dkms), je lance le logiciel : l'interface est agréable, des infobulles d'aide apparaissent pour chaque élément, bref, super ergonomie. Je suis le tutoriel disponible sur le site du zéro (http://www.siteduzero.com/tutoriel-3-35665-la-virtualisation-sous-linux.html) : nouveau disque dur de 20Go - ça devrait suffire, etc... Configuration ok, j'ai juste oublié de redémarrer pour installer les modules noyau. Ceci fait, je parviens à booter sur l'installateur via VirtualBox ! Après un message un peu flippant me disant un truc du style "Formattage du disque dur en cours" je me rappelle que le disque dur aussi est virtuel, ouf... enfin bon, faut quand même avoir la foi.
Je m'amuse alors à regarder comment la transition se fait pour la souris ou le clavier : en effet lorsque la souris de votre ordinateur colle le bord de l'écran, elle ne peut pas s'enfuir. Mais là... elle passe sur les autres fenêtres et sort de la machine virtuelle. VirtualBox empêche simplement la souris de sortir de l'écran virtuel, et pour en sortir il faut appuyer sur une touche spéciale (Control droit) pour arrêter la "capture" de la souris ou du clavier. En attendant, mon ordi rame bien... Il faut dire que l'installateur qui tourne en parallèle prend pas mal de temps processeur. On patiente un peu, et hop, insertion du second CD. Ca continue à mouliner méchant.

Et zut, impossible : l'installateur n'arrive pas à aller plus loin que l'outil d'installation de la partition de restauration cachée de Vista (WinRE) qui reste bloqué à 0% sans pour autant geler la machine. Bon, pas de nouveau petit Vista pour ce soir. C'est peut-être dû à ma configuration matérielle, je sais pas, mais ça aurait été fun d'avoir un Windows Vista totalement neuf sous la main pour s'amuser XD.
Je me tourne alors vers d'autres systèmes : on peut placer une image ISO dans le "lecteur CD" virtualisé, et la machine virtuelle démarrera dessus. J'essaie donc KDE-Four-Life (avec OpenSuse) que j'ai téléchargé en 64 bits pour tester d'une pierre deux coups. Bah zéro coup pour le coup : Grub détecte la machinevirtualisée comme étant en 32 bits et refuse de lancer le système. Pas de chance, alors que mon ordinateur "réel" supporte le 64 bits. Je me tourne finalement vers le LiveCD de Mandriva 2010, en 32 bits : ça marche :D
Bon, par contre, déjà que KDE 4 en LiveCD ça rame pas mal, si en plus on virtualise le tout, ça rame méchamment quand on ouvre plus d'une application ><

Conclusion de la journée : on essaiera de virtualiser Windows Seven quand j'aurai reçu le CD ;)

Demain j'essaierai la virtualisation avec un autre outil, WmWare, qui a pour avantage de pouvoir faire démarrer une machine virtuelle sur un disque dur existant, et donc sur mon installation de Vista actuelle.

XML / RPC & API Movable Type + Dotclear + KDE : bloguez depuis votre bureau !

Salut tout le monde :)

Un jour, j'ai mis à jour Dotclear (genre le gars sérieux) et j'ai vu l'apparition d'une certaine API XML/RPC, qui permettrait de se connecter au blog...

Avec un client XML/RPC, naturellement. Hop hop, KBlogger me semble cool, maiiis... non. Impossible de le faire marcher. Dommage.

Et là j'ai mis à jour ma Mandriva, la 2010 intègre KDE 4.3. Le système de gestion des informations personnelles de KDE (Kontact : KMail, KOrganizer, KNotes, etc...) contient un module de "Journal", dont je n'avais jamais vraiment compris l'utilité (non mais je vais pas non plus écrire un journal intime sur mon ordi non plus ><).

Et vla-ty-pas qu'en ajoutant un nouveau calendrier (en ligne pour synchroniser mes ordinateurs) je vois que je peux ajouter, un "Journal dans un blog".

Je réactive donc l'interface XML/RPC de SecureM, je rentre les informations, et... ça marche :D

La preuve, j'écris ce message à partir du module Journal de Kontact !

Quelques trucs qui vont pas encore très bien : si ça supporte le "Texte enrichi", l'éditeur se limite au formatage basique (gras, italique, souligné, barré, alignement et listes-puces) et ne permet pas par exemple d'insérer des images, des liens, ou d'éditer directement le code HTML.

Mais j'aime bien le concept de KDE du "tout connecté" :D

Technologies d'exploration du web...

Salut tout le monde :)

Il y a peu, j'ai récupéré un petit script que j'avais créé et qui parcourait le web à la recherche d'acronymes / d'abréviations : un "crawler". Je me suis donc remis à la tâche, et aès de nombreuses améliorations et optimisations, je suis arrivé à un résultat assez sympa...

Mais commençons par le début

Le crawler : kesskécé & komensamarche ?

Ce que j'appelle "crawler" est un robot (programme informatique hein, n'imaginez pas un R2D2 qui se branche sur le réseau ><) qui "parcourt le web". Aussi appelé robot d'indexation, il télécharge les pages web et suit les liens présents sur ces pages qui mènent vers d'autres sites, d'où il pourra trouver de nouveaux liens, etc...
Le premier script était codé en VBS et datait de plusieurs années, mais le nouveau est codé en Perl, langage souple et puissant que j'apprends en même temps :)

Parcourir le web : pourquoi faire ?

Eh ben oui, à quoi ça sert d'avoir un programme qui se balade sur le web, à part à utiliser de la bande passante et du processeur, me direz-vous ?
A rien à part s'amuser, on est d'accord. C'est pourquoi, à la base, je suis parti avec l'objectif de collecter les acronymes et abréviations présentes sur le net.
Ca a donné le projet Acronymes, aujourd'hui plus ou moins abandonné (du moins l'interface web), et le nom du robot : le RIAcronymes.
Sur le plan technique, certains me diront "extraire des acronymes ? difficile ! bla bla bla analyse sémantique du contenu blablabla très difficile blablabla...". Eh bah non, pouf. Je vais pas me compliquer la vie à créer un robot qui comprend le français pour qu'il puisse comprendre les données comme n'importe qui, alors qu'il existe... une balise XHTML <acronym> et <abbr> !
Donc aucun problème sur le plan technique : à chaque page téléchargée, en plus d'appliquer la recherche de liens, on applique une détection de ces balises, et hop on les récupère et on les stocke sur le serveur. C'est rudimentaire mais très intéressant ;)

Les statistiques

Je ne résiste pas à l'envie de poster mes statistiques : en quelques heures d'éxecution, mon nouveau robot a récupéré pas moins de 100 000 liens et parcouru plus de 8000 pages (pour environ 1313 sites différents). La liste de ces sites est accessible ici : http://www.securem.eu/public/listes.html (ah oui, pas la peine d'essayer de trier les colonnes, j'ai copié cette page comme un sagouin et les liens pointent toujours vers l'adresse locale 127.0.0.1 - pas les liens vers les sites par contre).
Cependant je me suis vite heurté à une grosse difficulté technique : l'élimination des doublons lorsque le nombre d'adresses est très grand (100 000, tout est relatif ^^). Après quelques benchmarks (chronométrages des parties les plus chronophages du script) il apparaît clairement que le temps mis à vérifier que le lien découvert n'a pas déjà été découvert est énorme par rapport au reste, et aboutit à une perte d'efficacité du script.
Il faut préciser que pour un script bien optimisé, l'étape qui doit prendre le plus de temps est le téléchargement de la page !
Sur le nouveau robot que je réécris, il prend 98% du temps ;)

Pour les différentes stratégies d'exploration et ce qui rend mon nouveau robot plus rapide, je vous ferai sûrement un autre billet avant novembre.
Je posterai aussi sûrement les sources du robot :)

La Bruteforce

La Bruteforce... c'est un nom un peu bourrin je vous l'accorde ^^
Mais pourtant ça veut bien dire ce que ça veut dire : la force brute.

Mais commençons par le début : kesskécé ?

La Bruteforce : utilité

La Bruteforce est une attaque informatique visant à trouver une bonne séquence de caractères : un code.
On va donc "forcer" le code comme on forcerait une porte. Cette attaque utilise la puissance de l'ordinateur, qui est capable d'essayer plusieurs centaines de combinaisons à la minute...

La cible est généralement un système demandant un mot de passe, ou bien un mot de passe crypté qu'il faut décrypter - j'en reparlerai plus tard.

On a donc un système demandant un mot de passe. Par exemple une page de connexion à une interface d'administration. On a déjà le nom d'utilisateur mais pas le mot de passe.
Le bruteforceur a pour but de trouver ce mot de passe en les essayant... tous.

La Bruteforce : essayer toutes les combinaisons possibles

Quand je vous disais que c'était un truc de bourrin...
Ce procédé est aussi appelé attaque incrémentale parce qu'elle essaie chaque mot de passe possible l'un après l'autre :
Par exemple pour un code numérique à 4 caractères :
  • 0000
  • 0001
  • 0002
  • ...
  • 0010
  • 0011
  • ...
  • 0156
  • ...
  • 5910
  • ...
  • 9999
En pratique le bruteforceur n'atteindra que rarement 9999, puisqu'en envoyant chaque code au système, celui-ci répondra inlassablement "Code faux" jusqu'à que le bon code soit essayé... "Code valide, vous êtes maintenant connecté !"
Vous imaginez ? Ca fait froid dans le dos non ?
Surtout que les protections existantes ne sont pas nombreuses : on est obligé de laisser un utilisateur envoyer son code pour qu'il puisse se connecter...
Lorsque le réseau délivre 5000 tentatives de connexions à la minute, il fait parfaitement son travail !

La Bruteforce : protections

Voici les moyens les plus efficaces et les plus utilisés pour lutter contre la bruteforce :
  1. La limitation du nombre de connexions échouées : on fixe par exemple une limite de 5 tentatives ratées avant le blocage de l'utilisateur, par exemple pour une durée de 24H. Le problème majeur présenté par cette technique - qui est cependant la plus répandue car plus facile à mettre en place - est qu'elle bloque un ordinateur / une adresse IP pour un certain temps, ce qui peut être regrettable dans le cadre d'un poste public, ou pire d'un serveur piraté qui ne pourrait plus se connecter à un autre serveur (par exemple un serveur de base de données qui bloquerait le serveur web) ...
  2. Le placement d'un délai minimum entre deux tentatives : par exemple pas plus d'une tentative en 30 secondes, ce qui est raisonnable pour un humain normal, mais augmenterait considérablement le temps mis pour bruteforcer le mot de passe...
    Cette technique est plus difficile à mettre en place, mais est plus intelligente que la première.
  3. Les deux mon capitaine ! C'est en effet le mélange des deux principales solutions qui marche le mieux, et qui est par exemple utilisé sur les système Unix/Linux.
Et encore je ne vous ai pas parlé des autres solutions, qui restent grosso modo des remake de ces deux solutions...
Vous pourrez ainsi voir fail2ban, qui bloque automatiquement les IPs suspectes ...

La Bruteforce : performances et solution ultime

Mais LA meilleure solution pour éviter de voir une attaque par bruteforce réussir, c'est... d'avoir un bon mot de passe !
Plus "strong" sera ton mot de passe, plus de temps mettra ton attaquant à le trouver ! dixit un jour un petit personnage vert dont je ne me souviens plus du nom...
Ainsi, un mot de passe de 6 caractères ou moins est très vulnérable (moins de 24H environ pour le trouver) ;
Un mot de passe avec uniquement des minuscule aussi, c'est pourquoi je préconise un mot de passe de 7-8 caractères au moins, avec des majuscules, des minuscules, des chiffres et des caractères spéciaux.
Enfin, évitez par pitié les mots courants ou les noms ! Ces mots de passe sont particulièrement vulnérables aux attaques par dictionnaire (Dictionary Attacks) et aux Rainbow Attacks (pas la pêche de traduire ^^)

Le mot de passe idéal est par exemple la première lettre de chaque mot d'une phrase : personnellement j'utilise souvent - par exemple - "Je suis un joueur de ce jeu formidable qu'est ***" qui donne > j$1j2cjfqe***
Avouez que c'est plus dur à trouver que sebastien42 !

La défense ultime contre la Bruteforce est donc de choisir un mot de passe suffisamment long :)

Les attaques par dictionnaire et les Rainbow Attacks

Il existe 3 types d'attaques BruteForce :
  • La Bruteforce incrémentale, qui essaie TOUTES les combinaisons dans l'ordre. (AAA AAB AAC ...)
  • Les Dictionary Attacks : on essaie tous les mots du dictionnaire, ce qui est nettement moins lourd que l'attaque incrémentale (beaucoup moins de possibilités).
  • Les Rainbow Attacks : on essaie tous les mots du dictionnaire, avec d'autres lettres, chiffres, symboles, etc... C'est une sorte de remix des deux précédents. Et la plus efficace.


J'étofferai sûrement cet article dans les jours qui viennent si je ne suis pas trop pris.
A bientôt, en espérant que cet article vous a été utile :)

Optimisez votre code PHP

Bonjour à tous !

C'est après une longue absence que je publie cet article, pour vous donner quelques conseils d'optimisation PHP.

C'est en fait depuis que je me suis remis à mon jeu en ligne futuriste FuturaX que j'avais commencé il y a quelques années, et que je développe seul la seconde version, que je me suis rendu compte de la différence entre mon code de 2004 et mon code d'aujourd'hui, et que je me suis dit que ça valait la peine de vous faire profiter de quelques astuces...

Voici la liste des différences techniques :

  • J'ai changé d'OS pour développer (Win 98 ou 2000 je sais plus, à un Linux Mandriva, mais sur un ordinateur qui ne s'est pas amélioré comme l'OS : 256Mo de RAM et processeur poussif).
  • J'ai changé d'encodage : je passe du ISO-machinchouette-3 à l'UTF-8, j'avais notamment eu un problème dû à l'encodage quand j'ai tenté de passer à l'AJAX (vous ne savez pas ce que c'est ? Pas grave, je vous ferai un article là-dessus si je prends le temps ^^)...
  • J'utilise désormais KDE (avec Linux, désolé pour les gnomes^^) qui permet notamment la gestion transparente du protocole FTP (entre autres), c'est-à-dire que je visualise un dossier distant, j'ouvre une image pour la modifier, je change les propriétés d'un fichier comme si j'étais en local. Cependant comme c'est légèrement plus lent (on s'en doute) et que Olympe-Network (mon génial hébergeur :P ) n'autorise pas plus de 80 utilisateurs connectés en même temps sur le FTP (sur 18000 utilisateurs), ça devenait difficile de développer directement en ligne... J'ai donc installé le serveur Apache et le reste (15 secondes chrono, vive Linux) pour pouvoir développer en local (et je me débarrasse ainsi de tout EasyPHP ou autre pseudo-serveur).
Ce que vous devez retenir, c'est (mis à part que Linux c'est bien - et compliqué) :
  • L'UTF-8 C'EST BIEN C'EST BON MANGEZ-EN !! Sérieusement, c'est beaucoup plus souple, pour quelques modifications pas difficiles : changer l'encodage dans votre éditeur préféré, ajoutez l'en tête HTTP et le meta-tag dans vos pages :
    <?php header('Content-type: text/html; Charset: UTF-8'); ?> (en adaptant bien sûr le content-type - type du contenu)
    Et <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> (de même pour le content-type)
  • Au niveau technique c'est à peu près tout, je ne préfère pas vous donner de conseils trop précis pour ce qui est de l'IDE (environnement de développement) ni de l'OS (gniark gniark ^^)
Pour ce qui est des habitudes de codage, certaines astuces vont sûrement vous intéresser / vous aider :

Manuel des fonctions

Premièrement, n'hésitez pas à ajouter le moteur de recherche du manuel PHP (RTFM qu'on vous dit !) soit en allant directement sur le site http://php.net/ et en ajoutant le moteur de recherche (ça marche sous Firefox du moins) ou bien pour vous simplifier la vie, en cliquant ici pour ajouter directement le moteur de recherche à Firefox.

Vous n'avez alors plus qu'à entrer un nom de fonction, de classe, de ce que vous voulez pour obtenir la documentation complète et les exemples !

Utilisez des classes (et vive la POO ^^)

Deuxièmement, utilisez des classes si cela est pertinent et utile dans le contexte, par exemple pour la gestion de MySQL. C'est ce que j'ai fait, et je peux ainsi centraliser la gestion des erreurs MySQL, la sécurisation des requêtes (contre les injections SQL), ou encore compter le temps mis pour effectuer une requête, le nombre de requête par page en moyenne...

Les expressions ternaires

Utilisez les expressions ternaires, qui fonctionnent de la manière suivante :

$nom = (empty($pseudo)) : 'Inconnu' ? $pseudo;

Elles permettent de remplacer les instructions IF de manière plus réduite, condensée et efficace (sous la forme condition : valeur-si-oui ? valeur-si-non).

L'URL Rewriting

L'URL Rewriting (un article viendra prochainement) permet une gestion beaucoup plus souple des URLs, une petite sécurité supplémentaire, et beaucoup de soucis en moins !L'utilisation des include_once (et require_once) permet d'éviter la double inclusion de code (librairies, configurations, etc...)

Les guillemets simples et doubles

Les guillemets simples sont à préférer aux guillemets doubles dans de nombreux cas. Ce n'est pas une réelle question de rapidité, mais plus de facilité pour vous :
Je rappelle qu'il existe deux manières d'afficher (par exemple) une chaîne de caractères :
echo "Bonjour toi !"; ou echo 'Bonjour toi !';
La différence majeure est que les guillemets doubles permettent d'inclure des variables :
echo "Bonjour "; echo $nom; echo ", ça va ?"; est similaire à echo "Bonjour $nom, ça va ?"; et affichera "Bonjour Camille, ça va ?" si la variable $nom contient Camille.
Au contraire, vous serez obligés de concaténer les morceaux avec les guillemets simples :
echo "Bonjour ",$nom,", ça va ?";
Dans ce cas, les guillemets doubles sont largement préférables, car ils vous permettent de mieux écrire et visualiser vos chaînes formatées, et je vous encourage vivement à les utiliser dans le cas où vous avez des variables à inclure.
Cependant, dans le cas où par exemple vous affichez du HTML, avec de nombreux attributs pour vos balises, je pense qu'il est préférable d'utiliser les guillemets simple pour éviter d'antislasher tous les guillemets doubles du HTML (mais il faudra alors antislasher les guillemets simples). Je m'explique (vous en avez sans doute grand besoin) :
Pour afficher <div class="volante" id="boite_volante"><bold title="Vous avez un nouveau message"><a href="messages.html" title="Afficher ces 2 nouveaux messages">2 Nouveaux Messages !</a></bold></div>, il vaut mieux écrire
echo '<div class="volante" id="boite_volante"><bold title="Vous avez
un nouveau message"><a href="messages.html" title="Afficher ces 2
nouveaux messages">2 Nouveaux Messages
!</a></bold></div>';
que
echo "<div class=\"volante\" id=\"boite_volante\"><bold title=\"Vous avez
un nouveau message\"><a href=\"messages.html\" title=\"Afficher ces 2
nouveaux messages\">2 Nouveaux Messages
!</a></bold></div>";
De même, j'utilise généralement des fonctions du type message($message) appelées par exemple comme message('Une erreur vient de se produire !!"); (enfin celui-ci j'aime pas trop m'en servir :P). Ici l'utilisation de guillemets simples est plus utile.
En règle générale, il vaut mieux faire comme bon vous semble, pour avoir le moins d'anti-slashs à placer dans vos chaînes de caractères.

Journalisation des évènements

Depuis la première version de mon jeu, je journalise les évènements se produisant dans le jeu, au niveau du PHP, comme par exemple, la connexion d'un joueur, chacune de ses actions (construire, attaquer, message...) ainsi que les données techniques (synchronisation de tel ou tel fichier, intrusion, etc...).
Une simple fonction gère cela :

// Enregistrement des actions
function logue($txt){
    // $fic = @file_get_contents(_LOG); // Le fichier n'existe pas forcément
    $txt = '['.date('d/m-H:i').'] '.$txt;
    $fichier = fopen(_LOG,'a+');
    fputs($fichier, "\n$txt");
    fclose($fichier);
}

// Nettoyage des logs
function nettoielogs(){
    unlink(_LOG);
    logue('Réinitialisation du log...');
}


Où _LOG contient l'adresse interne du fichier.
Ce fichier est envoyé toutes les 24H sur mon adresse mail personnelle et termine dans un dossier spécialisé.

Les raccourcis, les astuces diverses

Connaissez-vous file_get_contents, ou file_put_contents ?
Savez-vous chronométrer le temps d'exécution d'une page, d'une action particulière ?
Un simple appel à microtime et une petite soustraction suffit !

Les optimisations SQL

Ca, c'est un gros chapitre ! J'ai récemment écouté quelqu'un qui comptait stocker ses templates HTML (tout le code HTML autour du contenu) dans une base de données, et qui voulait les récupérer pour chaque page. Il ne connaissait pas la fonction include.
Mon hébergement chez Olympe m'a fait comprendre qu'il était utile - et même important - d'optimiser le volume de données transitant sur le réseau, notamment pour les requêtes SQL.
En utilisant des requêtes plus complètes, plus poussées, on peut faire transiter le strict minimum, et ainsi alléger grandement le pauvre serveur SQL d'Olympe-Network votre propre serveur SQL.
  • Tout d'abord, l'étoile (astérisque) est à proscrire des requêtes :
SELECT * FROM `utilisateurs` est mauvais pour plusieurs raisons :
  1. Vous récupérez TOUS les champs correpondants, même si vous n'en avez pas besoin. C'est souvent près d'1Ko de données par requête par enregistrement, soit beaucoup en 24H pour l'ensemble des enregistrements de l'ensemble de vos bases sur l'ensemble de vos pages appelées par l'ensemble de vos visiteurs... Vous suivez ? ^^
  2. Vous ne connaissez pas la structure de la table que vous appelez ! Généralement je préfère avoir le nom des champs que j'utilise, ça me permet de coder beaucoup plus rapidement.
  • Pour compter le nombre d'enregistrements (nombre de messages, de joueurs, etc...) pensez à utiliser SELECT COUNT(*) AS nb FROM `utilisateurs` qui renvoie pour nb le nombre d'enregistrements (non nuls).
  • Vous pouvez effectuer des sélections croisées, pour cela je ne peux que vous indiquer l'excellent article sur le site du zéro

Voilà, là je suis un peu en manque d'idées, je compléterai sûrement cet article si des choses me reviennent.
J'espère que tout ceci pourra vous être utile =D

TPE : Le piratage informatique

Aujourd'hui, nous avons passé l'oral du TPE, qui compte je vous le rappelle pour le baccalauréat coefficient 2.
Dans notre groupe de quatre, nous avons choisi le piratage informatique comme thème, et joins à ce message le dossier et la présentation que nous avons réalisés.

Au programme : le Hacking (histoire, grands noms, types de hackers), puis les injections SQL, réalisée par Victor B., le phishing, réalisé par Patrick E., puis la Bruteforce, réalisé par Victor A., et pour finir, la faille DNS, que j'ai moi-même présenté.
Nous avons effectuée une démonstration de bruteforce sur les ordinateurs de la salle, où l'examinateur rentrait un mot de passe numérique à 4 chiffres sur le serveur, et un autre ordinateur client le bruteforçait en moins de 4 secondes =D

Le dossier n'est peut-être pas parfait, mais j'espère qu'il pourra vous aider, vous éclairer, ou au moins vous faire découvrir le piratage informatique =D

Télécharger le dossier (PDF)
Télécharger la présentation (Présentation OpenDocument)