[{"data":1,"prerenderedAt":934},["ShallowReactive",2],{"article_list_cryptography_":3},[4,540],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"publishDate":11,"tags":12,"excerpt":10,"body":16,"_type":531,"_id":532,"_source":533,"_file":534,"_stem":535,"_extension":536,"author":537},"/avogan/2012-07/salt-2","2012-07",false,"","What Your Users Don't Know (Part 2)","In my last post we saw that what your users don't know can hurt them. In other words, how securely you handle your users' private data behind the scenes can have profound implications both for your business and your users' well being. To put it bluntly, it's bad for your business to be publicly shamed over your handling of sensitive data, and it's bad for your users to have their bank accounts pilfered -- those being some of the worse case scenarios.","2012-07-26",[13,14,15],"cryptography","security","series",{"type":17,"children":18,"toc":528},"root",[19,35,53,58,66,71,91,99,104,112,117,222,227,232,240,245,253,258,273,298,303,395,400,405,410,418,423,467,477,485,500,508,513,518,523],{"type":20,"tag":21,"props":22,"children":23},"element","p",{},[24,27,33],{"type":25,"value":26},"text","In my last post we saw that what your users don't know ",{"type":20,"tag":28,"props":29,"children":30},"strong",{},[31],{"type":25,"value":32},"can",{"type":25,"value":34}," hurt them. In other words, how securely you handle your users' private data behind the scenes can have profound implications both for your business and your users' well being. To put it bluntly, it's bad for your business to be publicly shamed over your handling of sensitive data, and it's bad for your users to have their bank accounts pilfered -- those being some of the worse case scenarios.",{"type":20,"tag":21,"props":36,"children":37},{},[38],{"type":20,"tag":39,"props":40,"children":41},"em",{},[42,44,51],{"type":25,"value":43},"(This is part 2 of a series on web security; see ",{"type":20,"tag":45,"props":46,"children":48},"a",{"href":47},"/search/user:avogan/what/your/users/don't/know",[49],{"type":25,"value":50},"the whole series",{"type":25,"value":52},").",{"type":20,"tag":21,"props":54,"children":55},{},[56],{"type":25,"value":57},"So today I'd like to resume our discussion of secure password storage. Let's put our black hat back on, and see what we can break.",{"type":20,"tag":21,"props":59,"children":60},{},[61],{"type":20,"tag":28,"props":62,"children":63},{},[64],{"type":25,"value":65},"Bad Assumptions",{"type":20,"tag":21,"props":67,"children":68},{},[69],{"type":25,"value":70},"I'll start with the easiest case. Sometimes developers assume that as long as their database is safely hidden behind a firewall and an ordinary web server, then it's OK to store everything in plaintext. But this is not true. There are many ways data can leak from your production database, including:",{"type":20,"tag":72,"props":73,"children":74},"ul",{},[75,81,86],{"type":20,"tag":76,"props":77,"children":78},"li",{},[79],{"type":25,"value":80},"performing a SQL injection attack through your website",{"type":20,"tag":76,"props":82,"children":83},{},[84],{"type":25,"value":85},"digging through a backup or archive of your database -- therefore as long as you don't create backups, you're safe*",{"type":20,"tag":76,"props":87,"children":88},{},[89],{"type":25,"value":90},"gaining access to the server file system, e.g. through telnet/SSH/RDP",{"type":20,"tag":21,"props":92,"children":93},{},[94],{"type":20,"tag":39,"props":95,"children":96},{},[97],{"type":25,"value":98},"* That was a joke.",{"type":20,"tag":21,"props":100,"children":101},{},[102],{"type":25,"value":103},"It is a good rule of thumb that you should design your database with the assumption that malicious users may gain unrestricted access to it at some point. But even if that happens, you should be prepared to breathe a sigh of (slightly nervous) relief, knowing that they still won't be able to use the information maliciously. That doesn't mean you have to encrypt everything, but you should definitely encrypt anything sensitive, such as credit card numbers, passwords, and so on.",{"type":20,"tag":21,"props":105,"children":106},{},[107],{"type":20,"tag":28,"props":108,"children":109},{},[110],{"type":25,"value":111},"Naïve Hashing",{"type":20,"tag":21,"props":113,"children":114},{},[115],{"type":25,"value":116},"A natural first step is to perform a one-way encryption, or \"hash\" on passwords so they are no longer readable in the database. Here's an example:",{"type":20,"tag":118,"props":119,"children":120},"table",{},[121,140],{"type":20,"tag":122,"props":123,"children":124},"thead",{},[125],{"type":20,"tag":126,"props":127,"children":128},"tr",{},[129,135],{"type":20,"tag":130,"props":131,"children":132},"th",{},[133],{"type":25,"value":134},"UserName",{"type":20,"tag":130,"props":136,"children":137},{},[138],{"type":25,"value":139},"PasswordHash",{"type":20,"tag":141,"props":142,"children":143},"tbody",{},[144,158,171,184,196,209],{"type":20,"tag":126,"props":145,"children":146},{},[147,153],{"type":20,"tag":148,"props":149,"children":150},"td",{},[151],{"type":25,"value":152},"JSmith",{"type":20,"tag":148,"props":154,"children":155},{},[156],{"type":25,"value":157},"0xF49A",{"type":20,"tag":126,"props":159,"children":160},{},[161,166],{"type":20,"tag":148,"props":162,"children":163},{},[164],{"type":25,"value":165},"AJones",{"type":20,"tag":148,"props":167,"children":168},{},[169],{"type":25,"value":170},"0x923E",{"type":20,"tag":126,"props":172,"children":173},{},[174,179],{"type":20,"tag":148,"props":175,"children":176},{},[177],{"type":25,"value":178},"PHensley",{"type":20,"tag":148,"props":180,"children":181},{},[182],{"type":25,"value":183},"0x14D0",{"type":20,"tag":126,"props":185,"children":186},{},[187,192],{"type":20,"tag":148,"props":188,"children":189},{},[190],{"type":25,"value":191},"MRandolph",{"type":20,"tag":148,"props":193,"children":194},{},[195],{"type":25,"value":157},{"type":20,"tag":126,"props":197,"children":198},{},[199,204],{"type":20,"tag":148,"props":200,"children":201},{},[202],{"type":25,"value":203},"DGrove",{"type":20,"tag":148,"props":205,"children":206},{},[207],{"type":25,"value":208},"0xE8DB",{"type":20,"tag":126,"props":210,"children":211},{},[212,217],{"type":20,"tag":148,"props":213,"children":214},{},[215],{"type":25,"value":216},"RSchneider",{"type":20,"tag":148,"props":218,"children":219},{},[220],{"type":25,"value":221},"0x551E",{"type":20,"tag":21,"props":223,"children":224},{},[225],{"type":25,"value":226},"Do you see any problems with the above? OK, ignore the fact that the hashes are very small numbers. This is just pseudo-data for illustration.",{"type":20,"tag":21,"props":228,"children":229},{},[230],{"type":25,"value":231},"Observant readers will notice that one of the hashes occurs more than once (JSmith and MRandolph). Did you catch that? This is one of the problems with storing password hashes in your database - it's still very easy to see which users chose the same password. Remember, users won't protect themselves, and a surprising number of users may have a password of \"12345\" or \"password\" (or \"Password1\", just to anticipate and refute a well-intentioned, but ultimately insufficient attempt to solve this problem via a more strict password selection process).",{"type":20,"tag":21,"props":233,"children":234},{},[235],{"type":20,"tag":28,"props":236,"children":237},{},[238],{"type":25,"value":239},"Beware the Dictionary",{"type":20,"tag":21,"props":241,"children":242},{},[243],{"type":25,"value":244},"An even deeper problem here is that a hashing scheme like the above is susceptible to a dictionary attack using a large, pre-calculated collection of hashes of common passwords. All it takes is one successful match to positively identify the hashing scheme used, and then start doing damage.",{"type":20,"tag":21,"props":246,"children":247},{},[248],{"type":20,"tag":28,"props":249,"children":250},{},[251],{"type":25,"value":252},"Don't Follow This Recipe",{"type":20,"tag":21,"props":254,"children":255},{},[256],{"type":25,"value":257},"So we have to make sure the hashes we store are unique. We don't want an attacker to be able to recognize any of them. To do this, people use what's called a \"salt\" to make the output more random.",{"type":20,"tag":21,"props":259,"children":260},{},[261],{"type":20,"tag":45,"props":262,"children":266},{"href":263,"rel":264},"https://artandlogic.com/wp-content/uploads/2012/07/potatohash1.jpg",[265],"nofollow",[267],{"type":20,"tag":268,"props":269,"children":272},"img",{"alt":270,"src":271,"title":270},"Image of Potato Hash","/avogan/2012-07/img/potatohash.jpg",[],{"type":20,"tag":21,"props":274,"children":275},{},[276,281,283],{"type":20,"tag":39,"props":277,"children":278},{},[279],{"type":25,"value":280},"A well-salted hash.",{"type":25,"value":282}," ",{"type":20,"tag":39,"props":284,"children":285},{},[286,288,296],{"type":25,"value":287},"(",{"type":20,"tag":45,"props":289,"children":293},{"href":290,"rel":291,"title":292},"http://www.flickr.com/photos/tavallai/7583913946/in/photostream/",[265],"Flickr photo source",[294],{"type":25,"value":295},"photo by Tavallai",{"type":25,"value":297},", CC BY-ND 2.0)",{"type":20,"tag":21,"props":299,"children":300},{},[301],{"type":25,"value":302},"The salt is just a random number, and you can combine it with the hash process to get a more random looking output. Here's how one person did that, showing the same data from the table above, but with salt included. Pay special attention to the JSmith and MRandolph records, as before:",{"type":20,"tag":118,"props":304,"children":305},{},[306,320],{"type":20,"tag":122,"props":307,"children":308},{},[309],{"type":20,"tag":126,"props":310,"children":311},{},[312,316],{"type":20,"tag":130,"props":313,"children":314},{},[315],{"type":25,"value":134},{"type":20,"tag":130,"props":317,"children":318},{},[319],{"type":25,"value":139},{"type":20,"tag":141,"props":321,"children":322},{},[323,335,347,359,371,383],{"type":20,"tag":126,"props":324,"children":325},{},[326,330],{"type":20,"tag":148,"props":327,"children":328},{},[329],{"type":25,"value":152},{"type":20,"tag":148,"props":331,"children":332},{},[333],{"type":25,"value":334},"0x832B F49A",{"type":20,"tag":126,"props":336,"children":337},{},[338,342],{"type":20,"tag":148,"props":339,"children":340},{},[341],{"type":25,"value":165},{"type":20,"tag":148,"props":343,"children":344},{},[345],{"type":25,"value":346},"0xDC3C 923E",{"type":20,"tag":126,"props":348,"children":349},{},[350,354],{"type":20,"tag":148,"props":351,"children":352},{},[353],{"type":25,"value":178},{"type":20,"tag":148,"props":355,"children":356},{},[357],{"type":25,"value":358},"0x09A4 14D0",{"type":20,"tag":126,"props":360,"children":361},{},[362,366],{"type":20,"tag":148,"props":363,"children":364},{},[365],{"type":25,"value":191},{"type":20,"tag":148,"props":367,"children":368},{},[369],{"type":25,"value":370},"0xF12E F49A",{"type":20,"tag":126,"props":372,"children":373},{},[374,378],{"type":20,"tag":148,"props":375,"children":376},{},[377],{"type":25,"value":203},{"type":20,"tag":148,"props":379,"children":380},{},[381],{"type":25,"value":382},"0x4C88 E8DB",{"type":20,"tag":126,"props":384,"children":385},{},[386,390],{"type":20,"tag":148,"props":387,"children":388},{},[389],{"type":25,"value":216},{"type":20,"tag":148,"props":391,"children":392},{},[393],{"type":25,"value":394},"0xA4FC 551E",{"type":20,"tag":21,"props":396,"children":397},{},[398],{"type":25,"value":399},"Whoa, wait a minute. Do you see a new problem here? It is true that each \"PasswordHash\" attribute is now unique since a random number has been prefixed. And the developers may run a few simple SQL queries and verify that no two PasswordHash attributes are the same, and pat themselves on the back. But that is merely a dangerous illusion, and this is a very wrong implementation.",{"type":20,"tag":21,"props":401,"children":402},{},[403],{"type":25,"value":404},"Since you have your black hat on, it will be obvious to you that a hacker can just bit mask out the part of the hash they are interested in, sort of like performing a Python slice, and exclude the \"salt\" that way. So this erroneous approach has no meaningful improvement over the \"simple hash only\" example above.",{"type":20,"tag":21,"props":406,"children":407},{},[408],{"type":25,"value":409},"Note: I actually found this erroneous approach used in an online source code recipe, several years ago. Of course it seems absurd to us under this analysis, but somebody thought it was correct enough to post on a source code recipe sharing website, so I think the point was worth belaboring here a bit.",{"type":20,"tag":21,"props":411,"children":412},{},[413],{"type":20,"tag":28,"props":414,"children":415},{},[416],{"type":25,"value":417},"A Better Seasoned Recipe",{"type":20,"tag":21,"props":419,"children":420},{},[421],{"type":25,"value":422},"Here is a more accurate description of how to use salt to protect your password hashes:",{"type":20,"tag":424,"props":425,"children":426},"ol",{},[427,432,437,452,457,462],{"type":20,"tag":76,"props":428,"children":429},{},[430],{"type":25,"value":431},"In context of creating a new user record or updating a password, receive the plaintext password from the user.",{"type":20,"tag":76,"props":433,"children":434},{},[435],{"type":25,"value":436},"Generate a strongly random number to use as the unique salt value for this user record.",{"type":20,"tag":76,"props":438,"children":439},{},[440,442,450],{"type":25,"value":441},"Compute: a hash of (the salt concatenated with a hash of (the salt concatenated with the password)). Here's ",{"type":20,"tag":45,"props":443,"children":447},{"href":444,"rel":445,"title":446},"https://blog.whitehatsec.com/hash-length-extension-attacks/",[265],"Hash length extension attacks",[448],{"type":25,"value":449},"a link",{"type":25,"value":451}," explaining why this expression needs to be this complex, instead of simply a hash of the concatenation.",{"type":20,"tag":76,"props":453,"children":454},{},[455],{"type":25,"value":456},"Store both the result of that final hash calculation, and also the unmodified salt value in your database in the user record. I personally like to concatenate the final hash and salt and store them in the same record attribute, just to be obscure. But that doesn't really matter. Note that it's OK to store the salt in plaintext; in fact, that's required.",{"type":20,"tag":76,"props":458,"children":459},{},[460],{"type":25,"value":461},"After we are finished with this process, deliberately forget the plaintext password. Depending on the overall architecture, maybe it is was provided by the user, or maybe it was system-generated and must now be emailed to the user. Either way, it must not be stored as plaintext.",{"type":20,"tag":76,"props":463,"children":464},{},[465],{"type":25,"value":466},"Later on, when the user enters their user name and password to log in, look up the record by user name, then repeat the calculation in step 3 using the salt value retrieved from the record. The resulting hash (using the password being entered) can be checked against the stored hash from the database to determine if the user entered the right password.",{"type":20,"tag":21,"props":468,"children":469},{},[470,472],{"type":25,"value":471},"If you do it right, your database's hashes should now look totally scrambled and inscrutable to an unauthorized reader.  ",{"type":20,"tag":39,"props":473,"children":474},{},[475],{"type":25,"value":476},"(Reminder to self: next time must avoid blogging while hungry, especially about recipes for salted hashes and ordering crackable things as scrambled.)",{"type":20,"tag":21,"props":478,"children":479},{},[480],{"type":20,"tag":28,"props":481,"children":482},{},[483],{"type":25,"value":484},"Choosing a Hash Function",{"type":20,"tag":21,"props":486,"children":487},{},[488,490,498],{"type":25,"value":489},"This blog post is not a complete treatment of the subject of server side salting and password hashing. Another important decision is what hashing function to use. A hash function in this context is typically chosen to be both secure and slow. But it's also a moving target, as cryptographic standards must continually respond to rapidly advancing cracking capabilities. Somehow the very weak MD5 ended up as an entrenched hash function in very widespread use in the 90's and aughts. (Boy, was that a short sighted mistake.) Many people are still using SHA-1, which wasn't considered horrible just a few years ago, but really needs to be deprecated in favor of stronger options. I recommend you spend some time reading the links in ",{"type":20,"tag":45,"props":491,"children":495},{"href":492,"rel":493,"title":494},"http://stackoverflow.com/questions/2549988/whats-the-recommended-hashing-algorithm-to-use-for-stored-passwords",[265],"Stack Overflow discussion of what hash function to choose",[496],{"type":25,"value":497},"this discussion",{"type":25,"value":499}," to get a sense of what's out there. I'm deliberately not giving a specific recommendation here, in order to reinforce that there is actually more than one possible answer, and also that the \"correct answers\" periodically change.",{"type":20,"tag":21,"props":501,"children":502},{},[503],{"type":20,"tag":28,"props":504,"children":505},{},[506],{"type":25,"value":507},"Don't Try This At Home",{"type":20,"tag":21,"props":509,"children":510},{},[511],{"type":25,"value":512},"My final advice may sound like it contradicts everything I've said thus far. But that's OK. :)",{"type":20,"tag":21,"props":514,"children":515},{},[516],{"type":25,"value":517},"If at all possible, you should not come up with your own implementation of these approaches. Ideally, you should rely on your framework libraries to provide high level, complete authentication and authorization wrappers. Or if not, at least you should find and integrate a secure implementation from a trusted source. I already showed you how some guy on the internet thought they were salting their passwords, but got it totally wrong; so definitely don't trust random stuff you google up.",{"type":20,"tag":21,"props":519,"children":520},{},[521],{"type":25,"value":522},"Be very cautious if you're not a cryptographic expert. Certainly, you can and should learn the basics of information security, and use your knowledge to audit and critique your own systems. But any implementations you deploy to production should be from trusted frameworks, or at least closely follow standard industry best practices. Don't assemble something off the top of your head, or it will almost certainly be cryptographically weak and defective.",{"type":20,"tag":21,"props":524,"children":525},{},[526],{"type":25,"value":527},"Thanks for reading! You can take the black hat off now. Hopefully this was informative for somebody; if you have any questions or want to share your own advice for readers, I'd love to read your comments below.",{"title":8,"searchDepth":529,"depth":529,"links":530},3,[],"markdown","content:avogan:2012-07:salt-2.md","content","avogan/2012-07/salt-2.md","avogan/2012-07/salt-2","md",{"user":538,"name":539},"avogan","Andrew Vogan",{"_path":541,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":542,"description":543,"publishDate":544,"tags":545,"excerpt":543,"body":546,"_type":531,"_id":930,"_source":533,"_file":931,"_stem":932,"_extension":536,"author":933},"/avogan/2012-07/salt","What Your Users Don't Know (Part 1)","What's wrong with this code?","2012-07-13",[13,14,15],{"type":17,"children":547,"toc":928},[548,552,565,766,781,804,811,826,838,844,849,855,897,903,917,922],{"type":20,"tag":21,"props":549,"children":550},{},[551],{"type":25,"value":543},{"type":20,"tag":21,"props":553,"children":554},{},[555],{"type":20,"tag":39,"props":556,"children":557},{},[558,560,564],{"type":25,"value":559},"(This is part 1 of a series on web security; see ",{"type":20,"tag":45,"props":561,"children":562},{"href":47},[563],{"type":25,"value":50},{"type":25,"value":52},{"type":20,"tag":566,"props":567,"children":571},"pre",{"className":568,"code":569,"language":570,"meta":8,"style":8},"language-csharp shiki shiki-themes github-light github-dark","bool IsValidUser(string userName, string password)\n{\n   string sql = string.Format(\"SELECT 1 FROM person \" + \n                              \"WHERE userName = '{0}' AND password = '{1}'\",\n                              userName,\n                              password);\n   return ExecuteQuery(sql).Rows > 0;\n}\n","csharp",[572],{"type":20,"tag":573,"props":574,"children":575},"code",{"__ignoreMap":8},[576,628,637,690,704,713,722,757],{"type":20,"tag":577,"props":578,"children":581},"span",{"class":579,"line":580},"line",1,[582,588,594,599,604,609,614,618,623],{"type":20,"tag":577,"props":583,"children":585},{"style":584},"--shiki-default:#D73A49;--shiki-dark:#F97583",[586],{"type":25,"value":587},"bool",{"type":20,"tag":577,"props":589,"children":591},{"style":590},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[592],{"type":25,"value":593}," IsValidUser",{"type":20,"tag":577,"props":595,"children":597},{"style":596},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[598],{"type":25,"value":287},{"type":20,"tag":577,"props":600,"children":601},{"style":584},[602],{"type":25,"value":603},"string",{"type":20,"tag":577,"props":605,"children":606},{"style":590},[607],{"type":25,"value":608}," userName",{"type":20,"tag":577,"props":610,"children":611},{"style":596},[612],{"type":25,"value":613},", ",{"type":20,"tag":577,"props":615,"children":616},{"style":584},[617],{"type":25,"value":603},{"type":20,"tag":577,"props":619,"children":620},{"style":590},[621],{"type":25,"value":622}," password",{"type":20,"tag":577,"props":624,"children":625},{"style":596},[626],{"type":25,"value":627},")\n",{"type":20,"tag":577,"props":629,"children":631},{"class":579,"line":630},2,[632],{"type":20,"tag":577,"props":633,"children":634},{"style":596},[635],{"type":25,"value":636},"{\n",{"type":20,"tag":577,"props":638,"children":639},{"class":579,"line":529},[640,645,650,655,660,665,670,674,680,685],{"type":20,"tag":577,"props":641,"children":642},{"style":584},[643],{"type":25,"value":644},"   string",{"type":20,"tag":577,"props":646,"children":647},{"style":590},[648],{"type":25,"value":649}," sql",{"type":20,"tag":577,"props":651,"children":652},{"style":584},[653],{"type":25,"value":654}," =",{"type":20,"tag":577,"props":656,"children":657},{"style":584},[658],{"type":25,"value":659}," string",{"type":20,"tag":577,"props":661,"children":662},{"style":596},[663],{"type":25,"value":664},".",{"type":20,"tag":577,"props":666,"children":667},{"style":590},[668],{"type":25,"value":669},"Format",{"type":20,"tag":577,"props":671,"children":672},{"style":596},[673],{"type":25,"value":287},{"type":20,"tag":577,"props":675,"children":677},{"style":676},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[678],{"type":25,"value":679},"\"SELECT 1 FROM person \"",{"type":20,"tag":577,"props":681,"children":682},{"style":584},[683],{"type":25,"value":684}," +",{"type":20,"tag":577,"props":686,"children":687},{"style":596},[688],{"type":25,"value":689}," \n",{"type":20,"tag":577,"props":691,"children":693},{"class":579,"line":692},4,[694,699],{"type":20,"tag":577,"props":695,"children":696},{"style":676},[697],{"type":25,"value":698},"                              \"WHERE userName = '{0}' AND password = '{1}'\"",{"type":20,"tag":577,"props":700,"children":701},{"style":596},[702],{"type":25,"value":703},",\n",{"type":20,"tag":577,"props":705,"children":707},{"class":579,"line":706},5,[708],{"type":20,"tag":577,"props":709,"children":710},{"style":596},[711],{"type":25,"value":712},"                              userName,\n",{"type":20,"tag":577,"props":714,"children":716},{"class":579,"line":715},6,[717],{"type":20,"tag":577,"props":718,"children":719},{"style":596},[720],{"type":25,"value":721},"                              password);\n",{"type":20,"tag":577,"props":723,"children":725},{"class":579,"line":724},7,[726,731,736,741,746,752],{"type":20,"tag":577,"props":727,"children":728},{"style":584},[729],{"type":25,"value":730},"   return",{"type":20,"tag":577,"props":732,"children":733},{"style":590},[734],{"type":25,"value":735}," ExecuteQuery",{"type":20,"tag":577,"props":737,"children":738},{"style":596},[739],{"type":25,"value":740},"(sql).Rows ",{"type":20,"tag":577,"props":742,"children":743},{"style":584},[744],{"type":25,"value":745},">",{"type":20,"tag":577,"props":747,"children":749},{"style":748},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[750],{"type":25,"value":751}," 0",{"type":20,"tag":577,"props":753,"children":754},{"style":596},[755],{"type":25,"value":756},";\n",{"type":20,"tag":577,"props":758,"children":760},{"class":579,"line":759},8,[761],{"type":20,"tag":577,"props":762,"children":763},{"style":596},[764],{"type":25,"value":765},"}\n",{"type":20,"tag":21,"props":767,"children":768},{},[769,771,779],{"type":25,"value":770},"Any jokester who says \"it looks fine to me\" will be sent to the ",{"type":20,"tag":45,"props":772,"children":776},{"href":773,"rel":774,"title":775},"http://starwars.wikia.com/wiki/Spice_Mines_of_Kessel",[265],"Spice Mines of Kessel",[777],{"type":25,"value":778},"spice mines of Kessel",{"type":25,"value":780},". But I think for observant readers, a couple of critical security errors will practically jump off the screen:",{"type":20,"tag":72,"props":782,"children":783},{},[784,799],{"type":20,"tag":76,"props":785,"children":786},{},[787,789,797],{"type":25,"value":788},"User inputs are being concatenated directly into a SQL query string, risking a ",{"type":20,"tag":45,"props":790,"children":794},{"href":791,"rel":792,"title":793},"http://en.wikipedia.org/wiki/SQL_injection",[265],"SQL Injection Attack",[795],{"type":25,"value":796},"SQL injection",{"type":25,"value":798}," attack.",{"type":20,"tag":76,"props":800,"children":801},{},[802],{"type":25,"value":803},"Passwords are stored in plaintext, exposing users to further harm in the event the database is accessed.",{"type":20,"tag":805,"props":806,"children":808},"h1",{"id":807},"in-the-real-world",[809],{"type":25,"value":810},"In the Real World",{"type":20,"tag":21,"props":812,"children":813},{},[814,816,824],{"type":25,"value":815},"If you keep up with the news, you may have seen that no less an internet giant than Yahoo may have been guilty of both of the above mistakes, leading to ",{"type":20,"tag":45,"props":817,"children":821},{"href":818,"rel":819,"title":820},"http://arstechnica.com/security/2012/07/yahoo-service-hacked/",[265],"Yahoo security breach",[822],{"type":25,"value":823},"a breach of 435K user credentials",{"type":25,"value":825},". This is a disaster for any company that safeguards private user data.",{"type":20,"tag":21,"props":827,"children":828},{},[829,831,836],{"type":25,"value":830},"You can't depend on users to protect themselves; somewhere out in the world today is a person who set up the same password on a Justin Bieber mailing list website and also their online banking. So if even a fluffy, non-important website drops the ball, users may see their bank accounts emptied. That's probably an extreme worst case, though there is a whole range of other mischief you don't want to enable either. The point is, ",{"type":20,"tag":28,"props":832,"children":833},{},[834],{"type":25,"value":835},"your users trust you, and they don't know what you are doing with their data.",{"type":25,"value":837}," Be worthy of their trust.",{"type":20,"tag":805,"props":839,"children":841},{"id":840},"get-in-the-mindset",[842],{"type":25,"value":843},"Get In the Mindset",{"type":20,"tag":21,"props":845,"children":846},{},[847],{"type":25,"value":848},"A technique I find helpful for programmers is to temporarily put down their shining knight helmet and try on a black hat for size. Put yourself in a creative trouble-maker frame of mind. Ask the question: how could somebody compromise my website and harm my users or my organization?",{"type":20,"tag":805,"props":850,"children":852},{"id":851},"sql-injection",[853],{"type":25,"value":854},"SQL Injection",{"type":20,"tag":21,"props":856,"children":857},{},[858,860,868,870,878,879,886,888,895],{"type":25,"value":859},"In the case of the code snippet at the top of this article, it is trivially easy for a hacker to enter a user name or password that will disrupt the query. By placing complex SQL expressions (perhaps cleverly including SQL comments) in the user name and password inputs, you can easily ",{"type":20,"tag":45,"props":861,"children":865},{"href":862,"rel":863,"title":864},"http://xkcd.com/327/",[265],"Little Bobby Tables",[866],{"type":25,"value":867},"delete or insert",{"type":25,"value":869}," records. What would totally frustrate your hacking attempts, though, is if the programmer used their framework's feature for formal query value parameters (see for ",{"type":20,"tag":45,"props":871,"children":875},{"href":872,"rel":873,"title":874},"http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.parameters.aspx",[265],"framework query params",[876],{"type":25,"value":877},"C#",{"type":25,"value":613},{"type":20,"tag":45,"props":880,"children":883},{"href":881,"rel":882,"title":874},"http://stackoverflow.com/questions/3410455/how-do-i-use-sql-parameters-with-python",[265],[884],{"type":25,"value":885},"Python",{"type":25,"value":887},", etc). And that, of course, is the correct answer here. Never concatenate user inputs into SQL strings. It's even a bad idea to write your own sanitizing functions, because you'll probably get a detail wrong, even if you're a smart person. Just use the framework, all the time, or else use a mainstream ",{"type":20,"tag":45,"props":889,"children":893},{"href":890,"rel":891,"title":892},"http://en.wikipedia.org/wiki/Object-relational_mapping",[265],"ORM",[894],{"type":25,"value":892},{"type":25,"value":896}," that hides the query string safely out of your code's sight.",{"type":20,"tag":805,"props":898,"children":900},{"id":899},"plaintext-passwords",[901],{"type":25,"value":902},"Plaintext Passwords",{"type":20,"tag":21,"props":904,"children":905},{},[906,908,915],{"type":25,"value":907},"The other major problem is the use of plaintext password storage. That's obviously a really bad thing if the database is accessed, and many programmers are aware that if you just do a one-way hash of the password, it's more secure. You do lose the ability to remind the user of their password, but it's usually OK, because they can just create a new one when needed via an email challenge/response. But even hashing itself doesn't cut it. If you are not ",{"type":20,"tag":45,"props":909,"children":913},{"href":910,"rel":911,"title":912},"http://en.wikipedia.org/wiki/Salt_(cryptography)",[265],"salting",[914],{"type":25,"value":912},{"type":25,"value":916}," your password hashes, your users are exposed to unacceptable risk.",{"type":20,"tag":21,"props":918,"children":919},{},[920],{"type":25,"value":921},"I'll take a closer look at how to salt passwords in a future post. It really sounds more difficult than it is. So to summarize: do some reading, and also occasionally just put yourself in a black hat frame of mind, and you can work out how to close common loopholes.",{"type":20,"tag":923,"props":924,"children":925},"style",{},[926],{"type":25,"value":927},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":529,"depth":529,"links":929},[],"content:avogan:2012-07:salt.md","avogan/2012-07/salt.md","avogan/2012-07/salt",{"user":538,"name":539},1780330276914]