256 21 1MB
English Pages 201 Year 2005
php|architect’s Guide to PHP Security
Written by Ilia Alshanetsky, one of the foremost experts on PHP security in the world, php|architect’s Guide to PHP Security focuses on providing you with all the tools and knowledge you need to both secure your existing applications and writing new systems with security in mind. This book gives you a step-by-step guide to each security-related topic, providing you with real-world examples of proper coding practices and their implementation in PHP in an accurate, concise and complete way. ¸ Provides techniques applicable to any version of PHP, including 4.x and 5.x ¸ Includes a step-by-step guide to securing your applications ¸ Includes a comprehensive coverage of security design ¸ Teaches you how to defend yourself from hackers ¸ Shows you how to distract hackers with a “tar pit” to help you fend off potential attacks
php|architect’s Guide to PHP Security
With the number of security flaws and exploits discovered and released every day constantly on the rise, knowing how to write secure and reliable applications is become more and more important every day.
php|architect’s Guide to PHP Security A Step-by-step Guide to Writing Secure and Reliable PHP Applications Ilia Alshanetsky
NanoBooks are excellent, in-depth resources created by the publishers of php|architect (http://www.phparch.com), the world’s premier magazine dedicated to PHP professionals.
US Canada UK (net)
From the publishers of
$32.99 $47.99 £18.99
Shelve under PHP/Web Development/Internet Programming
7.50 x 9.25
Ilia Alshanetsky
NanoBooks focus on delivering high-quality content with in-depth analysis and expertise, centered around a single, well-defined topic and without any of the fluff of larger, more expensive books.
.424
Foreword by Rasmus Lerdorf
7.50 x 9.25
PHP|ARCHITECT’S
GUIDE TO
PHP SECURITY by Ilia Alshanetsky
php|architect’sGuidetoSecurity
Contents Copyright © 2005 Ilia Alshanetsky – All Rights Reserved Book and cover layout, design and text Copyright © 2005 Marco Tabini & Associates, Inc. – All Rights Reserved First Edition: First Edition ISBN 0-9738621-0-6 Produced in Canada Printed in the United States No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical reviews or articles.
Disclaimer
Although every effort has been made in the preparation of this book to ensure the accuracy of the information contained therein, this book is provided “as-is” and the publisher, the author(s), their distributors and retailers, as well as all affiliated, related or subsidiary parties take no responsibility for any inaccuracy and any and all damages caused, either directly or indirectly, by the use of such information. We have endeavoured to properly provide trademark information on all companies and products mentioned in this book by the appropriate use of capitals. However, we cannot guarantee the accuracy of such information. Marco Tabini & Associates, The MTA logo, php|architect, the php|architect logo, NanoBook and NanoBook logo are trademarks or registered trademarks of Marco Tabini & Associates Inc.
BulkCopies
Marco Tabini & Associates, Inc. offers trade discounts on purchases of ten or more copies of this book. For more information, please contact our sales offices at the address or numbers below.
Credits Written by
Ilia Alshanetsky
Published by Marco Tabini & Associates, Inc. 28 Bombay Ave. Toronto, ON M3H 1B7 Canada
Edited By
Martin Streicher
Technical Reviewers
Marco Tabini
Layout and Design
Arbi Arzoumani
Managing Editor
Emanuela Corso
(416) 630-6202 (877) 630-6202 toll free within North America [email protected] / www.phparch.com Marco Tabini, Publisher
AbouttheAuthor
Ilia Alshanetsky is the principal of Advanced Internet Designs Inc., a company that specializes in security auditing, performance analysis and application development. He is the author of FUDforum (http://fudforum.org), a highly popular, Open Source bulletin board focused on providing the maximum functionality at the highest level of security and performance. Ilia is also a Core PHP Developer who authored or co-authored a series of extensions, including SHMOP, PDO, SQLite, GD and ncurses. An active member of PHP’s Quality Assurance Team, he is responsible for hundreds of bug fixes, as well as a sizable number of performance tweaks and features. Ilia is a regular speaker at PHP-related conferences worldwide and can often be found teaching the Zend Certification Training and Professional PHP Development courses that he has written for php|architect. He is also a prolific author, with articles for PHP|Architect, International PHP Magazine, Oracle Technology Network, Zend.com and others to his name. Ilia maintains an active blog at http://ilia.ws, filled tips and tricks on how to get the most out of PHP.
Tomyparents, Whoareandhavebeenmypillarofsupport
Contents
Foreword • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 13 Introduction • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 17
1InputValidation
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 21
TheTroublewithInput AnAlternativetoRegisterGlobals:Superglobals TheConstantSolution The$_REQUESTTrojanHorse ValidatingInput ValidatingNumericData LocaleTroubles StringValidation ContentSizeValidation WhiteListValidation
22 25 25 27 28 28 29 30 34 36
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
8
Contents
BeingCarefulwithFileUploads ConfigurationSettings FileInput FileContentValidation AccessingUploadedData FileSize TheDangersofMagicQuotes MagicQuotesNormalization MagicQuotes&Files ValidatingSerializedData ExternalResourceValidation
37 37 38 39 41 42 43 44 46 47 49
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
2Cross-SiteScriptingPrevention TheEncodingSolution HandlingAttributes HTMLEntities&Filters ExclusionApproach HandlingValidAttributes URLAttributeTricks XSSviaEnvironmentVariables IPAddressInformation ReferringURL ScriptLocation MoreSevereXSSExploits Cookie/SessionTheft FormDataTheft ChangingPageContent
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 53
54 54 56 60 63 64 66 66 67 67 68 69 70 71
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
3SQLInjection
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 73
MagicQuotes PreparedStatements NoMeansofEscape TheLIKEQuandary SQLErrorHandling
74 75 77 78 79
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Contents
AuthenticationDataStorage DatabasePermissions MaintainingPerformance QueryCaching
80 83 83 85
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
4PreventingCodeInjection
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 87
PathValidation UsingFullPaths AvoidingDynamicPaths PossibleDangersofRemoteFileAccess ValidatingFileNames SecuringEval DynamicFunctionsandVariables CodeInjectionviaPCRE
88 88 89 89 91 94 95 97
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
5CommandInjection
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 101
ResourceExhaustionviaCommandInjection ThePATHExploit HiddenDangers ApplicationBugsandSettingLimits PHPExecutionProcess
102 104 105 106 108
•••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
6SessionSecurity
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 113
Sessions&Cookies ManintheMiddleAttacks EncryptiontotheRescue! ServerSideWeakness URLSessions SessionFixation SurvivingAttacks NativeProtectionMechanism User-landSessionTheft ExpiryTimeTricks
114 114 115 115 115 117 117 118 119 119
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
9
10
Contents
ServerSideExpiryMechanisms MixingSecurityandConvenience SecuringSessionStorage SessionIDRotation IPBasedValidation BrowserSignature ReferrerValidation UserEducation
120 121 122 126 128 129 130 131
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
7SecuringFileAccess
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 135
TheDangersof“Worldwide”Access SecuringReadAccess PHPEncoders ManualEncryption OpenBaseDirectory SecuringUploadedFiles SecuringWriteAccess FileSignature SafeMode AnAlternatePHPExecutionMechanism CGI FastCGI SharedHostingWoes FileMasking
136 137 137 138 139 140 140 142 143 144 145 145 146 147
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
8SecuritythroughObscurity WordsofCaution HideYourFiles ObscureCompiledTemplates TransmissionObfuscation ObscureFieldNames FieldNameRandomization UsePOST ContentCompression
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 153
153 154 156 158 158 159 160 161
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Contents
HTMLComments SoftwareIdentification
161 162
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
9SandboxesandTarPits
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 165
MisdirectAttackswithSandboxes BuildingaSandbox TrackingPasswords IdentifytheSourceoftheAttackSource FindRoutingInformation LimitationswithIPAddresses SmartCookieTricks RecordtheReferringURL CaptureallInputData BuildaTarPit
166 166 167 169 170 171 173 173 174 176
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
10SecuringYourApplications
• • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 179
EnableVerboseErrorReporting ReplacetheUsageofRegisterGlobals Avoid$_REQUEST DisableMagicQuotes TrytoPreventCross-SiteScripting(XSS) ImproveSQLSecurity PreventCodeInjection Discontinueuseofeval() MindYourRegularExpressions WatchOutforDynamicNames MinimizetheUseofExternalCommands ObfuscateandPrepareaSandbox
180 180 181 182 183 183 184 185 185 185 186 187
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
•••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••
••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Index • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 189
11
Foreword
W
hen I started the PHP project years ago, the goal was to develop a tool for solvingtheWebproblembyremovingbarriersandsimplifyingtheinteractionbetween thewebserverandthehundredsofsub-systemsrequiredtosolveawidevarietyof problems.Overtheyears,Ithinkwehaveachievedthat.PHPhasallowedpeoplewithallsorts ofdifferentbackgroundstoputtheirideasontheWeb.Tome,thisisthesuccessofPHPand whatkeepsmemotivatedtocontinueworkingonit. WithallthesuccessofPHP,Iwillbethefirsttoadmitthatthereareareaswherewehaven’t doneaverygoodjobofeducatingandprovidingpeoplewiththetoolstheyneed.Securityis atthetopofthatlist—wehavesimplifiedaccesstothings,providedalanguageandasetof functionstodoanythinganybodycouldwanttodo,butwehavenotprovidedmuchintheway oftoolsorguidanceaimedathelpingpeoplewritesecureapplications.Wehavebeencontent withbeingonparwithotherenvironmentsinthisrespect,whileinalmostallotherareaswe havestrivedtobebetter. Securityisnoteasy.Peoplehavetounderstandtheirsystemswelltoknowwheresecurity
14
Foreword
issuesarelikelytoappear,andtheyhavetoremembertoactuallycheck.Likeasmallholein aballoon,onemissedsecuritycheckwillbursttheirapplication.PHPprovidesanumberof tools to help people address security problems, but without a good understanding of when andhowtoapplythem,theyaren’tveryuseful.Wewillthereforeneedacombinedefforttotry tocollectivelyachievebettersecurity.Usersneedtobecomebettereducated,andweneedto providebettertools. Recently,anumberofautomatedsecurityscannershaveappeared.Primarily,thesedetect cross-sitescriptingproblems,buttheyalsocatchtheoccasionalSQLinjection.Themainthing Ihavegottenoutofseeingtheresultsofthesescansisthatthewebapplicationsecurityproblemispervasiveanddoesn’tcarewhatlanguageanapplicationiswrittenin. Afirststepisforpeopletoreadabooklikethisonethatoutlinescommonsecurityproblemsinwebapplications.And,whilethesolutionspresentedhereareallPHP-basedusingthe toolsprovidedbyPHP,mostoftheproblemsapplytoanylanguageandenvironment.People should use this book to solve their PHP-based web application security problems, but they shouldalsousethisbooktotakeahigher-levellookatsecurityeverywhereinalltheirsystems. Cross-sitescriptingandSQLinjectionarejusttwoexamplesofinadvertentlyexposingasubsystemtoend-userdatainput.Whatothersub-systemsareinyourarchitecture?Aretheyappropriatelyprotectedagainstdirectuserinput? Thereisnosecuritypanaceahere.—nobodywilleverbeabletoprovideone.Theclosest wewillgetistotrytoimprovetheoverallawarenessoftheseissuesandtoprovidebettertools forsolvingthem.Havingastraightforwardarchitecturethatiseasytounderstandmakesthis easierforPHPusers.Havingabooklikethisonyourbookshelfmakesiteveneasier.
RasmusLerdorf
Introduction
S
inceitsinceptionin1995,PHPhasbecomethescriptinglanguageofchoiceforavast majorityofwebdevelopers,poweringover22milliondomainnamesrunningonover1.3 milliondistinctservers.PHP’srapidgrowthcanbeattributedtoitssimplicity,itseverevolvingcapabilities,anditsexcellentperformance. Unfortunately,thesamequalitiesthathavemadePHPsopopularhavealsolulledmany developersintoasenseofcomplacency,leadingthemtoneglectaveryimportantaspectof development:security. WhenPHPwasstillyoungandusedprimarilyforhobbyistapplications,securitywasn’t anutmostconcern.Backthen,a“serious”intrusionmightleavesomenastyHTMLinaguestbook. Now, however, when PHP powers shopping carts, registration systems, and corporate webportals,insecurecodecanhaveveryseriousconsequencesforasite,thesite’sowners,and thesite’susers. Thisbookhastwogoals:toexplainthecommontypesofsecurityshortcomingsthatplague PHPapplicationsandtoprovidesimpleandefficientremediestothoseproblems.Ingeneral,
18
Introduction
beingawareofrisksismorethanhalfthebattle.ImplementingasolutioninPHPisusually quitestraightforward.Andthat’simportant:ifimplementingsecurityisprohibitivelydifficult, fewdeveloperswillbother.
1
Input Validation
P
racticallyallsoftwareapplicationsdependonsomeformofuserinputtocreateoutput.Thisisespeciallytrueforwebapplications,wherejustaboutalloutputdependson whattheuserprovidesasinput. Firstandforemost,youmustrealizeandacceptthatanyuser-supplieddataisinherently unreliableandcannotbetrusted.BythetimeinputreachesPHP,it’spassedthroughtheuser’s browser,anynumberofproxyserversandfirewalls,filteringtoolsonyourserver,andpossibly otherprocessingmodules.Anyoneofthose“hops”haveanopportunity—beitintentionalor accidental—tocorruptoralterthedatainsomeunexpectedmanner.Andbecausethedataultimatelyoriginatesfromauser,theinputcouldbecoercedortailoredoutofcuriosityormalice toexploreorpushthelimitsofyourapplication.Itisabsolutelyimperativetovalidatealluser inputtoensureitmatchestheexpectedform. There’sno“silverbullet”thatvalidatesallinput,nouniversalsolution.Infact,anattemptto deviseabroadsolutiontendstocauseasmanyproblemsasitsolves—asPHP’s“magicquotes” willsoondemonstrate.Inawell-written,secureapplication,eachinputhasitsownvalidation
22
Input Validation
routine,specificallytailoredtotheexpecteddataandthewaysit’sused.Forexample,integers canbeverifiedviaafairlysimplecastingoperation,whilestringsrequireamuchmoreverbose approachtoaccountforallpossiblevalidvaluesandhowtheinputisutilized. Thischapterfocusesonthreethings: •Howtoidentifyinputmethods.(Understandinghowexternaldatamakesitswayinto ascriptisessential.) •Howeachinputmethodcanbeexploitedbyanattacker. •Howeachformofinputcanbevalidatedtopreventsecurityproblems.
TheTroublewithInput Originally, PHP programmers accessed user-supplied data via the“register globals” mechanism.Usingregisterglobals,anyparameterpassedtoascriptismadeavailableasavariable with the same name as the parameter. For example, the URL script.php?foo=bar creates a variable$foowithavalueofbar. Whileregisterglobalsisasimpleandlogicalapproachtocapturingscriptparameters,it’s vulnerabletoaslewofproblemsandexploits. Oneproblemistheconflictbetweenincomingparameters.Datasuppliedtothescriptcan comefromseveralsources,includingGET,POST,cookies,serverenvironmentvariables,and systemenvironmentvariables,noneofwhichareexclusive.Hence,ifthesameparameteris suppliedbymorethanoneofthosesources,PHPisforcedtomergethedata,losinginformationintheprocess.Forexample,ifanidparameterissimultaneouslyprovidedinaPOSTrequestandacookie,oneofthevaluesischoseninfavoroftheother.Thisselectionprocessis calledamerge. Twophp.inidirectivescontroltheresultofthemerge:theoldergpc_orderandthenewer variables_order.Bothsettingsreflecttherelativepriorityofeachinputsource.Thedefaultorderforgpc_orderisGPC(forGET,POST,cookie,respectively),wherecookiehasthehighestpriority;thedefaultorderforvariables_orderisEGPCS(systemEnvironment,GET,POST,cookie,and Serverenvironment,respectively).Accordingtobothdefaults,ifparameteridissuppliedvia aGETandacookie,thecookie’svalueforidispreferred.Perhapsoddly,thedatamergeoccurs outsidethemilieuofthescriptitself,whichhasnoindicationthatanydatawaslost. Asolutiontothisproblemistogiveeachparameteradistinctprefixthatreflectsitsorigin. Forexample,parameterssentviaPOSTwouldhaveap_prefix.Butthistechniqueisonlyreliable inacontrolledenvironmentwhereallapplicationsfollowtheconvention.Fordistributableap-
Input Validation
plicationsthatworkinamultitudeofenvironments,thissolutionisbynomeansreliable. A more reliable but cumbersome solution uses $HTTP_GET_VARS, $HTTP_POST_VARS, and $HTTP_COOKIE_VARStoretainthedataforGET,POST,andcookie,respectively.Forexample,the expression$HTTP_GET_VARS[‘id’]referencestheidparameterassociatedwiththeGETportion oftherequest. However, while this approach doesn’t lose data and makes it very clear where data is coming from, the $HTTP_*_VARS variables aren’t global and using them from within func-
tions and methods makes for very tedious code. For instance, to import $HTTP_GET_VARS into the scope of a method or function, you must use the special $GLOBALS variable, as in $GLOBALS[‘HTTP_GET_VARS’], and to access the value of id, you must write the longwinded $GLOBALS[‘HTTP_GET_VARS’][‘id’]. Incomparison,thevariable$idcanbeimportedintothefunctionviathemuchsimpler (buterror-prone)$GLOBALS[‘id’].It’shardlysurprisingthatmanydeveloperschosethepath ofleastresistanceandusedthesimpler,butmuchlesssecureregisterglobalvariables.Indeed, thevulnerabilityofregisterglobalsultimatelyledtotheoptionbeingdisabledbydefault. Foraperspective,considerthefollowingcode:
if (is_authorized_user()) { $auth = TRUE; } if ($auth) { /* display content intended only for authorized users */ }
Whenenabled,registerglobalscreatesvariablestorepresentuserinputthatareotherwiseindistinguishablefromotherscriptvariables.So,ifascriptvariableisleftuninitialized,anenterprisingusercaninjectanarbitraryvalueintothatvariablebysimplypassingitviaaninput method. Intheinstanceabove,thefunctionis_authorized_user()determinesifthecurrentuser haselevatedprivilegesandassignsTRUEto$authifthat’sthecase.Otherwise,$authisleftuninitialized.Byprovidinganauthparameterviaanyinputmethod,theusercangainaccessto privilegedcontent. Theissueisfurthercompoundedbythefactthat,unlikeotherprogramminglanguages, uninitializedvariablesinsidePHParenotoriouslydifficulttodetect.Thereisno“strict”mode (asfoundinPerl)orcompilerwarnings(asfoundinC/C++)thatimmediatelyhighlightques-
23
24
Input Validation
tionableusage.TheonlywaytospotuninitializedvariablesinPHPistoelevatetheerrorreportingleveltoE_ALL.Buteventhen,aredflagisraisedonlyifthescripttriestouseanuninitializedvariable. InascriptinglanguagesuchasPHP,wherethescriptisinterpretedeachexecution,itisinefficientforthecompilertoanalyzethecodeforuninitializedvariables,soit’ssimplynotdone. However,theexecutorisawareofuninitializedvariablesandraisesnotices(E_NOTICE)ifyour errorreportinglevelissettoE_ALL.
# Inside PHP configuration error_reporting=E_ALL # Inside httpd.conf or .htacces for Apache # numeric values must be used php_value error_reporting 2047 # You can even change the error # reporting level inside the script itself error_reporting(E_ALL);
Whileraisingthereportingleveleventuallydetectsmostuninitializedvariables,itdoesn’tdetectallofthem.Forexample,PHPhappilyappendsvaluestoanonexistentarray,automatically creating the array if it doesn’t exist.This operation is quite common and unfortunately isn’t flagged.Nonetheless,itisverydangerous,asdemonstratedinthiscode:
# Assuming script.php?del_user[]=1&del_user[]=2 & register_globals=On $del_user[] = “95”; // add the only desired value foreach ($del_user as $v) { mysql_query(“DELETE FROM users WHERE id=”.(int)$v); }
Above,thelistofuserstoberemovedisstoredinsidethe$del_userarray,whichissupposed tobecreatedandinitializedbythescript.However,sinceregisterglobalsisenabled,$del_user isalreadyinitializedthroughuserinputandcontainstwoarbitraryvalues.Thevalue95isappendedasathirdelement.Theconsequence?Oneuserisintentionallyremovedandtwousers aremaliciouslyremoved.
Input Validation
Thereareonlytwowaystopreventthisproblem.Thefirstandarguablybestoneistoalwaysinitializeyourarrays,whichrequiresjustasinglelineofcode:
// initialize the array $del_user = array(); $del_user[] = “95”; // add the only desired value
Setting$del_usercreatesanewemptyarray,erasinganyinjectedvaluesintheprocess. Theothersolution,whichmaynotalwaysbeapplicable,istoavoidappendingvaluesto arraysinsidetheglobalscopeofthescriptwherevariablesbasedoninputmaybepresent.
AnAlternativetoRegisterGlobals:Superglobals Comparativelyspeaking,registerglobalsareprobablythemostcommoncauseofsecurityvulnerabilitiesinPHPapplications. It should hardly be surprising then that the developers of PHP deprecated register globalsinfavorofabetterinputaccessmechanism.PHP4.1introducedtheso-calledsuperglobal variables$_GET,$_POST,$_COOKIE,$_SERVER,and$_ENVtoprovideglobal,dedicatedaccessto individualinputmethodsfromanywhereinsidethescript.Superglobalsincreaseclarity,identifytheinputsource,andeliminatetheaforementionedmergingproblem.GiventhesuccessfuladoptionofsuperglobalsafterthereleaseofPHP4.1,PHP4.2disabledregisterglobalsby default. Alas,gettingridofregisterglobalswasn’tassimpleasthat.WhilenewinstallationsofPHP have register globals disabled, upgraded installations retain the setting in php.ini. Furthermore,manyhostingprovidersintentionallyenableregisterglobals,becausetheirusersdepend onlegacyorpoorly-writtenPHPapplicationsthatrelyonregisterglobalsforinputprocessing. Eventhoughregisterglobalswasdeprecatedyearsago,mostserversstillhaveitenabledandall applicationsneedtobedesignedwiththisinmind.
TheConstantSolution The use of constants provides very basic protection against register globals. Constants have tobecreatedexplicitlyviathedefine()functionandaren’taffectedbyregisterglobals(unless thenameparametertothedefinefunctionisbasedonavariablethatcouldbeinjectedbythe user).Here,theconstantauthreflectstheresultsofis_authorized_user():
25
26
Input Validation
define(‘auth’, is_authorized_user()); if (auth) { /* display content intended only for authorized users */ }
Asidefromtheaddedsecurity,constantsarealsoavailablefromallscopesandcannotbemodified.Onceaconstanthasbeenset,itremainsdefineduntiltheendoftherequest.Constants canalsobemadecase-insensitivebypassingdefine()athird,optionalparameter,thevalue TRUE,whichavoidsaccidentalaccesstoadifferentdatumcausedbycasevariance. Thatsaid,constantshaveoneproblematicfeaturethatstemsfromPHP’slackofstrictness: ifyoutrytoaccessanundefinedconstant,itsvalueisastringcontainingtheconstantname insteadofNULL(thevalueofallundefinedvariables).Asaresult,conditionalexpressionsthat testanundefinedconstantalwayssucceed,whichmakesitasomewhatdangeroussolution, especiallyiftheconstantsaredefinedinsideconditionalexpressionsthemselves.Forexample, considerwhathappenshereifthecurrentuserisnotauthorized:
if (is_authorized_user()) define(‘auth’, TRUE); if (auth) // will always be true, either Boolean(TRUE) or String(“auth”) /* display content intended only for authorized users */
Anotherapproachtothesameproblemistousetype-sensitivecomparison.AllPHPinputdata isrepresentedeitherasastringoranarrayofstringsif[]isusedintheparametername.Typesensitive comparisons always fail when comparing incompatible types such as string and Booleans.
if (is_authorized_user()) $auth = TRUE; if ($auth === TRUE) /* display content intended only for authorized users */
Type-sensitivecomparisonsvalidateyourdata.Andfortheperformance-mindeddeveloper, type-sensitivecomparisonsalsoslightlyimprovetheperformanceofyourapplicationbyafew
Input Validation
preciousmicroseconds,whichafterafewhundredsofthousandsoperationsadduptoasecond. Thebestwaytopreventregisterglobalsfrombecomingaproblemistodisabletheoption. However,becauseinputprocessingisdonepriortothescriptexecution,youcannotsimply useini_set()toturnthemoff.Youmustdisabletheoptioninphp.ini,httpd.conf,or.htaccess.Thelattercanbeincludedindistributableapplications,sothatyourprogramcanbenefit fromamoresecureenvironmentevenonserverscontrolledbysomeoneelse.Thatsaid,not everyonerunsApacheandnotallinstancesofApacheallowtheuseof.htaccesstospecify configurationdirectives,sostrivetowritecodethatisregisterglobals-safe.
The$_REQUESTTrojanHorse WhensuperglobalswereaddedtoPHP,aspecialsuperglobalwasaddedspecificallytosimplify thetransitionfromoldercode.The$_REQUESTsuperglobalcombinesthevaluesfromGET,POST, andcookiesintoasinglearrayforeaseofuse.ButasPHPoftendemonstrates,theroadtohell ispavedwithgoodintentions.Whilethe$_REQUESTsuperglobalcanbeconvenient,itsuffers fromthesamelossofdataproblemcausedwhenthesameparameterisprovidedbymultiple inputsources. Touse$_REQUESTsafely,youmustimplementchecksthroughothersuperglobalstouse theproperinputsource.Here,anidparameterprovidedbyacookieinsteadofGETorPOSTis removed.
# safe use of _REQUEST where only GET/POST are valid if (!empty($_REQUEST[‘id’]) && isset($_COOKIE[‘id’])) unset($_REQUEST[‘id’]);
But validating all of the input in a request is tedious, and negates the convenience of $_REQUEST.It’smuchsimplertojustusetheinputmethod-specificsuperglobalsinstead:
if (!empty($_GET[‘id’])) $id = $_GET[‘id’]; else if (!empty($_POST[‘id’])) $id = $_POST[‘id’]; else $id = NULL;
27
28
Input Validation
ValidatingInput Nowthatyou’veupdatedyourcodetoaccessinputdatainasafermanner,youcanproceed withtheactualgutsoftheapplication,right? Wrong! Justaccessingthedatainsafemannerishardlyenough.Ifyoudon’tvalidatethecontentof theinput,you’rejustasvulnerableasyouwerebefore. Allinputisprovidedasstrings,butvalidationdiffersdependingonhowthedataistobe used.Forinstance,youmightexpectoneparametertocontainnumericvaluesandanotherto adheretoacertainpattern.
ValidatingNumericData Ifaparameterissupposedtobenumeric,validatingitisexceptionallysimple:simplycastthe parametertothedesirednumerictype.
$_GET[‘product_id’] = (int) $_GET[‘product_id’]; $_GET[‘price’] = (float) $_GET[‘price’];
AcastforcesPHPtoconverttheparameterfromastringtoanumericvalue,ensuringthatthe inputisavalidnumber. Intheeventadatumcontainsonlynon-numericcharacters,theresultoftheconversion is0.Ontheotherhand,ifthedatumisentirelynumericorbeginswithanumber,thenumeric portionofthestringisconvertedtoyieldavalue.Innearlyallcasesthevalueof0isundesirable andasimpleconditionalexpressionsuchasif (!$value) {error handling}basedontype castvariablewillbesufficienttovalidatetheinput. Whencasting,besuretoselectthedesiredtype,sincecastingafloating-pointnumberto anintegerlosessignificantdigitsafterthedecimalpoint.Youshouldalwayscasttoafloatingpointnumberifthepotentialvalueoftheparameterexceedsthemaximumintegervalueofthe system.ThemaximumvaluethatcanbecontainedinaPHPintegerdependsonthebit-size ofyourprocessor.On32-bitsystems,thelargestintegerisamere2,147,483,647.Ifthestring “1000000000000000000”iscasttointeger,it’llactuallyoverflowthestoragecontainerresulting indataloss.Castinghugenumbersasfloatsstorestheminscientificnotation,avoidingtheloss ofdata.
Input Validation
echo (int)”100000000000000000”; // 2147483647 echo (float)”100000000000000000”; // float(1.0E+17)
While casting works well for integers and floating-point numbers, it does not handle hexadecimalnumbers(0xFF),octalnumbers(0755)andscientificnotation(1e10).Ifthesenumber formatsareacceptableinput,analternatevalidationmechanismisrequired. Theslowerbutmoreflexibleis_numeric()functionsupportsalltypesofnumberformats. ItreturnsaBooleanTRUEifthevalueresemblesanumberorFALSEotherwise.Forhexadecimal numbers,“digits”otherthan[0-9A-Fa-f]areinvalid.However,octalnumberscan(perhaps incorrectly)containanydigit[0-9].
is_numeric(“0xFF”); is_numeric(“0755”); is_numeric(“1e10”); is_numeric(“0xGG”); is_numeric(“0955”);
// // // // //
true true true false true
LocaleTroubles Althoughfloating-pointnumbersarerepresentedinmanywaysaroundtheworld,bothcastingandis_numeric()considerfloating-pointnumbersthatdonotuseaperiodasthedecimal pointasinvalid.Forexample,ifyoucast1,23asafloatyouget1;ifyouaskis_numeric(“1,23”), theanswerisFALSE.
(float)”1,23”; // float(1) is_numeric(“1,23”); // false
ThispresentsaproblemformanyEuropeanlocales,suchasFrenchandGerman,wherethe decimalseparatorisacommaandnotaperiod.But,asfarasPHPisconcerned,onlytheperiod canbeusedadecimalpoint.Thisistrueregardlessoflocalesettings,sochangingthelocalehas noimpactonthisbehavior.
29
30
Input Validation
setlocale(LC_ALL, “french”); echo (float) “9,99”; // 9 is_numeric(“9,99”); // false
Performance Tip
Casting is faster than is_numeric() because it requires no function calls. Additionally, casting returns a numericvalue,ratherthana“yes”or“no”answer.
Onceyou’vevalidatedeachnumericinput,there’sonemorestep:youmustreplaceeachinput withitsvalidatedvalue.Considerthefollowingexample:
# $_GET[‘del’] = “1; /* Muwahaha */ TRUNCATE users;” if ((int)$_GET[‘del’]) { mysql_query(“DELETE FROM users WHERE id=”.$_GET[‘del’]); }
Whilethestring$GET[‘del’]castssuccessfullytoaninteger(1),usingtheoriginaldatainjects additionalSQLintothequery,truncatingtheusertable.Oops! Thepropercodeisshownbelow:
if (($_GET[‘del’] = (int)$_GET[‘del’])) { mysql_query(“DELETE FROM users WHERE id=”.$_GET[‘del’]); } # OR if ((int)$_GET[‘del’]) { mysql_query(“DELETE FROM users WHERE id=”.(int)$_GET[‘del’]); }
Ofthetwosolutionsshownabove,theformerisarguablyslightlysaferbecauseitrendersfurthercastsunnecessary—thesimpler,thebetter.
StringValidation Whileintegervalidationisrelativelystraightforward,validatingstringsisabittrickierbecause acastsimplydoesn’tsuffice.Validatingastringhingesonwhatthedataissupposedtorepre-
Input Validation
sent:azipcode,aphonenumber,aURL,aloginname,andsoon. ThesimplestandfastestwaytovalidatestringdatainPHPisviathectypeextensionthat’s enabledbydefault.Forexample,tovalidatealoginname,ctype_alpha()maybeused.ctype_ alpha() returns TRUE if all of the characters found in the string are letters, either uppercase orlowercase.Or ifnumbersareallowedina loginname, ctype_alnum() permitslettersand numbers.
ctype_alpha(“Ilia”); // true ctype_alpha(“JohnDoe1”); // false ctype_alnum(“JohnDoe1”); // true
ctype_alnum() only accepts digits 0-9, so floating point numbers do not validate.The letter testingisinterestingaswell,becauseit’slocale-dependent.Ifastringcontainsvalidlettersfrom alocaleotherthanthecurrentlocale,it’sconsideredinvalid.Forexample,ifthecurrentlocale issettoEnglishandtheinputstringcontainsFrenchnameswithhigh-ASCIIcharacterssuch asé,thestringisconsideredinvalid.Tohandlethosecharactersthelocalemustbechangedto onethatsupportsthem:
ctype_alpha(“François”); // false on most systems setlocale(LC_CTYPE, “french”); // change the current locale to French ctype_alpha(“François”); // true now it works (assuming setlocale() succeeded)
Asshownabove,yousetthelocaleviasetlocale().Thefunctiontakesthetypeoflocaletoset andanidentifierforthelocale.Tovalidatedata,specifyLC_CTYPE; alternatively,useLC_ALLto changethelocaleforalllocale-sensitiveoperations.Thelanguageidentifierisusuallythename ofthelanguageitselfinlowercase. Oncethelocalehasbeenset,contentcheckscanbeperformedwithoutthefearofspecializedlanguagecharactersinvalidatingthestring. Convenient? Not Really Somesystems,likeFreeBSDandWindows,includehigh-ASCIIcharactersusedinmostEuropeanlanguages in the base English character set. However you shouldn’t rely on this behavior. On various flavors of Linuxandseveralotheroperatingsystems,youmustsettheproperlocale.
31
32
Input Validation
Likemostfastandsimplemechanisms,ctypehasanumberoflimitations,whichsomewhat limititsusefulness.Various,perfectlyvalidcharacters,suchasemdashes(—)andsinglequotes arenotfoundinthelocale-sensitive[A-Za-z]rangeandinvalidatestrings.Whitespacecharacterssuchasspaces,tabs,andnewlinesarealsoconsideredinvalid.Moreover,becausectypeis aseparateextension,itmaybemissingordisabled(althoughthatisararesituation).Ctypeis alsolimitedtosingle-bytecharactersets,soforgetaboutusingittovalidateJapanesetext. Wherectypefails,regularexpressionscometotherescue.Foundintheperennialeregextension,regularexpressionscanperformalloftrickyvalidationsctypebalkson.Youcaneven validatemultibytestringsifyoucombineeregwiththembstring(PHPmultibytestrings)extension.Alas,regularexpressionsaren’texceptionallyfastandvalidatinglargestringsofdatamay takenoticeableamountoftime.But,safetymustcomefirst. Here’sanexamplethatdeterminesifastringcontainsanycharacterotherthanaletter,a digit,atab,anewline,aspace,anemdash,orasinglequote:
# string validation ereg(“[^-’A-Za-z0-9 \t]”, “don’t forget about secu-rity”); // Boolean(false)
ereg(pattern, string)returnsint(1)ifthestringmatchesthepattern. Forthisexample,avalidstringcancontainaletter,adigit,atab,anewline,aspace,an emdash,orasinglequote.However,sincethegoalisvalidation—lookingforcharactersother thanthosevalidcharacters—theselectionisreversedwiththecaret(^)operator.Ineffect,the pattern [^-’A-Za-z0-9 \t] says,“Find any character that isn’t one of the characters in the specifiedlist.”Thus,ifereg()returnsint(1),thestringcontainsinvaliddata. Whiletheregularexpression(orregex)shownaboveworkswell,itdoesnotincludevalid lettersinotherlanguages.Ininstanceswherethedatamaycontaincharactersfromdifferent locales,specialcaremustbetakentopreventthosecharactersfromtriggeringinvalidinput condition. As with the ctype functions, you must set the appropriate locale and specify the properalphabeticcharacterrange.Butsincethelattermaybeabitcomplex,[[:alnum:]]providesashortcutforallvalid,locale-specificalphanumericcharacters,and[[:alpha:]]providesashortcutforjustthealphabet.
ereg(“[^-’[[:alpha:]] \t]”, “François») ; // int(1)
Input Validation
setlocale(LC_CTYPE, «french»); ereg(“[^-’[[:alpha:]] \t]”, “François») ; // boolean(false)
Thefirstcalltoereg()returnsint(1)becausethecharacterçisnotfoundwithinthestandard Englishcharacterset.However,oncethelocaleischangedtoFrench,FALSEisreturned,indicatingthestringisvalid(notinvalid,accordingtothelogic). For multibyte strings, use the mb_ereg() function and a character range for the specific multibytelanguageused.Inmanyinstances,multibytecharactersmaycomeencodedasnumericHTMLentitiessuchasいandmustbedecodedviaanothermbstringfunction, mb_decode_numericentity(). Asmentionedabove,ereg()canbetimeconsuming,especiallywhencomparedtocasting.Oneinefficiencyofereg()istherepeatedcompilationoftheregularexpression(thepattern)itself.Iftwoorthreedozenstringsneedtobevalidated,constantrecompilationimposes quiteabitofoverhead. Toreducethisoverhead,youmaywanttoconsiderusingadifferentregexpackageavailableinPHP,thePCREextension.PCREprovidesaninterfacetoamuchmorepowerful,Perlcompatible regular expression library that offers a number of advantages over vanilla PHP regex.Forexample,PCREstoresthecompiledregularexpressionafterthefirstexecution.Subsequentcomparessimplyperformthematch. Forsinglebytecharactersets,thecombinationofaproperlocaleand[[:alpha:]]works justasitdoesinthestandardPHPregex.InPCRE,youcanalsousethe\widentifierinsteadof [[:alpha:]]torepresentletters,numbers,andunderscoreinthelocale. Formultibytelanguages,PCREoffersnoequivalenttombstring,butinsteadnativelysupportsUTF-8characterencodingthatcanbeusedtostoremultibytedata.
# string validation w/PCRE preg_match(“![^-’A-Za-z \t\n]!”, “don’t forget about secu-rity”); // int(0) # validation of Russian text encoded in UTF-8 preg_match(“![^-’\t\n \x{0410}-\x{042F}\x{0430}-\x{044F}]!u”, “Руский”);
TovalidateaUTF-8string,afewextrastepsareneeded.First,thepatternmustbemodified withthe[u]operatortoindicatethepresenceofUTF-8.(Bydefault,PCREworkswithASCII strings only). Next, the ranges for the language’s uppercase (\x{0410}-\x{042F}) and lowercase(\x{0430}-\x{044F})charactersmustbespecified(aUTF-8letterisdenotedby\xand
33
34
Input Validation
theUTF-8characternumberinsidesquigglybrackets.)IfthesourcedataisnotUTF-8,PHP providersseveralmechanismsforconvertingit,includingtheiconv,recode,andmbstringextensions. Besidesitsexpansivefeatures,PCREisalsosafertouse.Here’sanexampleofhowthestandardregexcanbeexploitedbyawilyattacker:
ereg(“[^-’A-Za-z0-9 \t\n]”, “don’t forget about secu-rity\0\\”); // Boolean(false) preg_match(“![^-’A-Za-z \t\n]!”, “don’t forget about secu-rity\0\\”); // int(1)
ereg() yields FALSE because the newline preceding“secu-rity” stops all parsing. Unlike the standardregex,whichstopsscanningifitencountersanewline(\n)oraNULL\0,PCREscans theentirestring.
ContentSizeValidation Justlikenumericdata,stringinputmustmeetcertainspecifications.Regularexpressionscan validatethesyntaxoftheinput,butit’salsoimportanttovalidatethelengthoftheinput.Some inputparametersmaybelimitedtoacertainlengthbyconvention.Forexample,telephone numbersintheUnitedStatesarealwaystendigits(athreedigitareacode,athreedigitprefix, andafourdigitnumber).Otherinputparametersmaybelimitedtoacertainlengthbydesign. Forinstance,ifatextfieldispersistedinadatabase,thesizeofitscolumndictatesitsmaximum length. PostgreSQL is very strict about limits and a query can fail if a field exceeds its column size.Otherdatabases,suchasMySQL,automaticallytrimthedatatothemaximumsizeofthe column,makingthequerysucceed,butlosingdataintheprocess.Eitherway,there’saproblem—onethatcanbeavoidedbyvalidatingthelengthofstringdata. Thesolutiontothisproblemhastwoparts:makingyourformssmarter,whenpossible, andmakingyourcodesmarter. Textformfieldscanbelimitedtoamaximumsize.Bysettingthemaxlengthattribute,the user’sbrowserautomaticallypreventsexcessdata:
Input Validation
Unfortunately, maxlength only applies to text and password field types; the element,usedtoinputblocksoftext,doesnothaveabuilt-inlimiter.Tovalidatethosefieldsin userspace,youhavenochoicebuttoturntoJavaScript:
255) { alert(‘Keep it short, eh?’) return false; }”>
On submit, the Javascript code checks the length of the submitted field and if it’s too long, raisesawarningandabortstheformsubmission.Simpleenough,right? Well,nothinginsecurityisquiteassimpleasitseems.ManyusersdisableJavaScript,andJavaScriptandtheHTMLlimitsimposedbyaformcanbebypassediftheformisdoctored.Trustingauserislikeaplacinga5-year-oldbehindthewheelofamonstertruck:there’sjusttoo muchpotentialformayhem. IfJavaScriptandHTMLcanbecircumvented,server-sidePHPprovidestherealstopgap. Thesimplestapproachtovalidatingtextformfieldsofallkindsistocreateanarraywhere thenamesofthetextformfieldsarekeysusedtofindthemaximumlengthofeachfield.Given suchanarray,validatingthelengthsofalltextfieldsintheformisasimpleloop:
$form_fields = array(“Fname”=>50, “Lname”=>100, “Address”=>255, /* . . . */); foreach ($form_fields as $k => $v) if (!empty($_POST[$k]) && strlen($_POST[$k]) > $v) exit(“{$k} is longer then the allowed {$v} byte length.”);
Foreachnamedfield,thecheckloopfirstensuresthatthefieldispresentandhasavalueto validate.Ifso,strlen()isusedtoassertthatthefield’svaluedoesnotexceeditsmaximum length.Ifthere’saproblem,theformsubmissionisabortedwithamessagetellingtheuserto “fix”theirinput.Thecheckitselfisveryquick,becausestrlen()doesn’tcalculatethestring length,butfetchesitfromapre-calculatedvalueinaninternalPHPstructure.Nonetheless, strlen()isafunctioncall,andintheinterestofoptimizingtheperformanceofvalidation,is bestavoided.
35
36
Input Validation
AsofPHP4.3.10,youcandojustthatbyusingalittleknownfeatureoftheisset()languageconstruct.Theisset()constructisnormallyusedtodetermineifavariableisset,butin laterversionsitcanalsobeusedtocheckifastringoffsetispresent.Ifafieldhasastringoffset of(1+themaximumlengthofthefield),isset()returnsTRUE,indicatingthatthestringistoo long.
$form_fields = array(“Fname”=>50, “Lname”=>100, “Address”=>255, /* . . . */); foreach ($form_fields as $k => $v) { if (!empty($_POST[$k]) && isset($_POST[$k]{$v + 1})) { exit(“{$k} is longer then the allowed {$v} byte length.”); } }
Becauseisset()isalanguageconstruct,it’sconvertedtoasingleinstructionbyZend’sPHP parserandtakesvirtuallynotimetoexecute.
WhiteListValidation Assumptionistheenemyofsecurityandmakingassumptionsaboutuserinputisasurewayto allowanattackertosubvertyourcode. A common assumption made by developers is that selection boxes, check boxes, radio buttons,andhiddenfieldsneednotbevalidated.Afterall,theassumptiongoes,thesesortsof inputfieldscanonlycontainpredeterminedvalues.Ah,theoptimismofyouth… Therealityofthematteristhatausercansimplycopytheform’sHTMLsourceandmodify itorsimplydoctortherequestviaabrowserdevelopmenttool,suchasFirefox’s“WebDeveloper”plug-in.Why,PHPitselfcanbeusedtoemulateanytypeofarequest,allowingthedelivery ofarbitrarydatatoyourscript. Nomatterwhattypeofformfieldprovidesinput,allofthedatayourscriptreceivesmust bevalidatedpriortouse. Validatingafieldwithanexpectedsetofresponsesisquitesimpleandissparedthetricky exceptionsthatcomplicateothervalidationmethods.Forthesefields,createa“whitelist”or permittedsetofvaluesandcheckiftheinputisoneofthosevalues.Arraysareperfectforwhite lists:
$months = array(“January”, “February”, /* ... */);
Input Validation
if (empty($_POST[‘month’]) || !in_array($_POST[‘month’], $months)) { exit(“Quit hacking, you’re not a lumberjack!”); }
Inthesamplecode,theuserisexpectedtosubmitthenameofamonth,chosenfromaselectionbox.Becausethenamesofthemonthsareknown,anarraycapturesallpossiblevaluesand in_array()yieldsTRUEiftheinputvalueisanelementofthearray.Ifthevalueisnotprovided,asdeterminedbyempty(),orifthevalueisn’tacceptable,theformsubmissionisrejected. Case-sensitivity,charactersets,andsoonaren’tissuesherebecausetheinputvaluesmayonly comefromapredeterminedsetthatshouldn’tchange;anyunexpecteddataindicatesaninput error.
BeingCarefulwithFileUploads Inadditiontoforms,usersmayalsoprovidefilesasinput.Filestobeuploadedcanbefoundin the$_FILESsuperglobal. FileuploadhasbeenhasbeensomewhatofathorninPHP’sside,giventhenumberof seriousvulnerabilitiesfoundinthischunkofPHP’sinternals.Ingeneral,ifyoudon’tneedthe feature,youshoulddisableitinphp.ini(Thefeatureisenabledbydefault.)
# php.ini file_uploads=Off # .htaccess or httpd.conf php_flag file_uploads 0
By disabling file uploads, you can also prevent server overloads caused by hand-crafted requeststhatattempttouploadalargenumberoffiles.Oncedisabled,PHPrefusestoprocess anysuchrequests. However,ifyourapplicationsupportsfileuploads,youshouldconfigurePHPtominimize yourrisksandperformsomevalidationontheincomingfiles.
ConfigurationSettings Ontheconfigurationsideofthings,PHPoffersaseriesofdirectivestofine-tunefileuploads. Theupload_max_filesizedirectivecontrolsthemaximumsize(inbytes)ofafileupload.
37
38
Input Validation
Generally speaking, you want to keep this number as low as possible to prevent uploads of massivefiles,whichcanimposeaconsiderableprocessingloadontheserver.Bydefault,the upload_max_filesize is set to 2 megabytes, but that is far larger then most people need. For comparison,animagetakenbya3megapixelcamerarequiresabout1megabyte. ArelatedPHPconfigurationdirectiveispost_max_size;itlimitsthesizeofthetotalPOST formsubmission.Ifyourapplicationuploadsonefileatatime,post_max_sizecanbesetto slightly exceed the size of upload_max_filesize. If your application uploads multiple files at once,post_max_sizemustbesetlargerthanthesizeofallfilescombined.Bydefault,post_ max_sizeissettotherathergenerous8megabytes,andinmostcasesshouldbelowered.This isespeciallytrueforapplicationsthatdonotuploadfiles,wherethelimitcanbesafelylowered to100kilobytesorsoinmostcases. The final file uploads configuration directive is upload_tmp_dir, which indicates where temporary files should be placed on the server. Storing uploaded files in memory would be exhaustive,soPHPplacesuploadeddataintorandomlygeneratedfilesinsideatemporarydirectory.Ifanuploadedfileisn’tremovedormovedelsewherebytheendofarequest,it’sautomaticallypurgedtopreventfillingtheharddrive. Bydefault,PHPusesthesystemtemporarydirectorytoprovisionallystoreuploadedfiles. Butthatdirectoryistypicallyworld-readable(andmaybeworld-writeable),allowinganyuser orprocesstoaccess(andevenmodify)thefiles.It’salwaysagoodideatospecifyacustomupload_tmp_dirforeachofyourapplications.
FileInput Whenarequestincludesfilestoupload,thesuperglobal$_FILEScontainsasubarrayofdata foreachfileuploaded.
$_FILES[‘file’] => Array ( [name] => a.exe // original file name [type] => application/x-msdos-program // mime type [tmp_name] => /tmp/phpoud3hu // temporary storage location [error] => 0 // error code [size] => 12933 // uploaded file size )
Thenameparameterrepresentsthefile’soriginalfilenameontheuser’sfilesystem.Accordingto theW3CHTTPspecification,nameshouldonlycontainthenameofthefileandnodirectoryin-
Input Validation
formation.Unfortunately,notallbrowsersfollowthespecification(inablatantviolationofthe specificationandofuser’sprivacy,InternetExplorersendsthecompletepathofthefile),anda scriptthatusesnameverbatimmaycauseitselfandotherapplicationsnoendofproblems. Forexample,thescriptsnippetbelowplacestheincomingfileinthewronglocation:
# assuming $_FILES[‘file’][‘name’] = “../../config.php”; move_uploaded_file($_FILES[‘file’][‘tmp_name’], “/home/www/app/dir/” . $_FILES[‘file’][‘name’]);
Inthiscase,theleading../../componentoftheincomingfilenamemakesthescriptplace thefileinside/home/www/config.php,potentiallyoverwritingthatfile(ifitexistedandiftheweb serverhadwriteaccesstoit). PHP tries to automatically protect against such an occurrence by stripping everything priortothefilename,butthisdidn’talwaysworkproperly.UpuntilPHP4.3.10,theWindows implementationwasincompleteandinsomecaseswouldallow\directoryseparatorstomake itintothepath. TopreventolderversionsofPHPfromcausingproblemsandtoavoidnewexploitsthat haveyettobediscovered,it’sagoodideatovalidatethenamecomponentmanually:
# assuming $_FILES[‘file’][‘name’] = “../../config.php”; move_uploaded_file($_FILES[‘file’][‘tmp_name’], “/home/www/app/dir/”. basename($_FILES[‘file’][‘name’]));
Thebasename()functionensuresthatnothingotherthanthefilenamemakesitthroughvalidation.Inthisinstance,onlyconfig.phpremains,makingthefilemoveoperationsafe.
FileContentValidation
The second element of the file array, type, contains the MIME type of the file, according to thebrowser.Thisinformationisnotoriouslyunreliableandshouldnotbetrustedunderany circumstance.
39
40
Input Validation
The Browser Does Not Know Best Youmightassumethatthebrowserdeterminesafile’sMIMEtypebyexaminingthefile’sheaderandthe filecontent,butthat’sjustnotthecase.Inreality,thebrowserlooksatthefile’sextensiontoassignaMIME type.So,ifanasty_trojan.exeweretoberenamedtocute_puppies.jpg,thebrowserwouldhappilysend image/jpegastheMIMEtype.
Forthemostcommontypeofuploadeddata,images,PHPprovidesgetimagesize().Thefunctioncheckstheheadersofthefileandeitherreturnsimageinformation,suchasdimensions andquality,orFALSEiftheimageisn’tvalid. Forotherfiletypes,validationisabitmorecomplicatedandrequirestheuseofthePECL fileinfoextension.
# fileinfo installation instructions # As root user (only available for *Nix based systems) run pear install fileinfo # this point the built extension can be loaded via PHP.ini extension=fileinfo.so # or loaded at run-time dl(“fileinfo.” . PHP_SHLIB_SUFFIX);
fileinfoisverysavvy,abletoprocessjustaboutallcommonfileformats.Itsfinfo_file()functioncaneitherreturnatextualdescriptionofafileoraMIMEtype.
dl(‘fileinfo.’ . PHP_SHLIB_SUFFIX); # cute_puppies.jpg is really a nasty_trojan.exe finfo_file(finfo_open(), “cute_puppies.jpg”); // MS-DOS executable (EXE), OS/2 or MS Windows finfo_file(finfo_open(FILEINFO_MIME), “cute_puppies.jpg”); // application/x-dosexec
finfo_open() createsafileinforesourcethatcanbeusedbyfinfo_file()todeterminethetrue nature of the file. If multiple files need to be checked, this resource can be reused as many timesasyoulike.PassingtheoptionalFILEINFO_MIMEconstantparametertofinfo_open()returnstheMIMEtyperatherthanadescription.
Input Validation
AccessingUploadedData Thetemporarydirectoryforuploadedfiles,tmp_name,isreliablesinceitdoesn’tdependonuser input.Thatsaid,whenworkingwithit,it’simportanttousethetwoPHPprescribedfunctions thatmanipulateuploadedfiles,move_uploaded_file()andis_uploaded_file().Theformeris usedtomovetheuploadedfilefromitstemporarypathtoadesireddestination,andthelatter checksthattheprovidedpathisinfactanuploadedfile. Bothofthesefunctionsarepreferredbecauseeachvalidatesitsargumentagainstarunning,internalhashtableofuploaded,temporaryfilenames.Ifanuploadedfileismovedvia
move_uploaded_file(), its temporary filename is removed from the hash and additional attemptstoworkwiththetemporaryfilenamefail.
If (move_uploaded_file($_FILES[‘file’][‘tmp_name’], $destination)) { /* file moved correctly */ var_dump(is_uploaded_file($_FILES[‘file’][‘tmp_name’])); // Boolean(false) // the file is no longer in uploaded file hash table, due to prior operation }
(Keepinmindthatthishashtableisrecreatedwitheachrequest;afterarequestcompletes,its filenamesarenolongervalidandanytemporaryfilesnotmovedfromtmp_namearedeleted.) Bothmove_uploaded_file()andis_uploaded_file()arealsoexemptfromtherestrictions imposedbytheopen_basedirandsafe_modesettings.However,normalfileaccessoperations, suchasgetimagesize(),donothavespecialexceptionsforuploadedfiles.So,evenifis_uploaded_file()yieldsTRUE,furtheraccessmaybedisallowed:
if (is_uploaded_file($_FILES[‘file’][‘tmp_name’])) { $info = getimagesize($_FILES[‘file’][‘tmp_name’]); // Warning: getimagesize(): open_basedir restriction in effect. // File(/tmp/phpBKbE2p) is not within the allowed path(s): (/home/user) }
Thefirsttwolinesofthecodesnippetaboveshowtheproperuseofis_uploaded_file(),butthe followingtwocommentspointoutthatevenifthefileisanuploadedfile,youmaybedenied accesstoit. TopreventaccessproblemsandmakeyourcodeworkonallPHPconfigurations,there’sno
41
42
Input Validation
choicebuttoalwaysmovethefiletosomescript-accessiblelocationpriortousingit.Ifthefile isn’tneededaftertheoperation,youcandeletethefilemanually.Hence,sincefilesarealways moved,theis_uploaded_file()checkbecomesunnecessary.
Access Exemptions Asecurity-mindedadministratorgenerallydoesn’twantuserstoaccessthecentraltemporarydirectory, sinceitcanstoresessions.Topreventaccessto/tmp,theadministratorwillenablesafe_modeand/orexclude/tmpfromopen_basedir.However,becauseuploadedfilesareoftenownedbythewebserveruserbut needtobeaccessedbyascriptrunningasanotheruser,move_uploaded_file()andis_uploaded_file()are exemptfromtherestrictionsimposedbysafe_modeandopen_basedir.
FileSize Thefinalpieceoftheuploadedfileinformationarrayisthesizeelement,whichcontainsthe bytesizeofthefile.Youcanconsiderthisinformationreliableanditshouldmatchthesizeof thetemporaryfileprecisely.(Ifforsomereasonafilecannotbeuploadedcompletely,perhaps becausethetemporarydirectoryisoutofspace,theuploadismarkedasfailedandtheerror elementofthefileinformationarrayisset.) Sincethesizeisreliable,youcanactuallyuseittoperformanotherlittlesafetycheckto ensurethatafileispristinebeforeyoubegintoworkwithit:
if (!move_uploaded_file($_FILES[‘file’][‘tmp_name’], $destination)) { exit(“Uh oh…”); } if (filesize($destination) != $_FILES[‘file’][‘size’]) { unlink($destination); exit(“System error, run for the hills!”); }
Thiscodemovesafilefromthetemporarydirectorytoalocaldirectory,$destination,toavoid
open_basedirandsafe_moderestrictions.filesize() returnsthesizeofthefile,whichisthen comparedtothesizeoftheuploadedfile.Ifthesizesdon’tcorrelateexactly,thereisaproblem, the(assumedtobeuntrustworthyorcorrupt)fileisremovedtopreventaccumulationofdata inthe$destinationdirectory,andanerrorisraised. Thisparticularprotectionmechanismisn’tintendedtoprotectyoufromattackers,who areprobablyintelligentenoughtoensurethatthemodifiedfileisthesamesizeastheoriginal. Instead, it ensures that the file has been fully transferred by move_uploaded_file() and that
Input Validation
anotherprocessdidn’talterthefile(intentionallyoraccidentally)whileitwassittinginsidethe temporarydirectory.
TheDangersofMagicQuotes Whilesomemagictrickslikepullingbunniesoutofahatarebenignandevenentertaining, others—like voodoo—skirt the realm of black magic and can be quite troublesome. Despite beingdesignedwiththebestintentionsinmind,PHP’smagic_quotes_gpcdirectiveiscloserto voodoothanpartytricksandasmartdevelopermaywanttosteerclearofitsjuju. Thepremisebehindmagic_quotes_gpcisthatinputmaycontainallsortsofspecialcharacters,suchasquotes(‘and“),NUL(\0),andbackslash(\),thatleftunfilteredcouldcause problemsifpasseddirectlytovariousfunctions.Givensuchanobviousthreat,thedevelopers ofPHPdecidedtoautomaticallysecureallinputbyescapingit,effectivelyrunningaddslashes()onalldatafromallinputmethodsandontheoriginalfilenamesinthe$_FILESarray.
# magic_quotes_gpc=On & script.php?text=Cthulhu’s Guide to “Necronomicon” echo $_GET[‘text’]; // Cthulhu\’s Guide to \”Necronomicon\”
Butautomaticsecurityislikeawoolblanket:itprotectsyoufromharshweather,butcanalso causeirritationandeventriggerallergies.Bewareofsideeffects. Themostcommonproblemassociatedwith“magicquotes”isblindrelianceonthefeature.Thefactisthatmagicquotescanbedisabledviaphp.ini,httpd.conf,andeven.htaccess.Ifyouassumethatmagicquotesisenabledandeschewmanualvalidationofinput,you leaveyourapplicationopentoSQLinjection,commandinjection,andanynumberofother exploits. Furthermore,assumingthatmagicquotesalways“doestherightthing”iserroneous.While magicquotesescapesthemostcommon“special”characters,othercharactersmayhavespecialmeaningaswell,dependingonwheretheyarebeingused.Properprotectionmechanisms arehighlyspecific.
# magic_quotes_gpc=On # script.php?data=test’ echo $_GET[‘data’]; // test\’ # MySQL Specific Escaping, also escapes \n, \t and \x1a
43
44
Input Validation
echo mysql_real_escape_string($_GET[‘data’]); // test\\\’ (double escaping) echo mysql_real_escape_string(stripslashes($_GET[‘data’])); // test\’
Here,mysql_real_escape_string()isacustomfunctionthatpreparesastringtobeusedwith MySQL.Theproperuseisshownonthelastline:side-effectsfrommagicquoteshavetobe undonewithstripslashes()beforeadditionalprocessing. Automaticescapingisalsooftenunnecessary,sincethedatamaybegoingtoaflatfile, wheredatadoesnotneedtobeescaped.Infact,escapingthedatamayactuallycauseproblems,becausetheescapedoutputwillappearratherthantheactualdata. Moreover, the implementation of addslashes() in PHP is not especially efficient. It allocatestwicethememoryneededtostorethestringtoaccommodatethepossibilitythatall charactersmustbeescapedandtraversesthestringonebyteatatimelookingforspecialcharacters.Onceallspecialcharactersarefoundandescaped,thememoryisresizedtotheactual stringlength.Theentire“magicquoting”operationcanbequiteslowiftherearealargenumberofinputvariablesorifthevariablescontainlargeamountsofdata.Worseyet,allthiswork mayneedtobereversed,wastingevenmoreCPUcycles.
MagicQuotesNormalization It is hardly surprising many developers and administrators concerned with performance choosetodisablemagicquotes.However,adistributableapplicationcannotassumeormandateasettingformagic_quotes_gpc,sosomesortofanormalizationprocedureisrequiredthat ensuresthedataisintheexpectedform.Changinginisettingsduringruntimecannotwork, simplybecauseescapingisdonepriortoscriptexecution,duringtheinputparsingstage. AbitofPHPcodeisneededtoresolvethisinconsistency.
if (get_magic_quotes_gpc()) { // check magic_quotes_gpc state function strip_quotes(&$var) { if (is_array($var) array_walk($var, ‘strip_quotes’); else $var = stripslashes($var); } // Handle GPC foreach (array(‘GET’,’POST’,’COOKIE’) as $v) if (!empty(${“_”.$v})) array_walk(${“_”.$v}, ‘strip_quotes’); }
Input Validation
The normalization code first checks the state of magic escaping via the get_magic_quotes_ gpc()function;itreturnsFALSEifmagicquotesisdisabledandTRUEifit’senabled.Thecode then creates a function that takes a value by reference, and either strips backslashes, if the referencepointstoastring,orappliesthestrip_quotes()functionagainviaarray_walk(),if thereferencepointstoanarray.Inthelattercase,thefunctionperformsarecursivetraversal toensurenovalueisleftuntouched.Thecodetheniteratesthroughthesuperglobals,applying thefunctiontoeachifnon-empty.Theendresultisescape-freeinput. Alternatively,thelogiccouldbereversedtoescapeinput,bysimplychangingstripslashes()toaddslashes()andchangingthemainconditionalexpressionto!get_magic_quotes_ gpc()toonlyescapeifnecessary. There’soneproblemwiththisparticularcodethough,andit’sactuallyalimitationofPHP. InPHP,arecursivefunction,orafunctionthatcallsitself,utilizesthesystemstackfortracking purposes.Alas,thesystemstackislimited,andwithenoughiterations,it’spossibleto“smash” thestackandcrashPHP.Forexample,iftheusersuppliesaverydeep,multidimentionalarray, suchasfoo[][][][]…,strip_quotes()canrecursetothepointofexhaustingthestack.Generatingsuchanattackstringisquitetrivial,viatheuseofthestr_repeat()function.
$str = str_repeat(“[]”, 100000); file_get_contents(http://site.com/script.php?foo={$str});
Asaferapproachavoidsrecursionbyflatteningthe(arbitrarilydeep)inputarraysintoasingle array:
if (get_magic_quotes_gpc()) { $input = array(&$_GET, &$_POST, &$_COOKIE, &$_ENV, &$_SERVER); while (list($k,$v) = each($input)) { foreach ($v as $key => $val) { if (!is_array($val)) { $input[$k][$key] = stripslashes($val); continue; } $input[] =& $input[$k][$key]; } } unset($input); }
45
46
Input Validation
Besidesspurningrecursion,thecodeaboveisvirtuallyfunction-free,makingitnotonlysafer butquiteabitfasteraswell.Thefirststepcreatesanarrayofdatasourcestoprocess;eachof thedatasourcesisassignedbyreferenceto$inputtoensurethatchangespropagate.$input istheniteratedusingeach(),whichusesaninternalarraypointertonavigatethearray—an importantpoint,becauseadditionalelementswillbeaddedtothearrayasprocessingcontinues. Foreveryelementoftheinputarray,whichisanarrayitself,thecodeiteratesusingforeach. Ifthesubelementofthecurrentarrayisastring,thecoderemovesanyescapecharacters.Ifthe sub-elementisanarray,it’sappendedbyreferenceto$inputforfutureprocessing.This“flattening”processcontinuesuntilalldatahasbeen“unescaped”.Atthatpoint,the$inputarrayis nolongerneedandcanberemovedviaunset().
MagicQuotes&Files Oneotherdatumescapedbymagicquotesthat’sbeenneglectedsofaristheoriginalfilename storedinsidethe$_FILESsuper-global.Thisissomewhatofaninterestingcase,whichdemonstratestheproblematicnatureofautomation. Let’ssaythatthefilenameofthefilebeinguploadedcontainsasinglequote,aperfectly valid part of a filename, albeit a bit unusual. If magic quotes is enabled, the single quote is transformedto\’.PriortoPHP4.3.10,Windowssystemswouldinterpretthebackslashasa pathseparator;afterthatreleaseanduptothepresent,ifmagicquotesisenabled,Windows systemsstripeverythinguntilthelastsingleordoublequote,resultingindataloss.
# Original file name was foo’bar.txt and magic_quotes_gpc=On # on Windows in PHP < 4.3.10 & < 5.0.3 $_FILES[‘file’][‘name’] = “foo\’bar.txt” # on Windows in PHP > 4.3.10 & > 5.0.3 $_FILES[‘file’][‘name’] = “bar.txt”
(OnLinux,only/isavaliddirectoryseparator,sothere’snoissuewithbackslash.However,unlessbackslashesarestrippedfromthename,thecharactersbecomepartofthefilenameifthe originalnameisusedforstorage.) Ifyourapplicationuploadsfiles,itishighlyrecommendedthatyoudisablemagic_quotes_ gpc.Ifthat’snotpossible,thenremovequotecharactersbeforeusingthenameforfileplacement or basename() validation onWindows, which treats backslash as a directory separator
Input Validation
andalsostripeverythingpriortoit.
if (get_magic_quotes_gpc()) { $_FILES[‘file’][‘name’] = stripslashes($_FILES[‘file’][‘name’]); }
Whenemulatingmagicquotesinyourowncode,itisalsobettertoskipescapingtheoriginal nametopreventitpotentiallyacquiringthebackslashdirectoryseparator.
ValidatingSerializedData Anothercommontypeofinformationthatisoftenpassedbetweenrequestsisserializeddata,a specialPHPencodingformforcomplexdatatypeslikearraysandobjectsthatcanbedeserializedintotheoriginaldatatypes. Theruleofthumbisthatserializeddataisintendedforinternal,server-sideuseonly.A usershouldnotbeallowedtoaccessormodifythisinformation.Anattackercaneasilymake PHPgeneratehighlycomplexandmemory-intensivedatastructuresthateatupCPUtimeand wasteenormousamountsofmemory.InolderversionsofPHP,hackedserializeddatacould evenbeusedtodeviseexploitstoexecutearbitrarycommandsontheserver. PHPdoesnexttonovalidationonserializeddata,asitexpectsthedatatobeasafeinternal format.Whensomeunexpectedorundesireddataisinjectedintoserializedstrings,itopensa bigandnastycanofworms.ForexamplewhenPHPtriestodeserializethisseeminglyharmless string,it’llattempttocreateanarraywith100millionelements.
a:100000000:{}
100millionelementsrequiresonlyabout500megabytesofmemory.Nowimaginetwoorthree ofthoserequestssentmoreorlessatthesametimeandyourserverissuretogrindtoahaltdue tolackofmemory.Afarmorecreativeattackermayuseunguardedserializeddatatoinjectvaluesintotheapplicationanddoallsortsofnastiness.Thelimitofwhatcanbedoneultimately dependsontheimaginationandskilloftheattacker. Thebestwaytoprotectyourapplicationagainstsuchexploitsistoneverpassserialized datainsuchawaythatausercanaccessit.Unfortunately,thatmaynotalwaysbepossibleor
47
48
Input Validation
practicalwithoutcomplicatingtheapplication.Ifserializeddatamustbepassedviauseraccessiblemeans,theapplicationhastheonustovalidatetheserializeddatabeforedeserializing it. Oneofthesimplestwaystovalidateserializeddataisachecksum.Beforesendingthedata, generateachecksumbasedonasecretkey.Thesecretkeyguaranteesthatonlytheholderof thatkeycangenerateavalidchecksumofthedata.Thismethodologyisgenerallyreferredas HMAC,orkeyed-HashMessageAuthenticationCode.
If (extension_loaded(‘mhash’)) $hmac = bin2hex(mhash(MHASH_SHA1, $serialized_data, “secret key”));
ThefastestandsimplestwaytogenerateHMACisthemhashextension,whichoffersvarious hashingalgorithms.Iftheextensionisavailable,itsmhash()functiongeneratesbasicandkeybasedhashes.Thefunction’sfirstparameteristhealgorithm,orMHASH_SHA1inthecodeabove (a160-bithashingalgorithmshouldbefairlydifficulttocrackforanyonewithoutafewyears andafewhundredsupercomputerstospare).Thesecondparameteristhedatatobehashed, andthethirdandfinalparametercontainsthesecretkeyitself. Theoutputofmhash()isa20-byte-longbinaryhash.Tomakethehashreadableanddo avoidcorruptionwhenpassingitaround,thebinaryhashcanbeconvertedtohexadecimal formviathebin2hex()function. Intheabsenceofthemhashextension,whichisrelativelyrare,thePEARrepositoryoffers a slower implementation for SHA1-and MD5-based HMAC generation via the Crypt_HMAC library.
require “Crypt/HMAC.php”; // load Crypt_HMAC library. $hash = new Crypt_HMAC(‘secret key’, ‘sha1’); // secret key & hash algorithm $hmac = $hash->hash($serialized_data); // generate HMAC hash
ThePEAR-generatedHMACalreadycomesinhexadecimalform,sonoadditionaloperations areneeded. Givenavalidationkey,youcannowaddthekeytotheserializeddatastringeitherasasuffixoraprefix.Duringthedecoding,anewHMACisgeneratedbasedontheprovideddataand
Input Validation
thesecretkey.Onlyifthenewly-generatedHMACmatchestheoneincludedwiththeserialized stringisdeserializationallowedtocontinue.
// encoding process $sd = serialize($other_data); $hmac = bin2hex(mhash(MHASH_SHA1, $sd, $secret)); $_COOKIE[‘value’] = $hmac . $sd; // decoding process $key = substr($_COOKIE[‘value’], 0, 40); $sd = substr($_COOKIE[‘value’], 40); if ($key != bin2hex(mhash(MHASH_SHA1, $sd, $secret))) exit(“Invalid input”);
Hashesbasedonthesamealgorithmalwayshavethesamelength,soretrievingthekeyoutof theserializedstringisassimpleasdoingasubstr()forthekeylength.Thehexadecimallength ofthehashdependsonitsbitstrength;forexample,thelengthofanSHA1hashis40bytes;for MD5,it’s32bytes. Thequickformulaforgettingthesizeofahashis:
[bit size] / 8 (binary size) [bit size] / 8 * 2 (hexadecimal size)
Withoutknowingthesecretkey,theonlywayforanattackertoinjectdatawouldbetocome upwithahashcollision,essentiallyanotherstringthatcombinedwiththesecretkeygenerates thesamehashastheoriginal.ThishasbeenproventobepossiblewithMD5,althoughcertainlynotsomethingdoneeasily,whichiswhyastrongerhashingmechanismsuchasSHA1, Tiger160,orSHA256shouldbeused. ThemostimportantpartwithHMAChashingisthattheysecretkeystaysecretandisnot somethingathird-partycouldpotentiallyguess.Arandomstringoflettersandnumbersisthe bestvalueforasecretkey,andpreferablythekeyshouldbeatleast10-characterslong.
ExternalResourceValidation Aside from serialized data there are few other dangerous inputs that should be strictly validated.
49
50
Input Validation
Anexternalreferencetoanimage,suchastheonesuppliedbyausertopostcontentor provideanavatar,isrifewithproblems.Becausetheimageisanexternalresource,itcanbe changedatanytimetoanother,perhapssalaciousimage,ortoaJavaScriptprogram.Thelattercanbeusedtodoallsortsofnastythings,suchasaskuserstoprovidetheirauthentication information. Validatingeveryimagebeforeit’sdisplayedissimplyunwieldy,slow,andinefficient.Instead,upload,copy,validate,andstoreeachimagelocallyandusethelocalimagesfordisplay. Thecopystepisnecessarytoavoidcontentchangingbetweenthetimeit’svalidatedandthe timeit’sstored.
if (copy($avatar_url, “./local_file”)) { if (!getimagesize(“./local_file”)) { unlink(“./local_file”); exit(“Invalid Avatar”); } }
Inthissampleimplementation,copy()generatesalocalcopyofthedatafoundattheuser-
supplied URL. getimagesize() validates the file’s content, and if the file is invalid, unlink() removesthefileandexit()warnstheuserandquits.Ontheotherhand,ifalliswell,youhave alocalcopyoftheuser-supplieddata,whichcannowbeusedsafelyasanavatar. Anotheradvantageofthisapproachisthatvisitorstothepagethatcontainssuchanimagewon’tsendrequeststoexternalservers,blockingthird-partiesfromtrackingyouraudience. Furthermore,it’salsoamorereliablewaytoprovideinformation,sinceinmostinstancesthe imagesmaycomefromfreehostingsitesthatarehighlyunreliable,whilealocalcopyremains activeaslongasyoursiteis. ThereisahiddendangerdownloadingfromURLsinPHP,andit’ssomethingtobevery cognizantof.WhenPHPmakesanHTTPrequesttodownloadcontentfromaURL,itfollows anyredirectsprovidedbythetargetserver.Whileitdoessetalimit(20)onthenumberofredirectstofollowtopreventendlesslooping,itdoesnothingtovalidatethoseURLs. So,acreativetrickstercouldhavehisorherserverredirectrequestsforcertainURLsto externalsources.Forexample,ahackerworkingforCompanyAcouldmakeyourserversubmit clickrequestsforCompanyA’smaincompetitor’spaidlistingonGoogle.Worse,certainvulnerabilitiesinPHPthatwillbeaddressedinfuturereleases(4.3.12,5.0.5)couldevenallowtheattackertoconnecttoanSMTPserverandsendemail.
Input Validation
SohowcanyoumakeURLrequestssafely?UsethecURLextension.Itcanperformallsorts ofvia-the-webrequests,andyoucandisablethefollowingofHTTPredirects:
$ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return fetched data curl_setopt($ch, CURLOPT_MAXREDIRS, 0); // do not follow redirects $data = curl_exec($ch); curl_close($ch);
ThiscURLcodeensuresthatonlytheURLspecifiedbytheuserisopenedandnothingelse.Ifa givenURLpagetriestoredirecttheconnectionelsewhere,itisrejected.Buteventhisdoesnot guaranteesafety,sincethe“trick”URLcansimplybespecifiedastheoriginalvalue. TheonethingcommontomostURLsthatperformsomesortofanaction,suchasabanneroradclick,isaquery.Bycheckingforthepresenceofaquery,youcandetermineifalink istoastaticfileornot,inmostcases.OnesurewaytoparsethequeryfromaURLisviaparse_ url() afunctionthatbreaksdownanyURLintoaseriesofcomponents.
print_r(parse_url(“http://www.google.ca/url?sa=l&q=abc&num=2”)); # output Array ( [scheme] => http [host] => www.google.ca [path] => /url [query] => sa=l&q=abc&num=2 )
OncetheURLhasbeenconvertedtoaURLcomponentarray,it’sasimplemattertocheckifthe queryparameterisemptyornot.SincetheURLhasalreadybeenparsed,youcanalsousethe hostcomponenttoensurethatthefileisnotcomingfromablack-listeddomain,ifyoukeep suchalist. Whileitstillmaybepossibleforausertosupplyinappropriatedata,itbecomesfarmore difficulttodoandprovidesadeterrentagainstallbutthemostpersistentattackers.Coupled withinternalimagevalidation,theworseyoucanexpectisaninappropriateimage,whichis thejobofmoderationstafftoinspectandapproveorreject.
51
2
Cross-Site Scripting Prevention
C
ross-sitescripting(XSS)isoneofthemostcommonvulnerabilitiesofwebapplications. Insuchanattack,ahackerstoresuntowardCSS,HTML,orJavaScriptcontentinthe application’sdatabase.Later,whenthatcontentisdisplayedbytheapplication—say, aspartofabulletinboardposting—italtersthepageorrunssomecode,oftentostealauser’s cookiesorredirectconfidentialinformationtoathird-party’ssite. XSSisapopularandofteneasy-to-achieveexploitbecausewebapplicationslargelyecho userinput.Indeed,mostwebapplicationscyclerepeatedlybetweenshowinginformation,collectinginput,andshowingnewinformationinresponse.Ifanattackercansubmitnefarious codeasinput,theapplicationandthewebbrowserdotherest.Ingeneral,asuccessfulXSS attackcanbetracedtocarelessapplicationdesign. ThereareprimarilytwotypesofXSSvulnerabilities:adirectactionwheretheinjectedinputisechoedonlytotheinjectinguser,andthestoredaction,whereanynumberoffutureusers “see”theinjectedcontent.Adirectactionusuallyattemptstogaininsightaboutanapplication orawebsitetodeduceamoresubstantialexploit.Astoredaction,arguablythemostdanger-
54
Cross-Site Scripting Prevention
oustypeofXSSsinceitseffectsareessentiallyunbounded,typicallytriestostealidentitiesfor subsequentexploitsagainstindividualsorthesiteat-large.(Forinstance,ifprivilegedcredentialscanbestolen,theentiresitecouldbecompromised.)
TheEncodingSolution Sohowdoyousecureyoursite—ultimately,theinputsenttoyoursite—againstXSS?Fortunately,thisisveryeasytodoinsidePHP,whichoffersaseriesoffunctionstoremoveorencode charactersthathavespecialmeaninginHTML. Thefirstofthosefunctionsishtmlspecialchars().Ittakesasingleparameter,presumablyrawuserinput,andencodesthecharacters&(ampersand),(greaterthan),“ (double-quote),andoptionally,‘(single-quote).Allofthose“special”charactersgetconverted totheequivalentHTMLentities,suchas&forampersand,whicheffectivelytreatthecharacterasaliteralinsteadofpartoftheunderlyingpagecode.
$raw_input = ‘’; $encoded_input = htmlspecialchars($raw_input); echo $encoded_input; //
Asanotherexample,= 65 && $matches[1] = 97 && $matches[1] = 48 && $matches[1] (a-z)],or[48-57->(0-9)],thevalueis convertedtoaliteralviachr(),whichtakesanumericvalueandreturnstheASCIIcharacterassociatedwiththatvalue.Allotherentitiesthataredoublyencoded,whicharetheresultofuser inputtingHTMLentitiesmanually,areleftas-isandarelaterdisplayedasentities.Forexample, iftheusertypes@,thepagedisplays@,not@. Evenwiththiscautiousapproach,therearenumberofissuesthatremainwhenworking withHTMLentities.Forinstance,anentitydoesnotneedatrailingsemicolon.'isaperfectlyvalidentitythatthebrowserhappilydisplaysasasinglequote.Butifthesemicolonis optional,thenalloftheregularexpressionsshownpreviouslycouldfail.Tofurthercomplicate matters,thenumericvalueofanentitycanbeexpressedasahexadecimalvalue.So,@
Cross-Site Scripting Prevention
alsorepresentsasinglequote.(ForcomplexcharacterencodingschemassuchasUnicode,the hexadecimalformisallbutstandard.)Hexadecimalvaluesaren’tcoveredbytheregularexpressionsshownaboveeither. Toaddressbothoftheseissues,amorerobustregularexpressionisrequiredandthedecodingfunctionneedsabitmorelogic.First,theregularexpression:
preg_replace_callback( ‘!((?:[0-9]+)|(?:x(?:[0-9A-F]+)));?!i’, ‘decode’, $input);
Theregularexpressionnowcapturesentitiesthatstartwith&followedbyeitheraseriesof decimaldigits,oranxfollowedbydigitsand/orA-Fcharacters.Someofthegroupingsub-patternsincludethespecial?:qualifier,topreventstorageofthesub-pattern.Hence,thematch array only contains two elements as before: the character number (expressed in decimal or hexadecimalform)andthecompleteversionofthestring.Finally,thesemicolonattheendis madeoptional.The“i”patternmodifierattheendmakesallmatchescase-insensitive. Thedecodefunctionalsoacquiresabitofnewcodetohandlethevariouspossiblevalues:
function decode($matches) { if (!is_int($matches[1]{0})) { $val = ‘0’.$matches[1] + 0; } else { $val = (int) $matches[1]; }
}
if ($val > 255) { return ‘’.$matches[1].’;’; } if (($val >= 65 && $val = 97 && $val = 48 && $val ’, ‘’); echo preg_replace(‘!]+)>!i’, ‘’, $input); // Exploiting XSS for fun & profit”>
Theattributedummy,whichwouldhaveotherwisebeenignored,nowisapartofthenormal HTMLoutput.Furthermore,ifstrip_tags()isn’tcalled,theattackercouldpacktagsintothe attributevalue,obtainingtheabilitytoexecuteJavaScriptthroughthe
Thesleep()functionidlesthescriptwithoutaddingloadtotheserver.Afterthescript“wakes”, returnaresponse. Inadditiontoslowingdictionaryattacksandperhapspreventingtheserverfromoverload,adelaytacticisalsoeffectiveagainstmanualattacks.Anattackerislikelytolosepatience afterwaitingtwentysecondsbetweenrequestsandmaypromptthemischief-makertofindan alternate,lessfrustratingtarget. Thedownsideofimposingadelayisthatittakesupawebserverprocess,whichcould haveotherwisebeenusedtodelivercontenttorealusers.
AmoreperformancefriendlysolutionmayinvolvetheuseofJavaScripttodelaypageresponse,butthat wouldnotbeaffectiveagainstanautomatedscriptandcouldbeeasilyspottedbyahumanattacker.
10
Securing Your Applications
I
deally, securing an application would be an inherent part of the software development process,includedaspartoftheapplication’sinitialdesign,codedandauditedthroughout development, tested prior to deployment, and vetted continuously when in production use.Unfortunately,realityistypicallylessthanideal,anddevelopersandadministratorsareoftentaskedwithmaintaininganexistingcodebase.Securinginherited,perhapslegacy(already deployed)codeisquiteabitmoredifficult,sincethere’sanimmediate,largecodebasetoaudit andyoumaynotbeabletochangethebehaviorsandcallingsequencesofexistingAPIsand conventions.Leavinganapplicationinsecureissimplynotanoption,soalogicalandconsistentapproachisneededtomakethecodeassafeandsecureaspossible. Thischapterpresentsaseriesofstepstofollowtoauditandsecureexistingcode.While nosecurityauditcanrevealallpossibleproblems,aproperauditcanatleastaddressamajorityofsignificantissuesandcreateaframeworktodetect,analyze,andresolveanyproblems discoveredlater.
180
Securing Your Applications
EnableVerboseErrorReporting ThefirststeptosecureaPHPapplicationistoenablethosePHPfeaturesthattrackapplication errors.Applicationerrorsoftenpointdirectlytoorprovidecluesaboutvulnerabilities. Forexample,manyoftheregisterglobal-relateduninitializedvariableerrorscanbedetectedsimplybyraisingtheerrorreportinglevelwithinanapplication.Here’ssomecodetodo justthat:
error_reporting(E_ALL); // in PHP 5.0 E_ALL | E_STRICT ini_set(“display_errors”, 0); ini_set(“log_errors”, 1); ini_set(“error_log”, “/home/user/logs/app_xyz.php.log”);
Thefirsttwolinesofcodeenablethetrackingofallerrors(E_ALLinPHP4.0orE_ALL | E_ STRICT in PHP 5.0 and above), including warnings, fatal errors, and notices about uninitializedvariables.Thesecondlineofcodedisablesthedisplayoferrors,sothatthecodecanbe deployedinaproductionenvironment,whilelinesthreeandfourspecifythaterrorsshouldbe loggedtoanamedfile—aneasywaytomonitorerrormessagesinanypageroreditorutility, suchaslessorvim,respectively.Tousethissnippet,justplaceitatthetopofanyapplication, inaheaderforexample.
ReplacetheUsageofRegisterGlobals Ifpossible,youshouldchangetheapplicationtonotretrieveuserinputviaPHP’sregisterglobals.Ifyouundertakesuchmodifications,however,it’sbesttoperformyourauditandverify yourchangesonaseparate,testsystemthatdoesnotinterferewithyourproductionsystem. Beforedisablingregister_globals,determineifitsalternativesarebeingutilizedbyrunningasimplegrepcommandacrossallofthecode:
grep –rI “_\(GET\|POST\|COOKIE\|SERVER\|REQUEST\)” /home/user/app/*
This grep recursively searches through all non-binary files inside the specified directories, lookingforcommonsuperglobalnamesortheequivalent,pre-PHP4.1alternatives,suchas HTTP_GET_VARS.
Securing Your Applications
Withthisonecommand,youcandetermineifthemajorityofthecodehasbeendesigned torelyonregisterglobals.Ifgrepyieldsagreatnumberofmatches,it’slikelythatsuperglobals aren’tneededandcanbesafelydisabled.Ontheotherhand,iftheapplicationislargeandthe commandfailstoreturnanyorfewlines,youprobablyhavequiteabitforworktodoto“modernize”theprogram’sinputprocessingmechanism. Oncetheconversionismadeorifyou’reluckytohavestartedwithagoodcodebase,you canturnregister_globalsoffandtrytousethesite,loadingatleastoneinstanceofeachpage as a bare minimum. Enable all error messages, too, and monitor the error log for E_NOTICE messagesaboutuninitializedvariables.Orusethefilterawktoscanthelogforthealertsyou’re lookingfor:
awk -F ] ‘/PHP Notice:/ { print $2 }’ app_xyz.php.log | sort | uniq
ThisawkcommandfindsallE_NOTICEerrormessagesinthePHPerrorlog.Toavoidrepetitious messages(becausethesameerroroccurredoverandover),eachlineisbrokenintotwoparts (via–F),thedateandtheactualerrormessage,whereonlythelatterisemitted(it’s$2).The entireoutputofawkisthensortedandstrippedofduplicates(usingthehandyuniq),yielding onelineofoutputforeveryerrorrecorded.Theendresultisahopefullyshortlistofareasvulnerabletovariableinjection,asshownbelow:
PHP Notice: PHP Notice:
Undefined variable: Undefined variable:
sendflag in /home/user/a.php on line 23 sendflag in /home/user/b.php on line 31
Avoid$_REQUEST
While the $_REQUEST superglobal is convenient for accessing input from multiple sources, it obscurestheactualsourceofthedata.Suchopaquenesscanleadtovulnerabilitiesandbugs causedbyunexpectedinput.Forexample,acookiecouldoverwritetheintendedinputprovidedviaGETorPOST. Sinceit’sbettertobesafethansorry,theusageof$_REQUESTissomethingbestavoided. Fortunately,detectionofitsusageisdownrighttrivialwithgrep:
181
182
Securing Your Applications
grep –rI “\$_REQUEST” /home/user/app/*
Insomesituations,theuseof$_REQUESTcanbereplacedwithsomethingmorespecific,like $_GETor$_POST.However,insomecases,validinputcancomefromtwodifferentsources.For example,ifyourapplicationeditsdata,the“ID”parameterofarecordcouldbesuppliedvia
GETontheinitialrequestforaneditformandcouldbepassedviaPOSTwhenchangesaresubsequentlysubmitted.Insuchinstances,theuseof$_REQUESTcanbereplacedbyasimplesetof conditionalexpressions:
if (!empty($_POST)) { $id = empty($_POST[‘id’]) ? 0 : (int) $_POST[‘id’]; } else { $id = empty($_GET[‘id’]) ? 0 : (int) $_GET[‘id’]; } if (!$id) { exit(“Invalid Request”); }
ThismoresecureinputprocessingcodedeterminesifthedatawassubmittedviaGETorPOST. If$_POSTisempty,the“ID”isextractedfrom$_GETandifvalidisretrievedfromtheinpreparationforediting.Otherwise,the“ID”isin$_POST,indicatingthatchangestotherecordshould bepersistedtothedatabase.Ifno“ID”isprovidedineitherformofrequest,itsvalueis0,necessitatinganerror.Thesimplecheck,if (!$id) …,checkstoseeif“ID”isempty.
DisableMagicQuotes Ifthephp.inioptionmagic_quotes_gpcisenabled,allGET,POST,andcookievariablessentto theapplicationareautomaticallyfilteredto“escape”specialcharacterssuchasbackslash\and thesingleanddoublequotecharacters.Theintentofmagic_quotes_gpcistomakestringssafe fordirectuseinSQLqueries. There’sbeenagreatdealofdebateaboutthemeritsofmagic_quotes_gpc,buttoenhance security,it’sbesttodisablethisfeatureandaddanormalizationfunctiontoprocessallinput accordingtotheneedsoftheapplication.(Anexamplenormalizationroutinecanbefound inthefirstchapterofthisbook.)Anormalizationfunction—somethingyouprovide—ensures thattheinputvaluesareconsistent,regardlessofthePHP’ssettings.
Securing Your Applications
TrytoPreventCross-SiteScripting(XSS) Unfortunately,checkingforcross-sitescripting(XSS)attacksishardtoautomate,becauseit oftenrequiresmanualcheckingofalloftheformandinputparametersthatendupbeingdisplayedtotheuser. Thereishoweversomethingthatcansimplifytheprocess:theXSSspider.TheXSSspideris aspecialstring(like>///\0/\\\terminationofthetag.The subsequentdatawillthenbethendisplayedtoscreen,demonstratingapotentialXSS. AsidefromXSSvalidationthespidercontainsotherspecialcharsthatifleftasismaytriggerSQLinjectionsorbreakJavaScript,suchas\and‘makingitaperfectallaroundvalidation marker.So,whentestingyourwebforms,ratherthenplacingvalidvalues,trytopopulatethe fieldswiththespiderandseewhateffectithasonthegeneratedpage.Theresultsmaybequite surprising.
ImproveSQLSecurity Unlikethepreviousprecautions,securingSQLrequiresabitmorework,asit’softendifficultto tellifaqueryissubjecttoSQLinjectionornot.Thevariablesplacedintothequerymaycome fromanypartofthecodeandyou’llhavenochoicebuttotrackdowntheiroriginanddeterminetheircontents.Therearehoweverafewidealstofollow. Wheneverpossible,trytousepreparedstatements,whichnotonlyspeedupexecution, butalsopreventdynamicvaluesplacedinthequeryfrombeingusedasanythingotherthan values.
pg_prepare($db, “qry1”, ‘SELECT * FROM users WHERE id=$1’); pg_execute($db, “qry1”, array((int)$_GET[‘id’]));
When you cannot create prepared query statements—which holds true for the MySQL and SQLiteextensions—carefulparametervalidationisabsolutelyessential.
183
184
Securing Your Applications
Oneimportanttip:donotomitsinglequoteswhenpassingintegervalues.Here’sanexamplethatdemonstratesthedanger:
$id = ‘id’; mysql_query(“DELETE FROM messages WHERE id={$id}”); // will remove all records mysql_query(“DELETE FROM messages WHERE id=’{$id}’”); // will remove 0 records
Ifthevalueiseverinjectedandleftunvalidated,youcouldbesubjecttoSQLinjection. Ontheotherhand,ifyouusesinglequotestoencompasstheargument,theattackstring will need single quotes itself, a character both addslashes() and database-prescribed functionsescape,nullifyingit’sspecialmeaning. As far as string values go, there’s no choice but to examine all of the queries, trace the sourceofinput,andensurethattheinputisvalidated,escaped,orencoded.Keepinmindthat somedatabasesrequireaspecificapproachforplain-textandbinarydata,sobesuretousethe rightescapingorencodingfunctionforthejob.
PreventCodeInjection Codeinjectionvulnerabilitiesareverydangerous.Anyoperationthatpotentiallyexposesyour applicationtothisexploitshouldbeauditedverycarefully. Thefirstandsimpleststeptotaketoavoidcodeinjectionistoexaminethecodeloading constructsandfunctionsbeingused.Onceagain,grepisaninvaluabletoolforthistask.
grep –riI “\(include\|require\|eval\)” /home/user/app/*
The grep command above performs a case-insensitive search to find all of the mechanisms normallyusedinPHPtoexecutecode.Oncetheinstancesarefound,makesurethatfullpaths forinclude/requireareusedandthatsuchstatements(ideally)omitembeddedvariables.If dynamiccomponentsareneeded,useconstants,whichcannotbeinjectedintothescript. Ifanyuserinputnamescompiledtemplates,makesurethenamesarefilteredthroughthe
basename()function,whichremovespathcomponents,leavingjustthefile’sname. Finally,makesurethatincludeandrequireareonlyusedforactualPHPcode.Acommon mistakeistouseincludeorrequiretoloadplaintextdatalikeaheaderorafooter,something
Securing Your Applications
that’sbestdoneviareadfile().
Discontinueuseofeval()
As a rule of thumb, it’s also best to avoid eval(). In most cases, it is simply too difficult to validateandeventhesimplestmistakecanleadtocodeinjection.Ifyouabsolutelymustuse eval(),trytoensurethatthecontenttobeevaluateddoesn’tcontainanyvariablesthatoriginatewithorcanbemodifiedbyuserinput.
MindYourRegularExpressions Similartoeval(),thefunctionpreg_replace()withtheemodifiercanalsobeusedtoexecute codeoneachfoundpattern.Ifanapplicationreliesonthisuseofpreg_replace(),consider changing it to preg_replace_callback() or make sure that the replacement string is not affectedbyusersinputinanyway.Theformersolutionusesapre-definedfunctiontoreplace thestring,eliminatingthepossibilityofinjectingcode;thelattermandateensuresthatonlythe specifiedcodeisexecutedandnothingmore.
WatchOutforDynamicNames Themostdifficultthingtodetectduringasecurityauditistheuseofdynamicvariable,function,method,andpropertynames.Inthoseinstances,theentitytoexecuteorevaluatemay changedependingonthesituation.Locatingtheseinstancescanbedoneinpartbyjudicious useofgrep,butsomeusesrequiremanualcodeanalysisanyway. Tobegin,lookfordynamicfunctionandmethodnames,asthesepresentthemostdanger.
grep –rI “\$[_a-zA-Z][A-Za-z0-9_]*[:space:]*(“ /home/user/app/*
The regular expression in the grep command above searches for a dollar sign followed by a validPHPidentifier(whichconsistsofaunderscoreoraletterasthefirstcharacter,followedby anynumberofletters,numbersandunderscores),followedbyanynumberofoptionalwhite spaces,andendingwitharightparenthesis.Thisshoulddetectmostdynamicfunctionand methodcalls,exceptforthosewherethevariableholdingthenameandtheparenthesisareon separatelines,asgrepworksonelineatatime.
185
186
Securing Your Applications
$foo(“val”); // will be detected $foo (“val”); // will not be
(YoucancertainlyuseaslightlymorecomplexregularexpressionandPerltofindmorevariationsofdynamically-namedmethodsandfunctions.) If detecting dynamic function usage was hard, detecting dynamically used variables is evenharder.Therearetwopossiblesyntaxes:$$fooand${$foo.”str”}.Tomakethingseven morefun,youcanstackdynamicreferencesontopofoneanother,formingexpressionssuch as$$$baz.Parsingthesevariationswithregularexpressionsisn’ttheeasiestoftasksandislikely toresultinmorethenonefalsepositive.Buthere’soneattempt:
# Basic syntax grep –rI “\$\$[_a-zA-Z][A-Za-z0-9_]*” /home/user/app/* # Curly braces syntax grep –rI “\${[^}]\+}”/home/user/app/*
Thefirstregexisprettysimpleandisquiteaccurate,asitsimplylooksfortwodollarsignsfollowedbyavalidPHPidentifier,whichisaperfectdefinitionofasimpledynamicvariable. Thesecondregexisintendedtocapturecurlybracedefinitionofvariablesisnotasreliable, since the expression used to compose the variable can be virtually anything.The only restrictionpossibleisthatthematchedportionstartswithadollarsign($),followedbyaleft brace({),followedbyasequenceofcharactersasidefromtherightbrace(}),andterminated byarightbrace.Whilethisregularexpressioncapturesmostdynamicvariables,itnonetheless failstodetectthingslike${“fo”.${foo}.”o”},whichiscertainlyunusuallooking,butstillis perfectlyvalidcode.
MinimizetheUseofExternalCommands Thelastiteminthesecurityauditisobscure,butstillimportant. AfractionofPHPapplicationscallexternalapplications.Evenifitprovidesnothingelse than piece of mind, validate all user input passed to functions such as exec(), system(), popen(),passthru(),andproc_open()(andothersthatrunexternalcommands).
Securing Your Applications
You can find command execution quite quickly, once again thanks to the ever helpful grep.
# Procedural External Command Execution Detection grep –i “\(exec\|system\|popen\|passthru\|proc_open\)[:space:]*(“ *.php # Detecting Backticks grep “\`[^\`]\+\`”
Thefirstgrepcommanddetectsallcommandexecutionsperformedviasomeoftheapropos functionsinPHP.Thesecondgrepcommanddetectsthebackticksconstruct(`…`),whichworks inanidenticalmannertoshell_exec(),butismucheasiertotype.Whendetectingbackticks, it’simportanttoescapetheminsidetheregularexpression,otherwisetheymayacquirespecial meaningonthecommand-line. There’salsothemail()function,whichonUnixandLinuxinstallationsofPHPallowsarbitraryargumentstobepassedtothebinaryviaafifthargument.Thereisnoquickwaytocount arguments, especially to mail(), whose arguments may be split across many links. Instead, performabasicsearchforthefunctionandmanuallyexamineeachoccurrencetodetermine ifthefifthargumentisused.Ifso,checkthatthecodeproperlyescapestheargumentviaescapeshellarg().
ObfuscateandPrepareaSandbox Thetwoverylaststepsinthesecurityprocessareoptional,butcanbothslowanattackand detectsuchprovocation. First,obfuscateyouradministrativecontrols.Thisprovidessomesmallmeasureofprotection against automated vulnerability scanning scripts, which are designed to only check specificlocationsorlookforcertainstrings. Next,putasandboxinplaceofalldisplacedpanelssoanattackercanfind“something”. Ofcourse,thatsomethingcantrytodeterminethenatureoftheattackandalertthesystem administratorthatsomethingnefariousmaybeoccurring.Asandboxcanalsoactasavirtual tar pit, delaying the attack by adding delays, recursive URLs to capture spiders, and so on. Hopefully,bythetimeancrawlsoutofthetarpit,afixfortheproblem—ifonedoesexist—will alreadyhavebeenapplied.
187
Index
Symbols $_COOKIE25,27,45,49,114,173,175.See alsosuperglobals;Seealsosuperglobal variables;Seealsocookies $_ENV25,45.Seealsosuperglobals;Seealsosuperglobalvariables $_FILES37,38,39,41,42,43,46,47,101,102, 103.Seealsofileuploads arrayelements38 $_GET25,27,28,30,43,44,45,91,92,93, 98,182,183.Seealsosuperglobals;See alsosuperglobalvariables $_POST25,27,35,36,37,45,159,182.See alsosuperglobals;Seealsosuperglobal variables $_REQUEST27,181,182.Seealsosuperglobals;
Seealsosuperglobalvariables avoidingusage181 comparedtoregisterglobals27 $_SERVER25,45,66,67,68,82,90,116,129, 130,131,168,169,172,173,174,175, 176.Seealsosuperglobals;Seealsosuperglobalvariables andscriptlocation67 andXSS66 $HTTP_GET_VARS23 usinginplaceofregisterglobals23 $HTTP_POST_VARS usinginplaceofregisterglobals23 .htaccess27,37,43,81,175 usingtostoresecuredata82 /etc/passwd91 /tmp38,41,42,85,115,123,144,148,149,150
190
Index
[[:alnum:]]32 [[:alpha:]]32,33 \w(PCREpatterns)33 __FILE__68
A addcslashes()79 addslashes()43,44,45,77,79,103,184 allow_url_fopen89,90 andrecursion45 Apache24,27,67,68,81,82,84,89,90,91,95, 105,123,124,137,145,146,161,171 andfilepermissions105 apache_child_terminate()95 application securing179 array_walk()45 arrays flattening45 preventingstealthentries25 stealthilyaddingentriesto24 auditing179 avatar validating50 awk181
B basename()39,46,92,102,103,184 usingtopreventfileuploadsecurityissues39 bin2hex()48 browsersignature usingtovalidatesessions129 bugs settinglimitstopreventrunawayinstances106
C caching preventingwithHTTPheaders128 casting22,28,29,30,33,78 numericdata28 limitations29 CGI139,145,146.SeealsoFastCGI
andfileaccessrestrictions139 asasecuritymechanism145 limitations146 checkbox validating36 chown105,106 chr()58,71 codeinjection83,87,89,94,95,97,98,101,146, 184,185 andregularexpressions97 avoidingdynamicpaths89 defined87 detectinglikelyinjectionpoints88 dynamicfunctionsandvariables95 pathvalidation88 usingconstants88 preventing184 remotefileaccess89 limiting90 registryapproach93 validatingfilenames91 commandinjection43,101,102,103,105 defined101 exploitingPATH104 filepermissions105 manufacturingrequests103 preventingrunawayprocesses106 resourceexhaustion102 constants accessingundefined26 andcasesensitivity26 asprotectionagainstregisterglobals25 scope26 usingforpathvalidation88 cookies22,27,53,66,69,114,115,117,118, 119,120,121,173,174 andHTTPS115 andsessions114 andXSS69 mixingsessionandlong-duration121 sessionsvs.long-duration121 usingexclusivelyforsessions118 usingtobanusers173 copy()50,144
Index
Cross-sitescripting.SeeXSS Crypt_HMAC48 ctype_alnum()31 usingtovalidatestrings31 ctype_alpha()31 usingtovalidatestrings31 cURL51,89,90 usingtosafelyrequestURLs51
discontinuinguseaspartofsecurityaudit185 securing94 executingexternalcommands101 exit()50,95 expirymechanisms120 externalcommands executing101 minimizinguseof186
D
F
define()25,26 DELETE73,74,77,78,126,128,184 DELETE(SQLstatement).SeeSQL:DELETE dl()146 doc2pdf101,102,104 dynamicfunctionsandvariables95 dynamicvariables detectingduringsecurityaudit185 examiningduringsecurityaudit185
FastCGI145,146 limitations146 usingtosecurefileaccess145 fileinfo40 usingtopreventuploadtrojans40 FILEINFO_MIME40 filesize()42 fileaccess CGIrestrictions139 problemswith135 securingreadaccess137 usingmanualencryption138 securingusingfilemasking147 securingwriteaccess140 usingfilesignatures142 worldreadable136 filenames andcommandinjection102 avoidingpredictable148 escaping103 manufacturinginvalid103 obscuring155 validating91 filepermissions105 andfileaccess135 worldreadable136 fileuploads37 accessing41 andmagicquotes46 browserlimitationswithMIMEtypes40 determiningtemporaryfilestoragedirectory38 disabling37 limitingfilesize37
E E_ALL24,94,180 E_NOTICE24 E_STRICT180 each()46 echo53,54,55,56,58,60,61,62,63,64,68,79, 82,92,94,96,110,136,167,169,170,175 empty()37 usingforwhitelistvalidation37 encoder.Seeencoding encoding toprotectfiles154 ENT_QUOTES55 error_reporting24,94,180.Seealsoerrorreporting errorreporting24,180 detectinguninitializedvariables24 enablingtoauditcode180 limitationsindetectinguninitializedvariables24 settinglevel24 escapeshellargs()103 escapeshellcmd()103 eval()
191
192
Index
movingfilestopreventaccessproblems41 preventingfilefrombeingoverwritten39 securing140 validation39 filesize42 finfo_file()40 finfo_open()40 fixation.Seesessions:fixation foreach24,35,36,44,45,46,93,129,136,149, 157,159,168,172,175 forms obfuscating158 randomizingfieldnames159 textfields maximumsize34
escapingquotes55 HTMLcomments removingtoobfuscateoutput161 HTTP_ACCEPT_*68 HTTP_REFERER67,90,116,126,130,131, 160,173,174.SeealsoURLs,referring HTTP_USER_AGENT68,129 HTTP_X_FORWARDED_FOR66,67,129,172 httpd.conf24,27,37,43,81,175 HTTPS usingtoobfuscatedatatransmission158 HTTPheaders usingtopreventcaching128
G
iconv34 ImageMagick109 images.Seealsouserinput,externalresources in_array()37,92 usingforwhitelistvalidation37 include_path88 ini_set()27 usingtodisableregisterglobals27 input.Seealsouserinput INSERT(SQLstatement).SeeSQL:INSERT ip2long()66,67 limitationsandworkarounds67 IPaddresses66,67,68,90,128,129,130,158, 169,170,171,172,173,174 andproxyservers66 limitationsinsandboxapplications171 usingtovalidatesessions128 is_link()148,149 is_numeric()29,30 andlocales29 vs.casting30 is_uploaded_file()41,42 isset()36,93 usingtovalidatestringlength36
garbagecollection.Seesessions:garbagecollection get_magic_quotes_gpc()44,45,47,74 getenv()82 getimagesize()40,41,50,66,90 limitationswithsafe_modeandopen_basedir41 usingtopreventtrojanuploads40 glob()92,149,155 Google50,91,97,160,169 gpc_order22 grep87,88,180,181,182,184,185,186,187 GRSecurity150
H hacker identifying169 profiling167 HashMessageAuthenticationCode48 determiningsizeofhashcode49 importanceofsecretkey49 protectingserializeddatawith48 usingPEARtogeneratehashcodes48 hijacking.Seeseesessions HMAC.SeeHashMessageAuthenticationCode honeypot.Seesandbox htmlspecialchars()54,55,57,61,68,80,97
I
J JavaScript35,50,53,54,55,61,63,65,66,67, 69,70,71,118,131,160,174,176,183
Index
usingforclient-sidevalidation35
L LC_ALL30,31 LC_CTYPE31,33 ls115,147,155
M magic_quotes.Seealsomagicquotes magic_quotes_gpc43,44,45,46,47,74,182.See alsomagicquotes magicquotes43 andfiles46 andMySQL44 andSQLinjections74 disabling182 efficiencyandperformanceconsiderations44 enablinganddisabling43 limitations43 limitationsinWindows46 normalization44 mail()187 man-in-the-middle-attacks114 MaxDB75 maxlength34,35 limitationswithtextareas35 mb_decode_numericentity()33 mb_ereg()33 mbstring32,33,34 combiningwithregularexpressions32,33 MD548,49,93,129,141,142,155,159 usingtoobscurefilenames155 usingtosecurewriteaccesstofiles141 vs.otherhashingalgorithms49 md5_file()141 mhash()48 MHASH_SHA148,49 mod_gzip161 mod_security103 move_uploaded_file()41,42 MySQL34,43,44,74,75,84,85,123,124,125, 137,183 connectingusingINIdirectives82
enablingsafemode83 querystacking74 mysql_pconnect()84 mysql_query()74 mysql_real_escape_string()44,74
N nice(Unix/Linuxutility)106 numericdata.Seealsouserinput,numericdata
O obfuscation code techniques157 obscurity,securityby definition153 obscurity,securitythrough encoding154 hidingfiles154 namingconventions155 obfuscatingcode157 obfuscatingformfields158 obfuscatingoutputwithcompression161 POSTvs.GET160 removingHTMLcomments161 removingsoftwareidentification162 roleinsecurityaudit187 scenariosofapplication154 usingcompiledtemplates156 opcodecache93,94 open_basedir41,42,106,122,139,140,144 limitations106 usingtolimitreadaccesstofiles139 openbasedirectory.Seeopen_basedir Oracle76,84,123
P parse_url()51 passthru()186 PATH67,68,104,105,176 exploitingforcommandinjection104 path
193
194
Index
avoidingdynamictopreventcodeinjection89 validating88 PATH_INFO67,68,176 andXSS67 PATH_TRANSLATED67,68 andXSS67 PCRE33,34,97.Seealsoregularexpressions codeinjectionvia97 PEAR48,139 persistentconnections84 pg_escape_bytea()75 php.ini22,25,27,37,43,89,138,146,182 register_globaldirectives22 PHP_SELF67,68 andXSS67 phpBB97 PHPencoders137 usingtosecurereadaccesstofiles137 popen()186 POST vs.GET160 post_max_size38 PostgreSQL34,74,75,76,84,85,123,137 preg_replace_callback()57,58,63,185 proactiveprotection defined165 legalramifications165 proc_open()186
Q queries caching85 unbuffered84 QUERY_STRING68,175 querystacking74
R radio validating36 radiobutton.Seeradio realpath()149 recode34 recursion45,46
flatteningarrays45 problemswithstripslashes()45 referer_check130.SeealsoURLs:referring referrer.SeeURLs:referring regeneration.Seesessions:regeneration regex32,33,34,62,63,97,186.Seealsoregular expressions register_globals24,87,88,175,180,181.See alsoregisterglobals andcodeinjection87 registerglobal replacingduringcodeaudit180 registerglobals22,23,24,25,27,94,96,180, 181 andconstants25 andhostingproviders25 anduninitializedvariables23 disabling27 explained22 php.inidirectives22 problems22 regularexpression examiningduringsecurityaudit185 regularexpressions32,33,34,58,59,156,186. Seealsoregularexpressions asanalternativetoctype32 codeinjectionvia97 handlingmulti-bytestringswithPCRE33 PCREvs.POSIX34 performance33 usingtovalidatestrings32 REMOTE_ADDR66,129,169,171,172 remotefiles.Seealsocodeinjection,remotefile access limitingaccessto90 resourceexhaustion viacommandinjection102 runawayinstances preventingbysettinglimits106
S safe_mode41,42,83,143,144 usingtosecurewriteaccesstofiles143
Index
sandbox banninghackerswithcookies173 capturinguserinput174 defined166 determiningroutinginformation170 identifyingthesourceofanattack169 limitationsofIPaddresstracking171 passwordtracking167 roleinsecurityaudit187 usingreferrertostophackers173 scarletletter usingtobanhackers173 select validating36 selectionboxes.Seeselect SELECT(SQLstatement).SeeSQL:SELECT selfexploitation117 serializeddata limitationswhenusedasuserinput47 protectingwithHMAC48 validation47 session.Seesessions imageexploits116 session.gc_divisor120,121 session.gc_probability120,121 session.use_only_cookies118 session_regenerate_id()118,119,126,127 session_save_path()122 session_set_save_handler()123 sessions42,69,113,115,116,117,118,119,120, 121,122,123,124,125,126,128,130, 144,149,160 allowingonlytheuseofcookies118 compromisingbyaccessinganexternalsite116 cookies114 creatingcustomhandlers123 encryptingwithHTTPS115 expiringautomatically120 fixation117 preventing118 garbagecollection120 IDrotation126 IPvalidation128 man-in-the-middleattacks114
mixingsessioncookiesandlong-durationcookies121 referrervalidation130 regeneration118 securing113 securingstorage122 selfexploitation117 server-sideweaknesses115 theft119 validatingthroughbrowsersignatures129 SetEnv82 setlocale()31 sha1()141 SHA25649 sharedhosting146 shell_exec()102,144,169,187 shouldersurfing116 sleep()176 usingtostophackers176 softwareidentification removing162 SQL.SeealsoSQLinjection DELETE24,30,73,74,77,78,126,128,184 enablingsafemode83 improvingsecurity183 INSERT76,83,84,85,125,128,141,142,168 protectingauthenticationdata80 SELECT73,74,76,77,78,79,125,128,168, 172,183 unbufferedqueries84 UPDATE83,84,85,125,128 sql.safe_mode83 SQLite74,75,123,137,168,169,183 SQLinjection43,66,73,74,76,77,79,80,83, 95,96,125,160,183,184 andperformance83.SeealsoSQLinjection, LIKEclausesandperformance querycaching85 unbufferedqueries84 usingpersistentconnections84 defined73 encodingstringsusingBase6476 errorhandling79 escapingmulti-bytestrings75
195
196
Index
example73 LIKEclausesandperformanceconsiderations 78 managingdatabasepermissions83 preventingusingpreparedstatements75 alternatives76 preventingwithnativeescapingfunctions74 limitations77 protectingauthenticationdata80 protectingconnectiondatausingINIdirectives 82 querystackinginMySQL74 usingdynamicvariablestocause95 stat()105,142,143,144 strcmp()131 String.fromCharCode()70 strings.Seealsouserinput,strings locale-dependentvalidation settinglocale31 validatinglengthwithisset()36 strip_quotes()45 limitationsrelatedtorecursion45 strip_tags()60,61,62,63,64 andattributes62 limits61 preventingtagsfrombeingstripped61 stripslashes()44,45 usingto“undo”magicquotes44 strlen()35 strncmp()131 strstr()131 strtok()141 StructuredQueryLanguage.SeeSQL substr()49 superglobals.Seealsosuperglobalvariables superglobalvariables25.Seealso$_COOKIE; Seealso$_ENV;Seealso$_ENV;See also$_GET;Seealso$_POST;Seealso$_ SERVER;Seealsosuperglobals asanalternativetoregisterglobals25 switch()96 Sybase75 symboliclink.Seesymlink symlink147,148,149,150
checkingfor149 system()186
T tarpit defined176 templates76,91,140,142,156,157,184 usingtoobscurefiles156 temporarydirectory38,41,42,43,115,122,147, 148 usingcustomforsessionstorage122 Tiger16049
U UniformResourceLocator.SeeURL unlink()50,148,149 unset()46 UPDATE(SQLstatement).SeeSQL:UPDATE upload_max_filesize37,38 upload_tmp_dir38 URLs manufacturingforXSS55 referring67 usingtohijacksessions116 usingtovalidatesessions130 spoofingwithJavaScript71 usingtotracksessions115 validating50,65 verifyingusingparse_url()51 URLs,referring useinsandboxes173 usereducation131 userinput capturingforanalysis174 externalresources49 dangersinherentinaccessingexternalURLs 50 validating50 fileuploads.Seefileuploads IPaddresses validating66 usingip2long()66 numeric
Index
locale-dependentvalidation29 numericdata28 serializeddata.Seeserializeddata strings30 locale-dependentvalidation31 limitations32 onFreeBSDandWindows31 magicquotes.Seemagicquotes validation usingregularexpressions32 trappingHTMLentityabuse56,65 validating28 contentsize34 usingisset()36 replacingtainteddatawithcleandata30 usingJavaScript35 limitations35 validation whitelist36
V variables_order22
W Whitelistvalidation36 whois169,170
X XSS53,54,55,61,63,64,65,66,68,69,70,95, 96,131,160,183 andcharactersets57 andcookies69 andenvironmentvariables66 HTTP_REFERER67 HTTP_USER_AGENT68 PHP_SELF,PATH_INFOandPATH_TRANSLATED67 QUERY_STRING68 andformdatatheft70 andHTMLentities56 usingregularexpressionstodecode58 andURLsessions69
defined53 directaction53 encodingattributes54 handlingvalidattributes63 usingregularexpressions62 encodingdatatoprevent54 escapingquotes55 preventing183 preventingbyexclusion60 storedaction53 usingdynamicvariablestocause95 usingJavaScripttoalterapage’scontent71 usingnumberstoproduce70
Z Zend36,137,138,154 Zlib161 usingtoobfuscateoutput161
197