Source code security review challenge at Confoo 2012 - Montreal (confoo.ca)
The audience was challenged in attempting to spot security vulnerabilities in a series of source code examples.
1. Trouvez la faille!
Antonio Fontes / Confoo 2012 - Montréal
Notice 1: cette présentation contient des références à Common Weakness Enumeration:
http://cwe.mitre.org/data/index.html
Notice 2: aucun chat n'a été maltraité durant la préparation de cette séance.
Notice 3: cette présentation contient des références au
document "Test your Security IQ", par M. Howard et B. Sullivan
Notice 4: Un grand merci à Sébastien pour ses idées & propositions!
2. • Règles de jeu:
– Lire l’exemple de code affiché à l'écran
– Trouver la ou les éléments pouvant constituer un risque pour la sécurité du S.I.
– Tenir une comptabilité analytique des points obtenus!
02.03.2012 Confoo Conference 2012 - Antonio Fontes 2
3. Antonio Fontes
Genève (Suisse)
Consultant indépendant Infosécurité logicielle:
Sécurité des applications web
Visibilité et gestion du risque sur Internet
Formation / accompagnement durant les projets de développement
Bulletin d'information"cybermenaces et sécurité Internet":
http://cddb.ch
OWASP:
Membre du Comité - OWASP Suisse
Leader - OWASP Genève
A propos du conférencier…
02.03.2012 Confoo Conference 2012 - Antonio Fontes 3
4. • Le site d’actualité permet la création de comptes personnels, la
publication de réactions à l’actualité, l’échange de messages entre
membres.
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 4
5. Envoi des éléments
d'authentification en
clair.
• Le site d’actualité permet la création de comptes personnels, la
publication de réactions à l’actualité, l’échange de messages entre
membres.
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 5
7. function printFile($username,$filename){
//read file into string
$file = file_get_contents($filename);
if ($file && isOwnerOf($username,$filename)){
echo $file;
return true;
} else {
echo 'You are not authorized to view this file';
}
return false;
}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 7
8. function printFile($username,$filename){
//read file into string
$file = file_get_contents($filename);
Le chargement du
if ($file && isOwnerOf($username,$filename)){
fichier a lieu avant
echo $file;
le contrôle d'accès.
return true;
} else {
echo 'You are not authorized to view this file';
}
return false;
}
• 1 point
– Identification du risque pour la disponibilité du S.I.
02.03.2012 Confoo Conference 2012 - Antonio Fontes 8
9. protected void Page_Load(object sender, EventArgs e)
{
string lastLogin = Request["LastLogin"];
if (String.IsNullOrEmpty(lastLogin)) {
HttpCookie lastLoginCookie = new HttpCookie("LastLogin",
DateTime.Now.ToShortDateString());
lastLoginCookie.Expires = DateTime.Now.AddYears(1);
Response.Cookies.Add(lastLoginCookie);
}
else {
Response.Write("Welcome back! You last logged in on " +
lastLogin);
Response.Cookies["LastLogin"].Value =
DateTime.Now.ToShortDateString();
}
}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 9
10. protected void Page_Load(object sender, EventArgs e)
{
string lastLogin = Request["LastLogin"];
if (String.IsNullOrEmpty(lastLogin)) { Appel vers la
HttpCookie lastLoginCookie = new HttpCookie("LastLogin",
collection parente
DateTime.Now.ToShortDateString());
"Request"
lastLoginCookie.Expires = DateTime.Now.AddYears(1);
Response.Cookies.Add(lastLoginCookie);
}
else {
Response.Write("Welcome back! You last logged in on " +
lastLogin);
Response.Cookies["LastLogin"].Value Transfert du contenu
=
DateTime.Now.ToShortDateString(); vers le client, sans
} encodage approprié.
}
• 1 point
– Cas de type "XSS" (Cross-site scripting)
02.03.2012 Confoo Conference 2012 - Antonio Fontes 10
11. $role = $_COOKIES['role'];
if (!$role) {
$role = getRole('user');
if ($role) {
// save the cookie to send out in future responses
setcookie("role", $role, time()+60*60*2);
} else {
ShowLoginScreen();
die("n");
}
}
if ($role == 'Reader') {
DisplayMedicalHistory($_POST['patient_ID']);
} else {
die("You are not Authorized to view this recordn");
}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 11
12. $role = $_COOKIES['role'];
if (!$role) { Absence de contrôle
$role = getRole('user'); d'intégrité du
if ($role) {
cookie.
// save the cookie to send out in future responses
setcookie("role", $role, time()+60*60*2);
} else {
Contournement du
ShowLoginScreen(); mécanisme
die("n"); d'authentification
}
}
if ($role == 'Reader') { Contournement du
DisplayMedicalHistory($_POST['patient_ID']); contrôle d'accès
} else {
die("You are not Authorized to view this recordn");
}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 12
13. • 1 + 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 13
14. Transfert de
confiance à un
tiers
• 1 + 1 + 1 points
– 1 point: identification du risque d'injection de contenu par un tiers
– 1 point: identification du risque sur la confidentialité (fuite des referrers)
– 1 point: identification du risque de déni de service sur le tiers
02.03.2012 Confoo Conference 2012 - Antonio Fontes 14
15. • 1 + 1 + 1 points
– Faire attention aux recommandations sur le web: elles vont souvent à
l'encontre de la sécurité et visent à faciliter la collecte de données par des tiers.
– Vérifier qui est l'auteur d'une recommandation de codage.
02.03.2012 Confoo Conference 2012 - Antonio Fontes 15
16. // API flag, output JSON if set
$json = $_GET['json'];
$username = $_GET['user'];
if($json)
{
$record = getUserRecord($username);
echo(json_encode($record));
} else {
$record = getUserRecord($username);
foreach($record as $fieldName => $fieldValue) {
// never disclose user email addresses to the public (privacy req.)
if(!($fieldName == "email_address"))
renderToHtmlTable ($fieldName,$fieldValue);
}}}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 16
17. // API flag, output JSON if set
$json = $_GET['json']; Dans le cas json,
$username = $_GET['user'];
l'adresse email n'est
if($json)
{ plus protégée contre les
$record = getUserRecord($username); fuites.
echo(json_encode($record));
} else {
$record = getUserRecord($username);
foreach($record as $fieldName => $fieldValue) {
// never disclose user email addresses to the public (privacy req.)
if(!($fieldName == "email_address"))
renderToHtmlTable ($fieldName,$fieldValue);
}}}
• 1 point
– Identification de la fuite d'adresses email
02.03.2012 Confoo Conference 2012 - Antonio Fontes 17
18. byte[] GetKey(UInt32 keySize)
{
byte[] key = null;
try
{
key = new byte[keySize];
RNGCryptoServiceProvider.Create().GetBytes(key);
}
catch (Exception e)
{
Math.Random r = new Math.Random();
r.NextBytes(key);
}
return key;
}
1 + 1 + 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 18
19. byte[] GetKey(UInt32 keySize)
{
byte[] key = null;
try
{
key = new byte[keySize];
RNGCryptoServiceProvider.Create().GetBytes(key);
}
catch (Exception e)
{ Exception générique?
Math.Random r = new Math.Random();
r.NextBytes(key);
} Math.Random?
Fail-safe?
return key;
}
1 + 1 + 1 point
– Exception générique: privilégier l'exception typée
– Principe de conception "Fail-safe": le code n'échoue pas en haute sécurité
– La classe Math.Random ne fournit pas d'entropie de niveau cryptographique
02.03.2012 Confoo Conference 2012 - Antonio Fontes 19
21. private decimal? lookupPrice(XmlDocument doc)
{
string query = @"//products/product[id/text()='" + Request["itemId"] +
"']/price"
XmlNode node = doc.SelectSingleNode(query); Validation?
if (node == null)
return null;
else
return(Convert.ToDecimal(node.InnerText));
}
• 1 point
– Injection de type Xpath (il n'y a pas que des injections SQL!!)
– Marche aussi sur: commandes système, LDAP, APIs ORM, etc.
02.03.2012 Confoo Conference 2012 - Antonio Fontes 21
22. public class MySessionIDManager : System.Web.Session State.SessionIDManager
{
private static object lockObject = new object();
public override string CreateSessionID(HttpContext context)
{
lock (lockObject)
{
Int32? lastSessionId = (int?)context.Application ["LastSessionId"];
if (lastSessionId == null)
lastSessionId = 1;
else
lastSessionId++;
context.Application["LastSessionId"] = lastSessionId;
return lastSessionId.ToString();
}}}
• 1 + 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 22
23. public class MySessionIDManager : System.Web.Session State.SessionIDManager
{
private static object lockObject = new object();
public override string CreateSessionID(HttpContext context)
{
lock (lockObject) Multi-serveur?
{
Int32? lastSessionId = (int?)context.Application ["LastSessionId"];
if (lastSessionId == null)
lastSessionId = 1; ID de session prédictibles
else
lastSessionId++;
context.Application["LastSessionId"] = lastSessionId;
return lastSessionId.ToString();
}}}
• 1 + 1 point
– Identification de l'identifiant de session prédictible
– Collision des identifiants de session si le serveur est répliqué!
02.03.2012 Confoo Conference 2012 - Antonio Fontes 23
24. bool login(SqlConnection connection, out string errorMessage)
{
string uname = Request.Form["username"];
string pword = Request.Form["password"];
SqlCommand selectUserAndPassword = new SqlCommand( "SELECT pwd FROM Users WHERE uname =
@username", connection);
selectUserAndPassword.Parameters.Add( new SqlParameter("@username", uname));
string validPassword = (string)selectUserAndPassword.ExecuteScalar();
if (validPassword == null)
{
// the user doesn't exist in the database
errorMessage = "The username is invalid.";
return false;
}
else if (validPassword != pword)
{
// the given password doesn't match
errorMessage = "The password is incorrect.";
return false;
}
else
{
// success
Bug #9 errorMessage = String.Empty;
return true;
}
} 1 + 1 + 1 points
– 1 point + bonus point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 24
25. bool login(SqlConnection connection, out string errorMessage)
{ Requête paramétrée, ça, c'est juste!
string uname = Request.Form["username"];
string pword = Request.Form["password"];
SqlCommand selectUserAndPassword = new SqlCommand( "SELECT pwd FROM Users WHERE uname =
@username", connection);
selectUserAndPassword.Parameters.Add( new SqlParameter("@username", uname));
string validPassword = (string)selectUserAndPassword.ExecuteScalar();
if (validPassword == null)
{
// the user doesn't exist in the database Rapatriement inutile du
errorMessage = "The username is invalid.";
return false;
mot de passe!
}
else if (validPassword != pword)
{ Stockage du mot de passe en
// the given password doesn't match
clair.
errorMessage = "The password is incorrect.";
return false;
}
else Message d'erreur variable lorsque le
{
// success
login ou le mdp est faux (fuite)
Bug #9 errorMessage = String.Empty;
return true;
}
} 1 + 1 + 1 points
– 1 point + bonus point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 25
26. // SilverLight code module review
bool verifyCode(string discountCode)
{
// We store the hash of the secret code instead of the plaintext of the secret code for security.
// We hash the incoming value and compare it against the stored hash.
byte[] codeHash = SecurityUtils.ComputeHash(discountCode, "MD5");
byte[] secretCode = new byte[] { 116, 46, 130, 122, 36, 234, 158, 125, 163, 122, 157, 186, 64, 142, 51,
153, 113, 79, 1, 42 };
// This should never happen, but we check it anyway
if (codeHash.Length != secretCode.Length)
return false;
// perform an element-by-element comparison of the arrays
for (int i = 0; i < codeHash.Length; i++)
{
if (codeHash[i] != secretCode[i])
return false; // the hashes don't match
} // all the elements match, so the strings match
// the discount code is valid, inform the server
WebServiceSoapClient client = new WebServiceSoapClient();
client.ApplyDiscountCode();
return true; 1 + 1 + 2 points
}
02.03.2012 Confoo Conference 2012 - Antonio Fontes 26
27. // SilverLight code module review
bool verifyCode(string discountCode) Algorithme déconseillé (+1)
{
// We store the hash of the secret code instead of the plaintext of the secret code for security.
// We hash the incoming value and compare it against the stored hash.
byte[] codeHash = SecurityUtils.ComputeHash(discountCode, "MD5");
byte[] secretCode = new byte[] { 116, 46, 130, 122, 36, 234, 158, 125, 163, 122, 157, 186, 64, 142, 51,
153, 113, 79, 1, 42 };
// This should never happen, but we check it anyway
A-t-on besoin d'un
if (codeHash.Length != secretCode.Length) sel? (seed) +1
return false;
// perform an element-by-element comparison of the arrays
for (int i = 0; i < codeHash.Length; i++)
{
if (codeHash[i] != secretCode[i]) Défense côté client
return false; // the hashes don't match
} // all the elements match, so the strings match totalement inutile! (+2
points)
// the discount code is accepted, inform the server
WebServiceSoapClient client = new WebServiceSoapClient();
client.ApplyDiscountCode();
return true; 1 + 1 + 2 points
}
02.03.2012 Confoo Conference 2012 - Antonio Fontes 27
28. • 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 28
29. Injection SQL
(absence de
validation)
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 29
31. $MessageFile = "messages/messages.out";
if ($_GET["action"] == "NewMessage") {
$name = $_GET["name"];
$message = $_GET["message"];
$handle = fopen($MessageFile, "a+");
fwrite($handle, "<b>$name</b> says '$message'<hr>n");
Et s'il y a du script
fclose($handle);
client?
echo "Message Saved!<p>n";
} else if ($_GET["action"] == "ViewMessages") {
include($MessageFile); include == eval()
} ?
• 1 + 1 point
– Identification de l'injection de code côté-serveur (via la fonction "include")
– Identification de l'injection de code côté-client (via l'affichage du fichier)
02.03.2012 Confoo Conference 2012 - Antonio Fontes 31
32. // anti SQL-injection filter for user input
string SQliProtect(string formValue)
{
string tmp = formValue.ToUpperCase();
return(tmp.Replace("SELECT", "").Replace("INSERT
", "").Replace("UPDATE", "").Replace("UNION","")
.Replace("BENCHMARK, "").Replace("--
", "").Replace("OR
1=1", "").Replace("DROP", "").Replace("@@version
", "").Replace("WAITFOR", "").Replace("OUTFILE",
"")
...
return(tmp)
}
• 1 point
02.03.2012 Confoo Conference 2012 - Antonio Fontes 32
33. // anti SQL-injection filter for user input
string SQliProtect(string formValue)
{
string tmp = formValue.ToUpperCase();
return(tmp.Replace("SELECT", table" ?
"DRDROPOP "").Replace("INSERT
", "").Replace("UPDATE", "").Replace("UNION","")
.Replace("BENCHMARK, "").Replace("--
", "").Replace("OR
1=1", "").Replace("DROP", "").Replace("@@version
", "").Replace("WAITFOR", "").Replace("OUTFILE",
"")
...
return(tmp)
}
• 1 point
– Identification de la technique de contournement du filtre
02.03.2012 Confoo Conference 2012 - Antonio Fontes 33
34. <?
$reqId = 0;
if(isset($_GET[“account_id"]))
$reqId = (int)(htmlentities($_GET[“account_id"]));
if($reqId == 0)
{
// no account selected, show the list of authorized accounts
$sql = " SELECT * FROM accounts a "
." INNER JOIN account_managers am "
." ON a.id = am.account_id "
." WHERE am.manager_id = ".$currentUserID;
echo(RenderHTMLTable($sql));
} else {
// docucment is clicked -> show statement
$sql = " SELECT * FROM accounts a WHERE a.id = ".$reqId;
RenderHTMLAccount($sql);
}
• 2 points
02.03.2012 Confoo Conference 2012 - Antonio Fontes 34
35. <?
$reqId = 0;
if(isset($_GET[“account_id"]))
$reqId = (int)(htmlentities($_GET[“account_id"]));
if($reqId == 0)
Références internes?
{
// no account selected, show the list of authorized accounts
$sql = " SELECT * FROM accounts a "
Contrôle d’accès. Bien!
." INNER JOIN account_managers am "
." ON a.id = am.account_id "
." WHERE am.manager_id = ".$currentUserID;
echo(RenderHTMLTable($sql));
} else {
// docucment is clicked -> show statement
$sql = " SELECT * FROM accounts a WHERE a.id = ".$reqId;
RenderHTMLAccount($sql);
} Mais ici?
• 2 points
– Identification de l'exposition de références internes
– Identification de l'absence de contrôle d'accès lors de l'affichage du document
02.03.2012 Confoo Conference 2012 - Antonio Fontes 35
36. bool verifyPassword(string formPwd, int userId)
{
byte[] formHash = Tools.ComputeSHA1Hash(formPwd);
byte[] dbHash = B64.Decode(User.GetPasswordHash(userId));
if (formHash.Length != dbHash.Length)
return false;
for (int i = 0; i < formHash.Length; i++)
{
if (formHash[i] != dbHash[i])
return false; // the hashes don't match
}
// we are still here, so the passwords matched
return true;
}
• 1+1+1+1+1 points
02.03.2012 Confoo Conference 2012 - Antonio Fontes 36
37. bool verifyPassword(string formPwd, int userId)
{ Sel?
byte[] formHash = Tools.ComputeSHA1Hash(formPwd);
byte[] dbHash = B64.Decode(User.GetPasswordHash(userId));
Rappatriement du mode
de passe?
if (formHash.Length != dbHash.Length) Algorithme fort?
return false;
for (int i = 0; i < formHash.Length; i++)
{
if (formHash[i] != dbHash[i]) Longueurs variables
return false; // the hashes don't match
possibles?
}
// we are still here, so the passwords matched
return true;
}
Stratégie Fail safe?
– Absence probable de sel +1
– S'interroger sur la nature de l'algorithme choisi +1
– Rapatriement inutile du mot de passe +1
– Identification de l'absence de mécanisme fail-safe +1
– Présence de signes indiquant une méconnaissance des fonctions de hachage +1
02.03.2012 Confoo Conference 2012 - Antonio Fontes 37
38. Quel a été votre score?
20 points et plus: changez de carrière, ça embauche!
De 13 à 19 points: Très bien! Vous vous y intéressez et ça
se voit. Vous devriez songer à appliquer vos
connaissances aussi au code de vos collègues si ce n'est
déjà fait, pensez aussi à joindre une association ou
communauté traitant du sujet. Sensibilisez les gens
autour de vous!
De 8 à 12 points: Vous avez clairement identifié la notion
de risque dans le code mais vous ne savez probablement
pas encore où regarder. Il faut à présent consolider les
bases simplement en…pratiquant!
De 4 à 7 points: Demandez à vos chefs de vous faire
suivre un cours!
Moins de 4 points: Si vous êtes développeur(ou
développeuse), votre code est probablement dangereux
pour la survie de l'organisation. Assurez-vous qu'il soit
relu par une personne expérimentée dans l'attente
d'avoir un peu plus d'expérience!
02.03.2012 Confoo Conference 2012 - Antonio Fontes 38
39. Common Weakness Enumeration database:
http://cwe.mitre.org/data/index.html
OWASP Secure Coding Checklist:
https://www.owasp.org/index.php/OWASP_Secure_Coding_Practices_-
_Quick_Reference_Guide
OWASP ASVS:
https://www.owasp.org/index.php/Category:OWASP_Application_Security_Ve
rification_Standard_Project
Merci de votre attention!
Si vous souhaitez me contacter:
– antonio.fontes@L7securite.ch ou @starbuck3000
– Newsletter: http://cddb.ch
02.03.2012 Confoo Conference 2012 - Antonio Fontes 39